@dbx-tools/projen 0.9.144 → 0.9.146

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 CHANGED
@@ -540,9 +540,14 @@ the current branch is pushed first. A non-`main` branch then fast-forwards
540
540
  diverged. From `main`, it calls the existing `bump` task, commits the generated
541
541
  version changes, pushes `main`, and pushes the matching annotated `vX.Y.Z` tag.
542
542
  Pass `--no-bump` only to release an existing synchronized local bump. The tag
543
- workflow verifies that the tag points at the exact `origin/main` commit. One
544
- `build-release` job installs workspace dependencies, runs validation, packs the
545
- npm archives, builds all Python distributions, and builds the documentation
543
+ workflow verifies that the tag points at the exact `origin/main` commit. The
544
+ local task waits for this workflow by default, displays live job and step
545
+ progress, then prints its logs. Local-only releases and repositories without a
546
+ resolved authenticated GitHub account skip the watcher. Stopping the local
547
+ watcher does not cancel the remote run.
548
+
549
+ One `build-release` job installs workspace dependencies, runs validation, packs
550
+ the npm archives, builds all Python distributions, and builds the documentation
546
551
  site. Install validation prerequisites through `releaseSetupSteps`.
547
552
  `releaseDocs.prepareSteps` runs after validation.
548
553
 
@@ -553,56 +558,37 @@ publisher without a checkout or workspace install. Pages deploys the already
553
558
  built site after registry publication succeeds. Local npm and Python registry publication remains available
554
559
  through the direct local publication tooling.
555
560
 
