@dbx-tools/projen 0.9.60 → 0.9.61

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 CHANGED
@@ -481,8 +481,22 @@ bun run release --publish pypi --no-docs
481
481
  bun run release --no-npm --no-local-publish
482
482
  bun run release --publish local --install never
483
483
  bun run release --local-registry false --local-pypi auto
484
+ bun run release --demo-deploy
485
+ bun run release --no-release-notes
484
486
  ```
485
487
 
488
+ `--demo-deploy` is off by default. After tagging and any local registry
489
+ publication it runs `bun run demo:deploy`, which stages the AppKit demo
490
+ and deploys it to the FEVM workspace. That step is local only and is not
491
+ recorded in the annotated tag.
492
+
493
+ After bump, release writes `docs/releases/vX.Y.Z.md` with `dbx genie exec -C
494
+ "$PWD" --sandbox read-only --ephemeral -o`. If Genie fails or writes an empty
495
+ file, the step keeps going with a short git-log summary. `--no-release-notes`
496
+ skips the file. Notes are committed with the version bump and are not recorded
497
+ in the annotated tag. They are skipped on `--no-bump` because that path
498
+ requires an already committed tree.
499
+
486
500
  `--install auto` keeps Projen's normal local dependency-install behavior;
487
501
  `always` performs a manifest-resolved install first, and `never` uses dependencies already
488
502
  installed while still regenerating versioned sources. `--no-validation` skips
@@ -553,6 +567,8 @@ release [options]
553
567
  | `--docs` | build and deploy docs for a selected scope |
554
568
  | `--no-docs` | skip documentation build and deployment |
555
569
  | `--no-validation` | skip optional release validation tasks; version/source checks remain mandatory |
570
+ | `--no-release-notes` | skip writing docs/releases notes (Genie and git-log fallback) |
571
+ | `--demo-deploy` | after tagging, stage and deploy the AppKit demo app (off by default) |
556
572
  | `--no-local-publish` | skip publishing to configured local registries |
557
573
  | `--local-registry <auto\|false\|url>` | local npm registry selection (default: "auto") |
558
574
  | `--local-pypi <auto\|false\|url>` | local devpi 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.60";
6
+ export const PACKAGE_VERSION = "0.9.61";
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.60",
34
- "@dbx-tools/path": "^0.9.60",
35
- "@dbx-tools/shared-core": "^0.9.60",
33
+ "@dbx-tools/core": "^0.9.61",
34
+ "@dbx-tools/path": "^0.9.61",
35
+ "@dbx-tools/shared-core": "^0.9.61",
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.60",
57
+ "version": "0.9.61",
58
58
  "types": "index.ts",
59
59
  "type": "module",
60
60
  "exports": {
@@ -69,6 +69,25 @@ export function captureTaskCommand(
69
69
  return result.exitCode === 0 ? (result.stdout?.trim() ?? "") : "";
70
70
  }
71
71
 
72
+ /** Run a command with inherited output and return whether it exited successfully. */
73
+ export function tryTaskCommand(
74
+ cwd: string,
75
+ command: string,
76
+ args: readonly string[],
77
+ options: TaskCommandOptions = {},
78
+ ): boolean {
79
+ return (
80
+ exec.spawnSync(executable(command), [...args], {
81
+ cwd,
82
+ env: options.env,
83
+ stdout: "inherit",
84
+ stderr: options.stderr ?? "inherit",
85
+ stdin: "ignore",
86
+ check: false,
87
+ }).exitCode === 0
88
+ );
89
+ }
90
+
72
91
  /** Whether a quiet, non-interactive command exits successfully. */
73
92
  export function taskCommandSucceeds(
74
93
  cwd: string,
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Write user-facing release notes under `docs/releases/` during `bun run release`.
3
+ *
4
+ * Prefers Genie Code (`dbx genie exec`). When that fails or writes an empty
5
+ * file, falls back to a short git-log summary so the release still commits notes.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { log } from "@dbx-tools/shared-core";
13
+
14
+ import { captureTaskCommand, tryTaskCommand } from "../src/_task-command.ts";
15
+
16
+ const logger = log.logger("projen:release-notes");
17
+
18
+ const GENIE_PROMPT =
19
+ "Compare HEAD and the working tree against the latest v* tag. Produce concise user-facing release notes. Ignore generated-file churn and test-only changes. Include breaking changes, fixes, and validation results. Do not modify files.";
20
+
21
+ /** Inputs for one notes write. */
22
+ export interface WriteReleaseNotesOptions {
23
+ readonly prefix: string;
24
+ readonly root: string;
25
+ readonly version: string;
26
+ /** Override Genie invocation; return false to force the git-log fallback. */
27
+ readonly runGenie?: (args: readonly string[]) => boolean;
28
+ }
29
+
30
+ /** Path of the notes file for one workspace version. */
31
+ export function releaseNotesPath(root: string, version: string): string {
32
+ return join(root, "docs", "releases", `v${version}.md`);
33
+ }
34
+
35
+ /** Short markdown notes from commit subjects since the previous matching tag. */
36
+ export function fallbackReleaseNotes(
37
+ version: string,
38
+ commits: readonly string[],
39
+ previousTag?: string,
40
+ ): string {
41
+ const range = previousTag ? `since ${previousTag}` : "in this release";
42
+ const items =
43
+ commits.length > 0
44
+ ? commits.map((subject) => `- ${subject}`).join("\n")
45
+ : "- Version bump and synchronized package metadata.";
46
+ return `# Release ${version}\n\ndbx-tools ${version} ${range}.\n\n${items}\n`;
47
+ }
48
+
49
+ /** Run Genie into `docs/releases/vX.Y.Z.md`, or write {@link fallbackReleaseNotes}. */
50
+ export function writeReleaseNotes(options: WriteReleaseNotesOptions): string {
51
+ const { prefix, root, version } = options;
52
+ const destination = releaseNotesPath(root, version);
53
+ mkdirSync(dirname(destination), { recursive: true });
54
+ const relative = join("docs", "releases", `v${version}.md`);
55
+ const genieArgs = [
56
+ "exec",
57
+ "-C",
58
+ root,
59
+ "--sandbox",
60
+ "read-only",
61
+ "--ephemeral",
62
+ "-o",
63
+ relative,
64
+ GENIE_PROMPT,
65
+ ] as const;
66
+ const ran =
67
+ options.runGenie?.([...genieArgs]) ??
68
+ tryTaskCommand(root, "bun", ["run", "dbx", "genie", ...genieArgs]);
69
+ if (ran && readNotes(destination)) {
70
+ logger.info("wrote Genie release notes", { path: relative });
71
+ return destination;
72
+ }
73
+ const previousTag = captureTaskCommand(root, "git", [
74
+ "describe",
75
+ "--tags",
76
+ "--abbrev=0",
77
+ `--match=${prefix}*`,
78
+ ]);
79
+ const logArgs = previousTag
80
+ ? ["log", "--no-merges", "--pretty=format:%s", `${previousTag}..HEAD`]
81
+ : ["log", "--no-merges", "--pretty=format:%s", "-15"];
82
+ const commits = captureTaskCommand(root, "git", logArgs)
83
+ .split("\n")
84
+ .map((line) => line.trim())
85
+ .filter(Boolean);
86
+ writeFileSync(destination, fallbackReleaseNotes(version, commits, previousTag || undefined));
87
+ logger.warn("Genie release notes unavailable; wrote git-log summary", { path: relative });
88
+ return destination;
89
+ }
90
+
91
+ function readNotes(path: string): string {
92
+ try {
93
+ return readFileSync(path, "utf8").trim();
94
+ } catch {
95
+ return "";
96
+ }
97
+ }
package/tasks/release.ts CHANGED
@@ -4,6 +4,7 @@ import * as projectUtils from "@dbx-tools/core/project-utils";
4
4
  import { log } from "@dbx-tools/shared-core";
