@dbx-tools/projen 0.9.18 → 0.9.21

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.
Files changed (105) hide show
  1. package/._LICENSE +0 -0
  2. package/._README.md +0 -0
  3. package/._index.ts +0 -0
  4. package/._package.json +0 -0
  5. package/README.md +72 -88
  6. package/index.ts +2 -7
  7. package/package.json +9 -5
  8. package/src/.__lazy-require.ts +0 -0
  9. package/src/.__release-platform.ts +0 -0
  10. package/src/.__rust-project.ts +0 -0
  11. package/src/.__rust-release-workflow.ts +0 -0
  12. package/src/.__task-command.ts +0 -0
  13. package/src/._barrels.ts +0 -0
  14. package/src/._bun-app.ts +0 -0
  15. package/src/._bun-workflow.ts +0 -0
  16. package/src/._clean.ts +0 -0
  17. package/src/._codegen.ts +0 -0
  18. package/src/._dbx-tools-config.ts +0 -0
  19. package/src/._engine-root.ts +0 -0
  20. package/src/._generated.ts +0 -0
  21. package/src/._mixin.ts +0 -0
  22. package/src/._module-exports.ts +0 -0
  23. package/src/._openapi.ts +0 -0
  24. package/src/._packages.ts +0 -0
  25. package/src/._pnpm-workspace.ts +0 -0
  26. package/src/._project-js.ts +0 -0
  27. package/src/._project-predicate.ts +0 -0
  28. package/src/._project-py.ts +0 -0
  29. package/src/._project-rs.ts +0 -0
  30. package/src/._project.ts +0 -0
  31. package/src/._projen-version.ts +0 -0
  32. package/src/._publish.ts +0 -0
  33. package/src/._release-catalog.ts +0 -0
  34. package/src/._release-dispatch.ts +0 -0
  35. package/src/._release-github.ts +0 -0
  36. package/src/._release.ts +0 -0
  37. package/src/._scaffold.ts +0 -0
  38. package/src/._tags.ts +0 -0
  39. package/src/._tsconfig.ts +0 -0
  40. package/src/._uniffi.ts +0 -0
  41. package/src/._vscode.ts +0 -0
  42. package/src/._watch.ts +0 -0
  43. package/src/._workspace-lock.ts +0 -0
  44. package/src/._workspace-version.ts +0 -0
  45. package/src/_rust-release-workflow.ts +18 -582
  46. package/src/_task-command.ts +75 -0
  47. package/src/project-js.ts +2 -42
  48. package/src/project-py.ts +60 -162
  49. package/src/project-rs.ts +4 -100
  50. package/src/release-catalog.ts +37 -203
  51. package/src/release.ts +96 -775
  52. package/src/scaffold.ts +2 -6
  53. package/src/workspace-version.ts +9 -121
  54. package/tasks/._barrels.ts +0 -0
  55. package/tasks/._bump.ts +0 -0
  56. package/tasks/._clean.ts +0 -0
  57. package/tasks/._compile-workspace.ts +0 -0
  58. package/tasks/._local-publish.ts +0 -0
  59. package/tasks/._openapi.ts +0 -0
  60. package/tasks/._projenrc.ts +0 -0
  61. package/tasks/._publish-npm.ts +0 -0
  62. package/tasks/._publish-python.ts +0 -0
  63. package/tasks/._publish-uniffi-local.ts +0 -0
  64. package/tasks/._publish.ts +0 -0
  65. package/tasks/._python-registry.ts +0 -0
  66. package/tasks/._release-assets.ts +0 -0
  67. package/tasks/._release-candidate.ts +0 -0
  68. package/tasks/._release-manifest.ts +0 -0
  69. package/tasks/._release-pr.ts +0 -0
  70. package/tasks/._release-summary.ts +0 -0
  71. package/tasks/._release-version.ts +0 -0
  72. package/tasks/._rust.ts +0 -0
  73. package/tasks/._sync.ts +0 -0
  74. package/tasks/._uniffi-python.d.ts +0 -0
  75. package/tasks/._uniffi-python.js +0 -0
  76. package/tasks/._uniffi-release.mjs +0 -0
  77. package/tasks/._uniffi.ts +0 -0
  78. package/tasks/._version-check.ts +0 -0
  79. package/tasks/bump.ts +17 -34
  80. package/tasks/local-publish.ts +124 -117
  81. package/tasks/publish-npm.ts +125 -41
  82. package/tasks/publish-python.ts +79 -106
  83. package/tasks/publish-uniffi-local.ts +129 -84
  84. package/tasks/publish.ts +44 -139
  85. package/tasks/python-registry.ts +2 -9
  86. package/tasks/release-assets.ts +56 -65
  87. package/tasks/release-candidate.ts +416 -0
  88. package/tasks/release-manifest.ts +273 -0
  89. package/tasks/release-pr.ts +312 -369
  90. package/tasks/release-summary.ts +8 -10
  91. package/tasks/release-version.ts +23 -2
  92. package/tasks/rust.ts +8 -13
  93. package/tasks/uniffi-python.d.ts +2 -3
  94. package/tasks/uniffi-python.js +7 -20
  95. package/tasks/uniffi-release.mjs +19 -13
  96. package/tasks/uniffi.ts +15 -24
  97. package/tasks/version-check.ts +24 -56
  98. package/src/release-plan.ts +0 -269
  99. package/src/release-please.ts +0 -242
  100. package/tasks/release-bootstrap.ts +0 -59
  101. package/tasks/release-plan.ts +0 -89
  102. package/tasks/release-please.ts +0 -162
  103. package/tasks/release-request.ts +0 -166
  104. package/tasks/release-summary-units.ts +0 -75
  105. package/tasks/stamp-python.ts +0 -13
package/src/release.ts CHANGED
@@ -7,21 +7,23 @@ import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workf
7
7
  import { projectReleaseBranch, type DBXToolsJavaScriptProject } from "./project-js.ts";
8
8
  import { applyTasks, taskScript } from "./project.ts";
9
9
  import {
10
+ RELEASE_SHA,
11
+ RELEASE_TAG,
10
12
  RELEASE_VERSION,
11
13
  releaseSourceSteps,
12
14
  type ReleaseSummaryProviderName,
13
15
  } from "./release-dispatch.ts";
14
16
  import { readWorkspaceVersion } from "./workspace-version.ts";
15
17
 
16
- const NODE_VERSION = "lts/*";
18
+ const NODE_VERSION = "24";
19
+ const NPM_VERSION = "11.4.2";
17
20
  const NPM_REGISTRY_URL = "https://registry.npmjs.org";
18
21
  const nodeReleaseProjects = new WeakSet<DBXToolsJavaScriptProject>();
19
22
  const releaseTagPrefixes = new WeakMap<DBXToolsJavaScriptProject, string>();
