@dbx-tools/projen 0.6.195 → 0.6.197
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 +45 -31
- package/index.ts +2 -2
- package/package.json +4 -4
- package/src/pnpm-workspace.ts +4 -2
- package/src/project-js.ts +43 -12
- package/src/project-rs.ts +17 -6
- package/src/release.ts +35 -4
- package/src/workspace-version.ts +57 -7
- package/tasks/bump.ts +19 -439
- package/tasks/clean.ts +1 -1
- package/tasks/local-publish.ts +166 -0
- package/tasks/publish-uniffi-local.ts +30 -8
- package/tasks/publish.ts +22 -59
- package/tasks/release-pr.ts +285 -0
- package/tasks/version-check.ts +85 -0
package/README.md
CHANGED
|
@@ -128,10 +128,15 @@ in dependency order; adding or
|
|
|
128
128
|
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
|
+
`rs:bindings` is explicit rather than part of root `pre-compile`, so ordinary
|
|
132
|
+
JavaScript PR builds type-check committed generated bindings without performing
|
|
133
|
+
a host Rust build. Release preparation runs Cargo workspace tests and
|
|
134
|
+
regenerates bindings before JavaScript validation.
|
|
131
135
|
|
|
132
136
|
The workspace generates one `release.yml` workflow for every ecosystem. A push
|
|
133
|
-
to the configured release branch starts it
|
|
134
|
-
|
|
137
|
+
to the configured release branch starts it only when `VERSION` changed. The
|
|
138
|
+
context job requires the version to differ from its parent and exceed the latest
|
|
139
|
+
tag, creates the corresponding annotated `v*` tag, and exposes the verified tag,
|
|
135
140
|
commit, and version to every source job. If the tag already exists, it must be
|
|
136
141
|
annotated and reference the same commit. A manual run accepts that same tag and
|
|
137
142
|
commit but requires dry-run mode, so it builds and validates without publishing
|
|
@@ -189,7 +194,7 @@ Set `LOCAL_CARGO_REGISTRY` to a named Cargo registry such as a loopback
|
|
|
189
194
|
Cargo crates also publish directly to crates.io
|
|
190
195
|
with `CARGO_REGISTRY_TOKEN`. Override `releaseTargets` only when a consumer has
|
|
191
196
|
additional native runners; ordinary projects inherit the maintained matrix
|
|
192
|
-
automatically. `bun run
|
|
197
|
+
automatically. `bun run release` also accepts repeatable `--os` and `--arch`
|
|
193
198
|
selectors; every selected operating system is crossed with every selected
|
|
194
199
|
architecture. Omit both filters to regenerate the complete maintained matrix.
|
|
195
200
|
`DBX_TOOLS_RELEASE_PLATFORMS` can select the generated matrix without repeating
|
|
@@ -383,22 +388,27 @@ Every repo-wide task lives on the root, and the root's `compile` / `test`
|
|
|
383
388
|
delegate with `bun run --filter '*'` rather than emitting a step per member - so
|
|
384
389
|
a new package is covered without a re-synth. Work from the root:
|
|
385
390
|
|
|
386
|
-
| Task
|
|
387
|
-
|
|
|
388
|
-
| `bun run build`
|
|
389
|
-
| `bun run compile`
|
|
390
|
-
| `bun run test`
|
|
391
|
-
| `bun run sync`
|
|
392
|
-
| `bun run barrels`
|
|
393
|
-
| `bun run bump`
|
|
394
|
-
|
|
395
|
-
`
|
|
396
|
-
|
|
397
|
-
|
|
391
|
+
| Task | What it does |
|
|
392
|
+
| ----------------------- | ------------------------------------------------------- |
|
|
393
|
+
| `bun run build` | synth + workspace compile and tests |
|
|
394
|
+
| `bun run compile` | `tsc --build` in each member, in parallel |
|
|
395
|
+
| `bun run test` | `eslint` once, then each member's tests |
|
|
396
|
+
| `bun run sync` | re-synth (`--watch` to keep synthing) |
|
|
397
|
+
| `bun run barrels` | regenerate the read-only `index.ts` barrels |
|
|
398
|
+
| `bun run bump` | increment `VERSION` and synchronize generated versions |
|
|
399
|
+
| `bun run version:check` | verify every version surface matches `VERSION` |
|
|
400
|
+
| `bun run release` | run full local preflight and open a reviewed release PR |
|
|
401
|
+
|
|
402
|
+
`release` commits pending work on the current branch, pushes it, creates a
|
|
403
|
+
dedicated `release/v<version>` branch, invokes the pure `bump` task, validates
|
|
404
|
+
the complete workspace, runs local publication, and opens a PR into the
|
|
405
|
+
configured release branch. `--message` sets the source commit message. The task
|
|
406
|
+
never merges the PR unless `--approve` is passed; that option requests an
|
|
407
|
+
immediate admin merge so the main release can start without waiting for PR
|
|
408
|
+
checks. npm uses `npm config get registry` and publishes to a local Verdaccio
|
|
409
|
+
automatically. Publishable JavaScript members compile once
|
|
398
410
|
from the root in parallel, then upload through a bounded pool without rerunning
|
|
399
|
-
their `prepack` tasks.
|
|
400
|
-
lock release-current, publishing also skips redundant version stamping and the
|
|
401
|
-
lockfile reinstall. Python prefers uv's default index and only
|
|
411
|
+
their `prepack` tasks. Python prefers uv's default index and only
|
|
402
412
|
treats a loopback `.../+simple/` URL as writable devpi; a read-only cache such as
|
|
403
413
|
proxpi (`.../index/`) is deliberately ignored. The task stamps every Python
|
|
404
414
|
member and its sibling dependencies to the release version, builds the workspace
|
|
@@ -410,14 +420,18 @@ Use `--local-registry false` or `--local-pypi false` to disable either local
|
|
|
410
420
|
publish. An explicit `--local-pypi http://localhost:3141/user/index/` overrides
|
|
411
421
|
auto-detection; `--python-root` defaults to `packages/py`.
|
|
412
422
|
|
|
413
|
-
The
|
|
414
|
-
the
|
|
423
|
+
The GitHub PR workflow runs synth plus the workspace TypeScript compile rather
|
|
424
|
+
than the complete root build. Release preparation has already run Rust tests,
|
|
425
|
+
binding generation, workspace tests, and local package preflight before opening
|
|
426
|
+
the PR.
|
|
427
|
+
|
|
428
|
+
The configured release branch publishes only when a reviewed PR changes
|
|
429
|
+
`VERSION`. Its workflow creates the annotated `v*` public release boundary.
|
|
430
|
+
Available workflow
|
|
415
431
|
stages form the generated chain Rust -> Python -> Node -> docs: Rust builds and
|
|
416
432
|
publishes native artifacts and Cargo crates, Python publishes standard
|
|
417
433
|
distributions, Node publishes standard workspace packages, and docs deploys
|
|
418
|
-
after publication. A stage with no corresponding outputs is omitted.
|
|
419
|
-
ongoing development on another branch when every release-branch update should
|
|
420
|
-
publish.
|
|
434
|
+
after publication. A stage with no corresponding outputs is omitted.
|
|
421
435
|
|
|
422
436
|
Members intentionally keep only the tasks that something OTHER than a human
|
|
423
437
|
invokes, so there is no second place to run the same thing:
|
|
@@ -429,8 +443,8 @@ invokes, so there is no second place to run the same thing:
|
|
|
429
443
|
- `watch` - a single-package `tsc --build -w`, for narrowing a long
|
|
430
444
|
edit/compile loop to one package.
|
|
431
445
|
- `build` / `package` - a complete compile/test/pack lifecycle when invoked in
|
|
432
|
-
one package.
|
|
433
|
-
|
|
446
|
+
one package. Release preparation validates through the root's filtered tasks,
|
|
447
|
+
and publication uses concurrent `bun publish --ignore-scripts`. The package phase also packs
|
|
434
448
|
with `--ignore-scripts` because its build already compiled; `prepack` remains
|
|
435
449
|
available for a standalone publish that did not run `build` first.
|
|
436
450
|
- `install` / `install:ci` / `default` / `pre-compile` / `post-compile` -
|
|
@@ -452,9 +466,9 @@ example apps - so the packages, the engine, and the examples always match.
|
|
|
452
466
|
`src/workspace-version.ts` owns reading and writing it. Synth only ever reads it;
|
|
453
467
|
it never resets, upgrades, or downgrades a version on its own.
|
|
454
468
|
|
|
455
|
-
`bun run bump` is the only command that changes the number: it fetches
|
|
456
|
-
tags once, takes the highest
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
469
|
+
`bun run bump` is the only command that changes the number: it fetches remote
|
|
470
|
+
tags once, takes the highest `v*` tag as the base, increments by `--level`, writes
|
|
471
|
+
`VERSION`, then synths so every manifest copies it. It has no git or publication
|
|
472
|
+
side effects. `bun run release` invokes it while preparing the reviewed release
|
|
473
|
+
PR. The remote is consulted only on `bump` and on one-time creation of a missing
|
|
474
|
+
`VERSION` file, never on an ordinary synth.
|
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.197";
|
|
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";
|
|
@@ -64,4 +64,4 @@ export type { TypeScriptBindingModule } from "./src/uniffi.ts";
|
|
|
64
64
|
export { DBXToolsVsCode } from "./src/vscode.ts";
|
|
65
65
|
export type { IgnoreGroupOptions } from "./src/watch.ts";
|
|
66
66
|
export { VERSION_FILE, DEFAULT_VERSION } from "./src/workspace-version.ts";
|
|
67
|
-
export type { Semver } from "./src/workspace-version.ts";
|
|
67
|
+
export type { Semver, VersionLevel } from "./src/workspace-version.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.197",
|
|
29
|
+
"@dbx-tools/path": "0.6.197",
|
|
30
|
+
"@dbx-tools/shared-core": "0.6.197",
|
|
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.197",
|
|
51
51
|
"types": "index.ts",
|
|
52
52
|
"type": "module",
|
|
53
53
|
"exports": {
|
package/src/pnpm-workspace.ts
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { relative } from "node:path";
|
|
42
42
|
import { javascript, type Project } from "projen";
|
|
43
|
+
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
43
44
|
import { toPosix } from "./packages.ts";
|
|
44
45
|
|
|
45
46
|
/**
|
|
@@ -63,8 +64,8 @@ export type AllowBuilds = Record<string, boolean>;
|
|
|
63
64
|
* always available at `catalog:` without a per-repo override.
|
|
64
65
|
*/
|
|
65
66
|
const DEFAULT_CATALOG: Catalog = {
|
|
66
|
-
react: "
|
|
67
|
-
"react-dom": "
|
|
67
|
+
react: "~19.2.4",
|
|
68
|
+
"react-dom": "~19.2.4",
|
|
68
69
|
"@types/react": "^19.2.2",
|
|
69
70
|
"@types/react-dom": "^19.2.2",
|
|
70
71
|
// Tailwind v4 compiler for bun's dev server + `Bun.build` (the `app` tag).
|
|
@@ -117,6 +118,7 @@ const DEFAULT_ALLOW_BUILDS: AllowBuilds = {
|
|
|
117
118
|
};
|
|
118
119
|
|
|
119
120
|
const DEFAULT_OVERRIDES: Readonly<Record<string, string>> = {
|
|
121
|
+
bun: BUN_VERSION,
|
|
120
122
|
glob: "^13.0.0",
|
|
121
123
|
};
|
|
122
124
|
|
package/src/project-js.ts
CHANGED
|
@@ -44,7 +44,7 @@ import { DBXToolsRelease, type ReleaseDocsOptions } from "./release.ts";
|
|
|
44
44
|
import { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS, type PackageTag } from "./tags.ts";
|
|
45
45
|
import { DBXToolsRootTsconfig } from "./tsconfig.ts";
|
|
46
46
|
import { DBXToolsVsCode } from "./vscode.ts";
|
|
47
|
-
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
47
|
+
import { readWorkspaceVersion, syncWorkspaceManifestVersion } from "./workspace-version.ts";
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* The dbx-tools project surface, backed by projen's Node toolchain. A single
|
|
@@ -415,10 +415,10 @@ function defaultProjectOptions(
|
|
|
415
415
|
// would omit the key - except that `npmProvenance` then defaults on and forces
|
|
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
|
-
// in its own `Publisher`, and `release: false` means none exists)
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
//
|
|
418
|
+
// in its own `Publisher`, and `release: false` means none exists). The release
|
|
419
|
+
// workflow opts in per run through `npm_config_provenance`, so local publishes
|
|
420
|
+
// to Verdaccio still work without a CI OIDC provider. See
|
|
421
|
+
// {@link DBXToolsRelease}.
|
|
422
422
|
...(isRoot ? {} : { npmAccess: javascript.NpmAccess.PUBLIC }),
|
|
423
423
|
buildWorkflow: false,
|
|
424
424
|
workflowPackageCache: false,
|
|
@@ -426,8 +426,7 @@ function defaultProjectOptions(
|
|
|
426
426
|
// The root build validates the whole workspace and must not also pack every
|
|
427
427
|
// member into unused `dist/js` tarballs. Child projects keep projen's package
|
|
428
428
|
// task so `bun run build` in ONE package remains a complete compile/test/pack
|
|
429
|
-
// operation.
|
|
430
|
-
// compiles once with filtered root tasks and packs via
|
|
429
|
+
// operation. Release publication compiles once with filtered root tasks and packs via
|
|
431
430
|
// `bun publish --ignore-scripts` (see {@link applyCompiledPublish}).
|
|
432
431
|
...(isRoot ? { package: false } : {}),
|
|
433
432
|
jest: false,
|
|
@@ -450,7 +449,9 @@ function defaultProjectOptions(
|
|
|
450
449
|
prettierOptions: {
|
|
451
450
|
settings: PRETTIER_SETTINGS,
|
|
452
451
|
ignoreFile: true,
|
|
453
|
-
ignoreFileOptions: {
|
|
452
|
+
ignoreFileOptions: {
|
|
453
|
+
ignorePatterns: [...ignore.ignorePatterns({ test: false, lock: true })],
|
|
454
|
+
},
|
|
454
455
|
},
|
|
455
456
|
}
|
|
456
457
|
: {}),
|
|
@@ -656,6 +657,10 @@ export class DBXToolsNodeProject
|
|
|
656
657
|
public override preSynthesize(): void {
|
|
657
658
|
if (this.rootInstallOnly) this.with(ROOT_INSTALL_ONLY_MIXIN);
|
|
658
659
|
super.preSynthesize();
|
|
660
|
+
const version = readWorkspaceVersion(this.outdir);
|
|
661
|
+
for (const member of this.extraWorkspaceMembers) {
|
|
662
|
+
syncWorkspaceManifestVersion(join(this.outdir, member, "package.json"), version);
|
|
663
|
+
}
|
|
659
664
|
// Members come from the attached subprojects, which the root's scan appends
|
|
660
665
|
// after construction - so the list is filled here, not in the constructor.
|
|
661
666
|
// `extraWorkspaceMembers` adds self-synthesizing siblings (e.g. `projen/`).
|
|
@@ -834,6 +839,29 @@ class WorkspaceValidationTasks extends Component {
|
|
|
834
839
|
*/
|
|
835
840
|
class WorkflowDefaults extends Component {
|
|
836
841
|
public override preSynthesize(): void {
|
|
842
|
+
const project = this.project as javascript.NodeProject;
|
|
843
|
+
const workflow = project.buildWorkflow?.workflow;
|
|
844
|
+
if (workflow) {
|
|
845
|
+
const job = workflow.getJob("build");
|
|
846
|
+
if ("steps" in job) {
|
|
847
|
+
const mutableJob = job as unknown as { steps: JobStep[] | (() => JobStep[]) };
|
|
848
|
+
const configuredSteps = mutableJob.steps;
|
|
849
|
+
const rewrite = (steps: JobStep[]): JobStep[] =>
|
|
850
|
+
steps.map((step) =>
|
|
851
|
+
step.name === "build"
|
|
852
|
+
? {
|
|
853
|
+
...step,
|
|
854
|
+
name: "Validate generated files and types",
|
|
855
|
+
run: "bunx projen default\nbun run compile",
|
|
856
|
+
}
|
|
857
|
+
: step,
|
|
858
|
+
);
|
|
859
|
+
mutableJob.steps =
|
|
860
|
+
typeof configuredSteps === "function"
|
|
861
|
+
? () => rewrite(configuredSteps())
|
|
862
|
+
: rewrite(configuredSteps);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
837
865
|
const build = this.project.tryFindObjectFile(".github/workflows/build.yml");
|
|
838
866
|
for (const job of ["build", "self-mutation"]) {
|
|
839
867
|
build?.addOverride(`jobs.${job}.timeout-minutes`, 30);
|
|
@@ -890,6 +918,7 @@ class PrettierIgnoreGenerated extends Component {
|
|
|
890
918
|
public override preSynthesize(): void {
|
|
891
919
|
const prettier = javascript.Prettier.of(this.project);
|
|
892
920
|
if (!prettier) return;
|
|
921
|
+
prettier.addIgnorePattern("**/src/generated/**");
|
|
893
922
|
const rootAbs = resolve(this.project.outdir);
|
|
894
923
|
for (const sub of this.project.subprojects) {
|
|
895
924
|
if (!(sub instanceof javascript.NodeProject)) continue;
|
|
@@ -1115,7 +1144,9 @@ function initProject(
|
|
|
1115
1144
|
// to re-include a file whose parent directory is excluded - so every per-file
|
|
1116
1145
|
// `!/.github/...` negation projen emits for its own generated files silently
|
|
1117
1146
|
// does nothing, and the file cannot be added at all.
|
|
1118
|
-
project.gitignore.addPatterns(
|
|
1147
|
+
project.gitignore.addPatterns(
|
|
1148
|
+
...[...ignore.ignorePatterns({ test: false, dot: false, lock: true })],
|
|
1149
|
+
);
|
|
1119
1150
|
// What the dot group was actually earning here, named explicitly: secrets and
|
|
1120
1151
|
// local editor state. Both ignore CONTENTS (`.idea/*`) rather than the
|
|
1121
1152
|
// directory, so a later `!` negation can still reach a file inside.
|
|
@@ -1156,6 +1187,7 @@ function initProject(
|
|
|
1156
1187
|
eslint.addIgnorePattern(`${root}/openapi/**`);
|
|
1157
1188
|
eslint.addIgnorePattern(`${root}/**/index.ts`);
|
|
1158
1189
|
}
|
|
1190
|
+
eslint.addIgnorePattern("**/src/generated/**");
|
|
1159
1191
|
eslint.addIgnorePattern("projen/index.ts");
|
|
1160
1192
|
// The generated bun app scripts + unmanaged overrides live at the package root,
|
|
1161
1193
|
// outside any `src/**` tsconfig include, so the type-aware parser cannot resolve
|
|
@@ -1253,9 +1285,8 @@ function initProject(
|
|
|
1253
1285
|
new PrettierIgnoreGenerated(project);
|
|
1254
1286
|
|
|
1255
1287
|
new GeneratedSource(project);
|
|
1256
|
-
//
|
|
1257
|
-
//
|
|
1258
|
-
// authors. Independent of projen's own `release` component.
|
|
1288
|
+
// Version mutation, reviewed release preparation, and public publication are
|
|
1289
|
+
// separate surfaces owned by one release component.
|
|
1259
1290
|
new DBXToolsRelease(project as DBXToolsNodeProject, {
|
|
1260
1291
|
tagPrefix: options.releaseTagPrefix,
|
|
1261
1292
|
nodeRelease: options.nodeRelease,
|
package/src/project-rs.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { project as coreProject } from "@dbx-tools/core";
|
|
5
|
+
import { exec, project as coreProject } from "@dbx-tools/core";
|
|
6
6
|
import { string } from "@dbx-tools/shared-core";
|
|
7
|
-
import { Project, TextFile, javascript } from "projen";
|
|
7
|
+
import { Component, Project, TextFile, javascript } from "projen";
|
|
8
8
|
import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
|
|
9
9
|
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
10
10
|
import { DBXToolsTypeScriptProject, projectRepositoryUrl } from "./project-js.ts";
|
|
@@ -337,6 +337,19 @@ if (process.env.GITHUB_OUTPUT) {
|
|
|
337
337
|
`;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
/** Keep tracked workspace package versions in Cargo.lock aligned with VERSION. */
|
|
341
|
+
class RustWorkspaceVersionLock extends Component {
|
|
342
|
+
public override postSynthesize(): void {
|
|
343
|
+
exec.spawnSync("cargo", ["metadata", "--format-version", "1", "--no-deps"], {
|
|
344
|
+
cwd: this.project.outdir,
|
|
345
|
+
stdout: "ignore",
|
|
346
|
+
stderr: "inherit",
|
|
347
|
+
stdin: "ignore",
|
|
348
|
+
check: true,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
340
353
|
/** Persisted mapping consumed by the focused Rust source watcher. */
|
|
341
354
|
export interface RustBindingMapping {
|
|
342
355
|
readonly crate: string;
|
|
@@ -893,17 +906,15 @@ export class DBXToolsRustWorkspace {
|
|
|
893
906
|
.trimEnd()
|
|
894
907
|
.split("\n"),
|
|
895
908
|
});
|
|
909
|
+
new RustWorkspaceVersionLock(project);
|
|
896
910
|
project.addTask("rs:format", { exec: "cargo fmt --all" });
|
|
897
911
|
project.addTask("rs:lint", { exec: "cargo clippy --workspace --all-targets --all-features" });
|
|
898
912
|
project.addTask("rs:test", { exec: "cargo test --workspace" });
|
|
899
913
|
project.addTask("rs:build", { exec: "cargo build --workspace" });
|
|
900
|
-
|
|
914
|
+
project.addTask("rs:bindings", {
|
|
901
915
|
exec: "bun node_modules/@dbx-tools/projen/tasks/rust.ts",
|
|
902
916
|
description: "Generate language bindings for UniFFI-enabled Rust crates",
|
|
903
917
|
});
|
|
904
|
-
if (this.bindingMappings.some((binding) => binding.node)) {
|
|
905
|
-
project.tasks.tryFind("pre-compile")?.spawn(bindingsTask);
|
|
906
|
-
}
|
|
907
918
|
project.removeTask("rs:bindings:demo");
|
|
908
919
|
if (releaseEnabled) {
|
|
909
920
|
new TextFile(project, ".projen/cargo-cache-key.mjs", {
|
package/src/release.ts
CHANGED
|
@@ -136,7 +136,7 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
|
|
|
136
136
|
uses: "actions/checkout@v6",
|
|
137
137
|
with: {
|
|
138
138
|
ref: "${{ github.event_name == 'push' && github.sha || inputs.expected_sha }}",
|
|
139
|
-
"fetch-depth":
|
|
139
|
+
"fetch-depth": 2,
|
|
140
140
|
},
|
|
141
141
|
},
|
|
142
142
|
{
|
|
@@ -158,12 +158,26 @@ function verifyContextJob(tagPrefix: string, releaseBranch: string): Job {
|
|
|
158
158
|
' test "$(git rev-parse HEAD)" = "$RELEASE_SHA"',
|
|
159
159
|
" RELEASE_VERSION=\"$(tr -d '\\r\\n' < VERSION)\"",
|
|
160
160
|
' [[ "$RELEASE_VERSION" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ ]]',
|
|
161
|
+
' PREVIOUS_VERSION="$(git show "$RELEASE_SHA^:VERSION" | tr -d \'\\r\\n\')"',
|
|
162
|
+
' test "$PREVIOUS_VERSION" != "$RELEASE_VERSION"',
|
|
161
163
|
` RELEASE_TAG="${tagPrefix}$RELEASE_VERSION"`,
|
|
162
164
|
' if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then',
|
|
163
165
|
' git fetch --force origin "+refs/tags/$RELEASE_TAG:refs/tags/$RELEASE_TAG"',
|
|
164
166
|
' test "$(git cat-file -t "$RELEASE_TAG")" = "tag"',
|
|
165
167
|
' test "$(git rev-parse "$RELEASE_TAG^{commit}")" = "$RELEASE_SHA"',
|
|
166
168
|
" else",
|
|
169
|
+
" git fetch --force --tags origin",
|
|
170
|
+
' LATEST_VERSION=""',
|
|
171
|
+
` for TAG in $(git tag --sort=-version:refname --list '${tagPrefix}*'); do`,
|
|
172
|
+
` CANDIDATE="\${TAG#${tagPrefix}}"`,
|
|
173
|
+
' if [[ "$CANDIDATE" =~ ^[0-9]+\\.[0-9]+\\.[0-9]+$ ]]; then',
|
|
174
|
+
' LATEST_VERSION="$CANDIDATE"',
|
|
175
|
+
" break",
|
|
176
|
+
" fi",
|
|
177
|
+
" done",
|
|
178
|
+
' if [ -n "$LATEST_VERSION" ]; then',
|
|
179
|
+
' node -e \'const a=process.argv[1].split(".").map(Number); const b=process.argv[2].split(".").map(Number); if (!(b[0]>a[0] || b[0]===a[0] && (b[1]>a[1] || b[1]===a[1] && b[2]>a[2]))) process.exit(1)\' "$LATEST_VERSION" "$RELEASE_VERSION"',
|
|
180
|
+
" fi",
|
|
167
181
|
' git config user.name "github-actions[bot]"',
|
|
168
182
|
' git config user.email "41898282+github-actions[bot]@users.noreply.github.com"',
|
|
169
183
|
' git tag -a "$RELEASE_TAG" "$RELEASE_SHA" -m "$RELEASE_TAG"',
|
|
@@ -307,7 +321,7 @@ function addDocsJobs(
|
|
|
307
321
|
});
|
|
308
322
|
}
|
|
309
323
|
|
|
310
|
-
/** Owns the single release workflow and
|
|
324
|
+
/** Owns the single release workflow and local release preparation tasks. */
|
|
311
325
|
export class DBXToolsRelease extends Component {
|
|
312
326
|
constructor(project: DBXToolsJavaScriptProject, options: DBXToolsReleaseOptions = {}) {
|
|
313
327
|
super(project);
|
|
@@ -319,8 +333,25 @@ export class DBXToolsRelease extends Component {
|
|
|
319
333
|
bump: {
|
|
320
334
|
exec: taskScript(project, "bump.ts", `--prefix ${tagPrefix}`),
|
|
321
335
|
receiveArgs: true,
|
|
322
|
-
description: "
|
|
336
|
+
description: "Increment VERSION and synchronize generated workspace versions",
|
|
323
337
|
},
|
|
338
|
+
"version:check": {
|
|
339
|
+
exec: taskScript(project, "version-check.ts"),
|
|
340
|
+
description: "Verify every package and generated barrel matches VERSION",
|
|
341
|
+
},
|
|
342
|
+
...(project.github
|
|
343
|
+
? {
|
|
344
|
+
release: {
|
|
345
|
+
exec: taskScript(
|
|
346
|
+
project,
|
|
347
|
+
"release-pr.ts",
|
|
348
|
+
`--prefix ${tagPrefix} --base ${releaseBranch}`,
|
|
349
|
+
),
|
|
350
|
+
receiveArgs: true,
|
|
351
|
+
description: "Prepare, validate, locally publish, and open a reviewed release PR",
|
|
352
|
+
},
|
|
353
|
+
}
|
|
354
|
+
: {}),
|
|
324
355
|
});
|
|
325
356
|
if (!project.github) return;
|
|
326
357
|
|
|
@@ -332,7 +363,7 @@ export class DBXToolsRelease extends Component {
|
|
|
332
363
|
workflow.runName =
|
|
333
364
|
"release ${{ github.event_name == 'push' && github.sha || inputs.release_tag }}";
|
|
334
365
|
workflow.on({
|
|
335
|
-
push: { branches: [releaseBranch] },
|
|
366
|
+
push: { branches: [releaseBranch], paths: ["VERSION"] },
|
|
336
367
|
workflowDispatch: {
|
|
337
368
|
inputs: {
|
|
338
369
|
release_tag: {
|
package/src/workspace-version.ts
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
* DEFAULT_VERSION} when it is absent on a fresh tree); it never rewrites it, so an
|
|
9
9
|
* ordinary `bunx projen` cannot move a package version up or down.
|
|
10
10
|
*
|
|
11
|
-
* Only two callers change the number: `bump`
|
|
12
|
-
* one-time bootstrap of a workspace that has no
|
|
13
|
-
* base from
|
|
14
|
-
* cut elsewhere is respected, and fall back
|
|
15
|
-
*
|
|
16
|
-
*
|
|
11
|
+
* Only two callers change the number: the pure `bump` task used while preparing
|
|
12
|
+
* a reviewed release PR, and the one-time bootstrap of a workspace that has no
|
|
13
|
+
* `VERSION` yet. Both resolve the base from remote git tags first ({@link
|
|
14
|
+
* resolveRemoteVersion}) so a release cut elsewhere is respected, and fall back
|
|
15
|
+
* to the local file (or {@link DEFAULT_VERSION}) when the remote is unreachable
|
|
16
|
+
* or has no tags. The remote is consulted only on those paths.
|
|
17
17
|
*/
|
|
18
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { chmodSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
19
19
|
import { join } from "node:path";
|
|
20
20
|
import { exec } from "@dbx-tools/core";
|
|
21
21
|
|
|
@@ -30,6 +30,9 @@ const SEMVER = /^\d+\.\d+\.\d+$/;
|
|
|
30
30
|
/** A parsed `[major, minor, patch]` tuple. */
|
|
31
31
|
export type Semver = [number, number, number];
|
|
32
32
|
|
|
33
|
+
/** Supported semantic release increments. */
|
|
34
|
+
export type VersionLevel = "patch" | "minor" | "major";
|
|
35
|
+
|
|
33
36
|
/** Parse `x.y.z` (ignoring any leading `v`/prefix), or `undefined` when it does not match. */
|
|
34
37
|
export function parseSemver(raw: string): Semver | undefined {
|
|
35
38
|
const m = /(\d+)\.(\d+)\.(\d+)/.exec(raw.trim());
|
|
@@ -41,6 +44,13 @@ export function compareSemver(a: Semver, b: Semver): number {
|
|
|
41
44
|
return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
/** Increment a semantic version tuple by the requested release level. */
|
|
48
|
+
export function incrementSemver(version: Semver, level: VersionLevel): Semver {
|
|
49
|
+
if (level === "major") return [version[0] + 1, 0, 0];
|
|
50
|
+
if (level === "minor") return [version[0], version[1] + 1, 0];
|
|
51
|
+
return [version[0], version[1], version[2] + 1];
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
/** Absolute path to the `VERSION` file for a workspace root. */
|
|
45
55
|
export function versionPath(root: string): string {
|
|
46
56
|
return join(root, VERSION_FILE);
|
|
@@ -70,6 +80,30 @@ export function writeWorkspaceVersion(root: string, version: string): void {
|
|
|
70
80
|
writeFileSync(versionPath(root), `${version}\n`);
|
|
71
81
|
}
|
|
72
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Synchronize one existing workspace manifest with the authoritative version.
|
|
85
|
+
*
|
|
86
|
+
* This covers workspace members that synthesize themselves and therefore are
|
|
87
|
+
* not child projects of the root. Missing manifests are ignored so a freshly
|
|
88
|
+
* bootstrapped extra member can create its package metadata independently.
|
|
89
|
+
*/
|
|
90
|
+
export function syncWorkspaceManifestVersion(manifestPath: string, version: string): boolean {
|
|
91
|
+
if (!existsSync(manifestPath)) return false;
|
|
92
|
+
const content = readFileSync(manifestPath, "utf8");
|
|
93
|
+
const manifest = JSON.parse(content) as Record<string, unknown>;
|
|
94
|
+
if (manifest.version === version) return false;
|
|
95
|
+
|
|
96
|
+
manifest.version = version;
|
|
97
|
+
const { mode } = statSync(manifestPath);
|
|
98
|
+
chmodSync(manifestPath, mode | 0o200);
|
|
99
|
+
try {
|
|
100
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
101
|
+
} finally {
|
|
102
|
+
chmodSync(manifestPath, mode);
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
|
|
73
107
|
/** Run git in `cwd`, capturing stdout and swallowing failure (offline, no repo). */
|
|
74
108
|
function gitCapture(cwd: string, args: string[]): string {
|
|
75
109
|
try {
|
|
@@ -138,6 +172,22 @@ export function resolveBaseVersion(
|
|
|
138
172
|
return { version: readWorkspaceVersion(root), source: "local" };
|
|
139
173
|
}
|
|
140
174
|
|
|
175
|
+
/** Resolve the next release version without mutating the workspace. */
|
|
176
|
+
export function resolveNextVersion(
|
|
177
|
+
root: string,
|
|
178
|
+
prefixes: readonly string[],
|
|
179
|
+
level: VersionLevel,
|
|
180
|
+
options: { fetch?: boolean } = {},
|
|
181
|
+
): { base: string; version: string; source: "remote" | "local" } {
|
|
182
|
+
const base = resolveBaseVersion(root, prefixes, options);
|
|
183
|
+
const parsed = parseSemver(base.version) ?? [0, 0, 1];
|
|
184
|
+
return {
|
|
185
|
+
base: parsed.join("."),
|
|
186
|
+
version: incrementSemver(parsed, level).join("."),
|
|
187
|
+
source: base.source,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
141
191
|
/**
|
|
142
192
|
* Create the `VERSION` file when it does not yet exist, seeding it from the remote
|
|
143
193
|
* tags (or {@link DEFAULT_VERSION} when the remote is unreachable / has no tag). An
|