@dbx-tools/projen 0.6.46 → 0.6.48
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 +27 -5
- package/package.json +6 -10
- package/src/barrels.ts +35 -9
- package/src/clean.ts +3 -3
- package/src/codegen.ts +19 -0
- package/src/generated.ts +22 -2
- package/src/openapi.ts +1 -1
- package/src/project.ts +74 -11
- package/src/publish.ts +1 -1
- package/src/release.ts +1 -1
- package/src/scaffold.ts +1 -1
- package/src/tags.ts +12 -6
- package/src/vscode.ts +2 -2
- package/tasks/barrels.ts +2 -2
- package/tasks/clean.ts +3 -3
- package/tasks/openapi.ts +1 -1
- package/tasks/projenrc.ts +2 -2
- package/tasks/sync.ts +4 -4
package/README.md
CHANGED
|
@@ -70,6 +70,19 @@ Built-in tag mixins set runtime defaults for `shared`, `node`, `cli`, `server`,
|
|
|
70
70
|
`ui`, and `openapi`. Repo-specific mixins layer package-specific dependencies,
|
|
71
71
|
scripts, and generated files on top.
|
|
72
72
|
|
|
73
|
+
A tag layers over a shared compiler floor every package gets at construction:
|
|
74
|
+
ES2022 plus the web-platform globals available in every runtime, and deliberately
|
|
75
|
+
no DOM lib and no Node types, so agnostic code stays isomorphic. The tags are what
|
|
76
|
+
add an environment on top - `node` adds Node types, `ui` adds the DOM lib. That
|
|
77
|
+
floor is also where `jsx` lives, for a reason worth knowing before moving it:
|
|
78
|
+
packages resolve each other to SOURCE, so a consumer type-checks its dependency's
|
|
79
|
+
files under its own tsconfig. The moment any package re-exports a `.tsx` module,
|
|
80
|
+
every package that imports it - however far down the graph, whatever its tag -
|
|
81
|
+
fails with `TS6142: ... but '--jsx' is not set`. Setting `jsx` per consumer is the
|
|
82
|
+
wrong fix, since the consumer authors no JSX and cannot know a transitive
|
|
83
|
+
dependency started to. The option is inert without JSX in the graph: it selects
|
|
84
|
+
how JSX syntax compiles and adds no lib, global, or type dependency.
|
|
85
|
+
|
|
73
86
|
## Work With Package Discovery
|
|
74
87
|
|
|
75
88
|
```ts
|
|
@@ -94,9 +107,18 @@ barrels.generateBarrels();
|
|
|
94
107
|
```
|
|
95
108
|
|
|
96
109
|
`generateCodegen()` reads `package.json` `codegen.inputs` and writes generated
|
|
97
|
-
schema modules.
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
schema modules. They are written read-only, and the root ESLint task runs with
|
|
111
|
+
`--fix` (which fails on a read-only file), so each generated module is added to
|
|
112
|
+
`ignorePatterns` at synth - named individually via `codegen.codegenModulePaths()`,
|
|
113
|
+
never as a blanket `<package>/src/**`. A codegen package may hold hand-written
|
|
114
|
+
modules beside its generated ones, and those must stay linted.
|
|
115
|
+
|
|
116
|
+
`generateBarrels()` writes package-root `index.ts` barrels with module
|
|
117
|
+
namespaces and flat unique type exports, returning the number that actually
|
|
118
|
+
changed. A name two modules both declare is ambiguous and stays namespace-only —
|
|
119
|
+
except when one of them is generated: the hand-written module is the curated view
|
|
120
|
+
of the generated shape (`shared-genie`'s `genie-model.ts` extends its own
|
|
121
|
+
codegen'd `dashboards.ts`), so it owns the name and stays hoisted. A barrel whose export surface is unchanged is left untouched,
|
|
100
122
|
read-only bit included, so concurrent writers never collide over it. Every
|
|
101
123
|
package is attempted even if one fails; the failures are re-thrown together as an
|
|
102
124
|
`AggregateError` naming each package, rather than the first one abandoning the
|
|
@@ -167,13 +189,13 @@ file contract as the CLI.
|
|
|
167
189
|
- `barrels` / `moduleExports` - public entrypoint generation.
|
|
168
190
|
- `codegen` - `.d.ts` to zod schema generation.
|
|
169
191
|
- `openapi` - tsoa/OpenAPI package generation.
|
|
170
|
-
- `
|
|
192
|
+
- `bunApp` / `tsconfig` / `vscode` - generated support files/components.
|
|
171
193
|
- `generated` / `clean` / `watch` / `scaffold` - read-only file stamping,
|
|
172
194
|
cleanup, watchers, and synth orchestration.
|
|
173
195
|
- `publish` - packaging and tag-based release helpers.
|
|
174
196
|
- `engineRoot` - engine package root resolution for bootstrapped repos.
|
|
175
197
|
|
|
176
198
|
The engine registers its commands as projen tasks on the workspace root, so run
|
|
177
|
-
them with `
|
|
199
|
+
them with `bun run <task>` - `sync` (add `--watch`), `barrels`, `openapi`, and
|
|
178
200
|
`clean`. [`@dbx-tools/cli`](../packages/cli/dbx-tools) is only needed to
|
|
179
201
|
bootstrap a folder that has no `.projenrc.ts` or toolchain yet.
|
package/package.json
CHANGED
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@clack/prompts": "^1.7.0",
|
|
29
|
-
"@dbx-tools/core": "
|
|
30
|
-
"@dbx-tools/path": "
|
|
31
|
-
"@dbx-tools/shared-core": "
|
|
29
|
+
"@dbx-tools/core": "0.6.48",
|
|
30
|
+
"@dbx-tools/path": "0.6.48",
|
|
31
|
+
"@dbx-tools/shared-core": "0.6.48",
|
|
32
32
|
"commander": "^15.0.0",
|
|
33
33
|
"concurrently": "^10.0.3",
|
|
34
34
|
"constructs": "^10.6.0",
|
|
@@ -37,21 +37,17 @@
|
|
|
37
37
|
"oxc-parser": "^0.90.0",
|
|
38
38
|
"projen": "^0.101.16",
|
|
39
39
|
"ts-to-zod": "^5.1.0",
|
|
40
|
-
"tsx": "^4.23.0",
|
|
41
40
|
"yaml": "^2.9.0"
|
|
42
41
|
},
|
|
43
|
-
"pnpm": {},
|
|
44
42
|
"devEngines": {
|
|
45
43
|
"packageManager": {
|
|
46
|
-
"name": "
|
|
47
|
-
"version": ">=10",
|
|
44
|
+
"name": "bun",
|
|
48
45
|
"onFail": "ignore"
|
|
49
46
|
}
|
|
50
47
|
},
|
|
51
48
|
"main": "index.ts",
|
|
52
49
|
"license": "Apache-2.0",
|
|
53
|
-
"version": "0.6.
|
|
54
|
-
"packageManager": "pnpm@10.33.0",
|
|
50
|
+
"version": "0.6.48",
|
|
55
51
|
"types": "index.ts",
|
|
56
52
|
"type": "module",
|
|
57
53
|
"exports": {
|
|
@@ -63,5 +59,5 @@
|
|
|
63
59
|
"src",
|
|
64
60
|
"tasks"
|
|
65
61
|
],
|
|
66
|
-
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"
|
|
62
|
+
"//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"bunx projen\"."
|
|
67
63
|
}
|
package/src/barrels.ts
CHANGED
|
@@ -33,6 +33,15 @@
|
|
|
33
33
|
* name stays namespace-only. Names that collide with a generated namespace, or
|
|
34
34
|
* that a hand-authored `exports.ts` declares, are never hoisted (that file wins).
|
|
35
35
|
*
|
|
36
|
+
* One collision is NOT ambiguous, though: a HAND-WRITTEN module and a GENERATED
|
|
37
|
+
* one (a codegen `src/` module, recognised by its do-not-edit banner) declaring
|
|
38
|
+
* the same name. The hand-written module is by definition the curated view of the
|
|
39
|
+
* generated shape - `shared-genie`'s `genie-model.ts` extends and re-exports its
|
|
40
|
+
* own generated `dashboards.ts` - so it WINS and its name is still hoisted.
|
|
41
|
+
* Treating that pair as ambiguous is what silently dropped `GenieMessage`,
|
|
42
|
+
* `GenieSpace`, and `MessageStatus` from the barrel the moment the two modules
|
|
43
|
+
* became siblings, breaking every consumer importing them by name.
|
|
44
|
+
*
|
|
36
45
|
* The result gets a do-not-edit header + read-only bit (see `./generated`).
|
|
37
46
|
*/
|
|
38
47
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -40,7 +49,7 @@ import { join, relative } from "node:path";
|
|
|
40
49
|
import { find } from "@dbx-tools/path";
|
|
41
50
|
import { string } from "@dbx-tools/shared-core";
|
|
42
51
|
import isIdentifier from "is-identifier";
|
|
43
|
-
import { header, makeReadonly, makeWritable, type HeaderOpts } from "./generated.ts";
|
|
52
|
+
import { header, isGenerated, makeReadonly, makeWritable, type HeaderOpts } from "./generated.ts";
|
|
44
53
|
import { moduleExports, moduleStatements, type ModuleExport } from "./module-exports.ts";
|
|
45
54
|
import { isModuleFile, toPosix, recordedPackages, repoRoot } from "./packages.ts";
|
|
46
55
|
|
|
@@ -140,8 +149,10 @@ function namespaceLines(content: string): { ns: string; modulePath: string }[] {
|
|
|
140
149
|
* package's modules - `export type { ... }` for types, `export { ... }` for
|
|
141
150
|
* non-function values (classes, consts, enums, …). `export function` names are
|
|
142
151
|
* never hoisted; they stay namespace-only (`posixPath.toPosix`). A name declared
|
|
143
|
-
* by two or more modules is ambiguous and left namespace-only
|
|
144
|
-
*
|
|
152
|
+
* by two or more modules is ambiguous and left namespace-only - UNLESS exactly one
|
|
153
|
+
* of them is hand-written and the rest are generated, in which case the
|
|
154
|
+
* hand-written module owns the name (see the module doc). `suppress` names (a
|
|
155
|
+
* hand-authored `exports.ts` surface) are never hoisted so that file stays
|
|
145
156
|
* authoritative.
|
|
146
157
|
*/
|
|
147
158
|
function hoistUniqueExports(content: string, pkgDir: string, suppress: Set<string>): string {
|
|
@@ -157,17 +168,32 @@ function hoistUniqueExports(content: string, pkgDir: string, suppress: Set<strin
|
|
|
157
168
|
// Uniqueness is tallied over hoistable types AND values together - name ->
|
|
158
169
|
// { count, owning module }. Functions are excluded from hoisting and from
|
|
159
170
|
// this tally so they do not block a same-named type/class in another module.
|
|
160
|
-
|
|
171
|
+
//
|
|
172
|
+
// A generated module never claims a name a hand-written sibling also declares:
|
|
173
|
+
// it is counted only while no hand-written module owns the name, and it yields
|
|
174
|
+
// ownership as soon as one does. So a curated re-export
|
|
175
|
+
// (`genie-model.ts`'s `GenieMessage`, extending generated `dashboards.ts`)
|
|
176
|
+
// stays hoisted, while two HAND-WRITTEN modules claiming one name are still
|
|
177
|
+
// ambiguous and stay namespace-only.
|
|
178
|
+
const seen = new Map<string, { count: number; modulePath: string; generated: boolean }>();
|
|
161
179
|
const perModule = new Map<string, ModuleExport[]>();
|
|
162
180
|
for (const { modulePath } of namespaces) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
);
|
|
181
|
+
const file = join(pkgDir, modulePath.replace(/^\.\//, ""));
|
|
182
|
+
const exports = moduleExports(file).filter((e) => !e.isFunction);
|
|
166
183
|
perModule.set(modulePath, exports);
|
|
184
|
+
const generated = isGenerated(file);
|
|
167
185
|
for (const { name } of exports) {
|
|
168
186
|
const prior = seen.get(name);
|
|
169
|
-
if (prior)
|
|
170
|
-
|
|
187
|
+
if (!prior) {
|
|
188
|
+
seen.set(name, { count: 1, modulePath, generated });
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
// Hand-written beats generated, either direction, without counting as a clash.
|
|
192
|
+
if (prior.generated !== generated) {
|
|
193
|
+
if (prior.generated) seen.set(name, { count: 1, modulePath, generated });
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
prior.count += 1;
|
|
171
197
|
}
|
|
172
198
|
}
|
|
173
199
|
|
package/src/clean.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `clean` task (`
|
|
2
|
+
* The `clean` task (`bun run clean`): enumerate the workspace's generated files (plus every
|
|
3
3
|
* `node_modules` directory) and delete a chosen subset. This is the pure filesystem
|
|
4
4
|
* half - reusable enumerate/remove helpers; the task that drives them (argv `-y`, the
|
|
5
5
|
* `@clack/prompts` multiselect picker with all preselected, the TTY guard) lives in
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* `.projenrc.ts` imports the engine by SOURCE path (relative into the repo, e.g.
|
|
18
18
|
* `packages/node/projen/src/...`, or from an installed package such as
|
|
19
19
|
* `@dbx-tools/projen`), so even after deleting every barrel, manifest, and
|
|
20
|
-
* `.projen/*`, `
|
|
21
|
-
* `
|
|
20
|
+
* `.projen/*`, `bun run default` still rebuilds the whole tree. Removing `node_modules` additionally requires a
|
|
21
|
+
* `bun install` first - the engine's runtime deps live there - so a clean that takes
|
|
22
22
|
* `node_modules` must be followed by reinstall, then re-synth.
|
|
23
23
|
*/
|
|
24
24
|
import { existsSync, rmSync, statSync } from "node:fs";
|
package/src/codegen.ts
CHANGED
|
@@ -89,6 +89,25 @@ function parseInputArg(value: string): CodegenInput {
|
|
|
89
89
|
return { source: value.slice(0, eq), name: value.slice(eq + 1) };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The `src/` modules a set of `codegen.inputs` specs will emit, as package-relative
|
|
94
|
+
* posix paths - WITHOUT resolving or reading any input.
|
|
95
|
+
*
|
|
96
|
+
* This exists so callers that only need to know WHICH files are generated (the
|
|
97
|
+
* ESLint ignore list, above all) can ask without running codegen. The output names
|
|
98
|
+
* are derived from the specs alone, so it is safe to call during synth, before
|
|
99
|
+
* `node_modules` is resolved and before any manifest has been written to disk -
|
|
100
|
+
* hence taking the specs rather than a directory to read them from.
|
|
101
|
+
*
|
|
102
|
+
* Naming a package's generated modules is strictly better than ignoring its whole
|
|
103
|
+
* `src/`: a codegen package may also hold HAND-WRITTEN modules (shared-genie
|
|
104
|
+
* generates `dashboards.ts` next to a hand-written `genie-model.ts`), and those
|
|
105
|
+
* must stay linted.
|
|
106
|
+
*/
|
|
107
|
+
export function codegenModulePaths(inputs: readonly string[]): string[] {
|
|
108
|
+
return inputs.map((input) => `src/${parseInputArg(input).name}.ts`);
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
/**
|
|
93
112
|
* Resolve a codegen input to an absolute path. A `node_modules/...` source is
|
|
94
113
|
* searched for in each `node_modules` from the consuming package up to the
|
package/src/generated.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { chmodSync, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
12
12
|
|
|
13
|
+
/** First line of every banner {@link header} builds. */
|
|
14
|
+
const MARKER = "// GENERATED by";
|
|
15
|
+
|
|
13
16
|
const READONLY = 0o444;
|
|
14
17
|
const WRITABLE = 0o644;
|
|
15
18
|
|
|
@@ -46,12 +49,29 @@ export interface HeaderOpts {
|
|
|
46
49
|
|
|
47
50
|
/** Build the do-not-edit banner (line comments, for TS/JS). */
|
|
48
51
|
export function header(opts: HeaderOpts): string {
|
|
49
|
-
const lines = [
|
|
52
|
+
const lines = [`${MARKER} ${opts.tool} - DO NOT EDIT.`];
|
|
50
53
|
if (opts.source) lines.push(`// Regenerated from ${opts.source}.`);
|
|
51
54
|
lines.push("// Hand edits are overwritten on the next watch; this file is read-only.");
|
|
52
55
|
return `${lines.join("\n")}\n`;
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
/**
|
|
59
|
+
* True if the file exists and carries the do-not-edit banner {@link header}
|
|
60
|
+
* writes - i.e. a toolchain wrote it and a hand edit would be overwritten.
|
|
61
|
+
*
|
|
62
|
+
* Prefer this over {@link isReadonly} when the answer must survive a fresh
|
|
63
|
+
* clone: git tracks no read-only bit, so a generated file checked out before the
|
|
64
|
+
* first synth looks hand-authored by mode alone, while the banner is part of the
|
|
65
|
+
* committed content.
|
|
66
|
+
*/
|
|
67
|
+
export function isGenerated(file: string): boolean {
|
|
68
|
+
try {
|
|
69
|
+
return readFileSync(file, "utf8").startsWith(MARKER);
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
55
75
|
/**
|
|
56
76
|
* Prepend the do-not-edit header to a file some tool just wrote (e.g. a
|
|
57
77
|
* generated barrel `index.ts`), then set it read-only. Idempotent.
|
|
@@ -59,7 +79,7 @@ export function header(opts: HeaderOpts): string {
|
|
|
59
79
|
export function stampGenerated(file: string, opts: HeaderOpts): void {
|
|
60
80
|
makeWritable(file);
|
|
61
81
|
const body = readFileSync(file, "utf8");
|
|
62
|
-
const next = body.startsWith(
|
|
82
|
+
const next = body.startsWith(MARKER) ? body : `${header(opts)}\n${body}`;
|
|
63
83
|
writeFileSync(file, next);
|
|
64
84
|
makeReadonly(file);
|
|
65
85
|
}
|
package/src/openapi.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* OpenAPI client generator.
|
|
17
17
|
*
|
|
18
18
|
* `tsoa`, `typescript`, and `openapi-typescript` are loaded lazily (heavy, and only
|
|
19
|
-
* needed for `
|
|
19
|
+
* needed for `bun run openapi`), so importing this module stays cheap. `tsoa` and
|
|
20
20
|
* `typescript` are not engine dependencies at all - both are resolved out of the
|
|
21
21
|
* consuming workspace, which is where they already live.
|
|
22
22
|
*/
|
package/src/project.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { dirname, join, relative, resolve } from "node:path";
|
|
|
14
14
|
import { Component, IgnoreFile, Project, type TaskOptions, javascript, typescript } from "projen";
|
|
15
15
|
import { ReleaseTrigger } from "projen/lib/release";
|
|
16
16
|
import { generateBarrels } from "./barrels.ts";
|
|
17
|
-
import { generateCodegen } from "./codegen.ts";
|
|
17
|
+
import { codegenModulePaths, generateCodegen } from "./codegen.ts";
|
|
18
18
|
import { DBXToolsConfig, type DBXToolsConfigOptions } from "./dbx-tools-config.ts";
|
|
19
19
|
import { resolvePkgRoot } from "./engine-root.ts";
|
|
20
20
|
import { PnpmWorkspaceState, type DBXToolsPNPMWorkspaceOptions } from "./pnpm-workspace.ts";
|
|
@@ -172,7 +172,6 @@ export function applyCompilerOptions(
|
|
|
172
172
|
if (value === undefined) continue;
|
|
173
173
|
file.addOverride(`compilerOptions.${key}`, value);
|
|
174
174
|
}
|
|
175
|
-
if (compilerOptions.jsx) pkg.tsconfig?.addInclude("src/**/*.tsx");
|
|
176
175
|
}
|
|
177
176
|
|
|
178
177
|
/**
|
|
@@ -287,6 +286,17 @@ export function srcModuleExports(pkg: javascript.NodeProject): Record<string, st
|
|
|
287
286
|
* only: the specifier style is a property of the SOURCE, so a `ui` package (which
|
|
288
287
|
* publishes source and is excluded from the compiled surface) still has to accept
|
|
289
288
|
* and rewrite it.
|
|
289
|
+
*
|
|
290
|
+
* `jsx` is here for the same reason, and it is NOT a per-tag concern even though
|
|
291
|
+
* only React packages author `.tsx`. Packages resolve each other to SOURCE
|
|
292
|
+
* (`main: index.ts`), so a consumer type-checks its dependency's files under its
|
|
293
|
+
* OWN tsconfig: the moment any package re-exports a `.tsx` module, every package
|
|
294
|
+
* that imports it - however far down the graph, whatever its tag - fails with
|
|
295
|
+
* `TS6142: ... but '--jsx' is not set`. Setting it per consumer is the wrong fix
|
|
296
|
+
* (the consumer does not author JSX and has no way to know a transitive dependency
|
|
297
|
+
* started to), so the floor carries it. The option is inert for a package with no
|
|
298
|
+
* `.tsx` in its graph: it selects how JSX syntax COMPILES and adds no lib, no
|
|
299
|
+
* global, and no type dependency on its own.
|
|
290
300
|
*/
|
|
291
301
|
const SHARED_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions & {
|
|
292
302
|
rewriteRelativeImportExtensions: boolean;
|
|
@@ -296,6 +306,7 @@ const SHARED_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions & {
|
|
|
296
306
|
skipLibCheck: true,
|
|
297
307
|
allowImportingTsExtensions: true,
|
|
298
308
|
rewriteRelativeImportExtensions: true,
|
|
309
|
+
jsx: javascript.TypeScriptJsxMode.REACT_JSX,
|
|
299
310
|
};
|
|
300
311
|
|
|
301
312
|
/** Shared formatting rules, applied by projen's Prettier on whichever project is root. */
|
|
@@ -365,6 +376,13 @@ function defaultProjectOptions(options: DBXToolsProjectOptions): DBXToolsProject
|
|
|
365
376
|
...(isRoot ? {} : { npmAccess: javascript.NpmAccess.PUBLIC }),
|
|
366
377
|
buildWorkflow: false,
|
|
367
378
|
release: false,
|
|
379
|
+
// No `npm pack` step on any project. projen wires `package` into `build`, so
|
|
380
|
+
// `bunx projen build` would tarball all 36 manifests (root included) into
|
|
381
|
+
// gitignored `dist/js` on every CI run and never read them: publishing here
|
|
382
|
+
// is `bun publish` driving each package's own `prepack` (see
|
|
383
|
+
// {@link applyCompiledPublish} and the `publish` task), and `release: false`
|
|
384
|
+
// means no projen Publisher exists to consume the artifacts either.
|
|
385
|
+
package: false,
|
|
368
386
|
jest: false,
|
|
369
387
|
github: false,
|
|
370
388
|
npmignoreEnabled: false,
|
|
@@ -570,8 +588,8 @@ export class DBXToolsNodeProject extends javascript.NodeProject implements DBXTo
|
|
|
570
588
|
/**
|
|
571
589
|
* A single package (usually created by a root's scan), or a standalone
|
|
572
590
|
* compiling root. The agnostic tsconfig floor is applied at construction; the
|
|
573
|
-
* source-first package fields (`main`/`types`/`exports` -> `index.ts`) and
|
|
574
|
-
*
|
|
591
|
+
* source-first package fields (`main`/`types`/`exports` -> `index.ts`) and optional
|
|
592
|
+
* Bun app scaffolding are applied after. Per-tag deps/tsconfig arrive later
|
|
575
593
|
* via the {@link PACKAGE_TAG_MIXINS} the root applies.
|
|
576
594
|
*/
|
|
577
595
|
export class DBXToolsTypeScriptProject
|
|
@@ -608,6 +626,11 @@ export class DBXToolsTypeScriptProject
|
|
|
608
626
|
},
|
|
609
627
|
});
|
|
610
628
|
this.scope = scope;
|
|
629
|
+
// Pairs with `jsx` in SHARED_COMPILER_OPTIONS: projen's default `include` is
|
|
630
|
+
// `src/**/*.ts` only, which silently omits a `.tsx` file from the program
|
|
631
|
+
// instead of failing, so authoring a React component would otherwise need
|
|
632
|
+
// per-package tsconfig config to be compiled at all.
|
|
633
|
+
this.tsconfig?.addInclude("src/**/*.tsx");
|
|
611
634
|
this.dbxToolsConfig = new DBXToolsConfig(this, options);
|
|
612
635
|
// Source-first entry: point the package at its package-ROOT `index.ts` barrel
|
|
613
636
|
// so packages resolve each other's `@scope/pkg` imports to source.
|
|
@@ -624,9 +647,9 @@ export class DBXToolsTypeScriptProject
|
|
|
624
647
|
// directory auto-discovers `*.test.ts` recursively. But `bun test` EXITS 1
|
|
625
648
|
// when it matches no files (unlike the old `tsx --test 'glob'`, which was a
|
|
626
649
|
// 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 .
|
|
629
|
-
);
|
|
650
|
+
this.testTask.exec("bun test test", {
|
|
651
|
+
condition: 'find test -name "*.test.ts" 2>/dev/null | grep -q .',
|
|
652
|
+
});
|
|
630
653
|
if (options.bunApp ?? false) {
|
|
631
654
|
new BunfigFile(this);
|
|
632
655
|
new BunDevServerFile(this);
|
|
@@ -661,6 +684,37 @@ class GeneratedSource extends Component {
|
|
|
661
684
|
}
|
|
662
685
|
}
|
|
663
686
|
|
|
687
|
+
/**
|
|
688
|
+
* Make the ROOT `compile` / `test` tasks actually validate the workspace.
|
|
689
|
+
*
|
|
690
|
+
* projen gives a monorepo root empty `compile`/`test` tasks - a child's tasks
|
|
691
|
+
* are the child's business - so `bun run build` at the root type-checked nothing
|
|
692
|
+
* and ran no package tests. The fan-out is delegated to bun's own workspace
|
|
693
|
+
* filter rather than one `exec` per member, which matters three ways: bun runs
|
|
694
|
+
* the members in PARALLEL (measured ~2.5x faster across this repo than the
|
|
695
|
+
* sequential per-`cwd` form), a member that does not define the script is
|
|
696
|
+
* skipped instead of needing a guard, and the filter reads the workspace from
|
|
697
|
+
* `package.json` - so it stays correct when a package is added without a
|
|
698
|
+
* re-synth. A non-zero member exit still fails the run.
|
|
699
|
+
*
|
|
700
|
+
* `*` matches every workspace MEMBER and never the root itself, so the root
|
|
701
|
+
* task delegating to it cannot recurse. Members declared outside the scanned
|
|
702
|
+
* package roots (`extraWorkspaceMembers`) are workspace members too, so they are
|
|
703
|
+
* covered by the same filter.
|
|
704
|
+
*/
|
|
705
|
+
class WorkspaceValidationTasks extends Component {
|
|
706
|
+
private configured = false;
|
|
707
|
+
|
|
708
|
+
public override preSynthesize(): void {
|
|
709
|
+
if (this.configured) return;
|
|
710
|
+
this.configured = true;
|
|
711
|
+
const project = this.project as javascript.NodeProject;
|
|
712
|
+
for (const task of [project.compileTask, project.testTask]) {
|
|
713
|
+
task.exec(`bun run --filter '*' ${task.name}`);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
664
718
|
/**
|
|
665
719
|
* Ignore each `codegen`-declaring package's `src/` from the root ESLint config.
|
|
666
720
|
* Those modules are read-only (ts-to-zod); lint `--fix` otherwise EACCES-crashes
|
|
@@ -673,10 +727,17 @@ class EslintIgnoreCodegen extends Component {
|
|
|
673
727
|
const rootAbs = resolve(this.project.outdir);
|
|
674
728
|
for (const sub of this.project.subprojects) {
|
|
675
729
|
if (!(sub instanceof javascript.NodeProject)) continue;
|
|
676
|
-
const codegen = sub.package.manifest.codegen as { inputs?:
|
|
730
|
+
const codegen = sub.package.manifest.codegen as { inputs?: string[] } | undefined;
|
|
677
731
|
if (!codegen?.inputs?.length) continue;
|
|
678
732
|
const rel = toPosix(relative(rootAbs, sub.outdir));
|
|
679
|
-
|
|
733
|
+
// Ignore the generated MODULES, not the package's whole `src/`. A codegen
|
|
734
|
+
// package may hold hand-written modules next to its generated ones
|
|
735
|
+
// (shared-genie generates `dashboards.ts` beside a hand-written
|
|
736
|
+
// `genie-model.ts`), and a blanket `src/**` would silently stop linting
|
|
737
|
+
// them - the failure mode being invisible, since ESLint just reports less.
|
|
738
|
+
for (const module of codegenModulePaths(codegen.inputs)) {
|
|
739
|
+
eslint.addIgnorePattern(`${rel}/${module}`);
|
|
740
|
+
}
|
|
680
741
|
}
|
|
681
742
|
}
|
|
682
743
|
}
|
|
@@ -781,7 +842,7 @@ function registerRootTasks(project: javascript.NodeProject): void {
|
|
|
781
842
|
barrels: { exec: taskScript(project, "barrels.ts") },
|
|
782
843
|
openapi: { exec: taskScript(project, "openapi.ts") },
|
|
783
844
|
clean: { exec: taskScript(project, "clean.ts"), receiveArgs: true },
|
|
784
|
-
// `receiveArgs` forwards `--watch`, so `
|
|
845
|
+
// `receiveArgs` forwards `--watch`, so `bun run sync -- --watch` syncs once
|
|
785
846
|
// then starts the single node-path watcher loop.
|
|
786
847
|
sync: { exec: taskScript(project, "sync.ts"), receiveArgs: true },
|
|
787
848
|
});
|
|
@@ -901,7 +962,7 @@ function initProject(
|
|
|
901
962
|
prettier: Boolean(project.prettier),
|
|
902
963
|
tsconfigPath: "./tsconfig.json",
|
|
903
964
|
});
|
|
904
|
-
// Generated read-only outputs (barrels, openapi clients,
|
|
965
|
+
// Generated read-only outputs (barrels, openapi clients, app scripts, codegen).
|
|
905
966
|
// ESLint --fix cannot rewrite them; they are stamped by the barrel generator /
|
|
906
967
|
// openapi / codegen / projen.
|
|
907
968
|
for (const root of roots) {
|
|
@@ -995,6 +1056,8 @@ function initProject(
|
|
|
995
1056
|
project.with(...enabledTagMixins.map((t) => PACKAGE_TAG_MIXINS[t]));
|
|
996
1057
|
}
|
|
997
1058
|
|
|
1059
|
+
new WorkspaceValidationTasks(project);
|
|
1060
|
+
|
|
998
1061
|
new GeneratedSource(project);
|
|
999
1062
|
// The `bump` task (compute next version + commit + tag + push) is useful on
|
|
1000
1063
|
// any root; the actual publish is a tag-triggered GitHub workflow the caller
|
package/src/publish.ts
CHANGED
|
@@ -148,7 +148,7 @@ export function applyCompiledPublish(pkg: javascript.NodeProject): void {
|
|
|
148
148
|
const config = publishConfig(pkg);
|
|
149
149
|
if (config) pkg.package.addField("publishConfig", config);
|
|
150
150
|
|
|
151
|
-
// The release workflow publishes straight after `
|
|
151
|
+
// The release workflow publishes straight after `bun install`, with no build
|
|
152
152
|
// in between, so the compiled output has to be produced by the pack itself
|
|
153
153
|
// rather than assumed present. This also covers a bare `pnpm pack` and the
|
|
154
154
|
// bump task's local-registry publish.
|
package/src/release.ts
CHANGED
|
@@ -88,7 +88,7 @@ function publishSetupSteps(): JobStep[] {
|
|
|
88
88
|
*
|
|
89
89
|
* Declaring one also enlists it in the root's `bump`, which cuts BOTH tags at one
|
|
90
90
|
* shared version. The separate tag namespace still lets it be released alone
|
|
91
|
-
* (`cd <directory> &&
|
|
91
|
+
* (`cd <directory> && bun run bump`) for a consumer who wants only this package -
|
|
92
92
|
* but a routine root bump no longer leaves it behind, which is how the engine
|
|
93
93
|
* drifted to 0.1.24 while the packages reached 0.3.41.
|
|
94
94
|
*/
|
package/src/scaffold.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { repoRoot } from "./packages.ts";
|
|
|
9
9
|
* Re-run projen synth by executing `.projenrc.ts` with `node --import tsx` (no
|
|
10
10
|
* projen network re-exec).
|
|
11
11
|
*
|
|
12
|
-
* `post: true` runs the full flow - projen's post-synth `
|
|
12
|
+
* `post: true` runs the full flow - projen's post-synth `bun install` AND the
|
|
13
13
|
* post-synth barrels component - which is what the one-shot `sync` task
|
|
14
14
|
* wants. The default (`post: false`) sets `PROJEN_DISABLE_POST`, skipping both so
|
|
15
15
|
* the watch loop stays fast; there the caller rebuilds barrels explicitly.
|
package/src/tags.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tags, expressed as MIXINS (`constructs` `IMixin`).
|
|
3
3
|
*
|
|
4
|
-
* A tag names a target environment (React/
|
|
4
|
+
* A tag names a target environment (React/Bun, Node, agnostic, ...) - modeled on
|
|
5
5
|
* `databricks apps init` (AppKit): `ui`, `server`, `shared`. Any `src`-bearing folder
|
|
6
6
|
* under a package root is discovered automatically; path-derived tag
|
|
7
7
|
* candidates plus `packageTagPaths` decide which mixins apply. ("Scope" is
|
|
@@ -61,15 +61,18 @@ export const AGNOSTIC_COMPILER_OPTIONS: javascript.TypeScriptCompilerOptions = {
|
|
|
61
61
|
export const PACKAGE_TAG_MIXINS = {
|
|
62
62
|
// `ui`: a React COMPONENT LIBRARY (source-first, consumed by apps) - modeled
|
|
63
63
|
// on `@databricks/appkit-ui`. React + DOM lib + JSX, and the default `tsc`
|
|
64
|
-
// compile (typecheck). No
|
|
65
|
-
// `app`-tagged package (see below) that layers
|
|
64
|
+
// compile (typecheck). No app build / index.html: a full browser app is an
|
|
65
|
+
// `app`-tagged package (see below) that layers Bun's build tooling on top.
|
|
66
66
|
ui: create(projectPredicate.hasTag("ui"), (p) => {
|
|
67
67
|
p.addDeps("react@catalog:", "react-dom@catalog:");
|
|
68
68
|
p.addDevDeps("@types/react@catalog:", "@types/react-dom@catalog:");
|
|
69
|
+
// `jsx` is not set here: it is in the shared floor for EVERY package, because
|
|
70
|
+
// packages resolve each other to source and so every consumer of a `.tsx`
|
|
71
|
+
// module needs it too (see SHARED_COMPILER_OPTIONS). What this tag adds is the
|
|
72
|
+
// browser part - the DOM lib and React's types.
|
|
69
73
|
applyCompilerOptions(p, {
|
|
70
74
|
target: "ES2022",
|
|
71
75
|
lib: [...DOM_LIB],
|
|
72
|
-
jsx: javascript.TypeScriptJsxMode.REACT_JSX,
|
|
73
76
|
});
|
|
74
77
|
// A component library's standard subpath surface: `./react` (components),
|
|
75
78
|
// `./styles.css` (Tailwind entry), and `./package.json`. A package that
|
|
@@ -98,7 +101,6 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
98
101
|
applyCompilerOptions(p, {
|
|
99
102
|
target: "ES2022",
|
|
100
103
|
lib: [...DOM_LIB],
|
|
101
|
-
jsx: javascript.TypeScriptJsxMode.REACT_JSX,
|
|
102
104
|
// `@types/bun` (a root/subproject devDep) supplies the `Bun.*` globals the
|
|
103
105
|
// generated `dev.ts`/`build.ts` use; no `vite/client`.
|
|
104
106
|
types: ["bun"],
|
|
@@ -109,10 +111,14 @@ export const PACKAGE_TAG_MIXINS = {
|
|
|
109
111
|
});
|
|
110
112
|
// bun runs the generated scripts directly. `build` resets the compile task so
|
|
111
113
|
// `compile` bundles with `Bun.build` rather than `tsc`.
|
|
114
|
+
//
|
|
115
|
+
// No `preview` task: that pairing is Vite's, where `dev` runs the dev server
|
|
116
|
+
// and `preview` serves the already-built bundle. `Bun.serve` builds on request,
|
|
117
|
+
// so `preview` could only be spelled the same as `dev` - two names for one
|
|
118
|
+
// command, and a reader would reasonably assume the second one served `dist/`.
|
|
112
119
|
applyTasks(p, {
|
|
113
120
|
dev: { exec: "bun dev.ts" },
|
|
114
121
|
build: { exec: "bun build.ts" },
|
|
115
|
-
preview: { exec: "bun dev.ts" },
|
|
116
122
|
});
|
|
117
123
|
new BunfigFile(p);
|
|
118
124
|
new BunDevServerFile(p);
|
package/src/vscode.ts
CHANGED
|
@@ -45,7 +45,7 @@ export class DBXToolsVsCode extends Component {
|
|
|
45
45
|
detail:
|
|
46
46
|
"projen sync --watch - projenrc (.projenrc.ts + syncResynthPaths re-synth) + barrels + openapi watchers",
|
|
47
47
|
type: "shell",
|
|
48
|
-
command: "
|
|
48
|
+
command: "bun run sync -- --watch",
|
|
49
49
|
isBackground: true,
|
|
50
50
|
problemMatcher: [],
|
|
51
51
|
runOptions: { runOn: "folderOpen" },
|
|
@@ -59,7 +59,7 @@ export class DBXToolsVsCode extends Component {
|
|
|
59
59
|
label: "synth",
|
|
60
60
|
detail: "projen - synthesize all generated config",
|
|
61
61
|
type: "shell",
|
|
62
|
-
command: "
|
|
62
|
+
command: "bun run default",
|
|
63
63
|
problemMatcher: [],
|
|
64
64
|
},
|
|
65
65
|
],
|
package/tasks/barrels.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env -S
|
|
1
|
+
#!/usr/bin/env -S bun
|
|
2
2
|
import { sep } from "node:path";
|
|
3
3
|
import { generateBarrels } from "../src/barrels.ts";
|
|
4
4
|
import { log, string } from "@dbx-tools/shared-core";
|
|
@@ -35,7 +35,7 @@ if (process.argv.includes("--watch")) {
|
|
|
35
35
|
if (unowned.length) {
|
|
36
36
|
logger.warn(
|
|
37
37
|
`no recorded package owns ${string.pluralize(unowned.length, "change")}; ` +
|
|
38
|
-
"run `
|
|
38
|
+
"run `bun run default` (or touch .projenrc.ts) to pick up a new package folder",
|
|
39
39
|
);
|
|
40
40
|
}
|
|
41
41
|
return;
|
package/tasks/clean.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env -S
|
|
1
|
+
#!/usr/bin/env -S bun
|
|
2
2
|
import { relative } from "node:path";
|
|
3
3
|
import { listGeneratedFiles, listNodeModulesDirs, removePaths } from "../src/clean.ts";
|
|
4
4
|
import { log, string } from "@dbx-tools/shared-core";
|
|
@@ -18,8 +18,8 @@ if (targets.length === 0) {
|
|
|
18
18
|
|
|
19
19
|
const regenHint = (removedNodeModules: boolean): string =>
|
|
20
20
|
removedNodeModules
|
|
21
|
-
? "reinstall with `
|
|
22
|
-
: "regenerate with `
|
|
21
|
+
? "reinstall with `bun install`, then `bun run default`"
|
|
22
|
+
: "regenerate with `bun run default`";
|
|
23
23
|
|
|
24
24
|
if (yes) {
|
|
25
25
|
const n = removePaths(targets);
|
package/tasks/openapi.ts
CHANGED
package/tasks/projenrc.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env -S
|
|
1
|
+
#!/usr/bin/env -S bun
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { log } from "@dbx-tools/shared-core";
|
|
4
4
|
import { runSynth } from "../src/scaffold.ts";
|
|
@@ -25,7 +25,7 @@ const WATCH_PATHS = resynthWatchPaths();
|
|
|
25
25
|
// scoping re-synth (the one expensive, install-bearing step) to those manifests is what
|
|
26
26
|
// makes the watch intelligent vs stock `projen --watch`, which re-synths on any tree
|
|
27
27
|
// change. `{ dot: false }` keeps the default dotfile ignore group from pruning dotfile
|
|
28
|
-
// targets. (A one-shot "projenrc" is just a synth, i.e. `
|
|
28
|
+
// targets. (A one-shot "projenrc" is just a synth, i.e. `bun run default`.)
|
|
29
29
|
watchLoop(
|
|
30
30
|
"projenrc",
|
|
31
31
|
WATCH_PATHS,
|
package/tasks/sync.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env -S
|
|
1
|
+
#!/usr/bin/env -S bun
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import concurrently from "concurrently";
|
|
4
4
|
import { log } from "@dbx-tools/shared-core";
|
|
@@ -54,9 +54,9 @@ if (!process.argv.includes("--watch")) {
|
|
|
54
54
|
|
|
55
55
|
const { result } = concurrently(
|
|
56
56
|
[
|
|
57
|
-
{ command: `
|
|
58
|
-
{ command: `
|
|
59
|
-
{ command: `
|
|
57
|
+
{ command: `bun "${taskPath("projenrc.ts")}"`, name: "projenrc", prefixColor: "magenta" },
|
|
58
|
+
{ command: `bun "${taskPath("barrels.ts")}" --watch`, name: "barrels", prefixColor: "cyan" },
|
|
59
|
+
{ command: `bun "${taskPath("openapi.ts")}" --watch`, name: "openapi", prefixColor: "green" },
|
|
60
60
|
],
|
|
61
61
|
{
|
|
62
62
|
prefix: "name",
|