@dbx-tools/projen 0.6.194 → 0.6.195
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 +22 -18
- package/index.ts +1 -1
- package/package.json +4 -4
- package/src/project-js.ts +1 -1
- package/src/project-py.ts +7 -6
- package/src/project-rs.ts +82 -124
- package/src/release.ts +39 -20
- package/tasks/bump.ts +25 -30
package/README.md
CHANGED
|
@@ -129,13 +129,14 @@ removing a crate or `setup_scaffolding!()` marker triggers a full synth. Repos
|
|
|
129
129
|
without Rust crates start no Rust watcher. When Rust projects are detected,
|
|
130
130
|
Cargo is required and the focused task fails immediately if it is unavailable.
|
|
131
131
|
|
|
132
|
-
The workspace generates one `release.yml` workflow for every ecosystem.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
commit, and version to every source job.
|
|
132
|
+
The workspace generates one `release.yml` workflow for every ecosystem. A push
|
|
133
|
+
to the configured release branch starts it. The context job reads `VERSION`,
|
|
134
|
+
creates the corresponding annotated `v*` tag, and exposes the verified tag,
|
|
135
|
+
commit, and version to every source job. If the tag already exists, it must be
|
|
136
|
+
annotated and reference the same commit. A manual run accepts that same tag and
|
|
136
137
|
commit but requires dry-run mode, so it builds and validates without publishing
|
|
137
|
-
packages or deploying documentation. One `release` concurrency
|
|
138
|
-
|
|
138
|
+
packages or deploying documentation. One non-cancelling `release` concurrency
|
|
139
|
+
group serializes publication.
|
|
139
140
|
|
|
140
141
|
Release generation writes `release.yml` without scanning for or removing other
|
|
141
142
|
workflow files. Consumers explicitly delete exact workflow files they no longer
|
|
@@ -146,15 +147,15 @@ For a separate recovery run, choose `node`, `python`, or `docs` instead of
|
|
|
146
147
|
`all`. Node and Python recovery can supply the prior `release.yml` run ID; the
|
|
147
148
|
workflow verifies that run used the commit selected by the annotated release
|
|
148
149
|
tag before downloading its artifacts. Dispatch the workflow with the release
|
|
149
|
-
tag as its Git ref so
|
|
150
|
+
tag as its Git ref so recovery checks out the immutable release boundary. Manual recovery
|
|
150
151
|
defaults to dry-run and publishes only when `dry_run` is cleared. npm retries skip an exact version only
|
|
151
152
|
after the local archive integrity and repository identity match registry
|
|
152
153
|
metadata. PyPI retries enable Twine's hash-aware existing-file behavior, which
|
|
153
154
|
skips matching files and fails when an existing filename has different content.
|
|
154
155
|
|
|
155
156
|
The Rust matrix has one row per target. Each row installs native dependencies,
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
builds the Cargo workspace once, then packages every discovered output from
|
|
158
|
+
that shared build. Set a source-only crate's or
|
|
158
159
|
release-enabled binary's `releaseExcludeOs` package option to omit it from
|
|
159
160
|
incompatible rows through Cargo `--exclude`. Release binary packaging and
|
|
160
161
|
artifact upload are skipped in those rows. UniFFI crates cannot use this option
|
|
@@ -171,10 +172,11 @@ Node facades. A binary-only row therefore installs no language package tool.
|
|
|
171
172
|
`rustVersion` remains the MSRV recorded in package manifests, while
|
|
172
173
|
`releaseRustVersion` independently defaults release compilation to `stable`.
|
|
173
174
|
Stable Windows rows verify and use the hosted runner's installed Rust toolchain
|
|
174
|
-
and select `rust-lld` for the workspace build.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
175
|
+
and select `rust-lld` for the workspace build. Rust release rows restore and
|
|
176
|
+
update dependency-only Cargo caches in the release branch's cache scope. The
|
|
177
|
+
cache key excludes workspace version changes, and manual tag recovery restores
|
|
178
|
+
without saving a tag-scoped copy. There is no separate cache workflow or
|
|
179
|
+
sccache layer. Phase timings are written to each build log. Python generation executes the already-built
|
|
178
180
|
`target/<triple>/release/<crate>-uniffi-bindgen` directly. Artifact packaging therefore
|
|
179
181
|
does no Rust compilation after the main workspace build.
|
|
180
182
|
|
|
@@ -204,7 +206,7 @@ to run the optional nonblocking registry install and import check after facade
|
|
|
204
206
|
publication. Python combines same-run platform wheels with standard wheel and
|
|
205
207
|
source builds, then publishes each distribution through its own PyPI
|
|
206
208
|
trusted-publisher environment. Binding publishers wait for their dependencies.
|
|
207
|
-
Trusted-publisher instructions name `release.yml` and the release
|
|
209
|
+
Trusted-publisher instructions name `release.yml` and the release branch policy.
|
|
208
210
|
The docs jobs generate README and TypeScript API content and deploy GitHub Pages
|
|
209
211
|
from the same workflow. When a conventional Node binding path already belongs
|
|
210
212
|
to a root subproject, Rust mapping reuses that project and adds binding
|
|
@@ -388,7 +390,7 @@ a new package is covered without a re-synth. Work from the root:
|
|
|
388
390
|
| `bun run test` | `eslint` once, then each member's tests |
|
|
389
391
|
| `bun run sync` | re-synth (`--watch` to keep synthing) |
|
|
390
392
|
| `bun run barrels` | regenerate the read-only `index.ts` barrels |
|
|
391
|
-
| `bun run bump` | version,
|
|
393
|
+
| `bun run bump` | version, commit, and push the current branch |
|
|
392
394
|
|
|
393
395
|
`bump` also mirrors a release into local registries when the active clients are
|
|
394
396
|
pointed at loopback services. npm uses `npm config get registry` and publishes
|
|
@@ -408,12 +410,14 @@ Use `--local-registry false` or `--local-pypi false` to disable either local
|
|
|
408
410
|
publish. An explicit `--local-pypi http://localhost:3141/user/index/` overrides
|
|
409
411
|
auto-detection; `--python-root` defaults to `packages/py`.
|
|
410
412
|
|
|
411
|
-
The
|
|
413
|
+
The configured release branch is the publication trigger. Its workflow creates
|
|
414
|
+
the annotated `v*` public release boundary from `VERSION`. Available workflow
|
|
412
415
|
stages form the generated chain Rust -> Python -> Node -> docs: Rust builds and
|
|
413
416
|
publishes native artifacts and Cargo crates, Python publishes standard
|
|
414
417
|
distributions, Node publishes standard workspace packages, and docs deploys
|
|
415
|
-
after publication. A stage with no corresponding outputs is omitted.
|
|
416
|
-
|
|
418
|
+
after publication. A stage with no corresponding outputs is omitted. Keep
|
|
419
|
+
ongoing development on another branch when every release-branch update should
|
|
420
|
+
publish.
|
|
417
421
|
|
|
418
422
|
Members intentionally keep only the tasks that something OTHER than a human
|
|
419
423
|
invokes, so there is no second place to run the same thing:
|
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.6.
|
|
6
|
+
export const PACKAGE_VERSION = "0.6.194";
|
|
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";
|
package/package.json
CHANGED
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@clack/prompts": "^1.7.0",
|
|
28
|
-
"@dbx-tools/core": "0.6.
|
|
29
|
-
"@dbx-tools/path": "0.6.
|
|
30
|
-
"@dbx-tools/shared-core": "0.6.
|
|
28
|
+
"@dbx-tools/core": "0.6.195",
|
|
29
|
+
"@dbx-tools/path": "0.6.195",
|
|
30
|
+
"@dbx-tools/shared-core": "0.6.195",
|
|
31
31
|
"commander": "^15.0.0",
|
|
32
32
|
"concurrently": "^10.0.3",
|
|
33
33
|
"constructs": "^10.6.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"main": "index.ts",
|
|
49
49
|
"license": "Apache-2.0",
|
|
50
|
-
"version": "0.6.
|
|
50
|
+
"version": "0.6.195",
|
|
51
51
|
"types": "index.ts",
|
|
52
52
|
"type": "module",
|
|
53
53
|
"exports": {
|
package/src/project-js.ts
CHANGED
|
@@ -416,7 +416,7 @@ function defaultProjectOptions(
|
|
|
416
416
|
// the block to render, giving the root a `publishConfig` it does not have
|
|
417
417
|
// today. Provenance is never written to a manifest here (projen only reads it
|
|
418
418
|
// in its own `Publisher`, and `release: false` means none exists); the
|
|
419
|
-
//
|
|
419
|
+
// The release workflow opts in per run through `npm_config_provenance`.
|
|
420
420
|
// instead, so LOCAL publishes to a verdaccio still work with no CI OIDC
|
|
421
421
|
// provider. See {@link DBXToolsRelease}.
|
|
422
422
|
...(isRoot ? {} : { npmAccess: javascript.NpmAccess.PUBLIC }),
|
package/src/project-py.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { IResolver } from "projen/lib/file";
|
|
|
6
6
|
import { JobPermission } from "projen/lib/github/workflows-model";
|
|
7
7
|
import { parse, stringify } from "smol-toml";
|
|
8
8
|
import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
|
|
9
|
-
import { projectRepositoryUrl } from "./project-js.ts";
|
|
9
|
+
import { projectReleaseBranch, projectRepositoryUrl } from "./project-js.ts";
|
|
10
10
|
import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
|
|
11
11
|
import type { DBXToolsProject, DBXToolsProjectOptions } from "./project.ts";
|
|
12
12
|
import { RELEASE_VERSION, releaseSourceSteps } from "./release-dispatch.ts";
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
releaseArtifactSteps,
|
|
15
15
|
releasePublishCondition,
|
|
16
16
|
releaseStageCondition,
|
|
17
|
-
releaseTagPattern,
|
|
18
17
|
releaseWorkflow,
|
|
19
18
|
} from "./release.ts";
|
|
20
19
|
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
@@ -577,13 +576,15 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
577
576
|
): void {
|
|
578
577
|
const repository = this.githubRepository();
|
|
579
578
|
const publications = this.trustedPublisherPublications(options);
|
|
580
|
-
const
|
|
579
|
+
const releaseBranch = isDBXToolsJavaScriptProject()(project)
|
|
580
|
+
? projectReleaseBranch(project)
|
|
581
|
+
: "main";
|
|
581
582
|
const linesBeforeAuthentication = [
|
|
582
583
|
"# PyPI Trusted Publisher Setup Instructions",
|
|
583
584
|
"",
|
|
584
585
|
"Use the system browser to audit and configure the PyPI trusted publishers below.",
|
|
585
586
|
"Do not use an in-app browser or embedded webview.",
|
|
586
|
-
"Do not visit GitHub or use the GitHub API or CLI. Every required GitHub owner, repository, workflow, environment, and
|
|
587
|
+
"Do not visit GitHub or use the GitHub API or CLI. Every required GitHub owner, repository, workflow, environment, and branch value is provided below and is authoritative.",
|
|
587
588
|
"Use only PyPI pages for this task.",
|
|
588
589
|
"",
|
|
589
590
|
"## Audit and confirmation",
|
|
@@ -601,7 +602,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
601
602
|
"",
|
|
602
603
|
"## GitHub environment policy",
|
|
603
604
|
"",
|
|
604
|
-
`- The supplied
|
|
605
|
+
`- The supplied branch policy value is ${releaseBranch}.`,
|
|
605
606
|
"- Do not inspect or configure GitHub environments during this task.",
|
|
606
607
|
"- GitHub environment administration is a separate task; use the supplied values only when comparing PyPI publisher entries.",
|
|
607
608
|
"",
|
|
@@ -642,7 +643,7 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
642
643
|
`- Repository name: ${repository.name}`,
|
|
643
644
|
"- Workflow name: release.yml",
|
|
644
645
|
`- Environment name: ${publication.environment}`,
|
|
645
|
-
`- GitHub environment
|
|
646
|
+
`- GitHub environment branch: ${releaseBranch}`,
|
|
646
647
|
...(publication.artifacts ? [`- Artifacts: ${publication.artifacts}`] : []),
|
|
647
648
|
"",
|
|
648
649
|
];
|
package/src/project-rs.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import { project as coreProject } from "@dbx-tools/core";
|
|
6
6
|
import { string } from "@dbx-tools/shared-core";
|
|
7
7
|
import { Project, TextFile, javascript } from "projen";
|
|
8
|
-
import { GithubWorkflow } from "projen/lib/github";
|
|
9
8
|
import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
|
|
10
9
|
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
11
10
|
import { DBXToolsTypeScriptProject, projectRepositoryUrl } from "./project-js.ts";
|
|
@@ -84,7 +83,7 @@ export interface DBXToolsRustWorkspaceOptions {
|
|
|
84
83
|
readonly pythonRoot?: string;
|
|
85
84
|
readonly pythonModulePrefix?: string;
|
|
86
85
|
readonly private?: boolean;
|
|
87
|
-
/** Generate
|
|
86
|
+
/** Generate release-branch-driven cross-platform UniFFI package releases. */
|
|
88
87
|
readonly release?: boolean;
|
|
89
88
|
/** Native release targets; defaults to the maintained GitHub-hosted matrix. */
|
|
90
89
|
readonly releaseTargets?: readonly UniFFIReleaseTarget[];
|
|
@@ -174,30 +173,35 @@ export const UNIFFI_RELEASE_TARGETS: readonly UniFFIReleaseTarget[] = [
|
|
|
174
173
|
},
|
|
175
174
|
] as const;
|
|
176
175
|
|
|
176
|
+
const UBRN_VERSION = "0.31.0-5";
|
|
177
|
+
const RELEASE_PLATFORMS_ENV = "DBX_TOOLS_RELEASE_PLATFORMS";
|
|
177
178
|
const RUST_CACHE_ENV = {
|
|
178
179
|
CARGO_INCREMENTAL: "0",
|
|
179
180
|
CARGO_TERM_COLOR: "always",
|
|
180
181
|
} as const;
|
|
181
|
-
const UBRN_VERSION = "0.31.0-5";
|
|
182
|
-
const RELEASE_PLATFORMS_ENV = "DBX_TOOLS_RELEASE_PLATFORMS";
|
|
183
182
|
|
|
184
|
-
function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
function cargoCacheKeyStep(): Record<string, unknown> {
|
|
184
|
+
return {
|
|
185
|
+
name: "Resolve Cargo dependency cache key",
|
|
186
|
+
id: "cargo_cache_key",
|
|
187
|
+
run: "node .projen/cargo-cache-key.mjs",
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function rustCacheSteps(sharedKey: string): readonly Record<string, unknown>[] {
|
|
189
192
|
return [
|
|
190
193
|
{
|
|
191
|
-
name: "Cache Cargo registry and
|
|
192
|
-
id:
|
|
194
|
+
name: "Cache Cargo registry and dependencies",
|
|
195
|
+
id: "cargo_cache",
|
|
193
196
|
uses: "Swatinem/rust-cache@v2.9.2",
|
|
194
197
|
with: {
|
|
195
198
|
"cache-targets": true,
|
|
196
199
|
"cache-workspace-crates": false,
|
|
197
200
|
"add-job-id-key": false,
|
|
198
|
-
"add-rust-environment-hash-key":
|
|
201
|
+
"add-rust-environment-hash-key": false,
|
|
202
|
+
key: "${{ steps.cargo_cache_key.outputs.key }}",
|
|
199
203
|
"shared-key": sharedKey,
|
|
200
|
-
"save-if":
|
|
204
|
+
"save-if": "${{ github.event_name == 'push' }}",
|
|
201
205
|
},
|
|
202
206
|
},
|
|
203
207
|
];
|
|
@@ -273,6 +277,66 @@ function uniffiReleaseTaskSource(): string {
|
|
|
273
277
|
return readFileSync(source, "utf8");
|
|
274
278
|
}
|
|
275
279
|
|
|
280
|
+
function cargoCacheKeySource(): string {
|
|
281
|
+
return `#!/usr/bin/env node
|
|
282
|
+
import { createHash } from "node:crypto";
|
|
283
|
+
import { appendFileSync, existsSync, readFileSync } from "node:fs";
|
|
284
|
+
import { spawnSync } from "node:child_process";
|
|
285
|
+
|
|
286
|
+
function command(command, args) {
|
|
287
|
+
const result = spawnSync(command, args, { encoding: "utf8" });
|
|
288
|
+
if (result.error) throw result.error;
|
|
289
|
+
if (result.status !== 0) throw new Error(\`\${command} exited with \${result.status}\`);
|
|
290
|
+
return result.stdout;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const hasLock = existsSync("Cargo.lock");
|
|
294
|
+
const metadata = JSON.parse(
|
|
295
|
+
command("cargo", [
|
|
296
|
+
"metadata",
|
|
297
|
+
...(hasLock ? ["--locked"] : []),
|
|
298
|
+
"--format-version",
|
|
299
|
+
"1",
|
|
300
|
+
"--no-deps",
|
|
301
|
+
]),
|
|
302
|
+
);
|
|
303
|
+
const workspaceNames = new Set(metadata.packages.map((pkg) => pkg.name));
|
|
304
|
+
const dependencyLock = hasLock
|
|
305
|
+
? readFileSync("Cargo.lock", "utf8")
|
|
306
|
+
.split("[[package]]")
|
|
307
|
+
.slice(1)
|
|
308
|
+
.filter((block) => {
|
|
309
|
+
const name = /^\\s*name = "([^"]+)"/m.exec(block)?.[1];
|
|
310
|
+
return name && !workspaceNames.has(name);
|
|
311
|
+
})
|
|
312
|
+
.map((block) => \`[[package]]\${block}\`)
|
|
313
|
+
.join("")
|
|
314
|
+
: "";
|
|
315
|
+
const manifests = [
|
|
316
|
+
metadata.workspace_root + "/Cargo.toml",
|
|
317
|
+
...metadata.packages.map((pkg) => pkg.manifest_path),
|
|
318
|
+
]
|
|
319
|
+
.map((path) =>
|
|
320
|
+
readFileSync(path, "utf8").replace(/^version = "[0-9]+\\.[0-9]+\\.[0-9]+"\\s*$/gm, ""),
|
|
321
|
+
)
|
|
322
|
+
.join("\\n");
|
|
323
|
+
const config = existsSync(".cargo/config.toml")
|
|
324
|
+
? readFileSync(".cargo/config.toml", "utf8")
|
|
325
|
+
: "";
|
|
326
|
+
const key = createHash("sha256")
|
|
327
|
+
.update(dependencyLock)
|
|
328
|
+
.update(manifests)
|
|
329
|
+
.update(config)
|
|
330
|
+
.update(command("rustc", ["-vV"]))
|
|
331
|
+
.digest("hex");
|
|
332
|
+
if (process.env.GITHUB_OUTPUT) {
|
|
333
|
+
appendFileSync(process.env.GITHUB_OUTPUT, \`key=\${key}\\n\`);
|
|
334
|
+
} else {
|
|
335
|
+
process.stdout.write(\`\${key}\\n\`);
|
|
336
|
+
}
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
|
|
276
340
|
/** Persisted mapping consumed by the focused Rust source watcher. */
|
|
277
341
|
export interface RustBindingMapping {
|
|
278
342
|
readonly crate: string;
|
|
@@ -842,112 +906,13 @@ export class DBXToolsRustWorkspace {
|
|
|
842
906
|
}
|
|
843
907
|
project.removeTask("rs:bindings:demo");
|
|
844
908
|
if (releaseEnabled) {
|
|
845
|
-
|
|
909
|
+
new TextFile(project, ".projen/cargo-cache-key.mjs", {
|
|
910
|
+
lines: cargoCacheKeySource().trimEnd().split("\n"),
|
|
911
|
+
});
|
|
846
912
|
this.addReleaseWorkflow(project, options, nativeTargets);
|
|
847
913
|
}
|
|
848
914
|
}
|
|
849
915
|
|
|
850
|
-
private addCacheWorkflow(
|
|
851
|
-
project: javascript.NodeProject,
|
|
852
|
-
options: DBXToolsRustWorkspaceOptions,
|
|
853
|
-
targets: readonly UniFFIReleaseTarget[],
|
|
854
|
-
): void {
|
|
855
|
-
if (!project.github || !isDBXToolsJavaScriptProject()(project) || targets.length === 0) return;
|
|
856
|
-
const releaseRustVersion = options.releaseRustVersion ?? "stable";
|
|
857
|
-
const usePreinstalledWindowsRust = releaseRustVersion === "stable";
|
|
858
|
-
const usesCargoLock = existsSync(join(project.outdir, "Cargo.lock"));
|
|
859
|
-
const hasReleaseExclusions = this.packages.some(
|
|
860
|
-
(pkg) => pkg.packageOptions.releaseExcludeOs?.length,
|
|
861
|
-
);
|
|
862
|
-
const matrix = targets.map((target) => ({
|
|
863
|
-
...target,
|
|
864
|
-
...(hasReleaseExclusions
|
|
865
|
-
? {
|
|
866
|
-
cargoExcludes: this.packages
|
|
867
|
-
.filter((pkg) => pkg.packageOptions.releaseExcludeOs?.includes(target.os))
|
|
868
|
-
.map((pkg) => `--exclude ${pkg.crateName}`)
|
|
869
|
-
.join(" "),
|
|
870
|
-
}
|
|
871
|
-
: {}),
|
|
872
|
-
}));
|
|
873
|
-
const workflow = new GithubWorkflow(project.github, "rust-cache", {
|
|
874
|
-
fileName: "rust-cache.yml",
|
|
875
|
-
limitConcurrency: true,
|
|
876
|
-
concurrencyOptions: { group: "rust-cache", cancelInProgress: true },
|
|
877
|
-
});
|
|
878
|
-
workflow.on({
|
|
879
|
-
push: {
|
|
880
|
-
branches: ["main"],
|
|
881
|
-
paths: [
|
|
882
|
-
".cargo/**",
|
|
883
|
-
".github/workflows/rust-cache.yml",
|
|
884
|
-
"Cargo.lock",
|
|
885
|
-
"Cargo.toml",
|
|
886
|
-
"packages/rs/**",
|
|
887
|
-
],
|
|
888
|
-
},
|
|
889
|
-
workflowDispatch: {},
|
|
890
|
-
});
|
|
891
|
-
workflow.addJob("prime", {
|
|
892
|
-
name: "${{ matrix.node }}",
|
|
893
|
-
runsOn: ["${{ matrix.runner }}"],
|
|
894
|
-
permissions: { contents: JobPermission.READ },
|
|
895
|
-
env: RUST_CACHE_ENV,
|
|
896
|
-
strategy: {
|
|
897
|
-
failFast: false,
|
|
898
|
-
matrix: { include: matrix },
|
|
899
|
-
},
|
|
900
|
-
steps: [
|
|
901
|
-
{ name: "Checkout", uses: "actions/checkout@v6" },
|
|
902
|
-
{
|
|
903
|
-
name: "Setup Rust",
|
|
904
|
-
...(usePreinstalledWindowsRust ? { if: "${{ matrix.os != 'win32' }}" } : {}),
|
|
905
|
-
uses: `dtolnay/rust-toolchain@${releaseRustVersion}`,
|
|
906
|
-
with: { targets: "${{ matrix.cargo }}" },
|
|
907
|
-
},
|
|
908
|
-
...(usePreinstalledWindowsRust
|
|
909
|
-
? [
|
|
910
|
-
{
|
|
911
|
-
name: "Verify preinstalled Windows Rust",
|
|
912
|
-
if: "${{ matrix.os == 'win32' }}",
|
|
913
|
-
shell: "bash",
|
|
914
|
-
run: [
|
|
915
|
-
"rustc --version --verbose",
|
|
916
|
-
"cargo --version",
|
|
917
|
-
'rustup target list --installed | grep -Fx "${{ matrix.cargo }}"',
|
|
918
|
-
'test -f "$(rustc --print sysroot)/lib/rustlib/${{ matrix.cargo }}/bin/rust-lld.exe"',
|
|
919
|
-
].join("\n"),
|
|
920
|
-
},
|
|
921
|
-
]
|
|
922
|
-
: []),
|
|
923
|
-
...rustCacheSteps(`release-\${{ matrix.cargo }}-rust-${releaseRustVersion}`),
|
|
924
|
-
{
|
|
925
|
-
name: "Install Linux native dependencies",
|
|
926
|
-
if: "${{ matrix.os == 'linux' }}",
|
|
927
|
-
run: [
|
|
928
|
-
"sudo rm -f /etc/apt/sources.list.d/google-chrome.list",
|
|
929
|
-
"sudo apt-get update",
|
|
930
|
-
"sudo apt-get install --yes libdbus-1-dev pkg-config",
|
|
931
|
-
].join("\n"),
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
name: "Prime Cargo target cache",
|
|
935
|
-
shell: "bash",
|
|
936
|
-
env: {
|
|
937
|
-
CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER:
|
|
938
|
-
"${{ matrix.os == 'win32' && 'rust-lld' || '' }}",
|
|
939
|
-
},
|
|
940
|
-
run: timedBash(
|
|
941
|
-
"rust_cache",
|
|
942
|
-
`cargo build --release --workspace${usesCargoLock ? " --locked" : ""} --target "\${{ matrix.cargo }}"${
|
|
943
|
-
hasReleaseExclusions ? " ${{ matrix.cargoExcludes }}" : ""
|
|
944
|
-
}`,
|
|
945
|
-
),
|
|
946
|
-
},
|
|
947
|
-
],
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
|
|
951
916
|
private addReleaseWorkflow(
|
|
952
917
|
project: javascript.NodeProject,
|
|
953
918
|
options: DBXToolsRustWorkspaceOptions,
|
|
@@ -1142,15 +1107,8 @@ export class DBXToolsRustWorkspace {
|
|
|
1142
1107
|
},
|
|
1143
1108
|
]
|
|
1144
1109
|
: []),
|
|
1145
|
-
|
|
1146
|
-
{
|
|
1147
|
-
name: "Log cache configuration",
|
|
1148
|
-
shell: "bash",
|
|
1149
|
-
run: [
|
|
1150
|
-
`echo "cargo_cache_namespace=release-\${{ matrix.cargo }}-rust-${releaseRustVersion}"`,
|
|
1151
|
-
'echo "cargo_cache_hit=${{ steps.cargo_cache.outputs.cache-hit }}"',
|
|
1152
|
-
].join("\n"),
|
|
1153
|
-
},
|
|
1110
|
+
cargoCacheKeyStep(),
|
|
1111
|
+
...rustCacheSteps(`release-\${{ matrix.cargo }}-rust-${releaseRustVersion}`),
|
|
1154
1112
|
{
|
|
1155
1113
|
name: "Install Linux native dependencies",
|
|
1156
1114
|
if: "${{ matrix.os == 'linux' }}",
|
package/src/release.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/** Unified
|
|
1
|
+
/** Unified default-branch release workflow generation. */
|
|
2
2
|
import { Component } from "projen";
|
|
3
3
|
import { GithubWorkflow } from "projen/lib/github";
|
|
4
4
|
import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
|
|
5
5
|
import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
|
|
6
|
-
import type
|
|
6
|
+
import { projectReleaseBranch, type DBXToolsJavaScriptProject } from "./project-js.ts";
|
|
7
7
|
import { applyTasks, taskScript } from "./project.ts";
|
|
8
8
|
import { RELEASE_VERSION, releaseSourceSteps } from "./release-dispatch.ts";
|
|
9
9
|
|
|
@@ -44,14 +44,14 @@ export function hasNodeRelease(project: DBXToolsJavaScriptProject): boolean {
|
|
|
44
44
|
return nodeReleaseProjects.has(project);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/** Tag pattern
|
|
47
|
+
/** Tag pattern created by release jobs and accepted by manual recovery. */
|
|
48
48
|
export function releaseTagPattern(project: DBXToolsJavaScriptProject): string {
|
|
49
49
|
const prefix = releaseTagPrefixes.get(project);
|
|
50
50
|
if (!prefix) throw new Error("Release workflow is not configured");
|
|
51
51
|
return `${prefix}*`;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/** Run a release stage on
|
|
54
|
+
/** Run a release stage on default-branch pushes or when selected for manual recovery. */
|
|
55
55
|
export function releaseStageCondition(stage: Exclude<ReleaseStage, "all">): string {
|
|
56
56
|
return `\${{ github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == '${stage}' }}`;
|
|
57
57
|
}
|
|
@@ -121,10 +121,10 @@ export function npmPublishEnvironment(): Record<string, string> {
|
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function verifyContextJob(tagPrefix: string): Job {
|
|
124
|
+
function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
|
|
125
125
|
return {
|
|
126
126
|
runsOn: ["ubuntu-latest"],
|
|
127
|
-
permissions: { actions: JobPermission.READ, contents: JobPermission.
|
|
127
|
+
permissions: { actions: JobPermission.READ, contents: JobPermission.WRITE },
|
|
128
128
|
outputs: {
|
|
129
129
|
release_tag: { stepId: "release", outputName: "release_tag" },
|
|
130
130
|
expected_sha: { stepId: "release", outputName: "expected_sha" },
|
|
@@ -132,10 +132,10 @@ function verifyContextJob(tagPrefix: string): Job {
|
|
|
132
132
|
},
|
|
133
133
|
steps: [
|
|
134
134
|
{
|
|
135
|
-
name: "Checkout release
|
|
135
|
+
name: "Checkout release source",
|
|
136
136
|
uses: "actions/checkout@v6",
|
|
137
137
|
with: {
|
|
138
|
-
ref: "${{ github.event_name == 'push' && github.
|
|
138
|
+
ref: "${{ github.event_name == 'push' && github.sha || inputs.expected_sha }}",
|
|
139
139
|
"fetch-depth": 1,
|
|
140
140
|
},
|
|
141
141
|
},
|
|
@@ -145,18 +145,36 @@ function verifyContextJob(tagPrefix: string): Job {
|
|
|
145
145
|
shell: "bash",
|
|
146
146
|
env: {
|
|
147
147
|
RELEASE_TAG:
|
|
148
|
-
"${{ github.event_name == '
|
|
148
|
+
"${{ github.event_name == 'workflow_dispatch' && inputs.release_tag || '' }}",
|
|
149
149
|
EXPECTED_SHA:
|
|
150
150
|
"${{ github.event_name == 'workflow_dispatch' && inputs.expected_sha || '' }}",
|
|
151
151
|
DRY_RUN: "${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false }}",
|
|
152
152
|
},
|
|
153
153
|
run: [
|
|
154
|
-
|
|
155
|
-
'
|
|
156
|
-
|
|
157
|
-
'RELEASE_SHA="$
|
|
158
|
-
'test "$(git rev-parse HEAD)" = "$RELEASE_SHA"',
|
|
159
|
-
|
|
154
|
+
'if [ "$GITHUB_EVENT_NAME" = "push" ]; then',
|
|
155
|
+
' test "$GITHUB_REF_TYPE" = "branch"',
|
|
156
|
+
` test "$GITHUB_REF_NAME" = "${releaseBranch}"`,
|
|
157
|
+
' RELEASE_SHA="$GITHUB_SHA"',
|
|
158
|
+
' test "$(git rev-parse HEAD)" = "$RELEASE_SHA"',
|
|
159
|
+
" RELEASE_VERSION=\"$(tr -d '\\r\\n' < VERSION)\"",
|
|
160
|
+
' [[ "$RELEASE_VERSION" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ ]]',
|
|
161
|
+
` RELEASE_TAG="${tagPrefix}$RELEASE_VERSION"`,
|
|
162
|
+
' if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then',
|
|
163
|
+
' git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"',
|
|
164
|
+
' test "$(git cat-file -t "$RELEASE_TAG")" = "tag"',
|
|
165
|
+
' test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$RELEASE_SHA"',
|
|
166
|
+
" else",
|
|
167
|
+
' git config user.name "github-actions[bot]"',
|
|
168
|
+
' git config user.email "41898282+github-actions[bot]@users.noreply.github.com"',
|
|
169
|
+
' git tag -a "$RELEASE_TAG" "$RELEASE_SHA" -m "$RELEASE_TAG"',
|
|
170
|
+
' git push origin "refs/tags/$RELEASE_TAG"',
|
|
171
|
+
" fi",
|
|
172
|
+
"else",
|
|
173
|
+
` case "$RELEASE_TAG" in ${tagPrefix}*) ;; *) exit 1 ;; esac`,
|
|
174
|
+
' git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"',
|
|
175
|
+
' test "$(git cat-file -t "$RELEASE_TAG")" = "tag"',
|
|
176
|
+
' RELEASE_SHA="$(git rev-parse "$RELEASE_TAG^{commit}")"',
|
|
177
|
+
' test "$(git rev-parse HEAD)" = "$RELEASE_SHA"',
|
|
160
178
|
' test "$GITHUB_REF_TYPE" = "tag"',
|
|
161
179
|
' test "$GITHUB_REF_NAME" = "$RELEASE_TAG"',
|
|
162
180
|
' test "$RELEASE_SHA" = "$EXPECTED_SHA"',
|
|
@@ -294,13 +312,14 @@ export class DBXToolsRelease extends Component {
|
|
|
294
312
|
constructor(project: DBXToolsJavaScriptProject, options: DBXToolsReleaseOptions = {}) {
|
|
295
313
|
super(project);
|
|
296
314
|
const tagPrefix = options.tagPrefix ?? "v";
|
|
315
|
+
const releaseBranch = projectReleaseBranch(project);
|
|
297
316
|
releaseTagPrefixes.set(project, tagPrefix);
|
|
298
317
|
if (options.nodeRelease !== false) nodeReleaseProjects.add(project);
|
|
299
318
|
applyTasks(project, {
|
|
300
319
|
bump: {
|
|
301
320
|
exec: taskScript(project, "bump.ts", `--prefix ${tagPrefix}`),
|
|
302
321
|
receiveArgs: true,
|
|
303
|
-
description: "Bump the release version (default patch), then commit
|
|
322
|
+
description: "Bump the release version (default patch), then commit and push it",
|
|
304
323
|
},
|
|
305
324
|
});
|
|
306
325
|
if (!project.github) return;
|
|
@@ -308,12 +327,12 @@ export class DBXToolsRelease extends Component {
|
|
|
308
327
|
const workflow = new GithubWorkflow(project.github, "release", {
|
|
309
328
|
fileName: "release.yml",
|
|
310
329
|
limitConcurrency: true,
|
|
311
|
-
concurrencyOptions: { group: "release", cancelInProgress:
|
|
330
|
+
concurrencyOptions: { group: "release", cancelInProgress: false },
|
|
312
331
|
});
|
|
313
332
|
workflow.runName =
|
|
314
|
-
"release ${{ github.event_name == 'push' && github.
|
|
333
|
+
"release ${{ github.event_name == 'push' && github.sha || inputs.release_tag }}";
|
|
315
334
|
workflow.on({
|
|
316
|
-
push: {
|
|
335
|
+
push: { branches: [releaseBranch] },
|
|
317
336
|
workflowDispatch: {
|
|
318
337
|
inputs: {
|
|
319
338
|
release_tag: {
|
|
@@ -350,7 +369,7 @@ export class DBXToolsRelease extends Component {
|
|
|
350
369
|
});
|
|
351
370
|
workflow.file?.addOverride("permissions.contents", "read");
|
|
352
371
|
workflow.file?.addOverride("on.workflow_dispatch.inputs.dry_run.default", true);
|
|
353
|
-
workflow.addJob("verify-context", verifyContextJob(tagPrefix));
|
|
372
|
+
workflow.addJob("verify-context", verifyContextJob(tagPrefix, releaseBranch));
|
|
354
373
|
if (options.nodeRelease !== false) {
|
|
355
374
|
workflow.addJob("publish-node", nodePublishJob(project));
|
|
356
375
|
}
|
package/tasks/bump.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* `projen bump` - compute the next release version, write it to the workspace
|
|
4
4
|
* `VERSION` file, synth so every manifest copies it, then (by default) commit,
|
|
5
|
-
*
|
|
5
|
+
* and push. Pushing the release branch triggers the workflow, which creates the
|
|
6
|
+
* annotated version tag before publishing.
|
|
6
7
|
*
|
|
7
8
|
* The base version is the HIGHEST published git tag across `<prefix>` and every
|
|
8
9
|
* `--sibling` prefix (fetched from the remote so a release cut elsewhere wins),
|
|
@@ -22,27 +23,28 @@
|
|
|
22
23
|
* tags. Both draw the fallback from the one root `VERSION` file, so the engine
|
|
23
24
|
* and the packages share a single source of truth.
|
|
24
25
|
*
|
|
25
|
-
* Flags
|
|
26
|
+
* Flags default ON unless noted; negate with the `--no-` form, per commander:
|
|
26
27
|
* --synth / --no-synth synth after writing VERSION so manifests copy it
|
|
27
28
|
* --version / --no-version write the bumped version into `VERSION`
|
|
28
29
|
* --commit / --no-commit commit the release (staged with `git add -A`)
|
|
29
|
-
* --tag
|
|
30
|
-
* --push / --no-push push the CURRENT branch
|
|
30
|
+
* --tag create the `<prefix><version>` git tag locally (default OFF)
|
|
31
|
+
* --push / --no-push push the CURRENT branch to origin
|
|
31
32
|
*
|
|
32
|
-
* `--publish` / `--no-publish` is an alias for `--push
|
|
33
|
-
*
|
|
33
|
+
* `--publish` / `--no-publish` is an alias for `--push`. A push publishes only
|
|
34
|
+
* when the current branch is the configured release branch. The tag prefix
|
|
35
|
+
* comes from `--prefix` (default `v`).
|
|
34
36
|
*
|
|
35
37
|
* `--local-registry <value>` publishes npm packages to a LOCAL registry (e.g.
|
|
36
|
-
* verdaccio) right after the
|
|
38
|
+
* verdaccio) right after the branch push. `--local-pypi <value>` does the same for
|
|
37
39
|
* Python packages through a writable devpi index. Values for both:
|
|
38
40
|
* - `auto` (default): publish only when `npm config get registry` is a
|
|
39
|
-
* loopback host, or when ANY active Python index
|
|
40
|
-
* `index-url` OR any `extra-index-url`, across uv and pip
|
|
41
|
+
* loopback host, or when ANY active Python index - the primary
|
|
42
|
+
* `index-url` OR any `extra-index-url`, across uv and pip - is a loopback
|
|
41
43
|
* devpi URL. Scanning the extras is what lets the corp proxy stay the
|
|
42
44
|
* primary index while a local devpi added as an extra is the detected
|
|
43
45
|
* publish target. The deploy endpoint for that index is taken from the
|
|
44
|
-
* GLOBAL uv config
|
|
45
|
-
* (or `UV_PUBLISH_URL`)
|
|
46
|
+
* GLOBAL uv config - an explicit `publish-url` on the matching `[[index]]`
|
|
47
|
+
* (or `UV_PUBLISH_URL`) - falling back to deriving it from the `+simple`
|
|
46
48
|
* URL shape when no such setting exists.
|
|
47
49
|
* - `false`: never publish locally.
|
|
48
50
|
* - a URL: always publish to that registry.
|
|
@@ -182,7 +184,7 @@ function localCargoRegistry(): string | undefined {
|
|
|
182
184
|
|
|
183
185
|
const program = new Command();
|
|
184
186
|
program
|
|
185
|
-
.description("Bump the release version, then commit
|
|
187
|
+
.description("Bump the release version, then commit and push it")
|
|
186
188
|
.addOption(
|
|
187
189
|
new Option("-l, --level <level>", "semver increment").choices([...LEVELS]).default("patch"),
|
|
188
190
|
)
|
|
@@ -211,13 +213,13 @@ program
|
|
|
211
213
|
.option("--no-synth", "do not run `projen` (synth) before bumping")
|
|
212
214
|
.option("--no-version", "do not write the bumped version into package.json")
|
|
213
215
|
.option("--no-commit", "do not commit the version change")
|
|
214
|
-
.option("--
|
|
215
|
-
.option("--no-push", "do not push the branch
|
|
216
|
-
// `--publish` is a friendlier alias for `--push
|
|
216
|
+
.option("--tag", "create the release tag locally instead of leaving it to the workflow")
|
|
217
|
+
.option("--no-push", "do not push the current branch to origin")
|
|
218
|
+
// `--publish` is a friendlier alias for `--push`.
|
|
217
219
|
.option("--no-publish", "alias for --no-push")
|
|
218
220
|
.option(
|
|
219
221
|
"--local-registry <value>",
|
|
220
|
-
"publish locally after the
|
|
222
|
+
"publish locally after the branch push: 'auto' (only a loopback npm registry), 'false', or a registry URL",
|
|
221
223
|
"auto",
|
|
222
224
|
)
|
|
223
225
|
.option(
|
|
@@ -280,6 +282,7 @@ program
|
|
|
280
282
|
}
|
|
281
283
|
|
|
282
284
|
const push = opts.push && opts.publish;
|
|
285
|
+
const createTags = opts.tag || siblings.length > 0;
|
|
283
286
|
|
|
284
287
|
// Write the source of truth first, then synth so every generated manifest
|
|
285
288
|
// COPIES it - synth never invents, resets, or drifts a version on its own.
|
|
@@ -338,7 +341,7 @@ program
|
|
|
338
341
|
"-m",
|
|
339
342
|
`chore(release): ${version}`,
|
|
340
343
|
"-m",
|
|
341
|
-
"
|
|
344
|
+
"Shipped with Kanna - https://kanna.sh",
|
|
342
345
|
"-m",
|
|
343
346
|
"Co-Authored-By: Kanna <noreply@kanna.sh>\nKanna-Agent: codex/databricks-gpt-5-6-sol",
|
|
344
347
|
]);
|
|
@@ -347,32 +350,24 @@ program
|
|
|
347
350
|
}
|
|
348
351
|
}
|
|
349
352
|
|
|
350
|
-
if (
|
|
353
|
+
if (createTags) {
|
|
351
354
|
for (const t of tags) git(["tag", "-a", t, "-m", t]);
|
|
352
355
|
logger.info(`tagged ${tags.join(", ")}`);
|
|
353
356
|
}
|
|
354
357
|
|
|
355
358
|
if (push) {
|
|
356
359
|
git(["push", "origin", "HEAD"]);
|
|
357
|
-
|
|
358
|
-
// all-zero remote SHA as a branch with no upstream and scans hundreds of
|
|
359
|
-
// historical commits. The release commit was already scanned by the
|
|
360
|
-
// branch push immediately above, and each annotated tag points to that
|
|
361
|
-
// exact commit, so bypass the hook only for the tag-ref transport.
|
|
362
|
-
// Push every tag in one invocation so a partial push cannot release
|
|
363
|
-
// only some namespaces at this version.
|
|
364
|
-
if (opts.tag) {
|
|
360
|
+
if (createTags) {
|
|
365
361
|
assertReleaseTagsPointToHead(tags);
|
|
366
362
|
git(["push", "--no-verify", "origin", ...tags]);
|
|
367
363
|
}
|
|
368
|
-
logger.success(`pushed ${
|
|
364
|
+
logger.success(`pushed ${createTags ? tags.join(", ") : "HEAD"} to origin`);
|
|
369
365
|
} else {
|
|
370
366
|
logger.info("skipped push (--no-push / --no-publish)");
|
|
371
367
|
}
|
|
372
368
|
|
|
373
|
-
// Local registry
|
|
374
|
-
//
|
|
375
|
-
// `--no-version` (nothing bumped to publish).
|
|
369
|
+
// Local registry publication starts after the branch push so the GitHub
|
|
370
|
+
// release workflow can own public publication independently.
|
|
376
371
|
const localRegistry = resolveLocalRegistry(opts.localRegistry);
|
|
377
372
|
const publishToLocalRegistry = opts.version && localRegistry;
|
|
378
373
|
if (opts.version === false && localRegistry) {
|