@dbx-tools/projen 0.9.30 → 0.9.32

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/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.30";
6
+ export const PACKAGE_VERSION = "0.9.32";
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
@@ -34,9 +34,9 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@clack/prompts": "^1.7.0",
37
- "@dbx-tools/core": "^0.9.30",
38
- "@dbx-tools/path": "^0.9.30",
39
- "@dbx-tools/shared-core": "^0.9.30",
37
+ "@dbx-tools/core": "^0.9.32",
38
+ "@dbx-tools/path": "^0.9.32",
39
+ "@dbx-tools/shared-core": "^0.9.32",
40
40
  "commander": "^15.0.0",
41
41
  "concurrently": "^10.0.3",
42
42
  "constructs": "^10.6.0",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "main": "index.ts",
58
58
  "license": "Apache-2.0",
59
- "version": "0.9.30",
59
+ "version": "0.9.32",
60
60
  "types": "index.ts",
61
61
  "type": "module",
62
62
  "exports": {
@@ -28,8 +28,14 @@ export function resolveLocalRegistry(value: string): string | undefined {
28
28
  return trimmed;
29
29
  }
30
30
 
31
+ /** Outcome of a local publish: which ecosystems were deployed to a local registry. */
32
+ export interface LocalPublishResult {
33
+ readonly npm: boolean;
34
+ readonly python: boolean;
35
+ }
36
+
31
37
  /** Build and publish the current workspace directly to local registries. */
32
- export async function publishLocalRelease(options: LocalPublishOptions): Promise<void> {
38
+ export async function publishLocalRelease(options: LocalPublishOptions): Promise<LocalPublishResult> {
33
39
  const root = resolve(options.root);
34
40
  const localRegistry = resolveLocalRegistry(options.localRegistry);
35
41
  const activeIndexes = activePythonIndexes();
@@ -76,8 +82,14 @@ export async function publishLocalRelease(options: LocalPublishOptions): Promise
76
82
  );
77
83
  }
78
84
 
85
+ if (publishes.length === 0) {
86
+ logger.info("no local registries configured; skipping local publish");
87
+ return { npm: false, python: false };
88
+ }
89
+
79
90
  await Promise.all(publishes);
80
91
  logger.success(`published local workspace ${options.version}`);
92
+ return { npm: Boolean(localRegistry), python: Boolean(localPypi) };
81
93
  }
82
94
 
