@dbx-tools/projen 0.6.199 → 0.6.201
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 +16 -12
- package/index.ts +1 -1
- package/package.json +4 -4
- package/src/project-js.ts +23 -4
- package/src/project-rs.ts +1 -1
- package/tasks/release-pr.ts +100 -44
package/README.md
CHANGED
|
@@ -130,8 +130,9 @@ 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
|
`rs:bindings` is explicit rather than part of root `pre-compile`, so ordinary
|
|
132
132
|
JavaScript PR builds type-check committed generated bindings without performing
|
|
133
|
-
a host Rust build.
|
|
134
|
-
|
|
133
|
+
a host Rust build. Regenerate bindings through the focused watcher or explicit
|
|
134
|
+
task while changing a UniFFI API. Release preparation runs Cargo workspace tests
|
|
135
|
+
without invoking UBRN.
|
|
135
136
|
|
|
136
137
|
The workspace generates one `release.yml` workflow for every ecosystem. A push
|
|
137
138
|
to the configured release branch starts it only when `VERSION` changed. The
|
|
@@ -399,14 +400,17 @@ a new package is covered without a re-synth. Work from the root:
|
|
|
399
400
|
| `bun run version:check` | verify every version surface matches `VERSION` |
|
|
400
401
|
| `bun run release` | run full local preflight and open a reviewed release PR |
|
|
401
402
|
|
|
402
|
-
`release` commits pending work on the current branch,
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
403
|
+
`release` commits pending work on the current branch, merges the latest remote
|
|
404
|
+
release branch when needed, and pushes it. It prepares the dedicated
|
|
405
|
+
`release/v<version>` branch inside `.worktrees/<tag>`, leaving the source
|
|
406
|
+
checkout on its current branch throughout validation, local publication, and
|
|
407
|
+
PR creation. Failed preparation can resume from that worktree; success removes
|
|
408
|
+
it. `--message` sets the source commit message. The task never merges
|
|
409
|
+
the PR unless `--approve` is passed; that option merges the release branch
|
|
410
|
+
directly through GitHub's merge API so no PR checks are created. A repository
|
|
411
|
+
that blocks direct merges falls back to an immediate admin-merged PR. npm uses
|
|
412
|
+
`npm config get registry` and publishes to a local Verdaccio automatically.
|
|
413
|
+
Publishable JavaScript members compile once
|
|
410
414
|
from the root in parallel, then upload through a bounded pool without rerunning
|
|
411
415
|
their `prepack` tasks. Python prefers uv's default index and only
|
|
412
416
|
treats a loopback `.../+simple/` URL as writable devpi; a read-only cache such as
|
|
@@ -422,8 +426,8 @@ auto-detection; `--python-root` defaults to `packages/py`.
|
|
|
422
426
|
|
|
423
427
|
The GitHub PR workflow runs synth plus the workspace TypeScript compile rather
|
|
424
428
|
than the complete root build. Release preparation has already run Rust tests,
|
|
425
|
-
|
|
426
|
-
|
|
429
|
+
workspace type-checking, and local package preflight before opening the PR.
|
|
430
|
+
JavaScript behavior tests remain an explicit developer task.
|
|
427
431
|
|
|
428
432
|
The configured release branch publishes only when a reviewed PR changes
|
|
429
433
|
`VERSION`. Its workflow creates the annotated `v*` public release boundary.
|
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.201";
|
|
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.201",
|
|
29
|
+
"@dbx-tools/path": "0.6.201",
|
|
30
|
+
"@dbx-tools/shared-core": "0.6.201",
|
|
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.201",
|
|
51
51
|
"types": "index.ts",
|
|
52
52
|
"type": "module",
|
|
53
53
|
"exports": {
|
package/src/project-js.ts
CHANGED
|
@@ -863,17 +863,36 @@ class WorkflowDefaults extends Component {
|
|
|
863
863
|
}
|
|
864
864
|
}
|
|
865
865
|
const build = this.project.tryFindObjectFile(".github/workflows/build.yml");
|
|
866
|
+
build?.addOverride("on.pull_request.types", ["opened", "synchronize", "reopened", "closed"]);
|
|
867
|
+
build?.addOverride(
|
|
868
|
+
"jobs.build.if",
|
|
869
|
+
"${{ github.event_name != 'pull_request' || github.event.action != 'closed' }}",
|
|
870
|
+
);
|
|
866
871
|
for (const job of ["build", "self-mutation"]) {
|
|
867
872
|
build?.addOverride(`jobs.${job}.timeout-minutes`, 30);
|
|
868
873
|
}
|
|
869
|
-
this.project
|
|
870
|
-
|
|
871
|
-
|
|
874
|
+
const pullRequestLint = this.project.tryFindObjectFile(
|
|
875
|
+
".github/workflows/pull-request-lint.yml",
|
|
876
|
+
);
|
|
877
|
+
pullRequestLint?.addOverride("on.pull_request_target.types", [
|
|
878
|
+
"labeled",
|
|
879
|
+
"opened",
|
|
880
|
+
"synchronize",
|
|
881
|
+
"reopened",
|
|
882
|
+
"ready_for_review",
|
|
883
|
+
"edited",
|
|
884
|
+
"closed",
|
|
885
|
+
]);
|
|
886
|
+
pullRequestLint?.addOverride(
|
|
887
|
+
"jobs.validate.if",
|
|
888
|
+
"(github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && github.event.action != 'closed'",
|
|
889
|
+
);
|
|
890
|
+
pullRequestLint?.addOverride("jobs.validate.timeout-minutes", 10);
|
|
872
891
|
for (const name of ["build", "pull-request-lint"]) {
|
|
873
892
|
const workflow = this.project.tryFindObjectFile(`.github/workflows/${name}.yml`);
|
|
874
893
|
workflow?.addOverride("permissions", { contents: "read" });
|
|
875
894
|
workflow?.addOverride("concurrency", {
|
|
876
|
-
group: "${{ github.workflow }}-${{ github.ref }}",
|
|
895
|
+
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}",
|
|
877
896
|
"cancel-in-progress": true,
|
|
878
897
|
});
|
|
879
898
|
}
|
package/src/project-rs.ts
CHANGED
|
@@ -340,7 +340,7 @@ if (process.env.GITHUB_OUTPUT) {
|
|
|
340
340
|
/** Keep tracked workspace package versions in Cargo.lock aligned with VERSION. */
|
|
341
341
|
class RustWorkspaceVersionLock extends Component {
|
|
342
342
|
public override postSynthesize(): void {
|
|
343
|
-
exec.spawnSync("cargo", ["metadata", "--format-version", "1"
|
|
343
|
+
exec.spawnSync("cargo", ["metadata", "--format-version", "1"], {
|
|
344
344
|
cwd: this.project.outdir,
|
|
345
345
|
stdout: "ignore",
|
|
346
346
|
stderr: "inherit",
|
package/tasks/release-pr.ts
CHANGED
|
@@ -95,11 +95,21 @@ function run(root: string, command: string, args: string[], env?: NodeJS.Process
|
|
|
95
95
|
});
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
function
|
|
98
|
+
function runIgnoringStdout(root: string, command: string, args: string[]): void {
|
|
99
|
+
exec.spawnSync(command, args, {
|
|
100
|
+
cwd: root,
|
|
101
|
+
stdout: "ignore",
|
|
102
|
+
stderr: "inherit",
|
|
103
|
+
stdin: "ignore",
|
|
104
|
+
check: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function githubAccount(root: string): { owner: string; repository: string; token: string } {
|
|
99
109
|
const repository = project.repositoryUrl(root);
|
|
100
110
|
if (!repository) throw new Error("Release preparation requires a GitHub repository");
|
|
101
|
-
const owner = new URL(repository).pathname.split("/").filter(Boolean)
|
|
102
|
-
if (!owner) throw new Error(`Cannot determine GitHub
|
|
111
|
+
const [owner, name] = new URL(repository).pathname.split("/").filter(Boolean);
|
|
112
|
+
if (!owner || !name) throw new Error(`Cannot determine GitHub identity from ${repository}`);
|
|
103
113
|
const token = exec
|
|
104
114
|
.spawnSync("gh", ["auth", "token", "--user", owner], {
|
|
105
115
|
cwd: root,
|
|
@@ -110,7 +120,7 @@ function githubAccount(root: string): { owner: string; token: string } {
|
|
|
110
120
|
})
|
|
111
121
|
.stdout?.trim();
|
|
112
122
|
if (!token) throw new Error(`No GitHub CLI authentication found for ${owner}`);
|
|
113
|
-
return { owner, token };
|
|
123
|
+
return { owner, repository: name.replace(/\.git$/, ""), token };
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
const program = new Command();
|
|
@@ -138,7 +148,7 @@ program
|
|
|
138
148
|
.option("--local-pypi <value>", "local PyPI index: auto, false, or an explicit URL", "auto")
|
|
139
149
|
.option("--python-root <path>", "Python workspace package root", "packages/py")
|
|
140
150
|
.option("--no-local-cargo", "skip local Cargo publication")
|
|
141
|
-
.option("--approve", "merge the release
|
|
151
|
+
.option("--approve", "merge the release branch directly into the release base")
|
|
142
152
|
.action(
|
|
143
153
|
async (opts: {
|
|
144
154
|
level: VersionLevel;
|
|
@@ -161,9 +171,9 @@ program
|
|
|
161
171
|
const next = resolveNextVersion(root, [opts.prefix], opts.level, { fetch: false });
|
|
162
172
|
const releaseTag = `${opts.prefix}${next.version}`;
|
|
163
173
|
const releaseBranch = `release/${releaseTag}`;
|
|
164
|
-
const
|
|
165
|
-
if (currentBranch.startsWith("release/")
|
|
166
|
-
throw new Error(
|
|
174
|
+
const releaseRoot = join(root, ".worktrees", releaseTag);
|
|
175
|
+
if (currentBranch.startsWith("release/")) {
|
|
176
|
+
throw new Error("Release preparation must start from a source branch");
|
|
167
177
|
}
|
|
168
178
|
if (
|
|
169
179
|
git(root, ["ls-remote", "--tags", "origin", `refs/tags/${releaseTag}`], {
|
|
@@ -174,19 +184,21 @@ program
|
|
|
174
184
|
throw new Error(`Release tag already exists: ${releaseTag}`);
|
|
175
185
|
}
|
|
176
186
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
pushCurrentBranch(root, currentBranch);
|
|
187
|
+
const status = git(root, ["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
188
|
+
capture: true,
|
|
189
|
+
});
|
|
190
|
+
if (status) {
|
|
191
|
+
git(root, ["add", "-A"]);
|
|
192
|
+
git(root, ["commit", "-m", opts.message]);
|
|
193
|
+
}
|
|
194
|
+
if (!gitSucceeds(root, ["merge-base", "--is-ancestor", `origin/${opts.base}`, "HEAD"])) {
|
|
195
|
+
git(root, ["merge", "--no-edit", `origin/${opts.base}`]);
|
|
196
|
+
}
|
|
197
|
+
pushCurrentBranch(root, currentBranch);
|
|
189
198
|
|
|
199
|
+
const worktreeExists = existsSync(join(releaseRoot, ".git"));
|
|
200
|
+
if (!worktreeExists) {
|
|
201
|
+
git(root, ["worktree", "prune"]);
|
|
190
202
|
const localBranch = git(root, ["branch", "--list", releaseBranch], { capture: true });
|
|
191
203
|
const remoteBranch = git(
|
|
192
204
|
root,
|
|
@@ -194,16 +206,31 @@ program
|
|
|
194
206
|
{ capture: true, check: false },
|
|
195
207
|
);
|
|
196
208
|
if (localBranch || remoteBranch) {
|
|
197
|
-
throw new Error(`Release branch already exists: ${releaseBranch}`);
|
|
209
|
+
throw new Error(`Release branch already exists without its worktree: ${releaseBranch}`);
|
|
198
210
|
}
|
|
199
|
-
git(root, ["
|
|
211
|
+
git(root, ["worktree", "add", "-b", releaseBranch, releaseRoot, "HEAD"]);
|
|
212
|
+
run(releaseRoot, process.execPath, ["install"]);
|
|
200
213
|
} else {
|
|
201
|
-
|
|
214
|
+
const releaseStatus = git(
|
|
215
|
+
releaseRoot,
|
|
216
|
+
["status", "--porcelain=v1", "--untracked-files=all"],
|
|
217
|
+
{ capture: true },
|
|
218
|
+
);
|
|
219
|
+
if (releaseStatus) {
|
|
220
|
+
git(releaseRoot, ["stash", "push", "--include-untracked", "--message", "release-resume"]);
|
|
221
|
+
}
|
|
222
|
+
if (!gitSucceeds(releaseRoot, ["merge-base", "--is-ancestor", currentBranch, "HEAD"])) {
|
|
223
|
+
git(releaseRoot, ["merge", "--no-edit", currentBranch]);
|
|
224
|
+
}
|
|
225
|
+
if (releaseStatus) {
|
|
226
|
+
git(releaseRoot, ["stash", "pop"]);
|
|
227
|
+
}
|
|
228
|
+
logger.info(`resuming ${releaseBranch} in ${releaseRoot}`);
|
|
202
229
|
}
|
|
203
230
|
|
|
204
231
|
const bumpScript = fileURLToPath(new URL("./bump.ts", import.meta.url));
|
|
205
232
|
const versionCheckScript = fileURLToPath(new URL("./version-check.ts", import.meta.url));
|
|
206
|
-
run(
|
|
233
|
+
run(releaseRoot, process.execPath, [
|
|
207
234
|
bumpScript,
|
|
208
235
|
"--level",
|
|
209
236
|
opts.level,
|
|
@@ -212,50 +239,55 @@ program
|
|
|
212
239
|
...opts.os.flatMap((value) => ["--os", value]),
|
|
213
240
|
...opts.arch.flatMap((value) => ["--arch", value]),
|
|
214
241
|
]);
|
|
215
|
-
if (readWorkspaceVersion(
|
|
242
|
+
if (readWorkspaceVersion(releaseRoot) !== next.version) {
|
|
216
243
|
throw new Error(`Release preparation did not produce ${next.version}`);
|
|
217
244
|
}
|
|
218
245
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
246
|
+
if (existsSync(join(releaseRoot, "Cargo.toml"))) {
|
|
247
|
+
runIgnoringStdout(releaseRoot, "cargo", ["metadata", "--format-version", "1"]);
|
|
248
|
+
}
|
|
249
|
+
run(releaseRoot, process.execPath, [versionCheckScript]);
|
|
250
|
+
if (existsSync(join(releaseRoot, "Cargo.toml"))) {
|
|
251
|
+
run(releaseRoot, "cargo", [
|
|
222
252
|
"test",
|
|
223
253
|
"--workspace",
|
|
224
|
-
...(existsSync(join(
|
|
254
|
+
...(existsSync(join(releaseRoot, "Cargo.lock")) ? ["--locked"] : []),
|
|
225
255
|
]);
|
|
226
|
-
run(root, process.execPath, ["run", "rs:bindings"]);
|
|
227
256
|
}
|
|
228
|
-
run(
|
|
229
|
-
run(root, process.execPath, ["run", "test"]);
|
|
257
|
+
run(releaseRoot, process.execPath, ["run", "compile"]);
|
|
230
258
|
await publishLocalRelease({
|
|
231
|
-
root,
|
|
259
|
+
root: releaseRoot,
|
|
232
260
|
version: next.version,
|
|
233
261
|
localRegistry: opts.localRegistry,
|
|
234
262
|
localPypi: opts.localPypi,
|
|
235
263
|
pythonRoot: opts.pythonRoot,
|
|
236
264
|
localCargo: opts.localCargo,
|
|
237
265
|
});
|
|
238
|
-
run(
|
|
266
|
+
run(releaseRoot, process.execPath, [versionCheckScript]);
|
|
239
267
|
|
|
240
|
-
git(
|
|
241
|
-
const staged = git(
|
|
268
|
+
git(releaseRoot, ["add", "-A"]);
|
|
269
|
+
const staged = git(releaseRoot, ["diff", "--cached", "--name-only"], { capture: true });
|
|
242
270
|
if (staged) {
|
|
243
|
-
git(
|
|
244
|
-
} else if (!
|
|
271
|
+
git(releaseRoot, ["commit", "-m", `chore(release): ${next.version}`]);
|
|
272
|
+
} else if (!worktreeExists) {
|
|
245
273
|
throw new Error("Release preparation produced no changes");
|
|
246
274
|
}
|
|
247
|
-
|
|
275
|
+
// The source push and release commit hooks have already scanned every new
|
|
276
|
+
// byte. A new remote branch has no upstream comparison point, so the
|
|
277
|
+
// managed pre-push hook would rescan the repository's complete history.
|
|
278
|
+
git(releaseRoot, ["push", "--no-verify", "--set-upstream", "origin", releaseBranch]);
|
|
248
279
|
|
|
249
280
|
const title = `chore(release): ${next.version}`;
|
|
250
281
|
const body = [
|
|
251
282
|
`Release ${releaseTag}.`,
|
|
252
283
|
"",
|
|
253
|
-
`Source commit: ${git(
|
|
284
|
+
`Source commit: ${git(releaseRoot, ["rev-parse", `${releaseBranch}^`], { capture: true })}`,
|
|
254
285
|
"",
|
|
255
286
|
"Merging this PR updates VERSION on main and starts the public release workflow.",
|
|
256
287
|
].join("\n");
|
|
257
288
|
const githubEnvironment = { ...process.env, GH_TOKEN: account.token };
|
|
258
|
-
|
|
289
|
+
const ensurePullRequest = (): void => {
|
|
290
|
+
if (commandSucceeds(root, "gh", ["pr", "view", releaseBranch], githubEnvironment)) return;
|
|
259
291
|
run(
|
|
260
292
|
root,
|
|
261
293
|
"gh",
|
|
@@ -273,11 +305,35 @@ program
|
|
|
273
305
|
],
|
|
274
306
|
githubEnvironment,
|
|
275
307
|
);
|
|
276
|
-
}
|
|
308
|
+
};
|
|
277
309
|
if (opts.approve) {
|
|
278
|
-
|
|
310
|
+
const merged = commandSucceeds(
|
|
311
|
+
root,
|
|
312
|
+
"gh",
|
|
313
|
+
[
|
|
314
|
+
"api",
|
|
315
|
+
"--method",
|
|
316
|
+
"POST",
|
|
317
|
+
`repos/${account.owner}/${account.repository}/merges`,
|
|
318
|
+
"-f",
|
|
319
|
+
`base=${opts.base}`,
|
|
320
|
+
"-f",
|
|
321
|
+
`head=${releaseBranch}`,
|
|
322
|
+
"-f",
|
|
323
|
+
`commit_message=Merge ${releaseTag}`,
|
|
324
|
+
],
|
|
325
|
+
githubEnvironment,
|
|
326
|
+
);
|
|
327
|
+
if (!merged) {
|
|
328
|
+
ensurePullRequest();
|
|
329
|
+
run(root, "gh", ["pr", "merge", releaseBranch, "--admin", "--merge"], githubEnvironment);
|
|
330
|
+
}
|
|
331
|
+
git(releaseRoot, ["push", "--no-verify", "origin", "--delete", releaseBranch]);
|
|
332
|
+
} else {
|
|
333
|
+
ensurePullRequest();
|
|
279
334
|
}
|
|
280
|
-
|
|
335
|
+
git(root, ["worktree", "remove", "--force", releaseRoot]);
|
|
336
|
+
git(root, ["branch", "--delete", "--force", releaseBranch]);
|
|
281
337
|
logger.success(`${opts.approve ? "merged" : "opened"} ${releaseBranch} for ${releaseTag}`);
|
|
282
338
|
},
|
|
283
339
|
);
|