@dbx-tools/projen 0.6.39 → 0.6.41

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,6 +3,7 @@
3
3
  // Hand edits are overwritten on the next watch; this file is read-only.
4
4
 
5
5
  export * as barrels from "./src/barrels.ts";
6
+ export * as bunApp from "./src/bun-app.ts";
6
7
  export * as clean from "./src/clean.ts";
7
8
  export * as codegen from "./src/codegen.ts";
8
9
  export * as dbxToolsConfig from "./src/dbx-tools-config.ts";
@@ -20,7 +21,6 @@ export * as release from "./src/release.ts";
20
21
  export * as scaffold from "./src/scaffold.ts";
21
22
  export * as tags from "./src/tags.ts";
22
23
  export * as tsconfig from "./src/tsconfig.ts";
23
- export * as vite from "./src/vite.ts";
24
24
  export * as vscode from "./src/vscode.ts";
25
25
  export * as watch from "./src/watch.ts";
26
26
  export { DBXToolsConfig } from "./src/dbx-tools-config.ts";
@@ -41,6 +41,6 @@ export type { StandaloneRelease, DBXToolsReleaseOptions } from "./src/release.ts
41
41
  export { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS } from "./src/tags.ts";
42
42
  export type { PackageTag } from "./src/tags.ts";
43
43
  export { DBXToolsRootTsconfig } from "./src/tsconfig.ts";
44
- export { DEFAULT_VITE_OVERRIDES, ViteConfigFile } from "./src/vite.ts";
44
+ export { BUN_DEV_OVERRIDE, BUN_BUILD_OVERRIDE, BUN_APP_OVERRIDES, BunfigFile, BunDevServerFile, BunBuildFile } from "./src/bun-app.ts";
45
45
  export { DBXToolsVsCode } from "./src/vscode.ts";
46
46
  export type { IgnoreGroupOptions } from "./src/watch.ts";
