@dbx-tools/projen 0.9.17 → 0.9.19
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 +39 -36
- package/index.ts +2 -2
- package/package.json +4 -5
- package/src/_release-platform.ts +10 -0
- package/src/_rust-project.ts +3 -0
- package/src/_rust-release-workflow.ts +117 -528
- package/src/project-js.ts +8 -13
- package/src/project-py.ts +62 -54
- package/src/project-rs.ts +7 -37
- package/src/release-github.ts +23 -0
- package/src/release-plan.ts +32 -40
- package/src/release.ts +91 -135
- package/tasks/compile-workspace.ts +121 -0
- package/tasks/publish-npm.ts +63 -11
- package/tasks/publish-python.ts +101 -26
- package/tasks/publish.ts +22 -3
- package/tasks/release-assets.ts +476 -0
- package/tasks/release-candidate.ts +383 -0
- package/tasks/release-manifest.ts +273 -0
- package/tasks/release-plan.ts +1 -2
- package/tasks/release-pr.ts +86 -60
- package/tasks/uniffi-python.js +24 -12
- package/tasks/uniffi-release.mjs +35 -2
- package/src/_rust-release.ts +0 -633
- package/tasks/build-rust-release.ts +0 -68
- package/tasks/rust-release.d.ts +0 -45
- package/tasks/rust-release.mjs +0 -1726
package/tasks/release-pr.ts
CHANGED
|
@@ -15,6 +15,7 @@ import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
|
15
15
|
import { asyncUtils, json, log, object } from "@dbx-tools/shared-core";
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
import { publishLocalRelease } from "./local-publish.ts";
|
|
18
|
+
import { hasLocalReleaseTargets, missingLocalReleaseTools } from "./release-assets.ts";
|
|
18
19
|
import { generateReleaseSummary } from "./release-summary.ts";
|
|
19
20
|
import {
|
|
20
21
|
releaseArchitectureOption,
|
|
@@ -38,7 +39,9 @@ import {
|
|
|
38
39
|
githubAccountSupportsWorkflowChanges,
|
|
39
40
|
githubAuthenticatedAccounts,
|
|
40
41
|
githubRepositoryApiPath,
|
|
42
|
+
githubRepositoryCanManage,
|
|
41
43
|
githubRepositoryIdentity,
|
|
44
|
+
githubRepositoryPermissions,
|
|
42
45
|
githubRepositorySpecifier,
|
|
43
46
|
githubTokenArguments,
|
|
44
47
|
githubTokenEnvironmentName,
|
|
@@ -77,6 +80,34 @@ async function waitForPullRequestMerge(
|
|
|
77
80
|
env: NodeJS.ProcessEnv,
|
|
78
81
|
timeoutMs: number,
|
|
79
82
|
): Promise<string> {
|
|
83
|
+
const checksDeadline = Date.now() + timeoutMs;
|
|
84
|
+
let checksReported = false;
|
|
85
|
+
while (Date.now() < checksDeadline) {
|
|
86
|
+
const count = Number(
|
|
87
|
+
captureTaskCommand(
|
|
88
|
+
root,
|
|
89
|
+
"gh",
|
|
90
|
+
[
|
|
91
|
+
"pr",
|
|
92
|
+
"view",
|
|
93
|
+
releaseBranch,
|
|
94
|
+
"--json",
|
|
95
|
+
"statusCheckRollup",
|
|
96
|
+
"--jq",
|
|
97
|
+
".statusCheckRollup | length",
|
|
98
|
+
],
|
|
99
|
+
{ env },
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
if (Number.isInteger(count) && count > 0) {
|
|
103
|
+
checksReported = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
await asyncUtils.sleep(2_000);
|
|
107
|
+
}
|
|
108
|
+
if (!checksReported) {
|
|
109
|
+
throw new Error(`Release pull request reported no checks within ${timeoutMs}ms`);
|
|
110
|
+
}
|
|
80
111
|
await runTaskCommandAsync(
|
|
81
112
|
root,
|
|
82
113
|
"gh",
|
|
@@ -107,56 +138,13 @@ async function waitForPullRequestMerge(
|
|
|
107
138
|
throw new Error(`Release pull request did not merge within ${timeoutMs}ms`);
|
|
108
139
|
}
|
|
109
140
|
|
|
110
|
-
/** Find and watch the release workflow for an exact merged commit. */
|
|
111
|
-
async function waitForReleaseWorkflow(
|
|
112
|
-
root: string,
|
|
113
|
-
baseBranch: string,
|
|
114
|
-
mergeSha: string,
|
|
115
|
-
env: NodeJS.ProcessEnv,
|
|
116
|
-
timeoutMs: number,
|
|
117
|
-
): Promise<void> {
|
|
118
|
-
const deadline = Date.now() + timeoutMs;
|
|
119
|
-
let runId = "";
|
|
120
|
-
while (Date.now() < deadline) {
|
|
121
|
-
runId = captureTaskCommand(
|
|
122
|
-
root,
|
|
123
|
-
"gh",
|
|
124
|
-
[
|
|
125
|
-
"run",
|
|
126
|
-
"list",
|
|
127
|
-
"--workflow",
|
|
128
|
-
"release.yml",
|
|
129
|
-
"--branch",
|
|
130
|
-
baseBranch,
|
|
131
|
-
"--event",
|
|
132
|
-
"push",
|
|
133
|
-
"--commit",
|
|
134
|
-
mergeSha,
|
|
135
|
-
"--limit",
|
|
136
|
-
"1",
|
|
137
|
-
"--json",
|
|
138
|
-
"databaseId",
|
|
139
|
-
"--jq",
|
|
140
|
-
".[0].databaseId // empty",
|
|
141
|
-
],
|
|
142
|
-
{ env },
|
|
143
|
-
);
|
|
144
|
-
if (runId) break;
|
|
145
|
-
await asyncUtils.sleep(5_000);
|
|
146
|
-
}
|
|
147
|
-
if (!runId) throw new Error(`Release workflow did not start within ${timeoutMs}ms`);
|
|
148
|
-
await runTaskCommandAsync(root, "gh", ["run", "watch", runId, "--exit-status"], {
|
|
149
|
-
env,
|
|
150
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
141
|
function githubAccount(root: string): {
|
|
155
142
|
hostname: string;
|
|
156
143
|
owner: string;
|
|
157
144
|
repository: string;
|
|
158
145
|
login: string;
|
|
159
146
|
token: string;
|
|
147
|
+
canManage: boolean;
|
|
160
148
|
} {
|
|
161
149
|
const repository = projectUtils.repositoryUrl(root);
|
|
162
150
|
if (!repository) throw new Error("Release preparation requires a GitHub repository");
|
|
@@ -199,21 +187,20 @@ function githubAccount(root: string): {
|
|
|
199
187
|
});
|
|
200
188
|
const repositoryData =
|
|
201
189
|
repositoryResult.exitCode === 0 ? json.parseRecord(repositoryResult.stdout ?? "") : undefined;
|
|
202
|
-
const permissions =
|
|
203
|
-
|
|
204
|
-
: undefined;
|
|
205
|
-
if (
|
|
206
|
-
permissions?.push !== true &&
|
|
207
|
-
permissions?.maintain !== true &&
|
|
208
|
-
permissions?.admin !== true
|
|
209
|
-
) {
|
|
190
|
+
const permissions = githubRepositoryPermissions(repositoryData);
|
|
191
|
+
if (!permissions.push && !permissions.maintain && !permissions.admin) {
|
|
210
192
|
continue;
|
|
211
193
|
}
|
|
212
194
|
logger.info("selected GitHub CLI account", {
|
|
213
195
|
login: account.login,
|
|
214
196
|
repository: `${identity.owner}/${identity.repository}`,
|
|
215
197
|
});
|
|
216
|
-
return {
|
|
198
|
+
return {
|
|
199
|
+
...identity,
|
|
200
|
+
login: account.login,
|
|
201
|
+
token,
|
|
202
|
+
canManage: githubRepositoryCanManage(permissions),
|
|
203
|
+
};
|
|
217
204
|
}
|
|
218
205
|
const detected =
|
|
219
206
|
accounts
|
|
@@ -275,7 +262,7 @@ program
|
|
|
275
262
|
.option("--no-wait", "return after enabling automatic merge without watching publication")
|
|
276
263
|
.option(
|
|
277
264
|
"--wait-timeout-minutes <minutes>",
|
|
278
|
-
"maximum time for pull request checks
|
|
265
|
+
"maximum time for pull request checks and merge",
|
|
279
266
|
"120",
|
|
280
267
|
)
|
|
281
268
|
.action(
|
|
@@ -313,9 +300,21 @@ program
|
|
|
313
300
|
throw new Error("--wait-timeout-minutes must be a positive integer");
|
|
314
301
|
}
|
|
315
302
|
const waitTimeoutMs = waitTimeoutMinutes * 60_000;
|
|
303
|
+
if (opts.approve && !opts.wait) {
|
|
304
|
+
throw new Error("automatic merge requires waiting so the draft candidate can be built");
|
|
305
|
+
}
|
|
316
306
|
const currentBranch = git(root, ["branch", "--show-current"], { capture: true });
|
|
317
307
|
if (!currentBranch) throw new Error("Release preparation requires a local branch");
|
|
318
308
|
const account = githubAccount(root);
|
|
309
|
+
if (!account.canManage) {
|
|
310
|
+
throw new Error("Release preparation requires maintain or admin permission");
|
|
311
|
+
}
|
|
312
|
+
if (hasLocalReleaseTargets(root)) {
|
|
313
|
+
const localTools = missingLocalReleaseTools(root);
|
|
314
|
+
if (localTools.length > 0) {
|
|
315
|
+
throw new Error(`Local release candidate build requires: ${localTools.join(", ")}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
319
318
|
git(root, ["fetch", "--tags", "origin", opts.base]);
|
|
320
319
|
const comparisonBase = resolveBaseVersion(root, [opts.prefix], {
|
|
321
320
|
fetch: false,
|
|
@@ -470,7 +469,7 @@ program
|
|
|
470
469
|
"",
|
|
471
470
|
`Source commit: ${git(releaseRoot, ["rev-parse", `${releaseBranch}^`], { capture: true })}`,
|
|
472
471
|
"",
|
|
473
|
-
"Merging this PR updates VERSION on main and
|
|
472
|
+
"Merging this PR updates VERSION on main and prepares a draft GitHub Release.",
|
|
474
473
|
...(releaseSummary ? ["", releaseSummary] : []),
|
|
475
474
|
].join("\n");
|
|
476
475
|
const githubEnvironment = {
|
|
@@ -511,8 +510,6 @@ program
|
|
|
511
510
|
env: githubEnvironment,
|
|
512
511
|
});
|
|
513
512
|
}
|
|
514
|
-
git(root, ["worktree", "remove", "--force", releaseRoot]);
|
|
515
|
-
git(root, ["branch", "--delete", "--force", releaseBranch]);
|
|
516
513
|
if (opts.approve && opts.wait) {
|
|
517
514
|
const mergeSha = await waitForPullRequestMerge(
|
|
518
515
|
root,
|
|
@@ -521,10 +518,39 @@ program
|
|
|
521
518
|
waitTimeoutMs,
|
|
522
519
|
);
|
|
523
520
|
logger.info("release pull request merged", { releaseBranch, mergeSha });
|
|
524
|
-
|
|
525
|
-
|
|
521
|
+
git(root, ["fetch", "origin", opts.base]);
|
|
522
|
+
git(releaseRoot, ["switch", "--detach", mergeSha]);
|
|
523
|
+
runTaskCommand(releaseRoot, process.execPath, ["install"]);
|
|
524
|
+
const releaseCandidateScript = join(
|
|
525
|
+
releaseRoot,
|
|
526
|
+
"node_modules/@dbx-tools/projen/tasks/release-candidate.ts",
|
|
527
|
+
);
|
|
528
|
+
runTaskCommand(
|
|
529
|
+
releaseRoot,
|
|
530
|
+
process.execPath,
|
|
531
|
+
[
|
|
532
|
+
releaseCandidateScript,
|
|
533
|
+
"--root",
|
|
534
|
+
releaseRoot,
|
|
535
|
+
"--version",
|
|
536
|
+
next.version,
|
|
537
|
+
"--tag",
|
|
538
|
+
releaseTag,
|
|
539
|
+
"--sha",
|
|
540
|
+
mergeSha,
|
|
541
|
+
"--notes-file",
|
|
542
|
+
`docs/releases/v${next.version}.md`,
|
|
543
|
+
"--upload",
|
|
544
|
+
],
|
|
545
|
+
{ env: githubEnvironment },
|
|
546
|
+
);
|
|
547
|
+
git(root, ["worktree", "remove", "--force", releaseRoot]);
|
|
548
|
+
git(root, ["branch", "--delete", "--force", releaseBranch]);
|
|
549
|
+
logger.success(`prepared draft ${releaseTag} from ${mergeSha}`);
|
|
526
550
|
return;
|
|
527
551
|
}
|
|
552
|
+
git(root, ["worktree", "remove", "--force", releaseRoot]);
|
|
553
|
+
git(root, ["branch", "--delete", "--force", releaseBranch]);
|
|
528
554
|
logger.success(
|
|
529
555
|
`${
|
|
530
556
|
opts.approve ? "enabled automatic merge for" : "opened"
|
|
@@ -534,4 +560,4 @@ program
|
|
|
534
560
|
},
|
|
535
561
|
);
|
|
536
562
|
|
|
537
|
-
await program.parseAsync();
|
|
563
|
+
if (import.meta.main) await program.parseAsync();
|
package/tasks/uniffi-python.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
mkdirSync,
|
|
6
6
|
mkdtempSync,
|
|
7
7
|
readFileSync,
|
|
8
|
+
readdirSync,
|
|
8
9
|
rmSync,
|
|
9
10
|
writeFileSync,
|
|
10
11
|
} from "node:fs";
|
|
@@ -130,6 +131,7 @@ export const installPythonBindings = ({
|
|
|
130
131
|
module,
|
|
131
132
|
packageRoot,
|
|
132
133
|
platform,
|
|
134
|
+
generatedSource,
|
|
133
135
|
readonly = false,
|
|
134
136
|
run,
|
|
135
137
|
targetDirectory,
|
|
@@ -137,20 +139,25 @@ export const installPythonBindings = ({
|
|
|
137
139
|
const packageName = module.split(".").at(-1);
|
|
138
140
|
if (!packageName) throw new Error(`Invalid Python module ${module}`);
|
|
139
141
|
const packageDirectory = resolve(packageRoot, "src", ...module.split("."));
|
|
140
|
-
const generatedDirectory =
|
|
142
|
+
const generatedDirectory = generatedSource
|
|
143
|
+
? undefined
|
|
144
|
+
: mkdtempSync(join(tmpdir(), `${packageName}-python-`));
|
|
141
145
|
const generator = resolve(targetDirectory, pythonBindingGeneratorName(crate, platform));
|
|
142
|
-
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
143
146
|
|
|
144
147
|
try {
|
|
145
|
-
|
|
146
|
-
generator
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
if (!generatedSource) {
|
|
149
|
+
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
150
|
+
run(
|
|
151
|
+
generator,
|
|
152
|
+
pythonBindingGeneratorArgs({
|
|
153
|
+
crate,
|
|
154
|
+
library,
|
|
155
|
+
output: generatedDirectory,
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
const generated =
|
|
160
|
+
generatedSource ?? join(generatedDirectory, `${crate.replaceAll("-", "_")}.py`);
|
|
154
161
|
if (!existsSync(generated)) throw new Error(`Missing generated binding ${generated}`);
|
|
155
162
|
mkdirSync(packageDirectory, { recursive: true });
|
|
156
163
|
|
|
@@ -162,6 +169,11 @@ export const installPythonBindings = ({
|
|
|
162
169
|
setWritable(init);
|
|
163
170
|
writeFileSync(init, `${generatedHeader(crate)}from .bindings import *\n`);
|
|
164
171
|
|
|
172
|
+
for (const candidate of readdirSync(packageDirectory)) {
|
|
173
|
+
if (/\.(?:dll|dylib|so)$/.test(candidate)) {
|
|
174
|
+
rmSync(join(packageDirectory, candidate), { force: true });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
165
177
|
const nativeLibrary = join(packageDirectory, basename(library));
|
|
166
178
|
setWritable(nativeLibrary);
|
|
167
179
|
cpSync(library, nativeLibrary);
|
|
@@ -174,6 +186,6 @@ export const installPythonBindings = ({
|
|
|
174
186
|
rmSync(join(packageDirectory, "_generated"), { recursive: true, force: true });
|
|
175
187
|
return { bindings, init, library: nativeLibrary };
|
|
176
188
|
} finally {
|
|
177
|
-
rmSync(generatedDirectory, { recursive: true, force: true });
|
|
189
|
+
if (generatedDirectory) rmSync(generatedDirectory, { recursive: true, force: true });
|
|
178
190
|
}
|
|
179
191
|
};
|
package/tasks/uniffi-release.mjs
CHANGED
|
@@ -17,7 +17,12 @@ import { fileURLToPath } from "node:url";
|
|
|
17
17
|
import { createRequire } from "node:module";
|
|
18
18
|
import { parseArgs } from "node:util";
|
|
19
19
|
import { spawnSync } from "node:child_process";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
installPythonBindings,
|
|
22
|
+
pythonBindingGeneratorArgs,
|
|
23
|
+
pythonBindingGeneratorName,
|
|
24
|
+
stampPythonProject,
|
|
25
|
+
} from "./uniffi-python.js";
|
|
21
26
|
|
|
22
27
|
const require = createRequire(import.meta.url);
|
|
23
28
|
const bundledToml = fileURLToPath(new URL("./smol-toml.cjs", import.meta.url));
|
|
@@ -34,7 +39,9 @@ const parsed = parseArgs({
|
|
|
34
39
|
"native-package": { type: "string" },
|
|
35
40
|
"python-package": { type: "string" },
|
|
36
41
|
"python-module": { type: "string" },
|
|
42
|
+
"python-bindings": { type: "string" },
|
|
37
43
|
"cargo-target": { type: "string" },
|
|
44
|
+
"generator-target": { type: "string" },
|
|
38
45
|
"node-triple": { type: "string" },
|
|
39
46
|
"python-tag": { type: "string" },
|
|
40
47
|
os: { type: "string" },
|
|
@@ -414,8 +421,10 @@ const packagePython = ({
|
|
|
414
421
|
pythonTag,
|
|
415
422
|
version,
|
|
416
423
|
cargoTarget,
|
|
424
|
+
generatorTarget,
|
|
417
425
|
os,
|
|
418
426
|
pythonModule,
|
|
427
|
+
pythonBindings,
|
|
419
428
|
}) => {
|
|
420
429
|
const pythonRoot = resolve(output, "python-root");
|
|
421
430
|
cpSync(resolve(root, pythonDirectory), pythonRoot, { recursive: true });
|
|
@@ -442,8 +451,9 @@ const packagePython = ({
|
|
|
442
451
|
module: pythonModule,
|
|
443
452
|
packageRoot: pythonRoot,
|
|
444
453
|
platform: os,
|
|
454
|
+
generatedSource: pythonBindings,
|
|
445
455
|
run,
|
|
446
|
-
targetDirectory: resolve(cargoTargetRoot, cargoTarget, "release"),
|
|
456
|
+
targetDirectory: resolve(cargoTargetRoot, generatorTarget ?? cargoTarget, "release"),
|
|
447
457
|
});
|
|
448
458
|
|
|
449
459
|
const wheelDirectory = resolve(output, "python");
|
|
@@ -463,6 +473,25 @@ const packagePython = ({
|
|
|
463
473
|
]);
|
|
464
474
|
};
|
|
465
475
|
|
|
476
|
+
const generatePython = () => {
|
|
477
|
+
const crate = required("crate");
|
|
478
|
+
const cargoTarget = required("cargo-target");
|
|
479
|
+
const os = required("os");
|
|
480
|
+
const output = resolve(root, required("output"));
|
|
481
|
+
const generator = resolve(
|
|
482
|
+
cargoTargetRoot,
|
|
483
|
+
cargoTarget,
|
|
484
|
+
"release",
|
|
485
|
+
pythonBindingGeneratorName(crate, os),
|
|
486
|
+
);
|
|
487
|
+
const library = libraryPath(crate, cargoTarget, os);
|
|
488
|
+
if (!existsSync(generator)) throw new Error(`Missing UniFFI generator ${generator}`);
|
|
489
|
+
if (!existsSync(library)) throw new Error(`Missing native library ${library}`);
|
|
490
|
+
rmSync(output, { recursive: true, force: true });
|
|
491
|
+
mkdirSync(output, { recursive: true });
|
|
492
|
+
run(generator, pythonBindingGeneratorArgs({ crate, library, output }));
|
|
493
|
+
};
|
|
494
|
+
|
|
466
495
|
const build = () => {
|
|
467
496
|
const crate = required("crate");
|
|
468
497
|
const cargoTarget = required("cargo-target");
|
|
@@ -517,14 +546,18 @@ const build = () => {
|
|
|
517
546
|
pythonTag,
|
|
518
547
|
version,
|
|
519
548
|
cargoTarget,
|
|
549
|
+
generatorTarget: parsed.values["generator-target"],
|
|
520
550
|
os,
|
|
521
551
|
pythonModule: required("python-module"),
|
|
552
|
+
pythonBindings: parsed.values["python-bindings"],
|
|
522
553
|
});
|
|
523
554
|
}
|
|
524
555
|
};
|
|
525
556
|
|
|
526
557
|
if (parsed.positionals[0] === "build") {
|
|
527
558
|
build();
|
|
559
|
+
} else if (parsed.positionals[0] === "generate-python") {
|
|
560
|
+
generatePython();
|
|
528
561
|
} else if (parsed.positionals[0] === "facade") {
|
|
529
562
|
packageNodeFacade({
|
|
530
563
|
output: resolve(root, required("output")),
|