@dbx-tools/projen 0.6.40 → 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.
@@ -0,0 +1,156 @@
1
+ #!/usr/bin/env -S bun
2
+ /**
3
+ * `bun tasks/publish.ts <version> [--registry <url>] [--exclude <dir>] [--dry-run]`
4
+ * - set a release version on every workspace member and publish each non-private
5
+ * one with `bun publish`.
6
+ *
7
+ * Bun has no `pnpm -r publish`, so this loop is the recursive-publish stand-in.
8
+ * It leans on native bun for everything bun already does:
9
+ *
10
+ * - **version stamping** is `bun pm pkg set version=<version>` per member (bun's
11
+ * native package.json editor - idempotent, edits only that dir's manifest, no
12
+ * git side effects; `bun pm version` is for bumping and errors on an unchanged
13
+ * version, so `pkg set` is the right tool for an exact release value);
14
+ * - **`workspace:` / `catalog:` rewriting** is NOT done here - `bun publish`
15
+ * strips both protocols in the PACKED tarball, resolving `workspace:*` to the
16
+ * sibling's version and `catalog:` to the root catalog entry. (Verified: a
17
+ * packed manifest shows `"@scope/x": "<version>"` and the real catalog range,
18
+ * while the on-disk manifest keeps the protocols.) Setting each member's
19
+ * version first is the only prerequisite, so a sibling resolves the release
20
+ * version rather than the disk default of `0.0.0`;
21
+ * - **`publishConfig` substitution** (compiled `lib/` entry points) and the
22
+ * `prepack` (compile) run are `bun publish`'s own pack behavior.
23
+ *
24
+ * `--dry-run` forwards to `bun publish`: it packs + validates (running prepack)
25
+ * but uploads nothing, so the `release` workflow is testable end-to-end via a
26
+ * `workflow_dispatch` run without anything reaching npm. `--registry` targets a
27
+ * non-default registry (a local verdaccio); `--exclude <dir>` (repeatable,
28
+ * repo-relative) skips a member that releases on its OWN tag namespace (e.g.
29
+ * `projen`, published by `projen-release`, not the main `release`).
30
+ *
31
+ * The disk manifests normally carry `version: 0.0.0` (projen owns them, read-only);
32
+ * this unlocks each only long enough to set the version + publish. The next
33
+ * `projen` synth restores them - the release version lives in the git tag.
34
+ */
35
+ import { chmodSync, existsSync, readFileSync, rmSync, statSync } from "node:fs";
36
+ import { dirname, join, resolve } from "node:path";
37
+ import { exec } from "@dbx-tools/core";
38
+ import { log } from "@dbx-tools/shared-core";
39
+ import { parse } from "yaml";
40
+
41
+ const logger = log.logger("dbx-tools:publish");
42
+
43
+ /**
44
+ * Workspace member dirs (absolute), read from the root `pnpm-workspace.yaml` - the
45
+ * file the engine keeps for the Databricks Apps pnpm deploy, which also lists every
46
+ * bun workspace member. (`bun pm ls` reports installed deps, not the member globs,
47
+ * so the manifest list is the source of truth.)
48
+ */
49
+ function workspaceMembers(root: string): string[] {
50
+ const file = join(root, "pnpm-workspace.yaml");
51
+ if (!existsSync(file)) return [];
52
+ const doc = parse(readFileSync(file, "utf8")) as { packages?: string[] } | null;
53
+ return (doc?.packages ?? []).map((m) => resolve(root, m));
54
+ }
55
+
56
+ /** Spawn `command` in `cwd` with `PATH` overridden, failing the task on non-zero. */
57
+ function run(cwd: string, command: string, args: string[], path: string): void {
58
+ exec.spawnSync(command, args, {
59
+ cwd,
60
+ stdout: "inherit",
61
+ stderr: "inherit",
62
+ stdin: "ignore",
63
+ check: true,
64
+ env: { ...process.env, PATH: path },
65
+ });
66
+ }
67
+
68
+ /** Make a projen-readonly manifest writable so `bun pm pkg set` / `bun publish` can edit it. */
69
+ function unlockManifest(pkgPath: string): void {
70
+ chmodSync(pkgPath, statSync(pkgPath).mode | 0o200);
71
+ }
72
+
73
+ /**
74
+ * PATH with the workspace-root `node_modules/.bin` prepended. `bun publish` runs
75
+ * each package's `prepack` (compile) through projen's dax shell, which resolves
76
+ * `tsc` off PATH - but under the hoisted linker `tsc` lives ONLY in the root
77
+ * `.bin`, not a per-package one, so without this the compile fails with
78
+ * `dax: tsc: command not found`. (In CI `bun install` already puts it there; this
79
+ * makes the task self-sufficient when invoked directly too.)
80
+ */
81
+ function enrichedPath(root: string): string {
82
+ const binDir = join(root, "node_modules", ".bin");
83
+ const current = process.env.PATH ?? "";
84
+ return current.split(":").includes(binDir) ? current : `${binDir}:${current}`;
85
+ }
86
+
87
+ const [version, ...rest] = process.argv.slice(2);
88
+ if (!version) {
89
+ logger.error(
90
+ "usage: bun tasks/publish.ts <version> [--registry <url>] [--exclude <dir>] [--dry-run]",
91
+ );
92
+ process.exit(1);
93
+ }
94
+ const registryIdx = rest.indexOf("--registry");
95
+ const registry = registryIdx >= 0 ? rest[registryIdx + 1] : undefined;
96
+ const dryRun = rest.includes("--dry-run");
97
+ // `--stamp-only`: set versions + refresh the lockfile, then STOP (no publish).
98
+ // The standalone `projen-release` uses this to version-stamp the workspace so its
99
+ // own `bun publish` (run separately, in `projen/`) resolves `workspace:*` siblings
100
+ // to the release version; publishing every member here would double-publish them.
101
+ const stampOnly = rest.includes("--stamp-only");
102
+ const excluded = new Set(
103
+ rest.reduce<string[]>((acc, arg, i) => (arg === "--exclude" ? [...acc, rest[i + 1]] : acc), []),
104
+ );
105
+
106
+ const root = process.cwd();
107
+ const path = enrichedPath(root);
108
+ const members = workspaceMembers(root)
109
+ .filter((dir) => existsSync(join(dir, "package.json")))
110
+ .filter((dir) => !excluded.has(resolve(root, dir).replace(`${resolve(root)}/`, "")));
111
+
112
+ // Set the version on EVERY member first (native `bun pm pkg set`), so a sibling
113
+ // published later has its `workspace:*` dep resolved to the release version - not
114
+ // the disk default of 0.0.0 - by `bun publish`'s own protocol rewriting.
115
+ logger.info(`setting ${version} across ${members.length} members`);
116
+ for (const dir of members) {
117
+ unlockManifest(join(dir, "package.json"));
118
+ run(dir, "bun", ["pm", "pkg", "set", `version=${version}`], path);
119
+ }
120
+
121
+ // Refresh the lockfile so `bun publish` resolves each `workspace:*` to the version
122
+ // just set. `bun publish`/`pm pack` reads the workspace version from the LOCKFILE,
123
+ // not the live manifest, and a plain `bun install` (even `--force`) does NOT
124
+ // re-resolve it after only a version-field change - deleting the lockfile first
125
+ // does. Without this every `workspace:*` dep would publish as the stale `0.0.0`.
126
+ const lockfile = join(root, "bun.lock");
127
+ if (existsSync(lockfile)) rmSync(lockfile);
128
+ logger.info("refreshing lockfile so workspace deps resolve to the release version");
129
+ run(root, "bun", ["install"], path);
130
+
131
+ if (stampOnly) {
132
+ logger.success(`stamped ${members.length} members @ ${version} (no publish)`);
133
+ process.exit(0);
134
+ }
135
+
136
+ const publishArgs = [
137
+ "--access",
138
+ "public",
139
+ ...(registry ? ["--registry", registry] : []),
140
+ ...(dryRun ? ["--dry-run"] : []),
141
+ ];
142
+ let published = 0;
143
+ for (const dir of members) {
144
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8")) as {
145
+ name?: string;
146
+ private?: boolean;
147
+ };
148
+ if (pkg.private) {
149
+ logger.info(`skip private ${pkg.name ?? dirname(dir)}`);
150
+ continue;
151
+ }
152
+ logger.info(`${dryRun ? "dry-run publishing" : "publishing"} ${pkg.name} @ ${version}`);
153
+ run(dir, "bun", ["publish", ...publishArgs], path);
154
+ published += 1;
155
+ }
156
+ logger.success(`${dryRun ? "dry-run: packed" : "published"} ${published} packages @ ${version}`);
package/src/vite.ts DELETED
@@ -1,99 +0,0 @@
1
- /**
2
- * `vite.config.ts` as a first-class projen file component.
3
- *
4
- * {@link ViteConfigFile} extends projen's `TextFile` and emits a generated,
5
- * read-only Vite config: the React plugin plus a runtime OVERRIDE chain. At Vite
6
- * startup the generated config looks for each unmanaged override module sitting
7
- * beside it (see {@link DEFAULT_VITE_OVERRIDES}) and, when present, merges that
8
- * module's default export over the generated config with Vite's `mergeConfig` - in
9
- * listed order, so later files win and absent ones are skipped. A package thus
10
- * tweaks Vite WITHOUT editing the projen-owned file.
11
- *
12
- * An override may be written in TypeScript or JavaScript. The generated config
13
- * reaches it through a dynamic `import()` of a runtime-computed URL, which Vite's
14
- * config bundling cannot inline and so leaves for Node to execute - and Node
15
- * strips types from a `.ts` file outside `node_modules` on its own. Being a
16
- * package-ROOT file (not under `src/`), neither the generated `vite.config.ts`
17
- * nor the override is in the package's `tsconfig` `include`, so their `node:*`
18
- * usage never trips the `ui` package's `compile` under the DOM-only tsconfig.
19
- */
20
- import { type Project, TextFile } from "projen";
21
-
22
- /**
23
- * Default unmanaged override modules, merged over the generated config in order
24
- * (later wins, absent files skipped). Both extensions are accepted so an existing
25
- * JavaScript override keeps working; the TypeScript one is listed last so it wins
26
- * where a package is mid-migration and still has both.
27
- *
28
- * Exported because ESLint has to ignore them: an override is a package-ROOT file
29
- * outside any `src/**` tsconfig include, so the type-aware parser cannot resolve
30
- * it to a project - the same reason the generated `vite.config.ts` is ignored.
31
- */
32
- export const DEFAULT_VITE_OVERRIDES = ["vite.config.override.js", "vite.config.override.ts"];
33
-
34
- /** Render the generated `vite.config.ts` source with the override chain inlined. */
35
- function renderViteConfig(overridePaths: string[]): string {
36
- const overrides = overridePaths.map((path) => ` ${JSON.stringify(path)},`).join("\n");
37
- return String.raw`
38
- import { existsSync } from "node:fs";
39
- import react from "@vitejs/plugin-react";
40
- import {
41
- defineConfig,
42
- mergeConfig,
43
- type ConfigEnv,
44
- type UserConfig,
45
- type UserConfigExport,
46
- } from "vite";
47
-
48
- // Unmanaged override modules (relative to this file), merged over the generated
49
- // config in order - later wins, absent files are skipped.
50
- const OVERRIDE_FILES = [
51
- ${overrides}
52
- ];
53
-
54
- async function resolveConfig(
55
- config: UserConfigExport,
56
- env: ConfigEnv,
57
- ): Promise<UserConfig> {
58
- if (typeof config === "function") {
59
- return await config(env);
60
- }
61
- return await config;
62
- }
63
-
64
- export default defineConfig(async (configEnv: ConfigEnv) => {
65
- let config: UserConfig = {
66
- plugins: [react()],
67
- };
68
-
69
- for (const file of OVERRIDE_FILES) {
70
- const overrideUrl = new URL(file, import.meta.url);
71
- if (!existsSync(overrideUrl)) {
72
- continue;
73
- }
74
- const overrideModule = await import(overrideUrl.href);
75
- const override = await resolveConfig(
76
- overrideModule.default as UserConfigExport,
77
- configEnv,
78
- );
79
- config = mergeConfig(config, override);
80
- }
81
-
82
- return config;
83
- });
84
- `.trimStart();
85
- }
86
-
87
- /**
88
- * A projen-owned, read-only `vite.config.ts` (React + the runtime override merge
89
- * chain described in the module docstring).
90
- */
91
- export class ViteConfigFile extends TextFile {
92
- constructor(project: Project) {
93
- super(project, "vite.config.ts", {
94
- marker: true,
95
- readonly: true,
96
- lines: renderViteConfig(DEFAULT_VITE_OVERRIDES).split("\n"),
97
- });
98
- }
99
- }