@dbx-tools/projen 0.6.161 → 0.6.163

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
@@ -122,13 +122,18 @@ MSRV recorded in package manifests, while `releaseRustVersion` independently
122
122
  defaults release compilation to `stable`. UBRN uses that same release toolchain
123
123
  unless `ubrnRustVersion` explicitly selects another one.
124
124
 
125
+ Generated build and release workflows share the Bun cache helpers from
126
+ `bun-workflow.ts`. One `BUN_VERSION` value drives setup and cache keys, while a
127
+ generated dependency-only fingerprint keeps release version bumps from
128
+ invalidating Bun's global package cache. `node_modules` remains uncached.
129
+
125
130
  The final host UBRN executable is cached by pinned UBRN version, Rust toolchain,
126
131
  runner OS, and runner architecture. A hit skips both the workspace `bun install`
127
132
  and the UBRN Cargo build. Release packaging passes the cached executable directly
128
133
  to the Node binding generator and keeps the package barrel copied from source,
129
134
  so it does not need the installed projen dependency graph. A miss saves the
130
135
  validated executable immediately, before workspace build and packaging can fail.
131
- Cargo registry caches
136
+ Bun runtime setup still runs because the binding generator is TypeScript. Cargo registry caches
132
137
  and the `SCCACHE_GHA_VERSION` namespace stay stable per target/toolchain across
133
138
  version tags. Cache keys, restore results, sccache statistics, and phase timings
134
139
  are written to each build log. Python generation executes the already-built
package/index.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
6
6
  export * as barrels from "./src/barrels.ts";
7
7
  export * as bunApp from "./src/bun-app.ts";
8
+ export * as bunWorkflow from "./src/bun-workflow.ts";
8
9
  export * as clean from "./src/clean.ts";
9
10
  export * as codegen from "./src/codegen.ts";
10
11
  export * as dbxToolsConfig from "./src/dbx-tools-config.ts";
@@ -31,6 +32,8 @@ export * as vscode from "./src/vscode.ts";
31
32
  export * as watch from "./src/watch.ts";
32
33
  export * as workspaceVersion from "./src/workspace-version.ts";
33
34
  export { BUN_DEV_OVERRIDE, BUN_BUILD_OVERRIDE, BUN_APP_OVERRIDES, RootBunfigFile, BunfigFile, BunDevServerFile, BunBuildFile } from "./src/bun-app.ts";
35
+ export { BUN_VERSION } from "./src/bun-workflow.ts";
36
+ export type { BunWorkflowCacheOptions } from "./src/bun-workflow.ts";
34
37
  export { DBXToolsConfig } from "./src/dbx-tools-config.ts";
35
38
  export type { DBXToolsConfigOptions } from "./src/dbx-tools-config.ts";
36
39
  export { resolvePkgRoot } from "./src/engine-root.ts";
package/package.json CHANGED
@@ -26,9 +26,9 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@clack/prompts": "^1.7.0",
29
- "@dbx-tools/core": "0.6.161",
30
- "@dbx-tools/path": "0.6.161",
31
- "@dbx-tools/shared-core": "0.6.161",
29
+ "@dbx-tools/core": "0.6.163",
30
+ "@dbx-tools/path": "0.6.163",
31
+ "@dbx-tools/shared-core": "0.6.163",
32
32
  "commander": "^15.0.0",
33
33
  "concurrently": "^10.0.3",
34
34
  "constructs": "^10.6.0",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "main": "index.ts",
50
50
  "license": "Apache-2.0",
51
- "version": "0.6.161",
51
+ "version": "0.6.163",
52
52
  "types": "index.ts",
53
53
  "type": "module",