20
23
  const releaseWorkflows = new WeakMap<DBXToolsJavaScriptProject, GithubWorkflow>();
21
- const independentPublicationJobs = new WeakMap<GithubWorkflow, Set<string>>();
22
24
 
23
25
  /** Independently recoverable portions of the release workflow. */
24
- export type ReleaseStage = "all" | "node" | "python" | "docs";
26
+ export type ReleaseStage = "all" | "node" | "python" | "cargo" | "docs";
25
27
 
26
28
  /** GitHub Pages configuration included in the unified release workflow. */
27
29
  export interface ReleaseDocsOptions {
@@ -55,14 +57,8 @@ export interface DBXToolsReleaseOptions {
55
57
  readonly docs?: ReleaseDocsOptions;
56
58
  /** Python package root passed to local release preparation. */
57
59
  readonly pythonRoot?: string;
58
- /** Repository task names run in the release worktree before expensive validation and publication. */
60
+ /** Repository task names run after VERSION generation and before candidate construction. */
59
61
  readonly validationTasks?: readonly string[];
60
- /**
61
- * Existing source branch safely fast-forwarded after publication.
62
- *
63
- * Missing or diverged branches are left untouched.
64
- */
65
- readonly syncBranch?: string | false;
66
62
  /**
67
63
  * Generate a versioned release summary locally. Defaults to enabled with the
68
64
  * standard Cursor, Codex, Claude fallback order.
@@ -82,13 +78,6 @@ export function tryReleaseWorkflow(project: DBXToolsJavaScriptProject): GithubWo
82
78
  return releaseWorkflows.get(project);
83
79
  }
84
80
 
85
- /** Register one publication job for the independent release completion barrier. */
86
- export function registerIndependentPublicationJob(workflow: GithubWorkflow, jobId: string): void {
87
- const jobs = independentPublicationJobs.get(workflow) ?? new Set<string>();
88
- jobs.add(jobId);
89
- independentPublicationJobs.set(workflow, jobs);
90
- }
91
-
92
81
  /** Whether the unified workflow publishes the normal npm workspace. */
93
82
  export function hasNodeRelease(project: DBXToolsJavaScriptProject): boolean {
94
83
  return nodeReleaseProjects.has(project);
@@ -101,48 +90,32 @@ export function releaseTagPattern(project: DBXToolsJavaScriptProject): string {
101
90
  return `${prefix}*`;
102
91
  }
103
92
 
104
- /** Run a release stage on default-branch pushes or when selected for manual recovery. */
105
- export function releaseStageCondition(stage: Exclude<ReleaseStage, "all">): string {
106
- return `\${{ github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == '${stage}' }}`;
93
+ /** Run a release stage on promotion or when selected for manual recovery. */
94
+ export function releaseStageCondition(
95
+ stage: Exclude<ReleaseStage, "all">,
96
+ prerequisites: readonly string[] = [],
97
+ ): string {
98
+ if (prerequisites.length === 0) {
99
+ return `\${{ github.event_name == 'release' || inputs.stage == 'all' || inputs.stage == '${stage}' }}`;
100
+ }
101
+ const prefix = prerequisites.length
102
+ ? `always() && ${prerequisites.map((condition) => `(${condition})`).join(" && ")} && `
103
+ : "";
104
+ return `\${{ ${prefix}(github.event_name == 'release' || inputs.stage == 'all' || inputs.stage == '${stage}') }}`;
107
105
  }
108
106
 
109
107
  /** Publish a selected stage unless a manual run remains in dry-run mode. */
110
- export function releasePublishCondition(stage: Exclude<ReleaseStage, "all">): string {
111
- return `\${{ github.event_name == 'push' || (inputs.dry_run == false && (inputs.stage == 'all' || inputs.stage == '${stage}')) }}`;
112
- }
113
-
114
- /** Download release artifacts from this run or a verified earlier run. */
115
- export function releaseArtifactSteps(options: {
116
- readonly currentName: string;
117
- readonly recoveredName: string;
118
- readonly pattern: string;
119
- readonly path: string;
120
- readonly condition?: string;
121
- }): readonly JobStep[] {
122
- const shared = {
123
- pattern: options.pattern,
124
- path: options.path,
125
- "merge-multiple": true,
126
- };
127
- return [
128
- {
129
- name: options.currentName,
130
- if: `\${{ ${options.condition ? `${options.condition} && ` : ""}inputs.source_run_id == '' }}`,
131
- uses: "actions/download-artifact@v8",
132
- with: shared,
133
- },
134
- {
135
- name: options.recoveredName,
136
- if: `\${{ ${options.condition ? `${options.condition} && ` : ""}inputs.source_run_id != '' }}`,
137
- uses: "actions/download-artifact@v8",
138
- with: {
139
- ...shared,
140
- "run-id": "${{ inputs.source_run_id }}",
141
- "github-token": "${{ github.token }}",
142
- repository: "${{ github.repository }}",
143
- },
144
- },
145
- ];
108
+ export function releasePublishCondition(
109
+ stage: Exclude<ReleaseStage, "all">,
110
+ prerequisites: readonly string[] = [],
111
+ ): string {
112
+ if (prerequisites.length === 0) {
113
+ return `\${{ github.event_name == 'release' || (inputs.dry_run != true && (inputs.stage == 'all' || inputs.stage == '${stage}')) }}`;
114
+ }
115
+ const prefix = prerequisites.length
116
+ ? `always() && ${prerequisites.map((condition) => `(${condition})`).join(" && ")} && `
117
+ : "";
118
+ return `\${{ ${prefix}(github.event_name == 'release' || (inputs.dry_run != true && (inputs.stage == 'all' || inputs.stage == '${stage}'))) }}`;
146
119
  }
147
120
 
148
121
  /** Shared Bun, Node, cache, and install setup for Node release jobs. */
@@ -153,20 +126,24 @@ export function nodeReleaseSetupSteps(project: DBXToolsJavaScriptProject): reado
153
126
  {
154
127
  name: "Setup Node.js",
155
128
  uses: "actions/setup-node@v6",
156
- with: { "node-version": NODE_VERSION, "registry-url": NPM_REGISTRY_URL },
129
+ with: {
130
+ "node-version": NODE_VERSION,
131
+ "registry-url": NPM_REGISTRY_URL,
132
+ "package-manager-cache": false,
133
+ },
157
134
  },
135
+ { name: "Install npm CLI", run: `npm install --global npm@${NPM_VERSION}` },
158
136
  { name: "Install", run: "bun install" },
159
137
  bunCacheSaveStep(),
160
138
  ];
161
139
  }
162
140
 
163
- /** Authentication, provenance, and dry-run values shared by npm publishers. */
141
+ /** Token authentication, GitHub provenance, and dry-run values shared by npm publishers. */
164
142
  export function npmPublishEnvironment(): Record<string, string> {
165
143
  return {
166
- NPM_CONFIG_PROVENANCE:
167
- "${{ (github.event_name == 'push' || inputs.dry_run == false) && 'true' || 'false' }}",
168
- NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
169
144
  NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
145
+ NPM_CONFIG_PROVENANCE:
146
+ "${{ (github.event_name == 'release' || inputs.dry_run != true) && 'true' || 'false' }}",
170
147
  DRY_RUN:
171
148
  "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run && '--dry-run' || '' }}",
172
149
  };
@@ -175,19 +152,18 @@ export function npmPublishEnvironment(): Record<string, string> {
175
152
  function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
176
153
  return {
177
154
  runsOn: ["ubuntu-latest"],
178
- permissions: { actions: JobPermission.READ, contents: JobPermission.WRITE },
155
+ permissions: { contents: JobPermission.READ },
179
156
  outputs: {
180
157
  release_tag: { stepId: "release", outputName: "release_tag" },
181
158
  expected_sha: { stepId: "release", outputName: "expected_sha" },
182
159
  release_version: { stepId: "release", outputName: "release_version" },
183
- build_mode: { stepId: "release", outputName: "build_mode" },
184
160
  },
185
161
  steps: [
186
162
  {
187
163
  name: "Checkout release source",
188
164
  uses: "actions/checkout@v6",
189
165
  with: {
190
- ref: "${{ github.event_name == 'push' && github.sha || inputs.expected_sha }}",
166
+ ref: "${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}",
191
167
  "fetch-depth": 0,
192
168
  },
193
169
  },
@@ -203,90 +179,39 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
203
179
  shell: "bash",
204
180
  env: {
205
181
  RELEASE_TAG:
206
- "${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || '' }}",
207
- EXPECTED_SHA:
208
- "${{ github.event_name == 'workflow_dispatch' && inputs.expected_sha || '' }}",
209
- DRY_RUN: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}",
182
+ "${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}",
210
183
  GH_TOKEN: "${{ github.token }}",
211
184
  },
212
185
  // prettier-ignore
213
186
  run: stringUtils.dedent(
214
187
  // ============================================================================
215
188
  /*bash*/`
216
- if [ "$GITHUB_EVENT_NAME" = "push" ]; then
217
- test "$GITHUB_REF_TYPE" = "branch"
218
- test "$GITHUB_REF_NAME" = "${releaseBranch}"
219
- RELEASE_SHA="$GITHUB_SHA"
220
- test "$(git rev-parse HEAD)" = "$RELEASE_SHA"
221
- RELEASE_VERSION="$(tr -d '\\r\\n' < VERSION)"
222
- bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION"
223
- PREVIOUS_VERSION="$(git show "$RELEASE_SHA^:VERSION" | tr -d '\\r\\n')"
224
- test "$PREVIOUS_VERSION" != "$RELEASE_VERSION"
225
- RELEASE_TAG="${tagPrefix}$RELEASE_VERSION"
226
- if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then
227
- git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"
228
- test "$(git cat-file -t "$RELEASE_TAG")" = "tag"
229
- test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$RELEASE_SHA"
230
- else
231
- git fetch --force --tags origin
232
- bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION" --prefix ${JSON.stringify(tagPrefix)} --assert-next
233
- git config user.name "github-actions[bot]"
234
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
235
- git tag -a "$RELEASE_TAG" "$RELEASE_SHA" -m "$RELEASE_TAG"
236
- git push origin "refs/tags/$RELEASE_TAG"
237
- fi
238
- else
239
- case "$RELEASE_TAG" in ${tagPrefix}*) ;; *) exit 1 ;; esac
240
- RELEASE_VERSION="\${RELEASE_TAG#${tagPrefix}}"
241
- bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION"
242
- git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"
243
- test "$(git cat-file -t "$RELEASE_TAG")" = "tag"
244
- RELEASE_SHA="$(git rev-parse "$RELEASE_TAG^{commit}")"
245
- test "$(git rev-parse HEAD)" = "$RELEASE_SHA"
246
- test "$RELEASE_SHA" = "$EXPECTED_SHA"
247
- if [ -n "\${{ inputs.source_run_id }}" ]; then
248
- case "\${{ inputs.stage }}" in node|python) ;; *) exit 1 ;; esac
249
- case "\${{ inputs.source_run_id }}" in *[!0-9]*|"") exit 1 ;; esac
250
- fi
251
- fi
252
- BUILD_MODE="$(git log -1 --format=%B -- VERSION | sed -n 's/^Release-Build: //p' | tail -1)"
253
- case "$BUILD_MODE" in local|remote) ;; *) BUILD_MODE="remote" ;; esac
254
- if [ "$BUILD_MODE" = "local" ] && ! gh release view "$RELEASE_TAG" >/dev/null 2>&1; then
255
- gh release create "$RELEASE_TAG" --draft --title "$RELEASE_TAG" --target "$RELEASE_SHA"
256
- fi
189
+ case "$RELEASE_TAG" in ${tagPrefix}*) ;; *) exit 1 ;; esac
190
+ RELEASE_VERSION="\${RELEASE_TAG#${tagPrefix}}"
191
+ bun node_modules/@dbx-tools/projen/tasks/release-version.ts --version "$RELEASE_VERSION"
192
+ git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"
193
+ git fetch --force origin "+refs/heads/${releaseBranch}:refs/remotes/origin/${releaseBranch}"
194
+ test "$(git cat-file -t "$RELEASE_TAG")" = "tag"
195
+ RELEASE_SHA="$(git rev-parse "$RELEASE_TAG^{commit}")"
196
+ test "$(git rev-parse HEAD)" = "$RELEASE_SHA"
197
+ git merge-base --is-ancestor "$RELEASE_SHA" "origin/${releaseBranch}"
198
+ test "$(tr -d '\\r\\n' < VERSION)" = "$RELEASE_VERSION"
199
+ test "$(gh release view "$RELEASE_TAG" --json isDraft --jq .isDraft)" = "false"
200
+ rm -rf dist/release-candidate
201
+ mkdir -p dist/release-candidate
202
+ gh release download "$RELEASE_TAG" --dir dist/release-candidate
203
+ bun node_modules/@dbx-tools/projen/tasks/release-manifest.ts verify \
204
+ --directory dist/release-candidate \
205
+ --tag "$RELEASE_TAG" \
206
+ --sha "$RELEASE_SHA" \
207
+ --version "$RELEASE_VERSION"
257
208
  echo "release_tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
258
209
  echo "expected_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT"
259
210
  echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
260
- echo "build_mode=$BUILD_MODE" >> "$GITHUB_OUTPUT"
261
211
  `
262
212
  // ============================================================================
263
213
  ),
264
214
  },
265
- {
266
- name: "Verify source artifact run",
267
- if: "${{ inputs.source_run_id != '' }}",
268
- uses: "actions/github-script@v8",
269
- env: {
270
- EXPECTED_SHA: "${{ steps.release.outputs.expected_sha }}",
271
- SOURCE_RUN_ID: "${{ inputs.source_run_id }}",
272
- },
273
- with: {
274
- // prettier-ignore
275
- script: stringUtils.dedent(
276
- // ============================================================================
277
- /*js*/`
278
- const run = await github.rest.actions.getWorkflowRun({
279
- owner: context.repo.owner,
280
- repo: context.repo.repo,
281
- run_id: Number(process.env.SOURCE_RUN_ID),
282
- });
283
- if (run.data.path !== ".github/workflows/release.yml") core.setFailed("Source run is not release.yml");
284
- if (run.data.head_sha !== process.env.EXPECTED_SHA) core.setFailed("Source run commit does not match the release tag");
285
- `
286
- // ============================================================================
287
- ),
288
- },
289
- },
290
215
  ],
291
216
  };
292
217
  }
@@ -302,13 +227,27 @@ function nodePublishJob(project: DBXToolsJavaScriptProject): Job {
302
227
  steps: [
303
228
  ...nodeReleaseSetupSteps(project),
304
229
  {
305
- name: "Compile, package, and publish npm workspace",
306
- env: { RELEASE_VERSION, ...npmPublishEnvironment() },
230
+ name: "Download approved npm archives",
231
+ env: { GH_TOKEN: "${{ github.token }}", RELEASE_SHA, RELEASE_TAG, RELEASE_VERSION },
232
+ shell: "bash",
307
233
  run: [
308
- "chmod -R u+w . || true",
309
- 'bun node_modules/@dbx-tools/projen/tasks/publish.ts "$RELEASE_VERSION" $DRY_RUN',
234
+ "rm -rf dist/release-download dist/npm-release",
235
+ "mkdir -p dist/release-download",
236
+ 'gh release download "$RELEASE_TAG" --pattern release-manifest.json --pattern SHA256SUMS --pattern "*.tgz" --dir dist/release-download',
237
+ "bun node_modules/@dbx-tools/projen/tasks/release-manifest.ts verify \\",
238
+ " --directory dist/release-download \\",
239
+ ' --tag "$RELEASE_TAG" \\',
240
+ ' --sha "$RELEASE_SHA" \\',
241
+ ' --version "$RELEASE_VERSION" \\',
242
+ " --kind npm \\",
243
+ " --output dist/npm-release",
310
244
  ].join("\n"),
311
245
  },
246
+ {
247
+ name: "Publish approved npm archives",
248
+ env: { RELEASE_VERSION, ...npmPublishEnvironment() },
249
+ run: 'bun node_modules/@dbx-tools/projen/tasks/publish-npm.ts --directory dist/npm-release --version "$RELEASE_VERSION" $DRY_RUN',
250
+ },
312
251
  ],
313
252
  };
314
253
  }
@@ -366,627 +305,10 @@ function addDocsJobs(
366
305
  });
