@dbx-tools/projen 0.6.161 → 0.6.168

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
@@ -96,10 +96,29 @@ derives the crate name and repository from the parent project. A crate containin
96
96
  automatically wires matching private Node and Python binding packages using the
97
97
  same capability name. Repository-specific dependencies and features remain
98
98
  declarative options in `.projenrc.ts`; generated bindings are built separately
99
- from projen synthesis.
99
+ from projen synthesis. Target-independent Node binding TypeScript is committed
100
+ and remains generated/read-only, while native libraries stay ignored. Node
101
+ facades compile to `lib/` and publish JavaScript entry points that plain Node
102
+ can load from `node_modules`. The generated barrel exports the binding API from
103
+ the package root. Do not create a `nodeExports` binding subpath or a
104
+ handwritten type facade.
105
+
106
+ Rust dependencies between binding-enabled workspace crates become Node
107
+ `workspace:*` and Python `internalDependencies` automatically. Python generation
108
+ selects the owning crate and supplies `external_packages` imports. UBRN currently
109
+ emits every linked component; the generation task discards dependency outputs
110
+ and rewrites their imports to the owning package root, including its generated
111
+ `uniffiModule` converter table. It never copies a dependency's records or enums.
112
+ Foreign callback adapters must first be wrapped in an object created by their
113
+ own native library (`createStorageHandle` for auth), because callback registries
114
+ are library-local. Release wheels replace sibling Git requirements with matching
115
+ native-wheel versions, and dependent publishers wait for their dependencies.
116
+ Each crate builds its own `<crate>-uniffi-bindgen` executable so a workspace
117
+ build has no colliding binary outputs. Packaging runs that prebuilt executable.
100
118
 
101
119
  `sync --watch` runs a focused Rust watcher beside the OpenAPI watcher. Changes
102
- inside an existing UniFFI crate regenerate only that crate's bindings; adding or
120
+ inside an existing UniFFI crate regenerate that crate and its dependent bindings
121
+ in dependency order; adding or
103
122
  removing a crate or `setup_scaffolding!()` marker triggers a full synth. Repos
104
123
  without Rust crates start no Rust watcher. When Rust projects are detected,
105
124
  Cargo is required and the focused task fails immediately if it is unavailable.
@@ -122,17 +141,28 @@ MSRV recorded in package manifests, while `releaseRustVersion` independently
122
141
  defaults release compilation to `stable`. UBRN uses that same release toolchain
123
142
  unless `ubrnRustVersion` explicitly selects another one.
124
143
 
144
+ Generated build and release workflows share the Bun cache helpers from
145
+ `bun-workflow.ts`. One `BUN_VERSION` value drives setup and cache keys, while a
146
+ generated dependency-only fingerprint keeps release version bumps from
147
+ invalidating Bun's global package cache. `node_modules` remains uncached.
148
+
125
149
  The final host UBRN executable is cached by pinned UBRN version, Rust toolchain,
126
150
  runner OS, and runner architecture. A hit skips both the workspace `bun install`
127
151
  and the UBRN Cargo build. Release packaging passes the cached executable directly
128
152
  to the Node binding generator and keeps the package barrel copied from source,
129
153
  so it does not need the installed projen dependency graph. A miss saves the
130
154
  validated executable immediately, before workspace build and packaging can fail.
131
- Cargo registry caches
155
+ Bun runtime setup still runs in the single facade row because the binding
156
+ generator and facade bundle are TypeScript. The bundle points its type surface
157
+ at the committed generated source and needs no standalone TypeScript compiler
158
+ or workspace install on a UBRN cache hit. Non-facade rows skip the complete
159
+ Bun/UBRN path.
160
+ Stable Windows rows verify and use the hosted runner's installed Rust toolchain
161
+ and select `rust-lld` for the workspace build. Cargo registry caches
132
162
  and the `SCCACHE_GHA_VERSION` namespace stay stable per target/toolchain across
133
163
  version tags. Cache keys, restore results, sccache statistics, and phase timings
134
164
  are written to each build log. Python generation executes the already-built
135
- `target/<triple>/release/uniffi-bindgen` directly. Artifact packaging therefore
165
+ `target/<triple>/release/<crate>-uniffi-bindgen` directly. Artifact packaging therefore
136
166
  does no Rust compilation after the main workspace build.
137
167
 
