@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/src/project.ts CHANGED
@@ -22,7 +22,13 @@ import { applyCompiledPublish } from "./publish.ts";
22
22
  import { DBXToolsRelease, type StandaloneRelease } from "./release.ts";
23
23
  import { AGNOSTIC_COMPILER_OPTIONS, PACKAGE_TAG_MIXINS, type PackageTag } from "./tags.ts";
24
24
  import { DBXToolsRootTsconfig } from "./tsconfig.ts";
25
- import { DEFAULT_VITE_OVERRIDES, ViteConfigFile } from "./vite.ts";
25
+ import {
26
+ BUN_APP_OVERRIDES,
27
+ BunBuildFile,
28
+ BunDevServerFile,
29
+ BunfigFile,
30
+ RootBunfigFile,
31
+ } from "./bun-app.ts";
26
32
  import { DBXToolsVsCode } from "./vscode.ts";
27
33
  import {
28
34
  DEFAULT_PACKAGE_ROOTS,
@@ -147,11 +153,11 @@ function applyRepository(project: javascript.NodeProject, override?: string): vo
147
153
  });
148
154
  }
149
155
 
150
- /** Inherit a parent's package manager, else pnpm. */
156
+ /** Inherit a parent's package manager, else bun. */
151
157
  function inheritedPackageManager(
152
158
  parent: javascript.NodeProject | undefined,
153
159
  ): javascript.NodePackageManager {
154
- return parent?.package.packageManager ?? javascript.NodePackageManager.PNPM;
160
+ return parent?.package.packageManager ?? javascript.NodePackageManager.BUN;
155
161
  }
156
162
 
157
163
  /** Override a package's generated tsconfig `compilerOptions` (later-wins per key). */