367
306
  }
368
307
 
369
- function independentReleasePleaseJob(project: DBXToolsJavaScriptProject, branch: string): Job {
370
- return {
371
- if: "${{ github.event_name == 'push' || inputs.automatic == true }}",
372
- runsOn: ["ubuntu-latest"],
373
- permissions: {
374
- actions: JobPermission.WRITE,
375
- contents: JobPermission.WRITE,
376
- pullRequests: JobPermission.WRITE,
377
- },
378
- timeoutMinutes: 30,
379
- env: { BUN_VERSION },
380
- outputs: {
381
- releases_created: { stepId: "release", outputName: "releases_created" },
382
- prs_created: { stepId: "release", outputName: "prs_created" },
383
- releases: { stepId: "release", outputName: "releases" },
384
- prs: { stepId: "release", outputName: "prs" },
385
- docs_changed: { stepId: "changes", outputName: "docs_changed" },
386
- recovery_requested: { stepId: "recovery", outputName: "requested" },
387
- recovery_component: { stepId: "recovery", outputName: "component" },
388
- recovery_version: { stepId: "recovery", outputName: "version" },
389
- },
390
- steps: [
391
- {
392
- name: "Checkout source",
393
- uses: "actions/checkout@v6",
394
- with: { "fetch-depth": 2 },
395
- },
396
- ...bunCacheRestoreSteps(project, {
397
- ignorePaths: project.workflowCacheIgnorePaths,
398
- }),
399
- { name: "Install dependencies", run: "bun install" },
400
- {
401
- name: "Refresh Release Please state",
402
- id: "release",
403
- env: { GITHUB_TOKEN: "${{ github.token }}" },
404
- run: [
405
- 'OWNER="${GITHUB_REPOSITORY%%/*}"',
406
- 'REPO="${GITHUB_REPOSITORY#*/}"',
407
- `bun node_modules/@dbx-tools/projen/tasks/release-please.ts --owner "$OWNER" --repo "$REPO" --target-branch ${JSON.stringify(branch)}`,
408
- ].join("\n"),
409
- },
410
- {
411
- name: "Detect documentation changes",
412
- id: "changes",
413
- shell: "bash",
414
- run: [
415
- "if git diff --quiet HEAD^ HEAD -- README.md AGENTS.md ':(glob)**/README.md' docs ':(exclude)docs/releases/**' ':(exclude).release-notes/**'; then",
416
- ' echo "docs_changed=false" >> "$GITHUB_OUTPUT"',
417
- "else",
418
- ' echo "docs_changed=true" >> "$GITHUB_OUTPUT"',
419
- "fi",
420
- ].join("\n"),
421
- },
422
- {
423
- name: "Detect committed recovery request",
424
- id: "recovery",
425
- shell: "bash",
426
- run: [
427
- "if [ -f .release-recovery.json ]; then",
428
- ' echo "requested=true" >> "$GITHUB_OUTPUT"',
429
- ' echo "component=$(jq -r .component .release-recovery.json)" >> "$GITHUB_OUTPUT"',
430
- ' echo "version=$(jq -r .version .release-recovery.json)" >> "$GITHUB_OUTPUT"',
431
- "else",
432
- ' echo "requested=false" >> "$GITHUB_OUTPUT"',
433
- "fi",
434
- ].join("\n"),
435
- },
436
- {
437
- name: "Reconcile generated release PR files",
438
- if: "${{ steps.release.outputs.prs_created == 'true' }}",
439
- env: {
440
- GH_TOKEN: "${{ github.token }}",
441
- RELEASE_BRANCH: branch,
442
- RELEASE_PRS: "${{ steps.release.outputs.prs }}",
443
- },
444
- shell: "bash",
445
- // prettier-ignore
446
- run: stringUtils.dedent(
447
- // ============================================================================
448
- /*bash*/`
449
- BRANCH="$(jq -r '.[0].headBranchName' <<<"$RELEASE_PRS")"
450
- test -n "$BRANCH" && test "$BRANCH" != "null"
451
- git fetch origin "$BRANCH"
452
- git switch --force-create "$BRANCH" "origin/$BRANCH"
453
- bun install
454
- bunx projen
455
- bun node_modules/@dbx-tools/projen/tasks/version-check.ts
456
- bun node_modules/@dbx-tools/projen/tasks/release-plan.ts --base-ref origin/${branch}
457
- bun node_modules/@dbx-tools/projen/tasks/release-summary-units.ts --from-ref origin/${branch}
458
- if ! git diff --quiet || test -n "$(git ls-files --others --exclude-standard)"; then
459
- git config user.name "github-actions[bot]"
460
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
461
- git add -A
462
- git commit -m "chore: reconcile release metadata"
463
- git push origin "HEAD:$BRANCH"
464
- fi
465
- MERGED="false"
466
- for ATTEMPT in $(seq 1 15); do
467
- STATE="$(gh pr view "$BRANCH" --json state,mergeable --jq '[.state,.mergeable] | join(" ")')"
468
- if [[ "$STATE" == MERGED* ]]; then MERGED="true"; break; fi
469
- if [ "$STATE" = "OPEN CONFLICTING" ]; then
470
- echo "::error::release pull request has merge conflicts"
471
- exit 1
472
- fi
473
- if [ "$STATE" = "OPEN MERGEABLE" ] && gh pr merge "$BRANCH" --merge; then
474
- MERGED="true"
475
- break
476
- fi
477
- echo "release pull request is not mergeable yet (attempt $ATTEMPT/15)"
478
- sleep 2
479
- done
480
- if [ "$MERGED" != "true" ]; then
481
- echo "::error::release pull request did not become mergeable"
482
- exit 1
483
- fi
484
- gh workflow run release.yml --ref "$RELEASE_BRANCH" -f automatic=true
485
- `
486
- // ============================================================================
487
- ),
488
- },
489
- ],
490
- };
491
- }
492
-
493
- function configureReleaseRequestWorkflow(
494
- project: DBXToolsJavaScriptProject,
495
- baseBranch: string,
496
- ): void {
497
- if (!project.github) return;
498
- const workflow = new GithubWorkflow(project.github, "release-request", {
499
- fileName: "release-request.yml",
500
- limitConcurrency: true,
501
- concurrencyOptions: {
502
- group: "release-request-${{ github.ref_name }}",
503
- cancelInProgress: false,
504
- },
505
- });
506
- workflow.runName = "release request ${{ github.ref_name }}";
507
- workflow.on({ push: {} });
508
- workflow.file?.addOverride("permissions.contents", "read");
509
- workflow.addJob("request", {
510
- if: `\${{ github.ref_name != '${baseBranch}' }}`,
511
- runsOn: ["ubuntu-latest"],
512
- permissions: {
513
- actions: JobPermission.WRITE,
514
- contents: JobPermission.WRITE,
515
- pullRequests: JobPermission.WRITE,
516
- },
517
- steps: [
518
- {
519
- name: "Checkout source branch",
520
- uses: "actions/checkout@v6",
521
- with: { "fetch-depth": 2 },
522
- },
523
- {
524
- name: "Read release request",
525
- id: "request",
526
- shell: "bash",
527
- // prettier-ignore
528
- run: stringUtils.dedent(
529
- // ============================================================================
530
- /*bash*/`
531
- MESSAGE="$(git log -1 --format=%B)"
532
- if ! grep -q "^Release-Request: true$" <<<"$MESSAGE"; then
533
- echo "requested=false" >> "$GITHUB_OUTPUT"
534
- exit 0
535
- fi
536
- git fetch origin ${JSON.stringify(baseBranch)}
537
- if git diff --quiet "origin/${baseBranch}...HEAD"; then
538
- echo "requested=false" >> "$GITHUB_OUTPUT"
539
- exit 0
540
- fi
541
- echo "requested=true" >> "$GITHUB_OUTPUT"
542
- echo "branch=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT"
543
- echo "notes_path=$(sed -n 's/^Release-Notes-Path: //p' <<<"$MESSAGE" | tail -1)" >> "$GITHUB_OUTPUT"
544
- echo "title=$(git log -1 --skip=1 --format=%s)" >> "$GITHUB_OUTPUT"
545
- `
546
- // ============================================================================
547
- ),
548
- },
549
- {
550
- name: "Create, merge, and release source pull request",
551
- if: "${{ steps.request.outputs.requested == 'true' }}",
552
- env: {
553
- GH_TOKEN: "${{ github.token }}",
554
- SOURCE_BRANCH: "${{ steps.request.outputs.branch }}",
555
- BASE_BRANCH: baseBranch,
556
- TITLE: "${{ steps.request.outputs.title }}",
557
- NOTES_PATH: "${{ steps.request.outputs.notes_path }}",
558
- },
559
- shell: "bash",
560
- // prettier-ignore
561
- run: stringUtils.dedent(
562
- // ============================================================================
563
- /*bash*/`
564
- test -f "$NOTES_PATH"
565
- NOTES="$(cat "$NOTES_PATH")"
566
- BODY="$(printf '## Release notes\\n\\n%s\\n' "$NOTES")"
567
- PR="$(gh pr list --head "$SOURCE_BRANCH" --base "$BASE_BRANCH" --state open --json number --jq '.[0].number // empty')"
568
- if [ -n "$PR" ]; then
569
- gh pr edit "$PR" --title "$TITLE" --body "$BODY"
570
- else
571
- gh pr create --head "$SOURCE_BRANCH" --base "$BASE_BRANCH" --title "$TITLE" --body "$BODY"
572
- PR="$(gh pr list --head "$SOURCE_BRANCH" --base "$BASE_BRANCH" --state open --json number --jq '.[0].number')"
573
- fi
574
- gh pr merge "$PR" --merge
575
- gh workflow run release.yml --repo "$GITHUB_REPOSITORY" --ref "$BASE_BRANCH" -f automatic=true
576
- `
577
- // ============================================================================
578
- ),
579
- },
580
- ],
581
- });
582
- }
583
-
584
- function independentReleasePlanJob(project: DBXToolsJavaScriptProject): Job {
585
- return {
586
- if: "${{ always() && ((github.event_name == 'workflow_dispatch' && inputs.automatic != true) || needs.release-please.outputs.releases_created == 'true' || (needs.release-please.outputs.docs_changed == 'true' && needs.release-please.outputs.prs_created != 'true') || needs.release-please.outputs.recovery_requested == 'true') }}",
587
- needs: ["release-please"],
588
- runsOn: ["ubuntu-latest"],
589
- permissions: { contents: JobPermission.READ },
590
- timeoutMinutes: 15,
591
- env: { BUN_VERSION },
592
- outputs: {
593
- release_sha: { stepId: "plan", outputName: "release_sha" },
594
- rust: { stepId: "plan", outputName: "rust" },
595
- python: { stepId: "plan", outputName: "python" },
596
- node: { stepId: "plan", outputName: "node" },
597
- github: { stepId: "plan", outputName: "github" },
598
- docs: { stepId: "plan", outputName: "docs" },
599
- units: { stepId: "plan", outputName: "units" },
600
- python_packages: { stepId: "plan", outputName: "python_packages" },
601
- rust_packages: { stepId: "plan", outputName: "rust_packages" },
602
- artifacts: { stepId: "plan", outputName: "artifacts" },
603
- rust_targets: { stepId: "plan", outputName: "rust_targets" },
604
- },
605
- steps: [
606
- {
607
- name: "Checkout release source",
608
- uses: "actions/checkout@v6",
609
- with: { "fetch-depth": 2 },
610
- },
611
- ...bunCacheRestoreSteps(project, {
612
- ignorePaths: project.workflowCacheIgnorePaths,
613
- }),
614
- { name: "Install dependencies", run: "bun install" },
615
- {
616
- name: "Build affected release plan",
617
- id: "plan",
618
- shell: "bash",
619
- env: {
620
- COMPONENT:
621
- "${{ inputs.component || needs.release-please.outputs.recovery_component || '' }}",
622
- VERSION: "${{ inputs.version || needs.release-please.outputs.recovery_version || '' }}",
623
- DOCS_CHANGED: "${{ needs.release-please.outputs.docs_changed || 'false' }}",
624
- },
625
- run: [
626
- "ARGS=()",
627
- 'if [ -n "$COMPONENT" ]; then ARGS+=(--component "$COMPONENT" --version "$VERSION"); fi',
628
- 'if [ "$DOCS_CHANGED" = "true" ]; then ARGS+=(--docs); fi',
629
- 'bun node_modules/@dbx-tools/projen/tasks/release-plan.ts "${ARGS[@]}"',
630
- ].join("\n"),
631
- },
632
- {
633
- name: "Upload release plan",
634
- uses: "actions/upload-artifact@v7",
635
- with: {
636
- name: "release-plan",
637
- path: "dist/release-plan.json",
638
- "if-no-files-found": "error",
639
- },
640
- },
641
- ],
642
- };
643
- }
644
-
645
- /** Checkout and install the immutable source selected by an independent release plan. */
646
- export function independentReleaseSetupSteps(
647
- project: DBXToolsJavaScriptProject,
648
- ): readonly JobStep[] {
649
- return [
650
- {
651
- name: "Checkout release source",
652
- uses: "actions/checkout@v6",
653
- with: { ref: "${{ needs.release-plan.outputs.release_sha }}", "fetch-depth": 1 },
654
- },
655
- ...bunCacheRestoreSteps(project, {
656
- ignorePaths: project.workflowCacheIgnorePaths,
657
- }),
658
- { name: "Install dependencies", run: "bun install" },
659
- {
660
- name: "Download release plan",
661
- uses: "actions/download-artifact@v8",
662
- with: { name: "release-plan", path: "dist" },
663
- },
664
- ];
665
- }
666
-
667
- function independentNodePublishJob(project: DBXToolsJavaScriptProject): Job {
668
- return {
669
- if: "${{ needs.release-plan.outputs.node == 'true' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'node') }}",
670
- needs: ["release-plan"],
671
- runsOn: ["ubuntu-latest"],
672
- permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
673
- timeoutMinutes: 60,
674
- env: { BUN_VERSION, CI: "true" },
675
- steps: [
676
- ...independentReleaseSetupSteps(project),
677
- {
678
- name: "Publish affected npm packages",
679
- env: npmPublishEnvironment(),
680
- run: "bun node_modules/@dbx-tools/projen/tasks/publish.ts --plan dist/release-plan.json",
681
- },
682
- ],
683
- };
684
- }
685
-
686
- function independentReleaseNotesJob(project: DBXToolsJavaScriptProject): Job {
687
- return {
688
- if: "${{ needs.release-plan.outputs.units != '[]' }}",
689
- needs: ["release-plan"],
690
- runsOn: ["ubuntu-latest"],
691
- permissions: { contents: JobPermission.WRITE },
692
- env: { BUN_VERSION },
693
- steps: [
694
- ...independentReleaseSetupSteps(project),
695
- {
696
- name: "Publish component release notes",
697
- env: { GH_TOKEN: "${{ github.token }}" },
698
- shell: "bash",
699
- run: [
700
- "jq -c '.units[]' dist/release-plan.json | while read -r UNIT; do",
701
- ' COMPONENT="$(jq -r .component <<<"$UNIT")"',
702
- ' VERSION="$(jq -r .newVersion <<<"$UNIT")"',
703
- ' gh release edit "$COMPONENT-v$VERSION" --notes-file ".release-notes/final/$COMPONENT-v$VERSION.md"',
704
- "done",
705
- ].join("\n"),
706
- },
707
- {
708
- name: "Remove consumed release notes",
709
- shell: "bash",
710
- // prettier-ignore
711
- run: stringUtils.dedent(
712
- // ============================================================================
713
- /*bash*/`
714
- git fetch origin ${JSON.stringify(projectReleaseBranch(project))}
715
- if [ "$(git rev-parse origin/${projectReleaseBranch(project)})" != "$(git rev-parse HEAD)" ]; then
716
- echo "::warning::main advanced before release-note cleanup; leaving temporary notes for the next release"
717
- exit 0
718
- fi
719
- git rm -r --ignore-unmatch .release-notes
720
- if ! git diff --cached --quiet; then
721
- git config user.name "github-actions[bot]"
722
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
723
- git commit -m "chore: remove consumed release notes"
724
- git push origin "HEAD:${projectReleaseBranch(project)}"
725
- fi
726
- `
727
- // ============================================================================
728
- ),
729
- },
730
- ],
731
- };
732
- }
733
-
734
- function addIndependentDocsJobs(
735
- workflow: GithubWorkflow,
736
- project: DBXToolsJavaScriptProject,
737
- options: ReleaseDocsOptions,
738
- ): void {
739
- workflow.addJob("build-docs", {
740
- if: "${{ always() && needs['release-plan'].outputs.docs == 'true' && needs['publication-complete'].result == 'success' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'docs') }}",
741
- needs: ["release-plan", "publication-complete"],
742
- runsOn: ["ubuntu-latest"],
743
- permissions: {
744
- contents: JobPermission.READ,
745
- pages: JobPermission.WRITE,
746
- idToken: JobPermission.WRITE,
747
- },
748
- timeoutMinutes: 30,
749
- env: {
750
- BUN_VERSION,
751
- DOCS_SITE_URL: options.siteUrl,
752
- DOCS_BASE: options.base ?? "/",
753
- },
754
- steps: [
755
- ...independentReleaseSetupSteps(project),
756
- ...options.prepareSteps,
757
- bunCacheSaveStep(),
758
- ...options.buildSteps,
759
- {
760
- name: "Upload Pages artifact",
761
- uses: "actions/upload-pages-artifact@v4",
762
- with: { path: options.artifactPath },
763
- },
764
- ],
765
- });
766
- workflow.addJob("deploy-docs", {
767
- if: "${{ always() && needs['build-docs'].result == 'success' }}",
768
- needs: ["build-docs"],
769
- environment: {
770
- name: "github-pages",
771
- url: "${{ steps.deployment.outputs.page_url }}",
772
- },
773
- runsOn: ["ubuntu-latest"],
774
- permissions: { pages: JobPermission.WRITE, idToken: JobPermission.WRITE },
775
- timeoutMinutes: 15,
776
- steps: [
777
- {
778
- name: "Deploy to GitHub Pages",
779
- id: "deployment",
780
- uses: "actions/deploy-pages@v4",
781
- },
782
- ],
783
- });
784
- }
785
-
786
- function addIndependentBranchSyncJob(
787
- workflow: GithubWorkflow,
788
- project: DBXToolsJavaScriptProject,
789
- branch: string,
790
- ): void {
791
- const releaseBranch = projectReleaseBranch(project);
792
- workflow.addJob("sync-release-branch", {
793
- if: "${{ always() && (github.event_name == 'push' || inputs.automatic == true) && needs['publication-complete'].result == 'success' }}",
794
- needs: ["publication-complete"],
795
- runsOn: ["ubuntu-latest"],
796
- permissions: { contents: JobPermission.WRITE },
797
- env: { BUN_VERSION },
798
- steps: [
799
- {
800
- name: "Checkout released main",
801
- uses: "actions/checkout@v6",
802
- with: { "fetch-depth": 0 },
803
- },
804
- ...bunCacheRestoreSteps(project, {
805
- ignorePaths: project.workflowCacheIgnorePaths,
806
- }),
807
- { name: "Install dependencies", run: "bun install" },
808
- {
809
- name: `Safely sync ${branch}`,
810
- shell: "bash",
811
- env: { SOURCE_BRANCH: branch, RELEASE_BRANCH: releaseBranch },
812
- // prettier-ignore
813
- run: stringUtils.dedent(
814
- // ============================================================================
815
- /*bash*/`
816
- if ! git ls-remote --exit-code --heads origin "refs/heads/$SOURCE_BRANCH" >/dev/null 2>&1; then
817
- echo "source branch $SOURCE_BRANCH no longer exists; nothing to sync"
818
- exit 0
819
- fi
820
- git fetch origin "refs/heads/$SOURCE_BRANCH:refs/remotes/origin/$SOURCE_BRANCH"
821
- git fetch origin "refs/heads/$RELEASE_BRANCH:refs/remotes/origin/$RELEASE_BRANCH"
822
- if git merge-base --is-ancestor "origin/$SOURCE_BRANCH" "origin/$RELEASE_BRANCH"; then
823
- git push origin "refs/remotes/origin/$RELEASE_BRANCH:refs/heads/$SOURCE_BRANCH"
824
- elif git merge-base --is-ancestor "origin/$RELEASE_BRANCH" "origin/$SOURCE_BRANCH"; then
825
- echo "source branch $SOURCE_BRANCH already contains released main"
826
- else
827
- git config user.name "github-actions[bot]"
828
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
829
- git switch --force-create "$SOURCE_BRANCH" "origin/$SOURCE_BRANCH"
830
- if git merge --no-ff --no-edit "origin/$RELEASE_BRANCH"; then
831
- git push origin "HEAD:refs/heads/$SOURCE_BRANCH"
832
- else
833
- SAFE_GENERATED="true"
834
- while IFS= read -r FILE; do
835
- case "$FILE" in
836
- .projen/release-units.json|.release-please-manifest.json|.release-units/*/source.json|.release-units/*/version.txt|.release-units/*/CHANGELOG.md|Cargo.lock|*/Cargo.toml|*/package.json|*/pyproject.toml|*/index.ts) ;;
837
- *) SAFE_GENERATED="false" ;;
838
- esac
839
- done < <(git diff --name-only --diff-filter=U)
840
- if [ "$SAFE_GENERATED" = "true" ] && bunx projen; then
841
- git add -A
842
- if [ -z "$(git diff --name-only --diff-filter=U)" ]; then
843
- git commit --no-edit
844
- git push origin "HEAD:refs/heads/$SOURCE_BRANCH"
845
- exit 0
846
- fi
847
- fi
848
- git merge --abort
849
- echo "::warning::source branch $SOURCE_BRANCH has handwritten or unresolved conflicts with released main; leaving it untouched"
850
- fi
851
- fi
852
- `
853
- // ============================================================================
854
- ),
855
- },
856
- ],
857
- });
858
- }
859
-
860
- class IndependentReleaseFinalizer extends Component {
861
- private finalized = false;
862
-
863
- constructor(
864
- project: DBXToolsJavaScriptProject,
865
- private readonly workflow: GithubWorkflow,
866
- private readonly docs: ReleaseDocsOptions | undefined,
867
- private readonly syncBranch: string | undefined,
868
- ) {
869
- super(project);
870
- }
871
-
872
- public override preSynthesize(): void {
873
- if (this.finalized) return;
874
- this.finalized = true;
875
- const jobs = [...(independentPublicationJobs.get(this.workflow) ?? [])].sort();
876
- const successful = jobs
877
- .map((job) => `needs.${job}.result != 'failure' && needs.${job}.result != 'cancelled'`)
878
- .join(" && ");
879
- this.workflow.addJob("publication-complete", {
880
- if: `\${{ always()${successful ? ` && ${successful}` : ""} }}`,
881
- needs: jobs,
882
- runsOn: ["ubuntu-latest"],
883
- permissions: { contents: JobPermission.READ },
884
- steps: [{ name: "Confirm publication stages", run: "true" }],
885
- });
886
- if (this.docs) {
887
- addIndependentDocsJobs(this.workflow, this.project as DBXToolsJavaScriptProject, this.docs);
888
- }
889
- if (this.syncBranch) {
890
- addIndependentBranchSyncJob(
891
- this.workflow,
892
- this.project as DBXToolsJavaScriptProject,
893
- this.syncBranch,
894
- );
895
- }
896
- }
897
- }
898
-
899
- function configureIndependentRelease(
900
- project: DBXToolsJavaScriptProject,
901
- options: DBXToolsReleaseOptions,
902
- ): void {
903
- const branch = projectReleaseBranch(project);
904
- if (options.nodeRelease !== false) nodeReleaseProjects.add(project);
905
- applyTasks(project, {
906
- "version:check": {
907
- exec: taskScript(project, "version-check.ts"),
908
- description: "Verify every package against its release unit",
909
- },
910
- "release:plan": {
911
- exec: taskScript(project, "release-plan.ts"),
912
- receiveArgs: true,
913
- description: "Build the affected release plan",
914
- },
915
- release: {
916
- exec: taskScript(project, "release-request.ts", `--base ${JSON.stringify(branch)}`),
917
- receiveArgs: true,
918
- description: "Commit, annotate, and push a source release request",
919
- },
920
- "release:refresh": {
921
- exec: taskScript(project, "release-please.ts", `--target-branch ${JSON.stringify(branch)}`),
922
- receiveArgs: true,
923
- description: "Refresh Release Please PRs, tags, and GitHub Releases",
924
- },
925
- });
926
- if (!project.github) return;
927
- const workflow = new GithubWorkflow(project.github, "release", {
928
- fileName: "release.yml",
929
- limitConcurrency: true,
930
- concurrencyOptions: { group: "release", cancelInProgress: false },
931
- });
932
- workflow.runName =
933
- "release units " +
934
- "${{ github.event_name == 'push' && github.sha || inputs.automatic && 'automatic' || inputs.component }}";
935
- workflow.on({
936
- push: { branches: [branch] },
937
- workflowDispatch: {
938
- inputs: {
939
- automatic: {
940
- description: "Continue an automatically merged Release Please pull request",
941
- type: "boolean",
942
- default: "false",
943
- required: false,
944
- },
945
- component: {
946
- description: "Component to recover",
947
- type: "string",
948
- default: "",
949
- required: false,
950
- },
951
- version: {
952
- description: "Component version to recover",
953
- type: "string",
954
- default: "",
955
- required: false,
956
- },
957
- stage: {
958
- description: "Publication target to recover",
959
- type: "choice",
960
- options: ["all", "node", "python", "rust", "github", "docs"],
961
- default: "all",
962
- required: true,
963
- },
964
- },
965
- },
966
- });
967
- workflow.file?.addOverride("on.workflow_dispatch.inputs.automatic.default", false);
968
- workflow.file?.addOverride("permissions.contents", "read");
969
- workflow.addJob("release-please", independentReleasePleaseJob(project, branch));
970
- workflow.addJob("release-plan", independentReleasePlanJob(project));
971
- workflow.addJob("publish-release-notes", independentReleaseNotesJob(project));
972
- registerIndependentPublicationJob(workflow, "publish-release-notes");
973
- if (options.nodeRelease !== false) {
974
- workflow.addJob("publish-node", independentNodePublishJob(project));
975
- registerIndependentPublicationJob(workflow, "publish-node");
976
- }
977
- new IndependentReleaseFinalizer(project, workflow, options.docs, options.syncBranch || undefined);
978
- configureReleaseRequestWorkflow(project, branch);
979
- releaseWorkflows.set(project, workflow);
980
- }
981
-
982
308
  /** Owns the single release workflow and local release preparation tasks. */