54
54
  "exports": {
@@ -0,0 +1,145 @@
1
+ /** Shared Bun setup and package-cache steps for generated workflows. */
2
+ import { TextFile, javascript } from "projen";
3
+
4
+ export const BUN_VERSION = "1.3.14";
5
+
6
+ const cacheKeyScript = `#!/usr/bin/env node
7
+ import { createHash } from "node:crypto";
8
+ import { readdirSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+
11
+ const root = process.cwd();
12
+ const ignored = new Set([
13
+ ".git",
14
+ ".docs-build",
15
+ ".venv",
16
+ ".worktrees",
17
+ "coverage",
18
+ "dist",
19
+ "lib",
20
+ "node_modules",
21
+ "target",
22
+ ]);
23
+ const manifests = [];
24
+ const walk = (directory) => {
25
+ for (const entry of readdirSync(directory, { withFileTypes: true })) {
26
+ if (ignored.has(entry.name)) continue;
27
+ const path = join(directory, entry.name);
28
+ if (entry.isDirectory()) walk(path);
29
+ else if (entry.name === "package.json") manifests.push(path);
30
+ }
31
+ };
32
+ walk(root);
33
+
34
+ const dependencyFields = [
35
+ "catalog",
36
+ "dependencies",
37
+ "devDependencies",
38
+ "optionalDependencies",
39
+ "overrides",
40
+ "peerDependencies",
41
+ "peerDependenciesMeta",
42
+ "resolutions",
43
+ "trustedDependencies",
44
+ ];
45
+ const canonical = (value) => {
46
+ if (Array.isArray(value)) return value.map(canonical);
47
+ if (!value || typeof value !== "object") return value;
48
+ return Object.fromEntries(
49
+ Object.entries(value)
50
+ .sort(([left], [right]) => left.localeCompare(right))
51
+ .map(([key, child]) => [key, canonical(child)]),
52
+ );
53
+ };
54
+ const dependencies = manifests
55
+ .sort()
56
+ .map((path) => {
57
+ const manifest = JSON.parse(readFileSync(path, "utf8"));
58
+ return [
59
+ path.slice(root.length + 1),
60
+ Object.fromEntries(
61
+ dependencyFields
62
+ .filter((field) => manifest[field] !== undefined)
63
+ .map((field) => [field, canonical(manifest[field])]),
64
+ ),
65
+ ];
66
+ });
67
+ process.stdout.write(createHash("sha256").update(JSON.stringify(dependencies)).digest("hex"));
68
+ `;
69
+
70
+ const configured = new WeakSet<javascript.NodeProject>();
71
+
72
+ function ensureCacheKeyScript(project: javascript.NodeProject): void {
73
+ if (configured.has(project)) return;
74
+ new TextFile(project.root, ".projen/bun-cache-key.mjs", {
75
+ lines: cacheKeyScript.trimEnd().split("\n"),
76
+ });
77
+ configured.add(project);
78
+ }
79
+
80
+ function stepCondition(condition: string | undefined, cacheMiss = false): string | undefined {
81
+ const expressions = [
82
+ condition,
83
+ ...(cacheMiss ? ["steps.bun_cache.outputs.cache-hit != 'true'"] : []),
84
+ ].filter(Boolean);
85
+ return expressions.length ? `\${{ ${expressions.join(" && ")} }}` : undefined;
86
+ }
87
+
88
+ export interface BunWorkflowCacheOptions {
89
+ readonly setupCondition?: string;
90
+ readonly condition?: string;
91
+ }
92
+
93
+ /** Set up Bun and restore its global package cache. */
94
+ export function bunCacheRestoreSteps(
95
+ project: javascript.NodeProject,
96
+ options: BunWorkflowCacheOptions = {},
97
+ ): readonly Record<string, unknown>[] {
98
+ ensureCacheKeyScript(project);
99
+ const condition = stepCondition(options.condition);
100
+ const setupCondition = stepCondition(options.setupCondition);
101
+ return [
102
+ {
103
+ name: "Setup Bun",
104
+ ...(setupCondition ? { if: setupCondition } : {}),
105
+ uses: "oven-sh/setup-bun@v2",
106
+ with: { "bun-version": "${{ env.BUN_VERSION }}" },
107
+ },
108
+ {
109
+ name: "Resolve Bun cache",
110
+ id: "bun_cache_metadata",
111
+ ...(condition ? { if: condition } : {}),
112
+ shell: "bash",
113
+ run: [
114
+ 'echo "path=$(bun pm cache)" >> "$GITHUB_OUTPUT"',
115
+ 'echo "dependency_hash=$(node .projen/bun-cache-key.mjs)" >> "$GITHUB_OUTPUT"',
116
+ ].join("\n"),
117
+ },
118
+ {
119
+ name: "Restore Bun cache",
120
+ id: "bun_cache",
121
+ ...(condition ? { if: condition } : {}),
122
+ uses: "actions/cache/restore@v5",
123
+ with: {
124
+ path: "${{ steps.bun_cache_metadata.outputs.path }}",
125
+ key: `bun-\${{ runner.os }}-\${{ runner.arch }}-\${{ env.BUN_VERSION }}-\${{ steps.bun_cache_metadata.outputs.dependency_hash }}`,
126
+ "restore-keys": `bun-\${{ runner.os }}-\${{ runner.arch }}-\${{ env.BUN_VERSION }}-`,
127
+ },
128
+ },
129
+ ];
130
+ }
131
+
132
+ /** Save Bun's global package cache immediately after installation. */
133
+ export function bunCacheSaveStep(
134
+ options: BunWorkflowCacheOptions = {},
135
+ ): Readonly<Record<string, unknown>> {
136
+ return {
137
+ name: "Save Bun cache",
138
+ if: stepCondition(options.condition, true),
139
+ uses: "actions/cache/save@v5",
140
+ with: {
141
+ path: "${{ steps.bun_cache_metadata.outputs.path }}",
142
+ key: "${{ steps.bun_cache.outputs.cache-primary-key }}",
143
+ },
144
+ };
145
+ }
package/src/project-js.ts CHANGED
@@ -13,6 +13,7 @@ import { ignore, match } from "@dbx-tools/path";
13
13
  import { object, string, type OneOrMany } from "@dbx-tools/shared-core";
14
14
  import { type IConstruct } from "constructs";
15
15
  import { Component, IgnoreFile, Project, type TaskOptions, javascript, typescript } from "projen";
16
+ import type { JobStep } from "projen/lib/github/workflows-model";
16
17
  import { ReleaseTrigger } from "projen/lib/release";
17
18
  import { mixin } from "..";
18
19
  import { generateBarrels } from "./barrels.ts";
@@ -23,6 +24,7 @@ import {
23
24
  BunfigFile,
24
25
  RootBunfigFile,
25
26
  } from "./bun-app.ts";
27
+ import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
26
28
  import { codegenModulePaths, generateCodegen } from "./codegen.ts";
27
29
  import { DBXToolsConfig, type DBXToolsConfigOptions } from "./dbx-tools-config.ts";
28
30
  import { resolvePkgRoot } from "./engine-root.ts";
@@ -642,6 +644,24 @@ export class DBXToolsNodeProject
642
644
  initProject(this, options);
643
645
  }