556
- The default release still publishes npm, Python, and docs, then publishes to
557
- configured local registries automatically. Select a scope or disable individual
558
- steps for one run without changing the Projen definition:
561
+ The default release publishes npm, Python, and docs, then publishes to
562
+ configured local registries. Use `bun run release --help` or the generated
563
+ [command reference](#release) for the complete option set. Common workflows:
559
564
 
560
565
  ```bash
566
+ bun run release
561
567
  bun run release --publish pypi --no-docs
562
- bun run release --no-npm --no-local-publish
563
568
  bun run release --publish local --install never
564
- bun run release --local-registry false --local-pypi auto
565
569
  bun run release --demo-deploy --profile <profile>
566
- bun run release --no-release-notes
567
- bun run release --release-notes-text "# Release 1.2.3"
568
- bun run release --release-notes-file ./notes.md
569
- bun run release --release-notes-instructions "Focus on operator-visible changes."
570
570
  ```
571
571
 
572
- `--demo-deploy` is off by default. After tagging and any local registry
573
- publication it runs `bun run demo:deploy`, which stages the AppKit demo from
574
- locally compiled Node packages and locally built Python wheels, resolves
575
- the default environment or configured credentials through
572
+ Demo deployment is a local post-tag action. It stages locally compiled Node
573
+ packages and Python wheels, resolves credentials through
576
574
  `@dbx-tools/auth`, including `DATABRICKS_CONFIG_PROFILE`, `DATABRICKS_HOST`,
577
575
  and the other auth options, force-refreshes its token with interactive login
578
576
  fallback, and deploys it. When auth selects a profile, the deploy passes that
579
- resolved profile explicitly to every bundle command. That step is local only
580
- and is not recorded in the annotated tag.
581
-
582
- Pass `--profile <name>` to set `DATABRICKS_CONFIG_PROFILE` only for that local
583
- demo deployment. The profile is not forwarded as a release subcommand
584
- argument or stored in the release tag.
577
+ resolved profile explicitly to every bundle command. The deployment selection
578
+ and profile are not recorded in the annotated tag.
585
579
 
586
580
  After bump and configured validation, release writes
587
581
  `docs/releases/vX.Y.Z.md` with `dbx genie exec -C "$PWD" --sandbox read-only
588
- --ephemeral -o`. The standard prompt forbids running tests or other validation
589
- commands. If Genie fails or writes an empty file, the step keeps going with a
590
- short git-log summary. `--no-release-notes` skips the file.
591
- `--release-notes-text` writes supplied markdown directly, while
592
- `--release-notes-file` copies a prepared markdown file. Both bypass Genie.
593
- `--release-notes-instructions` appends run-specific guidance to the standard
594
- Genie prompt. Notes are committed with the version bump and are not recorded in
595
- the annotated tag. They are skipped on `--no-bump` because that path requires an
596
- already committed tree.
597
-
598
- `--install auto` keeps Projen's normal local dependency-install behavior.
599
- `always` performs a manifest-resolved install first, and `never` uses dependencies already
600
- installed while still regenerating versioned sources. Local release validation
601
- runs `eslint:fix` before branch preparation and bump, then runs the selected
602
- fail-closed tasks. CI remains check-only.
603
- CI rebuilds the selected release artifacts instead of checking generated-source fingerprints.
604
- `--no-validation` skips optional task checks locally and in CI, not workspace-version or immutable-source checks.
605
- `--no-docs` skips building and deploying the site, not package README validation.
582
+ --ephemeral -o`. The standard prompt cannot run validation commands. Failures
583
+ fall back to a short git-log summary. Notes are committed with the version bump,
584
+ not stored in the annotated tag, and are omitted for an existing committed bump.
585
+
586
+ Automatic installation retains Projen's manifest-based install trigger; the
587
+ other install modes force installation or reuse the current dependency tree.
588
+ Local release validation runs `eslint:fix` before branch preparation and bump,
589
+ then runs the selected fail-closed tasks. CI remains check-only and rebuilds
590
+ selected release artifacts instead of checking generated-source fingerprints.
591
+
606
592
  Scopes select artifacts, not the normal commit and tag-push transaction.
607
593
  The annotated tag records CI step selections, so publishing a selected scope does
608
594
  not change the next release's defaults. Local registry URLs stay local.
@@ -652,37 +638,39 @@ release [options]
652
638
 
653
639
  #### Options
654
640
 
655
- | Option | Description |
656
- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
657
- | `--root <value>` | Repository or workspace root (env: PROJEN_RELEASE_ROOT) |
658
- | `--branch <value>` | Release branch (default: "main", env: PROJEN_RELEASE_BRANCH) |
659
- | `--prefix <value>` | Release tag prefix (default: "v", env: PROJEN_RELEASE_PREFIX) |
660
- | `--remote <value>` | Git remote (default: "origin", env: PROJEN_RELEASE_REMOTE) |
661
- | `--python-root <value>` | Python package root for local publish (default: "packages/py", env: PROJEN_RELEASE_PYTHON_ROOT) |
662
- | `--validate <value>` | Repeatable task to run before pushing (default: [], env: PROJEN_RELEASE_VALIDATE) |
663
- | `--bump` | Create and synchronize a patch version bump (default: true, env: PROJEN_RELEASE_BUMP) |
664
- | `--no-bump` | Disable create and synchronize a patch version bump |
665
- | `--publish <value>` | Publication scope (choices: "auto", "npm", "pypi", "local", "none", default: "auto", env: PROJEN_RELEASE_PUBLISH) |
666
- | `-i, --install <value>` | Local workspace dependency installation (choices: "auto", "always", "never", default: "auto", env: PROJEN_RELEASE_INSTALL) |
667
- | `-n, --npm` | Build and publish npm packages (default: true, env: PROJEN_RELEASE_NPM) |
668
- | `--no-npm` | Disable build and publish npm packages |
669
- | `--pypi` | Build and publish Python packages (default: true, env: PROJEN_RELEASE_PYPI) |
670
- | `--no-pypi` | Disable build and publish python packages |
671
- | `--docs` | Build and deploy documentation (env: PROJEN_RELEASE_DOCS) |
672
- | `--no-docs` | Disable build and deploy documentation |
673
- | `--validation` | Run optional release validation tasks before publishing (default: true, env: PROJEN_RELEASE_VALIDATION) |
674
- | `--no-validation` | Disable run optional release validation tasks before publishing |
675
- | `--release-notes` | Write release notes before tagging (default: true, env: PROJEN_RELEASE_RELEASE_NOTES) |
676
- | `--no-release-notes` | Disable write release notes before tagging |
677
- | `--release-notes-text <value>` | Write supplied release-note Markdown without invoking Genie (env: PROJEN_RELEASE_RELEASE_NOTES_TEXT) |
678
- | `--release-notes-file <value>` | Copy release-note Markdown from this file without invoking Genie (env: PROJEN_RELEASE_RELEASE_NOTES_FILE) |
679
- | `--release-notes-instructions <value>` | Append instructions to the Genie release-note prompt (env: PROJEN_RELEASE_RELEASE_NOTES_INSTRUCTIONS) |
680
- | `--demo-deploy` | Deploy the AppKit demo after tagging and local publication (env: PROJEN_RELEASE_DEMO_DEPLOY) |
681
- | `--no-demo-deploy` | Disable deploy the appkit demo after tagging and local publication |
682
- | `--profile <value>` | Optional Databricks CLI profile passed to the demo deployment (env: PROJEN_RELEASE_PROFILE) |
683
- | `--local-publish` | Publish to configured local registries (default: true, env: PROJEN_RELEASE_LOCAL_PUBLISH) |
684
- | `--no-local-publish` | Disable publish to configured local registries |
685
- | `--local-registry <value>` | Local npm registry selection: auto, false, or URL (default: "auto", env: PROJEN_RELEASE_LOCAL_REGISTRY) |
686
- | `--local-pypi <value>` | Local devpi registry selection: auto, false, or URL (default: "auto", env: PROJEN_RELEASE_LOCAL_PYPI) |
641
+ | Option | Description |
642
+ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
643
+ | `--root <value>` | Repository or workspace root (env: PROJEN_RELEASE_ROOT) |
644
+ | `--branch <value>` | Release branch (default: "main", env: PROJEN_RELEASE_BRANCH) |
645
+ | `--prefix <value>` | Release tag prefix (default: "v", env: PROJEN_RELEASE_PREFIX) |
646
+ | `--remote <value>` | Git remote (default: "origin", env: PROJEN_RELEASE_REMOTE) |
647
+ | `--python-root <value>` | Python package root for local publish (default: "packages/py", env: PROJEN_RELEASE_PYTHON_ROOT) |
648
+ | `--validate <value>` | Repeatable task to run before pushing (default: [], env: PROJEN_RELEASE_VALIDATE) |
649
+ | `--bump` | Create and synchronize a patch bump; disable only for an existing committed bump (default: true, env: PROJEN_RELEASE_BUMP) |
650
+ | `--no-bump` | Disable create and synchronize a patch bump; disable only for an existing committed bump |
651
+ | `--publish <value>` | Publication scope (choices: "auto", "npm", "pypi", "local", "none", default: "auto", env: PROJEN_RELEASE_PUBLISH) |
652
+ | `-i, --install <value>` | Dependency install mode: auto follows manifests, always installs, never reuses (choices: "auto", "always", "never", default: "auto", env: PROJEN_RELEASE_INSTALL) |
653
+ | `-n, --npm` | Build and publish npm packages (default: true, env: PROJEN_RELEASE_NPM) |
654
+ | `--no-npm` | Disable build and publish npm packages |
655
+ | `--pypi` | Build and publish Python packages (default: true, env: PROJEN_RELEASE_PYPI) |
656
+ | `--no-pypi` | Disable build and publish python packages |
657
+ | `--docs` | Build and deploy documentation (env: PROJEN_RELEASE_DOCS) |
658
+ | `--no-docs` | Disable build and deploy documentation |
659
+ | `--validation` | Run optional release validation tasks before publishing (default: true, env: PROJEN_RELEASE_VALIDATION) |
660
+ | `--no-validation` | Disable run optional release validation tasks before publishing |
661
+ | `-w, --watch-github-actions` | Wait for and stream the tag-triggered GitHub Actions release (default: true, env: PROJEN_RELEASE_WATCH_GITHUB_ACTIONS) |
662
+ | `--no-watch-github-actions` | Disable wait for and stream the tag-triggered github actions release |
663
+ | `--release-notes` | Generate and commit release notes for a new version bump (default: true, env: PROJEN_RELEASE_RELEASE_NOTES) |
664
+ | `--no-release-notes` | Disable generate and commit release notes for a new version bump |
665
+ | `--release-notes-text <value>` | Write supplied release-note Markdown without invoking Genie (env: PROJEN_RELEASE_RELEASE_NOTES_TEXT) |
666
+ | `--release-notes-file <value>` | Copy release-note Markdown from this file without invoking Genie (env: PROJEN_RELEASE_RELEASE_NOTES_FILE) |
667
+ | `--release-notes-instructions <value>` | Append instructions to the Genie release-note prompt (env: PROJEN_RELEASE_RELEASE_NOTES_INSTRUCTIONS) |
668
+ | `--demo-deploy` | Run the local-only AppKit demo deployment after tagging and publication (env: PROJEN_RELEASE_DEMO_DEPLOY) |
669
+ | `--no-demo-deploy` | Disable run the local-only appkit demo deployment after tagging and publication |
670
+ | `--profile <value>` | Optional Databricks CLI profile passed to the demo deployment (env: PROJEN_RELEASE_PROFILE) |
671
+ | `--local-publish` | Publish to configured local registries (default: true, env: PROJEN_RELEASE_LOCAL_PUBLISH) |
672
+ | `--no-local-publish` | Disable publish to configured local registries |
673
+ | `--local-registry <value>` | Local npm registry selection: auto, false, or URL (default: "auto", env: PROJEN_RELEASE_LOCAL_REGISTRY) |
674
+ | `--local-pypi <value>` | Local devpi registry selection: auto, false, or URL (default: "auto", env: PROJEN_RELEASE_LOCAL_PYPI) |
687
675
 
688
676
  <!-- cli-reference:end -->
package/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
5
  export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
6
- export const PACKAGE_VERSION = "0.9.144";
6
+ export const PACKAGE_VERSION = "0.9.146";
7
7
  export * as barrels from "./src/barrels.ts";
8
8
  export * as bunApp from "./src/bun-app.ts";
9
9
  export * as bunWorkflow from "./src/bun-workflow.ts";
@@ -65,7 +65,7 @@ export { PYTHON_NODE_RUNTIME_DISTRIBUTION, PYTHON_NODE_RUNTIME_MODULE, PYTHON_NO
65
65
  export type { PythonNodeRuntimeOptions } from "./src/python-node-runtime.ts";
66
66
  export { DBXToolsRelease } from "./src/release.ts";
67
67
  export type { ReleaseDocsOptions, DBXToolsReleaseOptions } from "./src/release.ts";
68
- export { RELEASE_VERSION } from "./src/release-context.ts";
68
+ export { RELEASE_VERSION, RELEASE_WORKFLOW_FILE } from "./src/release-context.ts";
69
69
  export { RELEASE_PUBLISH_TARGETS, RELEASE_INSTALL_MODES, ReleaseStepSelectionSchema } from "./src/release-options.ts";
70
70
  export type { ReleasePublishTarget, ReleaseInstallMode, ReleaseStepSelection, ReleaseSelectionOptions } from "./src/release-options.ts";
71
71
  export { SHARED_CORE_BINDING_EXPORTS } from "./src/shared-core-usage.ts";
package/package.json CHANGED
@@ -30,10 +30,10 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^1.7.0",
33
- "@dbx-tools/cli-args": "^0.9.144",
34
- "@dbx-tools/core": "^0.9.144",
35
- "@dbx-tools/path": "^0.9.144",
36
- "@dbx-tools/shared-core": "^0.9.144",
33
+ "@dbx-tools/cli-args": "^0.9.146",
34
+ "@dbx-tools/core": "^0.9.146",
35
+ "@dbx-tools/path": "^0.9.146",
36
+ "@dbx-tools/shared-core": "^0.9.146",
37
37
  "commander": "^15.0.0",
38
38
  "concurrently": "^10.0.3",
39
39
  "constructs": "^10.6.0",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "main": "index.ts",
57
57
  "license": "Apache-2.0",
58
- "version": "0.9.144",
58
+ "version": "0.9.146",
59
59
  "types": "index.ts",
60
60
  "type": "module",
61
61
  "exports": {
@@ -1,3 +1,5 @@
1
1
  /** Verified release context shared by every job in the unified workflow. */
2
2
  /** Version verified by the shared build that produced every publication artifact. */
3
3
  export const RELEASE_VERSION = "${{ needs.build-release.outputs.release_version }}";
4
+ /** Generated GitHub Actions workflow triggered by release tags. */
5
+ export const RELEASE_WORKFLOW_FILE = "release.yml";
package/src/release.ts CHANGED
@@ -16,7 +16,7 @@ import { GithubWorkflow } from "projen/lib/github";
16
16
  import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
17
17
  import { BUN_VERSION } from "./bun-workflow.ts";
18
18
  import { projectReleaseBranch, taskCommand, type DBXToolsJavaScriptProject } from "./project-js.ts";
19
- import { RELEASE_VERSION } from "./release-context.ts";
19
+ import { RELEASE_VERSION, RELEASE_WORKFLOW_FILE } from "./release-context.ts";
20
20
  import type { ReleaseStepSelection } from "./release-options.ts";
21
21
 
22
22
  const NODE_VERSION = "24";
@@ -339,7 +339,7 @@ export class DBXToolsRelease extends Component {
339
339
  if (!project.github) return;
340
340
 
341
341
  const workflow = new GithubWorkflow(project.github, "release", {
342
- fileName: "release.yml",
342
+ fileName: RELEASE_WORKFLOW_FILE,
343
343
  limitConcurrency: true,
344
344
  concurrencyOptions: {
345
345
  group: "release",
package/tasks/release.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import { readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import * as projectUtils from "@dbx-tools/core/project-utils";
6
- import { errorUtils, log } from "@dbx-tools/shared-core";
6
+ import { errorUtils, json, log } from "@dbx-tools/shared-core";
7
7
  import type { Command } from "commander";
8
8
  import { z } from "zod";
9
9
  import { parsedTaskOptions, runTaskMain, taskCommand } from "./cli.ts";
@@ -12,6 +12,7 @@ import { TaskDirectoriesOptionSchema, TaskRootOptionSchema } from "./options.ts"
12
12
  import { writeReleaseNotes } from "./release-notes.ts";
13
13
  import { assertReleaseVersion } from "./release-version.ts";
14
14
  import { captureTaskCommand, runTaskCommand, taskCommandSucceeds } from "../src/_task-command.ts";
15
+ import { RELEASE_WORKFLOW_FILE } from "../src/release-context.ts";
15
16
  import {
16
17
  RELEASE_INSTALL_MODES,
17
18
  RELEASE_PUBLISH_TARGETS,
@@ -24,6 +25,135 @@ import {
24
25
  import { readWorkspaceVersion } from "../src/workspace-version.ts";
25
26
 
26
27
  const logger = log.logger("projen:release");
28
+ const GITHUB_ACTIONS_START_TIMEOUT_MS = 2 * 60_000;
29
+ const GITHUB_ACTIONS_POLL_INTERVAL_MS = 2_000;
30
+
31
+ const GithubReleaseRunSchema = z
32
+ .object({
33
+ databaseId: z.number().int().positive(),
34
+ headBranch: z.string(),
35
+ headSha: z.string(),
36
+ status: z.string(),
37
+ url: z.string(),
38
+ })
39
+ .passthrough();
40
+
41
+ /** Inputs required to locate and watch one tag-triggered release workflow. */
42
+ export interface WatchReleaseGithubActionsOptions {
43
+ readonly account: projectUtils.ProjectGhAccount;
44
+ readonly head: string;
45
+ readonly root: string;
46
+ readonly tag: string;
47
+ readonly pollIntervalMs?: number;
48
+ readonly startTimeoutMs?: number;
49
+ }
50
+
51
+ /** GitHub release watcher boundary replaceable by release tests. */
52
+ export type ReleaseGithubActionsWatcher = (
53
+ options: WatchReleaseGithubActionsOptions,
54
+ ) => Promise<void>;
55
+
56
+ function githubRepository(account: projectUtils.ProjectGhAccount): string {
57
+ return account.host === "github.com"
58
+ ? account.remote.repository
59
+ : `${account.host}/${account.remote.repository}`;
60
+ }
61
+
62
+ function delay(milliseconds: number): Promise<void> {
63
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
64
+ }
65
+
66
+ /** Whether this release selection has remote work worth watching. */
67
+ export function shouldWatchReleaseGithubActions(
68
+ options: Pick<ReleaseSelectionOptions, "publish"> & { readonly watchGithubActions?: boolean },
69
+ selection: ReturnType<typeof releaseStepSelection>,
70
+ ): boolean {
71
+ if (options.watchGithubActions === false) return false;
72
+ if (options.publish === "local" && !selection.npm && !selection.pypi && !selection.docs) {
73
+ return false;
74
+ }
75
+ return Object.values(selection).some(Boolean);
76
+ }
77
+
78
+ /** Wait for a tag-triggered workflow, show live progress, then print its logs. */
79
+ export async function watchReleaseGithubActions(
80
+ options: WatchReleaseGithubActionsOptions,
81
+ ): Promise<void> {
82
+ const {
83
+ account,
84
+ head,
85
+ root,
86
+ tag,
87
+ pollIntervalMs = GITHUB_ACTIONS_POLL_INTERVAL_MS,
88
+ startTimeoutMs = GITHUB_ACTIONS_START_TIMEOUT_MS,
89
+ } = options;
90
+ const repository = githubRepository(account);
91
+ const deadline = Date.now() + startTimeoutMs;
92
+ let run: z.infer<typeof GithubReleaseRunSchema> | undefined;
93
+ while (!run && Date.now() < deadline) {
94
+ const output = captureTaskCommand(
95
+ root,
96
+ "gh",
97
+ [
98
+ "run",
99
+ "list",
100
+ "--repo",
101
+ repository,
102
+ "--workflow",
103
+ RELEASE_WORKFLOW_FILE,
104
+ "--commit",
105
+ head,
106
+ "--event",
107
+ "push",
108
+ "--limit",
109
+ "20",
110
+ "--json",
111
+ "databaseId,headBranch,headSha,status,url",
112
+ ],
113
+ { check: true, env: account.env, stderr: "inherit" },
114
+ );
115
+ const parsed = z.array(GithubReleaseRunSchema).safeParse(json.parse(output, []));
116
+ if (!parsed.success) throw new Error("GitHub returned invalid release workflow metadata");
117
+ run = parsed.data.find(
118
+ (candidate) => candidate.headSha === head && candidate.headBranch === tag,
119
+ );
120
+ if (!run) await delay(pollIntervalMs);
121
+ }
122
+ if (!run) {
123
+ logger.warn("GitHub Actions release run did not start before the watch timeout", {
124
+ repository: account.remote.repository,
125
+ tag,
126
+ });
127
+ return;
128
+ }
129
+
130
+ logger.info("watching GitHub Actions release", {
131
+ repository: account.remote.repository,
132
+ runId: run.databaseId,
133
+ status: run.status,
134
+ url: run.url,
135
+ });
136
+ try {
137
+ runTaskCommand(
138
+ root,
139
+ "gh",
140
+ ["run", "watch", String(run.databaseId), "--repo", repository, "--interval", "3"],
141
+ { env: account.env },
142
+ );
143
+ runTaskCommand(
144
+ root,
145
+ "gh",
146
+ ["run", "view", String(run.databaseId), "--repo", repository, "--log"],
147
+ { env: account.env },
148
+ );
149
+ } catch (error) {
150
+ logger.warn("GitHub Actions release watch stopped; the remote run was not cancelled", {
151
+ error: errorUtils.toError(error),
152
+ runId: run.databaseId,
153
+ url: run.url,
154
+ });
155
+ }
156
+ }
27
157
 
28
158
  /** Whether the root manifest exposes one repository-owned script. */
29
159
  function hasPackageScript(root: string, name: string): boolean {
@@ -244,6 +374,9 @@ export async function runRelease(
244
374
  /** Path to complete release notes supplied instead of invoking Genie. */
245
375
  readonly releaseNotesFile?: string;
246
376
  readonly writeReleaseNotes?: typeof writeReleaseNotes;
377
+ /** Wait for and stream a matching tag-triggered GitHub Actions run. */
378
+ readonly watchGithubActions?: boolean;
379
+ readonly githubActionsWatcher?: ReleaseGithubActionsWatcher;
247
380
  },
248
381
  ): Promise<string> {
249
382
  const { branch, prefix, remote, root } = options;
@@ -347,6 +480,23 @@ export async function runRelease(
347
480
  }
348
481
  logger.success(`released ${tag}`, { sha: head });
349
482
 
483
+ if (shouldWatchReleaseGithubActions(options, selection)) {
484
+ const account = projectUtils.resolveProjectGhAccount(root, { remote });
485
+ if (account) {
486
+ await (options.githubActionsWatcher ?? watchReleaseGithubActions)({
487
+ account,
488
+ head,
489
+ root,
490
+ tag,
491
+ });
492
+ } else {
493
+ logger.info("skipping GitHub Actions release watch: no authenticated account", {
494
+ remote,
495
+ tag,
496
+ });
497
+ }
498
+ }
499
+
350
500
  // Restore local deploys: when a non-standard (loopback) npm or uv registry is
351
501
  // configured, publish the freshly tagged version to it directly. Detection and
352
502
  // publishing are owned by local-publish.ts; this no-ops on standard registries.
@@ -383,12 +533,15 @@ export const ReleaseOptionsSchema = z.object({
383
533
  .default("packages/py")
384
534
  .describe("Python package root for local publish"),
385
535
  validate: TaskDirectoriesOptionSchema.describe("Repeatable task to run before pushing"),
386
- bump: z.boolean().default(true).describe("Create and synchronize a patch version bump"),
536
+ bump: z
537
+ .boolean()
538
+ .default(true)
539
+ .describe("Create and synchronize a patch bump; disable only for an existing committed bump"),
387
540
  publish: z.enum(RELEASE_PUBLISH_TARGETS).default("auto").describe("Publication scope"),
388
541
  install: z
389
542
  .enum(RELEASE_INSTALL_MODES)
390
543
  .default("auto")
391
- .describe("Local workspace dependency installation"),
544
+ .describe("Dependency install mode: auto follows manifests, always installs, never reuses"),
392
545
  npm: z.boolean().default(true).describe("Build and publish npm packages"),
393
546
  pypi: z.boolean().default(true).describe("Build and publish Python packages"),
394
547
  docs: z.boolean().optional().describe("Build and deploy documentation"),
@@ -396,7 +549,14 @@ export const ReleaseOptionsSchema = z.object({
396
549
  .boolean()
397
550
  .default(true)
398
551
  .describe("Run optional release validation tasks before publishing"),
399
- releaseNotes: z.boolean().default(true).describe("Write release notes before tagging"),
552
+ watchGithubActions: z
553
+ .boolean()
554
+ .default(true)
555
+ .describe("Wait for and stream the tag-triggered GitHub Actions release"),
556
+ releaseNotes: z
557
+ .boolean()
558
+ .default(true)
559
+ .describe("Generate and commit release notes for a new version bump"),
400
560
  releaseNotesText: z
401
561
  .string()
402
562
  .optional()
@@ -414,7 +574,7 @@ export const ReleaseOptionsSchema = z.object({
414
574
  demoDeploy: z
415
575
  .boolean()
416
576
  .optional()
417
- .describe("Deploy the AppKit demo after tagging and local publication"),
577
+ .describe("Run the local-only AppKit demo deployment after tagging and publication"),
418
578
  profile: z
419
579
  .string()
420
580
  .trim()
@@ -460,6 +620,7 @@ export function createReleaseCommand(): Command {
460
620
  pypi: options.pypi,
461
621
  docs: options.docs,
462
622
  validation: options.validation,
623
+ watchGithubActions: options.watchGithubActions,
463
624
  validationTasks: options.validate,
464
625
  demoDeploy: options.demoDeploy,
465
626
  demoProfile: options.profile,