983
309
  export class DBXToolsRelease extends Component {
984
310
  constructor(project: DBXToolsJavaScriptProject, options: DBXToolsReleaseOptions = {}) {
985
311
  super(project);
986
- if (project.releaseCatalog.mode === "independent") {
987
- configureIndependentRelease(project, options);
988
- return;
989
- }
990
312
  const tagPrefix = options.tagPrefix ?? "v";
991
313
  const releaseBranch = projectReleaseBranch(project);
992
314
  const summary = options.summary ?? true;
@@ -1028,12 +350,18 @@ export class DBXToolsRelease extends Component {
1028
350
  ),
1029
351
  receiveArgs: true,
1030
352
  description:
1031
- "Prepare, validate, locally publish, and automatically merge a release PR",
353
+ "Prepare, validate, locally publish, and upload a draft release candidate",
1032
354
  },
1033
355
  "release:assets": {
1034
- exec: taskScript(project, "release-assets.ts"),
356
+ exec: taskScript(
357
+ project,
358
+ "release-candidate.ts",
359
+ options.pythonRoot
360
+ ? `--python-root ${JSON.stringify(options.pythonRoot)}`
361
+ : undefined,
362
+ ),
1035
363
  receiveArgs: true,
1036
- description: "Build and upload native assets for an existing GitHub Release",
364
+ description: "Rebuild and upload a complete draft release candidate",
1037
365
  },
1038
366
  }
