mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-11 22:50:06 +00:00
ci: Refactor codespace previews script-base (#39042)
This commit is contained in:
@@ -93,7 +93,7 @@ needs `gh` with the codespace scope, the same as `pnpm session`.
|
||||
present. `preview:debug` sets `N8N_LOG_LEVEL=debug`. Adding or removing one
|
||||
re-serves the box; it never creates or deletes one. From a laptop the labels
|
||||
apply the same way — `pnpm preview refresh <pr>` reads them from the PR. The
|
||||
toggles are defined in `scripts/preview-labels.mjs`; add new ones there.
|
||||
toggles are defined in `scripts/codespace-preview/preview-labels.mjs`; add new ones there.
|
||||
- **Configure the instance from a webhook.** A preview also reads extra
|
||||
environment from an n8n webhook, so a value can change without a commit. It
|
||||
needs the `CODESPACE_ENV_URL`, `CODESPACE_ENV_USER` and `CODESPACE_ENV_PASSWORD`
|
||||
@@ -108,7 +108,7 @@ needs `gh` with the codespace scope, the same as `pnpm session`.
|
||||
builds a new one.
|
||||
- **A PR from a fork gets no preview.** A codespace's token is scoped to
|
||||
`n8n-io/n8n`, so it cannot check out a fork head.
|
||||
- **A PR that predates this tooling has no `scripts/preview-serve.mjs`.** The
|
||||
- **A PR that predates this tooling has no `scripts/codespace-preview/preview-serve.mjs`.** The
|
||||
serve step says so and stops; rebase the PR on master and retry.
|
||||
|
||||
## Agent worker (drive a session from n8n)
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"postStartCommand": "git config --global --get-all credential.helper 2>/dev/null | grep -qs gitcredential-refresh || git config --global --add credential.helper /usr/local/bin/gitcredential-refresh.sh",
|
||||
// Declarations only. All of these are set at the repository level, so Codespaces
|
||||
// injects them into every preview box whether or not they are listed here.
|
||||
// scripts/preview-serve.mjs reads them with codespaceSecret().
|
||||
// scripts/codespace-preview/preview-serve.mjs reads them with codespaceSecret().
|
||||
"secrets": {
|
||||
"N8N_LICENSE_ACTIVATION_KEY": {
|
||||
"description": "Sandbox licence key. The preview:enterprise label decides whether it reaches n8n."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* One-click sign-in for preview instances. Loaded through EXTERNAL_HOOK_FILES by
|
||||
* scripts/preview-serve.mjs, so it is never part of a packaged n8n.
|
||||
* scripts/codespace-preview/preview-serve.mjs, so it is never part of a packaged n8n.
|
||||
*
|
||||
* The route performs an ordinary login on the visitor's behalf: it calls
|
||||
* /rest/login with the preview instance's own seeded credentials and forwards the
|
||||
|
||||
@@ -254,7 +254,7 @@ parallelism). See the `--build-via-mcp` section in
|
||||
| `preview:debug` | `util-codespace-preview.yml` | Re-serves the instance with `N8N_LOG_LEVEL=debug` |
|
||||
|
||||
**Why:** A reviewer gets a running instance of the PR without a Docker build or a
|
||||
cloud deploy. The workflow calls `scripts/preview.mjs`, which keeps one codespace
|
||||
cloud deploy. The workflow calls `scripts/codespace-preview/preview.mjs`, which keeps one codespace
|
||||
for each PR (display name `preview/pr-<number>`) and shares port 5678 with the
|
||||
organization. A later push serves the new head in the same box. Removing the
|
||||
label, or closing the PR, deletes the box.
|
||||
@@ -266,7 +266,7 @@ scoped to `n8n-io/n8n` and cannot check out a fork head.
|
||||
|
||||
`up` and `refresh` take minutes, and an absent comment looks the same as a broken
|
||||
preview. So the comment goes up before the box work starts, as a checklist of the
|
||||
phases in `scripts/preview-phases.mjs`, and is edited for each phase and once a
|
||||
phases in `scripts/codespace-preview/preview-phases.mjs`, and is edited for each phase and once a
|
||||
minute after that. The final URL replaces it in place.
|
||||
|
||||
A comment **edit sends no notification** — only a create does. That is what makes a
|
||||
@@ -310,12 +310,12 @@ A `preview:*` label configures an instance that already exists, so adding or
|
||||
removing one re-serves the box instead of creating or deleting it. It does
|
||||
nothing on a PR without `codespace-preview`.
|
||||
|
||||
The vocabulary lives in `scripts/preview-labels.mjs`, which both ends import:
|
||||
The vocabulary lives in `scripts/codespace-preview/preview-labels.mjs`, which both ends import:
|
||||
`preview.mjs` turns the PR's labels into slugs, and `preview-serve.mjs` turns
|
||||
those slugs into environment inside the box. Add a toggle there, in one place.
|
||||
|
||||
Two things cross the gap, and both are shape-checked rather than trusted: a
|
||||
`preview:*` slug, and a phase key from `scripts/preview-phases.mjs`. The `gh
|
||||
`preview:*` slug, and a phase key from `scripts/codespace-preview/preview-phases.mjs`. The `gh
|
||||
codespace ssh` command is a shell string that appears in the box's process list, so
|
||||
a value is never passed through it — `preview:enterprise` resolves to a licence key
|
||||
inside the box, not on the runner.
|
||||
@@ -336,7 +336,7 @@ in this repository, so that is the set of people who already have write access.
|
||||
#### Preview environment from a webhook
|
||||
|
||||
A preview can also take environment from an n8n webhook we control, so a value
|
||||
can change without a commit and a merge. `scripts/preview-remote-env.mjs` fetches
|
||||
can change without a commit and a merge. `scripts/codespace-preview/preview-remote-env.mjs` fetches
|
||||
it, and `preview-serve.mjs` hands the result to the backend.
|
||||
|
||||
It needs three **Codespaces** secrets on `n8n-io/n8n`, again not Actions secrets:
|
||||
@@ -802,11 +802,11 @@ Scripts in `.github/scripts/`:
|
||||
| Script | Purpose | Called By |
|
||||
|---------------------------------|-------------------------------------------------------------------------|--------------------------------|
|
||||
| `codespace-preview.mjs` | Map a `pull_request` event or a manual operation onto a preview operation, comment the result | `util-codespace-preview.yml` |
|
||||
| `../../scripts/preview.mjs` | One codespace for each PR: `up`, `refresh`, `down`, `ls`. `--json` for CI | `codespace-preview.mjs`, developers |
|
||||
| `../../scripts/preview-remote-env.mjs` | Fetch extra environment for a preview from the webhook, inside the box | `../../scripts/preview-serve.mjs` |
|
||||
| `../../scripts/preview-phases.mjs` | The phase vocabulary and its one-line marker, so the runner, the box and the comment cannot drift | `codespace-preview.mjs`, `../../scripts/preview.mjs`, `../../scripts/preview-serve.mjs` |
|
||||
| `../../scripts/codespace-preview/preview.mjs` | One codespace for each PR: `up`, `refresh`, `down`, `ls`. `--json` for CI | `codespace-preview.mjs`, developers |
|
||||
| `../../scripts/codespace-preview/preview-remote-env.mjs` | Fetch extra environment for a preview from the webhook, inside the box | `../../scripts/codespace-preview/preview-serve.mjs` |
|
||||
| `../../scripts/codespace-preview/preview-phases.mjs` | The phase vocabulary and its one-line marker, so the runner, the box and the comment cannot drift | `codespace-preview.mjs`, `../../scripts/codespace-preview/preview.mjs`, `../../scripts/codespace-preview/preview-serve.mjs` |
|
||||
|
||||
`scripts/preview.mjs` is also the developer entry point (`pnpm preview up <pr>`).
|
||||
`scripts/codespace-preview/preview.mjs` is also the developer entry point (`pnpm preview up <pr>`).
|
||||
In `--json` mode stdout carries one line for each phase and then the report object,
|
||||
and all human progress goes to stderr. So a workflow can follow a run that also
|
||||
streams an in-box build log. The reader tells the two apart by the `url` field: the
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
// Drives a PR preview instance from CI. `scripts/preview.mjs` does the work; this
|
||||
// Drives a PR preview instance from CI. `scripts/codespace-preview/preview.mjs` does the work; this
|
||||
// script maps the pull_request event onto one of its operations and reports the
|
||||
// result back to the PR as a single, edited-in-place comment.
|
||||
//
|
||||
@@ -23,8 +23,8 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { PREVIEW_LABEL_PREFIX } from '../../scripts/preview-labels.mjs';
|
||||
import { PREVIEW_PHASES, createLineSplitter, phaseIndex } from '../../scripts/preview-phases.mjs';
|
||||
import { PREVIEW_LABEL_PREFIX } from '../../scripts/codespace-preview/preview-labels.mjs';
|
||||
import { PREVIEW_PHASES, createLineSplitter, phaseIndex } from '../../scripts/codespace-preview/preview-phases.mjs';
|
||||
import {
|
||||
ensureEnvVar,
|
||||
findCommentByMarker,
|
||||
@@ -50,7 +50,7 @@ export const WORKFLOW_URL =
|
||||
// What a manual run may ask for. `ls` is absent: it needs no PR and posts no comment.
|
||||
export const DISPATCH_OPERATIONS = ['up', 'refresh', 'down'];
|
||||
// Resolved against this file, so the script runs the same from any directory.
|
||||
const PREVIEW_SCRIPT = fileURLToPath(new URL('../../scripts/preview.mjs', import.meta.url));
|
||||
const PREVIEW_SCRIPT = fileURLToPath(new URL('../../scripts/codespace-preview/preview.mjs', import.meta.url));
|
||||
// Often enough to look alive, rarely enough that the edit history stays readable.
|
||||
const HEARTBEAT_MS = 60_000;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
"packageManager": "[email protected]",
|
||||
"scripts": {
|
||||
"test": "node --test --experimental-test-module-mocks ./*.test.mjs ./docker/*.test.mjs ./owners/*.test.mjs ./quality/*.test.mjs ./slack/*.test.mjs ./stale/*.test.mjs ../../scripts/*.test.mjs ../../scripts/licenses/*.test.mjs ../../scripts/mutation-health/*.test.mjs ../../.devcontainer/codespaces/*.test.mjs",
|
||||
"test": "node --test --experimental-test-module-mocks ./*.test.mjs ./docker/*.test.mjs ./owners/*.test.mjs ./quality/*.test.mjs ./slack/*.test.mjs ./stale/*.test.mjs ../../scripts/*.test.mjs ../../scripts/codespace-preview/*.test.mjs ../../scripts/licenses/*.test.mjs ../../scripts/mutation-health/*.test.mjs ../../.devcontainer/codespaces/*.test.mjs",
|
||||
"generate-sbom": "FETCH_LICENSE=true cdxgen -t pnpm --no-install-deps --profile license-compliance --spec-version 1.6 -o ../../sbom-source.cdx.json ../../compiled/",
|
||||
"enrich-sbom": "node ../../scripts/licenses/enrich-sbom.mjs ../../sbom-source.cdx.json",
|
||||
"render-licenses-md": "node ../../scripts/licenses/render-licenses-md.mjs ../../sbom-source.cdx.json ../../packages/cli/THIRD_PARTY_LICENSES.md ../../compiled/node_modules",
|
||||
|
||||
@@ -78,8 +78,7 @@ jobs:
|
||||
!scripts/agent-setup.mjs
|
||||
!scripts/backend-module/**
|
||||
!scripts/cloud-session.mjs
|
||||
!scripts/preview.mjs
|
||||
!scripts/preview-serve.mjs
|
||||
!scripts/codespace-preview/**
|
||||
!scripts/licenses/**
|
||||
!scripts/mutation-health/**
|
||||
!scripts/sync-agent-skill-links.mjs
|
||||
@@ -95,8 +94,7 @@ jobs:
|
||||
!scripts/agent-setup.mjs
|
||||
!scripts/backend-module/**
|
||||
!scripts/cloud-session.mjs
|
||||
!scripts/preview.mjs
|
||||
!scripts/preview-serve.mjs
|
||||
!scripts/codespace-preview/**
|
||||
!scripts/licenses/**
|
||||
!scripts/mutation-health/**
|
||||
!scripts/sync-agent-skill-links.mjs
|
||||
@@ -134,9 +132,7 @@ jobs:
|
||||
OWNERS
|
||||
scripts/*.test.mjs
|
||||
scripts/codespace-env.mjs
|
||||
scripts/preview.mjs
|
||||
scripts/preview*
|
||||
scripts/serve-ready.mjs
|
||||
scripts/codespace-preview/**
|
||||
scripts/licenses/**
|
||||
scripts/mutation-health/**
|
||||
cubic.yaml
|
||||
|
||||
+2
-2
@@ -34,8 +34,8 @@
|
||||
"dev-metrics:reset": "node scripts/dev-metrics/setup.mjs --reset",
|
||||
"clean": "turbo run clean",
|
||||
"session": "node scripts/cloud-session.mjs",
|
||||
"preview": "node scripts/preview.mjs",
|
||||
"preview:serve": "node scripts/preview-serve.mjs",
|
||||
"preview": "node scripts/codespace-preview/preview.mjs",
|
||||
"preview:serve": "node scripts/codespace-preview/preview-serve.mjs",
|
||||
"session:opencode": "node scripts/cloud-session.mjs --opencode",
|
||||
"session:shell": "node scripts/cloud-session.mjs --shell",
|
||||
"reset": "node scripts/ensure-zx.mjs && zx scripts/reset.mjs",
|
||||
|
||||
@@ -13,7 +13,7 @@ import { dirname, resolve } from 'node:path';
|
||||
import { setTimeout as sleep } from 'node:timers/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { codespaceSecret } from './codespace-env.mjs';
|
||||
import { codespaceSecret } from '../codespace-env.mjs';
|
||||
import { envForSlugs } from './preview-labels.mjs';
|
||||
import { phaseMarkerLine } from './preview-phases.mjs';
|
||||
import { fetchRemoteEnv } from './preview-remote-env.mjs';
|
||||
@@ -36,7 +36,7 @@ const OWNER_EMAIL = process.env.PREVIEW_OWNER_EMAIL ?? '[email protected]';
|
||||
const OWNER_PASSWORD = process.env.PREVIEW_OWNER_PASSWORD ?? 'PreviewInstance1';
|
||||
const HEAP_MB = process.env.PREVIEW_BUILD_HEAP_MB ?? '6144';
|
||||
// Resolve from this file, not cwd, so the tmux command lands in the right tree.
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const port = servePort();
|
||||
const healthPath = serveHealthPath();
|
||||
|
||||
@@ -171,10 +171,11 @@ const serveCommand = (pr, head) => {
|
||||
`git checkout --detach ${head.headRefOid}`,
|
||||
// The serve script comes from the checked-out PR head, so a PR cut before this
|
||||
// tooling landed on master does not have it. Say so, instead of letting pnpm
|
||||
// report a missing script.
|
||||
// report a missing script. A head from before the scripts moved into
|
||||
// scripts/codespace-preview still has it at the old path.
|
||||
// An `||` here would also fire when an earlier step in this && chain failed,
|
||||
// reporting the wrong cause. `if` keeps the test self-contained.
|
||||
'if [ ! -f scripts/preview-serve.mjs ]; then echo "This PR predates the preview tooling. Rebase it on master and retry."; exit 1; fi',
|
||||
'if [ ! -f scripts/codespace-preview/preview-serve.mjs ] && [ ! -f scripts/preview-serve.mjs ]; then echo "This PR predates the preview tooling. Rebase it on master and retry."; exit 1; fi',
|
||||
// After the guard above: a PR that predates the tooling must not tick off
|
||||
// "Install dependencies" and then stop.
|
||||
...marker('install'),
|
||||
@@ -5,7 +5,7 @@ import { execFileSync } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { setTimeout as sleep } from 'node:timers/promises';
|
||||
|
||||
import { codespaceName, forwardingDomain } from './codespace-env.mjs';
|
||||
import { codespaceName, forwardingDomain } from '../codespace-env.mjs';
|
||||
|
||||
const CODESPACES_DIR = '/workspaces/.codespaces';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// node --test scripts/serve-ready.test.mjs
|
||||
// node --test scripts/codespace-preview/serve-ready.test.mjs
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync } from 'node:fs';
|
||||
import { createServer } from 'node:http';
|
||||
+1
-1
@@ -19,7 +19,7 @@ import {
|
||||
servePort,
|
||||
shareWithOrg,
|
||||
waitForHealth,
|
||||
} from './serve-ready.mjs';
|
||||
} from './codespace-preview/serve-ready.mjs';
|
||||
|
||||
const build = process.argv.includes('--build');
|
||||
const port = servePort();
|
||||
|
||||
Reference in New Issue
Block a user