138
168
  Artifacts identify their crate, target, and type. Download-only publication
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";
@@ -55,7 +58,7 @@ export { DOWNSTREAM_RELEASE_EVENT, RUST_RELEASE_EVENT, RELEASE_TAG, RELEASE_SHA
55
58
  export { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS } from "./src/tags.ts";
56
59
  export type { PackageTag } from "./src/tags.ts";
57
60
  export { DBXToolsRootTsconfig } from "./src/tsconfig.ts";
58
- export { makeDefaultedInterfaceParametersOptional, addExplicitInterfaceReexports } from "./src/uniffi.ts";
61
+ export { makeDefaultedInterfaceParametersOptional, addTypeScriptExtensionsToBindingImports, addExplicitInterfaceReexports } from "./src/uniffi.ts";
59
62
  export type { TypeScriptBindingModule } from "./src/uniffi.ts";
60
63
  export { DBXToolsVsCode } from "./src/vscode.ts";
61
64
  export type { IgnoreGroupOptions } from "./src/watch.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.168",
30
+ "@dbx-tools/path": "0.6.168",
31
+ "@dbx-tools/shared-core": "0.6.168",
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.168",
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";
@@ -444,13 +446,13 @@ function defaultProjectOptions(
444
446
  devDeps: ["@types/node@^24.6.0"],
445
447
  ...(isRoot
446
448
  ? {
447
- prettier: true,
448
- prettierOptions: {
449
- settings: PRETTIER_SETTINGS,
450
- ignoreFile: true,
451
- ignoreFileOptions: { ignorePatterns: [...ignore.ignorePatterns({ test: false })] },
452
- },
453
- }
449
+ prettier: true,
450
+ prettierOptions: {
451
+ settings: PRETTIER_SETTINGS,
452
+ ignoreFile: true,
453
+ ignoreFileOptions: { ignorePatterns: [...ignore.ignorePatterns({ test: false })] },
454
+ },
455
+ }
454
456
  : {}),
455
457
  ...options,
456
458
  ...copiedGitIgnoreOptions(options),
@@ -493,24 +495,23 @@ function defaultTypeScriptProjectOptions(
493
495
  // ESLint is configured once on the ROOT (see initProject) and lints the whole
494
496
  // tree, so packages don't emit their own config. A caller can still override.
495
497
  eslint: false,
496
- devDeps: [...(base.devDeps ?? []), "typescript@^5.9.3", "@types/bun@^1.3.14"],
498
+ devDeps: [...(base.devDeps ?? []), "typescript@^5.9.3", `@types/bun@${BUN_VERSION}`],
497
499
  ...options,
498
500
  ...copiedGitIgnoreOptions(options),
499
501
  };
500
502
  }
501
503
 
502
- // Pinned to match the subproject defaults so bun resolves a single typescript
503
- // across the workspace (a bare name -> `*` could pull a second, newer major).
504
- // `@types/bun` gives the `Bun.*` globals the server/app tags now use.
505
- const DEV_DEPS_ROOT: string[] = ["typescript@^5.9.3", "@types/bun@^1.3.14"];
504
+ // Pinned to match the subproject defaults so bun resolves one TypeScript and
505
+ // one runtime-compatible set of Bun globals across the workspace.
506
+ const DEV_DEPS_ROOT: string[] = ["typescript@^5.9.3", `@types/bun@${BUN_VERSION}`];
506
507
 
507
508
  /** Options for {@link DBXToolsNodeProject} (the monorepo root). */
508
509
  export interface DBXToolsJavaScriptProjectOptions
509
510
  extends