1039
367
  : {}),
@@ -1043,14 +371,18 @@ export class DBXToolsRelease extends Component {
1043
371
  const workflow = new GithubWorkflow(project.github, "release", {
1044
372
  fileName: "release.yml",
1045
373
  limitConcurrency: true,
1046
- concurrencyOptions: { group: "release", cancelInProgress: false },
374
+ concurrencyOptions: {
375
+ group:
376
+ "release-${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}",
377
+ cancelInProgress: false,
378
+ },
1047
379
  });
1048
380
  const version = readWorkspaceVersion(project.outdir);
1049
381
  workflow.runName =
1050
382
  `release ${version} ` +
1051
- "${{ github.event_name == 'push' && github.sha || inputs.release_tag }}";
383
+ "${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release_tag }}";
1052
384
  workflow.on({
1053
- push: { branches: [releaseBranch], paths: ["VERSION"] },
385
+ release: { types: ["published"] },
1054
386
  workflowDispatch: {
1055
387
  inputs: {
1056
388
  release_tag: {
@@ -1058,24 +390,13 @@ export class DBXToolsRelease extends Component {
1058
390
  type: "string",
1059
391
  required: true,
1060
392
  },
1061
- expected_sha: {
1062
- description: "Commit the release tag must reference",
1063
- type: "string",
1064
- required: true,
1065
- },
1066
393
  stage: {
1067
- description: "Release stage to build, validate, or recover",
394
+ description: "Published release stage to validate or recover",
1068
395
  type: "choice",
1069
- options: ["all", "node", "python", "docs"],
396
+ options: ["all", "node", "python", "cargo", "docs"],
1070
397
  default: "all",
1071
398
  required: true,
1072
399
  },
1073
- source_run_id: {
1074
- description: "Earlier release workflow run containing Rust artifacts",
1075
- type: "string",
1076
- default: "",
1077
- required: false,
1078
- },
1079
400
  dry_run: {
1080
401
  description: "Build and validate without publishing",
1081
402
  type: "boolean",