@dbx-tools/projen 0.9.4 → 0.9.6
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 +64 -16
- package/index.ts +7 -5
- package/package.json +9 -6
- package/src/_rust-project.ts +356 -0
- package/src/_rust-release-workflow.ts +901 -0
- package/src/_rust-release.ts +605 -0
- package/src/_task-command.ts +89 -0
- package/src/barrels.ts +9 -7
- package/src/engine-root.ts +5 -5
- package/src/openapi.ts +1 -1
- package/src/packages.ts +9 -9
- package/src/pnpm-workspace.ts +1 -1
- package/src/project-js.ts +19 -25
- package/src/project-py.ts +6 -6
- package/src/project-rs.ts +100 -1098
- package/src/projen-version.ts +9 -0
- package/src/publish.ts +3 -4
- package/src/release-catalog.ts +1 -1
- package/src/release.ts +11 -12
- package/src/scaffold.ts +1 -1
- package/src/uniffi.ts +7 -0
- package/src/watch.ts +2 -2
- package/src/workspace-version.ts +20 -15
- package/tasks/barrels.ts +4 -4
- package/tasks/build-rust-release.ts +68 -0
- package/tasks/bump.ts +3 -2
- package/tasks/clean.ts +3 -3
- package/tasks/local-publish.ts +21 -3
- package/tasks/openapi.ts +2 -2
- package/tasks/publish-npm.ts +12 -6
- package/tasks/publish-python.ts +2 -2
- package/tasks/publish-uniffi-local.ts +11 -15
- package/tasks/publish.ts +88 -88
- package/tasks/python-registry.ts +1 -1
- package/tasks/release-bootstrap.ts +2 -2
- package/tasks/release-plan.ts +1 -1
- package/tasks/release-please.ts +1 -1
- package/tasks/release-pr.ts +60 -115
- package/tasks/release-request.ts +8 -29
- package/tasks/release-summary-units.ts +2 -9
- package/tasks/release-summary.ts +35 -24
- package/tasks/release-version.ts +44 -0
- package/tasks/rust-release.d.ts +41 -0
- package/tasks/rust-release.mjs +1717 -0
- package/tasks/uniffi-python.js +1 -1
- package/tasks/uniffi.ts +7 -1
- package/tasks/version-check.ts +6 -5
package/tasks/release-request.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { dirname, resolve } from "node:path";
|
|
6
|
-
import
|
|
7
|
-
import { log,
|
|
6
|
+
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
7
|
+
import { log, stringUtils } from "@dbx-tools/shared-core";
|
|
8
8
|
import { Command } from "commander";
|
|
9
9
|
import { selectReleaseSummary } from "./release-summary.ts";
|
|
10
|
+
import { captureTaskCommand, runTaskCommand } from "../src/_task-command.ts";
|
|
10
11
|
|
|
11
12
|
const logger = log.logger("projen:release-request");
|
|
12
13
|
|
|
@@ -33,13 +34,7 @@ export async function prepareReleaseRequest(
|
|
|
33
34
|
}
|
|
34
35
|
const baseRef = options.push === false ? options.baseBranch : `origin/${options.baseBranch}`;
|
|
35
36
|
if (options.synthesize !== false) {
|
|
36
|
-
|
|
37
|
-
cwd: root,
|
|
38
|
-
stdout: "inherit",
|
|
39
|
-
stderr: "inherit",
|
|
40
|
-
stdin: "ignore",
|
|
41
|
-
check: true,
|
|
42
|
-
});
|
|
37
|
+
runTaskCommand(root, process.execPath, ["x", "projen"]);
|
|
43
38
|
}
|
|
44
39
|
const dirty = capture(root, ["status", "--porcelain"]);
|
|
45
40
|
if (dirty) {
|
|
@@ -74,7 +69,7 @@ export async function prepareReleaseRequest(
|
|
|
74
69
|
throw new Error(`Release requests must run from a branch other than ${options.baseBranch}`);
|
|
75
70
|
}
|
|
76
71
|
|
|
77
|
-
const customNotes =
|
|
72
|
+
const customNotes = stringUtils.trimToNull(
|
|
78
73
|
options.notesFile ? readFileSync(resolve(root, options.notesFile), "utf8") : options.notes,
|
|
79
74
|
);
|
|
80
75
|
const notes =
|
|
@@ -130,27 +125,11 @@ async function generateRequestNotes(root: string, range: string): Promise<string
|
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
function capture(root: string, args: string[]): string {
|
|
133
|
-
return (
|
|
134
|
-
exec
|
|
135
|
-
.spawnSync("git", args, {
|
|
136
|
-
cwd: root,
|
|
137
|
-
stdout: "capture",
|
|
138
|
-
stderr: "inherit",
|
|
139
|
-
stdin: "ignore",
|
|
140
|
-
check: true,
|
|
141
|
-
})
|
|
142
|
-
.stdout?.trim() ?? ""
|
|
143
|
-
);
|
|
128
|
+
return captureTaskCommand(root, "git", args, { check: true, stderr: "inherit" });
|
|
144
129
|
}
|
|
145
130
|
|
|
146
131
|
function run(root: string, args: string[]): void {
|
|
147
|
-
|
|
148
|
-
cwd: root,
|
|
149
|
-
stdout: "inherit",
|
|
150
|
-
stderr: "inherit",
|
|
151
|
-
stdin: "ignore",
|
|
152
|
-
check: true,
|
|
153
|
-
});
|
|
132
|
+
runTaskCommand(root, "git", args);
|
|
154
133
|
}
|
|
155
134
|
|
|
156
135
|
/** Restore option text after Projen forwards task arguments without shell quoting. */
|
|
@@ -175,7 +154,7 @@ if (import.meta.main) {
|
|
|
175
154
|
throw new Error("Use --notes or --notes-file, not both");
|
|
176
155
|
}
|
|
177
156
|
await prepareReleaseRequest({
|
|
178
|
-
root:
|
|
157
|
+
root: projectUtils.root() ?? process.cwd(),
|
|
179
158
|
baseBranch: options.base,
|
|
180
159
|
message: optionText(options.message),
|
|
181
160
|
notes: optionText(options.notes),
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
|
|
4
4
|
import { existsSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
|
-
import { exec } from "@dbx-tools/core";
|
|
7
6
|
import { log } from "@dbx-tools/shared-core";
|
|
8
7
|
import { Command } from "commander";
|
|
9
8
|
import { generateReleaseSummary } from "./release-summary.ts";
|
|
9
|
+
import { captureTaskCommand } from "../src/_task-command.ts";
|
|
10
10
|
import type { ReleaseUnitGraph } from "../src/release-catalog.ts";
|
|
11
11
|
import type { ReleasePlan } from "../src/release-plan.ts";
|
|
12
12
|
|
|
@@ -71,12 +71,5 @@ function requestNotes(root: string): { summary?: string; paths: string[] } {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
function gitCapture(root: string, args: string[]): string {
|
|
74
|
-
|
|
75
|
-
cwd: root,
|
|
76
|
-
stdout: "capture",
|
|
77
|
-
stderr: "ignore",
|
|
78
|
-
stdin: "ignore",
|
|
79
|
-
check: false,
|
|
80
|
-
});
|
|
81
|
-
return result.exitCode === 0 ? (result.stdout?.trim() ?? "") : "";
|
|
74
|
+
return captureTaskCommand(root, "git", args);
|
|
82
75
|
}
|
package/tasks/release-summary.ts
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
|
-
import
|
|
10
|
-
import
|
|
9
|
+
import * as exec from "@dbx-tools/core/exec";
|
|
10
|
+
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
11
|
+
import { json, log, object, stringUtils } from "@dbx-tools/shared-core";
|
|
12
|
+
import { captureTaskCommand } from "../src/_task-command.ts";
|
|
11
13
|
import {
|
|
12
14
|
RELEASE_SUMMARY_PROVIDER_NAMES,
|
|
13
15
|
releaseSummaryFile,
|
|
@@ -84,30 +86,23 @@ export type ReleaseSummaryRunner = (
|
|
|
84
86
|
) => string | undefined | Promise<string | undefined>;
|
|
85
87
|
|
|
86
88
|
function capture(root: string, command: string, args: string[]): string {
|
|
87
|
-
|
|
88
|
-
cwd: root,
|
|
89
|
-
stdout: "capture",
|
|
90
|
-
stderr: "ignore",
|
|
91
|
-
stdin: "ignore",
|
|
92
|
-
check: false,
|
|
93
|
-
});
|
|
94
|
-
return result.exitCode === 0 ? (result.stdout?.trim() ?? "") : "";
|
|
89
|
+
return captureTaskCommand(root, command, args);
|
|
95
90
|
}
|
|
96
91
|
|
|
97
92
|
function contentText(value: unknown): string | undefined {
|
|
98
|
-
if (typeof value === "string") return
|
|
93
|
+
if (typeof value === "string") return stringUtils.trimToNull(value) ?? undefined;
|
|
99
94
|
if (!Array.isArray(value)) return undefined;
|
|
100
95
|
const text = value
|
|
101
96
|
.flatMap((item) => (object.isRecord(item) && typeof item.text === "string" ? [item.text] : []))
|
|
102
97
|
.join("");
|
|
103
|
-
return
|
|
98
|
+
return stringUtils.trimToNull(text) ?? undefined;
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
function eventText(event: Record<string, unknown>): string | undefined {
|
|
107
|
-
if (typeof event.result === "string") return
|
|
102
|
+
if (typeof event.result === "string") return stringUtils.trimToNull(event.result) ?? undefined;
|
|
108
103
|
const item = object.isRecord(event.item) ? event.item : undefined;
|
|
109
104
|
if (item?.type === "agent_message" && typeof item.text === "string") {
|
|
110
|
-
return
|
|
105
|
+
return stringUtils.trimToNull(item.text) ?? undefined;
|
|
111
106
|
}
|
|
112
107
|
const message = object.isRecord(event.message) ? event.message : undefined;
|
|
113
108
|
return contentText(message?.content);
|
|
@@ -121,7 +116,7 @@ function eventType(event: Record<string, unknown>): string {
|
|
|
121
116
|
function eventDetail(event: Record<string, unknown>): string | undefined {
|
|
122
117
|
const item = object.isRecord(event.item) ? event.item : undefined;
|
|
123
118
|
for (const value of [item?.message, event.message, event.error]) {
|
|
124
|
-
if (typeof value === "string") return
|
|
119
|
+
if (typeof value === "string") return stringUtils.trimToNull(value) ?? undefined;
|
|
125
120
|
}
|
|
126
121
|
return undefined;
|
|
127
122
|
}
|
|
@@ -129,7 +124,7 @@ function eventDetail(event: Record<string, unknown>): string | undefined {
|
|
|
129
124
|
function eventToolName(event: Record<string, unknown>): string | undefined {
|
|
130
125
|
const item = object.isRecord(event.item) ? event.item : undefined;
|
|
131
126
|
for (const value of [event.tool_name, event.toolName, event.name, item?.tool_name, item?.name]) {
|
|
132
|
-
if (typeof value === "string") return
|
|
127
|
+
if (typeof value === "string") return stringUtils.trimToNull(value) ?? undefined;
|
|
133
128
|
}
|
|
134
129
|
const toolCall = object.isRecord(event.tool_call)
|
|
135
130
|
? event.tool_call
|
|
@@ -185,7 +180,7 @@ async function runProvider(
|
|
|
185
180
|
(line) => {
|
|
186
181
|
const event = json.parseRecord(line);
|
|
187
182
|
if (!event) {
|
|
188
|
-
const text =
|
|
183
|
+
const text = stringUtils.trimToNull(line);
|
|
189
184
|
if (text) {
|
|
190
185
|
textOutput.push(text);
|
|
191
186
|
logEvent({ type: "text", text });
|
|
@@ -198,7 +193,7 @@ async function runProvider(
|
|
|
198
193
|
},
|
|
199
194
|
],
|
|
200
195
|
stderr: (line) => {
|
|
201
|
-
const message =
|
|
196
|
+
const message = stringUtils.trimToNull(line);
|
|
202
197
|
if (message) logger.warn("provider-stderr", { provider: provider.name, message });
|
|
203
198
|
},
|
|
204
199
|
stdin: "ignore",
|
|
@@ -243,6 +238,7 @@ export async function selectReleaseSummary(
|
|
|
243
238
|
}
|
|
244
239
|
|
|
245
240
|
function summaryPrompt(
|
|
241
|
+
projectName: string,
|
|
246
242
|
version: string,
|
|
247
243
|
fromRef: string | undefined,
|
|
248
244
|
commits: string,
|
|
@@ -250,7 +246,7 @@ function summaryPrompt(
|
|
|
250
246
|
diffStat: string,
|
|
251
247
|
): string {
|
|
252
248
|
return [
|
|
253
|
-
`Write a concise user-facing Markdown release summary for
|
|
249
|
+
`Write a concise user-facing Markdown release summary for ${projectName} ${version}.`,
|
|
254
250
|
"Use only the supplied Git context. Do not run commands, edit files, speculate,",
|
|
255
251
|
"mention commit hashes, use emojis, or use em/en dashes.",
|
|
256
252
|
"Return no title and no fenced block. Start with one short paragraph, then",
|
|
@@ -270,7 +266,12 @@ function summaryPrompt(
|
|
|
270
266
|
}
|
|
271
267
|
|
|
272
268
|
/** Deterministic fallback when every configured AI provider is unavailable. */
|
|
273
|
-
function gitSummary(
|
|
269
|
+
function gitSummary(
|
|
270
|
+
projectName: string,
|
|
271
|
+
version: string,
|
|
272
|
+
commits: string,
|
|
273
|
+
changedFiles: string,
|
|
274
|
+
): string {
|
|
274
275
|
const subjects = commits
|
|
275
276
|
.split("\n")
|
|
276
277
|
.map((line) => line.trim())
|
|
@@ -290,7 +291,7 @@ function gitSummary(version: string, commits: string, changedFiles: string): str
|
|
|
290
291
|
.slice(0, 12);
|
|
291
292
|
const bullets = subjects.length ? subjects : areas.map((area) => `Updated ${area}`);
|
|
292
293
|
return [
|
|
293
|
-
|
|
294
|
+
`${projectName} ${version} contains the reviewed changes listed below.`,
|
|
294
295
|
"",
|
|
295
296
|
"## Changes",
|
|
296
297
|
...(bullets.length ? bullets.map((item) => `- ${item}`) : ["- Release metadata updated."]),
|
|
@@ -310,6 +311,7 @@ export async function generateReleaseSummary(options: {
|
|
|
310
311
|
readonly providers?: readonly ReleaseSummaryProviderName[];
|
|
311
312
|
readonly runner?: ReleaseSummaryRunner;
|
|
312
313
|
}): Promise<string | undefined> {
|
|
314
|
+
const projectName = projectUtils.name(options.root);
|
|
313
315
|
const relativeOutput =
|
|
314
316
|
options.outputFile ?? releaseSummaryFile(options.version, options.component);
|
|
315
317
|
const output = join(options.root, relativeOutput);
|
|
@@ -347,17 +349,26 @@ export async function generateReleaseSummary(options: {
|
|
|
347
349
|
...scopedPaths,
|
|
348
350
|
`:(exclude)${relativeOutput}`,
|
|
349
351
|
]);
|
|
350
|
-
const customSummary =
|
|
352
|
+
const customSummary = stringUtils.trimToNull(options.customSummary);
|
|
351
353
|
const result = customSummary
|
|
352
354
|
? undefined
|
|
353
355
|
: await selectReleaseSummary(
|
|
354
356
|
options.root,
|
|
355
|
-
summaryPrompt(
|
|
357
|
+
summaryPrompt(
|
|
358
|
+
projectName,
|
|
359
|
+
options.version,
|
|
360
|
+
options.fromRef,
|
|
361
|
+
commits,
|
|
362
|
+
changedFiles,
|
|
363
|
+
diffStat,
|
|
364
|
+
),
|
|
356
365
|
options.runner,
|
|
357
366
|
options.providers,
|
|
358
367
|
);
|
|
359
368
|
const summary =
|
|
360
|
-
customSummary ??
|
|
369
|
+
customSummary ??
|
|
370
|
+
result?.summary ??
|
|
371
|
+
gitSummary(projectName, options.version, commits, changedFiles);
|
|
361
372
|
const provider = customSummary ? "custom" : (result?.provider ?? "git");
|
|
362
373
|
if (!customSummary && !result) {
|
|
363
374
|
logger.info("AI providers unavailable; using Git release summary");
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env -S bun
|
|
2
|
+
/** Validate one stable release version against the canonical workspace policy. */
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
6
|
+
import { compareSemver, parseSemver, resolveRemoteVersion } from "../src/workspace-version.ts";
|
|
7
|
+
|
|
8
|
+
export function assertReleaseVersion(
|
|
9
|
+
version: string,
|
|
10
|
+
options: { root?: string; prefixes?: readonly string[]; assertNext?: boolean } = {},
|
|
11
|
+
): void {
|
|
12
|
+
const parsed = parseSemver(version);
|
|
13
|
+
if (!parsed) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`release version must be an exact stable x.y.z, got ${JSON.stringify(version)}`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (!options.assertNext) return;
|
|
19
|
+
const root = resolve(options.root ?? projectUtils.root() ?? process.cwd());
|
|
20
|
+
const prefixes = options.prefixes?.length ? options.prefixes : ["v"];
|
|
21
|
+
const current = resolveRemoteVersion(root, prefixes, { fetch: false });
|
|
22
|
+
const currentParsed = current ? parseSemver(current) : undefined;
|
|
23
|
+
if (currentParsed && compareSemver(parsed, currentParsed) <= 0) {
|
|
24
|
+
throw new Error(`release version ${version} must be greater than published ${current}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (import.meta.main) {
|
|
29
|
+
const { values } = parseArgs({
|
|
30
|
+
options: {
|
|
31
|
+
version: { type: "string" },
|
|
32
|
+
root: { type: "string" },
|
|
33
|
+
prefix: { type: "string", multiple: true },
|
|
34
|
+
"assert-next": { type: "boolean", default: false },
|
|
35
|
+
},
|
|
36
|
+
strict: true,
|
|
37
|
+
});
|
|
38
|
+
if (!values.version) throw new Error("--version is required");
|
|
39
|
+
assertReleaseVersion(values.version, {
|
|
40
|
+
...(values.root ? { root: values.root } : {}),
|
|
41
|
+
...(values.prefix ? { prefixes: values.prefix } : {}),
|
|
42
|
+
assertNext: values["assert-next"],
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare const FINGERPRINT_SCHEMA = 3;
|
|
2
|
+
export declare const VERSION_SLOT_SCHEMA = 1;
|
|
3
|
+
export declare const VERSION_CAPACITY = 64;
|
|
4
|
+
/** Canonical TOML representation with workspace-owned release versions removed. */
|
|
5
|
+
export declare function normalizeManifest(text: string): string;
|
|
6
|
+
/** Canonical Cargo.lock representation with local package versions normalized. */
|
|
7
|
+
export declare function normalizeLockfile(text: string, workspaceVersion: string, packageNames?: ReadonlySet<string>): string;
|
|
8
|
+
/** Hash Cargo-discovered members and every tracked or unignored file beneath them. */
|
|
9
|
+
export declare function sourceHash(root: string, sources?: readonly string[]): string;
|
|
10
|
+
export declare function linkerIdentity(target: string): string;
|
|
11
|
+
export interface TargetKeyOptions {
|
|
12
|
+
readonly rustSourceHash: string;
|
|
13
|
+
readonly target: string;
|
|
14
|
+
readonly targetConfig?: string;
|
|
15
|
+
readonly toolchain?: string;
|
|
16
|
+
readonly rustc?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function targetKey({ rustSourceHash, target, targetConfig, toolchain, rustc, }: TargetKeyOptions): string;
|
|
19
|
+
export interface FingerprintOptions {
|
|
20
|
+
readonly root?: string;
|
|
21
|
+
readonly output?: string;
|
|
22
|
+
readonly check?: boolean;
|
|
23
|
+
readonly targets: readonly string[];
|
|
24
|
+
readonly toolchain?: string;
|
|
25
|
+
readonly portable?: boolean;
|
|
26
|
+
readonly sources?: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
export interface RustBuildFingerprint {
|
|
29
|
+
readonly schemaVersion: number;
|
|
30
|
+
readonly versionSlotSchema: number;
|
|
31
|
+
readonly rustSourceHash: string;
|
|
32
|
+
readonly targets: Readonly<Record<string, string>>;
|
|
33
|
+
}
|
|
34
|
+
export declare function fingerprint({ root, output, check, targets, toolchain, portable, sources, }: FingerprintOptions): RustBuildFingerprint;
|
|
35
|
+
export interface StampOptions {
|
|
36
|
+
readonly objcopy?: string;
|
|
37
|
+
readonly codesign?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function stamp(binary: string, version: string, options?: StampOptions): boolean;
|
|
40
|
+
export declare function stampTree(root: string, version: string, options?: StampOptions): number;
|
|
41
|
+
export declare function main(args?: readonly string[]): void;
|