@@ -328,12 +334,19 @@ export const PROJEN_VERSION = "^0.101.16";
328
334
  function defaultProjectOptions(options: DBXToolsProjectOptions): DBXToolsProjectOptions {
329
335
  const isRoot = options.parent === undefined;
330
336
  return {
331
- packageManager: javascript.NodePackageManager.PNPM,
337
+ // Bun owns install/run/build/test locally and in CI. projen renders
338
+ // `bun install`/`bunx` and a native `trustedDependencies` field from this.
339
+ // The engine still emits `pnpm-workspace.yaml` itself (see
340
+ // {@link PnpmWorkspaceState}) for the Databricks Apps platform, whose build
341
+ // phase installs with pnpm - so a deployed app keeps its catalog + build
342
+ // allowances even though the local/CI manager is bun.
343
+ packageManager: javascript.NodePackageManager.BUN,
332
344
  // Pinned rather than left to projen's "latest": 0.101.16 is the first release
333
- // whose `NodePackage` owns `pnpm-workspace.yaml` natively, and this engine
334
- // writes that file itself ({@link DBXToolsPNPMWorkspace}). Floating would let
335
- // an install cross that boundary silently, so the version the engine is known
336
- // to co-exist with is stated here and bumped deliberately.
345
+ // whose `NodePackage` renders bun's `trustedDependencies` natively. Under bun,
346
+ // projen does NOT create the `pnpm-workspace.yaml` component itself (that call
347
+ // site is gated to pnpm), so the engine constructs it directly ({@link
348
+ // PnpmWorkspaceState}). Floating would let an install cross that boundary
349
+ // silently, so the co-tested version is stated here and bumped deliberately.
337
350
  projenVersion: PROJEN_VERSION,
338
351
  defaultReleaseBranch: "main",
339
352
  projenrcJs: false,
@@ -403,8 +416,8 @@ function copiedGitIgnoreOptions(
403
416
  /**
404
417
  * The engine's `TypeScriptProject` defaults - a superset of {@link defaultProjectOptions}.
405
418
  * A DBXTools TS project can itself be the ROOT (a standalone compiling root), so the
406
- * same parent-based root/child logic applies; this just layers on tsx/typescript and
407
- * disables sample code.
419
+ * same parent-based root/child logic applies; this just layers on typescript +
420
+ * bun types and disables sample code. No `tsx`: bun runs `.ts` directly.
408
421
  */
409
422
  function defaultTypeScriptProjectOptions(
410
423
  options: DBXToolsTypeScriptProjectOptions,
@@ -417,15 +430,16 @@ function defaultTypeScriptProjectOptions(
417
430
  // ESLint is configured once on the ROOT (see initProject) and lints the whole
418
431
  // tree, so packages don't emit their own config. A caller can still override.
419
432
  eslint: false,
420
- devDeps: [...(base.devDeps ?? []), "tsx@^4.23.0", "typescript@^5.9.3"],
433
+ devDeps: [...(base.devDeps ?? []), "typescript@^5.9.3", "@types/bun@^1.3.14"],
421
434
  ...options,
422
435
  ...copiedGitIgnoreOptions(options),
423
436
  };
424
437
  }
425
438
 
426
- // Pinned to match the subproject defaults so pnpm resolves a single tsx/typescript
439
+ // Pinned to match the subproject defaults so bun resolves a single typescript
427
440
  // across the workspace (a bare name -> `*` could pull a second, newer major).
428
- const DEV_DEPS_ROOT: string[] = ["tsx@^4.23.0", "typescript@^5.9.3"];
441
+ // `@types/bun` gives the `Bun.*` globals the server/app tags now use.
442
+ const DEV_DEPS_ROOT: string[] = ["typescript@^5.9.3", "@types/bun@^1.3.14"];
429
443
 
430
444
  /** Options for {@link DBXToolsNodeProject} (the monorepo root). */
431
445
  export interface DBXToolsProjectOptions
@@ -478,13 +492,22 @@ export interface DBXToolsProjectOptions
478
492
  * `@dbx-tools/projen` engine in `projen/`, tagged `projen-v*`).
479
493
  */
480
494
  readonly standaloneReleases?: readonly StandaloneRelease[];
495
+ /**
496
+ * Extra workspace member paths (repo-relative, POSIX) to list in the workspace
497
+ * config ALONGSIDE the discovered `packageRoots` members - for a package that
498
+ * is synthesized by its OWN `.projenrc.ts` (so it isn't a root subproject) but
499
+ * should still resolve as a workspace sibling. The `@dbx-tools/projen` engine in
500
+ * `projen/` is the case: it synthesizes itself (avoiding a dogfooding cycle) yet
501
+ * is a member of the single bun workspace, so the root links it from source.
502
+ */
503
+ readonly extraWorkspaceMembers?: readonly string[];
481
504
  }
482
505
 
483
506
  /** Options for {@link DBXToolsTypeScriptProject} (a package, or a compiling root). */
484
507
  export interface DBXToolsTypeScriptProjectOptions
485
508
  extends Partial<typescript.TypeScriptProjectOptions>, DBXToolsProjectOptions {
486
- /** Emit a projen-owned `vite.config.ts`. */
487
- readonly viteConfig?: boolean;
509
+ /** Emit the projen-owned bun app scaffolding (`bunfig.toml`/`dev.ts`/`build.ts`). */
510
+ readonly bunApp?: boolean;
488
511
  }
489
512
 
490
513
  /**
@@ -498,6 +521,7 @@ export class DBXToolsNodeProject extends javascript.NodeProject implements DBXTo
498
521
  pnpmWorkspace?: PnpmWorkspaceState;
499
522
  rootTsconfig?: DBXToolsRootTsconfig;
500
523
  vsCode?: DBXToolsVsCode;
524
+ private readonly extraWorkspaceMembers: readonly string[];
501
525
 
502
526
  constructor(options: DBXToolsProjectOptions = {}) {
503
527
  const { name, scope } = resolveIdentity(options);
@@ -505,11 +529,12 @@ export class DBXToolsNodeProject extends javascript.NodeProject implements DBXTo
505
529
  options.release && options.releaseTrigger === undefined
506
530
  ? { releaseTrigger: ReleaseTrigger.tagged({ tags: ["v*"] }) }
507
531
  : {};
508
- // Before `super`, since `NodePackage` creates the native
509
- // `javascript.PnpmWorkspaceYaml` from these options inside the base
510
- // constructor and `this` is unreachable until it returns. Given nothing,
511
- // projen writes no workspace file at all - it never derives members from
512
- // `project.subprojects` (see `pnpm-workspace.ts`).
532
+ // Holds the workspace state (members/catalog/allowBuilds/overrides). Under
533
+ // bun, projen's base constructor does NOT create the `PnpmWorkspaceYaml`
534
+ // component (its `configurePnpm` call site is gated to pnpm), so this state's
535
+ // options are wired into a directly-constructed component below - AND mirrored
536
+ // into `package.json` (`workspaces`/`catalog`) for bun to read. The
537
+ // `pnpm-workspace.yaml` is still emitted for the Databricks Apps pnpm install.
513
538
  const pnpmWorkspace = new PnpmWorkspaceState(options);
514
539
  super({
515
540
  ...defaultProjectOptions(options),
@@ -522,7 +547,12 @@ export class DBXToolsNodeProject extends javascript.NodeProject implements DBXTo
522
547
  });
523
548
 
524
549
  this.pnpmWorkspace = pnpmWorkspace;
550
+ // Emit `pnpm-workspace.yaml` ourselves: under bun projen skips the native
551
+ // component, but the file is still required by the Databricks Apps platform
552
+ // (its build phase installs with pnpm and reads catalog + `allowBuilds`).
553
+ pnpmWorkspace.attachWorkspaceFile(this);
525
554
  this.scope = scope;
555
+ this.extraWorkspaceMembers = options.extraWorkspaceMembers ?? [];
526
556
  this.dbxToolsConfig = new DBXToolsConfig(this, options);
527
557
  initProject(this, options);
528
558
  }
@@ -531,7 +561,8 @@ export class DBXToolsNodeProject extends javascript.NodeProject implements DBXTo
531
561
  super.preSynthesize();
532
562
  // Members come from the attached subprojects, which the root's scan appends
533
563
  // after construction - so the list is filled here, not in the constructor.
534
- this.pnpmWorkspace?.resolveMembers(this);
564
+ // `extraWorkspaceMembers` adds self-synthesizing siblings (e.g. `projen/`).
565
+ this.pnpmWorkspace?.resolveMembers(this, this.extraWorkspaceMembers);
535
566
  preSynthesizeProject(this);
536
567
  }
537
568
  }
@@ -588,8 +619,19 @@ export class DBXToolsTypeScriptProject
588
619
  "./package.json": "./package.json",
589
620
  });
590
621
  addPackageFiles(this, "index.ts", "src");
591
- this.testTask.exec("tsx --test 'test/**/*.test.ts'");
592
- if (options.viteConfig ?? false) new ViteConfigFile(this);
622
+ // `bun test` intercepts `node:test` (the suites keep using node:test) and
623
+ // runs it with bun's own fast runner. Args are FILTERS, not globs; a bare
624
+ // directory auto-discovers `*.test.ts` recursively. But `bun test` EXITS 1
625
+ // when it matches no files (unlike the old `tsx --test 'glob'`, which was a
626
+ // no-op), so guard it: only invoke when a `*.test.ts` exists, else succeed.
627
+ this.testTask.exec(
628
+ 'find test -name "*.test.ts" 2>/dev/null | grep -q . && bun test test || true',
629
+ );
630
+ if (options.bunApp ?? false) {
631
+ new BunfigFile(this);
632
+ new BunDevServerFile(this);
633
+ new BunBuildFile(this);
634
+ }
593
635
  initProject(this, options);
594
636
  }
