@dbx-tools/cli 0.3.42 → 0.3.44

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
@@ -6,7 +6,7 @@ export * as bootstrap from "./src/bootstrap";
6
6
  export * as cli from "./src/cli";
7
7
  export * as pnpm from "./src/pnpm";
8
8
  export * as root from "./src/root";
9
- export { bootstrapWorkspace, seedToolchain, runInitialSynth } from "./src/bootstrap";
9
+ export { ensureEngineCurrent, bootstrapWorkspace, seedToolchain, runInitialSynth } from "./src/bootstrap";
10
10
  export { runCli } from "./src/cli";
11
11
  export { resolvePnpmArgv, runPnpm, ensureWorkspaceReady, runProjen } from "./src/pnpm";
12
12
  export { findWorkspaceRoot, needsBootstrap, needsInstall, needsToolchain, workspaceRoot, rootLabel } from "./src/root";
package/package.json CHANGED
@@ -18,15 +18,15 @@
18
18
  "commander": "^15.0.0",
19
19
  "pnpm": "^11.0.6",
20
20
  "tsx": "^4.23.0",
21
- "@dbx-tools/shared-core": "0.3.42",
22
- "@dbx-tools/core": "0.3.42"
21
+ "@dbx-tools/core": "0.3.44",
22
+ "@dbx-tools/shared-core": "0.3.44"
23
23
  },
24
24
  "main": "index.ts",
25
25
  "license": "UNLICENSED",
26
26
  "publishConfig": {
27
27
  "access": "public"
28
28
  },
29
- "version": "0.3.42",
29
+ "version": "0.3.44",
30
30
  "types": "index.ts",
31
31
  "type": "module",
