-
Notifications
You must be signed in to change notification settings - Fork 6
360 lines (322 loc) · 14.8 KB
/
Copy pathdeploy.yml
File metadata and controls
360 lines (322 loc) · 14.8 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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
name: Build, Deploy & Publish
on:
push:
branches: [master]
workflow_dispatch:
concurrency:
group: deploy
cancel-in-progress: true
permissions:
contents: write
pages: write
env:
# Node 24 ships npm 11.x which matches the developer-machine npm that
# authors lockfile updates. Pinning Node 22 (npm 10.x) caused the
# lockfile-sync preflight to fail on every push because npm 10 and
# npm 11 disagree on transitive entries (`@tailwindcss/oxide-wasm32-wasi`
# bundled deps) and `peer: true` markers on optional peers.
NODE_VERSION: '24'
# SERVER_IP + APP_DIR resolved from GitHub Actions repo secrets so
# deploy topology isn't encoded in the public workflow file. Set both
# at repo → Settings → Secrets and variables → Actions → Secrets.
SERVER_IP: ${{ secrets.SERVER_IP }}
APP_DIR: ${{ secrets.APP_DIR }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
# Verify lockfiles are in sync with package.json. Emits ANNOTATIONS
# only (no hard fail) because re-resolving `npm install
# --package-lock-only` on CI's Linux x64 can legitimately produce
# drift against a lockfile authored on macOS arm64 — optional
# platform-specific deps (e.g. `@tailwindcss/oxide-wasm32-wasi`
# bundled children, native `fsevents` vs `@rollup/rollup-linux-*`)
# resolve differently per platform even when package.json hasn't
# changed. The authoritative check is `npm ci` below: it hard-fails
# on REAL desync (missing/added top-level deps) but tolerates
# platform churn. This preflight catches common dependabot-style
# real desync and surfaces it as a GitHub annotation for the PR
# author to fix, without blocking the deploy when only platform
# variance is in play. Recipe was added originally for PR #264,
# #310; kept as warn-only after #312/#313/#314 showed platform
# drift false-positives.
- name: Verify lockfiles are in sync with package.json (warn-only)
run: |
set +e
check() {
local dir="$1"
local label="$2"
pushd "$dir" >/dev/null
npm install --package-lock-only --no-audit --no-fund --ignore-scripts >/dev/null 2>&1
if ! git diff --quiet package-lock.json; then
echo "::warning file=${dir}/package-lock.json::${label} package-lock.json drift detected (may be platform-specific — npm ci will fail if real desync)."
echo "::warning file=${dir}/package-lock.json::To resync: cd ${dir} && npm install --package-lock-only && git add package-lock.json && git commit -m 'chore(deps): sync lockfile' && git push"
echo ""
echo "--- drift in ${dir}/package-lock.json (warn) ---"
git diff package-lock.json | head -40
# Restore the committed lockfile so the subsequent npm ci
# runs against the committed state, not the re-resolved one.
git checkout -- package-lock.json
fi
popd >/dev/null
}
check "." "Root"
check "src/dashboard" "Dashboard"
- name: Guard against pnpm-workspace contamination in lockfiles
run: |
set -e
fail=0
for f in package-lock.json src/dashboard/package-lock.json; do
if [ ! -f "$f" ]; then continue; fi
if grep -q 'node_modules/\.pnpm/' "$f"; then
echo "::error file=$f::Lockfile contains 'node_modules/.pnpm/' substring. This means it was authored from inside a pnpm workspace and references parent paths that do not exist in CI. To repair: copy package.json to a directory outside any pnpm workspace, run 'npm install' there, copy the resulting package-lock.json back, commit, push."
fail=1
fi
done
exit $fail
- name: Install dependencies
run: npm ci
- name: Build TypeScript (engine + runtime)
run: npm run build
- name: Install dashboard dependencies
run: cd src/dashboard && npm ci
- name: Build dashboard
run: npm run dashboard:build
env:
# Vite inlines these at build time as `import.meta.env.VITE_*`.
# Empty values short-circuit Analytics.tsx so the build still
# works without secrets configured (local fork / fresh clone).
VITE_GA_MEASUREMENT_ID: ${{ secrets.GA_MEASUREMENT_ID }}
VITE_CLARITY_PROJECT_ID: ${{ secrets.CLARITY_PROJECT_ID }}
- name: Run tests
run: npm test || true
- name: Check doc examples compile against package
run: npm run check:doc-examples
# The build job's checkout has the dev-floor version (0.9.0) from
# package.json. The publish job auto-bumps to 0.9.${run_number} in
# its own checkout, so the build job never sees the published
# version. typedoc has `includeVersion: true` and reads
# package.json directly, which is why early v0.8 deploys advertised
# "v0.8.0" while npm shipped 0.8.${prev_run_number}.
#
# Query the npm registry for the latest published version and use
# it for typedoc. Most commits are UI-only (publish skipped) — for
# these, docs match npm exactly. Library commits drift docs behind
# by exactly one publish (build runs npm view BEFORE publish bumps
# the patch), and the next workflow run converges. Falling back to
# the run-number form when npm is unreachable so the docs deploy
# never produces a literal "0.9.0" again.
- name: Sync version for docs
run: |
LATEST=$(npm view paracosm version 2>/dev/null || true)
if [ -z "$LATEST" ]; then
LATEST="0.9.${{ github.run_number }}"
echo "npm view failed; falling back to ${LATEST}"
fi
node -e "const p=require('./package.json'); p.version='${LATEST}'; require('fs').writeFileSync('./package.json', JSON.stringify(p, null, 2) + '\n')"
echo "Docs will report version: ${LATEST}"
- name: Build API docs (TypeDoc)
run: |
npm run docs
cp assets/docs-theme/paracosm-override.css docs/api/assets/paracosm-override.css
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build
path: |
dist/
src/dashboard/dist/
docs/api/
assets/
scenarios/
scripts/
package.json
package-lock.json
src/
config/
typedoc.json
tsconfig.json
tsconfig.build.json
.env.example
retention-days: 1
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download build artifacts
uses: actions/download-artifact@v8
with:
name: build
path: build/
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H ${{ env.SERVER_IP }} >> ~/.ssh/known_hosts
- name: Deploy to Linode
run: |
SSH="ssh -i ~/.ssh/deploy_key root@${{ env.SERVER_IP }}"
# Sync build to server.
#
# --delete removes paths on the server that aren't in the
# artifact so old JS bundles + orphaned docs clean up on
# each deploy. The excludes below are paths that DO NOT
# belong in the artifact but MUST survive a deploy:
# .env — hand-authored, holds API keys + caps.
# node_modules — installed separately via npm ci.
# data/ — SQLite session store for cached runs.
# Without this exclude, every deploy
# wipes /opt/paracosm/data/sessions.db
# because rsync sees it as "not in
# source → delete it." The server
# creates data/ on first boot and
# owns its lifecycle from there.
# .event-buffer.json — transient SSE event buffer, written
# per-run so the dashboard can rehydrate
# after a restart. Regenerable.
# logs/ — runtime logs (pm2 stdout/stderr).
# output/ — RunArtifact JSONs persisted per
# completed sim. data/runs.db points
# at these files via record.artifactPath;
# without this exclude, every deploy
# wiped output/*.json and Library tab
# drawers showed "Artifact file
# unreadable" 410 errors for every run
# that survived in runs.db across the
# deploy. The server's run-artifact
# writer owns the file lifecycle.
rsync -azP --delete \
--exclude='.env' \
--exclude='node_modules' \
--exclude='data/' \
--exclude='.event-buffer.json' \
--exclude='logs/' \
--exclude='output/' \
-e "ssh -i ~/.ssh/deploy_key" \
build/ root@${{ env.SERVER_IP }}:${{ env.APP_DIR }}/
# Install production deps and restart
$SSH << 'REMOTE'
set -e
cd /opt/paracosm
# Install production dependencies
npm ci --omit=dev 2>/dev/null || npm install --omit=dev
# Restart with pm2
pm2 delete paracosm 2>/dev/null || true
pm2 start "npx tsx src/cli/serve.ts" \
--name paracosm \
--max-memory-restart 3G \
--env production
pm2 save
echo "Paracosm deployed and running"
REMOTE
docs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v8
with:
name: build
path: build/
- name: Deploy API docs to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: build/docs/api
cname: docs.agentos.sh
publish:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
steps:
- uses: actions/checkout@v4
with:
# Full history needed so scripts/generate-changelog.mjs can walk
# every package.json version boundary. Prior requirement was
# only the previous-commit diff (fetch-depth: 2) for library-
# change detection.
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
registry-url: https://registry.npmjs.org
# Skip the rest of the publish job when only the dashboard, docs,
# CI workflows, or other non-library files changed. The npm package
# ships dist/engine, dist/runtime, and dist/cli (server bits used by
# `npm run dashboard`). Dashboard React code is bundled at build time
# but not part of the public TS API surface — UI-only commits should
# NOT cut a new package version (was producing a release per UI tweak).
- name: Detect library changes
id: changes
run: |
# If we don't have a previous commit (initial push), publish.
if ! git rev-parse HEAD~1 >/dev/null 2>&1; then
echo "library_changed=true" >> "$GITHUB_OUTPUT"
echo "First commit — proceeding with publish."
exit 0
fi
CHANGED=$(git diff --name-only HEAD~1 HEAD)
echo "Changed files:"
echo "$CHANGED"
# Files that affect the published package surface
if echo "$CHANGED" | grep -qE '^(src/engine/|src/runtime/|src/cli/(serve|server-app|sim-config|cli-run-options|compile|run|run-a|run-b|pair-runner|custom-scenarios|types)\.ts|src/cli/(serve|server-app|sim-config|cli-run-options|compile|run|run-a|run-b|pair-runner|custom-scenarios|types)/|package\.json|tsconfig\.build\.json|LICENSE)'; then
echo "library_changed=true" >> "$GITHUB_OUTPUT"
echo "Library code changed — will publish."
else
echo "library_changed=false" >> "$GITHUB_OUTPUT"
echo "Only dashboard/docs/CI changed — skipping npm publish + GitHub release."
fi
- name: Install dependencies
if: steps.changes.outputs.library_changed == 'true'
run: npm ci
- name: Build
if: steps.changes.outputs.library_changed == 'true'
run: npm run build
- name: Auto-version from run number
if: steps.changes.outputs.library_changed == 'true'
id: version
run: |
# Use major.minor from package.json + GitHub run number as patch
# Always unique, never collides, no commit-back needed
CURRENT=$(node -p "require('./package.json').version")
IFS='.' read -r MAJOR MINOR _ <<< "$CURRENT"
NEXT="${MAJOR}.${MINOR}.${{ github.run_number }}"
node -e "const p=require('./package.json'); p.version='${NEXT}'; require('fs').writeFileSync('./package.json', JSON.stringify(p, null, 2) + '\n')"
echo "version=$NEXT" >> $GITHUB_OUTPUT
echo "Publishing ${NEXT} (run #${{ github.run_number }})"
- name: Generate CHANGELOG and release notes
if: steps.changes.outputs.library_changed == 'true'
run: node scripts/generate-changelog.mjs
- name: Commit CHANGELOG if changed
if: steps.changes.outputs.library_changed == 'true'
run: |
if ! git diff --quiet CHANGELOG.md; then
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add CHANGELOG.md
git commit -m "chore: update CHANGELOG"
git push origin HEAD:master
else
echo "CHANGELOG unchanged; no commit-back."
fi
- name: Publish to npm
if: steps.changes.outputs.library_changed == 'true'
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub Release
if: steps.changes.outputs.library_changed == 'true'
run: |
gh release create "v${{ steps.version.outputs.version }}" \
--title "v${{ steps.version.outputs.version }}" \
--notes-file release-notes.md \
--latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}