644
646
 
647
+ public override renderWorkflowSetup(options?: javascript.RenderWorkflowSetupOptions): JobStep[] {
648
+ const steps = super.renderWorkflowSetup(options);
649
+ if (this.parent) return steps;
650
+ return steps.flatMap((step) => {
651
+ if (step.uses === "oven-sh/setup-bun@v2") return [...bunCacheRestoreSteps(this)];
652
+ if (step.run === "bun install") {
653
+ return [
654
+ step,
655
+ bunCacheSaveStep({
656
+ condition:
657
+ "github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository",
658
+ }),
659
+ ];
660
+ }
661
+ return [step];
662
+ }) as JobStep[];
663
+ }
664
+
645
665
  public override preSynthesize(): void {
646
666
  if (this.rootInstallOnly) this.with(ROOT_INSTALL_ONLY_MIXIN);
647
667
  super.preSynthesize();
@@ -1066,6 +1086,7 @@ function initProject(
1066
1086
  // a plain `bun` exec rather than any wrapper: the default task is spawned by
1067
1087
  // nested installs/synths, and a wrapper that exported `npm_config_*` broke them.
1068
1088
  project.defaultTask?.reset("bun .projenrc.ts");
1089
+ project.buildWorkflow?.workflow.file?.addOverride("jobs.build.env.BUN_VERSION", BUN_VERSION);
1069
1090
 
1070
1091
  // Pin bun's hoisted linker workspace-wide (see RootBunfigFile) so a peer dep
1071
1092
  // resolves to one copy and singletons/types stay coherent.
package/src/project-rs.ts CHANGED
@@ -5,6 +5,7 @@ import { fileURLToPath } from "node:url";
5
5
  import { project as coreProject } from "@dbx-tools/core";
6
6
  import { string } from "@dbx-tools/shared-core";
7
7
  import { Project, TextFile, YamlFile, javascript } from "projen";
8
+ import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
8
9
  import type { DBXToolsProject } from "./project.ts";
9
10
  import {
10
11
  DBXToolsTypeScriptProject,
@@ -766,6 +767,7 @@ export class DBXToolsRustWorkspace {
766
767
  "runs-on": "${{ matrix.target.runner }}",
767
768
  env: {
768
769
  ...RUST_CACHE_ENV,
770
+ BUN_VERSION,
769
771
  SCCACHE_GHA_VERSION: `release-\${{ matrix.target.cargo }}-rust-${releaseRustVersion}`,
770
772
  },
771
773
  strategy: {
@@ -774,15 +776,6 @@ export class DBXToolsRustWorkspace {
774
776
  },
775
777
  steps: [
776
778
  ...releaseSourceSteps(),
777
- ...(hasNodeBindings
778
- ? [
779
- {
780
- name: "Setup Bun",
781
- uses: "oven-sh/setup-bun@v2",
782
- with: { "bun-version": "1.3.14" },
783
- },
784
- ]
785
- : []),
786
779
  ...(hasPythonBindings ? [{ name: "Setup uv", uses: "astral-sh/setup-uv@v7" }] : []),
787
780
  {
788
781
  name: "Setup Rust",
@@ -803,6 +796,11 @@ export class DBXToolsRustWorkspace {
803
796
  },
804
797
  ]
805
798
  : []),
799
+ ...(hasNodeBindings
800
+ ? bunCacheRestoreSteps(project, {
801
+ condition: "steps.ubrn_cache.outputs.cache-hit != 'true'",
802
+ })
803
+ : []),
806
804
  {
807
805
  name: "Log cache configuration",
808
806
  shell: "bash",
@@ -833,6 +831,9 @@ export class DBXToolsRustWorkspace {
833
831
  shell: "bash",
834
832
  run: timedBash("node_tooling", "bun install"),
835
833
  },
834
+ bunCacheSaveStep({
835
+ condition: "steps.ubrn_cache.outputs.cache-hit != 'true'",
836
+ }),
836
837
  ]
837
838
  : []),
838
839
  ...(hasNodeBindings
package/src/release.ts CHANGED
@@ -10,6 +10,7 @@ import { Component, YamlFile } from "projen";
10
10
  import { GithubWorkflow } from "projen/lib/github";
11
11
  import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
12
12
  import { object } from "@dbx-tools/shared-core";
13
+ import { BUN_VERSION, bunCacheRestoreSteps, bunCacheSaveStep } from "./bun-workflow.ts";
13
14
  import { applyTasks, taskScript, type DBXToolsNodeProject } from "./project.ts";
14
15
  import {
15
16
  DOWNSTREAM_RELEASE_EVENT,
@@ -21,7 +22,6 @@ import {
21
22
 
22
23
  const NODE_VERSION = "lts/*";
23
24
  const NPM_REGISTRY_URL = "https://registry.npmjs.org";
24
- const BUN_VERSION = "1.3.14";
25
25
 
26
26
  /**
27
27
  * The `release` workflow's version-stamp + publish step, as a shell script.
@@ -73,10 +73,10 @@ interface PublishWorkflow {
73
73
  }
74
74
 
75
75
  /** Shared checkout and toolchain setup for every npm publish workflow. */
76
- function publishSetupSteps(): JobStep[] {
76
+ function publishSetupSteps(project: DBXToolsNodeProject): JobStep[] {
77
77
  return [
78
78
  { name: "Checkout", uses: "actions/checkout@v6", with: { "fetch-depth": 0 } },
79
- { name: "Setup Bun", uses: "oven-sh/setup-bun@v2", with: { "bun-version": BUN_VERSION } },
79
+ ...bunCacheRestoreSteps(project),
80
80
  {
81
81
  name: "Setup Node.js",
82
82
  uses: "actions/setup-node@v6",
@@ -88,6 +88,7 @@ function publishSetupSteps(): JobStep[] {
88
88
  },
89
89
  // Bun's install; the lockfile may be absent or stale in CI so it is not frozen.
90
90
  { name: "Install", run: "bun install" },
91
+ bunCacheSaveStep(),
91
92
  ];
92
93
  }
93
94
 
@@ -301,7 +302,7 @@ export class DBXToolsRelease extends Component {
301
302
  project: DBXToolsNodeProject,
302
303
  { name, tagPrefix, steps, workingDirectory, upstreamWorkflow }: PublishWorkflow,
303
304
  ): void {
304
- const setupSteps = publishSetupSteps();
305
+ const setupSteps = publishSetupSteps(project);
305
306
  const branchDispatch = upstreamWorkflow === undefined;
306
307
  const workflow = new GithubWorkflow(project.github!, name, {
307
308
  // A newer release supersedes an older run even when publication has
@@ -369,6 +370,7 @@ export class DBXToolsRelease extends Component {
369
370
  // `DRY_RUN_INPUT` is `--dry-run` when the dispatch input is true, else empty;
370
371
  // the publish script also FORCES it on any `workflow_dispatch` run.
371
372
  env: {
373
+ BUN_VERSION,
372
374
  CI: "true",
373
375
  DRY_RUN_INPUT: "${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}",
374
376
  },
@@ -150,10 +150,8 @@ const packageNode = ({
150
150
  required("cargo-target"),
151
151
  "--node-package-base",
152
152
  `${nodePackage}-`,
153
- "--ubrn",
154
- required("ubrn"),
153
+ ...(parsed.values.ubrn ? ["--ubrn", parsed.values.ubrn, "--skip-barrels"] : []),
155
154
  "--skip-build",
156
- "--skip-barrels",
157
155
  ]);
158
156
  mkdirSync(resolve(output, "npm-facade"), { recursive: true });
159
157
  run("npm", ["pack", "--pack-destination", resolve(output, "npm-facade")], facadeDirectory);