83
95
  if (import.meta.main) {
@@ -88,20 +100,21 @@ if (import.meta.main) {
88
100
  .option("--local-registry <url>", "local npm registry", "auto")
89
101
  .option("--local-pypi <url>", "local devpi index", "auto")
90
102
  .action(
91
- (options: {
103
+ async (options: {
92
104
  version: string;
93
105
  root?: string;
94
106
  pythonRoot: string;
95
107
  localRegistry: string;
96
108
  localPypi: string;
97
- }) =>
98
- publishLocalRelease({
109
+ }) => {
110
+ await publishLocalRelease({
99
111
  localPypi: options.localPypi,
100
112
  localRegistry: options.localRegistry,
101
113
  pythonRoot: options.pythonRoot,
102
114
  root: options.root ?? projectUtils.root() ?? process.cwd(),
103
115
  version: options.version,
104
- }),
116
+ });
117
+ },
105
118
  )
106
119
  .parseAsync();
107
120
  }
package/tasks/release.ts CHANGED
@@ -10,34 +10,26 @@ import {
10
10
  runTaskCommand,
11
11
  } from "../src/_task-command.ts";
12
12
  import { assertReleaseVersion } from "./release-version.ts";
13
+ import { publishLocalRelease } from "./local-publish.ts";
13
14
  import { readWorkspaceVersion } from "../src/workspace-version.ts";
14
15
 
15
16
  const logger = log.logger("projen:release");
16
17
 
17
- export function runRelease(options: {
18
+ export async function runRelease(options: {
18
19
  readonly root: string;
19
20
  readonly branch: string;
20
21
  readonly bump?: boolean;
21
22
  readonly prefix: string;
22
23
  readonly remote: string;
23
- }): string {
24
+ readonly localPublish?: boolean;
25
+ readonly pythonRoot?: string;
26
+ }): Promise<string> {
24
27
  const { branch, prefix, remote, root } = options;
25
- const currentBranch = captureGitTaskCommand(root, ["branch", "--show-current"], { check: true });
26
- if (currentBranch !== branch) {
27
- throw new Error(
28
- `release must run on ${branch}, current branch is ${currentBranch || "detached"}`,
29
- );
30
- }
31
28
 
29
+ // Release runs from any branch: whatever is checked out is fast-forwarded onto
30
+ // `branch`. The working tree is committed into the release commit unless it is
31
+ // already clean - the clean tree is a convenience, not a precondition.
32
32
  if (options.bump ?? true) {
33
- const status = captureGitTaskCommand(
34
- root,
35
- ["status", "--porcelain=v1", "--untracked-files=all"],
36
- { check: true },
37
- );
38
- if (status) {
39
- throw new Error("release with automatic bump requires a clean working tree");
40
- }
41
33
  runTaskCommand(root, "bun", ["run", "bump"]);
42
34
  }
43
35
 
@@ -64,36 +56,69 @@ export function runRelease(options: {
64
56
  runGitTaskCommand(root, ["commit", "-m", `chore(release): ${version}`]);
65
57
  }
66
58
 
59
+ // Fast-forward only: HEAD must already contain the remote branch tip, otherwise
60
+ // the push would be a non-fast-forward (an unclean merge) and we fail instead
61
+ // of force-pushing. This holds regardless of which branch is checked out.
67
62
  const remoteHead = captureGitTaskCommand(root, ["rev-parse", `${remote}/${branch}`], {
68
63
  check: true,
69
64
  });
70
65
  const head = captureGitTaskCommand(root, ["rev-parse", "HEAD"], { check: true });
71
66
  if (!gitTaskCommandSucceeds(root, ["merge-base", "--is-ancestor", remoteHead, head])) {
72
- throw new Error(`${branch} must contain ${remote}/${branch} before release`);
67
+ throw new Error(
68
+ `cannot fast-forward ${remote}/${branch} to HEAD; ${branch} has diverged (rebase before release)`,
69
+ );
73
70
  }
74
71
 
75
72
  runGitTaskCommand(root, ["push", remote, `HEAD:${branch}`]);
76
73
  runGitTaskCommand(root, ["tag", "--annotate", tag, "--message", tag]);
77
74
  runGitTaskCommand(root, ["push", remote, `refs/tags/${tag}`]);
78
75
  logger.success(`released ${tag}`, { sha: head });
76
+
77
+ // Restore local deploys: when a non-standard (loopback) npm or uv registry is
78
+ // configured, publish the freshly tagged version to it directly. Detection and
79
+ // publishing are owned by local-publish.ts; this no-ops on standard registries.
80
+ if (options.localPublish ?? true) {
81
+ await publishLocalRelease({
82
+ localPypi: "auto",
83
+ localRegistry: "auto",
84
+ pythonRoot: options.pythonRoot ?? "packages/py",
85
+ root,
86
+ version,
87
+ });
88
+ }
89
+
79
90
  return tag;
80
91
  }
81
92
 
82
93
  if (import.meta.main) {
83
- new Command()
94
+ await new Command()
84
95
  .option("--root <path>", "repository root")
85
96
  .option("--branch <name>", "release branch", "main")
86
97
  .option("--prefix <prefix>", "release tag prefix", "v")
87
98
  .option("--remote <name>", "git remote", "origin")
99
+ .option("--python-root <path>", "Python package root for local publish", "packages/py")
88
100
  .option("--no-bump", "use an existing synchronized local version bump")
89
- .action((options: { root?: string; branch: string; bump: boolean; prefix: string; remote: string }) => {
90
- runRelease({
91
- root: options.root ?? projectUtils.root() ?? process.cwd(),
92
- branch: options.branch,
93
- bump: options.bump,
94
- prefix: options.prefix,
95
- remote: options.remote,
96
- });
97
- })
98
- .parse();
101
+ .option("--no-local-publish", "skip publishing to configured local registries")
102
+ .action(
103
+ async (options: {
104
+ root?: string;
105
+ branch: string;
106
+ bump: boolean;
107
+ prefix: string;
108
+ remote: string;
109
+ pythonRoot: string;
110
+ localPublish: boolean;
111
+ }) => {
112
+ await runRelease({
113
+ root: options.root ?? projectUtils.root() ?? process.cwd(),
114
+ branch: options.branch,
115
+ bump: options.bump,
116
+ prefix: options.prefix,
117
+ remote: options.remote,
118
+ pythonRoot: options.pythonRoot,
119
+ localPublish: options.localPublish,
120
+ });
121
+ },
122
+ )
123
+ .parseAsync();
99
124
  }