@dbx-tools/projen 0.6.223 → 0.6.225
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 +55 -8
- package/index.ts +7 -4
- package/package.json +5 -5
- package/src/barrels.ts +10 -4
- package/src/bun-workflow.ts +36 -17
- package/src/clean.ts +3 -3
- package/src/codegen.ts +16 -8
- package/src/packages.ts +14 -13
- package/src/project-js.ts +320 -148
- package/src/project-py.ts +8 -25
- package/src/project-rs.ts +100 -71
- package/src/release-dispatch.ts +10 -0
- package/src/release-github.ts +37 -0
- package/src/release.ts +57 -38
- package/src/tsconfig.ts +17 -17
- package/tasks/local-publish.ts +3 -3
- package/tasks/publish-npm.ts +75 -4
- package/tasks/publish-python.ts +13 -29
- package/tasks/publish-uniffi-local.ts +12 -9
- package/tasks/publish.ts +2 -2
- package/tasks/release-pr.ts +64 -10
- package/tasks/release-summary.ts +270 -0
- package/tasks/uniffi-python.d.ts +56 -0
- package/tasks/uniffi-python.js +160 -0
- package/tasks/uniffi-release.mjs +25 -49
- package/tasks/uniffi.ts +11 -45
package/src/project-py.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
import { project as coreProject } from "@dbx-tools/core";
|
|
3
3
|
import { string } from "@dbx-tools/shared-core";
|
|
4
4
|
import { Component, License, TextFile, type Project, javascript, python, vscode } from "projen";
|
|
5
|
-
import type { IResolver } from "projen/lib/file";
|
|
6
5
|
import { JobPermission } from "projen/lib/github/workflows-model";
|
|
7
|
-
import { parse, stringify } from "smol-toml";
|
|
8
6
|
import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
|
|
9
7
|
import { DBX_TOOLS_LICENSE, projectReleaseBranch, projectRepositoryUrl } from "./project-js.ts";
|
|
10
8
|
import { isDBXToolsJavaScriptProject } from "./project-predicate.ts";
|
|
@@ -14,7 +12,7 @@ import {
|
|
|
14
12
|
releaseArtifactSteps,
|
|
15
13
|
releasePublishCondition,
|
|
16
14
|
releaseStageCondition,
|
|
17
|
-
|
|
15
|
+
tryReleaseWorkflow,
|
|
18
16
|
} from "./release.ts";
|
|
19
17
|
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
20
18
|
|
|
@@ -111,24 +109,6 @@ const DEFAULT_DEV_DEPENDENCIES = [
|
|
|
111
109
|
|
|
112
110
|
const quote = (value: string): string => JSON.stringify(value);
|
|
113
111
|
|
|
114
|
-
interface TomlSynthesizer {
|
|
115
|
-
synthesizeContent(resolver: IResolver): string | undefined;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function formatPyproject(file: python.PyprojectTomlFile): void {
|
|
119
|
-
const target = file as unknown as TomlSynthesizer;
|
|
120
|
-
const synthesize = target.synthesizeContent.bind(file);
|
|
121
|
-
target.synthesizeContent = (resolver) => {
|
|
122
|
-
const content = synthesize(resolver);
|
|
123
|
-
if (!content) return content;
|
|
124
|
-
const marker = content.startsWith("# ") ? content.slice(0, content.indexOf("\n")) : undefined;
|
|
125
|
-
const body = stringify(parse(content))
|
|
126
|
-
.trimEnd()
|
|
127
|
-
.replace(/ = \[ (.*) \]$/gm, " = [$1]");
|
|
128
|
-
return `${marker ? `${marker}\n\n` : ""}${body}\n`;
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
112
|
/** Repository-relative path for a Python package directory. */
|
|
133
113
|
export function pythonPackagePath(repository: PythonRepositoryOptions, directory: string): string {
|
|
134
114
|
return `${repository.root ?? "packages/py"}/${directory}`;
|
|
@@ -222,7 +202,6 @@ export class DBXToolsPythonProject extends python.PythonProject implements DBXTo
|
|
|
222
202
|
if (pkg.uniffi !== undefined) {
|
|
223
203
|
this.uv.file.addOverride("tool.dbx_tools.config.uniffi", pkg.uniffi);
|
|
224
204
|
}
|
|
225
|
-
formatPyproject(this.uv.file);
|
|
226
205
|
this.uv.file.readonly = true;
|
|
227
206
|
|
|
228
207
|
for (const path of [".gitattributes", ".gitignore"]) {
|
|
@@ -397,7 +376,6 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
397
376
|
"tool.uv.sources",
|
|
398
377
|
Object.fromEntries(options.packages.map((pkg) => [pkg.name, { workspace: true }])),
|
|
399
378
|
);
|
|
400
|
-
formatPyproject(file);
|
|
401
379
|
file.readonly = true;
|
|
402
380
|
return file;
|
|
403
381
|
}
|
|
@@ -433,7 +411,10 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
433
411
|
const allPublications = [...publications, ...uniffiPublications];
|
|
434
412
|
if (allPublications.length === 0) return;
|
|
435
413
|
const usesRustArtifacts = uniffiPublications.length > 0;
|
|
436
|
-
const workflow =
|
|
414
|
+
const workflow = tryReleaseWorkflow(project);
|
|
415
|
+
if (!workflow) {
|
|
416
|
+
throw new Error("Python release requires the root dbx-tools release mode");
|
|
417
|
+
}
|
|
437
418
|
workflow.addJob("build-python", {
|
|
438
419
|
if: usesRustArtifacts
|
|
439
420
|
? "${{ always() && needs.verify-context.result == 'success' && needs.rust-build.result != 'failure' && needs.rust-build.result != 'cancelled' && (github.event_name == 'push' || inputs.stage == 'all' || inputs.stage == 'python') }}"
|
|
@@ -445,7 +426,9 @@ export class DBXToolsPythonWorkspace extends Component {
|
|
|
445
426
|
env: { BUN_VERSION },
|
|
446
427
|
steps: [
|
|
447
428
|
...releaseSourceSteps(),
|
|
448
|
-
...bunCacheRestoreSteps(project
|
|
429
|
+
...bunCacheRestoreSteps(project, {
|
|
430
|
+
ignorePaths: project.workflowCacheIgnorePaths,
|
|
431
|
+
}),
|
|
449
432
|
{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" },
|
|
450
433
|
{ name: "Install release helpers", run: "bun install" },
|
|
451
434
|
bunCacheSaveStep(),
|
package/src/project-rs.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** Filesystem-discovered Rust workspaces and UniFFI binding package wiring. */
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import { dirname, join, relative, resolve } from "node:path";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { exec, project as coreProject } from "@dbx-tools/core";
|
|
6
7
|
import { string } from "@dbx-tools/shared-core";
|
|
7
|
-
import { Component, Project, TextFile, javascript } from "projen";
|
|
8
|
+
import { Component, Project, TextFile, TomlFile, javascript } from "projen";
|
|
8
9
|
import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflows-model";
|
|
9
10
|
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
10
11
|
import {
|
|
@@ -18,6 +19,7 @@ import { pythonModuleName, type PythonPackageOptions } from "./project-py.ts";
|
|
|
18
19
|
import type { DBXToolsProject } from "./project.ts";
|
|
19
20
|
import {
|
|
20
21
|
RELEASE_SHA,
|
|
22
|
+
RELEASE_SUMMARY_FILE,
|
|
21
23
|
RELEASE_TAG,
|
|
22
24
|
RELEASE_VERSION,
|
|
23
25
|
releaseSourceSteps,
|
|
@@ -28,7 +30,7 @@ import {
|
|
|
28
30
|
nodeReleaseSetupSteps,
|
|
29
31
|
releaseArtifactSteps,
|
|
30
32
|
releaseStageCondition,
|
|
31
|
-
|
|
33
|
+
tryReleaseWorkflow,
|
|
32
34
|
} from "./release.ts";
|
|
33
35
|
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
34
36
|
|
|
@@ -180,6 +182,7 @@ export const UNIFFI_RELEASE_TARGETS: readonly UniFFIReleaseTarget[] = [
|
|
|
180
182
|
|
|
181
183
|
const UBRN_VERSION = "0.31.0-5";
|
|
182
184
|
const RELEASE_PLATFORMS_ENV = "DBX_TOOLS_RELEASE_PLATFORMS";
|
|
185
|
+
const require = createRequire(import.meta.url);
|
|
183
186
|
const RUST_CACHE_ENV = {
|
|
184
187
|
CARGO_INCREMENTAL: "0",
|
|
185
188
|
CARGO_TERM_COLOR: "always",
|
|
@@ -262,17 +265,25 @@ function rustCliRegistrySource(binaries: readonly RustReleaseBinaryMapping[]): s
|
|
|
262
265
|
].join("\n");
|
|
263
266
|
}
|
|
264
267
|
|
|
265
|
-
function
|
|
268
|
+
function taskSource(name: string): string {
|
|
266
269
|
const sourceDirectory = dirname(fileURLToPath(import.meta.url));
|
|
267
270
|
const candidates = [
|
|
268
|
-
resolve(sourceDirectory,
|
|
269
|
-
resolve(sourceDirectory,
|
|
271
|
+
resolve(sourceDirectory, `../tasks/${name}`),
|
|
272
|
+
resolve(sourceDirectory, `../../tasks/${name}`),
|
|
270
273
|
];
|
|
271
274
|
const source = candidates.find(existsSync);
|
|
272
|
-
if (!source) throw new Error(
|
|
275
|
+
if (!source) throw new Error(`Could not locate tasks/${name}`);
|
|
273
276
|
return readFileSync(source, "utf8");
|
|
274
277
|
}
|
|
275
278
|
|
|
279
|
+
function smolTomlSource(name: "dist/index.cjs" | "LICENSE"): string {
|
|
280
|
+
const entry = require.resolve("smol-toml");
|
|
281
|
+
return readFileSync(
|
|
282
|
+
resolve(dirname(entry), name === "LICENSE" ? "../LICENSE" : "index.cjs"),
|
|
283
|
+
"utf8",
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
276
287
|
/** Keep tracked workspace package versions in Cargo.lock aligned with VERSION. */
|
|
277
288
|
class RustWorkspaceVersionLock extends Component {
|
|
278
289
|
public override postSynthesize(): void {
|
|
@@ -390,26 +401,23 @@ function cargoDependency(
|
|
|
390
401
|
};
|
|
391
402
|
}
|
|
392
403
|
|
|
393
|
-
function
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (!entries || typeof entries !== "object" || Array.isArray(entries)) continue;
|
|
409
|
-
const rows = Object.entries(entries).map(([key, entry]) => `${key} = ${tomlValue(entry)}`);
|
|
410
|
-
blocks.push(`${section === "bin" ? "[[bin]]" : `[${section}]`}\n${rows.join("\n")}`);
|
|
404
|
+
function structuredTomlSections(value: Readonly<Record<string, unknown>>): Record<string, unknown> {
|
|
405
|
+
const result: Record<string, unknown> = {};
|
|
406
|
+
for (const [section, contents] of Object.entries(value)) {
|
|
407
|
+
const path = section.split(".");
|
|
408
|
+
let target = result;
|
|
409
|
+
for (const part of path.slice(0, -1)) {
|
|
410
|
+
const current = target[part];
|
|
411
|
+
if (current === undefined) {
|
|
412
|
+
target[part] = {};
|
|
413
|
+
} else if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
414
|
+
throw new Error(`Conflicting TOML section ${section}`);
|
|
415
|
+
}
|
|
416
|
+
target = target[part] as Record<string, unknown>;
|
|
417
|
+
}
|
|
418
|
+
target[path.at(-1)!] = contents;
|
|
411
419
|
}
|
|
412
|
-
return
|
|
420
|
+
return result;
|
|
413
421
|
}
|
|
414
422
|
|
|
415
423
|
/** One generated Cargo workspace member. */
|
|
@@ -455,9 +463,11 @@ export class DBXToolsRustProject extends Project implements DBXToolsProject {
|
|
|
455
463
|
: {}),
|
|
456
464
|
...(this.uniffi || binary
|
|
457
465
|
? {
|
|
458
|
-
bin:
|
|
459
|
-
|
|
460
|
-
|
|
466
|
+
bin: [
|
|
467
|
+
this.uniffi
|
|
468
|
+
? { name: `${crateName}-uniffi-bindgen`, path: "uniffi-bindgen.rs" }
|
|
469
|
+
: { name: binaryName, path: "src/main.rs" },
|
|
470
|
+
],
|
|
461
471
|
}
|
|
462
472
|
: {}),
|
|
463
473
|
...(options.features || options.defaultFeatures
|
|
@@ -489,20 +499,19 @@ export class DBXToolsRustProject extends Project implements DBXToolsProject {
|
|
|
489
499
|
}
|
|
490
500
|
: {}),
|
|
491
501
|
};
|
|
492
|
-
new
|
|
502
|
+
new TomlFile(this, "Cargo.toml", { marker: false, obj: manifest });
|
|
493
503
|
if (this.uniffi) {
|
|
494
504
|
new TextFile(this, "uniffi-bindgen.rs", {
|
|
495
505
|
lines: ["fn main() {", " uniffi::uniffi_bindgen_main();", "}", ""],
|
|
496
506
|
});
|
|
497
|
-
new
|
|
498
|
-
|
|
507
|
+
new TomlFile(this, "uniffi.toml", {
|
|
508
|
+
marker: false,
|
|
509
|
+
obj: structuredTomlSections(
|
|
499
510
|
options.uniffiConfig ?? {
|
|
500
511
|
"bindings.python": { cdylib_name: crateName.replaceAll("-", "_") },
|
|
501
512
|
"bindings.typescript": { strictTypeChecking: true },
|
|
502
513
|
},
|
|
503
|
-
)
|
|
504
|
-
.trimEnd()
|
|
505
|
-
.split("\n"),
|
|
514
|
+
),
|
|
506
515
|
});
|
|
507
516
|
}
|
|
508
517
|
}
|
|
@@ -833,8 +842,9 @@ function configureRustBindingFiles(
|
|
|
833
842
|
const dependencies = bindingDependencies(plan, pkg, "python");
|
|
834
843
|
if (dependencies.length === 0) continue;
|
|
835
844
|
pkg.tryRemoveFile("uniffi.toml");
|
|
836
|
-
new
|
|
837
|
-
|
|
845
|
+
new TomlFile(pkg, "uniffi.toml", {
|
|
846
|
+
marker: false,
|
|
847
|
+
obj: structuredTomlSections({
|
|
838
848
|
"bindings.python": { cdylib_name: pkg.crateName.replaceAll("-", "_") },
|
|
839
849
|
"bindings.typescript": { strictTypeChecking: true },
|
|
840
850
|
...pkg.packageOptions.uniffiConfig,
|
|
@@ -847,9 +857,7 @@ function configureRustBindingFiles(
|
|
|
847
857
|
)}.bindings`,
|
|
848
858
|
]),
|
|
849
859
|
),
|
|
850
|
-
})
|
|
851
|
-
.trimEnd()
|
|
852
|
-
.split("\n"),
|
|
860
|
+
}),
|
|
853
861
|
});
|
|
854
862
|
}
|
|
855
863
|
}
|
|
@@ -936,39 +944,41 @@ function configureRustWorkspaceFiles(
|
|
|
936
944
|
.filter((pkg) => !pkg.uniffi)
|
|
937
945
|
.map((pkg) => `${resolved.root}/${pkg.packageOptions.directory}`),
|
|
938
946
|
resolver: "2",
|
|
947
|
+
package: {
|
|
948
|
+
version: readWorkspaceVersion(project.outdir),
|
|
949
|
+
edition: options.edition ?? "2021",
|
|
950
|
+
"rust-version": options.rustVersion ?? "1.82",
|
|
951
|
+
license: options.license ?? DBX_TOOLS_LICENSE,
|
|
952
|
+
repository: resolved.repository,
|
|
953
|
+
},
|
|
954
|
+
...(options.workspaceDependencies
|
|
955
|
+
? {
|
|
956
|
+
dependencies: Object.fromEntries(
|
|
957
|
+
Object.entries(options.workspaceDependencies).map(([name, value]) => [
|
|
958
|
+
name,
|
|
959
|
+
cargoDependency(value),
|
|
960
|
+
]),
|
|
961
|
+
),
|
|
962
|
+
}
|
|
963
|
+
: {}),
|
|
939
964
|
},
|
|
940
|
-
"workspace.package": {
|
|
941
|
-
version: readWorkspaceVersion(project.outdir),
|
|
942
|
-
edition: options.edition ?? "2021",
|
|
943
|
-
"rust-version": options.rustVersion ?? "1.82",
|
|
944
|
-
license: options.license ?? DBX_TOOLS_LICENSE,
|
|
945
|
-
repository: resolved.repository,
|
|
946
|
-
},
|
|
947
|
-
...(options.workspaceDependencies
|
|
948
|
-
? {
|
|
949
|
-
"workspace.dependencies": Object.fromEntries(
|
|
950
|
-
Object.entries(options.workspaceDependencies).map(([name, value]) => [
|
|
951
|
-
name,
|
|
952
|
-
cargoDependency(value),
|
|
953
|
-
]),
|
|
954
|
-
),
|
|
955
|
-
}
|
|
956
|
-
: {}),
|
|
957
965
|
};
|
|
958
|
-
new
|
|
959
|
-
|
|
966
|
+
new TomlFile(project, "Cargo.toml", {
|
|
967
|
+
marker: false,
|
|
968
|
+
obj: manifest,
|
|
960
969
|
});
|
|
961
|
-
new
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
970
|
+
new TomlFile(project, ".cargo/config.toml", {
|
|
971
|
+
marker: false,
|
|
972
|
+
obj: {
|
|
973
|
+
target: {
|
|
974
|
+
"x86_64-pc-windows-msvc": {
|
|
975
|
+
rustflags: ["-C", "target-feature=+crt-static"],
|
|
976
|
+
},
|
|
977
|
+
"aarch64-pc-windows-msvc": {
|
|
978
|
+
rustflags: ["-C", "target-feature=+crt-static"],
|
|
979
|
+
},
|
|
968
980
|
},
|
|
969
|
-
}
|
|
970
|
-
.trimEnd()
|
|
971
|
-
.split("\n"),
|
|
981
|
+
},
|
|
972
982
|
});
|
|
973
983
|
new RustWorkspaceVersionLock(project);
|
|
974
984
|
}
|
|
@@ -1086,12 +1096,27 @@ function planRustRelease(
|
|
|
1086
1096
|
}
|
|
1087
1097
|
|
|
1088
1098
|
function configureRustReleaseTask(project: javascript.NodeProject, plan: RustReleasePlan): void {
|
|
1099
|
+
const supportFiles = [
|
|
1100
|
+
plan.releaseTask,
|
|
1101
|
+
".projen/uniffi-python.js",
|
|
1102
|
+
".projen/smol-toml.cjs",
|
|
1103
|
+
".projen/smol-toml.LICENSE",
|
|
1104
|
+
] as const;
|
|
1089
1105
|
if (plan.bindings.length) {
|
|
1090
1106
|
new TextFile(project, plan.releaseTask, {
|
|
1091
|
-
lines:
|
|
1107
|
+
lines: taskSource("uniffi-release.mjs").trimEnd().split("\n"),
|
|
1108
|
+
});
|
|
1109
|
+
new TextFile(project, supportFiles[1], {
|
|
1110
|
+
lines: taskSource("uniffi-python.js").trimEnd().split("\n"),
|
|
1111
|
+
});
|
|
1112
|
+
new TextFile(project, supportFiles[2], {
|
|
1113
|
+
lines: smolTomlSource("dist/index.cjs").trimEnd().split("\n"),
|
|
1114
|
+
});
|
|
1115
|
+
new TextFile(project, supportFiles[3], {
|
|
1116
|
+
lines: smolTomlSource("LICENSE").trimEnd().split("\n"),
|
|
1092
1117
|
});
|
|
1093
1118
|
} else {
|
|
1094
|
-
project.tryRemoveFile(
|
|
1119
|
+
for (const path of supportFiles) project.tryRemoveFile(path);
|
|
1095
1120
|
}
|
|
1096
1121
|
}
|
|
1097
1122
|
|
|
@@ -1335,6 +1360,7 @@ function rustGitHubReleaseJob(): Job {
|
|
|
1335
1360
|
uses: "softprops/action-gh-release@v2",
|
|
1336
1361
|
with: {
|
|
1337
1362
|
files: "dist/rust-release/*",
|
|
1363
|
+
body_path: RELEASE_SUMMARY_FILE,
|
|
1338
1364
|
"generate-release-notes": true,
|
|
1339
1365
|
tag_name: RELEASE_TAG,
|
|
1340
1366
|
target_commitish: RELEASE_SHA,
|
|
@@ -1476,8 +1502,11 @@ export class DBXToolsRustWorkspace {
|
|
|
1476
1502
|
): void {
|
|
1477
1503
|
if (!project.github || !isDBXToolsJavaScriptProject()(project)) return;
|
|
1478
1504
|
const plan = planRustRelease(project, options, targets, this.packages, this.bindingMappings);
|
|
1505
|
+
const workflow = tryReleaseWorkflow(project);
|
|
1506
|
+
if (!workflow) {
|
|
1507
|
+
throw new Error("Rust release requires the root dbx-tools release mode");
|
|
1508
|
+
}
|
|
1479
1509
|
configureRustReleaseTask(project, plan);
|
|
1480
|
-
const workflow = releaseWorkflow(project);
|
|
1481
1510
|
if (plan.hasTargetOutputs && plan.targets.length) {
|
|
1482
1511
|
workflow.addJob("rust-build", rustBuildJob(plan));
|
|
1483
1512
|
}
|
package/src/release-dispatch.ts
CHANGED
|
@@ -4,6 +4,16 @@ import type { JobStep } from "projen/lib/github/workflows-model";
|
|
|
4
4
|
export const RELEASE_TAG = "${{ needs.verify-context.outputs.release_tag }}";
|
|
5
5
|
export const RELEASE_SHA = "${{ needs.verify-context.outputs.expected_sha }}";
|
|
6
6
|
export const RELEASE_VERSION = "${{ needs.verify-context.outputs.release_version }}";
|
|
7
|
+
export const RELEASE_SUMMARY_DIRECTORY = "docs/releases";
|
|
8
|
+
export const RELEASE_SUMMARY_PROVIDER_NAMES = ["cursor", "codex", "claude"] as const;
|
|
9
|
+
export type ReleaseSummaryProviderName = (typeof RELEASE_SUMMARY_PROVIDER_NAMES)[number];
|
|
10
|
+
|
|
11
|
+
/** Immutable repository path for one version's release summary. */
|
|
12
|
+
export function releaseSummaryFile(version: string): string {
|
|
13
|
+
return `${RELEASE_SUMMARY_DIRECTORY}/v${version.replace(/^v/, "")}.md`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const RELEASE_SUMMARY_FILE = releaseSummaryFile(RELEASE_VERSION);
|
|
7
17
|
|
|
8
18
|
/** Check out and verify the immutable commit selected by the release tag. */
|
|
9
19
|
export function releaseSourceSteps(): readonly JobStep[] {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** GitHub repository identity used by release preparation. */
|
|
2
|
+
export interface GithubRepositoryIdentity {
|
|
3
|
+
hostname: string;
|
|
4
|
+
owner: string;
|
|
5
|
+
repository: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Parse a GitHub repository URL without treating its owner as the authenticated actor. */
|
|
9
|
+
export function githubRepositoryIdentity(repositoryUrl: string): GithubRepositoryIdentity {
|
|
10
|
+
const url = new URL(repositoryUrl);
|
|
11
|
+
const [owner, name] = url.pathname.split("/").filter(Boolean);
|
|
12
|
+
if (!owner || !name) {
|
|
13
|
+
throw new Error(`Cannot determine GitHub repository identity from ${repositoryUrl}`);
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
hostname: url.hostname,
|
|
17
|
+
owner,
|
|
18
|
+
repository: name.replace(/\.git$/, ""),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Ask GitHub CLI for the current actor's token on the repository host. */
|
|
23
|
+
export function githubTokenArguments(hostname: string): string[] {
|
|
24
|
+
return ["auth", "token", "--hostname", hostname];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** GitHub CLI token variable for a public, data-residency, or enterprise host. */
|
|
28
|
+
export function githubTokenEnvironmentName(hostname: string): "GH_TOKEN" | "GH_ENTERPRISE_TOKEN" {
|
|
29
|
+
return hostname === "github.com" || hostname.endsWith(".ghe.com")
|
|
30
|
+
? "GH_TOKEN"
|
|
31
|
+
: "GH_ENTERPRISE_TOKEN";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Host-qualified repository selector consumed by GitHub CLI. */
|
|
35
|
+
export function githubRepositorySpecifier(identity: GithubRepositoryIdentity): string {
|
|
36
|
+
return `${identity.hostname}/${identity.owner}/${identity.repository}`;
|
|
37
|
+
}
|
package/src/release.ts
CHANGED
|
@@ -5,7 +5,12 @@ import { JobPermission, type Job, type JobStep } from "projen/lib/github/workflo
|
|
|
5
5
|
import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
|
|
6
6
|
import { projectReleaseBranch, type DBXToolsJavaScriptProject } from "./project-js.ts";
|
|
7
7
|
import { applyTasks, taskScript } from "./project.ts";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
RELEASE_VERSION,
|
|
10
|
+
releaseSourceSteps,
|
|
11
|
+
type ReleaseSummaryProviderName,
|
|
12
|
+
} from "./release-dispatch.ts";
|
|
13
|
+
import { readWorkspaceVersion } from "./workspace-version.ts";
|
|
9
14
|
|
|
10
15
|
const NODE_VERSION = "lts/*";
|
|
11
16
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org";
|
|
@@ -20,6 +25,22 @@ export type ReleaseStage = "all" | "node" | "python" | "docs";
|
|
|
20
25
|
export interface ReleaseDocsOptions {
|
|
21
26
|
readonly siteUrl: string;
|
|
22
27
|
readonly base?: string;
|
|
28
|
+
/** Repository-defined setup and generation steps run before saving the Bun cache. */
|
|
29
|
+
readonly prepareSteps: readonly JobStep[];
|
|
30
|
+
/** Repository-defined documentation build and validation steps. */
|
|
31
|
+
readonly buildSteps: readonly JobStep[];
|
|
32
|
+
/** Directory uploaded as the GitHub Pages artifact. */
|
|
33
|
+
readonly artifactPath: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Optional local AI summary generation during reviewed release preparation. */
|
|
37
|
+
export interface ReleaseSummaryOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Provider fallback order.
|
|
40
|
+
*
|
|
41
|
+
* @default ["cursor", "codex", "claude"]
|
|
42
|
+
*/
|
|
43
|
+
readonly providers?: readonly ReleaseSummaryProviderName[];
|
|
23
44
|
}
|
|
24
45
|
|
|
25
46
|
/** Options for {@link DBXToolsRelease}. */
|
|
@@ -30,6 +51,13 @@ export interface DBXToolsReleaseOptions {
|
|
|
30
51
|
readonly nodeRelease?: boolean;
|
|
31
52
|
/** Build and deploy generated documentation through GitHub Pages. */
|
|
32
53
|
readonly docs?: ReleaseDocsOptions;
|
|
54
|
+
/** Python package root passed to local release preparation. */
|
|
55
|
+
readonly pythonRoot?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Generate a versioned release summary locally. Defaults to enabled with the
|
|
58
|
+
* standard Cursor, Codex, Claude fallback order.
|
|
59
|
+
*/
|
|
60
|
+
readonly summary?: boolean | ReleaseSummaryOptions;
|
|
33
61
|
}
|
|
34
62
|
|
|
35
63
|
/** Locate the unified workflow so attached language workspaces can add jobs. */
|
|
@@ -39,6 +67,11 @@ export function releaseWorkflow(project: DBXToolsJavaScriptProject): GithubWorkf
|
|
|
39
67
|
return workflow;
|
|
40
68
|
}
|
|
41
69
|
|
|
70
|
+
/** Locate the unified workflow when release generation is enabled. */
|
|
71
|
+
export function tryReleaseWorkflow(project: DBXToolsJavaScriptProject): GithubWorkflow | undefined {
|
|
72
|
+
return releaseWorkflows.get(project);
|
|
73
|
+
}
|
|
74
|
+
|
|
42
75
|
/** Whether the unified workflow publishes the normal npm workspace. */
|
|
43
76
|
export function hasNodeRelease(project: DBXToolsJavaScriptProject): boolean {
|
|
44
77
|
return nodeReleaseProjects.has(project);
|
|
@@ -98,7 +131,7 @@ export function releaseArtifactSteps(options: {
|
|
|
98
131
|
export function nodeReleaseSetupSteps(project: DBXToolsJavaScriptProject): readonly JobStep[] {
|
|
99
132
|
return [
|
|
100
133
|
...releaseSourceSteps(),
|
|
101
|
-
...bunCacheRestoreSteps(project),
|
|
134
|
+
...bunCacheRestoreSteps(project, { ignorePaths: project.workflowCacheIgnorePaths }),
|
|
102
135
|
{
|
|
103
136
|
name: "Setup Node.js",
|
|
104
137
|
uses: "actions/setup-node@v6",
|
|
@@ -270,44 +303,14 @@ function addDocsJobs(
|
|
|
270
303
|
},
|
|
271
304
|
steps: [
|
|
272
305
|
...releaseSourceSteps(),
|
|
273
|
-
...bunCacheRestoreSteps(project),
|
|
274
|
-
|
|
275
|
-
name: "Setup Node.js",
|
|
276
|
-
uses: "actions/setup-node@v6",
|
|
277
|
-
with: { "node-version": "22" },
|
|
278
|
-
},
|
|
279
|
-
{
|
|
280
|
-
name: "Setup Python",
|
|
281
|
-
uses: "actions/setup-python@v6",
|
|
282
|
-
with: { "python-version": "3.11" },
|
|
283
|
-
},
|
|
284
|
-
{ name: "Setup Rust", uses: "dtolnay/rust-toolchain@stable" },
|
|
285
|
-
{ name: "Configure Pages", uses: "actions/configure-pages@v5" },
|
|
286
|
-
{ name: "Install dependencies", run: "bun install" },
|
|
287
|
-
{
|
|
288
|
-
name: "Validate public source documentation",
|
|
289
|
-
run: "bun docs/scripts/check-source-docs.mjs",
|
|
290
|
-
},
|
|
291
|
-
{ name: "Generate docs from READMEs", run: "bun docs/scripts/sync-readmes.mjs" },
|
|
292
|
-
{ name: "Install docs dependencies", run: "bun install --cwd .docs-build/site" },
|
|
306
|
+
...bunCacheRestoreSteps(project, { ignorePaths: project.workflowCacheIgnorePaths }),
|
|
307
|
+
...options.prepareSteps,
|
|
293
308
|
bunCacheSaveStep(),
|
|
294
|
-
|
|
295
|
-
name: "Generate API docs",
|
|
296
|
-
run: "bun docs/scripts/generate-api-docs.mjs",
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
name: "Check generated titles",
|
|
300
|
-
run: "bun docs/scripts/check-generated-titles.mjs",
|
|
301
|
-
},
|
|
302
|
-
{ name: "Build docs", run: "bun run --cwd .docs-build/site build" },
|
|
303
|
-
{
|
|
304
|
-
name: "Check generated links",
|
|
305
|
-
run: "bun run --cwd .docs-build/site check-links",
|
|
306
|
-
},
|
|
309
|
+
...options.buildSteps,
|
|
307
310
|
{
|
|
308
311
|
name: "Upload Pages artifact",
|
|
309
312
|
uses: "actions/upload-pages-artifact@v4",
|
|
310
|
-
with: { path:
|
|
313
|
+
with: { path: options.artifactPath },
|
|
311
314
|
},
|
|
312
315
|
],
|
|
313
316
|
});
|
|
@@ -337,6 +340,13 @@ export class DBXToolsRelease extends Component {
|
|
|
337
340
|
super(project);
|
|
338
341
|
const tagPrefix = options.tagPrefix ?? "v";
|
|
339
342
|
const releaseBranch = projectReleaseBranch(project);
|
|
343
|
+
const summary = options.summary ?? true;
|
|
344
|
+
const summaryArgs =
|
|
345
|
+
summary === false || (typeof summary === "object" && summary.providers?.length === 0)
|
|
346
|
+
? ["--no-release-summary"]
|
|
347
|
+
: typeof summary === "object" && summary.providers
|
|
348
|
+
? [`--release-summary-providers ${summary.providers.join(",")}`]
|
|
349
|
+
: [];
|
|
340
350
|
releaseTagPrefixes.set(project, tagPrefix);
|
|
341
351
|
if (options.nodeRelease !== false) nodeReleaseProjects.add(project);
|
|
342
352
|
applyTasks(project, {
|
|
@@ -355,7 +365,14 @@ export class DBXToolsRelease extends Component {
|
|
|
355
365
|
exec: taskScript(
|
|
356
366
|
project,
|
|
357
367
|
"release-pr.ts",
|
|
358
|
-
|
|
368
|
+
[
|
|
369
|
+
`--prefix ${tagPrefix}`,
|
|
370
|
+
`--base ${releaseBranch}`,
|
|
371
|
+
...(options.pythonRoot
|
|
372
|
+
? [`--python-root ${JSON.stringify(options.pythonRoot)}`]
|
|
373
|
+
: []),
|
|
374
|
+
...summaryArgs,
|
|
375
|
+
].join(" "),
|
|
359
376
|
),
|
|
360
377
|
receiveArgs: true,
|
|
361
378
|
description: "Prepare, validate, locally publish, and open a reviewed release PR",
|
|
@@ -370,8 +387,10 @@ export class DBXToolsRelease extends Component {
|
|
|
370
387
|
limitConcurrency: true,
|
|
371
388
|
concurrencyOptions: { group: "release", cancelInProgress: false },
|
|
372
389
|
});
|
|
390
|
+
const version = readWorkspaceVersion(project.outdir);
|
|
373
391
|
workflow.runName =
|
|
374
|
-
|
|
392
|
+
`release ${version} ` +
|
|
393
|
+
"${{ github.event_name == 'push' && github.sha || inputs.release_tag }}";
|
|
375
394
|
workflow.on({
|
|
376
395
|
push: { branches: [releaseBranch], paths: ["VERSION"] },
|
|
377
396
|
workflowDispatch: {
|
package/src/tsconfig.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Root `tsconfig.base.json` and `tsconfig.json` for the projenrc program.
|
|
3
3
|
*/
|
|
4
|
-
import { Component,
|
|
4
|
+
import { Component, type Project, javascript, typescript } from "projen";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Compiler options for the ROOT program only (`.projenrc.ts` + the engine as
|
|
@@ -37,24 +37,24 @@ export class DBXToolsRootTsconfig extends Component {
|
|
|
37
37
|
constructor(scope: Project) {
|
|
38
38
|
super(scope);
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
obj: { compilerOptions: BASE_COMPILER_OPTIONS },
|
|
44
|
-
});
|
|
40
|
+
if (scope instanceof typescript.TypeScriptProject) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
45
43
|
|
|
46
|
-
new
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
exclude: ["node_modules", "**/dist", "**/node_modules"],
|
|
44
|
+
const base = new javascript.TypescriptConfig(scope, {
|
|
45
|
+
fileName: "tsconfig.base.json",
|
|
46
|
+
compilerOptions: BASE_COMPILER_OPTIONS,
|
|
47
|
+
});
|
|
48
|
+
base.file.readonly = true;
|
|
49
|
+
const root = new javascript.TypescriptConfig(scope, {
|
|
50
|
+
extends: javascript.TypescriptConfigExtends.fromTypescriptConfigs([base]),
|
|
51
|
+
compilerOptions: {
|
|
52
|
+
lib: ["ESNext"],
|
|
53
|
+
types: ["node"],
|
|
57
54
|
},
|
|
55
|
+
include: [".projenrc.ts"],
|
|
56
|
+
exclude: ["node_modules", "**/dist", "**/node_modules"],
|
|
58
57
|
});
|
|
58
|
+
root.file.readonly = true;
|
|
59
59
|
}
|
|
60
60
|
}
|
package/tasks/local-publish.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface LocalPublishOptions {
|
|
|
16
16
|
readonly version: string;
|
|
17
17
|
readonly localRegistry: string;
|
|
18
18
|
readonly localPypi: string;
|
|
19
|
-
readonly pythonRoot
|
|
19
|
+
readonly pythonRoot?: string;
|
|
20
20
|
readonly localCargo: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -50,7 +50,7 @@ export async function publishLocalRelease(options: LocalPublishOptions): Promise
|
|
|
50
50
|
const localRegistry = resolveLocalRegistry(options.localRegistry);
|
|
51
51
|
const activeIndexes = activePythonIndexes();
|
|
52
52
|
const localPypi = resolveLocalPypi(options.localPypi, activeIndexes);
|
|
53
|
-
const pythonRoot = resolve(options.root, options.pythonRoot);
|
|
53
|
+
const pythonRoot = options.pythonRoot ? resolve(options.root, options.pythonRoot) : undefined;
|
|
54
54
|
const localPublishes: Promise<unknown>[] = [];
|
|
55
55
|
|
|
56
56
|
if (
|
|
@@ -79,7 +79,7 @@ export async function publishLocalRelease(options: LocalPublishOptions): Promise
|
|
|
79
79
|
);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
if (localPypi && existsSync(pythonRoot)) {
|
|
82
|
+
if (localPypi && pythonRoot && existsSync(pythonRoot)) {
|
|
83
83
|
const publishPythonScript = fileURLToPath(new URL("./publish-python.ts", import.meta.url));
|
|
84
84
|
logger.info(`publishing Python ${options.version} to local devpi ${localPypi.publishUrl}`);
|
|
85
85
|
localPublishes.push(
|