@dbx-tools/projen 0.9.19 → 0.9.21
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/._LICENSE +0 -0
- package/._README.md +0 -0
- package/._index.ts +0 -0
- package/._package.json +0 -0
- package/README.md +67 -83
- package/index.ts +2 -7
- package/package.json +9 -5
- package/src/.__lazy-require.ts +0 -0
- package/src/.__release-platform.ts +0 -0
- package/src/.__rust-project.ts +0 -0
- package/src/.__rust-release-workflow.ts +0 -0
- package/src/.__task-command.ts +0 -0
- package/src/._barrels.ts +0 -0
- package/src/._bun-app.ts +0 -0
- package/src/._bun-workflow.ts +0 -0
- package/src/._clean.ts +0 -0
- package/src/._codegen.ts +0 -0
- package/src/._dbx-tools-config.ts +0 -0
- package/src/._engine-root.ts +0 -0
- package/src/._generated.ts +0 -0
- package/src/._mixin.ts +0 -0
- package/src/._module-exports.ts +0 -0
- package/src/._openapi.ts +0 -0
- package/src/._packages.ts +0 -0
- package/src/._pnpm-workspace.ts +0 -0
- package/src/._project-js.ts +0 -0
- package/src/._project-predicate.ts +0 -0
- package/src/._project-py.ts +0 -0
- package/src/._project-rs.ts +0 -0
- package/src/._project.ts +0 -0
- package/src/._projen-version.ts +0 -0
- package/src/._publish.ts +0 -0
- package/src/._release-catalog.ts +0 -0
- package/src/._release-dispatch.ts +0 -0
- package/src/._release-github.ts +0 -0
- package/src/._release.ts +0 -0
- package/src/._scaffold.ts +0 -0
- package/src/._tags.ts +0 -0
- package/src/._tsconfig.ts +0 -0
- package/src/._uniffi.ts +0 -0
- package/src/._vscode.ts +0 -0
- package/src/._watch.ts +0 -0
- package/src/._workspace-lock.ts +0 -0
- package/src/._workspace-version.ts +0 -0
- package/src/_rust-release-workflow.ts +4 -391
- package/src/_task-command.ts +75 -0
- package/src/project-js.ts +2 -42
- package/src/project-py.ts +5 -96
- package/src/project-rs.ts +3 -76
- package/src/release-catalog.ts +37 -203
- package/src/release.ts +12 -634
- package/src/scaffold.ts +2 -6
- package/src/workspace-version.ts +9 -121
- package/tasks/._barrels.ts +0 -0
- package/tasks/._bump.ts +0 -0
- package/tasks/._clean.ts +0 -0
- package/tasks/._compile-workspace.ts +0 -0
- package/tasks/._local-publish.ts +0 -0
- package/tasks/._openapi.ts +0 -0
- package/tasks/._projenrc.ts +0 -0
- package/tasks/._publish-npm.ts +0 -0
- package/tasks/._publish-python.ts +0 -0
- package/tasks/._publish-uniffi-local.ts +0 -0
- package/tasks/._publish.ts +0 -0
- package/tasks/._python-registry.ts +0 -0
- package/tasks/._release-assets.ts +0 -0
- package/tasks/._release-candidate.ts +0 -0
- package/tasks/._release-manifest.ts +0 -0
- package/tasks/._release-pr.ts +0 -0
- package/tasks/._release-summary.ts +0 -0
- package/tasks/._release-version.ts +0 -0
- package/tasks/._rust.ts +0 -0
- package/tasks/._sync.ts +0 -0
- package/tasks/._uniffi-python.d.ts +0 -0
- package/tasks/._uniffi-python.js +0 -0
- package/tasks/._uniffi-release.mjs +0 -0
- package/tasks/._uniffi.ts +0 -0
- package/tasks/._version-check.ts +0 -0
- package/tasks/bump.ts +17 -34
- package/tasks/local-publish.ts +124 -117
- package/tasks/publish-npm.ts +62 -30
- package/tasks/publish-python.ts +38 -140
- package/tasks/publish-uniffi-local.ts +129 -84
- package/tasks/publish.ts +25 -139
- package/tasks/python-registry.ts +2 -9
- package/tasks/release-assets.ts +56 -65
- package/tasks/release-candidate.ts +123 -90
- package/tasks/release-pr.ts +266 -240
- package/tasks/release-summary.ts +8 -10
- package/tasks/release-version.ts +23 -2
- package/tasks/rust.ts +8 -13
- package/tasks/uniffi-python.d.ts +2 -3
- package/tasks/uniffi-python.js +7 -20
- package/tasks/uniffi-release.mjs +19 -13
- package/tasks/uniffi.ts +15 -24
- package/tasks/version-check.ts +24 -56
- package/src/release-plan.ts +0 -269
- package/src/release-please.ts +0 -242
- package/tasks/release-bootstrap.ts +0 -59
- package/tasks/release-plan.ts +0 -89
- package/tasks/release-please.ts +0 -162
- package/tasks/release-request.ts +0 -166
- package/tasks/release-summary-units.ts +0 -75
- package/tasks/stamp-python.ts +0 -13
package/tasks/release-assets.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
} from "node:fs";
|
|
15
15
|
import { platform } from "node:os";
|
|
16
16
|
import { basename, delimiter, dirname, join, relative, resolve } from "node:path";
|
|
17
|
-
import * as exec from "@dbx-tools/core/exec";
|
|
18
17
|
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
19
18
|
import { json, log } from "@dbx-tools/shared-core";
|
|
20
19
|
import { Command } from "commander";
|
|
@@ -23,6 +22,11 @@ import type {
|
|
|
23
22
|
RustReleaseConfiguration,
|
|
24
23
|
RustReleaseTargetPlan,
|
|
25
24
|
} from "../src/_rust-release-workflow.ts";
|
|
25
|
+
import {
|
|
26
|
+
captureTaskCommand,
|
|
27
|
+
runLoggedTaskCommand,
|
|
28
|
+
taskCommandSucceeds,
|
|
29
|
+
} from "../src/_task-command.ts";
|
|
26
30
|
|
|
27
31
|
const logger = log.logger("projen:release-assets");
|
|
28
32
|
const emoji = /[\p{Extended_Pictographic}\uFE0F\u200D]/gu;
|
|
@@ -39,55 +43,8 @@ interface ReleaseAsset {
|
|
|
39
43
|
readonly size: number;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
command: string,
|
|
45
|
-
args: readonly string[],
|
|
46
|
-
env: NodeJS.ProcessEnv = process.env,
|
|
47
|
-
): void {
|
|
48
|
-
const result = exec.spawnSync(command, [...args], {
|
|
49
|
-
cwd: root,
|
|
50
|
-
env,
|
|
51
|
-
stdin: "ignore",
|
|
52
|
-
stdout: "capture",
|
|
53
|
-
stderr: "capture",
|
|
54
|
-
check: false,
|
|
55
|
-
});
|
|
56
|
-
for (const line of `${result.stdout}\n${result.stderr}`.split(/\r?\n/)) {
|
|
57
|
-
if (line) logger.info(line.replace(emoji, ""));
|
|
58
|
-
}
|
|
59
|
-
if (result.exitCode !== 0) {
|
|
60
|
-
throw new Error(`${command} exited with ${result.exitCode}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function capture(
|
|
65
|
-
root: string,
|
|
66
|
-
command: string,
|
|
67
|
-
args: readonly string[],
|
|
68
|
-
env: NodeJS.ProcessEnv,
|
|
69
|
-
): string {
|
|
70
|
-
return exec.spawnSync(command, [...args], {
|
|
71
|
-
cwd: root,
|
|
72
|
-
env,
|
|
73
|
-
stdin: "ignore",
|
|
74
|
-
stdout: "capture",
|
|
75
|
-
stderr: "inherit",
|
|
76
|
-
check: true,
|
|
77
|
-
}).stdout;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function commandSucceeds(root: string, command: string, args: readonly string[]): boolean {
|
|
81
|
-
return (
|
|
82
|
-
exec.spawnSync(command, [...args], {
|
|
83
|
-
cwd: root,
|
|
84
|
-
env: { ...process.env, PATH: releasePath() },
|
|
85
|
-
stdin: "ignore",
|
|
86
|
-
stdout: "ignore",
|
|
87
|
-
stderr: "ignore",
|
|
88
|
-
check: false,
|
|
89
|
-
}).exitCode === 0
|
|
90
|
-
);
|
|
46
|
+
function logReleaseOutput(line: string): void {
|
|
47
|
+
logger.info(line.replace(emoji, ""));
|
|
91
48
|
}
|
|
92
49
|
|
|
93
50
|
function releasePath(): string {
|
|
@@ -110,7 +67,12 @@ export function missingLocalReleaseTools(root: string): string[] {
|
|
|
110
67
|
["zip", "zip", ["-v"]],
|
|
111
68
|
];
|
|
112
69
|
return checks
|
|
113
|
-
.filter(
|
|
70
|
+
.filter(
|
|
71
|
+
([, command, args]) =>
|
|
72
|
+
!taskCommandSucceeds(root, command, args, {
|
|
73
|
+
env: { ...process.env, PATH: releasePath() },
|
|
74
|
+
}),
|
|
75
|
+
)
|
|
114
76
|
.map(([name]) => name);
|
|
115
77
|
}
|
|
116
78
|
|
|
@@ -205,7 +167,11 @@ function targetBuildEnvironment(
|
|
|
205
167
|
|
|
206
168
|
function cargoTargetRoot(root: string, env: NodeJS.ProcessEnv): string {
|
|
207
169
|
const metadata = json.parseRecord(
|
|
208
|
-
|
|
170
|
+
captureTaskCommand(root, "cargo", ["metadata", "--format-version", "1", "--no-deps"], {
|
|
171
|
+
env,
|
|
172
|
+
check: true,
|
|
173
|
+
stderr: "inherit",
|
|
174
|
+
}),
|
|
209
175
|
);
|
|
210
176
|
if (typeof metadata?.target_directory !== "string") {
|
|
211
177
|
throw new Error("Cargo metadata returned no target directory");
|
|
@@ -214,7 +180,13 @@ function cargoTargetRoot(root: string, env: NodeJS.ProcessEnv): string {
|
|
|
214
180
|
}
|
|
215
181
|
|
|
216
182
|
function rustHostTarget(root: string, env: NodeJS.ProcessEnv): string {
|
|
217
|
-
const host = /^host:\s+(.+)$/m.exec(
|
|
183
|
+
const host = /^host:\s+(.+)$/m.exec(
|
|
184
|
+
captureTaskCommand(root, "rustc", ["-vV"], {
|
|
185
|
+
env,
|
|
186
|
+
check: true,
|
|
187
|
+
stderr: "inherit",
|
|
188
|
+
}),
|
|
189
|
+
)?.[1];
|
|
218
190
|
if (!host) throw new Error("rustc did not report a host target");
|
|
219
191
|
return host;
|
|
220
192
|
}
|
|
@@ -226,7 +198,7 @@ function buildBindingGenerators(
|
|
|
226
198
|
env: NodeJS.ProcessEnv,
|
|
227
199
|
): void {
|
|
228
200
|
if (configuration.bindings.length === 0) return;
|
|
229
|
-
|
|
201
|
+
runLoggedTaskCommand(
|
|
230
202
|
root,
|
|
231
203
|
"cargo",
|
|
232
204
|
[
|
|
@@ -241,7 +213,7 @@ function buildBindingGenerators(
|
|
|
241
213
|
"--target",
|
|
242
214
|
hostTarget,
|
|
243
215
|
],
|
|
244
|
-
env,
|
|
216
|
+
{ env, onLine: logReleaseOutput },
|
|
245
217
|
);
|
|
246
218
|
}
|
|
247
219
|
|
|
@@ -255,7 +227,7 @@ function generatePythonBindings(
|
|
|
255
227
|
for (const binding of configuration.bindings) {
|
|
256
228
|
if (!binding.python) continue;
|
|
257
229
|
const output = join(root, "dist/local-bindings", binding.crate);
|
|
258
|
-
|
|
230
|
+
runLoggedTaskCommand(
|
|
259
231
|
root,
|
|
260
232
|
"node",
|
|
261
233
|
[
|
|
@@ -270,7 +242,7 @@ function generatePythonBindings(
|
|
|
270
242
|
"--output",
|
|
271
243
|
output,
|
|
272
244
|
],
|
|
273
|
-
env,
|
|
245
|
+
{ env, onLine: logReleaseOutput },
|
|
274
246
|
);
|
|
275
247
|
generated.set(binding.crate, join(output, `${binding.crate.replaceAll("-", "_")}.py`));
|
|
276
248
|
}
|
|
@@ -286,7 +258,7 @@ function packageBindings(
|
|
|
286
258
|
env: NodeJS.ProcessEnv,
|
|
287
259
|
): void {
|
|
288
260
|
for (const binding of configuration.bindings) {
|
|
289
|
-
|
|
261
|
+
runLoggedTaskCommand(
|
|
290
262
|
root,
|
|
291
263
|
"node",
|
|
292
264
|
[
|
|
@@ -325,7 +297,7 @@ function packageBindings(
|
|
|
325
297
|
? ["--python-bindings", pythonBindings.get(binding.crate)!]
|
|
326
298
|
: []),
|
|
327
299
|
],
|
|
328
|
-
env,
|
|
300
|
+
{ env, onLine: logReleaseOutput },
|
|
329
301
|
);
|
|
330
302
|
}
|
|
331
303
|
}
|
|
@@ -352,9 +324,15 @@ function packageBinaries(
|
|
|
352
324
|
const output = join(root, "dist/release", binary.crate, target.node, "binary");
|
|
353
325
|
const asset = join(output, releaseBinaryAssetName(binary.binary, target.node, target.os));
|
|
354
326
|
if (target.os === "win32") {
|
|
355
|
-
|
|
327
|
+
runLoggedTaskCommand(root, "zip", ["-j", asset, source], {
|
|
328
|
+
env,
|
|
329
|
+
onLine: logReleaseOutput,
|
|
330
|
+
});
|
|
356
331
|
} else {
|
|
357
|
-
|
|
332
|
+
runLoggedTaskCommand(root, "tar", ["-C", dirname(source), "-czf", asset, basename(source)], {
|
|
333
|
+
env,
|
|
334
|
+
onLine: logReleaseOutput,
|
|
335
|
+
});
|
|
358
336
|
}
|
|
359
337
|
}
|
|
360
338
|
}
|
|
@@ -418,8 +396,15 @@ function uploadAssets(
|
|
|
418
396
|
assets: readonly ReleaseAsset[],
|
|
419
397
|
manifest: string,
|
|
420
398
|
): void {
|
|
421
|
-
|
|
422
|
-
|
|
399
|
+
runLoggedTaskCommand(
|
|
400
|
+
root,
|
|
401
|
+
"gh",
|
|
402
|
+
["release", "upload", tag, ...assets.map((asset) => asset.path), "--clobber"],
|
|
403
|
+
{ onLine: logReleaseOutput },
|
|
404
|
+
);
|
|
405
|
+
runLoggedTaskCommand(root, "gh", ["release", "upload", tag, manifest, "--clobber"], {
|
|
406
|
+
onLine: logReleaseOutput,
|
|
407
|
+
});
|
|
423
408
|
}
|
|
424
409
|
|
|
425
410
|
/** Build all configured targets sequentially and optionally upload them. */
|
|
@@ -443,11 +428,17 @@ export function buildReleaseAssets(options: {
|
|
|
443
428
|
buildBindingGenerators(root, configuration, hostTarget, env);
|
|
444
429
|
const pythonBindings = generatePythonBindings(root, configuration, hostTarget, env);
|
|
445
430
|
for (const target of configuration.targets) {
|
|
446
|
-
|
|
431
|
+
runLoggedTaskCommand(root, "rustup", ["target", "add", target.cargo], {
|
|
432
|
+
env,
|
|
433
|
+
onLine: logReleaseOutput,
|
|
434
|
+
});
|
|
447
435
|
const invocation = localTargetCommand(configuration, target);
|
|
448
436
|
const buildEnv = targetBuildEnvironment(root, target, env);
|
|
449
437
|
logger.info("building Rust release target", { target: target.cargo });
|
|
450
|
-
|
|
438
|
+
runLoggedTaskCommand(root, invocation.command, invocation.args, {
|
|
439
|
+
env: buildEnv,
|
|
440
|
+
onLine: logReleaseOutput,
|
|
441
|
+
});
|
|
451
442
|
packageBindings(root, configuration, target, options.version, pythonBindings, env);
|
|
452
443
|
packageBinaries(root, configuration, target, targetRoot, env);
|
|
453
444
|
}
|
|
@@ -4,51 +4,23 @@
|
|
|
4
4
|
import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, join, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import * as exec from "@dbx-tools/core/exec";
|
|
8
7
|
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
9
8
|
import { json, log } from "@dbx-tools/shared-core";
|
|
10
9
|
import { Command } from "commander";
|
|
11
|
-
import type { RustReleaseConfiguration } from "../src/_rust-release-workflow.ts";
|
|
12
|
-
import { buildPythonProjects } from "./publish-python.ts";
|
|
13
10
|
import { readNpmArchiveIdentity } from "./publish-npm.ts";
|
|
11
|
+
import { buildPythonProjects } from "./publish-python.ts";
|
|
14
12
|
import { buildReleaseAssets } from "./release-assets.ts";
|
|
15
13
|
import {
|
|
16
14
|
type ReleaseArtifactInput,
|
|
17
15
|
type ReleaseArtifactRole,
|
|
16
|
+
verifyReleaseManifest,
|
|
18
17
|
writeReleaseManifest,
|
|
19
18
|
} from "./release-manifest.ts";
|
|
19
|
+
import type { RustReleaseConfiguration } from "../src/_rust-release-workflow.ts";
|
|
20
|
+
import { captureTaskCommand, runTaskCommand, taskCommandSucceeds } from "../src/_task-command.ts";
|
|
20
21
|
|
|
21
22
|
const logger = log.logger("projen:release-candidate");
|
|
22
23
|
|
|
23
|
-
function run(root: string, command: string, args: readonly string[], env = process.env): void {
|
|
24
|
-
exec.spawnSync(command, [...args], {
|
|
25
|
-
cwd: root,
|
|
26
|
-
env,
|
|
27
|
-
stdin: "ignore",
|
|
28
|
-
stdout: "inherit",
|
|
29
|
-
stderr: "inherit",
|
|
30
|
-
check: true,
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function capture(
|
|
35
|
-
root: string,
|
|
36
|
-
command: string,
|
|
37
|
-
args: readonly string[],
|
|
38
|
-
env = process.env,
|
|
39
|
-
): string {
|
|
40
|
-
return exec
|
|
41
|
-
.spawnSync(command, [...args], {
|
|
42
|
-
cwd: root,
|
|
43
|
-
env,
|
|
44
|
-
stdin: "ignore",
|
|
45
|
-
stdout: "capture",
|
|
46
|
-
stderr: "inherit",
|
|
47
|
-
check: true,
|
|
48
|
-
})
|
|
49
|
-
.stdout.trim();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
24
|
function files(directory: string): string[] {
|
|
53
25
|
if (!existsSync(directory)) return [];
|
|
54
26
|
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
@@ -74,12 +46,24 @@ function role(path: string): ReleaseArtifactRole {
|
|
|
74
46
|
return "native";
|
|
75
47
|
}
|
|
76
48
|
|
|
77
|
-
function releaseArtifacts(root: string, version: string): ReleaseArtifactInput[] {
|
|
49
|
+
export function releaseArtifacts(root: string, version: string): ReleaseArtifactInput[] {
|
|
78
50
|
const releaseRoot = join(root, "dist/release");
|
|
79
51
|
return files(releaseRoot)
|
|
80
52
|
.filter((path) => !path.includes(`${join(releaseRoot, "upload")}/`))
|
|
81
53
|
.flatMap((path): ReleaseArtifactInput[] => {
|
|
82
54
|
const normalized = path.split("\\").join("/");
|
|
55
|
+
if (
|
|
56
|
+
![
|
|
57
|
+
"/binary/",
|
|
58
|
+
"/npm/",
|
|
59
|
+
"/npm-facade/",
|
|
60
|
+
"/npm-workspace/",
|
|
61
|
+
"/python/",
|
|
62
|
+
"/python-workspace/",
|
|
63
|
+
].some((directory) => normalized.includes(directory))
|
|
64
|
+
) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
83
67
|
if (path.endsWith(".tgz")) {
|
|
84
68
|
const identity = readNpmArchiveIdentity(path);
|
|
85
69
|
if (identity.version !== version) {
|
|
@@ -123,17 +107,17 @@ function pruneDraftAssets(
|
|
|
123
107
|
desired: ReadonlySet<string>,
|
|
124
108
|
env: NodeJS.ProcessEnv,
|
|
125
109
|
): void {
|
|
126
|
-
const current =
|
|
110
|
+
const current = captureTaskCommand(
|
|
127
111
|
root,
|
|
128
112
|
"gh",
|
|
129
113
|
["release", "view", tag, "--json", "assets", "--jq", ".assets[].name"],
|
|
130
|
-
env,
|
|
114
|
+
{ env, check: true, stderr: "inherit" },
|
|
131
115
|
)
|
|
132
116
|
.split(/\r?\n/)
|
|
133
117
|
.filter(Boolean);
|
|
134
118
|
for (const name of current) {
|
|
135
119
|
if (!desired.has(name)) {
|
|
136
|
-
|
|
120
|
+
runTaskCommand(root, "gh", ["release", "delete-asset", tag, name, "--yes"], { env });
|
|
137
121
|
}
|
|
138
122
|
}
|
|
139
123
|
}
|
|
@@ -150,7 +134,7 @@ function buildFacades(root: string, version: string): void {
|
|
|
150
134
|
if (!configuration) return;
|
|
151
135
|
for (const binding of configuration.bindings) {
|
|
152
136
|
if (!binding.node || !binding.nodePackage) continue;
|
|
153
|
-
|
|
137
|
+
runTaskCommand(root, "node", [
|
|
154
138
|
".projen/uniffi-release.mjs",
|
|
155
139
|
"facade",
|
|
156
140
|
"--node",
|
|
@@ -174,96 +158,105 @@ function ensureDraftRelease(options: {
|
|
|
174
158
|
readonly notesFile?: string;
|
|
175
159
|
readonly env: NodeJS.ProcessEnv;
|
|
176
160
|
}): void {
|
|
177
|
-
const existingTag =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (existingTag.exitCode === 0) {
|
|
186
|
-
if (existingTag.stdout.trim() !== options.sha) {
|
|
161
|
+
const existingTag = captureTaskCommand(
|
|
162
|
+
options.root,
|
|
163
|
+
"git",
|
|
164
|
+
["rev-parse", "--verify", `${options.tag}^{commit}`],
|
|
165
|
+
{ env: options.env },
|
|
166
|
+
);
|
|
167
|
+
if (existingTag) {
|
|
168
|
+
if (existingTag !== options.sha) {
|
|
187
169
|
throw new Error(`Release tag ${options.tag} does not point to ${options.sha}`);
|
|
188
170
|
}
|
|
189
|
-
if (
|
|
171
|
+
if (
|
|
172
|
+
captureTaskCommand(options.root, "git", ["cat-file", "-t", options.tag], {
|
|
173
|
+
env: options.env,
|
|
174
|
+
check: true,
|
|
175
|
+
stderr: "inherit",
|
|
176
|
+
}) !== "tag"
|
|
177
|
+
) {
|
|
190
178
|
throw new Error(`Release tag ${options.tag} must be annotated`);
|
|
191
179
|
}
|
|
192
180
|
} else {
|
|
193
|
-
|
|
181
|
+
runTaskCommand(
|
|
194
182
|
options.root,
|
|
195
183
|
"git",
|
|
196
184
|
["tag", "-a", options.tag, options.sha, "-m", options.tag],
|
|
197
|
-
options.env,
|
|
185
|
+
{ env: options.env },
|
|
198
186
|
);
|
|
199
187
|
}
|
|
200
|
-
const remoteTag =
|
|
188
|
+
const remoteTag = taskCommandSucceeds(
|
|
189
|
+
options.root,
|
|
201
190
|
"git",
|
|
202
191
|
["ls-remote", "--exit-code", "--tags", "origin", `refs/tags/${options.tag}`],
|
|
203
|
-
{
|
|
204
|
-
cwd: options.root,
|
|
205
|
-
env: options.env,
|
|
206
|
-
stdin: "ignore",
|
|
207
|
-
stdout: "ignore",
|
|
208
|
-
stderr: "ignore",
|
|
209
|
-
check: false,
|
|
210
|
-
},
|
|
192
|
+
{ env: options.env },
|
|
211
193
|
);
|
|
212
|
-
if (remoteTag
|
|
213
|
-
|
|
194
|
+
if (remoteTag) {
|
|
195
|
+
runTaskCommand(
|
|
214
196
|
options.root,
|
|
215
197
|
"git",
|
|
216
198
|
["fetch", "--force", "origin", `+refs/tags/${options.tag}:refs/tags/${options.tag}`],
|
|
217
|
-
options.env,
|
|
199
|
+
{ env: options.env },
|
|
218
200
|
);
|
|
219
201
|
if (
|
|
220
|
-
|
|
221
|
-
|
|
202
|
+
captureTaskCommand(options.root, "git", ["rev-parse", `${options.tag}^{commit}`], {
|
|
203
|
+
env: options.env,
|
|
204
|
+
check: true,
|
|
205
|
+
stderr: "inherit",
|
|
206
|
+
}) !== options.sha
|
|
222
207
|
) {
|
|
223
208
|
throw new Error(`Remote release tag ${options.tag} does not point to ${options.sha}`);
|
|
224
209
|
}
|
|
225
|
-
if (
|
|
210
|
+
if (
|
|
211
|
+
captureTaskCommand(options.root, "git", ["cat-file", "-t", options.tag], {
|
|
212
|
+
env: options.env,
|
|
213
|
+
check: true,
|
|
214
|
+
stderr: "inherit",
|
|
215
|
+
}) !== "tag"
|
|
216
|
+
) {
|
|
226
217
|
throw new Error(`Remote release tag ${options.tag} must be annotated`);
|
|
227
218
|
}
|
|
228
219
|
} else {
|
|
229
|
-
|
|
220
|
+
runTaskCommand(options.root, "git", ["push", "origin", `refs/tags/${options.tag}`], {
|
|
221
|
+
env: options.env,
|
|
222
|
+
});
|
|
230
223
|
}
|
|
231
224
|
|
|
232
|
-
const release =
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const state = JSON.parse(release.stdout) as { isDraft?: boolean };
|
|
242
|
-
if (state.isDraft !== true)
|
|
225
|
+
const release = captureTaskCommand(
|
|
226
|
+
options.root,
|
|
227
|
+
"gh",
|
|
228
|
+
["release", "view", options.tag, "--json", "isDraft"],
|
|
229
|
+
{ env: options.env },
|
|
230
|
+
);
|
|
231
|
+
if (release) {
|
|
232
|
+
const state = JSON.parse(release) as { isDraft?: boolean };
|
|
233
|
+
if (state.isDraft !== true) {
|
|
243
234
|
throw new Error(`GitHub Release ${options.tag} is already published`);
|
|
235
|
+
}
|
|
244
236
|
if (options.notesFile) {
|
|
245
|
-
|
|
237
|
+
runTaskCommand(
|
|
246
238
|
options.root,
|
|
247
239
|
"gh",
|
|
248
240
|
["release", "edit", options.tag, "--title", options.tag, "--notes-file", options.notesFile],
|
|
249
|
-
options.env,
|
|
241
|
+
{ env: options.env },
|
|
250
242
|
);
|
|
251
243
|
}
|
|
252
244
|
return;
|
|
253
245
|
}
|
|
254
246
|
const notes =
|
|
255
247
|
options.notesFile && existsSync(options.notesFile) ? ["--notes-file", options.notesFile] : [];
|
|
256
|
-
|
|
248
|
+
runTaskCommand(
|
|
257
249
|
options.root,
|
|
258
250
|
"gh",
|
|
259
251
|
["release", "create", options.tag, "--draft", "--verify-tag", "--title", options.tag, ...notes],
|
|
260
|
-
options.env,
|
|
252
|
+
{ env: options.env },
|
|
261
253
|
);
|
|
262
254
|
}
|
|
263
255
|
|
|
264
256
|
/** Build the exact release candidate and optionally attach it to a draft release. */
|
|
265
257
|
export function buildReleaseCandidate(options: {
|
|
266
258
|
readonly root: string;
|
|
259
|
+
readonly pythonRoot: string;
|
|
267
260
|
readonly sha: string;
|
|
268
261
|
readonly tag: string;
|
|
269
262
|
readonly version: string;
|
|
@@ -273,13 +266,24 @@ export function buildReleaseCandidate(options: {
|
|
|
273
266
|
}): void {
|
|
274
267
|
const root = resolve(options.root);
|
|
275
268
|
const env = options.env ?? process.env;
|
|
276
|
-
if (
|
|
269
|
+
if (
|
|
270
|
+
captureTaskCommand(root, "git", ["rev-parse", "HEAD"], {
|
|
271
|
+
env,
|
|
272
|
+
check: true,
|
|
273
|
+
stderr: "inherit",
|
|
274
|
+
}) !== options.sha
|
|
275
|
+
) {
|
|
277
276
|
throw new Error(`Release candidate must be built from ${options.sha}`);
|
|
278
277
|
}
|
|
279
278
|
if (readFileSync(join(root, "VERSION"), "utf8").trim() !== options.version) {
|
|
280
279
|
throw new Error(`VERSION does not match ${options.version}`);
|
|
281
280
|
}
|
|
282
|
-
const status =
|
|
281
|
+
const status = captureTaskCommand(
|
|
282
|
+
root,
|
|
283
|
+
"git",
|
|
284
|
+
["status", "--porcelain=v1", "--untracked-files=all"],
|
|
285
|
+
{ env, check: true, stderr: "inherit" },
|
|
286
|
+
);
|
|
283
287
|
if (status) throw new Error("Release candidate source contains tracked changes");
|
|
284
288
|
|
|
285
289
|
rmSync(join(root, "dist/release"), { recursive: true, force: true });
|
|
@@ -288,13 +292,13 @@ export function buildReleaseCandidate(options: {
|
|
|
288
292
|
buildFacades(root, options.version);
|
|
289
293
|
}
|
|
290
294
|
const publishScript = fileURLToPath(new URL("./publish.ts", import.meta.url));
|
|
291
|
-
|
|
295
|
+
runTaskCommand(root, process.execPath, [
|
|
292
296
|
publishScript,
|
|
293
297
|
options.version,
|
|
294
298
|
"--output",
|
|
295
299
|
"dist/release/npm-workspace",
|
|
296
300
|
]);
|
|
297
|
-
const pythonRoot =
|
|
301
|
+
const pythonRoot = resolve(root, options.pythonRoot);
|
|
298
302
|
if (existsSync(pythonRoot)) {
|
|
299
303
|
buildPythonProjects({
|
|
300
304
|
allowEmpty: true,
|
|
@@ -318,6 +322,27 @@ export function buildReleaseCandidate(options: {
|
|
|
318
322
|
tag: options.tag,
|
|
319
323
|
});
|
|
320
324
|
if (!options.upload) return;
|
|
325
|
+
uploadReleaseCandidate(options);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Attach an already-built and verified candidate to its draft GitHub Release. */
|
|
329
|
+
export function uploadReleaseCandidate(options: {
|
|
330
|
+
readonly root: string;
|
|
331
|
+
readonly sha: string;
|
|
332
|
+
readonly tag: string;
|
|
333
|
+
readonly version: string;
|
|
334
|
+
readonly notesFile?: string;
|
|
335
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
336
|
+
}): void {
|
|
337
|
+
const root = resolve(options.root);
|
|
338
|
+
const env = options.env ?? process.env;
|
|
339
|
+
const uploadDirectory = join(root, "dist/release/upload");
|
|
340
|
+
verifyReleaseManifest({
|
|
341
|
+
directory: uploadDirectory,
|
|
342
|
+
gitSha: options.sha,
|
|
343
|
+
tag: options.tag,
|
|
344
|
+
version: options.version,
|
|
345
|
+
});
|
|
321
346
|
const notesFile = join(root, "dist/release/release-notes.md");
|
|
322
347
|
const summary =
|
|
323
348
|
options.notesFile && existsSync(resolve(root, options.notesFile))
|
|
@@ -348,7 +373,9 @@ export function buildReleaseCandidate(options: {
|
|
|
348
373
|
return priority(left) - priority(right) || left.localeCompare(right);
|
|
349
374
|
});
|
|
350
375
|
pruneDraftAssets(root, options.tag, new Set(uploadFiles.map((path) => basename(path))), env);
|
|
351
|
-
|
|
376
|
+
runTaskCommand(root, "gh", ["release", "upload", options.tag, ...uploadFiles, "--clobber"], {
|
|
377
|
+
env,
|
|
378
|
+
});
|
|
352
379
|
logger.success(`uploaded draft release candidate ${options.tag}`);
|
|
353
380
|
}
|
|
354
381
|
|
|
@@ -358,25 +385,31 @@ if (import.meta.main) {
|
|
|
358
385
|
.requiredOption("--tag <tag>", "annotated release tag")
|
|
359
386
|
.requiredOption("--sha <sha>", "exact release commit")
|
|
360
387
|
.option("--root <path>", "repository root")
|
|
388
|
+
.option("--python-root <path>", "Python package root", "packages/py")
|
|
361
389
|
.option("--notes-file <path>", "draft release notes file")
|
|
362
|
-
.option("--upload", "create or update the draft GitHub Release")
|
|
390
|
+
.option("--upload", "create or update the draft GitHub Release after building")
|
|
391
|
+
.option("--upload-existing", "upload the existing verified candidate without rebuilding")
|
|
363
392
|
.action(
|
|
364
393
|
(options: {
|
|
365
394
|
version: string;
|
|
366
395
|
tag: string;
|
|
367
396
|
sha: string;
|
|
368
397
|
root?: string;
|
|
398
|
+
pythonRoot: string;
|
|
369
399
|
notesFile?: string;
|
|
370
400
|
upload?: boolean;
|
|
401
|
+
uploadExisting?: boolean;
|
|
371
402
|
}) => {
|
|
372
|
-
|
|
403
|
+
const candidateOptions = {
|
|
373
404
|
root: options.root ?? projectUtils.root() ?? process.cwd(),
|
|
405
|
+
pythonRoot: options.pythonRoot,
|
|
374
406
|
sha: options.sha,
|
|
375
407
|
tag: options.tag,
|
|
376
408
|
version: options.version,
|
|
377
409
|
...(options.notesFile ? { notesFile: options.notesFile } : {}),
|
|
378
|
-
|
|
379
|
-
|
|
410
|
+
};
|
|
411
|
+
if (options.uploadExisting) uploadReleaseCandidate(candidateOptions);
|
|
412
|
+
else buildReleaseCandidate({ ...candidateOptions, upload: options.upload });
|
|
380
413
|
},
|
|
381
414
|
)
|
|
382
415
|
.parse();
|