32
32
  "exports": {
package/src/bootstrap.ts CHANGED
@@ -5,16 +5,85 @@
5
5
  */
6
6
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
+ import { fileURLToPath } from "node:url";
8
9
  import { intro, outro } from "@clack/prompts";
9
10
  import { exec } from "@dbx-tools/core";
10
11
  import { json } from "@dbx-tools/shared-core";
11
12
  import { resolvePnpmArgv, runPnpm } from "./pnpm";
12
13
  import { rootLabel } from "./root";
13
14
 
14
- // Pin to `@latest` explicitly: a bare `@dbx-tools/projen` can land on a stray
15
- // `0.0.0` (whose `^0.0.0` caret then can't reach any real release), leaving the
16
- // workspace on a stale engine. `@latest` always takes the newest published.
17
- const DEFAULT_PROJEN_SPECIFIER = "@dbx-tools/projen@latest";
15
+ /** Fallback when this CLI's own version isn't a real release (an in-repo `0.0.0`). */
16
+ const FALLBACK_PROJEN_SPECIFIER = "@dbx-tools/projen@latest";
17
+
18
+ /**
19
+ * Install the engine at THIS CLI's own version. The two are released together by
20
+ * the root `bump`, so the matching engine always exists on the registry.
21
+ *
22
+ * Not `@latest`, and not a bare `@dbx-tools/projen`. A bare specifier can land on
23
+ * a stray `0.0.0`, whose `^0.0.0` caret then reaches no real release. `@latest`
24
+ * has a subtler failure: pnpm 11 applies a `minimumReleaseAge` delay, so for the
25
+ * first day after a release it deliberately resolves a dist-tag to the newest
26
+ * version OLDER than the threshold and merely notes the newer one
27
+ * (`+ @dbx-tools/projen 0.1.24 (0.3.42 is available)`). A bootstrap run right
28
+ * after a release therefore installed a months-old engine against a current CLI,
29
+ * which is how `sync --watch` died on an engine predating its `concurrently`
30
+ * dependency. An explicit range admits only the version we want, so the age
31
+ * heuristic has nothing older to fall back to.
32
+ */
33
+ function defaultProjenSpecifier(): string {
34
+ const version = ownVersion();
35
+ return version ? `@dbx-tools/projen@^${version}` : FALLBACK_PROJEN_SPECIFIER;
36
+ }
37
+
38
+ /** This CLI's own released version, or `undefined` for an in-repo `0.0.0` build. */
39
+ function ownVersion(): string | undefined {
40
+ try {
41
+ const manifestPath = fileURLToPath(new URL("../package.json", import.meta.url));
42
+ const version = json.parseRecord(readFileSync(manifestPath, "utf8"))?.version;
43
+ return typeof version === "string" && version !== "0.0.0" ? version : undefined;
44
+ } catch {
45
+ return undefined;
46
+ }
47
+ }
48
+
49
+ /** Compare `x.y.z` triples; returns <0, 0, or >0. Missing parts count as 0. */
50
+ function compareVersions(a: string, b: string): number {
51
+ const parts = (v: string) => v.split(".").map((p) => Number.parseInt(p, 10) || 0);
52
+ const [x, y] = [parts(a), parts(b)];
53
+ return x[0] - y[0] || x[1] - y[1] || x[2] - y[2];
54
+ }
55
+
56
+ /** Version of the engine currently installed at `root`, if any. */
57
+ function installedEngineVersion(root: string): string | undefined {
58
+ try {
59
+ const manifest = join(root, "node_modules", "@dbx-tools", "projen", "package.json");
60
+ const version = json.parseRecord(readFileSync(manifest, "utf8"))?.version;
61
+ return typeof version === "string" ? version : undefined;
62
+ } catch {
63
+ return undefined;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Upgrade an established workspace whose installed engine predates this CLI.
69
+ *
70
+ * Bootstrapping pins the engine once, and nothing afterwards revisits it - so a
71
+ * workspace created months ago kept resolving its original engine no matter how
72
+ * current the CLI invoking it was, and failed inside the OLD engine's code
73
+ * (`sync --watch` dying on a `concurrently` that engine never declared). The two
74
+ * are released in lockstep, so this CLI's version is exactly the engine it
75
+ * expects.
76
+ *
77
+ * Only ever moves FORWARD: an engine at or ahead of this CLI is left alone, so
78
+ * an older CLI cannot downgrade a workspace.
79
+ */
80
+ export function ensureEngineCurrent(root: string): void {
81
+ const expected = ownVersion();
82
+ if (!expected) return;
83
+ const installed = installedEngineVersion(root);
84
+ if (installed && compareVersions(installed, expected) >= 0) return;
85
+ runPnpm(["add", "-D", defaultProjenSpecifier()], root);
86
+ }
18
87
 
19
88
  // Reach the class through its module NAMESPACE. Current engines also hoist it
20
89
  // flat, but every engine ever published exports the namespace, and this template
@@ -49,7 +118,7 @@ allowBuilds:
49
118
  */
50
119
  export function bootstrapWorkspace(
51
120
  root: string,
52
- projenSpecifier: string = DEFAULT_PROJEN_SPECIFIER,
121
+ projenSpecifier: string = defaultProjenSpecifier(),
53
122
  ): void {
54
123
  intro(`Bootstrapping dbx-tools workspace in ${rootLabel(root)}`);
55
124
 
@@ -75,7 +144,7 @@ export function bootstrapWorkspace(
75
144
  */
76
145
  export function seedToolchain(
77
146
  root: string,
78
- projenSpecifier: string = DEFAULT_PROJEN_SPECIFIER,
147
+ projenSpecifier: string = defaultProjenSpecifier(),
79
148
  ): void {
80
149
  const manifestPath = join(root, "package.json");
81
150
  if (!existsSync(manifestPath)) {
package/src/cli.ts CHANGED
@@ -4,7 +4,12 @@
4
4
  * @module
5
5
  */
6
6
  import { Command } from "commander";
7
- import { bootstrapWorkspace, runInitialSynth, seedToolchain } from "./bootstrap";
7
+ import {
8
+ bootstrapWorkspace,
9
+ ensureEngineCurrent,
10
+ runInitialSynth,
11
+ seedToolchain,
12
+ } from "./bootstrap";
8
13
  import { ensureWorkspaceReady, runPnpm, runProjen } from "./pnpm";
9
14
  import { findWorkspaceRoot, needsBootstrap, needsToolchain } from "./root";
10
15
 
@@ -19,7 +24,8 @@ import { findWorkspaceRoot, needsBootstrap, needsToolchain } from "./root";
19
24
  * the toolchain, then run the INITIAL synth directly (the projen tasks the
20
25
  * args would name, like `sync`, don't exist until `.projenrc.ts` has run
21
26
  * once), and install. Don't forward `projenArgs` - the synth is the work.
22
- * - otherwise (established workspace) -> ensure deps, then forward to projen.
27
+ * - otherwise (established workspace) -> ensure deps, bring the engine up to
28
+ * this CLI's version, then forward to projen.
23
29
  */
24
30
  async function prepareAndRunProjen(projenArgs: string[], startDir?: string): Promise<void> {
25
31
  const root = await findWorkspaceRoot(startDir);
@@ -34,6 +40,7 @@ async function prepareAndRunProjen(projenArgs: string[], startDir?: string): Pro
34
40
  return;
35
41
  }
36
42
  ensureWorkspaceReady(root);
43
+ ensureEngineCurrent(root);
37
44
  runProjen(projenArgs, root);
38
45
  }
39
46