@dbx-tools/projen 0.9.21 → 0.9.29
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 +77 -207
- package/index.ts +10 -14
- package/package.json +4 -4
- package/src/barrels.ts +1 -74
- package/src/module-exports.ts +175 -1
- package/src/openapi.ts +5 -72
- package/src/packages.ts +45 -0
- package/src/project-js.ts +3 -6
- package/src/project-predicate.ts +1 -2
- package/src/project-py.ts +82 -103
- package/src/project.ts +2 -2
- package/src/python-node-bindings.ts +138 -0
- package/src/python-node-bundle.ts +92 -0
- package/src/release-catalog.ts +3 -5
- package/src/{release-dispatch.ts → release-context.ts} +0 -11
- package/src/release.ts +70 -152
- package/tasks/bump.ts +12 -21
- package/tasks/databricks-cli-assets.ts +129 -0
- package/tasks/local-publish.ts +23 -107
- package/tasks/publish-npm.ts +10 -1
- package/tasks/publish-python.ts +26 -11
- package/tasks/publish.ts +12 -48
- package/tasks/python-node-bindings-watch.ts +47 -0
- package/tasks/python-node-bindings.ts +1017 -0
- package/tasks/python-release.ts +74 -0
- package/tasks/release.ts +99 -0
- package/tasks/sync.ts +13 -18
- package/tasks/version-check.ts +1 -28
- package/._LICENSE +0 -0
- package/._README.md +0 -0
- package/._index.ts +0 -0
- package/._package.json +0 -0
- 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/_release-platform.ts +0 -78
- package/src/_rust-project.ts +0 -429
- package/src/_rust-release-workflow.ts +0 -311
- package/src/project-rs.ts +0 -1054
- package/src/release-github.ts +0 -109
- package/src/uniffi.ts +0 -65
- 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/publish-uniffi-local.ts +0 -359
- package/tasks/release-assets.ts +0 -467
- package/tasks/release-candidate.ts +0 -416
- package/tasks/release-manifest.ts +0 -273
- package/tasks/release-pr.ts +0 -589
- package/tasks/release-summary.ts +0 -379
- package/tasks/rust.ts +0 -173
- package/tasks/uniffi-python.d.ts +0 -60
- package/tasks/uniffi-python.js +0 -178
- package/tasks/uniffi-release.mjs +0 -579
- package/tasks/uniffi.ts +0 -262
package/tasks/release-summary.ts
DELETED
|
@@ -1,379 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S bun
|
|
2
|
-
/**
|
|
3
|
-
* Optional AI-generated release summary with CLI fallback.
|
|
4
|
-
*
|
|
5
|
-
* @module
|
|
6
|
-
*/
|
|
7
|
-
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
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";
|
|
13
|
-
import {
|
|
14
|
-
RELEASE_SUMMARY_PROVIDER_NAMES,
|
|
15
|
-
releaseSummaryFile,
|
|
16
|
-
type ReleaseSummaryProviderName,
|
|
17
|
-
} from "../src/release-dispatch.ts";
|
|
18
|
-
|
|
19
|
-
const logger = log.logger("projen:release-summary");
|
|
20
|
-
const PROVIDER_TIMEOUT_MS = 60_000;
|
|
21
|
-
const PROVIDER_KILL_GRACE_MS = 5_000;
|
|
22
|
-
|
|
23
|
-
export interface ReleaseSummaryProvider {
|
|
24
|
-
readonly name: ReleaseSummaryProviderName;
|
|
25
|
-
readonly command: string;
|
|
26
|
-
readonly probeArgs: readonly string[];
|
|
27
|
-
args(root: string, prompt: string): string[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const RELEASE_SUMMARY_PROVIDERS: readonly ReleaseSummaryProvider[] = [
|
|
31
|
-
{
|
|
32
|
-
name: "cursor",
|
|
33
|
-
command: "cursor",
|
|
34
|
-
probeArgs: ["agent", "--version"],
|
|
35
|
-
args: (root, prompt) => [
|
|
36
|
-
"agent",
|
|
37
|
-
"--print",
|
|
38
|
-
"--mode",
|
|
39
|
-
"ask",
|
|
40
|
-
"--output-format",
|
|
41
|
-
"stream-json",
|
|
42
|
-
"--trust",
|
|
43
|
-
"--workspace",
|
|
44
|
-
root,
|
|
45
|
-
prompt,
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: "codex",
|
|
50
|
-
command: "codex",
|
|
51
|
-
probeArgs: ["--version"],
|
|
52
|
-
args: (root, prompt) => [
|
|
53
|
-
"exec",
|
|
54
|
-
"--sandbox",
|
|
55
|
-
"read-only",
|
|
56
|
-
"--skip-git-repo-check",
|
|
57
|
-
"--json",
|
|
58
|
-
"--color",
|
|
59
|
-
"never",
|
|
60
|
-
"-C",
|
|
61
|
-
root,
|
|
62
|
-
prompt,
|
|
63
|
-
],
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: "claude",
|
|
67
|
-
command: "claude",
|
|
68
|
-
probeArgs: ["--version"],
|
|
69
|
-
args: (_root, prompt) => [
|
|
70
|
-
"--print",
|
|
71
|
-
"--no-session-persistence",
|
|
72
|
-
"--output-format",
|
|
73
|
-
"stream-json",
|
|
74
|
-
"--verbose",
|
|
75
|
-
"--tools",
|
|
76
|
-
"",
|
|
77
|
-
prompt,
|
|
78
|
-
],
|
|
79
|
-
},
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
export type ReleaseSummaryRunner = (
|
|
83
|
-
provider: ReleaseSummaryProvider,
|
|
84
|
-
root: string,
|
|
85
|
-
prompt: string,
|
|
86
|
-
) => string | undefined | Promise<string | undefined>;
|
|
87
|
-
|
|
88
|
-
function contentText(value: unknown): string | undefined {
|
|
89
|
-
if (typeof value === "string") return stringUtils.trimToNull(value) ?? undefined;
|
|
90
|
-
if (!Array.isArray(value)) return undefined;
|
|
91
|
-
const text = value
|
|
92
|
-
.flatMap((item) => (object.isRecord(item) && typeof item.text === "string" ? [item.text] : []))
|
|
93
|
-
.join("");
|
|
94
|
-
return stringUtils.trimToNull(text) ?? undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function eventText(event: Record<string, unknown>): string | undefined {
|
|
98
|
-
if (typeof event.result === "string") return stringUtils.trimToNull(event.result) ?? undefined;
|
|
99
|
-
const item = object.isRecord(event.item) ? event.item : undefined;
|
|
100
|
-
if (item?.type === "agent_message" && typeof item.text === "string") {
|
|
101
|
-
return stringUtils.trimToNull(item.text) ?? undefined;
|
|
102
|
-
}
|
|
103
|
-
const message = object.isRecord(event.message) ? event.message : undefined;
|
|
104
|
-
return contentText(message?.content);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function eventType(event: Record<string, unknown>): string {
|
|
108
|
-
const item = object.isRecord(event.item) ? event.item : undefined;
|
|
109
|
-
return [event.type, item?.type].filter((value) => typeof value === "string").join(":") || "event";
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function eventDetail(event: Record<string, unknown>): string | undefined {
|
|
113
|
-
const item = object.isRecord(event.item) ? event.item : undefined;
|
|
114
|
-
for (const value of [item?.message, event.message, event.error]) {
|
|
115
|
-
if (typeof value === "string") return stringUtils.trimToNull(value) ?? undefined;
|
|
116
|
-
}
|
|
117
|
-
return undefined;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function eventToolName(event: Record<string, unknown>): string | undefined {
|
|
121
|
-
const item = object.isRecord(event.item) ? event.item : undefined;
|
|
122
|
-
for (const value of [event.tool_name, event.toolName, event.name, item?.tool_name, item?.name]) {
|
|
123
|
-
if (typeof value === "string") return stringUtils.trimToNull(value) ?? undefined;
|
|
124
|
-
}
|
|
125
|
-
const toolCall = object.isRecord(event.tool_call)
|
|
126
|
-
? event.tool_call
|
|
127
|
-
: object.isRecord(event.toolCall)
|
|
128
|
-
? event.toolCall
|
|
129
|
-
: undefined;
|
|
130
|
-
const wrapper = toolCall
|
|
131
|
-
? Object.keys(toolCall).find(
|
|
132
|
-
(key) => key.endsWith("ToolCall") && object.isRecord(toolCall[key]),
|
|
133
|
-
)
|
|
134
|
-
: undefined;
|
|
135
|
-
return wrapper ? wrapper.slice(0, -"ToolCall".length) : undefined;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Normalize one provider event into the fields retained in release logs. */
|
|
139
|
-
export function releaseSummaryProviderEvent(event: Record<string, unknown>): {
|
|
140
|
-
type: string;
|
|
141
|
-
text?: string;
|
|
142
|
-
detail?: string;
|
|
143
|
-
tool?: string;
|
|
144
|
-
} {
|
|
145
|
-
const text = eventText(event);
|
|
146
|
-
const detail = eventDetail(event);
|
|
147
|
-
const tool = eventToolName(event);
|
|
148
|
-
return {
|
|
149
|
-
type: eventType(event),
|
|
150
|
-
...(text ? { text } : {}),
|
|
151
|
-
...(detail ? { detail } : {}),
|
|
152
|
-
...(tool ? { tool } : {}),
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async function runProvider(
|
|
157
|
-
provider: ReleaseSummaryProvider,
|
|
158
|
-
root: string,
|
|
159
|
-
prompt: string,
|
|
160
|
-
): Promise<string | undefined> {
|
|
161
|
-
if (!captureTaskCommand(root, provider.command, [...provider.probeArgs])) return undefined;
|
|
162
|
-
let summary: string | undefined;
|
|
163
|
-
let previousEventKey: string | undefined;
|
|
164
|
-
const textOutput: string[] = [];
|
|
165
|
-
const logEvent = (event: Record<string, unknown>) => {
|
|
166
|
-
const fields = { provider: provider.name, ...event };
|
|
167
|
-
const key = JSON.stringify(fields);
|
|
168
|
-
if (key === previousEventKey) return;
|
|
169
|
-
previousEventKey = key;
|
|
170
|
-
logger.info("provider-event", fields);
|
|
171
|
-
};
|
|
172
|
-
const child = exec.spawn(provider.command, provider.args(root, prompt), {
|
|
173
|
-
cwd: root,
|
|
174
|
-
stdout: [
|
|
175
|
-
"capture",
|
|
176
|
-
(line) => {
|
|
177
|
-
const event = json.parseRecord(line);
|
|
178
|
-
if (!event) {
|
|
179
|
-
const text = stringUtils.trimToNull(line);
|
|
180
|
-
if (text) {
|
|
181
|
-
textOutput.push(text);
|
|
182
|
-
logEvent({ type: "text", text });
|
|
183
|
-
}
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
const fields = releaseSummaryProviderEvent(event);
|
|
187
|
-
if (fields.text) summary = fields.text;
|
|
188
|
-
logEvent(fields);
|
|
189
|
-
},
|
|
190
|
-
],
|
|
191
|
-
stderr: (line) => {
|
|
192
|
-
const message = stringUtils.trimToNull(line);
|
|
193
|
-
if (message) logger.warn("provider-stderr", { provider: provider.name, message });
|
|
194
|
-
},
|
|
195
|
-
stdin: "ignore",
|
|
196
|
-
check: false,
|
|
197
|
-
});
|
|
198
|
-
let escalation: ReturnType<typeof setTimeout> | undefined;
|
|
199
|
-
const timeout = setTimeout(() => {
|
|
200
|
-
logger.warn("provider-timeout", {
|
|
201
|
-
provider: provider.name,
|
|
202
|
-
timeoutMs: PROVIDER_TIMEOUT_MS,
|
|
203
|
-
});
|
|
204
|
-
child.kill("SIGTERM");
|
|
205
|
-
escalation = setTimeout(() => child.kill("SIGKILL"), PROVIDER_KILL_GRACE_MS);
|
|
206
|
-
escalation.unref();
|
|
207
|
-
}, PROVIDER_TIMEOUT_MS);
|
|
208
|
-
timeout.unref();
|
|
209
|
-
const result = await child.finally(() => {
|
|
210
|
-
clearTimeout(timeout);
|
|
211
|
-
if (escalation) clearTimeout(escalation);
|
|
212
|
-
});
|
|
213
|
-
if (result.exitCode !== 0) {
|
|
214
|
-
logger.warn("provider-failed", { provider: provider.name, exitCode: result.exitCode });
|
|
215
|
-
return undefined;
|
|
216
|
-
}
|
|
217
|
-
return summary ?? (textOutput.length ? textOutput.join("\n") : undefined);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/** Return the first non-empty provider response in Cursor, Codex, Claude order. */
|
|
221
|
-
export async function selectReleaseSummary(
|
|
222
|
-
root: string,
|
|
223
|
-
prompt: string,
|
|
224
|
-
runner: ReleaseSummaryRunner = runProvider,
|
|
225
|
-
providers: readonly ReleaseSummaryProviderName[] = RELEASE_SUMMARY_PROVIDER_NAMES,
|
|
226
|
-
): Promise<{ provider: ReleaseSummaryProvider["name"]; summary: string } | undefined> {
|
|
227
|
-
for (const name of providers) {
|
|
228
|
-
const provider = RELEASE_SUMMARY_PROVIDERS.find((candidate) => candidate.name === name);
|
|
229
|
-
if (!provider) continue;
|
|
230
|
-
const summary = (await runner(provider, root, prompt))?.trim();
|
|
231
|
-
if (summary) return { provider: provider.name, summary };
|
|
232
|
-
}
|
|
233
|
-
return undefined;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function summaryPrompt(
|
|
237
|
-
projectName: string,
|
|
238
|
-
version: string,
|
|
239
|
-
fromRef: string | undefined,
|
|
240
|
-
commits: string,
|
|
241
|
-
changedFiles: string,
|
|
242
|
-
diffStat: string,
|
|
243
|
-
): string {
|
|
244
|
-
return [
|
|
245
|
-
`Write a concise user-facing Markdown release summary for ${projectName} ${version}.`,
|
|
246
|
-
"Use only the supplied Git context. Do not run commands, edit files, speculate,",
|
|
247
|
-
"mention commit hashes, use emojis, or use em/en dashes.",
|
|
248
|
-
"Return no title and no fenced block. Start with one short paragraph, then",
|
|
249
|
-
"group concrete changes under brief Markdown headings with bullets.",
|
|
250
|
-
"",
|
|
251
|
-
`Comparison base: ${fromRef ?? "repository root"}`,
|
|
252
|
-
"",
|
|
253
|
-
"Commits:",
|
|
254
|
-
commits || "(none)",
|
|
255
|
-
"",
|
|
256
|
-
"Changed files:",
|
|
257
|
-
changedFiles || "(none)",
|
|
258
|
-
"",
|
|
259
|
-
"Diff stat:",
|
|
260
|
-
diffStat || "(none)",
|
|
261
|
-
].join("\n");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/** Deterministic fallback when every configured AI provider is unavailable. */
|
|
265
|
-
function gitSummary(
|
|
266
|
-
projectName: string,
|
|
267
|
-
version: string,
|
|
268
|
-
commits: string,
|
|
269
|
-
changedFiles: string,
|
|
270
|
-
): string {
|
|
271
|
-
const subjects = commits
|
|
272
|
-
.split("\n")
|
|
273
|
-
.map((line) => line.trim())
|
|
274
|
-
.filter(Boolean)
|
|
275
|
-
.filter((line, index, lines) => lines.indexOf(line) === index)
|
|
276
|
-
.slice(0, 20);
|
|
277
|
-
const files = changedFiles
|
|
278
|
-
.split("\n")
|
|
279
|
-
.map((line) => line.trim().split(/\s+/).at(-1))
|
|
280
|
-
.filter((value): value is string => Boolean(value));
|
|
281
|
-
const areas = files
|
|
282
|
-
.map((file) => {
|
|
283
|
-
if (file.startsWith("packages/")) return file.split("/").slice(0, 4).join("/");
|
|
284
|
-
return file.split("/")[0] ?? file;
|
|
285
|
-
})
|
|
286
|
-
.filter((area, index, all) => all.indexOf(area) === index)
|
|
287
|
-
.slice(0, 12);
|
|
288
|
-
const bullets = subjects.length ? subjects : areas.map((area) => `Updated ${area}`);
|
|
289
|
-
return [
|
|
290
|
-
`${projectName} ${version} contains the reviewed changes listed below.`,
|
|
291
|
-
"",
|
|
292
|
-
"## Changes",
|
|
293
|
-
...(bullets.length ? bullets.map((item) => `- ${item}`) : ["- Release metadata updated."]),
|
|
294
|
-
].join("\n");
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/** Generate one immutable versioned summary with an AI or Git fallback. */
|
|
298
|
-
export async function generateReleaseSummary(options: {
|
|
299
|
-
readonly root: string;
|
|
300
|
-
readonly version: string;
|
|
301
|
-
readonly component?: string;
|
|
302
|
-
readonly paths?: readonly string[];
|
|
303
|
-
readonly customSummary?: string;
|
|
304
|
-
readonly outputFile?: string;
|
|
305
|
-
readonly fromRef?: string;
|
|
306
|
-
readonly toRef?: string;
|
|
307
|
-
readonly providers?: readonly ReleaseSummaryProviderName[];
|
|
308
|
-
readonly runner?: ReleaseSummaryRunner;
|
|
309
|
-
}): Promise<string | undefined> {
|
|
310
|
-
const projectName = projectUtils.name(options.root);
|
|
311
|
-
const relativeOutput =
|
|
312
|
-
options.outputFile ?? releaseSummaryFile(options.version, options.component);
|
|
313
|
-
const output = join(options.root, relativeOutput);
|
|
314
|
-
if (existsSync(output)) rmSync(output);
|
|
315
|
-
const target =
|
|
316
|
-
options.toRef &&
|
|
317
|
-
captureTaskCommand(options.root, "git", ["rev-parse", "--verify", options.toRef])
|
|
318
|
-
? options.toRef
|
|
319
|
-
: "HEAD";
|
|
320
|
-
const range =
|
|
321
|
-
options.fromRef &&
|
|
322
|
-
captureTaskCommand(options.root, "git", ["rev-parse", "--verify", options.fromRef])
|
|
323
|
-
? `${options.fromRef}..${target}`
|
|
324
|
-
: target;
|
|
325
|
-
const scopedPaths = options.paths?.length ? [...options.paths] : ["."];
|
|
326
|
-
const commits = captureTaskCommand(options.root, "git", [
|
|
327
|
-
"log",
|
|
328
|
-
"--no-merges",
|
|
329
|
-
"--format=%s%n%b",
|
|
330
|
-
range,
|
|
331
|
-
"--",
|
|
332
|
-
...scopedPaths,
|
|
333
|
-
]);
|
|
334
|
-
const changedFiles = captureTaskCommand(options.root, "git", [
|
|
335
|
-
"diff",
|
|
336
|
-
"--name-status",
|
|
337
|
-
range,
|
|
338
|
-
"--",
|
|
339
|
-
...scopedPaths,
|
|
340
|
-
`:(exclude)${relativeOutput}`,
|
|
341
|
-
]);
|
|
342
|
-
const diffStat = captureTaskCommand(options.root, "git", [
|
|
343
|
-
"diff",
|
|
344
|
-
"--stat",
|
|
345
|
-
range,
|
|
346
|
-
"--",
|
|
347
|
-
...scopedPaths,
|
|
348
|
-
`:(exclude)${relativeOutput}`,
|
|
349
|
-
]);
|
|
350
|
-
const customSummary = stringUtils.trimToNull(options.customSummary);
|
|
351
|
-
const result = customSummary
|
|
352
|
-
? undefined
|
|
353
|
-
: await selectReleaseSummary(
|
|
354
|
-
options.root,
|
|
355
|
-
summaryPrompt(
|
|
356
|
-
projectName,
|
|
357
|
-
options.version,
|
|
358
|
-
options.fromRef,
|
|
359
|
-
commits,
|
|
360
|
-
changedFiles,
|
|
361
|
-
diffStat,
|
|
362
|
-
),
|
|
363
|
-
options.runner,
|
|
364
|
-
options.providers,
|
|
365
|
-
);
|
|
366
|
-
const summary =
|
|
367
|
-
customSummary ??
|
|
368
|
-
result?.summary ??
|
|
369
|
-
gitSummary(projectName, options.version, commits, changedFiles);
|
|
370
|
-
const provider = customSummary ? "custom" : (result?.provider ?? "git");
|
|
371
|
-
if (!customSummary && !result) {
|
|
372
|
-
logger.info("AI providers unavailable; using Git release summary");
|
|
373
|
-
}
|
|
374
|
-
const content = `# Release ${options.component ? `${options.component} ` : ""}${options.version}\n\n${summary.trim()}\n`;
|
|
375
|
-
mkdirSync(dirname(output), { recursive: true });
|
|
376
|
-
writeFileSync(output, content);
|
|
377
|
-
logger.info("generated", { provider, path: output });
|
|
378
|
-
return content;
|
|
379
|
-
}
|
package/tasks/rust.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S bun
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { dirname, isAbsolute, resolve, sep } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { log, object } from "@dbx-tools/shared-core";
|
|
6
|
-
import { runTaskCommand, taskCommandSucceeds } from "../src/_task-command.ts";
|
|
7
|
-
import { readDbxToolsConfig, repoRoot } from "../src/packages.ts";
|
|
8
|
-
import {
|
|
9
|
-
discoverRustCrates,
|
|
10
|
-
hasUniFFIBindings,
|
|
11
|
-
orderRustBindings,
|
|
12
|
-
type RustBindingMapping,
|
|
13
|
-
type RustWorkspaceMapping,
|
|
14
|
-
} from "../src/project-rs.ts";
|
|
15
|
-
import { runSynth } from "../src/scaffold.ts";
|
|
16
|
-
import { watchLoop } from "../src/watch.ts";
|
|
17
|
-
|
|
18
|
-
const logger = log.logger("projen:rust");
|
|
19
|
-
|
|
20
|
-
function rustConfig(): RustWorkspaceMapping | undefined {
|
|
21
|
-
const value = readDbxToolsConfig(repoRoot)?.rust;
|
|
22
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
23
|
-
const candidate = value as Partial<RustWorkspaceMapping>;
|
|
24
|
-
if (typeof candidate.root !== "string") return undefined;
|
|
25
|
-
if (!Array.isArray(candidate.crates) || !Array.isArray(candidate.bindings)) return undefined;
|
|
26
|
-
return candidate as RustWorkspaceMapping;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function cargoAvailable(): boolean {
|
|
30
|
-
return taskCommandSucceeds(repoRoot, "cargo", ["--version"]);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function currentStructure(config: RustWorkspaceMapping): RustWorkspaceMapping {
|
|
34
|
-
const root = resolve(repoRoot, config.root);
|
|
35
|
-
const directories = discoverRustCrates(root);
|
|
36
|
-
const crates = directories.map((directory) => `${config.root}/${directory}`);
|
|
37
|
-
const recorded = new Map(config.bindings.map((binding) => [binding.rust, binding]));
|
|
38
|
-
const bindings = crates.flatMap((rust) => {
|
|
39
|
-
if (!hasUniFFIBindings(resolve(repoRoot, rust))) return [];
|
|
40
|
-
const binding = recorded.get(rust);
|
|
41
|
-
return binding ? [binding] : [{ crate: "", rust }];
|
|
42
|
-
});
|
|
43
|
-
return { ...config, crates, bindings };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Whether discovered crate membership or UniFFI marker membership changed. */
|
|
47
|
-
export function rustStructureChanged(config: RustWorkspaceMapping): boolean {
|
|
48
|
-
return !sameStructure(config, currentStructure(config));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function sameStructure(left: RustWorkspaceMapping, right: RustWorkspaceMapping): boolean {
|
|
52
|
-
return (
|
|
53
|
-
JSON.stringify(left.crates) === JSON.stringify(right.crates) &&
|
|
54
|
-
JSON.stringify(left.bindings.map((binding) => binding.rust)) ===
|
|
55
|
-
JSON.stringify(right.bindings.map((binding) => binding.rust))
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function ownerBinding(
|
|
60
|
-
path: string,
|
|
61
|
-
bindings: readonly RustBindingMapping[],
|
|
62
|
-
): RustBindingMapping | undefined {
|
|
63
|
-
const absolute = isAbsolute(path) ? path : resolve(repoRoot, path);
|
|
64
|
-
return bindings.find((binding) => {
|
|
65
|
-
const directory = resolve(repoRoot, binding.rust);
|
|
66
|
-
return absolute === directory || absolute.startsWith(directory + sep);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function generate(binding: RustBindingMapping): void {
|
|
71
|
-
const targets = [
|
|
72
|
-
...(binding.node ? ["--node", binding.node] : []),
|
|
73
|
-
...(binding.python
|
|
74
|
-
? ["--python", binding.python, "--python-module", binding.pythonModule ?? ""]
|
|
75
|
-
: []),
|
|
76
|
-
];
|
|
77
|
-
runTaskCommand(repoRoot, process.execPath, [
|
|
78
|
-
resolve(dirname(fileURLToPath(import.meta.url)), "uniffi.ts"),
|
|
79
|
-
"--crate",
|
|
80
|
-
binding.crate,
|
|
81
|
-
...targets,
|
|
82
|
-
]);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function* iterateAffectedRustBindings(
|
|
86
|
-
bindings: readonly RustBindingMapping[],
|
|
87
|
-
changed: ReadonlySet<string>,
|
|
88
|
-
): Generator<RustBindingMapping> {
|
|
89
|
-
const affected = new Set(changed);
|
|
90
|
-
for (const binding of orderRustBindings(bindings)) {
|
|
91
|
-
if (binding.dependencies?.some((dependency) => affected.has(dependency))) {
|
|
92
|
-
affected.add(binding.crate);
|
|
93
|
-
}
|
|
94
|
-
if (affected.has(binding.crate)) yield binding;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function affectedRustBindings(
|
|
99
|
-
bindings: readonly RustBindingMapping[],
|
|
100
|
-
changed: ReadonlySet<string>,
|
|
101
|
-
): RustBindingMapping[] {
|
|
102
|
-
return [...iterateAffectedRustBindings(bindings, changed)];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Lazily resolve direct binding owners so lock checks stop at the first match.
|
|
107
|
-
*/
|
|
108
|
-
export function changedRustOwners(
|
|
109
|
-
config: RustWorkspaceMapping,
|
|
110
|
-
changed: readonly string[],
|
|
111
|
-
): object.Sequence<RustBindingMapping> {
|
|
112
|
-
return object
|
|
113
|
-
.sequence(changed)
|
|
114
|
-
.map((path) => ownerBinding(path, config.bindings))
|
|
115
|
-
.nonNull()
|
|
116
|
-
.distinct();
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** Collect direct owners, then lazily yield them and their dependents in order. */
|
|
120
|
-
export function changedRustBindings(
|
|
121
|
-
config: RustWorkspaceMapping,
|
|
122
|
-
changed: readonly string[],
|
|
123
|
-
): object.Sequence<RustBindingMapping> {
|
|
124
|
-
const targets = new Set(changedRustOwners(config, changed).map((binding) => binding.crate));
|
|
125
|
-
return object.sequence(iterateAffectedRustBindings(config.bindings, targets));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const config = rustConfig();
|
|
129
|
-
|
|
130
|
-
async function main(): Promise<void> {
|
|
131
|
-
if (!config || config.crates.length === 0 || !existsSync(resolve(repoRoot, config.root))) return;
|
|
132
|
-
if (!cargoAvailable()) {
|
|
133
|
-
throw new Error("Cargo is required because Rust projects were detected");
|
|
134
|
-
}
|
|
135
|
-
if (!process.argv.includes("--watch")) {
|
|
136
|
-
for (const binding of orderRustBindings(config.bindings)) generate(binding);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
watchLoop(
|
|
141
|
-
"rust",
|
|
142
|
-
[resolve(repoRoot, config.root)],
|
|
143
|
-
(changed) => {
|
|
144
|
-
const latest = rustConfig() ?? config;
|
|
145
|
-
if (rustStructureChanged(latest)) {
|
|
146
|
-
logger.start("Rust project structure changed - re-synthesizing (+install)");
|
|
147
|
-
runSynth({ post: true });
|
|
148
|
-
logger.success("Rust project structure synchronized");
|
|
149
|
-
const refreshed = rustConfig();
|
|
150
|
-
if (!refreshed) return;
|
|
151
|
-
for (const binding of changedRustBindings(refreshed, changed)) {
|
|
152
|
-
logger.start(`generating ${binding.crate} bindings`);
|
|
153
|
-
generate(binding);
|
|
154
|
-
logger.success(`generated ${binding.crate} bindings`);
|
|
155
|
-
}
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
for (const binding of changedRustBindings(latest, changed)) {
|
|
159
|
-
logger.start(`generating ${binding.crate} bindings`);
|
|
160
|
-
generate(binding);
|
|
161
|
-
logger.success(`generated ${binding.crate} bindings`);
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
check: (changed) => {
|
|
166
|
-
const latest = rustConfig() ?? config;
|
|
167
|
-
return changedRustOwners(latest, changed).some(() => true) || rustStructureChanged(latest);
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (import.meta.main) await main();
|
package/tasks/uniffi-python.d.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
export interface TomlCodec {
|
|
2
|
-
parse(source: string): unknown;
|
|
3
|
-
stringify(document: unknown): string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface PythonPackageMapping {
|
|
7
|
-
readonly directory: string;
|
|
8
|
-
readonly name: string;
|
|
9
|
-
readonly version: string;
|
|
10
|
-
readonly uniffi: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface PythonProjectInfo {
|
|
14
|
-
readonly name: string;
|
|
15
|
-
readonly version: string;
|
|
16
|
-
readonly private: boolean;
|
|
17
|
-
readonly uniffi: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** Read the project identity and publication flag from parsed TOML metadata. */
|
|
21
|
-
export function pythonProjectInfo(source: string, toml: TomlCodec): PythonProjectInfo;
|
|
22
|
-
|
|
23
|
-
/** Project registry-compatible sibling requirements onto a temporary package copy. */
|
|
24
|
-
export function preparePythonProjectForPublication(
|
|
25
|
-
source: string,
|
|
26
|
-
options: {
|
|
27
|
-
readonly packages: readonly PythonPackageMapping[];
|
|
28
|
-
readonly rewriteDependencies?: boolean;
|
|
29
|
-
readonly toml: TomlCodec;
|
|
30
|
-
readonly version: string;
|
|
31
|
-
},
|
|
32
|
-
): string;
|
|
33
|
-
|
|
34
|
-
/** Return the target-specific UniFFI generator executable name. */
|
|
35
|
-
export function pythonBindingGeneratorName(crate: string, platform: string): string;
|
|
36
|
-
|
|
37
|
-
/** Build the canonical UniFFI Python generator argument list. */
|
|
38
|
-
export function pythonBindingGeneratorArgs(options: {
|
|
39
|
-
readonly crate: string;
|
|
40
|
-
readonly library: string;
|
|
41
|
-
readonly output: string;
|
|
42
|
-
}): string[];
|
|
43
|
-
|
|
44
|
-
export interface InstalledPythonBindings {
|
|
45
|
-
readonly bindings: string;
|
|
46
|
-
readonly init: string;
|
|
47
|
-
readonly library: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Generate and place one Python binding module and its native library. */
|
|
51
|
-
export function installPythonBindings(options: {
|
|
52
|
-
readonly crate: string;
|
|
53
|
-
readonly library: string;
|
|
54
|
-
readonly module: string;
|
|
55
|
-
readonly packageRoot: string;
|
|
56
|
-
readonly platform: string;
|
|
57
|
-
readonly readonly?: boolean;
|
|
58
|
-
readonly run: (command: string, args: string[]) => void;
|
|
59
|
-
readonly targetDirectory: string;
|
|
60
|
-
}): InstalledPythonBindings;
|