@dbx-tools/projen 0.6.177 → 0.6.180
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/README.md +47 -35
- package/index.ts +3 -3
- package/package.json +4 -4
- package/src/project-js.ts +7 -16
- package/src/project-py.ts +47 -249
- package/src/project-rs.ts +210 -244
- package/src/release-dispatch.ts +9 -11
- package/src/release.ts +313 -625
- package/tasks/publish-npm.ts +197 -0
- package/tasks/publish.ts +38 -1
- package/tasks/stamp-python.ts +13 -0
package/src/release.ts
CHANGED
|
@@ -1,674 +1,362 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* commit + tag + push), parameterized by the project's git tag prefix, and - when
|
|
4
|
-
* the project has a GitHub component - authors the tag-driven npm publish
|
|
5
|
-
* workflow that the pushed tag triggers.
|
|
6
|
-
*/
|
|
7
|
-
import { existsSync, rmSync } from "node:fs";
|
|
8
|
-
import { resolve } from "node:path";
|
|
9
|
-
import { object } from "@dbx-tools/shared-core";
|
|
10
|
-
import { Component, YamlFile } from "projen";
|
|
1
|
+
/** Unified tag-driven release workflow generation. */
|
|
2
|
+
import { Component } from "projen";
|
|
11
3
|
import { GithubWorkflow } from "projen/lib/github";
|
|
12
|
-
import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
|
|
4
|
+
import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
|
|
13
5
|
import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
type RustWorkspaceMapping,
|
|
18
|
-
} from "./project-rs.ts";
|
|
19
|
-
import { applyTasks, taskScript, type DBXToolsNodeProject } from "./project.ts";
|
|
20
|
-
import {
|
|
21
|
-
DOWNSTREAM_RELEASE_EVENT,
|
|
22
|
-
RELEASE_SHA,
|
|
23
|
-
RELEASE_TAG,
|
|
24
|
-
RUST_RELEASE_EVENT,
|
|
25
|
-
releaseSourceSteps,
|
|
26
|
-
} from "./release-dispatch.ts";
|
|
6
|
+
import type { DBXToolsJavaScriptProject } from "./project-js.ts";
|
|
7
|
+
import { applyTasks, taskScript } from "./project.ts";
|
|
8
|
+
import { RELEASE_VERSION, releaseSourceSteps } from "./release-dispatch.ts";
|
|
27
9
|
|
|
28
10
|
const NODE_VERSION = "lts/*";
|
|
29
11
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
12
|
+
const nodeReleaseProjects = new WeakSet<DBXToolsJavaScriptProject>();
|
|
13
|
+
const releaseTagPrefixes = new WeakMap<DBXToolsJavaScriptProject, string>();
|
|
14
|
+
const releaseWorkflows = new WeakMap<DBXToolsJavaScriptProject, GithubWorkflow>();
|
|
30
15
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*/
|
|
51
|
-
function
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
16
|
+
/** Independently recoverable portions of the release workflow. */
|
|
17
|
+
export type ReleaseStage = "all" | "node" | "python" | "docs";
|
|
18
|
+
|
|
19
|
+
/** GitHub Pages configuration included in the unified release workflow. */
|
|
20
|
+
export interface ReleaseDocsOptions {
|
|
21
|
+
readonly siteUrl: string;
|
|
22
|
+
readonly base?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Options for {@link DBXToolsRelease}. */
|
|
26
|
+
export interface DBXToolsReleaseOptions {
|
|
27
|
+
/** Git tag prefix. Defaults to `v`. */
|
|
28
|
+
readonly tagPrefix?: string;
|
|
29
|
+
/** Omit normal npm workspace publication while retaining other release jobs. */
|
|
30
|
+
readonly nodeRelease?: boolean;
|
|
31
|
+
/** Build and deploy generated documentation through GitHub Pages. */
|
|
32
|
+
readonly docs?: ReleaseDocsOptions;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Locate the unified workflow so attached language workspaces can add jobs. */
|
|
36
|
+
export function releaseWorkflow(project: DBXToolsJavaScriptProject): GithubWorkflow {
|
|
37
|
+
const workflow = releaseWorkflows.get(project);
|
|
38
|
+
if (!workflow) throw new Error("Release workflow is not configured");
|
|
39
|
+
return workflow;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Whether the unified workflow publishes the normal npm workspace. */
|
|
43
|
+
export function hasNodeRelease(project: DBXToolsJavaScriptProject): boolean {
|
|
44
|
+
return nodeReleaseProjects.has(project);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Tag pattern accepted by release jobs and GitHub environments. */
|
|
48
|
+
export function releaseTagPattern(project: DBXToolsJavaScriptProject): string {
|
|
49
|
+
const prefix = releaseTagPrefixes.get(project);
|
|
50
|
+
if (!prefix) throw new Error("Release workflow is not configured");
|
|
51
|
+
return `${prefix}*`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Run a release stage on tag pushes or when selected for manual recovery. */
|
|
55
|
+
export function releaseStageCondition(stage: Exclude<ReleaseStage, "all">): string {
|
|
56
|
+
return `\${{ github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == '${stage}' }}`;
|
|
70
57
|
}
|
|
71
58
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
readonly steps: readonly JobStep[];
|
|
76
|
-
readonly workingDirectory?: string;
|
|
77
|
-
readonly upstreamWorkflow?: string;
|
|
78
|
-
readonly rustArtifacts?: boolean;
|
|
59
|
+
/** Publish a selected stage unless a manual run remains in dry-run mode. */
|
|
60
|
+
export function releasePublishCondition(stage: Exclude<ReleaseStage, "all">): string {
|
|
61
|
+
return `\${{ github.event_name == 'push' || (inputs.dry_run == false && (inputs.stage == 'all' || inputs.stage == '${stage}')) }}`;
|
|
79
62
|
}
|
|
80
63
|
|
|
81
|
-
/**
|
|
82
|
-
function
|
|
64
|
+
/** Download release artifacts from this run or a verified earlier run. */
|
|
65
|
+
export function releaseArtifactSteps(options: {
|
|
66
|
+
readonly currentName: string;
|
|
67
|
+
readonly recoveredName: string;
|
|
68
|
+
readonly pattern: string;
|
|
69
|
+
readonly path: string;
|
|
70
|
+
}): readonly JobStep[] {
|
|
71
|
+
const shared = {
|
|
72
|
+
pattern: options.pattern,
|
|
73
|
+
path: options.path,
|
|
74
|
+
"merge-multiple": true,
|
|
75
|
+
};
|
|
83
76
|
return [
|
|
84
|
-
{
|
|
77
|
+
{
|
|
78
|
+
name: options.currentName,
|
|
79
|
+
if: "${{ inputs.source_run_id == '' }}",
|
|
80
|
+
uses: "actions/download-artifact@v8",
|
|
81
|
+
with: shared,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: options.recoveredName,
|
|
85
|
+
if: "${{ inputs.source_run_id != '' }}",
|
|
86
|
+
uses: "actions/download-artifact@v8",
|
|
87
|
+
with: {
|
|
88
|
+
...shared,
|
|
89
|
+
"run-id": "${{ inputs.source_run_id }}",
|
|
90
|
+
"github-token": "${{ github.token }}",
|
|
91
|
+
repository: "${{ github.repository }}",
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Shared Bun, Node, cache, and install setup for Node release jobs. */
|
|
98
|
+
export function nodeReleaseSetupSteps(project: DBXToolsJavaScriptProject): readonly JobStep[] {
|
|
99
|
+
return [
|
|
100
|
+
...releaseSourceSteps(),
|
|
85
101
|
...bunCacheRestoreSteps(project),
|
|
86
102
|
{
|
|
87
103
|
name: "Setup Node.js",
|
|
88
104
|
uses: "actions/setup-node@v6",
|
|
89
|
-
// setup-node writes the temporary npmrc that maps NODE_AUTH_TOKEN onto
|
|
90
|
-
// npmjs. Omitting this leaves the secret in the environment but gives npm
|
|
91
|
-
// no registry-scoped auth entry, and every publish fails with ENEEDAUTH.
|
|
92
|
-
// (Bun installs deps; publishing still goes through `npm publish`.)
|
|
93
105
|
with: { "node-version": NODE_VERSION, "registry-url": NPM_REGISTRY_URL },
|
|
94
106
|
},
|
|
95
|
-
// Bun's install; the lockfile may be absent or stale in CI so it is not frozen.
|
|
96
107
|
{ name: "Install", run: "bun install" },
|
|
97
108
|
bunCacheSaveStep(),
|
|
98
109
|
];
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
* Declaring one also enlists it in the root's `bump`, which cuts BOTH tags at one
|
|
112
|
-
* shared version. The separate tag namespace still lets it be released alone
|
|
113
|
-
* (`cd <directory> && bun run bump`) for a consumer who wants only this package.
|
|
114
|
-
* Enlisting it is what keeps a routine root bump from leaving it behind and letting
|
|
115
|
-
* its version drift away from the packages'.
|
|
116
|
-
*/
|
|
117
|
-
export interface StandaloneRelease {
|
|
118
|
-
/** Workflow name (and `.github/workflows/<name>.yml` file). E.g. `projen-release`. */
|
|
119
|
-
readonly name: string;
|
|
120
|
-
/** Repo-relative directory of the standalone project. E.g. `projen`. */
|
|
121
|
-
readonly directory: string;
|
|
122
|
-
/**
|
|
123
|
-
* Git tag prefix that triggers this release, disjoint from the root's `v*`
|
|
124
|
-
* (e.g. `projen-v`). The pushed tag IS the published version.
|
|
125
|
-
*/
|
|
126
|
-
readonly tagPrefix: string;
|
|
112
|
+
/** Authentication, provenance, and dry-run values shared by npm publishers. */
|
|
113
|
+
export function npmPublishEnvironment(): Record<string, string> {
|
|
114
|
+
return {
|
|
115
|
+
NPM_CONFIG_PROVENANCE:
|
|
116
|
+
"${{ (github.event_name == 'push' || inputs.dry_run == false) && 'true' || 'false' }}",
|
|
117
|
+
NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
118
|
+
NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
119
|
+
DRY_RUN:
|
|
120
|
+
"${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && '--dry-run' || '' }}",
|
|
121
|
+
};
|
|
127
122
|
}
|
|
128
123
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
readonly upstreamWorkflow?: string;
|
|
146
|
-
/** Main Node release workflow name. Defaults to `node-release`. */
|
|
147
|
-
readonly workflowName?: string | false;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Adds a `bump` task: compute the next release version (from the higher of the
|
|
152
|
-
* latest `<prefix>*` git tag and the local `package.json`), then commit, tag,
|
|
153
|
-
* and push it - pushing the tag is what triggers the release workflow. Each step
|
|
154
|
-
* is toggleable (`--no-version` / `--no-commit` / `--no-tag` / `--no-push` /
|
|
155
|
-
* `--no-publish`); see `tasks/bump.ts`.
|
|
156
|
-
*
|
|
157
|
-
* When the project has a GitHub component (`github: true`), also authors the
|
|
158
|
-
* `release` workflow: on a pushed `<prefix>*` tag it sets that version on every
|
|
159
|
-
* publishable package and runs `pnpm -r publish` (which skips
|
|
160
|
-
* `private` packages and honors each package's `publishConfig`).
|
|
161
|
-
*
|
|
162
|
-
* Provenance is opt-in. Each package's generated `publishConfig` omits
|
|
163
|
-
* `provenance`, so LOCAL publishes (e.g. to a verdaccio) never try to attest -
|
|
164
|
-
* npm has no CI OIDC provider off-CI and would fail with `provider: null`. This
|
|
165
|
-
* CI workflow turns it on with `npm_config_provenance=true`, backed by the
|
|
166
|
-
* `id-token: write` permission that lets npm mint the OIDC token.
|
|
167
|
-
*
|
|
168
|
-
* Registry AUTH is still `NPM_TOKEN`, deliberately. OIDC here mints the
|
|
169
|
-
* provenance attestation only; full npm trusted publishing would additionally
|
|
170
|
-
* replace the token, but it has to be registered per package on npmjs.com
|
|
171
|
-
* against an exact repo + workflow filename, and this repo publishes 26 of
|
|
172
|
-
* them. That is a considered deferral, not an oversight - do not "fix" it by
|
|
173
|
-
* dropping the token without doing the registrations first, or every publish
|
|
174
|
-
* fails.
|
|
175
|
-
*/
|
|
176
|
-
export class DBXToolsRelease extends Component {
|
|
177
|
-
private readonly tagPrefix: string;
|
|
178
|
-
private readonly standaloneReleases: readonly StandaloneRelease[];
|
|
179
|
-
private readonly upstreamWorkflow?: string;
|
|
180
|
-
private readonly workflowName: string | false;
|
|
181
|
-
|
|
182
|
-
constructor(project: DBXToolsNodeProject, options: DBXToolsReleaseOptions = {}) {
|
|
183
|
-
super(project);
|
|
184
|
-
this.tagPrefix = options.tagPrefix ?? "v";
|
|
185
|
-
this.standaloneReleases = options.standaloneReleases ?? [];
|
|
186
|
-
this.upstreamWorkflow = options.upstreamWorkflow;
|
|
187
|
-
this.workflowName = options.workflowName ?? "node-release";
|
|
188
|
-
if (project.github) this.authorReleaseDispatcher(project);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
public override preSynthesize(): void {
|
|
192
|
-
const project = this.project as DBXToolsNodeProject;
|
|
193
|
-
const rust = project.dbxToolsConfig.rust;
|
|
194
|
-
if (
|
|
195
|
-
!this.workflowName &&
|
|
196
|
-
this.standaloneReleases.length === 0 &&
|
|
197
|
-
typeof project.dbxToolsConfig.pythonReleaseWorkflow !== "string" &&
|
|
198
|
-
!(object.isRecord(rust) && typeof rust.releaseWorkflow === "string")
|
|
199
|
-
) {
|
|
200
|
-
project.tryRemoveFile(".github/workflows/release-dispatch.yml");
|
|
201
|
-
}
|
|
202
|
-
const releaseEvent =
|
|
203
|
-
object.isRecord(rust) && typeof rust.releaseWorkflow === "string"
|
|
204
|
-
? RUST_RELEASE_EVENT
|
|
205
|
-
: DOWNSTREAM_RELEASE_EVENT;
|
|
206
|
-
const dispatcher = project.tryFindObjectFile(".github/workflows/release-dispatch.yml");
|
|
207
|
-
dispatcher?.addOverride("jobs.dispatch.steps.1.env.RELEASE_EVENT", releaseEvent);
|
|
208
|
-
const releaseWorkflows = [
|
|
209
|
-
...(object.isRecord(rust) && typeof rust.releaseWorkflow === "string"
|
|
210
|
-
? [rust.releaseWorkflow]
|
|
211
|
-
: []),
|
|
212
|
-
...(typeof project.dbxToolsConfig.pythonReleaseWorkflow === "string"
|
|
213
|
-
? [project.dbxToolsConfig.pythonReleaseWorkflow]
|
|
214
|
-
: []),
|
|
215
|
-
...(this.workflowName ? [this.workflowName] : []),
|
|
216
|
-
...this.standaloneReleases.map(({ name }) => name),
|
|
217
|
-
...(existsSync(resolve(project.outdir, ".github/workflows/docs.yml")) ? ["docs"] : []),
|
|
218
|
-
];
|
|
219
|
-
dispatcher?.addOverride(
|
|
220
|
-
"jobs.dispatch.steps.1.env.RELEASE_WORKFLOWS",
|
|
221
|
-
[...new Set(releaseWorkflows)].join(","),
|
|
222
|
-
);
|
|
223
|
-
// Release the standalone projects in the SAME run, at the same version. They
|
|
224
|
-
// are not workspace members, so nothing else would ever bring them along.
|
|
225
|
-
const siblingArgs = this.standaloneReleases
|
|
226
|
-
.map(({ directory, tagPrefix }) => ` --sibling ${directory}:${tagPrefix}`)
|
|
227
|
-
.join("");
|
|
228
|
-
applyTasks(project, {
|
|
229
|
-
bump: {
|
|
230
|
-
exec: taskScript(project, "bump.ts", `--prefix ${this.tagPrefix}${siblingArgs}`),
|
|
231
|
-
receiveArgs: true,
|
|
232
|
-
description: "Bump the release version (default patch), then commit, tag, and push it",
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Author the tag-driven publish workflows only when GitHub is enabled - they
|
|
237
|
-
// live in `.github/`, which requires projen's GitHub component.
|
|
238
|
-
if (project.github) {
|
|
239
|
-
if (this.workflowName) this.authorReleaseWorkflow(project);
|
|
240
|
-
for (const standalone of this.standaloneReleases) {
|
|
241
|
-
this.authorStandaloneReleaseWorkflow(project, standalone);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
public override postSynthesize(): void {
|
|
247
|
-
rmSync(resolve(this.project.outdir, ".github/workflows/rust-release-dispatch.yml"), {
|
|
248
|
-
force: true,
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private authorReleaseDispatcher(project: DBXToolsNodeProject): void {
|
|
253
|
-
new YamlFile(project, ".github/workflows/release-dispatch.yml", {
|
|
254
|
-
obj: {
|
|
255
|
-
name: "release-dispatch",
|
|
256
|
-
on: { push: { tags: [`${this.tagPrefix}*`] } },
|
|
257
|
-
concurrency: {
|
|
258
|
-
group: "release-dispatch",
|
|
259
|
-
"cancel-in-progress": true,
|
|
260
|
-
},
|
|
261
|
-
permissions: { actions: "write", contents: "write" },
|
|
262
|
-
jobs: {
|
|
263
|
-
dispatch: {
|
|
264
|
-
"runs-on": "ubuntu-latest",
|
|
265
|
-
steps: [
|
|
266
|
-
{
|
|
267
|
-
name: "Checkout release tag",
|
|
268
|
-
uses: "actions/checkout@v6",
|
|
269
|
-
with: { "fetch-depth": 1 },
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
name: "Dispatch release",
|
|
273
|
-
shell: "bash",
|
|
274
|
-
env: {
|
|
275
|
-
GH_TOKEN: "${{ github.token }}",
|
|
276
|
-
RELEASE_TAG: "${{ github.ref_name }}",
|
|
277
|
-
RELEASE_EVENT: DOWNSTREAM_RELEASE_EVENT,
|
|
278
|
-
RELEASE_WORKFLOWS: "",
|
|
279
|
-
},
|
|
280
|
-
run: [
|
|
281
|
-
`case "$RELEASE_TAG" in ${this.tagPrefix}*) ;; *) exit 1 ;; esac`,
|
|
282
|
-
'EXPECTED_SHA="$(git rev-parse "$RELEASE_TAG^{commit}")"',
|
|
283
|
-
'IFS="," read -r -a workflows <<< "$RELEASE_WORKFLOWS"',
|
|
284
|
-
'for workflow in "${workflows[@]}"; do',
|
|
285
|
-
" for status in in_progress queued requested waiting pending action_required; do",
|
|
286
|
-
" while IFS= read -r run_id; do",
|
|
287
|
-
' if [ -n "$run_id" ]; then gh run cancel "$run_id"; fi',
|
|
288
|
-
' done < <(gh run list --workflow "$workflow.yml" --status "$status" --limit 100 --json databaseId --jq \'.[].databaseId\')',
|
|
289
|
-
" done",
|
|
290
|
-
"done",
|
|
291
|
-
[
|
|
292
|
-
'gh api --method POST "repos/$GITHUB_REPOSITORY/dispatches"',
|
|
293
|
-
'--raw-field event_type="$RELEASE_EVENT"',
|
|
294
|
-
'--raw-field "client_payload[release_tag]=$RELEASE_TAG"',
|
|
295
|
-
'--raw-field "client_payload[expected_sha]=$EXPECTED_SHA"',
|
|
296
|
-
].join(" \\\n "),
|
|
297
|
-
].join("\n"),
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
},
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/** Author the common tag trigger, concurrency policy, permissions, and publish job. */
|
|
307
|
-
private authorPublishWorkflow(
|
|
308
|
-
project: DBXToolsNodeProject,
|
|
309
|
-
{ name, tagPrefix, steps, workingDirectory, upstreamWorkflow, rustArtifacts }: PublishWorkflow,
|
|
310
|
-
): void {
|
|
311
|
-
const setupSteps = publishSetupSteps(project);
|
|
312
|
-
const branchDispatch = upstreamWorkflow === undefined;
|
|
313
|
-
const workflow = new GithubWorkflow(project.github!, name, {
|
|
314
|
-
// A newer release supersedes an older run even when publication has
|
|
315
|
-
// started; the dispatcher also cancels active ecosystem runs immediately.
|
|
316
|
-
limitConcurrency: true,
|
|
317
|
-
concurrencyOptions: { group: name, cancelInProgress: true },
|
|
318
|
-
});
|
|
319
|
-
// Read-only floor for any job that does not declare its own permissions;
|
|
320
|
-
// the publish job below overrides it with the `id-token` it needs.
|
|
321
|
-
workflow.file?.addOverride("permissions", {
|
|
322
|
-
contents: "read",
|
|
323
|
-
...(upstreamWorkflow || rustArtifacts ? { actions: "read" } : {}),
|
|
324
|
-
});
|
|
325
|
-
if (upstreamWorkflow) {
|
|
326
|
-
workflow.file?.addOverride("on.workflow_run", {
|
|
327
|
-
workflows: [upstreamWorkflow],
|
|
328
|
-
types: ["completed"],
|
|
329
|
-
});
|
|
330
|
-
} else {
|
|
331
|
-
workflow.file?.addOverride("on.repository_dispatch", {
|
|
332
|
-
types: [DOWNSTREAM_RELEASE_EVENT],
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
// Manual trigger for testing the workflow WITHOUT reaching npm: a
|
|
336
|
-
// `workflow_dispatch` run has no tag, so the publish script forces
|
|
337
|
-
// `--dry-run` (pack + validate only). The `dry_run` input (default true)
|
|
338
|
-
// additionally lets a tag push be dry-run on demand.
|
|
339
|
-
workflow.file?.addOverride("on.workflow_dispatch", {
|
|
340
|
-
inputs: {
|
|
341
|
-
...(branchDispatch
|
|
342
|
-
? {
|
|
343
|
-
release_tag: {
|
|
344
|
-
description: "Release tag to package during a dry run",
|
|
345
|
-
type: "string",
|
|
346
|
-
required: true,
|
|
347
|
-
},
|
|
348
|
-
expected_sha: {
|
|
349
|
-
description: "Commit the release tag must reference",
|
|
350
|
-
type: "string",
|
|
351
|
-
required: true,
|
|
352
|
-
},
|
|
353
|
-
}
|
|
354
|
-
: {}),
|
|
355
|
-
dry_run: {
|
|
356
|
-
description: "Pack and validate but do not upload to npm",
|
|
357
|
-
type: "boolean",
|
|
358
|
-
default: true,
|
|
124
|
+
function verifyContextJob(tagPrefix: string): Job {
|
|
125
|
+
return {
|
|
126
|
+
runsOn: ["ubuntu-latest"],
|
|
127
|
+
permissions: { actions: JobPermission.READ, contents: JobPermission.READ },
|
|
128
|
+
outputs: {
|
|
129
|
+
release_tag: { stepId: "release", outputName: "release_tag" },
|
|
130
|
+
expected_sha: { stepId: "release", outputName: "expected_sha" },
|
|
131
|
+
release_version: { stepId: "release", outputName: "release_version" },
|
|
132
|
+
},
|
|
133
|
+
steps: [
|
|
134
|
+
{
|
|
135
|
+
name: "Checkout release tag",
|
|
136
|
+
uses: "actions/checkout@v6",
|
|
137
|
+
with: {
|
|
138
|
+
ref: "${{ github.event_name == 'push' && github.ref || inputs.expected_sha }}",
|
|
139
|
+
"fetch-depth": 1,
|
|
359
140
|
},
|
|
360
141
|
},
|
|
361
|
-
});
|
|
362
|
-
workflow.addJob("publish", {
|
|
363
|
-
...(upstreamWorkflow
|
|
364
|
-
? {
|
|
365
|
-
if: "${{ github.event_name != 'workflow_run' || (github.event.workflow_run.conclusion == 'success' && (github.event.workflow_run.event == 'workflow_run' || github.event.workflow_run.event == 'push' || github.event.workflow_run.event == 'repository_dispatch')) }}",
|
|
366
|
-
}
|
|
367
|
-
: {}),
|
|
368
|
-
runsOn: ["ubuntu-latest"],
|
|
369
|
-
// `id-token: write` lets npm mint the OIDC token for provenance attestation.
|
|
370
|
-
permissions: {
|
|
371
|
-
...(upstreamWorkflow || rustArtifacts ? { actions: JobPermission.READ } : {}),
|
|
372
|
-
contents: JobPermission.READ,
|
|
373
|
-
idToken: JobPermission.WRITE,
|
|
374
|
-
},
|
|
375
|
-
timeoutMinutes: 30,
|
|
376
|
-
// `DRY_RUN_INPUT` is `--dry-run` when the dispatch input is true, else empty;
|
|
377
|
-
// the publish script also FORCES it on any `workflow_dispatch` run.
|
|
378
|
-
env: {
|
|
379
|
-
BUN_VERSION,
|
|
380
|
-
CI: "true",
|
|
381
|
-
DRY_RUN_INPUT: "${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}",
|
|
382
|
-
},
|
|
383
|
-
steps: [
|
|
384
|
-
...(upstreamWorkflow
|
|
385
|
-
? [
|
|
386
|
-
{
|
|
387
|
-
name: "Download release metadata",
|
|
388
|
-
uses: "actions/download-artifact@v8",
|
|
389
|
-
with: {
|
|
390
|
-
name: "release-metadata",
|
|
391
|
-
path: ".release",
|
|
392
|
-
"run-id": "${{ github.event.workflow_run.id }}",
|
|
393
|
-
"github-token": "${{ github.token }}",
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
name: "Read release metadata",
|
|
398
|
-
id: "release_metadata",
|
|
399
|
-
shell: "bash",
|
|
400
|
-
run: [
|
|
401
|
-
'RELEASE_TAG="$(cat .release/tag)"',
|
|
402
|
-
'EXPECTED_SHA="$(cat .release/sha)"',
|
|
403
|
-
'test -n "$RELEASE_TAG"',
|
|
404
|
-
'test -n "$EXPECTED_SHA"',
|
|
405
|
-
'echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"',
|
|
406
|
-
'echo "expected_sha=$EXPECTED_SHA" >> "$GITHUB_OUTPUT"',
|
|
407
|
-
].join("\n"),
|
|
408
|
-
},
|
|
409
|
-
]
|
|
410
|
-
: []),
|
|
411
|
-
{
|
|
412
|
-
...setupSteps[0]!,
|
|
413
|
-
...(upstreamWorkflow
|
|
414
|
-
? {
|
|
415
|
-
with: {
|
|
416
|
-
...setupSteps[0]!.with,
|
|
417
|
-
ref: "${{ steps.release_metadata.outputs.expected_sha }}",
|
|
418
|
-
"fetch-depth": 0,
|
|
419
|
-
},
|
|
420
|
-
}
|
|
421
|
-
: {
|
|
422
|
-
with: {
|
|
423
|
-
...setupSteps[0]!.with,
|
|
424
|
-
ref: RELEASE_SHA,
|
|
425
|
-
"fetch-depth": 1,
|
|
426
|
-
},
|
|
427
|
-
}),
|
|
428
|
-
},
|
|
429
|
-
...(upstreamWorkflow
|
|
430
|
-
? [
|
|
431
|
-
{
|
|
432
|
-
name: "Verify release source",
|
|
433
|
-
shell: "bash",
|
|
434
|
-
env: {
|
|
435
|
-
SOURCE_RELEASE_TAG: "${{ steps.release_metadata.outputs.release_tag }}",
|
|
436
|
-
EXPECTED_SHA: "${{ steps.release_metadata.outputs.expected_sha }}",
|
|
437
|
-
},
|
|
438
|
-
run: [
|
|
439
|
-
'git fetch --force origin "+refs/tags/$SOURCE_RELEASE_TAG:refs/tags/$SOURCE_RELEASE_TAG"',
|
|
440
|
-
'test "$(git rev-parse "$SOURCE_RELEASE_TAG^{commit}")" = "$EXPECTED_SHA"',
|
|
441
|
-
'test "$(git rev-parse HEAD)" = "$EXPECTED_SHA"',
|
|
442
|
-
`RELEASE_TAG="$(git tag --points-at HEAD --list "${tagPrefix}*" | sort -V | tail -1)"`,
|
|
443
|
-
'test -n "$RELEASE_TAG"',
|
|
444
|
-
'echo "RELEASE_TAG=$RELEASE_TAG" >> "$GITHUB_ENV"',
|
|
445
|
-
`echo "RELEASE_VERSION=\${RELEASE_TAG#${tagPrefix}}" >> "$GITHUB_ENV"`,
|
|
446
|
-
].join("\n"),
|
|
447
|
-
} satisfies JobStep,
|
|
448
|
-
]
|
|
449
|
-
: []),
|
|
450
|
-
...(branchDispatch ? releaseSourceSteps().slice(1) : []),
|
|
451
|
-
...(branchDispatch
|
|
452
|
-
? [
|
|
453
|
-
{
|
|
454
|
-
name: "Resolve package release tag",
|
|
455
|
-
shell: "bash",
|
|
456
|
-
env: {
|
|
457
|
-
SOURCE_RELEASE_TAG: RELEASE_TAG,
|
|
458
|
-
EXPECTED_SHA: RELEASE_SHA,
|
|
459
|
-
},
|
|
460
|
-
run: [
|
|
461
|
-
`SOURCE_VERSION="\${SOURCE_RELEASE_TAG#${this.tagPrefix}}"`,
|
|
462
|
-
`RELEASE_TAG="${tagPrefix}\${SOURCE_VERSION}"`,
|
|
463
|
-
'git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"',
|
|
464
|
-
'test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$EXPECTED_SHA"',
|
|
465
|
-
'echo "RELEASE_TAG=$RELEASE_TAG" >> "$GITHUB_ENV"',
|
|
466
|
-
`echo "RELEASE_VERSION=\${RELEASE_TAG#${tagPrefix}}" >> "$GITHUB_ENV"`,
|
|
467
|
-
"mkdir -p .release",
|
|
468
|
-
'printf "%s\\n" "$SOURCE_RELEASE_TAG" > .release/tag',
|
|
469
|
-
'printf "%s\\n" "$EXPECTED_SHA" > .release/sha',
|
|
470
|
-
].join("\n"),
|
|
471
|
-
} satisfies JobStep,
|
|
472
|
-
]
|
|
473
|
-
: []),
|
|
474
|
-
...setupSteps.slice(1),
|
|
475
|
-
{
|
|
476
|
-
name: "Upload release metadata",
|
|
477
|
-
uses: "actions/upload-artifact@v7",
|
|
478
|
-
with: { name: "release-metadata", path: ".release" },
|
|
479
|
-
},
|
|
480
|
-
...steps,
|
|
481
|
-
],
|
|
482
|
-
...(workingDirectory ? { defaults: { run: { workingDirectory } } } : {}),
|
|
483
|
-
});
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Emit the `release` GitHub workflow: push `<prefix>1.2.3` and every
|
|
488
|
-
* publishable package is published to npm at 1.2.3. Setting the
|
|
489
|
-
* version on every package first makes the pushed tag the published version
|
|
490
|
-
* (no bump math).
|
|
491
|
-
*/
|
|
492
|
-
private nodeBindingReleaseSteps(project: DBXToolsNodeProject): {
|
|
493
|
-
before: JobStep[];
|
|
494
|
-
after: JobStep[];
|
|
495
|
-
} {
|
|
496
|
-
const config = project.dbxToolsConfig.rust;
|
|
497
|
-
if (!object.isRecord(config) || !Array.isArray(config.bindings)) {
|
|
498
|
-
return { before: [], after: [] };
|
|
499
|
-
}
|
|
500
|
-
const bindings = orderRustBindings(config.bindings as RustWorkspaceMapping["bindings"]).filter(
|
|
501
|
-
(binding): binding is RustBindingMapping & { node: string; nodePackage: string } =>
|
|
502
|
-
Boolean(binding.node && binding.nodePackage),
|
|
503
|
-
);
|
|
504
|
-
if (bindings.length === 0) return { before: [], after: [] };
|
|
505
|
-
|
|
506
|
-
const before: JobStep[] = [
|
|
507
142
|
{
|
|
508
|
-
name: "
|
|
509
|
-
|
|
143
|
+
name: "Verify release context",
|
|
144
|
+
id: "release",
|
|
510
145
|
shell: "bash",
|
|
511
146
|
env: {
|
|
512
|
-
|
|
513
|
-
|
|
147
|
+
RELEASE_TAG:
|
|
148
|
+
"${{ github.event_name == 'push' && github.ref_name || inputs.release_tag }}",
|
|
149
|
+
EXPECTED_SHA:
|
|
150
|
+
"${{ github.event_name == 'workflow_dispatch' && inputs.expected_sha || '' }}",
|
|
151
|
+
DRY_RUN: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}",
|
|
514
152
|
},
|
|
515
|
-
run: [
|
|
153
|
+
run: [
|
|
154
|
+
`case "$RELEASE_TAG" in ${tagPrefix}*) ;; *) exit 1 ;; esac`,
|
|
155
|
+
'git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"',
|
|
156
|
+
'test "$(git cat-file -t "$RELEASE_TAG")" = "tag"',
|
|
157
|
+
'RELEASE_SHA="$(git rev-parse "$RELEASE_TAG^{commit}")"',
|
|
158
|
+
'test "$(git rev-parse HEAD)" = "$RELEASE_SHA"',
|
|
159
|
+
'if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then',
|
|
160
|
+
' test "$GITHUB_REF_TYPE" = "tag"',
|
|
161
|
+
' test "$GITHUB_REF_NAME" = "$RELEASE_TAG"',
|
|
162
|
+
' test "$RELEASE_SHA" = "$EXPECTED_SHA"',
|
|
163
|
+
' if [ -n "${{ inputs.source_run_id }}" ]; then',
|
|
164
|
+
' case "${{ inputs.stage }}" in node|python) ;; *) exit 1 ;; esac',
|
|
165
|
+
' case "${{ inputs.source_run_id }}" in *[!0-9]*|"") exit 1 ;; esac',
|
|
166
|
+
" fi",
|
|
167
|
+
"fi",
|
|
168
|
+
'echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"',
|
|
169
|
+
'echo "expected_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"',
|
|
170
|
+
`echo "release_version=\${RELEASE_TAG#${tagPrefix}}" >> "$GITHUB_OUTPUT"`,
|
|
171
|
+
].join("\n"),
|
|
516
172
|
},
|
|
517
173
|
{
|
|
518
|
-
name: "
|
|
519
|
-
if: "${{
|
|
520
|
-
uses: "actions/
|
|
174
|
+
name: "Verify source artifact run",
|
|
175
|
+
if: "${{ inputs.source_run_id != '' }}",
|
|
176
|
+
uses: "actions/github-script@v8",
|
|
177
|
+
env: {
|
|
178
|
+
EXPECTED_SHA: "${{ steps.release.outputs.expected_sha }}",
|
|
179
|
+
SOURCE_RUN_ID: "${{ inputs.source_run_id }}",
|
|
180
|
+
},
|
|
521
181
|
with: {
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
182
|
+
script: [
|
|
183
|
+
"const run = await github.rest.actions.getWorkflowRun({",
|
|
184
|
+
" owner: context.repo.owner,",
|
|
185
|
+
" repo: context.repo.repo,",
|
|
186
|
+
" run_id: Number(process.env.SOURCE_RUN_ID),",
|
|
187
|
+
"});",
|
|
188
|
+
'if (run.data.path !== ".github/workflows/release.yml") core.setFailed("Source run is not release.yml");',
|
|
189
|
+
'if (run.data.head_sha !== process.env.EXPECTED_SHA) core.setFailed("Source run commit does not match the release tag");',
|
|
190
|
+
].join("\n"),
|
|
527
191
|
},
|
|
528
192
|
},
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function nodePublishJob(project: DBXToolsJavaScriptProject): Job {
|
|
198
|
+
return {
|
|
199
|
+
if: releaseStageCondition("node"),
|
|
200
|
+
needs: ["verify-context"],
|
|
201
|
+
runsOn: ["ubuntu-latest"],
|
|
202
|
+
permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
|
|
203
|
+
timeoutMinutes: 30,
|
|
204
|
+
env: { BUN_VERSION, CI: "true" },
|
|
205
|
+
steps: [
|
|
206
|
+
...nodeReleaseSetupSteps(project),
|
|
529
207
|
{
|
|
530
|
-
name: "
|
|
531
|
-
|
|
532
|
-
env: {
|
|
533
|
-
NPM_CONFIG_PROVENANCE: "true",
|
|
534
|
-
NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
535
|
-
NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
536
|
-
},
|
|
208
|
+
name: "Compile, package, and publish npm workspace",
|
|
209
|
+
env: { RELEASE_VERSION, ...npmPublishEnvironment() },
|
|
537
210
|
run: [
|
|
538
|
-
"
|
|
539
|
-
'
|
|
211
|
+
"chmod -R u+w . || true",
|
|
212
|
+
'bun node_modules/@dbx-tools/projen/tasks/publish.ts "$RELEASE_VERSION" $DRY_RUN',
|
|
540
213
|
].join("\n"),
|
|
541
214
|
},
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function addDocsJobs(
|
|
220
|
+
workflow: GithubWorkflow,
|
|
221
|
+
project: DBXToolsJavaScriptProject,
|
|
222
|
+
options: ReleaseDocsOptions,
|
|
223
|
+
): void {
|
|
224
|
+
workflow.addJob("build-docs", {
|
|
225
|
+
if: releaseStageCondition("docs"),
|
|
226
|
+
needs: ["verify-context"],
|
|
227
|
+
runsOn: ["ubuntu-latest"],
|
|
228
|
+
permissions: {
|
|
229
|
+
contents: JobPermission.READ,
|
|
230
|
+
pages: JobPermission.WRITE,
|
|
231
|
+
idToken: JobPermission.WRITE,
|
|
232
|
+
},
|
|
233
|
+
timeoutMinutes: 30,
|
|
234
|
+
env: {
|
|
235
|
+
BUN_VERSION,
|
|
236
|
+
DOCS_SITE_URL: options.siteUrl,
|
|
237
|
+
DOCS_BASE: options.base ?? "/",
|
|
238
|
+
},
|
|
239
|
+
steps: [
|
|
240
|
+
...releaseSourceSteps(),
|
|
241
|
+
...bunCacheRestoreSteps(project),
|
|
542
242
|
{
|
|
543
|
-
name: "
|
|
544
|
-
|
|
545
|
-
|
|
243
|
+
name: "Setup Node.js",
|
|
244
|
+
uses: "actions/setup-node@v6",
|
|
245
|
+
with: { "node-version": "22" },
|
|
546
246
|
},
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
{
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
}
|
|
579
|
-
|
|
247
|
+
{ name: "Configure Pages", uses: "actions/configure-pages@v5" },
|
|
248
|
+
{ name: "Install dependencies", run: "bun install" },
|
|
249
|
+
{ name: "Generate docs from READMEs", run: "bun docs/scripts/sync-readmes.mjs" },
|
|
250
|
+
{
|
|
251
|
+
name: "Check generated titles",
|
|
252
|
+
run: "bun docs/scripts/check-generated-titles.mjs",
|
|
253
|
+
},
|
|
254
|
+
{ name: "Install docs dependencies", run: "bun install --cwd .docs-build/site" },
|
|
255
|
+
bunCacheSaveStep(),
|
|
256
|
+
{
|
|
257
|
+
name: "Generate TypeScript API docs",
|
|
258
|
+
run: "bun docs/scripts/generate-api-docs.mjs",
|
|
259
|
+
},
|
|
260
|
+
{ name: "Build docs", run: "bun run --cwd .docs-build/site build" },
|
|
261
|
+
{
|
|
262
|
+
name: "Check generated links",
|
|
263
|
+
run: "bun run --cwd .docs-build/site check-links",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "Upload Pages artifact",
|
|
267
|
+
uses: "actions/upload-pages-artifact@v4",
|
|
268
|
+
with: { path: ".docs-build/dist" },
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
workflow.addJob("deploy-docs", {
|
|
273
|
+
if: releasePublishCondition("docs"),
|
|
274
|
+
needs: ["build-docs"],
|
|
275
|
+
environment: {
|
|
276
|
+
name: "github-pages",
|
|
277
|
+
url: "${{ steps.deployment.outputs.page_url }}",
|
|
278
|
+
},
|
|
279
|
+
runsOn: ["ubuntu-latest"],
|
|
280
|
+
permissions: { pages: JobPermission.WRITE, idToken: JobPermission.WRITE },
|
|
281
|
+
timeoutMinutes: 15,
|
|
282
|
+
steps: [
|
|
283
|
+
{
|
|
284
|
+
name: "Deploy to GitHub Pages",
|
|
285
|
+
id: "deployment",
|
|
286
|
+
uses: "actions/deploy-pages@v4",
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
});
|
|
290
|
+
}
|
|
580
291
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
// first), rewriting `workspace:*` sibling deps to `^<version>` so the
|
|
595
|
-
// published tarballs resolve each other. `bun publish` honors
|
|
596
|
-
// `publishConfig` (compiled `lib/` entry points) and provenance.
|
|
597
|
-
{
|
|
598
|
-
name: "Set version from tag and publish",
|
|
599
|
-
// Exclude every standalone-release dir (e.g. `projen`): it publishes on
|
|
600
|
-
// its own `<prefix>-v*` tag via its own workflow, not the main `v*` one.
|
|
601
|
-
run: BUN_PUBLISH_SCRIPT(
|
|
602
|
-
this.tagPrefix,
|
|
603
|
-
this.standaloneReleases.map((s) => s.directory),
|
|
604
|
-
),
|
|
605
|
-
env: {
|
|
606
|
-
// `bun publish` authenticates via NPM_CONFIG_TOKEN (not NODE_AUTH_TOKEN,
|
|
607
|
-
// which is the `npm publish` convention). Set both so either tool works.
|
|
608
|
-
NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
609
|
-
NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
610
|
-
NPM_CONFIG_PROVENANCE: "true",
|
|
611
|
-
},
|
|
612
|
-
},
|
|
613
|
-
...bindingSteps.after,
|
|
614
|
-
],
|
|
292
|
+
/** Owns the single release workflow and the local bump task. */
|
|
293
|
+
export class DBXToolsRelease extends Component {
|
|
294
|
+
constructor(project: DBXToolsJavaScriptProject, options: DBXToolsReleaseOptions = {}) {
|
|
295
|
+
super(project);
|
|
296
|
+
const tagPrefix = options.tagPrefix ?? "v";
|
|
297
|
+
releaseTagPrefixes.set(project, tagPrefix);
|
|
298
|
+
if (options.nodeRelease !== false) nodeReleaseProjects.add(project);
|
|
299
|
+
applyTasks(project, {
|
|
300
|
+
bump: {
|
|
301
|
+
exec: taskScript(project, "bump.ts", `--prefix ${tagPrefix}`),
|
|
302
|
+
receiveArgs: true,
|
|
303
|
+
description: "Bump the release version (default patch), then commit, tag, and push it",
|
|
304
|
+
},
|
|
615
305
|
});
|
|
616
|
-
|
|
306
|
+
if (!project.github) return;
|
|
617
307
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
"
|
|
655
|
-
"
|
|
656
|
-
|
|
657
|
-
// publish task's stamp phase), so bun resolves the engine's
|
|
658
|
-
// `workspace:*` sibling deps to the release version at pack time.
|
|
659
|
-
`bun ${stampScript} "$VERSION" --stamp-only`,
|
|
660
|
-
// Then publish ONLY the standalone directory.
|
|
661
|
-
`cd ${directory} && bun publish --access public $DRY_RUN`,
|
|
662
|
-
].join("\n"),
|
|
663
|
-
env: {
|
|
664
|
-
// `bun publish` authenticates via NPM_CONFIG_TOKEN (not NODE_AUTH_TOKEN,
|
|
665
|
-
// which is the `npm publish` convention). Set both so either tool works.
|
|
666
|
-
NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
667
|
-
NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
|
|
668
|
-
NPM_CONFIG_PROVENANCE: "true",
|
|
308
|
+
const workflow = new GithubWorkflow(project.github, "release", {
|
|
309
|
+
fileName: "release.yml",
|
|
310
|
+
limitConcurrency: true,
|
|
311
|
+
concurrencyOptions: { group: "release", cancelInProgress: true },
|
|
312
|
+
});
|
|
313
|
+
workflow.runName =
|
|
314
|
+
"release ${{ github.event_name == 'push' && github.ref_name || inputs.release_tag }}";
|
|
315
|
+
workflow.on({
|
|
316
|
+
push: { tags: [`${tagPrefix}*`] },
|
|
317
|
+
workflowDispatch: {
|
|
318
|
+
inputs: {
|
|
319
|
+
release_tag: {
|
|
320
|
+
description: "Annotated release tag to validate",
|
|
321
|
+
type: "string",
|
|
322
|
+
required: true,
|
|
323
|
+
},
|
|
324
|
+
expected_sha: {
|
|
325
|
+
description: "Commit the release tag must reference",
|
|
326
|
+
type: "string",
|
|
327
|
+
required: true,
|
|
328
|
+
},
|
|
329
|
+
stage: {
|
|
330
|
+
description: "Release stage to build, validate, or recover",
|
|
331
|
+
type: "choice",
|
|
332
|
+
options: ["all", "node", "python", "docs"],
|
|
333
|
+
default: "all",
|
|
334
|
+
required: true,
|
|
335
|
+
},
|
|
336
|
+
source_run_id: {
|
|
337
|
+
description: "Earlier release workflow run containing Rust artifacts",
|
|
338
|
+
type: "string",
|
|
339
|
+
default: "",
|
|
340
|
+
required: false,
|
|
341
|
+
},
|
|
342
|
+
dry_run: {
|
|
343
|
+
description: "Build and validate without publishing",
|
|
344
|
+
type: "boolean",
|
|
345
|
+
default: "true",
|
|
346
|
+
required: true,
|
|
669
347
|
},
|
|
670
348
|
},
|
|
671
|
-
|
|
349
|
+
},
|
|
672
350
|
});
|
|
351
|
+
workflow.file?.addOverride("permissions.contents", "read");
|
|
352
|
+
workflow.file?.addOverride("on.workflow_dispatch.inputs.dry_run.default", true);
|
|
353
|
+
workflow.addJob("verify-context", verifyContextJob(tagPrefix));
|
|
354
|
+
if (options.nodeRelease !== false) {
|
|
355
|
+
workflow.addJob("publish-node", nodePublishJob(project));
|
|
356
|
+
}
|
|
357
|
+
if (options.docs) {
|
|
358
|
+
addDocsJobs(workflow, project, options.docs);
|
|
359
|
+
}
|
|
360
|
+
releaseWorkflows.set(project, workflow);
|
|
673
361
|
}
|
|
674
362
|
}
|