510
- CommonProjectOptions,
511
- Partial<javascript.NodeProjectOptions>,
512
- DBXToolsConfigOptions,
513
- DBXToolsPNPMWorkspaceOptions {
511
+ CommonProjectOptions,
512
+ Partial<javascript.NodeProjectOptions>,
513
+ DBXToolsConfigOptions,
514
+ DBXToolsPNPMWorkspaceOptions {
514
515
  /**
515
516
  * The npm scope for generated package names (`@<scope>/<seg-...>`). Defaults to
516
517
  * the (resolved) project name; a leading `@` is optional.
@@ -591,8 +592,7 @@ export interface DBXToolsTypeScriptProjectOptions
591
592
  */
592
593
  export class DBXToolsNodeProject
593
594
  extends javascript.NodeProject
594
- implements DBXToolsJavaScriptProject
595
- {
595
+ implements DBXToolsJavaScriptProject {
596
596
  readonly language = "javascript" as const;
597
597
  readonly scope: string;
598
598
  readonly dbxToolsConfig: DBXToolsConfig;
@@ -642,6 +642,24 @@ export class DBXToolsNodeProject
642
642
  initProject(this, options);
643
643
  }
644
644
 
645
+ public override renderWorkflowSetup(options?: javascript.RenderWorkflowSetupOptions): JobStep[] {
646
+ const steps = super.renderWorkflowSetup(options);
647
+ if (this.parent) return steps;
648
+ return steps.flatMap((step) => {
649
+ if (step.uses === "oven-sh/setup-bun@v2") return [...bunCacheRestoreSteps(this)];
650
+ if (step.run === "bun install") {
651
+ return [
652
+ step,
653
+ bunCacheSaveStep({
654
+ condition:
655
+ "github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository",
656
+ }),
657
+ ];
658
+ }
659
+ return [step];
660
+ }) as JobStep[];
661
+ }
662
+
645
663
  public override preSynthesize(): void {
646
664
  if (this.rootInstallOnly) this.with(ROOT_INSTALL_ONLY_MIXIN);
647
665
  super.preSynthesize();
@@ -674,8 +692,8 @@ export const ROOT_INSTALL_ONLY_MIXIN = mixin.create(
674
692
  installDependencies(trigger: unknown): void;
675
693
  logInstallTrigger(trigger: unknown): void;
676
694
  };
677
- nodePackage.installDependencies = () => {};
678
- nodePackage.logInstallTrigger = () => {};
695
+ nodePackage.installDependencies = () => { };
696
+ nodePackage.logInstallTrigger = () => { };
679
697
  },
680
698
  );
681
699
 
@@ -688,8 +706,7 @@ export const ROOT_INSTALL_ONLY_MIXIN = mixin.create(
688
706
  */
689
707
  export class DBXToolsTypeScriptProject
690
708
  extends typescript.TypeScriptProject
691
- implements DBXToolsJavaScriptProject
692
- {
709
+ implements DBXToolsJavaScriptProject {
693
710
  readonly language = "javascript" as const;
694
711
  readonly scope: string;
695
712
  readonly dbxToolsConfig: DBXToolsConfig;
@@ -1066,6 +1083,7 @@ function initProject(
1066
1083
  // a plain `bun` exec rather than any wrapper: the default task is spawned by
1067
1084
  // nested installs/synths, and a wrapper that exported `npm_config_*` broke them.
1068
1085
  project.defaultTask?.reset("bun .projenrc.ts");
1086
+ project.buildWorkflow?.workflow.file?.addOverride("jobs.build.env.BUN_VERSION", BUN_VERSION);
1069
1087
 
1070
1088
  // Pin bun's hoisted linker workspace-wide (see RootBunfigFile) so a peer dep
1071
1089
  // resolves to one copy and singletons/types stay coherent.
package/src/project-py.ts CHANGED
@@ -683,7 +683,10 @@ export class DBXToolsPythonWorkspace extends Component {
683
683
  const linesBeforeAuthentication = [
684
684
  "# PyPI Trusted Publisher Setup Instructions",
685
685
  "",
686
- "Use a browser to audit and configure the PyPI trusted publishers below.",
686
+ "Use the system browser to audit and configure the PyPI trusted publishers below.",
687
+ "Do not use an in-app browser or embedded webview.",
688
+ "Do not visit GitHub or use the GitHub API or CLI. Every required GitHub owner, repository, workflow, environment, and branch value is provided below and is authoritative.",
689
+ "Use only PyPI pages for this task.",
687
690
  "",
688
691
  "## Audit and confirmation",
689
692
  "",
@@ -691,7 +694,7 @@ export class DBXToolsPythonWorkspace extends Component {
691
694
  "",
692
695
  "- Confirm that the active PyPI account can administer the listed projects and pending publishers.",
693
696
  "- Start at https://pypi.org/manage/projects/ and determine which listed projects exist.",
694
- "- Inspect every existing GitHub publisher for each project and compare its owner, repository name, workflow name, and environment name with the desired values below.",
697
+ "- On PyPI, inspect every existing GitHub Actions publisher for each project and compare its owner, repository name, workflow name, and environment name with the desired values below.",
695
698
  "- Identify duplicate and mismatched trusted publishers that must be replaced by removing the publisher entry and adding the correct one.",
696
699
  "- For projects that do not exist, identify the pending publisher that must be created.",
697
700
  "- Report the active account and a proposed reconciliation plan grouped by publishers that will be left unchanged, updated, replaced, created, or removed.",
@@ -700,9 +703,9 @@ export class DBXToolsPythonWorkspace extends Component {
700
703
  "",
701
704
  "## GitHub environment policy",
702
705
  "",
703
- `- Configure every environment listed below to permit deployments from the ${releaseBranch} branch.`,
704
- "- Branch-scoped release workflows use the environment names below; tag-only deployment policies reject their trusted-publishing jobs.",
705
- "- Keep the GitHub environment name synchronized with the PyPI trusted publisher environment.",
706
+ `- The supplied branch policy value is ${releaseBranch}.`,
707
+ "- Do not inspect or configure GitHub environments during this task.",
708
+ "- GitHub environment administration is a separate task; use the supplied values only when comparing PyPI publisher entries.",
706
709
  "",
707
710
  "## Authentication",
708
711
  "",
@@ -714,7 +717,7 @@ export class DBXToolsPythonWorkspace extends Component {
714
717
  "",
715
718
  "## Efficient browser workflow",
716
719
  "",
717
- "- Reuse or claim an existing PyPI browser tab when available.",
720
+ "- Reuse an existing PyPI tab in the system browser when available.",
718
721
  "- For an existing project, open its publisher page directly at https://pypi.org/manage/project/<project-name>/settings/publishing/.",
719
722
  "- For a project that does not exist, use the pending-publisher page at https://pypi.org/manage/account/publishing/.",
720
723
  "- Treat the publisher table shown after submission as authoritative confirmation of success, even if the navigation header unexpectedly appears signed out.",
@@ -769,7 +772,7 @@ export class DBXToolsPythonWorkspace extends Component {
769
772
  ],
770
773
  });
771
774
  project.root.addTask("pypiTrustedPublisherInstructions", {
772
- description: "Print browser-agent instructions for PyPI trusted publishers",
775
+ description: "Print system-browser instructions for PyPI trusted publishers",
773
776
  exec: `node ${helper}`,
774
777
  receiveArgs: true,
775
778
  });