@dbx-tools/projen 0.9.145 → 0.9.149
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 +69 -78
- package/index.ts +2 -2
- package/package.json +5 -5
- package/src/_task-command.ts +36 -1
- package/src/release-context.ts +2 -0
- package/src/release.ts +2 -2
- package/tasks/local-publish.ts +24 -5
- package/tasks/release.ts +183 -6
package/README.md
CHANGED
|
@@ -316,14 +316,14 @@ scripts, and generated files on top.
|
|
|
316
316
|
Every workspace root exposes a generic development watcher:
|
|
317
317
|
|
|
318
318
|
```bash
|
|
319
|
-
bun run dev:watch
|
|
319
|
+
bun run dev:watch <command> [args...]
|
|
320
320
|
```
|
|
321
321
|
|
|
322
|
-
It restarts after watched writes have been quiet for 15 seconds. Command paths
|
|
323
|
-
|
|
322
|
+
It restarts after watched writes have been quiet for 15 seconds. Command paths
|
|
323
|
+
and task selectors identify the owning package. Its transitive workspace
|
|
324
324
|
dependencies are watched with the shared generated/dependency/test/temp ignores.
|
|
325
|
-
|
|
326
|
-
run its concrete server entrypoint. The `server` tag does not assume one.
|
|
325
|
+
The configured restart key triggers an immediate restart. The demo uses this
|
|
326
|
+
task to run its concrete server entrypoint. The `server` tag does not assume one.
|
|
327
327
|
|
|
328
328
|
AppKit 0.81's `appkitServerConfig()` is the preferred tsdown preset for a
|
|
329
329
|
consumer app using conventional compiled `server/agents/*/agent.ts` entries. It
|
|
@@ -540,9 +540,15 @@ 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.
|
|
544
|
-
|
|
545
|
-
|
|
543
|
+
workflow verifies that the tag points at the exact `origin/main` commit. The
|
|
544
|
+
local task completes local publication and demo deployment first, then waits for
|
|
545
|
+
this workflow by default. It preserves the live checklist through a scroll-safe
|
|
546
|
+
non-TTY stream and prints the final logs. Local-only releases and repositories
|
|
547
|
+
without a resolved authenticated GitHub account skip the watcher. Stopping the
|
|
548
|
+
local watcher does not cancel the remote run.
|
|
549
|
+
|
|
550
|
+
One `build-release` job installs workspace dependencies, runs validation, packs
|
|
551
|
+
the npm archives, builds all Python distributions, and builds the documentation
|
|
546
552
|
site. Install validation prerequisites through `releaseSetupSteps`.
|
|
547
553
|
`releaseDocs.prepareSteps` runs after validation.
|
|
548
554
|
|
|
@@ -551,58 +557,41 @@ workspace. Each Python package retains its own publishing environment and waits
|
|
|
551
557
|
for its package dependencies to publish. The npm job uses the bundled archive
|
|
552
558
|
publisher without a checkout or workspace install. Pages deploys the already
|
|
553
559
|
built site after registry publication succeeds. Local npm and Python registry publication remains available
|
|
554
|
-
through the direct local publication tooling.
|
|
560
|
+
through the direct local publication tooling. Those local publishers run
|
|
561
|
+
concurrently; a failure or release termination stops every live publisher before
|
|
562
|
+
the command returns.
|
|
555
563
|
|
|
556
|
-
The default release
|
|
557
|
-
configured local registries
|
|
558
|
-
|
|
564
|
+
The default release publishes npm, Python, and docs, then publishes to
|
|
565
|
+
configured local registries. Use `bun run release --help` or the generated
|
|
566
|
+
[command reference](#release) for the complete option set. Common workflows:
|
|
559
567
|
|
|
560
568
|
```bash
|
|
569
|
+
bun run release
|
|
561
570
|
bun run release --publish pypi --no-docs
|
|
562
|
-
bun run release --no-npm --no-local-publish
|
|
563
571
|
bun run release --publish local --install never
|
|
564
|
-
bun run release --local-registry false --local-pypi auto
|
|
565
572
|
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
573
|
```
|
|
571
574
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
locally compiled Node packages and locally built Python wheels, resolves
|
|
575
|
-
the default environment or configured credentials through
|
|
575
|
+
Demo deployment is a local post-tag action. It stages locally compiled Node
|
|
576
|
+
packages and Python wheels, resolves credentials through
|
|
576
577
|
`@dbx-tools/auth`, including `DATABRICKS_CONFIG_PROFILE`, `DATABRICKS_HOST`,
|
|
577
578
|
and the other auth options, force-refreshes its token with interactive login
|
|
578
579
|
fallback, and deploys it. When auth selects a profile, the deploy passes that
|
|
579
|
-
resolved profile explicitly to every bundle command.
|
|
580
|
-
and
|
|
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.
|
|
580
|
+
resolved profile explicitly to every bundle command. The deployment selection
|
|
581
|
+
and profile are not recorded in the annotated tag.
|
|
585
582
|
|
|
586
583
|
After bump and configured validation, release writes
|
|
587
584
|
`docs/releases/vX.Y.Z.md` with `dbx genie exec -C "$PWD" --sandbox read-only
|
|
588
|
-
--ephemeral -o`. The standard prompt
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
the
|
|
596
|
-
|
|
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.
|
|
585
|
+
--ephemeral -o`. The standard prompt cannot run validation commands. Failures
|
|
586
|
+
fall back to a short git-log summary. Notes are committed with the version bump,
|
|
587
|
+
not stored in the annotated tag, and are omitted for an existing committed bump.
|
|
588
|
+
|
|
589
|
+
Automatic installation retains Projen's manifest-based install trigger; the
|
|
590
|
+
other install modes force installation or reuse the current dependency tree.
|
|
591
|
+
Local release validation runs `eslint:fix` before branch preparation and bump,
|
|
592
|
+
then runs the selected fail-closed tasks. CI remains check-only and rebuilds
|
|
593
|
+
selected release artifacts instead of checking generated-source fingerprints.
|
|
594
|
+
|
|
606
595
|
Scopes select artifacts, not the normal commit and tag-push transaction.
|
|
607
596
|
The annotated tag records CI step selections, so publishing a selected scope does
|
|
608
597
|
not change the next release's defaults. Local registry URLs stay local.
|
|
@@ -652,37 +641,39 @@ release [options]
|
|
|
652
641
|
|
|
653
642
|
#### Options
|
|
654
643
|
|
|
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
|
|
664
|
-
| `--no-bump` | Disable create and synchronize a patch
|
|
665
|
-
| `--publish <value>` | Publication scope (choices: "auto", "npm", "pypi", "local", "none", default: "auto", env: PROJEN_RELEASE_PUBLISH)
|
|
666
|
-
| `-i, --install <value>` |
|
|
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
|
-
|
|
|
676
|
-
| `--no-
|
|
677
|
-
| `--release-notes
|
|
678
|
-
| `--release-notes
|
|
679
|
-
| `--release-notes-
|
|
680
|
-
| `--
|
|
681
|
-
| `--
|
|
682
|
-
| `--
|
|
683
|
-
| `--
|
|
684
|
-
| `--
|
|
685
|
-
| `--local-
|
|
686
|
-
| `--local-
|
|
644
|
+
| Option | Description |
|
|
645
|
+
| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
646
|
+
| `--root <value>` | Repository or workspace root (env: PROJEN_RELEASE_ROOT) |
|
|
647
|
+
| `--branch <value>` | Release branch (default: "main", env: PROJEN_RELEASE_BRANCH) |
|
|
648
|
+
| `--prefix <value>` | Release tag prefix (default: "v", env: PROJEN_RELEASE_PREFIX) |
|
|
649
|
+
| `--remote <value>` | Git remote (default: "origin", env: PROJEN_RELEASE_REMOTE) |
|
|
650
|
+
| `--python-root <value>` | Python package root for local publish (default: "packages/py", env: PROJEN_RELEASE_PYTHON_ROOT) |
|
|
651
|
+
| `--validate <value>` | Repeatable task to run before pushing (default: [], env: PROJEN_RELEASE_VALIDATE) |
|
|
652
|
+
| `--bump` | Create and synchronize a patch bump; disable only for an existing committed bump (default: true, env: PROJEN_RELEASE_BUMP) |
|
|
653
|
+
| `--no-bump` | Disable create and synchronize a patch bump; disable only for an existing committed bump |
|
|
654
|
+
| `--publish <value>` | Publication scope (choices: "auto", "npm", "pypi", "local", "none", default: "auto", env: PROJEN_RELEASE_PUBLISH) |
|
|
655
|
+
| `-i, --install <value>` | Dependency install mode: auto follows manifests, always installs, never reuses (choices: "auto", "always", "never", default: "auto", env: PROJEN_RELEASE_INSTALL) |
|
|
656
|
+
| `-n, --npm` | Build and publish npm packages (default: true, env: PROJEN_RELEASE_NPM) |
|
|
657
|
+
| `--no-npm` | Disable build and publish npm packages |
|
|
658
|
+
| `--pypi` | Build and publish Python packages (default: true, env: PROJEN_RELEASE_PYPI) |
|
|
659
|
+
| `--no-pypi` | Disable build and publish python packages |
|
|
660
|
+
| `--docs` | Build and deploy documentation (env: PROJEN_RELEASE_DOCS) |
|
|
661
|
+
| `--no-docs` | Disable build and deploy documentation |
|
|
662
|
+
| `--validation` | Run optional release validation tasks before publishing (default: true, env: PROJEN_RELEASE_VALIDATION) |
|
|
663
|
+
| `--no-validation` | Disable run optional release validation tasks before publishing |
|
|
664
|
+
| `-w, --watch-github-actions` | Wait for and log the tag-triggered GitHub Actions release (default: true, env: PROJEN_RELEASE_WATCH_GITHUB_ACTIONS) |
|
|
665
|
+
| `--no-watch-github-actions` | Disable wait for and log the tag-triggered github actions release |
|
|
666
|
+
| `--release-notes` | Generate and commit release notes for a new version bump (default: true, env: PROJEN_RELEASE_RELEASE_NOTES) |
|
|
667
|
+
| `--no-release-notes` | Disable generate and commit release notes for a new version bump |
|
|
668
|
+
| `--release-notes-text <value>` | Write supplied release-note Markdown without invoking Genie (env: PROJEN_RELEASE_RELEASE_NOTES_TEXT) |
|
|
669
|
+
| `--release-notes-file <value>` | Copy release-note Markdown from this file without invoking Genie (env: PROJEN_RELEASE_RELEASE_NOTES_FILE) |
|
|
670
|
+
| `--release-notes-instructions <value>` | Append instructions to the Genie release-note prompt (env: PROJEN_RELEASE_RELEASE_NOTES_INSTRUCTIONS) |
|
|
671
|
+
| `--demo-deploy` | Run the local-only AppKit demo deployment after tagging and publication (env: PROJEN_RELEASE_DEMO_DEPLOY) |
|
|
672
|
+
| `--no-demo-deploy` | Disable run the local-only appkit demo deployment after tagging and publication |
|
|
673
|
+
| `--profile <value>` | Optional Databricks CLI profile passed to the demo deployment (env: PROJEN_RELEASE_PROFILE) |
|
|
674
|
+
| `--local-publish` | Publish to configured local registries (default: true, env: PROJEN_RELEASE_LOCAL_PUBLISH) |
|
|
675
|
+
| `--no-local-publish` | Disable publish to configured local registries |
|
|
676
|
+
| `--local-registry <value>` | Local npm registry selection: auto, false, or URL (default: "auto", env: PROJEN_RELEASE_LOCAL_REGISTRY) |
|
|
677
|
+
| `--local-pypi <value>` | Local devpi registry selection: auto, false, or URL (default: "auto", env: PROJEN_RELEASE_LOCAL_PYPI) |
|
|
687
678
|
|
|
688
679
|
<!-- 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.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.149";
|
|
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.
|
|
34
|
-
"@dbx-tools/core": "^0.9.
|
|
35
|
-
"@dbx-tools/path": "^0.9.
|
|
36
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
+
"@dbx-tools/cli-args": "^0.9.149",
|
|
34
|
+
"@dbx-tools/core": "^0.9.149",
|
|
35
|
+
"@dbx-tools/path": "^0.9.149",
|
|
36
|
+
"@dbx-tools/shared-core": "^0.9.149",
|
|
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.
|
|
58
|
+
"version": "0.9.149",
|
|
59
59
|
"types": "index.ts",
|
|
60
60
|
"type": "module",
|
|
61
61
|
"exports": {
|
package/src/_task-command.ts
CHANGED
|
@@ -40,7 +40,17 @@ export async function runTaskCommandAsync(
|
|
|
40
40
|
args: readonly string[],
|
|
41
41
|
options: TaskCommandOptions & { readonly signal?: AbortSignal } = {},
|
|
42
42
|
): Promise<void> {
|
|
43
|
-
await
|
|
43
|
+
await spawnTaskCommand(cwd, command, args, options);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Start one supervised task command and return its live process handle. */
|
|
47
|
+
export function spawnTaskCommand(
|
|
48
|
+
cwd: string,
|
|
49
|
+
command: string,
|
|
50
|
+
args: readonly string[],
|
|
51
|
+
options: TaskCommandOptions & { readonly signal?: AbortSignal } = {},
|
|
52
|
+
): exec.ChildProcessResult {
|
|
53
|
+
return exec.spawn(executable(command), [...args], {
|
|
44
54
|
cwd,
|
|
45
55
|
env: options.env,
|
|
46
56
|
stdout: "inherit",
|
|
@@ -51,6 +61,31 @@ export async function runTaskCommandAsync(
|
|
|
51
61
|
});
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Run a command with piped stdout forwarded line-by-line.
|
|
66
|
+
*
|
|
67
|
+
* The child sees a non-TTY output stream, so interactive redraw commands fall
|
|
68
|
+
* back to append-only output while the parent still receives live progress.
|
|
69
|
+
*/
|
|
70
|
+
export async function streamTaskCommand(
|
|
71
|
+
cwd: string,
|
|
72
|
+
command: string,
|
|
73
|
+
args: readonly string[],
|
|
74
|
+
options: TaskCommandOptions = {},
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
await exec.spawn(executable(command), [...args], {
|
|
77
|
+
cwd,
|
|
78
|
+
env: options.env,
|
|
79
|
+
stdout: {
|
|
80
|
+
capture: false,
|
|
81
|
+
onLine: (line) => console.log(line),
|
|
82
|
+
},
|
|
83
|
+
stderr: options.stderr ?? "inherit",
|
|
84
|
+
stdin: "ignore",
|
|
85
|
+
check: true,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
54
89
|
/** Capture trimmed stdout, optionally throwing when the command fails. */
|
|
55
90
|
export function captureTaskCommand(
|
|
56
91
|
cwd: string,
|
package/src/release-context.ts
CHANGED
|
@@ -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:
|
|
342
|
+
fileName: RELEASE_WORKFLOW_FILE,
|
|
343
343
|
limitConcurrency: true,
|
|
344
344
|
concurrencyOptions: {
|
|
345
345
|
group: "release",
|
package/tasks/local-publish.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** Build and publish the current workspace to configured loopback registries. */
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import type { ChildProcessResult } from "@dbx-tools/core/exec";
|
|
4
5
|
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
5
6
|
import { log, net } from "@dbx-tools/shared-core";
|
|
6
7
|
import { activePythonIndexes, resolveLocalPypi } from "./python-registry.ts";
|
|
7
|
-
import {
|
|
8
|
+
import { spawnTaskCommand } from "../src/_task-command.ts";
|
|
8
9
|
|
|
9
10
|
const logger = log.logger("projen:local-publish");
|
|
10
11
|
|
|
@@ -38,6 +39,24 @@ export function localPublishTaskPath(task: "publish.ts" | "publish-python.ts"):
|
|
|
38
39
|
return fileURLToPath(new URL(task, import.meta.url));
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
/** Await concurrent publishers and terminate every live sibling after one fails. */
|
|
43
|
+
export async function awaitLocalPublishers(
|
|
44
|
+
publishes: readonly ChildProcessResult[],
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
try {
|
|
47
|
+
await Promise.all(publishes);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
await Promise.all(
|
|
50
|
+
publishes.map((publish) =>
|
|
51
|
+
publish.exitCode === null && publish.signalCode === null
|
|
52
|
+
? publish.shutdown()
|
|
53
|
+
: Promise.resolve(),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
41
60
|
/** Build and publish the current workspace directly to local registries. */
|
|
42
61
|
export async function publishLocalRelease(
|
|
43
62
|
options: LocalPublishOptions,
|
|
@@ -46,7 +65,7 @@ export async function publishLocalRelease(
|
|
|
46
65
|
const localRegistry = resolveLocalRegistry(options.localRegistry);
|
|
47
66
|
const activeIndexes = activePythonIndexes();
|
|
48
67
|
const localPypi = resolveLocalPypi(options.localPypi, activeIndexes);
|
|
49
|
-
const publishes:
|
|
68
|
+
const publishes: ChildProcessResult[] = [];
|
|
50
69
|
|
|
51
70
|
if (
|
|
52
71
|
options.localPypi.toLowerCase() === "auto" &&
|
|
@@ -62,7 +81,7 @@ export async function publishLocalRelease(
|
|
|
62
81
|
const publishNpmScript = localPublishTaskPath("publish.ts");
|
|
63
82
|
logger.info(`building and publishing npm workspace to ${localRegistry}`);
|
|
64
83
|
publishes.push(
|
|
65
|
-
|
|
84
|
+
spawnTaskCommand(root, process.execPath, [
|
|
66
85
|
publishNpmScript,
|
|
67
86
|
options.version,
|
|
68
87
|
"--registry",
|
|
@@ -75,7 +94,7 @@ export async function publishLocalRelease(
|
|
|
75
94
|
const publishPythonScript = localPublishTaskPath("publish-python.ts");
|
|
76
95
|
logger.info(`building and publishing Python workspace to ${localPypi.publishUrl}`);
|
|
77
96
|
publishes.push(
|
|
78
|
-
|
|
97
|
+
spawnTaskCommand(root, process.execPath, [
|
|
79
98
|
publishPythonScript,
|
|
80
99
|
options.version,
|
|
81
100
|
"--root",
|
|
@@ -93,7 +112,7 @@ export async function publishLocalRelease(
|
|
|
93
112
|
return { npm: false, python: false };
|
|
94
113
|
}
|
|
95
114
|
|
|
96
|
-
await
|
|
115
|
+
await awaitLocalPublishers(publishes);
|
|
97
116
|
logger.success(`published local workspace ${options.version}`);
|
|
98
117
|
return { npm: Boolean(localRegistry), python: Boolean(localPypi) };
|
|
99
118
|
}
|
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";
|
|
@@ -11,7 +11,13 @@ import { publishLocalRelease } from "./local-publish.ts";
|
|
|
11
11
|
import { TaskDirectoriesOptionSchema, TaskRootOptionSchema } from "./options.ts";
|
|
12
12
|
import { writeReleaseNotes } from "./release-notes.ts";
|
|
13
13
|
import { assertReleaseVersion } from "./release-version.ts";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
captureTaskCommand,
|
|
16
|
+
runTaskCommand,
|
|
17
|
+
streamTaskCommand,
|
|
18
|
+
taskCommandSucceeds,
|
|
19
|
+
} from "../src/_task-command.ts";
|
|
20
|
+
import { RELEASE_WORKFLOW_FILE } from "../src/release-context.ts";
|
|
15
21
|
import {
|
|
16
22
|
RELEASE_INSTALL_MODES,
|
|
17
23
|
RELEASE_PUBLISH_TARGETS,
|
|
@@ -24,6 +30,143 @@ import {
|
|
|
24
30
|
import { readWorkspaceVersion } from "../src/workspace-version.ts";
|
|
25
31
|
|
|
26
32
|
const logger = log.logger("projen:release");
|
|
33
|
+
const GITHUB_ACTIONS_START_TIMEOUT_MS = 2 * 60_000;
|
|
34
|
+
const GITHUB_ACTIONS_POLL_INTERVAL_MS = 2_000;
|
|
35
|
+
|
|
36
|
+
const GithubReleaseRunSchema = z
|
|
37
|
+
.object({
|
|
38
|
+
databaseId: z.number().int().positive(),
|
|
39
|
+
headBranch: z.string(),
|
|
40
|
+
headSha: z.string(),
|
|
41
|
+
status: z.string(),
|
|
42
|
+
url: z.string(),
|
|
43
|
+
})
|
|
44
|
+
.passthrough();
|
|
45
|
+
|
|
46
|
+
/** Inputs required to locate and watch one tag-triggered release workflow. */
|
|
47
|
+
export interface WatchReleaseGithubActionsOptions {
|
|
48
|
+
readonly account: projectUtils.ProjectGhAccount;
|
|
49
|
+
readonly head: string;
|
|
50
|
+
readonly root: string;
|
|
51
|
+
readonly tag: string;
|
|
52
|
+
readonly pollIntervalMs?: number;
|
|
53
|
+
readonly startTimeoutMs?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** GitHub release watcher boundary replaceable by release tests. */
|
|
57
|
+
export type ReleaseGithubActionsWatcher = (
|
|
58
|
+
options: WatchReleaseGithubActionsOptions,
|
|
59
|
+
) => Promise<void>;
|
|
60
|
+
|
|
61
|
+
function githubRepository(account: projectUtils.ProjectGhAccount): string {
|
|
62
|
+
return account.host === "github.com"
|
|
63
|
+
? account.remote.repository
|
|
64
|
+
: `${account.host}/${account.remote.repository}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function delay(milliseconds: number): Promise<void> {
|
|
68
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Whether this release selection has remote work worth watching. */
|
|
72
|
+
export function shouldWatchReleaseGithubActions(
|
|
73
|
+
options: Pick<ReleaseSelectionOptions, "publish"> & { readonly watchGithubActions?: boolean },
|
|
74
|
+
selection: ReturnType<typeof releaseStepSelection>,
|
|
75
|
+
): boolean {
|
|
76
|
+
if (options.watchGithubActions === false) return false;
|
|
77
|
+
if (options.publish === "local" && !selection.npm && !selection.pypi && !selection.docs) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return Object.values(selection).some(Boolean);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Wait for a tag-triggered workflow, show live progress, then print its logs. */
|
|
84
|
+
export async function watchReleaseGithubActions(
|
|
85
|
+
options: WatchReleaseGithubActionsOptions,
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
const {
|
|
88
|
+
account,
|
|
89
|
+
head,
|
|
90
|
+
root,
|
|
91
|
+
tag,
|
|
92
|
+
pollIntervalMs = GITHUB_ACTIONS_POLL_INTERVAL_MS,
|
|
93
|
+
startTimeoutMs = GITHUB_ACTIONS_START_TIMEOUT_MS,
|
|
94
|
+
} = options;
|
|
95
|
+
const repository = githubRepository(account);
|
|
96
|
+
const deadline = Date.now() + startTimeoutMs;
|
|
97
|
+
let run: z.infer<typeof GithubReleaseRunSchema> | undefined;
|
|
98
|
+
while (!run && Date.now() < deadline) {
|
|
99
|
+
const output = captureTaskCommand(
|
|
100
|
+
root,
|
|
101
|
+
"gh",
|
|
102
|
+
[
|
|
103
|
+
"run",
|
|
104
|
+
"list",
|
|
105
|
+
"--repo",
|
|
106
|
+
repository,
|
|
107
|
+
"--workflow",
|
|
108
|
+
RELEASE_WORKFLOW_FILE,
|
|
109
|
+
"--commit",
|
|
110
|
+
head,
|
|
111
|
+
"--event",
|
|
112
|
+
"push",
|
|
113
|
+
"--limit",
|
|
114
|
+
"20",
|
|
115
|
+
"--json",
|
|
116
|
+
"databaseId,headBranch,headSha,status,url",
|
|
117
|
+
],
|
|
118
|
+
{ check: true, env: account.env, stderr: "inherit" },
|
|
119
|
+
);
|
|
120
|
+
const parsed = z.array(GithubReleaseRunSchema).safeParse(json.parse(output, []));
|
|
121
|
+
if (!parsed.success) throw new Error("GitHub returned invalid release workflow metadata");
|
|
122
|
+
run = parsed.data.find(
|
|
123
|
+
(candidate) => candidate.headSha === head && candidate.headBranch === tag,
|
|
124
|
+
);
|
|
125
|
+
if (!run) await delay(pollIntervalMs);
|
|
126
|
+
}
|
|
127
|
+
if (!run) {
|
|
128
|
+
logger.warn("GitHub Actions release run did not start before the watch timeout", {
|
|
129
|
+
repository: account.remote.repository,
|
|
130
|
+
tag,
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
logger.info("watching GitHub Actions release", {
|
|
136
|
+
repository: account.remote.repository,
|
|
137
|
+
runId: run.databaseId,
|
|
138
|
+
status: run.status,
|
|
139
|
+
url: run.url,
|
|
140
|
+
});
|
|
141
|
+
try {
|
|
142
|
+
await streamTaskCommand(
|
|
143
|
+
root,
|
|
144
|
+
"gh",
|
|
145
|
+
[
|
|
146
|
+
"run",
|
|
147
|
+
"watch",
|
|
148
|
+
String(run.databaseId),
|
|
149
|
+
"--repo",
|
|
150
|
+
repository,
|
|
151
|
+
"--interval",
|
|
152
|
+
String(Math.max(1, Math.round(pollIntervalMs / 1_000))),
|
|
153
|
+
],
|
|
154
|
+
{ env: { ...account.env, NO_COLOR: "1" } },
|
|
155
|
+
);
|
|
156
|
+
runTaskCommand(
|
|
157
|
+
root,
|
|
158
|
+
"gh",
|
|
159
|
+
["run", "view", String(run.databaseId), "--repo", repository, "--log"],
|
|
160
|
+
{ env: account.env },
|
|
161
|
+
);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.warn("GitHub Actions release watch stopped; the remote run was not cancelled", {
|
|
164
|
+
error: errorUtils.toError(error),
|
|
165
|
+
runId: run.databaseId,
|
|
166
|
+
url: run.url,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
27
170
|
|
|
28
171
|
/** Whether the root manifest exposes one repository-owned script. */
|
|
29
172
|
function hasPackageScript(root: string, name: string): boolean {
|
|
@@ -244,6 +387,10 @@ export async function runRelease(
|
|
|
244
387
|
/** Path to complete release notes supplied instead of invoking Genie. */
|
|
245
388
|
readonly releaseNotesFile?: string;
|
|
246
389
|
readonly writeReleaseNotes?: typeof writeReleaseNotes;
|
|
390
|
+
/** After local release actions, wait for and stream the matching GitHub Actions run. */
|
|
391
|
+
readonly watchGithubActions?: boolean;
|
|
392
|
+
readonly githubActionsWatcher?: ReleaseGithubActionsWatcher;
|
|
393
|
+
readonly resolveGithubAccount?: typeof projectUtils.resolveProjectGhAccount;
|
|
247
394
|
},
|
|
248
395
|
): Promise<string> {
|
|
249
396
|
const { branch, prefix, remote, root } = options;
|
|
@@ -368,6 +515,25 @@ export async function runRelease(
|
|
|
368
515
|
]);
|
|
369
516
|
}
|
|
370
517
|
|
|
518
|
+
if (shouldWatchReleaseGithubActions(options, selection)) {
|
|
519
|
+
const account = (options.resolveGithubAccount ?? projectUtils.resolveProjectGhAccount)(root, {
|
|
520
|
+
remote,
|
|
521
|
+
});
|
|
522
|
+
if (account) {
|
|
523
|
+
await (options.githubActionsWatcher ?? watchReleaseGithubActions)({
|
|
524
|
+
account,
|
|
525
|
+
head,
|
|
526
|
+
root,
|
|
527
|
+
tag,
|
|
528
|
+
});
|
|
529
|
+
} else {
|
|
530
|
+
logger.info("skipping GitHub Actions release watch: no authenticated account", {
|
|
531
|
+
remote,
|
|
532
|
+
tag,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
371
537
|
return tag;
|
|
372
538
|
}
|
|
373
539
|
|
|
@@ -383,12 +549,15 @@ export const ReleaseOptionsSchema = z.object({
|
|
|
383
549
|
.default("packages/py")
|
|
384
550
|
.describe("Python package root for local publish"),
|
|
385
551
|
validate: TaskDirectoriesOptionSchema.describe("Repeatable task to run before pushing"),
|
|
386
|
-
bump: z
|
|
552
|
+
bump: z
|
|
553
|
+
.boolean()
|
|
554
|
+
.default(true)
|
|
555
|
+
.describe("Create and synchronize a patch bump; disable only for an existing committed bump"),
|
|
387
556
|
publish: z.enum(RELEASE_PUBLISH_TARGETS).default("auto").describe("Publication scope"),
|
|
388
557
|
install: z
|
|
389
558
|
.enum(RELEASE_INSTALL_MODES)
|
|
390
559
|
.default("auto")
|
|
391
|
-
.describe("
|
|
560
|
+
.describe("Dependency install mode: auto follows manifests, always installs, never reuses"),
|
|
392
561
|
npm: z.boolean().default(true).describe("Build and publish npm packages"),
|
|
393
562
|
pypi: z.boolean().default(true).describe("Build and publish Python packages"),
|
|
394
563
|
docs: z.boolean().optional().describe("Build and deploy documentation"),
|
|
@@ -396,7 +565,14 @@ export const ReleaseOptionsSchema = z.object({
|
|
|
396
565
|
.boolean()
|
|
397
566
|
.default(true)
|
|
398
567
|
.describe("Run optional release validation tasks before publishing"),
|
|
399
|
-
|
|
568
|
+
watchGithubActions: z
|
|
569
|
+
.boolean()
|
|
570
|
+
.default(true)
|
|
571
|
+
.describe("Wait for and log the tag-triggered GitHub Actions release"),
|
|
572
|
+
releaseNotes: z
|
|
573
|
+
.boolean()
|
|
574
|
+
.default(true)
|
|
575
|
+
.describe("Generate and commit release notes for a new version bump"),
|
|
400
576
|
releaseNotesText: z
|
|
401
577
|
.string()
|
|
402
578
|
.optional()
|
|
@@ -414,7 +590,7 @@ export const ReleaseOptionsSchema = z.object({
|
|
|
414
590
|
demoDeploy: z
|
|
415
591
|
.boolean()
|
|
416
592
|
.optional()
|
|
417
|
-
.describe("
|
|
593
|
+
.describe("Run the local-only AppKit demo deployment after tagging and publication"),
|
|
418
594
|
profile: z
|
|
419
595
|
.string()
|
|
420
596
|
.trim()
|
|
@@ -460,6 +636,7 @@ export function createReleaseCommand(): Command {
|
|
|
460
636
|
pypi: options.pypi,
|
|
461
637
|
docs: options.docs,
|
|
462
638
|
validation: options.validation,
|
|
639
|
+
watchGithubActions: options.watchGithubActions,
|
|
463
640
|
validationTasks: options.validate,
|
|
464
641
|
demoDeploy: options.demoDeploy,
|
|
465
642
|
demoProfile: options.profile,
|