-
Notifications
You must be signed in to change notification settings - Fork 94
Expand file tree
/
Copy pathaction.yaml
More file actions
164 lines (148 loc) · 6.75 KB
/
action.yaml
File metadata and controls
164 lines (148 loc) · 6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
name: "App Run Local Environment"
description: "Starts the frontend server and/or localtest"
inputs:
run-frontend:
description: "Whether to serve frontend with http-server"
required: true
run-localtest:
description: "Whether to start localtest with studioctl"
required: true
frontend-port:
description: "Port for frontend server"
required: false
default: "8080"
app-run-targets-json:
description: "JSON array of app run targets. Each target requires path and can include image for prebuilt app images."
required: false
runs:
using: "composite"
steps:
- name: Validate inputs
run: |
if [[ "${{ inputs.run-frontend }}" != "true" && "${{ inputs.run-frontend }}" != "false" ]]; then
echo "Error: run-frontend must be 'true' or 'false', got: ${{ inputs.run-frontend }}"
exit 1
fi
if [[ "${{ inputs.run-localtest }}" != "true" && "${{ inputs.run-localtest }}" != "false" ]]; then
echo "Error: run-localtest must be 'true' or 'false', got: ${{ inputs.run-localtest }}"
exit 1
fi
# Validate frontend-port is a valid port number (1-65535)
if [[ ! "${{ inputs.frontend-port }}" =~ ^[0-9]+$ ]] || [[ "${{ inputs.frontend-port }}" -lt 1 ]] || [[ "${{ inputs.frontend-port }}" -gt 65535 ]]; then
echo "Error: frontend-port must be a valid port number (1-65535), got: ${{ inputs.frontend-port }}"
exit 1
fi
shell: bash
- name: Setup studioctl
if: ${{ inputs.run-localtest == 'true' }}
uses: ./.github/actions/setup-studioctl
- name: Set up Docker Buildx
if: ${{ inputs.run-localtest == 'true' }}
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Login to GitHub Container Registry
if: ${{ inputs.run-localtest == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }}
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Start localtest
if: ${{ inputs.run-localtest == 'true' }}
working-directory: ${{ github.workspace }}
shell: bash
env:
STUDIOCTL_REGISTRY_CACHE_WRITE: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
run: |
studioctl env up --detach
- name: Download the built frontend
if: ${{ inputs.run-frontend == 'true' }}
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
with:
name: app-frontend-dist
path: src/App/frontend/dist
- name: Setup Node.js
if: ${{ inputs.run-frontend == 'true' }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version: '22'
cache: 'yarn'
cache-dependency-path: yarn.lock
- name: Install dependencies
if: ${{ inputs.run-frontend == 'true' }}
working-directory: src/App/frontend
run: yarn install --immutable
shell: bash
- name: Start frontend server
if: ${{ inputs.run-frontend == 'true' }}
working-directory: src/App/frontend
run: |
echo "Starting frontend server on port ${{ inputs.frontend-port }}..."
yarn run serve -p ${{ inputs.frontend-port }} > frontend-server.log 2>&1 &
echo $! > frontend-server.pid
echo "Frontend server started with PID $(cat frontend-server.pid)"
shell: bash
- name: Wait for frontend server to be ready
if: ${{ inputs.run-frontend == 'true' }}
run: |
echo "Waiting for frontend server to be ready..."
timeout 60 bash -c 'until curl -f http://localhost:${{ inputs.frontend-port }} > /dev/null 2>&1; do sleep 2; done'
echo "Frontend server is ready on http://localhost:${{ inputs.frontend-port }}"
shell: bash
- name: Start app containers
if: ${{ inputs.run-localtest == 'true' && inputs.app-run-targets-json != '' }}
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
APP_RUN_TARGETS_JSON: ${{ inputs.app-run-targets-json }}
RUN_FRONTEND: ${{ inputs.run-frontend }}
STUDIOCTL_REGISTRY_CACHE_WRITE: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
with:
script: |
const workspace = process.env.GITHUB_WORKSPACE;
const maxParallelStarts = 4;
const runFrontend = process.env.RUN_FRONTEND === 'true';
const targetsJson = process.env.APP_RUN_TARGETS_JSON;
let targets;
try {
targets = JSON.parse(targetsJson);
} catch (error) {
throw new Error(`app-run-targets-json is not valid JSON: ${error.message}`);
}
if (!Array.isArray(targets)) {
throw new Error('app-run-targets-json must be a JSON array.');
}
function targetRunArgs(path, image) {
const args = ['run', '--mode', 'container', '--detach', '--random-host-port', '--path', path];
if (runFrontend) {
args.push('--dev-frontend');
}
if (image) {
args.push('--image-tag', image, '--pull', '--skip-build');
}
return args;
}
function validateTarget(target) {
if (!target || typeof target !== 'object' || Array.isArray(target)) {
throw new Error('Each app run target must be an object.');
}
if (typeof target.path !== 'string' || target.path === '') {
throw new Error('Each app run target requires a non-empty path.');
}
if (target.image !== undefined && (typeof target.image !== 'string' || target.image === '')) {
throw new Error('App run target image must be a non-empty string when set.');
}
return target;
}
const validTargets = targets.map(validateTarget);
if (validTargets.length === 0) {
throw new Error('No app containers were started.');
}
let nextTarget = 0;
async function startNext(workerId) {
while (nextTarget < validTargets.length) {
const target = validTargets[nextTarget++];
core.info(`Worker ${workerId}: starting ${target.path}`);
await exec.exec('studioctl', targetRunArgs(target.path, target.image), { cwd: workspace });
}
}
const workerCount = Math.min(maxParallelStarts, validTargets.length);
await Promise.all(Array.from({ length: workerCount }, (_, index) => startNext(index + 1)));