@dbx-tools/projen 0.9.112 → 0.9.117
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 +17 -10
- package/index.ts +1 -1
- package/package.json +5 -5
- package/src/barrels.ts +154 -101
- package/src/project-js.ts +12 -4
- package/tasks/publish.ts +7 -1
package/README.md
CHANGED
|
@@ -381,16 +381,23 @@ added to `ignorePatterns` at synth - named individually via
|
|
|
381
381
|
package may hold hand-written modules beside its generated ones, and those must
|
|
382
382
|
stay linted.
|
|
383
383
|
|
|
384
|
-
`generateBarrels()` writes package-root `index.ts`
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
384
|
+
`generateBarrels()` writes the package-root `index.ts` and, for packages with the
|
|
385
|
+
generated `"./*": "./src/*/index.ts"` export, read-only entrypoints in nested
|
|
386
|
+
source directories. For example, `src/example/one.ts` becomes importable through
|
|
387
|
+
`@scope/package/example`, and `src/example/wasm/one.ts` through
|
|
388
|
+
`@scope/package/example/wasm`. A sibling `exports.ts` can extend or override the
|
|
389
|
+
generated surface at the package root or any nested level. Existing hand-written
|
|
390
|
+
`index.ts` entrypoints remain authoritative.
|
|
391
|
+
|
|
392
|
+
Generated barrels include module namespaces and flat unique type/value exports;
|
|
393
|
+
the package root additionally exposes `PACKAGE_IDENTIFIER`. A name two modules
|
|
394
|
+
both declare is ambiguous and stays namespace-only, except when one is generated:
|
|
395
|
+
the hand-written module is the curated view of the generated shape
|
|
396
|
+
(`shared-genie`'s `genie-model.ts` extends its own codegen'd `dashboards.ts`), so
|
|
397
|
+
it owns the name and stays hoisted. An unchanged barrel remains untouched,
|
|
398
|
+
including its read-only bit, so concurrent writers do not collide. Every package
|
|
399
|
+
is attempted even if one fails; failures are re-thrown together as an
|
|
400
|
+
`AggregateError` naming each package.
|
|
394
401
|
|
|
395
402
|
## Configure pnpm Catalogs
|
|
396
403
|
|
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_IDENTIFIER = "@dbx-tools/projen";
|
|
6
|
-
export const PACKAGE_VERSION = "0.9.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.117";
|
|
7
7
|
export * as barrels from "./src/barrels.ts";
|
|
8
8
|
export * as bunApp from "./src/bun-app.ts";
|
|
9
9
|
export * as bunWorkflow from "./src/bun-workflow.ts";
|
package/package.json
CHANGED
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@clack/prompts": "^1.7.0",
|
|
33
|
-
"@dbx-tools/cli-args": "^0.9.
|
|
34
|
-
"@dbx-tools/core": "^0.9.
|
|
35
|
-
"@dbx-tools/path": "^0.9.
|
|
36
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
+
"@dbx-tools/cli-args": "^0.9.117",
|
|
34
|
+
"@dbx-tools/core": "^0.9.117",
|
|
35
|
+
"@dbx-tools/path": "^0.9.117",
|
|
36
|
+
"@dbx-tools/shared-core": "^0.9.117",
|
|
37
37
|
"commander": "^15.0.0",
|
|
38
38
|
"concurrently": "^10.0.3",
|
|
39
39
|
"constructs": "^10.6.0",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"main": "index.ts",
|
|
57
57
|
"license": "Apache-2.0",
|
|
58
|
-
"version": "0.9.
|
|
58
|
+
"version": "0.9.117",
|
|
59
59
|
"types": "index.ts",
|
|
60
60
|
"type": "module",
|
|
61
61
|
"exports": {
|
package/src/barrels.ts
CHANGED
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Barrel generator.
|
|
3
3
|
*
|
|
4
|
-
* For every package it writes
|
|
5
|
-
* `src
|
|
6
|
-
*
|
|
4
|
+
* For every package it writes the package-root `index.ts` plus generated
|
|
5
|
+
* `src/**/index.ts` subpath barrels when the package enables the generated
|
|
6
|
+
* nested export pattern. The root keeps its existing direct module
|
|
7
|
+
* namespaces; nested barrels expose each directory as
|
|
8
|
+
* `@scope/package/<directory>`. Both follow the same rules (see
|
|
9
|
+
* {@link isExcluded}):
|
|
7
10
|
* 1. a file/folder whose name starts with `_` is private and never barrelled;
|
|
8
11
|
* 2. test / `.d.ts` files are skipped;
|
|
9
12
|
* 3. a hand-authored `src/**/index.ts` is a subpath entry, while a generated
|
|
10
13
|
* nested index is the facade for its generated folder;
|
|
11
14
|
* 4. only files that actually contain an `export` are re-exported.
|
|
12
15
|
*
|
|
13
|
-
* A hand-authored `exports.ts` sitting next to
|
|
14
|
-
* override) is spliced in last and wins: its exports are appended,
|
|
15
|
-
* `export * as <ns>` whose namespace it also declares is
|
|
16
|
-
* takes priority. This keeps
|
|
17
|
-
* override individual exports.
|
|
16
|
+
* A hand-authored `exports.ts` sitting next to any generated `index.ts` (a
|
|
17
|
+
* Vite-style override) is spliced in last and wins: its exports are appended,
|
|
18
|
+
* and any generated `export * as <ns>` whose namespace it also declares is
|
|
19
|
+
* dropped so the custom one takes priority. This keeps barrels auto-generated
|
|
20
|
+
* while letting you add or override individual exports at every depth.
|
|
18
21
|
*
|
|
19
22
|
*
|
|
20
|
-
* Each eligible module becomes `export * as <name
|
|
21
|
-
* namespace from its path segments; invalid identifiers
|
|
22
|
-
* sorted by module path.
|
|
23
|
+
* Each eligible module becomes `export * as <name>` from its barrel-relative
|
|
24
|
+
* path (camelCase namespace from its path segments; invalid identifiers
|
|
25
|
+
* suffixed with `Module`), sorted by module path.
|
|
23
26
|
*
|
|
24
27
|
* On top of the namespace lines, every export that is UNIQUE across the package
|
|
25
28
|
* (declared in exactly one module) is also HOISTED to the barrel's top level, so
|
|
@@ -29,8 +32,8 @@
|
|
|
29
32
|
* `export { ... }`. The module namespaces stay either way, so a namespaced call
|
|
30
33
|
* site keeps working.
|
|
31
34
|
*
|
|
32
|
-
*
|
|
33
|
-
* from the package's own `package.json`.
|
|
35
|
+
* The package-root barrel also exports `PACKAGE_IDENTIFIER` and
|
|
36
|
+
* `PACKAGE_VERSION` from the package's own `package.json`.
|
|
34
37
|
*
|
|
35
38
|
* Uniqueness is tallied over types and values TOGETHER: a name carried by two
|
|
36
39
|
* modules is ambiguous whichever kind it is, and hoisting one module's value
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
* The result gets a do-not-edit header + read-only bit (see `./generated`).
|
|
51
54
|
*/
|
|
52
55
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
53
|
-
import { join, relative } from "node:path";
|
|
56
|
+
import { dirname, join, relative } from "node:path";
|
|
54
57
|
import { find } from "@dbx-tools/path";
|
|
55
58
|
import { json, stringUtils } from "@dbx-tools/shared-core";
|
|
56
59
|
import isIdentifier from "is-identifier";
|
|
@@ -133,9 +136,12 @@ function moduleSegmentToCamel(segment: string): string {
|
|
|
133
136
|
return head + rest.join("");
|
|
134
137
|
}
|
|
135
138
|
|
|
136
|
-
/** Derive a valid namespace identifier from a
|
|
139
|
+
/** Derive a valid namespace identifier from a barrel-relative module path. */
|
|
137
140
|
function modulePathToNamespace(modulePath: string): string {
|
|
138
|
-
const rel = modulePath
|
|
141
|
+
const rel = modulePath
|
|
142
|
+
.replace(/^\.\//, "")
|
|
143
|
+
.replace(/^src\//, "")
|
|
144
|
+
.replace(/\.(tsx?|jsx?|mjs|cjs)$/, "");
|
|
139
145
|
const segments = rel.split("/");
|
|
140
146
|
if (segments.at(-1) === "index" && segments.length > 1) segments.pop();
|
|
141
147
|
const names = segments.map(moduleSegmentToCamel);
|
|
@@ -153,11 +159,11 @@ function modulePathToNamespace(modulePath: string): string {
|
|
|
153
159
|
return name;
|
|
154
160
|
}
|
|
155
161
|
|
|
156
|
-
/** A
|
|
162
|
+
/** A relative module path parsed out of a generated `export * as <ns>` line. */
|
|
157
163
|
function namespaceLines(content: string): { ns: string; modulePath: string }[] {
|
|
158
164
|
const out: { ns: string; modulePath: string }[] = [];
|
|
159
165
|
for (const line of content.split("\n")) {
|
|
160
|
-
const match = /^export \* as (\w+) from "(
|
|
166
|
+
const match = /^export \* as (\w+) from "(\.\/.+)";\s*$/.exec(line);
|
|
161
167
|
if (match) out.push({ ns: match[1]!, modulePath: match[2]! });
|
|
162
168
|
}
|
|
163
169
|
return out;
|
|
@@ -174,7 +180,7 @@ function namespaceLines(content: string): { ns: string; modulePath: string }[] {
|
|
|
174
180
|
* hand-authored `exports.ts` surface) are never hoisted so that file stays
|
|
175
181
|
* authoritative.
|
|
176
182
|
*/
|
|
177
|
-
function hoistUniqueExports(content: string,
|
|
183
|
+
function hoistUniqueExports(content: string, barrelDir: string, suppress: Set<string>): string {
|
|
178
184
|
const namespaces = namespaceLines(content);
|
|
179
185
|
if (namespaces.length === 0) return content;
|
|
180
186
|
|
|
@@ -197,7 +203,7 @@ function hoistUniqueExports(content: string, pkgDir: string, suppress: Set<strin
|
|
|
197
203
|
const seen = new Map<string, { count: number; modulePath: string; generated: boolean }>();
|
|
198
204
|
const perModule = new Map<string, ModuleExport[]>();
|
|
199
205
|
for (const { modulePath } of namespaces) {
|
|
200
|
-
const file = join(
|
|
206
|
+
const file = join(barrelDir, modulePath.replace(/^\.\//, ""));
|
|
201
207
|
const exports = moduleExports(file).filter((e) => !e.isFunction);
|
|
202
208
|
perModule.set(modulePath, exports);
|
|
203
209
|
const generated = isGenerated(file);
|
|
@@ -271,8 +277,8 @@ function customExportNames(file: string): Set<string> {
|
|
|
271
277
|
* custom export wins - a plain `export *` cannot otherwise override an explicit
|
|
272
278
|
* `export * as`), then the whole module is re-exported last.
|
|
273
279
|
*/
|
|
274
|
-
function mergeCustomExports(content: string,
|
|
275
|
-
const customPath = join(
|
|
280
|
+
function mergeCustomExports(content: string, barrelDir: string): string {
|
|
281
|
+
const customPath = join(barrelDir, CUSTOM_EXPORTS_FILE);
|
|
276
282
|
if (!existsSync(customPath)) return content;
|
|
277
283
|
const overridden = customExportNames(customPath);
|
|
278
284
|
const kept = content.split("\n").filter((line) => {
|
|
@@ -324,108 +330,155 @@ function withPackageMetadata(content: string, pkgDir: string): string {
|
|
|
324
330
|
}
|
|
325
331
|
|
|
326
332
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* `export` - and 0 for a no-op. An edit *inside* an already-exported module (even
|
|
330
|
-
* adding a new named export) leaves the namespace `export * as … from "./src/x"`
|
|
331
|
-
* list identical, so it is a no-op.
|
|
333
|
+
* True when a generated index belongs to this barrel generator rather than a
|
|
334
|
+
* binding/codegen owner that also uses the repository's generated-file header.
|
|
332
335
|
*/
|
|
333
|
-
function
|
|
334
|
-
|
|
335
|
-
|
|
336
|
+
export function isGeneratedBarrel(file: string): boolean {
|
|
337
|
+
return existsSync(file) && readFileSync(file, "utf8").startsWith(header(BARREL_HEADER));
|
|
338
|
+
}
|
|
336
339
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// `export * as … from "./src/x"` list - and therefore this file - byte-for-byte
|
|
341
|
-
// identical.
|
|
342
|
-
const before = existsSync(rootBarrel) ? readFileSync(rootBarrel, "utf8") : undefined;
|
|
343
|
-
|
|
344
|
-
// The re-exportable module set under `src/`: every source file that actually
|
|
345
|
-
// exports something, minus private / test / declaration files and hand-authored
|
|
346
|
-
// `src/**/index.ts` subpath entries. A generated nested index is retained as
|
|
347
|
-
// that generated folder's public facade. `findFiles`
|
|
348
|
-
// yields posix paths relative to `srcDir`; `hasExport` parses each via
|
|
349
|
-
// `moduleStatements` and needs the absolute path.
|
|
350
|
-
const candidates = [...find.findFiles("**/*", { cwd: srcDir })]
|
|
340
|
+
/** Public direct modules and child-directory facades for one generated barrel. */
|
|
341
|
+
function barrelCandidates(barrelDir: string): string[] {
|
|
342
|
+
const files = [...find.findFiles("*", { cwd: barrelDir })]
|
|
351
343
|
.map(toPosix)
|
|
352
|
-
.filter(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
)
|
|
356
|
-
.filter((
|
|
357
|
-
|
|
344
|
+
.filter((file) => !file.includes("/"))
|
|
345
|
+
.filter((file) => file !== "index.ts" && file !== CUSTOM_EXPORTS_FILE)
|
|
346
|
+
.filter((file) => isModuleFile(file))
|
|
347
|
+
.filter((file) => !isExcluded(file, barrelDir))
|
|
348
|
+
.filter((file) => hasExport(join(barrelDir, file)));
|
|
349
|
+
const childIndexes = [...find.findFiles("*/index.ts", { cwd: barrelDir })]
|
|
350
|
+
.map(toPosix)
|
|
351
|
+
.filter((file) => file.split("/").length === 2)
|
|
352
|
+
.filter((file) => !isExcluded(file, barrelDir))
|
|
353
|
+
.filter((file) => hasExport(join(barrelDir, file)));
|
|
354
|
+
return [...files, ...childIndexes];
|
|
355
|
+
}
|
|
358
356
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
357
|
+
/** Write or remove one generated barrel and return whether its file changed. */
|
|
358
|
+
function generateBarrel(
|
|
359
|
+
pkgDir: string,
|
|
360
|
+
barrelDir: string,
|
|
361
|
+
moduleFiles: string[],
|
|
362
|
+
includePackageMetadata: boolean,
|
|
363
|
+
): number {
|
|
364
|
+
const barrel = includePackageMetadata ? join(pkgDir, "index.ts") : join(barrelDir, "index.ts");
|
|
365
|
+
if (!includePackageMetadata && existsSync(barrel) && !isGeneratedBarrel(barrel)) return 0;
|
|
366
|
+
const before = existsSync(barrel) ? readFileSync(barrel, "utf8") : undefined;
|
|
368
367
|
|
|
369
|
-
// Collapse each extensionless module path to one entry, preferring a TypeScript
|
|
370
|
-
// source over a sibling compiled artifact (`math.ts` wins over a committed
|
|
371
|
-
// `math.js`), so a module is barrelled exactly once. Then sort by module path.
|
|
372
368
|
const byModulePath = new Map<string, string>();
|
|
373
|
-
for (const
|
|
374
|
-
const stem =
|
|
369
|
+
for (const file of moduleFiles) {
|
|
370
|
+
const stem = file.replace(/(^|\/)index\.ts$/, "").replace(MODULE_EXT_RE, "");
|
|
375
371
|
const existing = byModulePath.get(stem);
|
|
376
|
-
if (!existing || (!isSourceExt(existing) && isSourceExt(
|
|
372
|
+
if (!existing || (!isSourceExt(existing) && isSourceExt(file))) byModulePath.set(stem, file);
|
|
377
373
|
}
|
|
378
|
-
const modulePaths = [...byModulePath.keys()].sort((
|
|
379
|
-
const namespacedModulePaths = modulePaths;
|
|
374
|
+
const modulePaths = [...byModulePath.keys()].sort((left, right) => left.localeCompare(right));
|
|
380
375
|
|
|
381
|
-
// No eligible modules -> no barrel: drop any stale root barrel and bail.
|
|
382
376
|
if (modulePaths.length === 0) {
|
|
383
|
-
if (
|
|
384
|
-
makeWritable(
|
|
385
|
-
rmSync(
|
|
377
|
+
if (before !== undefined && (includePackageMetadata || isGeneratedBarrel(barrel))) {
|
|
378
|
+
makeWritable(barrel);
|
|
379
|
+
rmSync(barrel, { force: true });
|
|
380
|
+
return includePackageMetadata ? 0 : 1;
|
|
386
381
|
}
|
|
387
382
|
return 0;
|
|
388
383
|
}
|
|
389
384
|
|
|
390
|
-
|
|
391
|
-
// segments (camelCase; invalid identifiers suffixed with `Module`). The
|
|
392
|
-
// extension is written because `tsc` rewrites it on emit
|
|
393
|
-
// (`rewriteRelativeImportExtensions`) - an extensionless specifier would be
|
|
394
|
-
// copied through verbatim and Node's ESM resolver cannot probe for it.
|
|
395
|
-
const namespaceExports = namespacedModulePaths
|
|
385
|
+
const namespaceExports = modulePaths
|
|
396
386
|
.map((stem) => {
|
|
397
|
-
const
|
|
387
|
+
const prefix = includePackageMetadata ? "./src/" : "./";
|
|
388
|
+
const modulePath = `${prefix}${byModulePath.get(stem)!}`;
|
|
398
389
|
return `export * as ${modulePathToNamespace(modulePath)} from "${modulePath}";`;
|
|
399
390
|
})
|
|
400
391
|
.join("\n");
|
|
401
|
-
let content =
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const
|
|
392
|
+
let content = includePackageMetadata
|
|
393
|
+
? `${PACKAGE_IDENTIFIER_LINE}\n${PACKAGE_VERSION_LINE}\n${namespaceExports}`
|
|
394
|
+
: namespaceExports;
|
|
395
|
+
const customDir = includePackageMetadata ? pkgDir : barrelDir;
|
|
396
|
+
const customPath = join(customDir, CUSTOM_EXPORTS_FILE);
|
|
405
397
|
const suppress = existsSync(customPath) ? customExportNames(customPath) : new Set<string>();
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
content =
|
|
411
|
-
|
|
412
|
-
// Package metadata is blanked for the structural comparison, then restored
|
|
413
|
-
// from package.json. If the result matches what's on disk, leave the file and
|
|
414
|
-
// its read-only bit untouched and report no change.
|
|
398
|
+
if (includePackageMetadata) {
|
|
399
|
+
suppress.add(PACKAGE_IDENTIFIER_EXPORT);
|
|
400
|
+
suppress.add(PACKAGE_VERSION_EXPORT);
|
|
401
|
+
}
|
|
402
|
+
content = hoistUniqueExports(content, includePackageMetadata ? pkgDir : barrelDir, suppress);
|
|
403
|
+
content = mergeCustomExports(content, customDir);
|
|
415
404
|
content = `${content.replace(/\n+$/, "")}\n`;
|
|
416
405
|
const template = `${header(BARREL_HEADER)}\n${content}`;
|
|
417
|
-
const next = withPackageMetadata(template, pkgDir);
|
|
418
|
-
|
|
419
|
-
|
|
406
|
+
const next = includePackageMetadata ? withPackageMetadata(template, pkgDir) : template;
|
|
407
|
+
const structurallySame = includePackageMetadata
|
|
408
|
+
? before !== undefined && withoutPackageMetadata(before) === template && before === next
|
|
409
|
+
: before === next;
|
|
410
|
+
if (structurallySame) return 0;
|
|
411
|
+
|
|
412
|
+
writeBarrel(barrel, next);
|
|
413
|
+
makeReadonly(barrel);
|
|
414
|
+
return 1;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Rebuild one package's nested and root barrels. Nested directories are processed
|
|
419
|
+
* deepest-first so a parent can export each child directory through its generated
|
|
420
|
+
* `index.ts`. The package root deliberately continues to export the underlying
|
|
421
|
+
* modules directly, preserving its existing namespaces while adding subpath entrypoints.
|
|
422
|
+
*/
|
|
423
|
+
function generateForPackage(pkgDir: string): number {
|
|
424
|
+
const srcDir = join(pkgDir, "src");
|
|
425
|
+
if (!existsSync(srcDir)) return 0;
|
|
426
|
+
|
|
427
|
+
const sourceFiles = [...find.findFiles("**/*", { cwd: srcDir })]
|
|
428
|
+
.map(toPosix)
|
|
429
|
+
.filter((file) => isModuleFile(file) || /(^|\/)index\.ts$/.test(file));
|
|
430
|
+
const manifest = json.parseRecord(readFileSync(join(pkgDir, "package.json"), "utf8"));
|
|
431
|
+
const exports = manifest?.exports;
|
|
432
|
+
const nestedExportsEnabled =
|
|
433
|
+
exports != null &&
|
|
434
|
+
typeof exports === "object" &&
|
|
435
|
+
!Array.isArray(exports) &&
|
|
436
|
+
(exports as Record<string, unknown>)["./*"] === "./src/*/index.ts";
|
|
437
|
+
const directories = new Set<string>();
|
|
438
|
+
for (const file of sourceFiles) {
|
|
439
|
+
const fileDir = dirname(file);
|
|
440
|
+
if (fileDir === "." || /(^|\/)_[^/]+/.test(fileDir)) continue;
|
|
441
|
+
let current = fileDir;
|
|
442
|
+
while (current !== ".") {
|
|
443
|
+
directories.add(current);
|
|
444
|
+
current = dirname(current);
|
|
445
|
+
}
|
|
420
446
|
}
|
|
421
447
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
448
|
+
let changed = 0;
|
|
449
|
+
const nestedDirectories = [...directories].sort((left, right) => {
|
|
450
|
+
const depth = (value: string): number => value.split("/").length;
|
|
451
|
+
return depth(right) - depth(left) || right.localeCompare(left);
|
|
452
|
+
});
|
|
453
|
+
for (const directory of nestedDirectories) {
|
|
454
|
+
const barrelDir = join(srcDir, directory);
|
|
455
|
+
if (nestedExportsEnabled) {
|
|
456
|
+
changed += generateBarrel(pkgDir, barrelDir, barrelCandidates(barrelDir), false);
|
|
457
|
+
} else {
|
|
458
|
+
const barrel = join(barrelDir, "index.ts");
|
|
459
|
+
if (isGeneratedBarrel(barrel)) {
|
|
460
|
+
makeWritable(barrel);
|
|
461
|
+
rmSync(barrel, { force: true });
|
|
462
|
+
changed += 1;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const candidates = sourceFiles
|
|
468
|
+
.filter((file) => !isGeneratedBarrel(join(srcDir, file)))
|
|
469
|
+
.filter((file) => !isExcluded(file, srcDir))
|
|
470
|
+
.filter((file) => hasExport(join(srcDir, file)));
|
|
471
|
+
const generatedIndexDirs = new Set(
|
|
472
|
+
candidates
|
|
473
|
+
.filter((file) => /(^|\/)index\.ts$/.test(file) && isGenerated(join(srcDir, file)))
|
|
474
|
+
.map((file) => file.replace(/(^|\/)index\.ts$/, "")),
|
|
475
|
+
);
|
|
476
|
+
const publicCandidates = candidates.filter((file) => {
|
|
477
|
+
if (/(^|\/)index\.ts$/.test(file)) return true;
|
|
478
|
+
return ![...generatedIndexDirs].some((dir) => dir && file.startsWith(`${dir}/`));
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
return changed + generateBarrel(pkgDir, srcDir, publicCandidates, true);
|
|
429
482
|
}
|
|
430
483
|
|
|
431
484
|
/**
|
package/src/project-js.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { type IConstruct } from "constructs";
|
|
|
15
15
|
import { Component, LogLevel, Project, type Task, javascript, typescript } from "projen";
|
|
16
16
|
import { JobPermission, type JobStep } from "projen/lib/github/workflows-model";
|
|
17
17
|
import type { ReleaseProjectOptions } from "projen/lib/release";
|
|
18
|
-
import { generateBarrels } from "./barrels.ts";
|
|
18
|
+
import { generateBarrels, isGeneratedBarrel } from "./barrels.ts";
|
|
19
19
|
|
|
20
20
|
import { BUN_APP_OVERRIDES, RootBunfigFile } from "./bun-app.ts";
|
|
21
21
|
import { BUN_VERSION } from "./bun-workflow.ts";
|
|
@@ -288,6 +288,8 @@ export function addPackageFiles(pkg: javascript.NodeProject, ...entries: string[
|
|
|
288
288
|
* non-`_` module, so those names are public through `.` either way - the subpaths
|
|
289
289
|
* just add a narrower import path. Deriving the map is what lets a tag carry the
|
|
290
290
|
* whole export layout, instead of each package hand-listing its own modules.
|
|
291
|
+
* The wildcard resolves generated nested barrels at every depth while exact
|
|
292
|
+
* exports continue to win for hand-authored entrypoints and assets.
|
|
291
293
|
*/
|
|
292
294
|
export function srcModuleExports(pkg: javascript.NodeProject): Record<string, string> {
|
|
293
295
|
const srcDir = join(pkg.outdir, "src");
|
|
@@ -310,9 +312,11 @@ export function srcModuleExports(pkg: javascript.NodeProject): Record<string, st
|
|
|
310
312
|
const index = ["index.ts", "index.tsx"].find((file) =>
|
|
311
313
|
existsSync(join(srcDir, entry.name, file)),
|
|
312
314
|
);
|
|
313
|
-
if (index
|
|
315
|
+
if (index && !isGeneratedBarrel(join(srcDir, entry.name, index))) {
|
|
316
|
+
exports[`./${entry.name}`] = `./src/${entry.name}/${index}`;
|
|
317
|
+
}
|
|
314
318
|
}
|
|
315
|
-
return exports;
|
|
319
|
+
return { ...exports, "./*": "./src/*/index.ts" };
|
|
316
320
|
}
|
|
317
321
|
|
|
318
322
|
/**
|
|
@@ -883,11 +887,15 @@ class GeneratedSource extends Component {
|
|
|
883
887
|
const sourceExports = Object.fromEntries(
|
|
884
888
|
modules.map((module) => [`./${module.slice("src/".length, -".ts".length)}`, `./${module}`]),
|
|
885
889
|
);
|
|
890
|
+
const nestedExport = current["./*"];
|
|
886
891
|
subproject.package.addField("exports", {
|
|
887
892
|
...Object.fromEntries(
|
|
888
|
-
Object.entries(current).filter(
|
|
893
|
+
Object.entries(current).filter(
|
|
894
|
+
([subpath]) => subpath !== "./*" && subpath !== "./package.json",
|
|
895
|
+
),
|
|
889
896
|
),
|
|
890
897
|
...sourceExports,
|
|
898
|
+
...(nestedExport ? { "./*": nestedExport } : {}),
|
|
891
899
|
...(current["./package.json"] ? { "./package.json": current["./package.json"] } : {}),
|
|
892
900
|
});
|
|
893
901
|
}
|
package/tasks/publish.ts
CHANGED
|
@@ -88,6 +88,12 @@ function compiledPublishTargets(value: unknown): string[] {
|
|
|
88
88
|
return Object.values(value).flatMap(compiledPublishTargets);
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
export function compiledPublishTargetExists(dir: string, target: string): boolean {
|
|
92
|
+
if (!target.includes("*")) return existsSync(resolve(dir, target));
|
|
93
|
+
const staticRoot = target.slice(0, target.indexOf("*")).replace(/\/+$/, "");
|
|
94
|
+
return existsSync(resolve(dir, staticRoot));
|
|
95
|
+
}
|
|
96
|
+
|
|
91
97
|
/**
|
|
92
98
|
* PATH with the workspace-root `node_modules/.bin` prepended. The root-level
|
|
93
99
|
* compile reaches each package's projen/dax task, which resolves `tsc` off PATH;
|
|
@@ -199,7 +205,7 @@ export async function main(argv: string[] = process.argv.slice(2)): Promise<void
|
|
|
199
205
|
if (skipCompile) {
|
|
200
206
|
const missing = compiled.flatMap((pkg) =>
|
|
201
207
|
pkg.compiledTargets
|
|
202
|
-
.filter((target) => !
|
|
208
|
+
.filter((target) => !compiledPublishTargetExists(pkg.dir, target))
|
|
203
209
|
.map((target) => `${pkg.name}:${target}`),
|
|
204
210
|
);
|
|
205
211
|
if (missing.length > 0) {
|