package/package.json CHANGED
@@ -1,5 +1,22 @@
1
1
  {
2
2
  "name": "@dbx-tools/projen",
3
+ "scripts": {
4
+ "build": "projen build",
5
+ "bump": "projen bump",
6
+ "clobber": "projen clobber",
7
+ "compile": "projen compile",
8
+ "default": "projen default",
9
+ "demo": "projen demo",
10
+ "eject": "projen eject",
11
+ "package": "projen package",
12
+ "post-compile": "projen post-compile",
13
+ "post-upgrade": "projen post-upgrade",
14
+ "pre-compile": "projen pre-compile",
15
+ "test": "projen test",
16
+ "upgrade": "projen upgrade",
17
+ "watch": "projen watch",
18
+ "projen": "projen"
19
+ },
3
20
  "devDependencies": {
4
21
  "@types/node": "^24.6.0",
5
22
  "constructs": "^10.0.0",
@@ -9,9 +26,9 @@
9
26
  },
10
27
  "dependencies": {
11
28
  "@clack/prompts": "^1.7.0",
12
- "@dbx-tools/core": "^0.6.39",
13
- "@dbx-tools/path": "^0.6.39",
14
- "@dbx-tools/shared-core": "^0.6.39",
29
+ "@dbx-tools/core": "^0.6.40",
30
+ "@dbx-tools/path": "^0.6.40",
31
+ "@dbx-tools/shared-core": "^0.6.40",
15
32
  "commander": "^15.0.0",
16
33
  "concurrently": "^10.0.3",
17
34
  "constructs": "^10.6.0",
@@ -23,6 +40,7 @@
23
40
  "tsx": "^4.23.0",
24
41
  "yaml": "^2.9.0"
25
42
  },
43
+ "pnpm": {},
26
44
  "devEngines": {
27
45
  "packageManager": {
28
46
  "name": "pnpm",
@@ -32,7 +50,8 @@
32
50
  },
33
51
  "main": "index.ts",
34
52
  "license": "Apache-2.0",
35
- "version": "0.6.39",
53
+ "version": "0.6.41",
54
+ "packageManager": "pnpm@10.33.0",
36
55
  "types": "index.ts",
37
56
  "type": "module",
38
57
  "exports": {
@@ -44,22 +63,5 @@
44
63
  "src",
45
64
  "tasks"
46
65
  ],
47
- "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\".",
48
- "scripts": {
49
- "build": "projen build",
50
- "bump": "projen bump",
51
- "clobber": "projen clobber",
52
- "compile": "projen compile",
53
- "default": "projen default",
54
- "demo": "projen demo",
55
- "eject": "projen eject",
56
- "package": "projen package",
57
- "post-compile": "projen post-compile",
58
- "post-upgrade": "projen post-upgrade",
59
- "pre-compile": "projen pre-compile",
60
- "test": "projen test",
61
- "upgrade": "projen upgrade",
62
- "watch": "projen watch",
63
- "projen": "projen"
64
- }
65
- }
66
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm exec projen\"."
67
+ }
package/src/bun-app.ts ADDED
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Bun browser-app scaffolding as first-class projen file components.
3
+ *
4
+ * Replaces the old Vite toolchain (`vite.ts`). An `app`-tagged package gets three
5
+ * generated, read-only files that stand in for `vite dev` / `vite build`:
6
+ *
7
+ * - {@link BunfigFile} - `bunfig.toml` wiring `bun-plugin-tailwind` into both the
8
+ * dev server (`[serve.static]`) and `Bun.build` (`[build]`) so Tailwind v4's
9
+ * `@import "tailwindcss"` + `@source` directives compile with no separate CLI.
10
+ * - {@link BunDevServerFile} - `dev.ts`: a `Bun.serve()` fullstack dev server that
11
+ * imports `index.html` and serves the SPA with React hot module reloading.
12
+ * - {@link BunBuildFile} - `build.ts`: a `Bun.build()` production bundle of
13
+ * `index.html` (the `bun-plugin-tailwind` plugin must run through the JS API,
14
+ * NOT the `bun build` CLI, which does not load it).
15
+ *
16
+ * Each file supports an unmanaged OVERRIDE beside it (`bunfig.override.toml`,
17
+ * `bun-dev.override.ts`, `bun-build.override.ts`): the dev/build scripts import
18
+ * the override's default export and merge it over the generated options, so a
19
+ * package tweaks its server/bundle WITHOUT editing the projen-owned file - the
20
+ * same escape hatch the Vite generator offered via `vite.config.override.*`.
21
+ *
22
+ * bun runs these `.ts` files directly. Being package-ROOT files (not under
23
+ * `src/`), they are outside the package's `tsconfig` include, so their `Bun.*`
24
+ * usage never trips the `app` package's DOM-only `compile`.
25
+ */
26
+ import { type Project, TextFile } from "projen";
27
+
28
+ /** Unmanaged dev-server override module, merged over the generated options if present. */
29
+ export const BUN_DEV_OVERRIDE = "bun-dev.override.ts";
30
+ /** Unmanaged build override module, merged over the generated options if present. */
31
+ export const BUN_BUILD_OVERRIDE = "bun-build.override.ts";
32
+ /** Unmanaged bunfig override (documented; merging TOML is left to the user). */
33
+ export const BUN_APP_OVERRIDES = ["bunfig.override.toml", BUN_DEV_OVERRIDE, BUN_BUILD_OVERRIDE];
34
+
35
+ /**
36
+ * Root `bunfig.toml`: pin bun's HOISTED linker workspace-wide.
37
+ *
38
+ * bun's default (isolated) linker instantiates a peer dependency once PER
39
+ * distinct peer context, so a package with many peers (`@mastra/*`) resolves to
40
+ * two peer-hash variants of the SAME `@mastra/core` version - and TypeScript then
41
+ * rejects passing a value built against one to an API typed by the other
42
+ * ("separate declarations of a private property"). The hoisted linker de-dupes to
43
+ * a single flat copy (npm-style), which is what keeps identity-based singletons
44
+ * (AppKit `CacheManager`, Mastra classes, one React) coherent - the same coherence
45
+ * the old cross-workspace `.pnpmfile.cjs` bridge used to guarantee.
46
+ */
47
+ export class RootBunfigFile extends TextFile {
48
+ constructor(project: Project) {
49
+ super(project, "bunfig.toml", {
50
+ marker: false,
51
+ readonly: true,
52
+ lines: [
53
+ '# ~~ Generated by projen. To modify, edit .projenrc.ts and run "bunx projen".',
54
+ "# Hoisted (flat) node_modules so a peer dep like @mastra/core resolves to ONE",
55
+ "# copy - the isolated default splits it into peer-hash variants and breaks",
56
+ "# identity-based singletons + cross-package types.",
57
+ "[install]",
58
+ 'linker = "hoisted"',
59
+ "",
60
+ ],
61
+ });
62
+ }
63
+ }
64
+
65
+ /**
66
+ * `bunfig.toml`: wire `bun-plugin-tailwind` into the DEV SERVER.
67
+ *
68
+ * Only `[serve.static]` is set - that is the one config bun reads for plugins. The
69
+ * `bun build` CLI does NOT load `bunfig.toml` plugins (a documented limitation),
70
+ * so the production build goes through `Bun.build()`'s JS API in `build.ts`, which
71
+ * passes the plugin directly. A `[build]` section here would be dead config that
72
+ * falsely implies `bun build ./index.html` applies Tailwind, so it is omitted.
73
+ */
74
+ export class BunfigFile extends TextFile {
75
+ constructor(project: Project) {
76
+ super(project, "bunfig.toml", {
77
+ marker: true,
78
+ readonly: true,
79
+ lines: [
80
+ "# Tailwind v4 for the dev server (Bun.serve reads [serve.static] plugins).",
81
+ "# Production build applies the plugin via Bun.build's API in build.ts - the",
82
+ "# `bun build` CLI does not load bunfig plugins, so there is no [build] here.",
83
+ "[serve.static]",
84
+ 'plugins = ["bun-plugin-tailwind"]',
85
+ "",
86
+ ],
87
+ });
88
+ }
89
+ }
90
+
91
+ /** `dev.ts`: a `Bun.serve()` fullstack dev server with React HMR. */
92
+ export class BunDevServerFile extends TextFile {
93
+ constructor(project: Project) {
94
+ super(project, "dev.ts", {
95
+ marker: true,
96
+ readonly: true,
97
+ lines: String.raw`
98
+ import { existsSync } from "node:fs";
99
+ import index from "./index.html";
100
+
101
+ // Unmanaged override (${BUN_DEV_OVERRIDE}): its default export is merged over the
102
+ // generated serve options - later wins - so a package tweaks the dev server
103
+ // without editing this generated file.
104
+ let options: Bun.ServeFunctionOptions<unknown, {}> = {
105
+ port: Number(process.env.PORT ?? 5173),
106
+ development: { hmr: true, console: true },
107
+ routes: { "/*": index },
108
+ };
109
+
110
+ const overrideUrl = new URL("./${BUN_DEV_OVERRIDE}", import.meta.url);
111
+ if (existsSync(overrideUrl)) {
112
+ const mod = await import(overrideUrl.href);
113
+ const override = typeof mod.default === "function" ? await mod.default() : mod.default;
114
+ options = { ...options, ...override };
115
+ }
116
+
117
+ const server = Bun.serve(options);
118
+ console.log("dev server running at " + server.url.href);
119
+ `
120
+ .trimStart()
121
+ .split("\n"),
122
+ });
123
+ }
124
+ }
125
+
126
+ /** `build.ts`: a `Bun.build()` production bundle (Tailwind via the plugin API). */
127
+ export class BunBuildFile extends TextFile {
128
+ constructor(project: Project) {
129
+ super(project, "build.ts", {
130
+ marker: true,
131
+ readonly: true,
132
+ lines: String.raw`
133
+ import { existsSync } from "node:fs";
134
+ import tailwind from "bun-plugin-tailwind";
135
+
136
+ // Unmanaged override (${BUN_BUILD_OVERRIDE}): its default export is merged over
137
+ // the generated build options - later wins.
138
+ let options: Bun.BuildConfig = {
139
+ entrypoints: ["./index.html"],
140
+ outdir: "./dist",
141
+ minify: true,
142
+ sourcemap: "linked",
143
+ plugins: [tailwind],
144
+ };
145
+
146
+ const overrideUrl = new URL("./${BUN_BUILD_OVERRIDE}", import.meta.url);
147
+ if (existsSync(overrideUrl)) {
148
+ const mod = await import(overrideUrl.href);
149
+ const override = typeof mod.default === "function" ? await mod.default() : mod.default;
150
+ options = { ...options, ...override, plugins: [...(options.plugins ?? []), ...(override.plugins ?? [])] };
151
+ }
152
+
153
+ const result = await Bun.build(options);
154
+ if (!result.success) {
155
+ for (const message of result.logs) console.error(message);
156
+ process.exit(1);
157
+ }
158
+ console.log("built " + result.outputs.length + " files to " + options.outdir);
159
+ `
160
+ .trimStart()
161
+ .split("\n"),
162
+ });
163
+ }
164
+ }
package/src/packages.ts CHANGED
@@ -85,18 +85,20 @@ function nestingTagsFromSegments(segments: readonly string[]): string[] {
85
85
  /** Matches a barrel `index.<ext>` (as a basename or a posix path tail). */
86
86
  const BARREL_RE = /(^|\/)index\.(ts|tsx|js|jsx|mjs|cjs)$/;
87
87
 
88
- /** Basenames this toolchain generates (projen manifests/tsconfigs + vite config). */
88
+ /** Basenames this toolchain generates (projen manifests/tsconfigs + bun app scaffolding). */
89
89
  const GENERATED_BASENAMES = new Set([
90
90
  "package.json",
91
91
  "tsconfig.json",
92
92
  "tsconfig.dev.json",
93
- "vite.config.ts",
93
+ "bunfig.toml",
94
+ "dev.ts",
95
+ "build.ts",
94
96
  ]);
95
97
 
96
98
  /**
97
99
  * True if the file matches the watcher's generated-file heuristic: projen manifest
98
- * basenames, package-root barrels (`index.ts`), vite config, or declaration files.
99
- * Other read-only toolchain output (e.g. openapi artifacts) is not covered here.
100
+ * basenames, package-root barrels (`index.ts`), bun app scaffolding, or declaration
101
+ * files. Other read-only toolchain output (e.g. openapi artifacts) is not covered here.
100
102
  */
101
103
  export function isGeneratedFile(file: string): boolean {
102
104
  const base = file.split(sep).pop() ?? "";
@@ -67,8 +67,8 @@ const DEFAULT_CATALOG: Catalog = {
67
67
  "react-dom": "^19.2.4",
68
68
  "@types/react": "^19.2.2",
69
69
  "@types/react-dom": "^19.2.2",
70
- vite: "^7.1.14",
71
- "@vitejs/plugin-react": "^5.0.4",
70
+ // Tailwind v4 compiler for bun's dev server + `Bun.build` (the `app` tag).
71
+ "bun-plugin-tailwind": "^0.1.2",
72
72
  "@types/node": "^24.6.0",
73
73
  "@types/express": "^5.0.5",
74
74
  express: "^5.1.0",
@@ -95,14 +95,26 @@ const DEFAULT_CATALOG: Catalog = {
95
95
  };
96
96
 
97
97
  /**
98
- * Build allowances every workspace needs, because the engine itself is what
99
- * drags each one in: `esbuild` arrives with tsx (so it has to be built for any
100
- * task to run at all), and `unrs-resolver` is the native binding behind
101
- * `eslint-import-resolver-typescript`, which projen's eslint component adds to
102
- * every generated project. Leaving either unlisted greets a freshly bootstrapped
103
- * workspace with pnpm's "Ignored build scripts" warning on its first install.
98
+ * Build allowances every workspace needs. These matter for the Databricks Apps
99
+ * pnpm install (which reads `pnpm-workspace.yaml`) and for bun (mirrored into the
100
+ * root `package.json` `trustedDependencies`): `unrs-resolver` is the native
101
+ * binding behind `eslint-import-resolver-typescript`, which projen's eslint
102
+ * component adds to every generated project, and `esbuild` is still pulled in by
103
+ * parts of the toolchain. Leaving either unlisted greets a freshly bootstrapped
104
+ * workspace with an "Ignored build scripts" warning on its first install.
104
105
  */
105
- const DEFAULT_ALLOW_BUILDS: AllowBuilds = { esbuild: true, "unrs-resolver": true };
106
+ const DEFAULT_ALLOW_BUILDS: AllowBuilds = {
107
+ esbuild: true,
108
+ "unrs-resolver": true,
109
+ // The `bun` npm package (a peer of `bun-plugin-tailwind`) ships a `bun.exe`
110
+ // placeholder and downloads the real platform binary in its postinstall. Left
111
+ // unbuilt, that `.exe` shim lands in `node_modules/.bin/bun` and makes projen's
112
+ // dax PATH walk `spawn ENOEXEC` on macOS/Linux, breaking EVERY task. Building it
113
+ // replaces the shim with a runnable binary.
114
+ bun: true,
115
+ // fastembed's native ONNX runtime (via `@mastra/fastembed`).
116
+ "onnxruntime-node": true,
117
+ };
106
118
 
107
119
  /**
108
120
  * pnpm settings this engine applies to every workspace, beyond members, catalog,
@@ -207,17 +219,66 @@ export class PnpmWorkspaceState {
207
219
  }
208
220
 
209
221
  /**
210
- * Fill `packages` from the project's attached subprojects.
222
+ * Emit `pnpm-workspace.yaml` directly.
223
+ *
224
+ * Under bun, projen's base `NodePackage` never runs `configurePnpm` (that call
225
+ * site is gated to `packageManager === PNPM`), so no `PnpmWorkspaceYaml`
226
+ * component is created. This engine still needs the file for the Databricks
227
+ * Apps platform, whose build phase installs with pnpm and reads the catalog +
228
+ * `allowBuilds`. The native component is just a `YamlFile` fed by a lazy
229
+ * `toJson(options)`, so constructing it directly (with the same options object
230
+ * whose arrays/objects are mutated in place up to synth) produces the identical
231
+ * file. Idempotent: skips if a `pnpm-workspace.yaml` component already exists
232
+ * (e.g. a future projen that DOES create it under bun).
233
+ */
234
+ public attachWorkspaceFile(project: Project): void {
235
+ const exists = project.files.some((file) => file.path === "pnpm-workspace.yaml");
236
+ if (!exists) new javascript.PnpmWorkspaceYaml(project, this.options);
237
+ }
238
+
239
+ /**
240
+ * Fill `packages` from the project's attached subprojects, then mirror the
241
+ * workspace shape into the root `package.json` so bun (which reads
242
+ * `workspaces`/`catalog` from the manifest, not `pnpm-workspace.yaml`) resolves
243
+ * the same members + catalog. `trustedDependencies` (bun's `allowBuilds` analog)
244
+ * is rendered by projen from its own allowlist, so the build allowances are
245
+ * routed there via `addAllowedScripts`.
211
246
  *
212
247
  * Called from the root's `preSynthesize`, which is the earliest point every
213
248
  * discovered package is attached - the list cannot be captured at
214
249
  * construction, since the root's scan runs after it.
215
250
  */
216
- public resolveMembers(project: Project): void {
251
+ public resolveMembers(project: Project, extraMembers: readonly string[] = []): void {
217
252
  const members = project.subprojects
218
253
  .map((sub) => toPosix(relative(project.outdir, sub.outdir)))
219
254
  .filter(Boolean);
255
+ // `extraMembers` are workspace siblings NOT attached as subprojects (e.g. the
256
+ // self-synthesizing `projen/` engine), so they must be added explicitly.
257
+ const all = [...members, ...extraMembers.map((m) => toPosix(m)).filter(Boolean)];
220
258
  // Replaces the array's CONTENTS, keeping the reference projen captured.
221
- this.packages.splice(0, this.packages.length, ...new Set(members.sort()));
259
+ this.packages.splice(0, this.packages.length, ...new Set(all.sort()));
260
+ this.mirrorToPackageJson(project);
261
+ }
262
+
263
+ /**
264
+ * Write `workspaces` / `catalog` / `overrides` into the root `package.json`
265
+ * (bun's source of truth) and feed the build allowances into projen's
266
+ * allowlist so bun's native `trustedDependencies` renders them.
267
+ */
268
+ private mirrorToPackageJson(project: Project): void {
269
+ const pkg = (project as { package?: javascript.NodePackage }).package;
270
+ if (!pkg) return;
271
+ pkg.addField("workspaces", [...this.packages]);
272
+ if (Object.keys(this.catalog).length > 0) {
273
+ pkg.addField("catalog", { ...this.catalog });
274
+ }
275
+ if (Object.keys(this.overrides).length > 0) {
276
+ pkg.addField("overrides", { ...this.overrides });
277
+ }
278
+ // projen renders bun's `trustedDependencies` from `allowedScripts`.
279
+ const allowed = Object.entries(this.allowBuilds)
280
+ .filter(([, on]) => on)
281
+ .map(([name]) => name);
282
+ if (allowed.length > 0) pkg.addAllowedScripts(...allowed);
222
283
  }
223
284
  }