595
637
 
@@ -746,15 +788,17 @@ function registerRootTasks(project: javascript.NodeProject): void {
746
788
  }
747
789
 
748
790
  /**
749
- * `tsx <rel>/tasks/<script>` command for a projen task, relative to `project.outdir`.
750
- * Resolves the engine's `tasks/` dir off its installed package root (via
751
- * {@link resolvePkgRoot}), so it works both in-repo and when the engine is a
752
- * dependency in a consumer's `node_modules` - no filesystem walking.
791
+ * `bun node_modules/@dbx-tools/projen/tasks/<script>` command for a projen task.
792
+ *
793
+ * Use the stable package symlink, never `require.resolve()`'s physical store
794
+ * path. A later install can change the peer-hash directory while leaving the
795
+ * package symlink valid; persisting the physical path made every generated task
796
+ * fail with ERR_MODULE_NOT_FOUND after such an update. bun runs the `.ts`
797
+ * directly (no tsx, no build step).
753
798
  */
754
- export function taskScript(project: javascript.NodeProject, script: string, args = ""): string {
755
- const scriptPath = join(resolvePkgRoot(), "tasks", script);
756
- const rel = toPosix(relative(resolve(project.outdir), scriptPath));
757
- return args ? `tsx ${rel} ${args}` : `tsx ${rel}`;
799
+ export function taskScript(_project: javascript.NodeProject, script: string, args = ""): string {
800
+ const scriptPath = toPosix(join("node_modules", "@dbx-tools", "projen", "tasks", script));
801
+ return args ? `bun ${scriptPath} ${args}` : `bun ${scriptPath}`;
758
802
  }
759
803
 
760
804
  /**
@@ -787,20 +831,21 @@ function initProject(
787
831
  project.package.file.readonly = false;
788
832
 
789
833
  // NodeProject has no built-in TS projenrc support (unlike TypeScriptProject), so
790
- // wire `.projenrc.ts` through the tsx runner - this also populates the `default`
791
- // task that `pnpm exec projen` runs (and that the `sync` watcher invokes to re-synth).
834
+ // wire `.projenrc.ts` through a runner - this also populates the `default` task
835
+ // that `bunx projen` runs (and that the `sync` watcher invokes to re-synth).
836
+ // The runner choice is immaterial since the exec is reset to plain `bun` below;
837
+ // `nodejs()` avoids declaring a `ts-node`/`tsx` dependency.
792
838
  new typescript.ProjenrcTs(project, {
793
- runner: typescript.TypeScriptRunner.tsx(),
839
+ runner: typescript.TypeScriptRunner.nodejs(),
794
840
  });
795
- // ProjenrcTs wraps that step in `npx -y -p tsx -c "tsx .projenrc.ts"` because the
796
- // tsx runner declares a `tsx` dependency (so it runs even uninstalled). tsx IS a
797
- // devDep here, so that wrapper is not merely redundant but harmful: `npx -c` exports
798
- // `npm_config_call="tsx .projenrc.ts"` into the environment, which every nested
799
- // `pnpm` inherits and then dies on ("Failed parsing JSON config key call"), failing
800
- // each subproject's post-synth install; the same `npx`/`npm` process also emits the
801
- // "Unknown env config" warnings for pnpm's `catalog`/`@jsr:registry`/etc. Reset to a
802
- // plain exec (tsx resolves from `node_modules/.bin`, which pnpm puts on PATH).
803
- project.defaultTask?.reset("tsx .projenrc.ts");
841
+ // bun runs `.projenrc.ts` directly (native TS, no loader to register). Reset to
842
+ // a plain `bun` exec rather than any wrapper: the default task is spawned by
843
+ // nested installs/synths, and a wrapper that exported `npm_config_*` broke them.
844
+ project.defaultTask?.reset("bun .projenrc.ts");
845
+
846
+ // Pin bun's hoisted linker workspace-wide (see RootBunfigFile) so a peer dep
847
+ // resolves to one copy and singletons/types stay coherent.
848
+ new RootBunfigFile(project);
804
849
 
805
850
  // Only reached on a ROOT (early-returned above otherwise), so the root devDeps
806
851
  // always apply; the self-dep is added only when the engine is an installed pkg.
@@ -863,11 +908,12 @@ function initProject(
863
908
  eslint.addIgnorePattern(`${root}/openapi/**`);
864
909
  eslint.addIgnorePattern(`${root}/**/index.ts`);
865
910
  }
866
- eslint.addIgnorePattern("**/vite.config.ts");
867
- // The unmanaged vite overrides live at the package root, outside any `src/**`
868
- // tsconfig include, so the type-aware parser cannot resolve them to a project.
869
- // They are hand-authored (not generated), but ESLint still cannot parse them.
870
- for (const override of DEFAULT_VITE_OVERRIDES) {
911
+ // The generated bun app scripts + unmanaged overrides live at the package root,
912
+ // outside any `src/**` tsconfig include, so the type-aware parser cannot resolve
913
+ // them to a project. ESLint still cannot parse them.
914
+ eslint.addIgnorePattern("**/dev.ts");
915
+ eslint.addIgnorePattern("**/build.ts");
916
+ for (const override of BUN_APP_OVERRIDES) {
871
917
  eslint.addIgnorePattern(`**/${override}`);
872
918
  }
873
919
  // Codegen packages declare `codegen.inputs` via mixins after construction; ignore
package/src/release.ts CHANGED
@@ -11,7 +11,44 @@ import { applyTasks, taskScript, type DBXToolsNodeProject } from "./project.ts";
11
11
 
12
12
  const NODE_VERSION = "lts/*";
13
13
  const NPM_REGISTRY_URL = "https://registry.npmjs.org";
14
- const PNPM_VERSION = "10.33.0";
14
+ const BUN_VERSION = "1.3.14";
15
+
16
+ /**
17
+ * The `release` workflow's version-stamp + publish step, as a shell script.
18
+ *
19
+ * Bun has no `pnpm -r publish` equivalent, so this drives the engine's
20
+ * `tasks/publish.ts` (shipped in the engine tarball, run via bun): it reads the
21
+ * workspace members from the root `package.json`, stamps the tag version onto
22
+ * every manifest (rewriting `@dbx-tools/*` `workspace:*` sibling deps to
23
+ * `^<version>` so the tarballs resolve each other), then `bun publish`es each
24
+ * non-`private` package. `bun publish` substitutes `publishConfig` (the compiled
25
+ * `lib/` entry points) at pack time, runs `prepack` (compile) so `lib/` exists,
26
+ * and honors `NPM_CONFIG_PROVENANCE`.
27
+ *
28
+ * Two ways in: a pushed `<prefix>*` tag (the real release - `GITHUB_REF_NAME` is
29
+ * the version) and a manual `workflow_dispatch` (no tag, so a throwaway
30
+ * `0.0.0-dry.<run>` version is used and `--dry-run` is FORCED regardless of the
31
+ * input, since a dispatch never has a tag to publish as). The `dry_run` input
32
+ * (default true) is what lets a maintainer exercise the whole workflow - setup,
33
+ * install, stamp, compile, pack, validate - with nothing reaching npm.
34
+ */
35
+ function BUN_PUBLISH_SCRIPT(tagPrefix: string, excludeDirs: readonly string[]): string {
36
+ const script = "node_modules/@dbx-tools/projen/tasks/publish.ts";
37
+ const excludes = excludeDirs.map((dir) => ` --exclude ${dir}`).join("");
38
+ return [
39
+ 'if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then',
40
+ // A manual run has no tag: use a throwaway version and never really publish.
41
+ ' VERSION="0.0.0-dry.${GITHUB_RUN_NUMBER}"',
42
+ " DRY_RUN=--dry-run",
43
+ "else",
44
+ ` VERSION="\${GITHUB_REF_NAME#${tagPrefix}}"`,
45
+ // A tag push honors the input too, so a dry-run tag can be tested if wanted.
46
+ ' DRY_RUN="${DRY_RUN_INPUT}"',
47
+ "fi",
48
+ "chmod -R u+w . || true",
49
+ `bun ${script} "$VERSION"${excludes} $DRY_RUN`,
50
+ ].join("\n");
51
+ }
15
52
 
16
53
  interface PublishWorkflow {
17
54
  readonly name: string;
@@ -24,17 +61,18 @@ interface PublishWorkflow {
24
61
  function publishSetupSteps(): JobStep[] {
25
62
  return [
26
63
  { name: "Checkout", uses: "actions/checkout@v6", with: { "fetch-depth": 0 } },
27
- { name: "Setup pnpm", uses: "pnpm/action-setup@v5", with: { version: PNPM_VERSION } },
64
+ { name: "Setup Bun", uses: "oven-sh/setup-bun@v2", with: { "bun-version": BUN_VERSION } },
28
65
  {
29
66
  name: "Setup Node.js",
30
67
  uses: "actions/setup-node@v6",
31
68
  // setup-node writes the temporary npmrc that maps NODE_AUTH_TOKEN onto
32
69
  // npmjs. Omitting this leaves the secret in the environment but gives npm
33
70
  // no registry-scoped auth entry, and every publish fails with ENEEDAUTH.
71
+ // (Bun installs deps; publishing still goes through `npm publish`.)
34
72
  with: { "node-version": NODE_VERSION, "registry-url": NPM_REGISTRY_URL },
35
73
  },
36
- // The lockfile is intentionally untracked and may be absent or stale in CI.
37
- { name: "Install", run: "pnpm install --no-frozen-lockfile" },
74
+ // Bun's install; the lockfile may be absent or stale in CI so it is not frozen.
75
+ { name: "Install", run: "bun install" },
38
76
  ];
39
77
  }
40
78
 
@@ -160,12 +198,30 @@ export class DBXToolsRelease extends Component {
160
198
  // the publish job below overrides it with the `id-token` it needs.
161
199
  workflow.file?.addOverride("permissions", { contents: "read" });
162
200
  workflow.on({ push: { tags: [`${tagPrefix}*`] } });
201
+ // Manual trigger for testing the workflow WITHOUT reaching npm: a
202
+ // `workflow_dispatch` run has no tag, so the publish script forces
203
+ // `--dry-run` (pack + validate only). The `dry_run` input (default true)
204
+ // additionally lets a tag push be dry-run on demand.
205
+ workflow.file?.addOverride("on.workflow_dispatch", {
206
+ inputs: {
207
+ dry_run: {
208
+ description: "Pack and validate but do not upload to npm",
209
+ type: "boolean",
210
+ default: true,
211
+ },
212
+ },
213
+ });
163
214
  workflow.addJob("publish", {
164
215
  runsOn: ["ubuntu-latest"],
165
216
  // `id-token: write` lets npm mint the OIDC token for provenance attestation.
166
217
  permissions: { contents: JobPermission.READ, idToken: JobPermission.WRITE },
167
218
  timeoutMinutes: 30,
168
- env: { CI: "true" },
219
+ // `DRY_RUN_INPUT` is `--dry-run` when the dispatch input is true, else empty;
220
+ // the publish script also FORCES it on any `workflow_dispatch` run.
221
+ env: {
222
+ CI: "true",
223
+ DRY_RUN_INPUT: "${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}",
224
+ },
169
225
  steps: [...publishSetupSteps(), ...steps],
170
226
  ...(workingDirectory ? { defaults: { run: { workingDirectory } } } : {}),
171
227
  });
@@ -182,26 +238,25 @@ export class DBXToolsRelease extends Component {
182
238
  name: "release",
183
239
  tagPrefix: this.tagPrefix,
184
240
  steps: [
185
- // The pushed tag is the version: `<prefix>1.2.3` -> `1.2.3`. Set it on
186
- // every package (manifests are projen-readonly, so unlock them first).
241
+ // The pushed tag is the version: `<prefix>1.2.3` -> `1.2.3`. Stamp it on
242
+ // every workspace package (manifests are projen-readonly, so unlock
243
+ // first), rewriting `workspace:*` sibling deps to `^<version>` so the
244
+ // published tarballs resolve each other. `bun publish` honors
245
+ // `publishConfig` (compiled `lib/` entry points) and provenance.
187
246
  {
188
- name: "Set version from tag",
189
- run: [
190
- `VERSION="\${GITHUB_REF_NAME#${this.tagPrefix}}"`,
191
- "chmod -R u+w . || true",
192
- 'pnpm -r exec npm version "$VERSION" --no-git-tag-version --allow-same-version',
193
- ].join("\n"),
194
- },
195
- {
196
- name: "Publish to npm",
197
- // `pnpm -r publish` publishes every non-private package,
198
- // rewriting `workspace:*` deps to the published version. Provenance is
199
- // opt-in (omitted from each package's `publishConfig` so local
200
- // publishes work); CI turns it on here via `npm_config_provenance`.
201
- run: "pnpm -r publish --no-git-checks --access public",
247
+ name: "Set version from tag and publish",
248
+ // Exclude every standalone-release dir (e.g. `projen`): it publishes on
249
+ // its own `<prefix>-v*` tag via its own workflow, not the main `v*` one.
250
+ run: BUN_PUBLISH_SCRIPT(
251
+ this.tagPrefix,
252
+ this.standaloneReleases.map((s) => s.directory),
253
+ ),
202
254
  env: {
255
+ // `bun publish` authenticates via NPM_CONFIG_TOKEN (not NODE_AUTH_TOKEN,
256
+ // which is the `npm publish` convention). Set both so either tool works.
257
+ NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
203
258
  NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
204
- npm_config_provenance: "true",
259
+ NPM_CONFIG_PROVENANCE: "true",
205
260
  },
206
261
  },
207
262
  ],
@@ -210,34 +265,55 @@ export class DBXToolsRelease extends Component {
210
265
 
211
266
  /**
212
267
  * Emit a {@link StandaloneRelease}'s workflow: push `<prefix>1.2.3` and the
213
- * single package in `directory` (a non-workspace-member project) is published
214
- * at 1.2.3 via `npm pack` + `npm publish`. Its `package.json` is
215
- * projen-generated read-only, so it is unlocked before `npm version` rewrites
216
- * it. No bump math - the pushed tag IS the published version.
268
+ * single package in `directory` is published at 1.2.3 via `bun publish`.
269
+ *
270
+ * `directory` (e.g. `projen/`) is a WORKSPACE MEMBER whose `@dbx-tools/*` deps
271
+ * are `workspace:*`. `bun publish` resolves those to whatever version its
272
+ * SIBLINGS carry (via the lockfile), so before publishing we set the version on
273
+ * the package AND its in-scope siblings, then refresh the lockfile - otherwise
274
+ * the published engine would depend on the siblings' on-disk `0.0.0`. The
275
+ * `Install` step already ran `bun install` from the repo root (the member
276
+ * subdir walks up to it), so the workspace is linked. The manifests are
277
+ * projen-readonly, hence the `chmod`. A manual `workflow_dispatch` run has no
278
+ * tag, so it uses a throwaway version and forces `--dry-run` (nothing to npm).
217
279
  */
218
280
  private authorStandaloneReleaseWorkflow(
219
281
  project: DBXToolsNodeProject,
220
282
  { name, directory, tagPrefix }: StandaloneRelease,
221
283
  ): void {
284
+ // The engine's release also stamps its in-scope siblings so `bun publish`
285
+ // resolves their `workspace:*` to the release version. Stamping the WHOLE
286
+ // workspace is simplest and harmless (only `directory` is published here).
287
+ const stampScript = "node_modules/@dbx-tools/projen/tasks/publish.ts";
222
288
  this.authorPublishWorkflow(project, {
223
289
  name,
224
290
  tagPrefix,
225
- workingDirectory: directory,
226
291
  steps: [
227
- // The pushed tag is the version: `<prefix>1.2.3` -> `1.2.3`. package.json
228
- // is projen-generated read-only, so unlock it before `npm version` writes.
229
292
  {
230
- name: "Set version from tag",
293
+ name: "Set version from tag and publish",
231
294
  run: [
232
- "chmod u+w package.json",
233
- `npm version "\${GITHUB_REF_NAME#${tagPrefix}}" --no-git-tag-version --allow-same-version`,
295
+ 'if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then',
296
+ ' VERSION="0.0.0-dry.${GITHUB_RUN_NUMBER}"',
297
+ " DRY_RUN=--dry-run",
298
+ "else",
299
+ ` VERSION="\${GITHUB_REF_NAME#${tagPrefix}}"`,
300
+ ' DRY_RUN="${DRY_RUN_INPUT}"',
301
+ "fi",
302
+ "chmod -R u+w . || true",
303
+ // Set the version across every member + refresh the lockfile (the
304
+ // publish task's stamp phase), so bun resolves the engine's
305
+ // `workspace:*` sibling deps to the release version at pack time.
306
+ `bun ${stampScript} "$VERSION" --stamp-only`,
307
+ // Then publish ONLY the standalone directory.
308
+ `cd ${directory} && bun publish --access public $DRY_RUN`,
234
309
  ].join("\n"),
235
- },
236
- { name: "Pack", run: "pnpm pack --pack-destination dist/js" },
237
- {
238
- name: "Publish to npm",
239
- run: "npm publish dist/js/*.tgz --access public",
240
- env: { NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}" },
310
+ env: {
311
+ // `bun publish` authenticates via NPM_CONFIG_TOKEN (not NODE_AUTH_TOKEN,
312
+ // which is the `npm publish` convention). Set both so either tool works.
313
+ NPM_CONFIG_TOKEN: "${{ secrets.NPM_TOKEN }}",
314
+ NODE_AUTH_TOKEN: "${{ secrets.NPM_TOKEN }}",
315
+ NPM_CONFIG_PROVENANCE: "true",
316
+ },
241
317
  },
242
318
  ],
243
319
  });
package/src/tags.ts CHANGED
@@ -15,6 +15,7 @@
15
15
  */
16
16
  import type { IMixin as ConstructsMixin } from "constructs";
17
17
  import { javascript } from "projen";
18
+ import { BunBuildFile, BunDevServerFile, BunfigFile } from "./bun-app.ts";
18
19
  import { create } from "./mixin.ts";
19
20
  import {
20
21
  addPackageFiles,
@@ -25,7 +26,6 @@ import {
25
26
  srcModuleExports,
26
27
  } from "./project.ts";
27
28
  import * as projectPredicate from "./project-predicate.ts";
28
- import { ViteConfigFile } from "./vite.ts";
29
29
 
30
30
  /** Node compiler options: ES2022 lib + node types, deliberately no DOM. */
31
31
  const NODE_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions = {
@@ -81,30 +81,42 @@ export const PACKAGE_TAG_MIXINS = {
81
81
  "./package.json": "./package.json",
82
82
  });
83
83
  }),
84
- // `app`: a full browser app built + served by Vite (needs an `index.html`
85
- // entry). Self-contained React app: React + DOM lib + JSX + the vite toolchain
86
- // and app tasks (`dev`/`build`/`preview`). `build` resets the compile task, so
87
- // `compile` bundles with vite rather than `tsc`.
84
+ // `app`: a full browser app built + served by BUN (needs an `index.html`
85
+ // entry). Self-contained React app: React + DOM lib + JSX + bun's fullstack
86
+ // dev server (`dev.ts`, `Bun.serve` + HMR) and production bundle (`build.ts`,
87
+ // `Bun.build`). Tailwind v4 is compiled by `bun-plugin-tailwind` (wired in the
88
+ // generated `bunfig.toml`). No Vite.
88
89
  app: create(projectPredicate.hasTag("app"), (p) => {
89
90
  p.addDeps("react@catalog:", "react-dom@catalog:");
90
91
  p.addDevDeps(
91
- "vite@catalog:",
92
- "@vitejs/plugin-react@catalog:",
93
92
  "@types/react@catalog:",
94
93
  "@types/react-dom@catalog:",
94
+ // The Tailwind plugin the dev server + build load. Tailwind itself is a
95
+ // catalog dep the app declares (it also owns the Tailwind entry CSS).
96
+ "bun-plugin-tailwind@catalog:",
95
97
  );
96
98
  applyCompilerOptions(p, {
97
99
  target: "ES2022",
98
100
  lib: [...DOM_LIB],
99
101
  jsx: javascript.TypeScriptJsxMode.REACT_JSX,
100
- types: ["vite/client"],
102
+ // `@types/bun` (a root/subproject devDep) supplies the `Bun.*` globals the
103
+ // generated `dev.ts`/`build.ts` use; no `vite/client`.
104
+ types: ["bun"],
105
+ // `@/` -> `src/` alias, resolved by both tsc and bun's bundler (bun reads
106
+ // tsconfig `paths`). Replaces the old Vite `resolve.alias` for `@`.
107
+ baseUrl: ".",
108
+ paths: { "@/*": ["./src/*"] },
101
109
  });
110
+ // bun runs the generated scripts directly. `build` resets the compile task so
111
+ // `compile` bundles with `Bun.build` rather than `tsc`.
102
112
  applyTasks(p, {
103
- dev: { exec: "vite" },
104
- build: { exec: "vite build" },
105
- preview: { exec: "vite preview" },
113
+ dev: { exec: "bun dev.ts" },
114
+ build: { exec: "bun build.ts" },
115
+ preview: { exec: "bun dev.ts" },
106
116
  });
107
- new ViteConfigFile(p);
117
+ new BunfigFile(p);
118
+ new BunDevServerFile(p);
119
+ new BunBuildFile(p);
108
120
  // An app has a single root entry, not a component library's subpaths - so it
109
121
  // replaces the `ui` tag's `./react`/`./styles.css` surface with a `.` root.
110
122
  applyExports(p, {
@@ -147,9 +159,11 @@ export const PACKAGE_TAG_MIXINS = {
147
159
  ...NODE_COMPILER_OPTIONS,
148
160
  experimentalDecorators: true,
149
161
  });
162
+ // bun runs the server `.ts` directly (native TS, no tsx). `--watch` restarts
163
+ // on change - the tsx-watch replacement.
150
164
  applyTasks(p, {
151
- dev: { exec: "tsx watch src/server.ts" },
152
- start: { exec: "tsx src/server.ts" },
165
+ dev: { exec: "bun --watch src/server.ts" },
166
+ start: { exec: "bun src/server.ts" },
153
167
  });
154
168
  }),
155
169
  node: create(projectPredicate.hasTag("node"), (p) => {