@dbx-tools/projen 0.9.38 → 0.9.43

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.
@@ -2,7 +2,7 @@
2
2
  /** Run the configured release transaction. */
3
3
  import * as projectUtils from "@dbx-tools/core/project-utils";
4
4
  import { log } from "@dbx-tools/shared-core";
5
- import { Command } from "commander";
5
+ import { Command, Option } from "commander";
6
6
  import { publishLocalRelease } from "./local-publish.ts";
7
7
  import { assertReleaseVersion } from "./release-version.ts";
8
8
  import {
@@ -10,6 +10,15 @@ import {
10
10
  runTaskCommand,
11
11
  taskCommandSucceeds,
12
12
  } from "../../src/_task-command.ts";
13
+ import {
14
+ RELEASE_INSTALL_MODES,
15
+ RELEASE_PUBLISH_TARGETS,
16
+ releasePublishesLocally,
17
+ releaseStepSelection,
18
+ releaseTagAnnotation,
19
+ type ReleaseInstallMode,
20
+ type ReleaseSelectionOptions,
21
+ } from "../../src/release-options.ts";
13
22
  import { readWorkspaceVersion } from "../../src/workspace-version.ts";
14
23
 
15
24
  const logger = log.logger("projen:release");
@@ -87,23 +96,34 @@ function prepareReleaseBranch(options: {
87
96
  runTaskCommand(root, "git", ["push", remote, `HEAD:${branch}`]);
88
97
  }
89
98
 
90
- export async function runRelease(options: {
91
- readonly root: string;
92
- readonly branch: string;
93
- readonly bump?: boolean;
94
- readonly prefix: string;
95
- readonly remote: string;
96
- readonly localPublish?: boolean;
97
- readonly pythonRoot?: string;
98
- readonly validationTasks?: readonly string[];
99
- }): Promise<string> {
99
+ export async function runRelease(
100
+ options: ReleaseSelectionOptions & {
101
+ readonly root: string;
102
+ readonly branch: string;
103
+ readonly bump?: boolean;
104
+ readonly prefix: string;
105
+ readonly remote: string;
106
+ readonly localPublish?: boolean;
107
+ readonly localRegistry?: string;
108
+ readonly localPypi?: string;
109
+ readonly install?: ReleaseInstallMode;
110
+ readonly pythonRoot?: string;
111
+ readonly validationTasks?: readonly string[];
112
+ },
113
+ ): Promise<string> {
100
114
  const { branch, prefix, remote, root } = options;
115
+ const selection = releaseStepSelection(options);
116
+ const install = options.install ?? "auto";
117
+
118
+ if (install === "always") runTaskCommand(root, "bun", ["install", "--frozen-lockfile"]);
101
119
 
102
120
  prepareReleaseBranch({ branch, remote, root });
103
121
 
104
122
  runTaskCommand(root, "git", ["fetch", remote, branch, "--tags"]);
105
123
  if (options.bump ?? true) {
106
- runTaskCommand(root, "bun", ["run", "bump"]);
124
+ runTaskCommand(root, "bun", ["run", "bump"], {
125
+ env: { ...process.env, DBX_TOOLS_RELEASE_INSTALL: install === "auto" ? "auto" : "never" },
126
+ });
107
127
  }
108
128
 
109
129
  const version = readWorkspaceVersion(root);
@@ -115,7 +135,7 @@ export async function runRelease(options: {
115
135
  }
116
136
 
117
137
  runTaskCommand(root, "bun", ["run", "version:check"]);
118
- for (const task of options.validationTasks ?? []) {
138
+ for (const task of selection.validation ? (options.validationTasks ?? []) : []) {
119
139
  runTaskCommand(root, "bun", ["run", task]);
120
140
  }
121
141
  const status = captureTaskCommand(
@@ -145,7 +165,13 @@ export async function runRelease(options: {
145
165
  );
146
166
  }
147
167
 
148
- runTaskCommand(root, "git", ["tag", "--annotate", tag, "--message", tag]);
168
+ runTaskCommand(root, "git", [
169
+ "tag",
170
+ "--annotate",
171
+ tag,
172
+ "--message",
173
+ releaseTagAnnotation(tag, selection),
174
+ ]);
149
175
  try {
150
176
  runTaskCommand(root, "git", ["push", "--atomic", remote, `HEAD:${branch}`, `refs/tags/${tag}`]);
151
177
  } catch (error) {
@@ -157,10 +183,10 @@ export async function runRelease(options: {
157
183
  // Restore local deploys: when a non-standard (loopback) npm or uv registry is
158
184
  // configured, publish the freshly tagged version to it directly. Detection and
159
185
  // publishing are owned by local-publish.ts; this no-ops on standard registries.
160
- if (options.localPublish ?? true) {
186
+ if ((options.localPublish ?? true) && releasePublishesLocally(options.publish)) {
161
187
  await publishLocalRelease({
162
- localPypi: "auto",
163
- localRegistry: "auto",
188
+ localPypi: options.pypi === false ? "false" : (options.localPypi ?? "auto"),
189
+ localRegistry: options.npm === false ? "false" : (options.localRegistry ?? "auto"),
164
190
  pythonRoot: options.pythonRoot ?? "packages/py",
165
191
  root,
166
192
  version,
@@ -170,8 +196,11 @@ export async function runRelease(options: {
170
196
  return tag;
171
197
  }
172
198
 
173
- export async function main(): Promise<void> {
174
- await new Command()
199
+ /** Build the release task's native parser without executing git or registry operations. */
200
+ export function createReleaseCommand(): Command {
201
+ return new Command()
202
+ .name("release")
203
+ .description("Prepare an annotated release and select its build and publication steps")
175
204
  .option("--root <path>", "repository root")
176
205
  .option("--branch <name>", "release branch", "main")
177
206
  .option("--prefix <prefix>", "release tag prefix", "v")
@@ -184,18 +213,43 @@ export async function main(): Promise<void> {
184
213
  [],
185
214
  )
186
215
  .option("--no-bump", "use an existing synchronized local version bump")
216
+ .addOption(
217
+ new Option("--publish <target>", "publication scope")
218
+ .choices([...RELEASE_PUBLISH_TARGETS])
219
+ .default("auto"),
220
+ )
221
+ .addOption(
222
+ new Option("--install <mode>", "local workspace dependency installation")
223
+ .choices([...RELEASE_INSTALL_MODES])
224
+ .default("auto"),
225
+ )
226
+ .option("--no-npm", "skip npm build and publication, including local npm publication")
227
+ .option("--no-pypi", "skip Python build and publication, including local Python publication")
228
+ .option("--docs", "build and deploy docs for a selected scope")
229
+ .option("--no-docs", "skip documentation build and deployment")
230
+ .option(
231
+ "--no-validation",
232
+ "skip optional release validation tasks; version/source checks remain mandatory",
233
+ )
187
234
  .option("--no-local-publish", "skip publishing to configured local registries")
235
+ .option("--local-registry <auto|false|url>", "local npm registry selection", "auto")
236
+ .option("--local-pypi <auto|false|url>", "local devpi registry selection", "auto")
188
237
  .action(
189
- async (options: {
190
- root?: string;
191
- branch: string;
192
- bump: boolean;
193
- prefix: string;
194
- remote: string;
195
- pythonRoot: string;
196
- localPublish: boolean;
197
- validate: string[];
198
- }) => {
238
+ async (
239
+ options: ReleaseSelectionOptions & {
240
+ root?: string;
241
+ branch: string;
242
+ bump: boolean;
243
+ prefix: string;
244
+ remote: string;
245
+ pythonRoot: string;
246
+ localPublish: boolean;
247
+ localRegistry: string;
248
+ localPypi: string;
249
+ install: ReleaseInstallMode;
250
+ validate: string[];
251
+ },
252
+ ) => {
199
253
  await runRelease({
200
254
  root: options.root ?? projectUtils.root() ?? process.cwd(),
201
255
  branch: options.branch,
@@ -204,9 +258,21 @@ export async function main(): Promise<void> {
204
258
  remote: options.remote,
205
259
  pythonRoot: options.pythonRoot,
206
260
  localPublish: options.localPublish,
261
+ localRegistry: options.localRegistry,
262
+ localPypi: options.localPypi,
263
+ install: options.install,
264
+ publish: options.publish,
265
+ npm: options.npm,
266
+ pypi: options.pypi,
267
+ docs: options.docs,
268
+ validation: options.validation,
207
269
  validationTasks: options.validate,
208
270
  });
209
271
  },
210
- )
211
- .parseAsync();
272
+ );
273
+ }
274
+
275
+ /** Execute the native release task parser. */
276
+ export async function main(): Promise<void> {
277
+ await createReleaseCommand().parseAsync();
212
278
  }
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env -S bun
2
+ /** Export the current annotated release tag's step selection to GitHub Actions. */
3
+ import { appendFileSync } from "node:fs";
4
+ import { parseArgs } from "node:util";
5
+ import { captureTaskCommand } from "../src/_task-command.ts";
6
+ import { parseReleaseTagAnnotation } from "../src/release-options.ts";
7
+
8
+ /** Verify and export a tag policy using the shared annotated-tag contract. */
9
+ export function main(args: string[] = process.argv.slice(2)): void {
10
+ const { values } = parseArgs({
11
+ args,
12
+ options: { tag: { type: "string" }, output: { type: "string" } },
13
+ strict: true,
14
+ });
15
+ if (!values.tag || !values.output) throw new Error("--tag and --output are required");
16
+ const annotation = captureTaskCommand(
17
+ process.cwd(),
18
+ "git",
19
+ ["for-each-ref", "--format=%(contents)", `refs/tags/${values.tag}`],
20
+ { check: true },
21
+ );
22
+ if (!annotation) throw new Error(`Release tag ${values.tag} has no annotation`);
23
+ const selection = parseReleaseTagAnnotation(annotation);
24
+ appendFileSync(
25
+ values.output,
26
+ Object.entries(selection)
27
+ .map(([name, enabled]) => `${name}=${enabled}\n`)
28
+ .join(""),
29
+ );
30
+ }
31
+
32
+ if (import.meta.main) main();
package/tasks/release.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env -S bun
2
2
  import { main } from "./lib/release.ts";
3
3
 
4
- export { main, runRelease } from "./lib/release.ts";
4
+ export { createReleaseCommand, main, runRelease } from "./lib/release.ts";
5
5
 
6
6
  if (import.meta.main) await main();