@substrat-run/contracts 0.48.1 → 0.50.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ci.d.ts +111 -0
- package/dist/ci.d.ts.map +1 -0
- package/dist/ci.js +279 -0
- package/dist/ci.js.map +1 -0
- package/dist/control-plane.d.ts +260 -0
- package/dist/control-plane.d.ts.map +1 -1
- package/dist/control-plane.js +82 -0
- package/dist/control-plane.js.map +1 -1
- package/dist/deploy.d.ts +197 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +136 -0
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts +66 -0
- package/dist/introspection.d.ts.map +1 -1
- package/dist/introspection.js +66 -0
- package/dist/introspection.js.map +1 -1
- package/package.json +1 -1
package/dist/ci.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The builder's CI vocabulary: preview tag conventions, the PR sticky-comment bodies,
|
|
3
|
+
* and the generated GitHub Actions workflow.
|
|
4
|
+
*
|
|
5
|
+
* It lives here — not in the dashboard, not in the CLI — for the same reason the deploy
|
|
6
|
+
* manifest does: BOTH ends must speak the same shape. Three writers exist for the same
|
|
7
|
+
* two artifacts and any drift between them is a silent bug:
|
|
8
|
+
*
|
|
9
|
+
* - the **dashboard's one-click CI setup** commits the workflow to a customer repo and
|
|
10
|
+
* (via `GithubRepoLinkDO`) posts the PR comment from the platform side;
|
|
11
|
+
* - **`substrat init --ci github`** writes the same workflow for a builder who owns their
|
|
12
|
+
* own CI and never connected the GitHub App;
|
|
13
|
+
* - the **workflow itself** posts the same comment from the CI side, as the fallback for
|
|
14
|
+
* an App installation that lacks `pull-requests: write`.
|
|
15
|
+
*
|
|
16
|
+
* The comment bodies are generated once here and rendered into the workflow's `printf`
|
|
17
|
+
* format string, so the platform-written and CI-written comments cannot say different
|
|
18
|
+
* things about the same PR. One generator, three writers, no drift.
|
|
19
|
+
*
|
|
20
|
+
* Pure string building — no zod, no network, no node. Safe in a worker, a CLI, and a
|
|
21
|
+
* browser bundle alike.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The sticky preview tag for a PR — one long-lived fork, REBOUND on every push, so the
|
|
25
|
+
* URL always serves the PR's latest code. The git analogy is a **branch ref**: a moving
|
|
26
|
+
* pointer, bookmarked once. Successive pushes roll their migrations forward on the one
|
|
27
|
+
* fork, which is the rehearsal that de-risks the eventual release.
|
|
28
|
+
*/
|
|
29
|
+
export declare const previewTag: (prNumber: number) => string;
|
|
30
|
+
/**
|
|
31
|
+
* The per-build preview tag — a FRESH scope per build, bound once and never rebound, so
|
|
32
|
+
* the URL is frozen to exactly that build forever. The git analogy is a **sha**.
|
|
33
|
+
*
|
|
34
|
+
* A moving pointer is only safe when every build is *also* addressable immutably: "the bug
|
|
35
|
+
* on the PR preview" must always de-reference to a fixed artifact. That is the whole reason
|
|
36
|
+
* this tag exists alongside the sticky one.
|
|
37
|
+
*/
|
|
38
|
+
export declare const buildPreviewTag: (prNumber: number, runId: string | number) => string;
|
|
39
|
+
/**
|
|
40
|
+
* The prefix that matches every per-build tag of one PR, and NOTHING else — note that
|
|
41
|
+
* PR 12's sticky tag (`pr-12`) does not start with PR 1's build prefix (`pr-1-`), so the
|
|
42
|
+
* two numbering spaces never collide.
|
|
43
|
+
*/
|
|
44
|
+
export declare const buildPreviewTagPrefix: (prNumber: number) => string;
|
|
45
|
+
/**
|
|
46
|
+
* The sticky-comment marker. Every writer upserts the comment that starts with this
|
|
47
|
+
* string, so whichever of the platform and CI posts first, the other updates in place
|
|
48
|
+
* rather than double-posting.
|
|
49
|
+
*/
|
|
50
|
+
export declare const PREVIEW_COMMENT_MARKER = "<!-- substrat-preview -->";
|
|
51
|
+
/**
|
|
52
|
+
* The sticky comment while the preview is live.
|
|
53
|
+
*
|
|
54
|
+
* `build` is the per-build immutable URL and is optional: it is present only when the repo
|
|
55
|
+
* opted into per-build previews (`SUBSTRAT_PER_BUILD_PREVIEW`), because a frozen scope per
|
|
56
|
+
* build is a real cost that not every project wants to pay.
|
|
57
|
+
*
|
|
58
|
+
* NOTE: keep the prose free of apostrophes. This same text is rendered into the workflow's
|
|
59
|
+
* single-quoted `printf` format string, where one apostrophe closes the quote and takes the
|
|
60
|
+
* whole preview job red on what looks like a copy-edit.
|
|
61
|
+
*/
|
|
62
|
+
export declare function previewCommentBody(urls: {
|
|
63
|
+
sticky: string;
|
|
64
|
+
build?: string | null;
|
|
65
|
+
}): string;
|
|
66
|
+
/** The sticky comment after the PR closed and the preview forks were deleted. */
|
|
67
|
+
export declare const previewReapedBody: () => string;
|
|
68
|
+
/**
|
|
69
|
+
* How a merge to the deploy branch turns into a prod release.
|
|
70
|
+
*
|
|
71
|
+
* - `trunk` — **every merge releases.** The push carries no `--version`, so the registry
|
|
72
|
+
* patch-bumps and `--promote prod` points prod at it in the same run. The simplest thing
|
|
73
|
+
* that works, and the default the dashboard commits.
|
|
74
|
+
* - `changesets` — **the repo owns the version** (`package.json`), so a merge that only
|
|
75
|
+
* lands a changeset must NOT release; only the merge that MOVES the version does. The job
|
|
76
|
+
* compares `package.json` against the previous commit and no-ops when it did not change.
|
|
77
|
+
* This is the workflow the release-train table in the docs describes.
|
|
78
|
+
*
|
|
79
|
+
* Both are legitimate; the platform enables workflows rather than encoding one (#509 §3).
|
|
80
|
+
*/
|
|
81
|
+
export type ReleaseMode = 'trunk' | 'changesets';
|
|
82
|
+
export interface DeployWorkflowOptions {
|
|
83
|
+
/** The branch whose pushes deploy prod. */
|
|
84
|
+
branch: string;
|
|
85
|
+
/** The vertical's bare slug — the control plane forms the `<tenant>/` prefix. */
|
|
86
|
+
slug: string;
|
|
87
|
+
/** The control-plane API base, e.g. `https://console.substrat.net/api`. */
|
|
88
|
+
cpUrl: string;
|
|
89
|
+
/** Defaults to `trunk`. */
|
|
90
|
+
release?: ReleaseMode;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The workflow the one-click setup commits and `substrat init --ci github` writes.
|
|
94
|
+
*
|
|
95
|
+
* Self-contained on purpose: a committed file is read by humans, so there is no
|
|
96
|
+
* reusable-workflow indirection to chase. The install step is load-bearing — `substrat
|
|
97
|
+
* push`/`preview` runs the repo's OWN build (a wrangler custom build), which needs the
|
|
98
|
+
* repo's devDependencies on disk; corepack picks the package manager from the lockfile.
|
|
99
|
+
*
|
|
100
|
+
* Two behaviours are opt-in through **repository variables**, so one generated file serves
|
|
101
|
+
* every project and enabling them never means regenerating it:
|
|
102
|
+
*
|
|
103
|
+
* - `SUBSTRAT_TEST_SCOPE_ID` — a long-lived test scope. Set it and every merge rebinds that
|
|
104
|
+
* scope to the just-built version: the "tracks main" environment, kept a CI step rather
|
|
105
|
+
* than a platform noun (a "this scope auto-tracks X" setting would be the retired
|
|
106
|
+
* dev/staging channel re-buried one layer down).
|
|
107
|
+
* - `SUBSTRAT_PER_BUILD_PREVIEW` — set it to `1` and each PR push also creates a frozen,
|
|
108
|
+
* short-TTL clean-room preview whose URL names exactly that build.
|
|
109
|
+
*/
|
|
110
|
+
export declare function deployWorkflowYaml(opts: DeployWorkflowOptions): string;
|
|
111
|
+
//# sourceMappingURL=ci.d.ts.map
|
package/dist/ci.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.d.ts","sourceRoot":"","sources":["../src/ci.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAMH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,aAAc,MAAM,KAAG,MAA0B,CAAC;AAEzE;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,aAAc,MAAM,SAAS,MAAM,GAAG,MAAM,KAAG,MAChD,CAAC;AAE5B;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,aAAc,MAAM,KAAG,MAA2B,CAAC;AAMrF;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,8BAA8B,CAAC;AAElE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,MAAM,CAa1F;AAED,iFAAiF;AACjF,eAAO,MAAM,iBAAiB,QAAO,MACiF,CAAC;AAMvH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,YAAY,CAAC;AAEjD,MAAM,WAAW,qBAAqB;IACpC,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAKD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,CA8LtE"}
|
package/dist/ci.js
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The builder's CI vocabulary: preview tag conventions, the PR sticky-comment bodies,
|
|
3
|
+
* and the generated GitHub Actions workflow.
|
|
4
|
+
*
|
|
5
|
+
* It lives here — not in the dashboard, not in the CLI — for the same reason the deploy
|
|
6
|
+
* manifest does: BOTH ends must speak the same shape. Three writers exist for the same
|
|
7
|
+
* two artifacts and any drift between them is a silent bug:
|
|
8
|
+
*
|
|
9
|
+
* - the **dashboard's one-click CI setup** commits the workflow to a customer repo and
|
|
10
|
+
* (via `GithubRepoLinkDO`) posts the PR comment from the platform side;
|
|
11
|
+
* - **`substrat init --ci github`** writes the same workflow for a builder who owns their
|
|
12
|
+
* own CI and never connected the GitHub App;
|
|
13
|
+
* - the **workflow itself** posts the same comment from the CI side, as the fallback for
|
|
14
|
+
* an App installation that lacks `pull-requests: write`.
|
|
15
|
+
*
|
|
16
|
+
* The comment bodies are generated once here and rendered into the workflow's `printf`
|
|
17
|
+
* format string, so the platform-written and CI-written comments cannot say different
|
|
18
|
+
* things about the same PR. One generator, three writers, no drift.
|
|
19
|
+
*
|
|
20
|
+
* Pure string building — no zod, no network, no node. Safe in a worker, a CLI, and a
|
|
21
|
+
* browser bundle alike.
|
|
22
|
+
*/
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Tag conventions
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* The sticky preview tag for a PR — one long-lived fork, REBOUND on every push, so the
|
|
28
|
+
* URL always serves the PR's latest code. The git analogy is a **branch ref**: a moving
|
|
29
|
+
* pointer, bookmarked once. Successive pushes roll their migrations forward on the one
|
|
30
|
+
* fork, which is the rehearsal that de-risks the eventual release.
|
|
31
|
+
*/
|
|
32
|
+
export const previewTag = (prNumber) => `pr-${prNumber}`;
|
|
33
|
+
/**
|
|
34
|
+
* The per-build preview tag — a FRESH scope per build, bound once and never rebound, so
|
|
35
|
+
* the URL is frozen to exactly that build forever. The git analogy is a **sha**.
|
|
36
|
+
*
|
|
37
|
+
* A moving pointer is only safe when every build is *also* addressable immutably: "the bug
|
|
38
|
+
* on the PR preview" must always de-reference to a fixed artifact. That is the whole reason
|
|
39
|
+
* this tag exists alongside the sticky one.
|
|
40
|
+
*/
|
|
41
|
+
export const buildPreviewTag = (prNumber, runId) => `pr-${prNumber}-${runId}`;
|
|
42
|
+
/**
|
|
43
|
+
* The prefix that matches every per-build tag of one PR, and NOTHING else — note that
|
|
44
|
+
* PR 12's sticky tag (`pr-12`) does not start with PR 1's build prefix (`pr-1-`), so the
|
|
45
|
+
* two numbering spaces never collide.
|
|
46
|
+
*/
|
|
47
|
+
export const buildPreviewTagPrefix = (prNumber) => `pr-${prNumber}-`;
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// The PR sticky comment
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* The sticky-comment marker. Every writer upserts the comment that starts with this
|
|
53
|
+
* string, so whichever of the platform and CI posts first, the other updates in place
|
|
54
|
+
* rather than double-posting.
|
|
55
|
+
*/
|
|
56
|
+
export const PREVIEW_COMMENT_MARKER = '<!-- substrat-preview -->';
|
|
57
|
+
/**
|
|
58
|
+
* The sticky comment while the preview is live.
|
|
59
|
+
*
|
|
60
|
+
* `build` is the per-build immutable URL and is optional: it is present only when the repo
|
|
61
|
+
* opted into per-build previews (`SUBSTRAT_PER_BUILD_PREVIEW`), because a frozen scope per
|
|
62
|
+
* build is a real cost that not every project wants to pay.
|
|
63
|
+
*
|
|
64
|
+
* NOTE: keep the prose free of apostrophes. This same text is rendered into the workflow's
|
|
65
|
+
* single-quoted `printf` format string, where one apostrophe closes the quote and takes the
|
|
66
|
+
* whole preview job red on what looks like a copy-edit.
|
|
67
|
+
*/
|
|
68
|
+
export function previewCommentBody(urls) {
|
|
69
|
+
if (!urls.build) {
|
|
70
|
+
return (`${PREVIEW_COMMENT_MARKER}\n🔎 **Substrat preview:** ${urls.sticky}\n\n` +
|
|
71
|
+
'_Runs the code in this PR against a fork of prod, and follows the PR — every push rebinds it. ' +
|
|
72
|
+
'Reaped when the PR closes._');
|
|
73
|
+
}
|
|
74
|
+
return (`${PREVIEW_COMMENT_MARKER}\n🔎 **Substrat preview:** ${urls.sticky}\n📌 **This build:** ${urls.build}\n\n` +
|
|
75
|
+
'_The preview URL follows the PR — every push rebinds it, so it always serves the latest code. ' +
|
|
76
|
+
'The build URL is frozen to this one build and never moves. Both are reaped when the PR closes._');
|
|
77
|
+
}
|
|
78
|
+
/** The sticky comment after the PR closed and the preview forks were deleted. */
|
|
79
|
+
export const previewReapedBody = () => `${PREVIEW_COMMENT_MARKER}\n🔎 **Substrat preview:** reaped — this PR is closed and the preview forks were deleted.`;
|
|
80
|
+
/** Render a comment body as a single-quoted `printf` format string (literal `\n`, `%s` holes). */
|
|
81
|
+
const printfFormat = (body) => body.replace(/\n/g, '\\n');
|
|
82
|
+
/**
|
|
83
|
+
* The workflow the one-click setup commits and `substrat init --ci github` writes.
|
|
84
|
+
*
|
|
85
|
+
* Self-contained on purpose: a committed file is read by humans, so there is no
|
|
86
|
+
* reusable-workflow indirection to chase. The install step is load-bearing — `substrat
|
|
87
|
+
* push`/`preview` runs the repo's OWN build (a wrangler custom build), which needs the
|
|
88
|
+
* repo's devDependencies on disk; corepack picks the package manager from the lockfile.
|
|
89
|
+
*
|
|
90
|
+
* Two behaviours are opt-in through **repository variables**, so one generated file serves
|
|
91
|
+
* every project and enabling them never means regenerating it:
|
|
92
|
+
*
|
|
93
|
+
* - `SUBSTRAT_TEST_SCOPE_ID` — a long-lived test scope. Set it and every merge rebinds that
|
|
94
|
+
* scope to the just-built version: the "tracks main" environment, kept a CI step rather
|
|
95
|
+
* than a platform noun (a "this scope auto-tracks X" setting would be the retired
|
|
96
|
+
* dev/staging channel re-buried one layer down).
|
|
97
|
+
* - `SUBSTRAT_PER_BUILD_PREVIEW` — set it to `1` and each PR push also creates a frozen,
|
|
98
|
+
* short-TTL clean-room preview whose URL names exactly that build.
|
|
99
|
+
*/
|
|
100
|
+
export function deployWorkflowYaml(opts) {
|
|
101
|
+
const { branch, slug, cpUrl } = opts;
|
|
102
|
+
const release = opts.release ?? 'trunk';
|
|
103
|
+
const cli = 'npx @substrat-run/cli';
|
|
104
|
+
// The install block repeats across jobs (self-contained file, see above). `fetch-depth: 2`
|
|
105
|
+
// is what lets the changesets release gate diff package.json against the previous commit.
|
|
106
|
+
const setup = (fetchDepth) => ` - uses: actions/checkout@v4${fetchDepth ? `\n with:\n fetch-depth: ${fetchDepth}` : ''}
|
|
107
|
+
- uses: actions/setup-node@v4
|
|
108
|
+
with:
|
|
109
|
+
# 22+: corepack resolves the latest pnpm for lockfile-only repos, and pnpm 11
|
|
110
|
+
# needs Node >= 22.13 (node:sqlite).
|
|
111
|
+
node-version: 22
|
|
112
|
+
- name: Install dependencies
|
|
113
|
+
env:
|
|
114
|
+
COREPACK_ENABLE_DOWNLOAD_PROMPT: '0'
|
|
115
|
+
run: |
|
|
116
|
+
if [ -f pnpm-lock.yaml ]; then corepack enable && pnpm install --frozen-lockfile
|
|
117
|
+
elif [ -f yarn.lock ]; then corepack enable && yarn install --frozen-lockfile
|
|
118
|
+
elif [ -f package-lock.json ]; then npm ci
|
|
119
|
+
else npm install
|
|
120
|
+
fi`;
|
|
121
|
+
const cpEnv = ` env:
|
|
122
|
+
SUBSTRAT_SERVICE_TOKEN: \${{ secrets.SUBSTRAT_SERVICE_TOKEN }}
|
|
123
|
+
SUBSTRAT_CP_URL: ${cpUrl}`;
|
|
124
|
+
// --- the release step, per mode ------------------------------------------------------
|
|
125
|
+
//
|
|
126
|
+
// Both print the pushed version id on the `✓ pushed … version <id> …` line; the test-env
|
|
127
|
+
// bind below reads it back out of the captured stdout. `set -euo pipefail` means a failed
|
|
128
|
+
// push fails the step rather than letting a masked non-zero exit reach the bind.
|
|
129
|
+
const release_ = release === 'changesets'
|
|
130
|
+
? ` - name: Release (only when package.json version moved)
|
|
131
|
+
${cpEnv}
|
|
132
|
+
run: |
|
|
133
|
+
set -euo pipefail
|
|
134
|
+
CUR=$(node -p "require('./package.json').version")
|
|
135
|
+
PREV=$(git show HEAD^:package.json 2>/dev/null | node -pe "JSON.parse(require('fs').readFileSync(0,'utf8')).version" 2>/dev/null || echo '')
|
|
136
|
+
if [ "$CUR" = "$PREV" ]; then
|
|
137
|
+
echo "package.json version is still $CUR — this merge landed a changeset, not a release. Skipping prod."
|
|
138
|
+
exit 0
|
|
139
|
+
fi
|
|
140
|
+
echo "releasing $CUR (was \${PREV:-none})"
|
|
141
|
+
${cli} push . --slug ${slug} --version "$CUR" --promote prod`
|
|
142
|
+
: ` - name: Release to prod
|
|
143
|
+
${cpEnv}
|
|
144
|
+
run: |
|
|
145
|
+
set -euo pipefail
|
|
146
|
+
${cli} push . --slug ${slug} --promote prod`;
|
|
147
|
+
// --- the "tracks main" test environment ---------------------------------------------
|
|
148
|
+
//
|
|
149
|
+
// Runs on EVERY merge, release or not — that is the point of a test env: it is always the
|
|
150
|
+
// head of the branch, a merge ahead of prod, rehearsing each migration on accumulated data.
|
|
151
|
+
// It pushes its OWN build rather than reusing the release step's, because in `changesets`
|
|
152
|
+
// mode most merges produce no release at all and the test env must still move; doing it the
|
|
153
|
+
// same way in both modes keeps one legible file. The label is a PRERELEASE, so this push can
|
|
154
|
+
// never claim or advance the release coordinate the repo owns.
|
|
155
|
+
const testEnv = ` - name: Update the test environment
|
|
156
|
+
if: vars.SUBSTRAT_TEST_SCOPE_ID != ''
|
|
157
|
+
${cpEnv}
|
|
158
|
+
run: |
|
|
159
|
+
set -euo pipefail
|
|
160
|
+
BASE=$(node -p "require('./package.json').version")
|
|
161
|
+
${cli} push . --slug ${slug} --version "$BASE-test.\${{ github.run_number }}" | tee push.out
|
|
162
|
+
# '|| true' so an unmatched grep does not trip pipefail before the message below —
|
|
163
|
+
# "could not read the pushed version id" beats a bare exit 1 from grep.
|
|
164
|
+
VID=$(grep -F '✓ pushed' push.out | grep -oE 'version [A-Za-z0-9]+' | head -1 | cut -d' ' -f2 || true)
|
|
165
|
+
if [ -z "$VID" ]; then echo "could not read the pushed version id from:" >&2; cat push.out >&2; exit 1; fi
|
|
166
|
+
${cli} scope bind \${{ vars.SUBSTRAT_TEST_SCOPE_ID }} --version "$VID" --snapshot`;
|
|
167
|
+
// --- the PR previews -----------------------------------------------------------------
|
|
168
|
+
//
|
|
169
|
+
// Sticky first (it is the URL a reviewer bookmarks and must exist even if the optional
|
|
170
|
+
// per-build one fails), then the frozen per-build clean room when the repo opted in.
|
|
171
|
+
const previewSteps = ` - name: Create/update the preview
|
|
172
|
+
${cpEnv}
|
|
173
|
+
run: |
|
|
174
|
+
set -euo pipefail
|
|
175
|
+
${cli} preview create . --slug ${slug} --tag pr-\${{ github.event.number }} | tee preview.out
|
|
176
|
+
# Take the URL from the CLI success line (the ✓ marker) only — the push it runs
|
|
177
|
+
# first also prints an https:// *deploy endpoint* we must never mistake for a preview.
|
|
178
|
+
grep -F '✓ preview' preview.out | grep -oE 'https://[a-zA-Z0-9.:/_-]+' | tail -1 > preview.url || true
|
|
179
|
+
- name: Create the per-build preview
|
|
180
|
+
if: vars.SUBSTRAT_PER_BUILD_PREVIEW != ''
|
|
181
|
+
${cpEnv}
|
|
182
|
+
run: |
|
|
183
|
+
set -euo pipefail
|
|
184
|
+
# A FRESH scope, bound once and never rebound, so this URL is frozen to this build
|
|
185
|
+
# forever. Clean-room (--empty) rather than a fork: a throwaway per build should not
|
|
186
|
+
# copy prod data every push. Short TTL — the sticky preview is the one that lives.
|
|
187
|
+
${cli} preview create . --slug ${slug} \\
|
|
188
|
+
--tag pr-\${{ github.event.number }}-\${{ github.run_id }} --empty --ttl 24h | tee build.out
|
|
189
|
+
grep -F '✓ preview' build.out | grep -oE 'https://[a-zA-Z0-9.:/_-]+' | tail -1 > build.url || true`;
|
|
190
|
+
// The comment body comes from previewCommentBody() so the CI-written and platform-written
|
|
191
|
+
// comments cannot diverge. Two formats: with and without the per-build line.
|
|
192
|
+
const stickyOnlyFmt = printfFormat(previewCommentBody({ sticky: '%s' }));
|
|
193
|
+
const withBuildFmt = printfFormat(previewCommentBody({ sticky: '%s', build: '%s' }));
|
|
194
|
+
const commentStep = ` - name: Comment the preview URLs
|
|
195
|
+
env:
|
|
196
|
+
GH_TOKEN: \${{ github.token }}
|
|
197
|
+
run: |
|
|
198
|
+
set -uo pipefail
|
|
199
|
+
URL=$(cat preview.url 2>/dev/null || true)
|
|
200
|
+
[ -z "$URL" ] && exit 0
|
|
201
|
+
BUILD=$(cat build.url 2>/dev/null || true)
|
|
202
|
+
if [ -n "$BUILD" ]; then
|
|
203
|
+
BODY=$(printf '${withBuildFmt}' "$URL" "$BUILD")
|
|
204
|
+
else
|
|
205
|
+
BODY=$(printf '${stickyOnlyFmt}' "$URL")
|
|
206
|
+
fi
|
|
207
|
+
REPO=\${{ github.repository }}
|
|
208
|
+
PR=\${{ github.event.number }}
|
|
209
|
+
ID=$(gh api "repos/$REPO/issues/$PR/comments" --jq '.[] | select(.body | startswith("${PREVIEW_COMMENT_MARKER}")) | .id' | head -1)
|
|
210
|
+
if [ -n "$ID" ]; then
|
|
211
|
+
gh api -X PATCH "repos/$REPO/issues/comments/$ID" -f body="$BODY" >/dev/null
|
|
212
|
+
else
|
|
213
|
+
gh api -X POST "repos/$REPO/issues/$PR/comments" -f body="$BODY" >/dev/null
|
|
214
|
+
fi`;
|
|
215
|
+
// Close ⇒ reap both forks. The per-build tag is unknown at close time (it named a run id),
|
|
216
|
+
// so the sticky one is deleted by tag and the rest are left to their 24h TTL — the GC sweep
|
|
217
|
+
// is the backstop the short TTL exists for.
|
|
218
|
+
const cleanup = ` preview_cleanup:
|
|
219
|
+
# Reap only — no repo build needed, so skip checkout/install and just run the CLI.
|
|
220
|
+
if: github.event_name == 'pull_request' && github.event.action == 'closed'
|
|
221
|
+
runs-on: ubuntu-latest
|
|
222
|
+
concurrency: substrat-preview-\${{ github.event.number }}
|
|
223
|
+
steps:
|
|
224
|
+
- uses: actions/setup-node@v4
|
|
225
|
+
with:
|
|
226
|
+
node-version: 22
|
|
227
|
+
- name: Reap the preview
|
|
228
|
+
${cpEnv}
|
|
229
|
+
run: ${cli} preview delete --slug ${slug} --tag pr-\${{ github.event.number }}`;
|
|
230
|
+
const releaseNote = release === 'changesets'
|
|
231
|
+
? ` # Release train: the version lives in package.json and only a version PR moves it, so a
|
|
232
|
+
# merge releases ONLY when that version changed. Every merge still updates the test env.`
|
|
233
|
+
: ` # Trunk-based: every merge to ${branch} releases. A private vertical's push lands admitted
|
|
234
|
+
# and prod is self-serve, so the same run points prod at the new version.`;
|
|
235
|
+
return `name: Deploy to Substrat
|
|
236
|
+
|
|
237
|
+
# Generated by Substrat (substrat init --ci github). The workflow this encodes — one prod
|
|
238
|
+
# channel, previews as the only non-prod environment — is documented at
|
|
239
|
+
# https://substrat.net/guide/environments-and-previews
|
|
240
|
+
#
|
|
241
|
+
# Optional repository variables (Settings → Secrets and variables → Actions → Variables):
|
|
242
|
+
# SUBSTRAT_TEST_SCOPE_ID a long-lived scope rebound to every merge (the test env)
|
|
243
|
+
# SUBSTRAT_PER_BUILD_PREVIEW set to 1 to also mint a frozen per-build preview URL per push
|
|
244
|
+
|
|
245
|
+
on:
|
|
246
|
+
push:
|
|
247
|
+
branches: [${branch}]
|
|
248
|
+
# Per-PR previews: open/update a PR → a preview instance running the PR's code against a
|
|
249
|
+
# FORK of prod on its own URL; close the PR → it is reaped (the TTL is the GC backstop).
|
|
250
|
+
pull_request:
|
|
251
|
+
types: [opened, synchronize, reopened, closed]
|
|
252
|
+
|
|
253
|
+
jobs:
|
|
254
|
+
deploy:
|
|
255
|
+
${releaseNote}
|
|
256
|
+
if: github.event_name == 'push'
|
|
257
|
+
runs-on: ubuntu-latest
|
|
258
|
+
steps:
|
|
259
|
+
${setup(release === 'changesets' ? 2 : undefined)}
|
|
260
|
+
${release_}
|
|
261
|
+
${testEnv}
|
|
262
|
+
|
|
263
|
+
preview:
|
|
264
|
+
if: github.event_name == 'pull_request' && github.event.action != 'closed'
|
|
265
|
+
runs-on: ubuntu-latest
|
|
266
|
+
# Never overlap two runs for the same PR — a rapid re-push waits for the first.
|
|
267
|
+
concurrency: substrat-preview-\${{ github.event.number }}
|
|
268
|
+
permissions:
|
|
269
|
+
contents: read
|
|
270
|
+
pull-requests: write
|
|
271
|
+
steps:
|
|
272
|
+
${setup()}
|
|
273
|
+
${previewSteps}
|
|
274
|
+
${commentStep}
|
|
275
|
+
|
|
276
|
+
${cleanup}
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=ci.js.map
|
package/dist/ci.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.js","sourceRoot":"","sources":["../src/ci.ts"],"names":[],"mappings":"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"}
|