5
5
  import { Command, Option } from "commander";
6
6
  import { publishLocalRelease } from "./local-publish.ts";
7
+ import { writeReleaseNotes } from "./release-notes.ts";
7
8
  import { assertReleaseVersion } from "./release-version.ts";
8
9
  import { captureTaskCommand, runTaskCommand, taskCommandSucceeds } from "../src/_task-command.ts";
9
10
  import {
@@ -105,6 +106,11 @@ export async function runRelease(
105
106
  readonly install?: ReleaseInstallMode;
106
107
  readonly pythonRoot?: string;
107
108
  readonly validationTasks?: readonly string[];
109
+ /** After tagging, stage and deploy the AppKit demo app. Off by default. */
110
+ readonly demoDeploy?: boolean;
111
+ /** Write `docs/releases/vX.Y.Z.md` via Genie, with a git-log fallback. On by default. */
112
+ readonly releaseNotes?: boolean;
113
+ readonly writeReleaseNotes?: typeof writeReleaseNotes;
108
114
  },
109
115
  ): Promise<string> {
110
116
  const { branch, prefix, remote, root } = options;
@@ -125,6 +131,9 @@ export async function runRelease(
125
131
  const version = readWorkspaceVersion(root);
126
132
  const tag = `${prefix}${version}`;
127
133
  assertReleaseVersion(version, { root, prefixes: [prefix], assertNext: true });
134
+ if ((options.releaseNotes ?? true) && (options.bump ?? true)) {
135
+ (options.writeReleaseNotes ?? writeReleaseNotes)({ prefix, root, version });
136
+ }
128
137
 
129
138
  if (taskCommandSucceeds(root, "git", ["rev-parse", "--verify", `refs/tags/${tag}`])) {
130
139
  throw new Error(`release tag ${tag} already exists`);
@@ -190,6 +199,10 @@ export async function runRelease(
190
199
  });
191
200
  }
192
201
 
202
+ if (options.demoDeploy) {
203
+ runTaskCommand(root, "bun", ["run", "demo:deploy"]);
204
+ }
205
+
193
206
  return tag;
194
207
  }
195
208
 
@@ -228,6 +241,8 @@ export function createReleaseCommand(): Command {
228
241
  "--no-validation",
229
242
  "skip optional release validation tasks; version/source checks remain mandatory",
230
243
  )
244
+ .option("--no-release-notes", "skip writing docs/releases notes (Genie and git-log fallback)")
245
+ .option("--demo-deploy", "after tagging, stage and deploy the AppKit demo app (off by default)")
231
246
  .option("--no-local-publish", "skip publishing to configured local registries")
232
247
  .option("--local-registry <auto|false|url>", "local npm registry selection", "auto")
233
248
  .option("--local-pypi <auto|false|url>", "local devpi registry selection", "auto")
@@ -245,6 +260,8 @@ export function createReleaseCommand(): Command {
245
260
  localPypi: string;
246
261
  install: ReleaseInstallMode;
247
262
  validate: string[];
263
+ demoDeploy?: boolean;
264
+ releaseNotes?: boolean;
248
265
  },
249
266
  ) => {
250
267
  await runRelease({
@@ -264,6 +281,8 @@ export function createReleaseCommand(): Command {
264
281
  docs: options.docs,
265
282
  validation: options.validation,
266
283
  validationTasks: options.validate,
284
+ demoDeploy: options.demoDeploy,
285
+ releaseNotes: options.releaseNotes,
267
286
  });
268
287
  },
269
288
  );