@dbx-tools/projen 0.9.61 → 0.9.64
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 +15 -8
- package/index.ts +1 -1
- package/package.json +4 -4
- package/src/python-node-bindings.ts +79 -3
- package/tasks/python-node-bindings-watch.ts +23 -25
- package/tasks/python-node-bindings.ts +4 -18
- package/tasks/release-notes.ts +9 -1
- package/tasks/release.ts +27 -1
package/README.md
CHANGED
|
@@ -205,9 +205,11 @@ The workspace creates one repository-wide binding lifecycle:
|
|
|
205
205
|
- `python-node-bindings` generates every configured bridge.
|
|
206
206
|
- `python-node-bindings:check` verifies generated files and fails on stale
|
|
207
207
|
`_generated/node` directories.
|
|
208
|
-
- `python-node-bindings:watch` watches
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
- `python-node-bindings:watch` watches only configured module entrypoints and
|
|
209
|
+
their transitive workspace source imports. Tests, compiled output, generated
|
|
210
|
+
bindings, and unrelated modules do not trigger regeneration or acquire the
|
|
211
|
+
workspace mutation lock. Binding-manifest and generator changes regenerate
|
|
212
|
+
the affected projects and restart the watcher when its input graph changes.
|
|
211
213
|
|
|
212
214
|
When a runtime owner is configured, the workspace also creates one shared
|
|
213
215
|
runtime lifecycle:
|
|
@@ -483,6 +485,7 @@ bun run release --publish local --install never
|
|
|
483
485
|
bun run release --local-registry false --local-pypi auto
|
|
484
486
|
bun run release --demo-deploy
|
|
485
487
|
bun run release --no-release-notes
|
|
488
|
+
bun run release --release-notes-instructions "Focus on operator-visible changes."
|
|
486
489
|
```
|
|
487
490
|
|
|
488
491
|
`--demo-deploy` is off by default. After tagging and any local registry
|
|
@@ -493,14 +496,17 @@ recorded in the annotated tag.
|
|
|
493
496
|
After bump, release writes `docs/releases/vX.Y.Z.md` with `dbx genie exec -C
|
|
494
497
|
"$PWD" --sandbox read-only --ephemeral -o`. If Genie fails or writes an empty
|
|
495
498
|
file, the step keeps going with a short git-log summary. `--no-release-notes`
|
|
496
|
-
skips the file.
|
|
497
|
-
|
|
498
|
-
|
|
499
|
+
skips the file. `--release-notes-instructions` appends run-specific guidance to
|
|
500
|
+
the standard Genie prompt. Notes are committed with the version bump and are not
|
|
501
|
+
recorded in the annotated tag. They are skipped on `--no-bump` because that
|
|
502
|
+
path requires an already committed tree.
|
|
499
503
|
|
|
500
504
|
`--install auto` keeps Projen's normal local dependency-install behavior;
|
|
501
505
|
`always` performs a manifest-resolved install first, and `never` uses dependencies already
|
|
502
|
-
installed while still regenerating versioned sources.
|
|
503
|
-
|
|
506
|
+
installed while still regenerating versioned sources. Local release validation
|
|
507
|
+
runs `eslint:fix` before branch preparation and bump, then runs the selected
|
|
508
|
+
fail-closed tasks; CI remains check-only.
|
|
509
|
+
`--no-validation` skips optional task checks locally and in CI, not version or immutable-source checks.
|
|
504
510
|
`--no-docs` skips building and deploying the site, not package README validation.
|
|
505
511
|
Scopes select artifacts, not the normal commit and tag-push transaction.
|
|
506
512
|
The annotated tag records CI step selections, so publishing a selected scope does
|
|
@@ -568,6 +574,7 @@ release [options]
|
|
|
568
574
|
| `--no-docs` | skip documentation build and deployment |
|
|
569
575
|
| `--no-validation` | skip optional release validation tasks; version/source checks remain mandatory |
|
|
570
576
|
| `--no-release-notes` | skip writing docs/releases notes (Genie and git-log fallback) |
|
|
577
|
+
| `--release-notes-instructions <text>` | append custom instructions to the Genie release-notes prompt |
|
|
571
578
|
| `--demo-deploy` | after tagging, stage and deploy the AppKit demo app (off by default) |
|
|
572
579
|
| `--no-local-publish` | skip publishing to configured local registries |
|
|
573
580
|
| `--local-registry <auto\|false\|url>` | local npm registry selection (default: "auto") |
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
|
|
6
|
-
export const PACKAGE_VERSION = "0.9.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.64";
|
|
7
7
|
export * as barrels from "./src/barrels.ts";
|
|
8
8
|
export * as bunApp from "./src/bun-app.ts";
|
|
9
9
|
export * as bunWorkflow from "./src/bun-workflow.ts";
|
package/package.json
CHANGED
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.7.0",
|
|
33
|
-
"@dbx-tools/core": "^0.9.
|
|
34
|
-
"@dbx-tools/path": "^0.9.
|
|
35
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
+
"@dbx-tools/core": "^0.9.64",
|
|
34
|
+
"@dbx-tools/path": "^0.9.64",
|
|
35
|
+
"@dbx-tools/shared-core": "^0.9.64",
|
|
36
36
|
"commander": "^15.0.0",
|
|
37
37
|
"concurrently": "^10.0.3",
|
|
38
38
|
"constructs": "^10.6.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"main": "index.ts",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
|
-
"version": "0.9.
|
|
57
|
+
"version": "0.9.64",
|
|
58
58
|
"types": "index.ts",
|
|
59
59
|
"type": "module",
|
|
60
60
|
"exports": {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/** Shared pyproject-backed configuration for PythonMonkey Node bindings. */
|
|
2
|
-
import { readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { readFileSync, realpathSync, rmSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { find } from "@dbx-tools/path";
|
|
6
6
|
import { object, stringUtils } from "@dbx-tools/shared-core";
|
|
7
7
|
import { parse } from "smol-toml";
|
|
8
|
+
import ts from "typescript";
|
|
8
9
|
import { runTaskCommand } from "./_task-command.ts";
|
|
9
10
|
import { resolveRepoRoot, workspaceDependencyDirectories } from "./packages.ts";
|
|
10
11
|
|
|
@@ -29,6 +30,7 @@ export interface ResolvedPythonNodeBindings {
|
|
|
29
30
|
readonly projectDirectory: string;
|
|
30
31
|
readonly pyproject: string;
|
|
31
32
|
readonly runtimeOutput: string;
|
|
33
|
+
readonly watchInputs: readonly string[];
|
|
32
34
|
readonly workspaceDirectories: readonly string[];
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -152,7 +154,7 @@ export function resolvePythonNodeBindings(
|
|
|
152
154
|
}
|
|
153
155
|
const bindingName = nodePackageName(packageName);
|
|
154
156
|
const bindingDirectory = join(moduleDirectory, "_generated", "node", bindingName);
|
|
155
|
-
|
|
157
|
+
const resolved = {
|
|
156
158
|
project,
|
|
157
159
|
projectDirectory,
|
|
158
160
|
pyproject,
|
|
@@ -177,11 +179,85 @@ export function resolvePythonNodeBindings(
|
|
|
177
179
|
}),
|
|
178
180
|
workspaceDirectories: workspaceDependencyDirectories(packageName, root),
|
|
179
181
|
};
|
|
182
|
+
return {
|
|
183
|
+
...resolved,
|
|
184
|
+
watchInputs: bindingSourceInputs(root, resolved),
|
|
185
|
+
};
|
|
180
186
|
});
|
|
181
187
|
validateBindings(configs);
|
|
182
188
|
return configs;
|
|
183
189
|
}
|
|
184
190
|
|
|
191
|
+
type BindingSourceConfig = Omit<ResolvedPythonNodeBindings, "watchInputs">;
|
|
192
|
+
|
|
193
|
+
/** Transitive workspace source files reachable from configured binding modules. */
|
|
194
|
+
function bindingSourceInputs(root: string, config: BindingSourceConfig): string[] {
|
|
195
|
+
const allowed = config.workspaceDirectories.map(canonicalPath);
|
|
196
|
+
const packageEntrypoint = Bun.resolveSync(config.entrypoint, config.projectDirectory);
|
|
197
|
+
const entrypoints =
|
|
198
|
+
config.modules.length > 0
|
|
199
|
+
? config.modules.map((module) => resolvePythonNodeBindingModule(packageEntrypoint, module))
|
|
200
|
+
: [packageEntrypoint];
|
|
201
|
+
const pending = [
|
|
202
|
+
...entrypoints,
|
|
203
|
+
...config.functionOverrides.map(({ handlerFile }) => handlerFile),
|
|
204
|
+
];
|
|
205
|
+
const visited = new Set<string>();
|
|
206
|
+
while (pending.length > 0) {
|
|
207
|
+
const candidate = canonicalPath(pending.pop()!);
|
|
208
|
+
if (
|
|
209
|
+
visited.has(candidate) ||
|
|
210
|
+
!allowed.some((directory) => containsResolvedPath(directory, candidate))
|
|
211
|
+
) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
visited.add(candidate);
|
|
215
|
+
let source: string;
|
|
216
|
+
try {
|
|
217
|
+
source = readFileSync(candidate, "utf8");
|
|
218
|
+
} catch {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const dependencies = ts.preProcessFile(source, true, true).importedFiles;
|
|
222
|
+
for (const dependency of dependencies) {
|
|
223
|
+
try {
|
|
224
|
+
pending.push(Bun.resolveSync(dependency.fileName, dirname(candidate)));
|
|
225
|
+
} catch {
|
|
226
|
+
// Built-ins and external packages are outside the workspace source graph.
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return [...visited].sort().map((path) => relative(root, path));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Resolve one configured namespace from a package entrypoint barrel. */
|
|
234
|
+
export function resolvePythonNodeBindingModule(entrypoint: string, module: string): string {
|
|
235
|
+
if (!/^[$A-Z_a-z][$\w]*$/.test(module)) {
|
|
236
|
+
throw new Error(`Node binding module must be a TypeScript identifier: ${module}`);
|
|
237
|
+
}
|
|
238
|
+
const source = readFileSync(entrypoint, "utf8");
|
|
239
|
+
const escaped = module.replaceAll(/[$()*+.?[\\\]^{|}]/g, "\\$&");
|
|
240
|
+
const match = new RegExp(`export\\s+\\*\\s+as\\s+${escaped}\\s+from\\s+["']([^"']+)["']`).exec(
|
|
241
|
+
source,
|
|
242
|
+
);
|
|
243
|
+
if (!match?.[1]) {
|
|
244
|
+
throw new Error(`${entrypoint} does not export namespace ${module}`);
|
|
245
|
+
}
|
|
246
|
+
return Bun.resolveSync(match[1], dirname(entrypoint));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function canonicalPath(path: string): string {
|
|
250
|
+
try {
|
|
251
|
+
return realpathSync(path);
|
|
252
|
+
} catch {
|
|
253
|
+
return resolve(path);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function containsResolvedPath(parent: string, candidate: string): boolean {
|
|
258
|
+
return candidate === parent || candidate.startsWith(`${parent}${sep}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
185
261
|
/** All paths that can change a workspace-backed generated Node runtime. */
|
|
186
262
|
export function pythonNodeBindingWatchInputs(config: ResolvedPythonNodeBindings): string[] {
|
|
187
263
|
if (config.workspaceDirectories.length === 0) {
|
|
@@ -189,7 +265,7 @@ export function pythonNodeBindingWatchInputs(config: ResolvedPythonNodeBindings)
|
|
|
189
265
|
}
|
|
190
266
|
return [
|
|
191
267
|
config.pyproject,
|
|
192
|
-
...config.
|
|
268
|
+
...config.watchInputs,
|
|
193
269
|
...config.functionOverrides.map(({ handlerFile }) => handlerFile),
|
|
194
270
|
];
|
|
195
271
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
2
|
/** Keep every configured Python Node bridge current with one repository watcher. */
|
|
3
|
-
import {
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { log
|
|
5
|
+
import { log } from "@dbx-tools/shared-core";
|
|
6
6
|
import { runTaskCommand } from "../src/_task-command.ts";
|
|
7
7
|
import { repoRoot } from "../src/packages.ts";
|
|
8
8
|
import {
|
|
@@ -12,36 +12,24 @@ import {
|
|
|
12
12
|
resolveAllPythonNodeBindings,
|
|
13
13
|
type ResolvedPythonNodeBindings,
|
|
14
14
|
} from "../src/python-node-bindings.ts";
|
|
15
|
-
import { watchLoop
|
|
15
|
+
import { watchLoop } from "../src/watch.ts";
|
|
16
16
|
import { withWorkspaceMutationLock } from "../src/workspace-lock.ts";
|
|
17
17
|
|
|
18
18
|
const logger = log.logger("projen:python-node-bindings-watch");
|
|
19
19
|
const generator = fileURLToPath(new URL("./python-node-bindings.ts", import.meta.url));
|
|
20
|
+
const watcher = fileURLToPath(import.meta.url);
|
|
21
|
+
const bindingSource = fileURLToPath(new URL("../src/python-node-bindings.ts", import.meta.url));
|
|
22
|
+
const toolingInputs = [generator, watcher, bindingSource];
|
|
20
23
|
|
|
21
24
|
function generate(project: string): void {
|
|
22
25
|
runTaskCommand(repoRoot, "bun", [generator, "--root", repoRoot, "--project", project]);
|
|
23
26
|
}
|
|
24
27
|
|
|
25
|
-
function containsPath(parent: string, candidate: string): boolean {
|
|
26
|
-
const resolvedParent = resolve(parent);
|
|
27
|
-
const resolvedCandidate = resolve(candidate);
|
|
28
|
-
return (
|
|
29
|
-
resolvedCandidate === resolvedParent || resolvedCandidate.startsWith(`${resolvedParent}${sep}`)
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
28
|
/**
|
|
34
|
-
* Watch
|
|
35
|
-
* exceptional inputs such as function overrides that live outside those roots.
|
|
29
|
+
* Watch only configured manifests and transitive workspace source inputs.
|
|
36
30
|
*/
|
|
37
31
|
function watchPaths(configs: readonly ResolvedPythonNodeBindings[]): string[] {
|
|
38
|
-
|
|
39
|
-
const pythonRoots = configs.map(({ projectDirectory }) => dirname(projectDirectory));
|
|
40
|
-
const covered = [...roots, ...pythonRoots];
|
|
41
|
-
const explicitInputs = configs
|
|
42
|
-
.flatMap(pythonNodeBindingWatchInputs)
|
|
43
|
-
.filter((input) => !covered.some((root) => containsPath(root, input)));
|
|
44
|
-
return [...object.sequence(covered, explicitInputs).distinct()].sort();
|
|
32
|
+
return [...new Set([...configs.flatMap(pythonNodeBindingWatchInputs), ...toolingInputs])].sort();
|
|
45
33
|
}
|
|
46
34
|
|
|
47
35
|
function selection(
|
|
@@ -51,14 +39,20 @@ function selection(
|
|
|
51
39
|
readonly all: boolean;
|
|
52
40
|
readonly configs: ResolvedPythonNodeBindings[];
|
|
53
41
|
readonly projects: string[];
|
|
42
|
+
readonly restart: boolean;
|
|
54
43
|
} {
|
|
55
44
|
const configs = resolveAllPythonNodeBindings(repoRoot);
|
|
56
45
|
const manifests = new Set([...previous, ...configs].map(({ pyproject }) => resolve(pyproject)));
|
|
57
|
-
const all = changed.some(
|
|
46
|
+
const all = changed.some(
|
|
47
|
+
(path) =>
|
|
48
|
+
manifests.has(resolve(path)) ||
|
|
49
|
+
toolingInputs.some((input) => resolve(input) === resolve(path)),
|
|
50
|
+
);
|
|
58
51
|
return {
|
|
59
52
|
all,
|
|
60
53
|
configs,
|
|
61
54
|
projects: all ? [] : affectedPythonNodeBindingProjects(repoRoot, configs, changed),
|
|
55
|
+
restart: watchPaths(previous).join("\n") !== watchPaths(configs).join("\n"),
|
|
62
56
|
};
|
|
63
57
|
}
|
|
64
58
|
|
|
@@ -77,11 +71,15 @@ export async function main(): Promise<void> {
|
|
|
77
71
|
if (next.all) {
|
|
78
72
|
generatePythonNodeBindings(repoRoot);
|
|
79
73
|
logger.success("regenerated all Python Node bindings");
|
|
80
|
-
|
|
74
|
+
} else {
|
|
75
|
+
for (const project of next.projects) {
|
|
76
|
+
generate(project);
|
|
77
|
+
logger.success(`regenerated ${project} Node bindings`);
|
|
78
|
+
}
|
|
81
79
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
80
|
+
if (next.restart) {
|
|
81
|
+
logger.info("binding inputs changed; restarting watcher");
|
|
82
|
+
setTimeout(() => process.exit(0), 0);
|
|
85
83
|
}
|
|
86
84
|
},
|
|
87
85
|
{
|
|
@@ -16,6 +16,7 @@ import { publicFunctionExports, publicNamespaceExports } from "../src/module-exp
|
|
|
16
16
|
import { resolveRepoRoot } from "../src/packages.ts";
|
|
17
17
|
import {
|
|
18
18
|
generatePythonNodeBindings,
|
|
19
|
+
resolvePythonNodeBindingModule,
|
|
19
20
|
resolvePythonNodeBindings,
|
|
20
21
|
type ResolvedPythonNodeBindings,
|
|
21
22
|
type ResolvedPythonNodeFunctionOverride,
|
|
@@ -139,7 +140,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
139
140
|
);
|
|
140
141
|
const packageEntrypoint = Bun.resolveSync(config.entrypoint, projectDirectory);
|
|
141
142
|
const entrypoint = selectedModule
|
|
142
|
-
?
|
|
143
|
+
? resolvePythonNodeBindingModule(packageEntrypoint, selectedModule)
|
|
143
144
|
: packageEntrypoint;
|
|
144
145
|
const functions = publicFunctionExports(entrypoint);
|
|
145
146
|
if (functions.length === 0) {
|
|
@@ -217,7 +218,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
217
218
|
return binding.modules.length > 0
|
|
218
219
|
? binding.modules.map((module) => ({
|
|
219
220
|
key: runtimeModuleName(binding, module),
|
|
220
|
-
entrypoint:
|
|
221
|
+
entrypoint: resolvePythonNodeBindingModule(entrypoint, module),
|
|
221
222
|
}))
|
|
222
223
|
: [{ key: runtimeModuleName(binding), entrypoint }];
|
|
223
224
|
});
|
|
@@ -310,7 +311,7 @@ export async function main(args: string[] = process.argv.slice(2)): Promise<void
|
|
|
310
311
|
const packageEntrypoint = Bun.resolveSync(candidate.entrypoint, candidate.projectDirectory);
|
|
311
312
|
const modules = publicNamespaceExports(packageEntrypoint).filter(
|
|
312
313
|
(module) =>
|
|
313
|
-
publicFunctionExports(
|
|
314
|
+
publicFunctionExports(resolvePythonNodeBindingModule(packageEntrypoint, module)).length > 0,
|
|
314
315
|
);
|
|
315
316
|
return { ...candidate, modules };
|
|
316
317
|
}
|
|
@@ -1220,19 +1221,4 @@ function pythonRuntimeLoader(source: string): string {
|
|
|
1220
1221
|
].join("\n");
|
|
1221
1222
|
}
|
|
1222
1223
|
|
|
1223
|
-
function resolveGeneratedModule(entrypoint: string, module: string): string {
|
|
1224
|
-
if (!/^[$A-Z_a-z][$\w]*$/.test(module)) {
|
|
1225
|
-
throw new Error(`Node binding module must be a TypeScript identifier: ${module}`);
|
|
1226
|
-
}
|
|
1227
|
-
const source = readFileSync(entrypoint, "utf8");
|
|
1228
|
-
const escaped = module.replaceAll(/[$()*+.?[\\\]^{|}]/g, "\\$&");
|
|
1229
|
-
const match = new RegExp(`export\\s+\\*\\s+as\\s+${escaped}\\s+from\\s+["']([^"']+)["']`).exec(
|
|
1230
|
-
source,
|
|
1231
|
-
);
|
|
1232
|
-
if (!match?.[1]) {
|
|
1233
|
-
throw new Error(`${entrypoint} does not export namespace ${module}`);
|
|
1234
|
-
}
|
|
1235
|
-
return Bun.resolveSync(match[1], dirname(entrypoint));
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
1224
|
if (import.meta.main) await main();
|
package/tasks/release-notes.ts
CHANGED
|
@@ -20,6 +20,8 @@ const GENIE_PROMPT =
|
|
|
20
20
|
|
|
21
21
|
/** Inputs for one notes write. */
|
|
22
22
|
export interface WriteReleaseNotesOptions {
|
|
23
|
+
/** Extra agent instructions appended after the standard release-note requirements. */
|
|
24
|
+
readonly instructions?: string;
|
|
23
25
|
readonly prefix: string;
|
|
24
26
|
readonly root: string;
|
|
25
27
|
readonly version: string;
|
|
@@ -27,6 +29,12 @@ export interface WriteReleaseNotesOptions {
|
|
|
27
29
|
readonly runGenie?: (args: readonly string[]) => boolean;
|
|
28
30
|
}
|
|
29
31
|
|
|
32
|
+
/** Compose the stable release-note prompt with optional caller guidance. */
|
|
33
|
+
export function releaseNotesPrompt(instructions?: string): string {
|
|
34
|
+
const additional = instructions?.trim();
|
|
35
|
+
return additional ? `${GENIE_PROMPT}\n\nAdditional instructions:\n${additional}` : GENIE_PROMPT;
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
/** Path of the notes file for one workspace version. */
|
|
31
39
|
export function releaseNotesPath(root: string, version: string): string {
|
|
32
40
|
return join(root, "docs", "releases", `v${version}.md`);
|
|
@@ -61,7 +69,7 @@ export function writeReleaseNotes(options: WriteReleaseNotesOptions): string {
|
|
|
61
69
|
"--ephemeral",
|
|
62
70
|
"-o",
|
|
63
71
|
relative,
|
|
64
|
-
|
|
72
|
+
releaseNotesPrompt(options.instructions),
|
|
65
73
|
] as const;
|
|
66
74
|
const ran =
|
|
67
75
|
options.runGenie?.([...genieArgs]) ??
|
package/tasks/release.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S bun
|
|
2
2
|
/** Run the configured release transaction. */
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
3
5
|
import * as projectUtils from "@dbx-tools/core/project-utils";
|
|
4
6
|
import { log } from "@dbx-tools/shared-core";
|
|
5
7
|
import { Command, Option } from "commander";
|
|
@@ -20,6 +22,14 @@ import { readWorkspaceVersion } from "../src/workspace-version.ts";
|
|
|
20
22
|
|
|
21
23
|
const logger = log.logger("projen:release");
|
|
22
24
|
|
|
25
|
+
/** Whether the root manifest exposes one repository-owned script. */
|
|
26
|
+
function hasPackageScript(root: string, name: string): boolean {
|
|
27
|
+
const manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8")) as {
|
|
28
|
+
scripts?: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
return Boolean(manifest.scripts?.[name]);
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
function gitHead(root: string, ref: string): string | undefined {
|
|
24
34
|
if (!taskCommandSucceeds(root, "git", ["rev-parse", "--verify", ref])) return undefined;
|
|
25
35
|
return captureTaskCommand(root, "git", ["rev-parse", ref], { check: true });
|
|
@@ -110,6 +120,8 @@ export async function runRelease(
|
|
|
110
120
|
readonly demoDeploy?: boolean;
|
|
111
121
|
/** Write `docs/releases/vX.Y.Z.md` via Genie, with a git-log fallback. On by default. */
|
|
112
122
|
readonly releaseNotes?: boolean;
|
|
123
|
+
/** Additional instructions appended to the standard Genie release-notes prompt. */
|
|
124
|
+
readonly releaseNotesInstructions?: string;
|
|
113
125
|
readonly writeReleaseNotes?: typeof writeReleaseNotes;
|
|
114
126
|
},
|
|
115
127
|
): Promise<string> {
|
|
@@ -118,6 +130,9 @@ export async function runRelease(
|
|
|
118
130
|
const install = options.install ?? "auto";
|
|
119
131
|
|
|
120
132
|
if (install === "always") runTaskCommand(root, "bun", ["install"]);
|
|
133
|
+
if (selection.validation && hasPackageScript(root, "eslint:fix")) {
|
|
134
|
+
runTaskCommand(root, "bun", ["run", "eslint:fix"]);
|
|
135
|
+
}
|
|
121
136
|
|
|
122
137
|
prepareReleaseBranch({ branch, remote, root });
|
|
123
138
|
|
|
@@ -132,7 +147,12 @@ export async function runRelease(
|
|
|
132
147
|
const tag = `${prefix}${version}`;
|
|
133
148
|
assertReleaseVersion(version, { root, prefixes: [prefix], assertNext: true });
|
|
134
149
|
if ((options.releaseNotes ?? true) && (options.bump ?? true)) {
|
|
135
|
-
(options.writeReleaseNotes ?? writeReleaseNotes)({
|
|
150
|
+
(options.writeReleaseNotes ?? writeReleaseNotes)({
|
|
151
|
+
prefix,
|
|
152
|
+
root,
|
|
153
|
+
version,
|
|
154
|
+
instructions: options.releaseNotesInstructions,
|
|
155
|
+
});
|
|
136
156
|
}
|
|
137
157
|
|
|
138
158
|
if (taskCommandSucceeds(root, "git", ["rev-parse", "--verify", `refs/tags/${tag}`])) {
|
|
@@ -242,6 +262,10 @@ export function createReleaseCommand(): Command {
|
|
|
242
262
|
"skip optional release validation tasks; version/source checks remain mandatory",
|
|
243
263
|
)
|
|
244
264
|
.option("--no-release-notes", "skip writing docs/releases notes (Genie and git-log fallback)")
|
|
265
|
+
.option(
|
|
266
|
+
"--release-notes-instructions <text>",
|
|
267
|
+
"append custom instructions to the Genie release-notes prompt",
|
|
268
|
+
)
|
|
245
269
|
.option("--demo-deploy", "after tagging, stage and deploy the AppKit demo app (off by default)")
|
|
246
270
|
.option("--no-local-publish", "skip publishing to configured local registries")
|
|
247
271
|
.option("--local-registry <auto|false|url>", "local npm registry selection", "auto")
|
|
@@ -262,6 +286,7 @@ export function createReleaseCommand(): Command {
|
|
|
262
286
|
validate: string[];
|
|
263
287
|
demoDeploy?: boolean;
|
|
264
288
|
releaseNotes?: boolean;
|
|
289
|
+
releaseNotesInstructions?: string;
|
|
265
290
|
},
|
|
266
291
|
) => {
|
|
267
292
|
await runRelease({
|
|
@@ -283,6 +308,7 @@ export function createReleaseCommand(): Command {
|
|
|
283
308
|
validationTasks: options.validate,
|
|
284
309
|
demoDeploy: options.demoDeploy,
|
|
285
310
|
releaseNotes: options.releaseNotes,
|
|
311
|
+
releaseNotesInstructions: options.releaseNotesInstructions,
|
|
286
312
|
});
|
|
287
313
|
},
|
|
288
314
|
);
|