@dbx-tools/projen 0.9.154 → 0.9.158
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 +36 -47
- package/index.ts +42 -20
- package/package.json +8 -8
- package/src/_task-command.ts +7 -7
- package/src/appkit-workspace.ts +278 -104
- package/src/barrels.ts +147 -259
- package/src/bun-app.ts +54 -10
- package/src/clean.ts +6 -6
- package/src/engine-root.ts +4 -4
- package/src/mixin.ts +0 -2
- package/src/module-exports.ts +4 -5
- package/src/npm-packaging.ts +128 -0
- package/src/npm-release.ts +200 -0
- package/src/packages.ts +11 -11
- package/src/project-js.ts +209 -103
- package/src/project-predicate.ts +5 -12
- package/src/project-py.ts +6 -6
- package/src/project.ts +7 -14
- package/src/publish.ts +3 -3
- package/src/python-node-bindings.ts +16 -12
- package/src/python-packaging.ts +177 -0
- package/src/release-options.ts +2 -2
- package/src/release.ts +3 -3
- package/src/shared-core-usage.ts +17 -4
- package/src/tags.ts +10 -9
- package/src/watch.ts +7 -9
- package/src/workspace-lock.ts +2 -2
- package/src/workspace-manifest.ts +2 -2
- package/tasks/appkit-deploy.ts +35 -15
- package/tasks/barrels.ts +6 -9
- package/tasks/bump.ts +1 -1
- package/tasks/clean.ts +3 -3
- package/tasks/cli.ts +3 -3
- package/tasks/compile-workspace.ts +8 -8
- package/tasks/dev-watch.ts +11 -11
- package/tasks/local-publish.ts +6 -6
- package/tasks/publish-npm.ts +11 -319
- package/tasks/publish-python.ts +2 -180
- package/tasks/publish.ts +6 -11
- package/tasks/python-node-bindings.ts +2 -2
- package/tasks/python-registry.ts +6 -6
- package/tasks/python-sync.ts +2 -3
- package/tasks/release-version.ts +1 -1
- package/tasks/release.ts +11 -11
- package/tasks/sync.ts +2 -2
- package/tasks/test-workspace.ts +9 -9
- package/tasks/verify-publications.ts +9 -5
- package/tasks/version-check.ts +5 -5
- /package/{tasks → src}/python-release.ts +0 -0
package/src/barrels.ts
CHANGED
|
@@ -1,81 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Generate one namespace-only package barrel plus one root-level barrel for
|
|
3
|
+
* every public source folder.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `@scope/package/<directory>`. Both follow the same rules (see
|
|
9
|
-
* {@link isExcluded}):
|
|
10
|
-
* 1. a file/folder whose name starts with `_` is private and never barrelled;
|
|
11
|
-
* 2. test / `.d.ts` files are skipped;
|
|
12
|
-
* 3. a hand-authored `src/**/index.ts` is a subpath entry, while a generated
|
|
13
|
-
* nested index is the facade for its generated folder;
|
|
14
|
-
* 4. only files that actually contain an `export` are re-exported.
|
|
5
|
+
* `src/auth/react/gate.ts` contributes to `index.ts`, `index.auth.ts`, and
|
|
6
|
+
* `index.auth.react.ts`. Package export maps point folder subpaths at those root
|
|
7
|
+
* files. Source folders never contain generated `index.ts` files, and
|
|
8
|
+
* individual modules never receive package subpaths.
|
|
15
9
|
*
|
|
16
|
-
*
|
|
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.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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.
|
|
26
|
-
*
|
|
27
|
-
* On top of the namespace lines, every export that is UNIQUE across the package
|
|
28
|
-
* (declared in exactly one module) is also HOISTED to the barrel's top level, so
|
|
29
|
-
* consumers can write `GenieMessage` or `DBXToolsNodeProject` instead of
|
|
30
|
-
* `genieModel.GenieMessage` / `project.DBXToolsNodeProject`. Types go out as
|
|
31
|
-
* `export type { ... }` (required under `isolatedModules`), values as
|
|
32
|
-
* `export { ... }`. The module namespaces stay either way, so a namespaced call
|
|
33
|
-
* site keeps working.
|
|
34
|
-
*
|
|
35
|
-
* The package-root barrel also exports `PACKAGE_IDENTIFIER` and
|
|
36
|
-
* `PACKAGE_VERSION` from the package's own `package.json`.
|
|
37
|
-
*
|
|
38
|
-
* Uniqueness is tallied over types and values TOGETHER: a name carried by two
|
|
39
|
-
* modules is ambiguous whichever kind it is, and hoisting one module's value
|
|
40
|
-
* beside another's same-named type would emit two conflicting re-exports. Such a
|
|
41
|
-
* name stays namespace-only. Names that collide with a generated namespace, or
|
|
42
|
-
* that a hand-authored `exports.ts` declares, are never hoisted (that file wins).
|
|
43
|
-
*
|
|
44
|
-
* One collision is NOT ambiguous, though: a HAND-WRITTEN module and a GENERATED
|
|
45
|
-
* one (a codegen `src/` module, recognised by its do-not-edit banner) declaring
|
|
46
|
-
* the same name. The hand-written module is by definition the curated view of the
|
|
47
|
-
* generated shape - `shared-genie`'s `genie-model.ts` extends and re-exports its
|
|
48
|
-
* own generated `dashboards.ts` - so it WINS and its name is still hoisted.
|
|
49
|
-
* Treating that pair as ambiguous is what silently dropped `GenieMessage`,
|
|
50
|
-
* `GenieSpace`, and `MessageStatus` from the barrel the moment the two modules
|
|
51
|
-
* became siblings, breaking every consumer importing them by name.
|
|
52
|
-
*
|
|
53
|
-
* The result gets a do-not-edit header + read-only bit (see `./generated`).
|
|
10
|
+
* Every output is stamped and read-only.
|
|
54
11
|
*/
|
|
55
|
-
import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { existsSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
56
13
|
import { dirname, join, relative } from "node:path";
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
14
|
+
import { findFilesSync } from "@dbx-tools/path";
|
|
15
|
+
import { parseRecord, tokenizeWithOptions, capitalize, pluralize } from "@dbx-tools/shared-core";
|
|
59
16
|
import isIdentifier from "is-identifier";
|
|
60
17
|
import { header, isGenerated, makeReadonly, makeWritable, type HeaderOpts } from "./generated.ts";
|
|
61
18
|
import { moduleExports, moduleStatements, type ModuleExport } from "./module-exports.ts";
|
|
62
19
|
import { isModuleFile, toPosix, recordedPackages, resolveRepoRoot } from "./packages.ts";
|
|
63
20
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
66
|
-
* 1. any path segment starting with `_` (private module or folder);
|
|
67
|
-
* 2. a test / spec file;
|
|
68
|
-
* 3. a `.d.ts` declaration;
|
|
69
|
-
* 4. a hand-authored `src/**/index.ts` - a subpath entry (e.g. `src/react/index.ts`
|
|
70
|
-
* behind a package's `./react` export), not a module to namespace into the barrel.
|
|
71
|
-
* Generated nested indexes are retained as the generated folder's facade.
|
|
72
|
-
*/
|
|
73
|
-
function isExcluded(relPath: string, srcDir: string): boolean {
|
|
21
|
+
/** Whether a source-relative module is private, test-only, declared, or an index. */
|
|
22
|
+
function isExcluded(relPath: string): boolean {
|
|
74
23
|
return (
|
|
75
24
|
/(^|\/)_/.test(relPath) ||
|
|
76
25
|
/\.(test|spec)\./.test(relPath) ||
|
|
77
26
|
/\.d\.ts$/.test(relPath) ||
|
|
78
|
-
|
|
27
|
+
/(^|\/)index\.(?:tsx?|jsx?|mts|cts)$/.test(relPath)
|
|
79
28
|
);
|
|
80
29
|
}
|
|
81
30
|
|
|
@@ -110,7 +59,7 @@ const BARREL_HEADER: HeaderOpts = {
|
|
|
110
59
|
source: "the exporting modules in ./src",
|
|
111
60
|
};
|
|
112
61
|
|
|
113
|
-
/** Generated package metadata exports
|
|
62
|
+
/** Generated package metadata exports. */
|
|
114
63
|
const PACKAGE_IDENTIFIER_EXPORT = "PACKAGE_IDENTIFIER";
|
|
115
64
|
const PACKAGE_IDENTIFIER_LINE = `export const ${PACKAGE_IDENTIFIER_EXPORT} = "";`;
|
|
116
65
|
const PACKAGE_IDENTIFIER_LINE_RE = /^export const PACKAGE_IDENTIFIER = .*;$/m;
|
|
@@ -120,9 +69,7 @@ const PACKAGE_VERSION_LINE_RE = /^export const PACKAGE_VERSION = .*;$/m;
|
|
|
120
69
|
|
|
121
70
|
/** `config-tools` / `config_tools` -> `configTools`; `local-fs` -> `localFS`. */
|
|
122
71
|
function moduleSegmentToCamel(segment: string): string {
|
|
123
|
-
const tokens = [
|
|
124
|
-
...stringUtils.tokenizeWithOptions({ lowerCase: true, capitalize: true }, segment),
|
|
125
|
-
];
|
|
72
|
+
const tokens = [...tokenizeWithOptions({ lowerCase: true, capitalize: true }, segment)];
|
|
126
73
|
if (tokens.length === 0) return segment;
|
|
127
74
|
const first = tokens[0]!;
|
|
128
75
|
const rest = tokens.slice(1);
|
|
@@ -151,7 +98,7 @@ function modulePathToNamespace(modulePath: string): string {
|
|
|
151
98
|
: names[0]! +
|
|
152
99
|
names
|
|
153
100
|
.slice(1)
|
|
154
|
-
.map((s) =>
|
|
101
|
+
.map((s) => capitalize(s))
|
|
155
102
|
.join("");
|
|
156
103
|
if (!isIdentifier(name)) {
|
|
157
104
|
name = `${name}Module`;
|
|
@@ -159,7 +106,7 @@ function modulePathToNamespace(modulePath: string): string {
|
|
|
159
106
|
return name;
|
|
160
107
|
}
|
|
161
108
|
|
|
162
|
-
/**
|
|
109
|
+
/** Relative modules parsed from generated namespace exports. */
|
|
163
110
|
function namespaceLines(content: string): { ns: string; modulePath: string }[] {
|
|
164
111
|
const out: { ns: string; modulePath: string }[] = [];
|
|
165
112
|
for (const line of content.split("\n")) {
|
|
@@ -169,42 +116,24 @@ function namespaceLines(content: string): { ns: string; modulePath: string }[] {
|
|
|
169
116
|
return out;
|
|
170
117
|
}
|
|
171
118
|
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
* hand-written module owns the name (see the module doc). `suppress` names (a
|
|
180
|
-
* hand-authored `exports.ts` surface) are never hoisted so that file stays
|
|
181
|
-
* authoritative.
|
|
182
|
-
*/
|
|
183
|
-
function hoistUniqueExports(content: string, barrelDir: string, suppress: Set<string>): string {
|
|
119
|
+
/** Append flat exports for names owned by exactly one module. */
|
|
120
|
+
function hoistUniqueExports(
|
|
121
|
+
content: string,
|
|
122
|
+
barrelDir: string,
|
|
123
|
+
modulePaths: readonly string[],
|
|
124
|
+
reserved: Set<string>,
|
|
125
|
+
): string {
|
|
184
126
|
const namespaces = namespaceLines(content);
|
|
185
|
-
if (namespaces.length === 0) return content;
|
|
186
127
|
|
|
187
|
-
|
|
188
|
-
// namespace (e.g. a `mixin.ts` exporting a `mixin` value alongside the
|
|
189
|
-
// `export * as mixin` line), so treat every namespace id as suppressed too.
|
|
190
|
-
const blocked = new Set<string>(suppress);
|
|
128
|
+
const blocked = new Set(reserved);
|
|
191
129
|
for (const { ns } of namespaces) blocked.add(ns);
|
|
192
130
|
|
|
193
|
-
// Uniqueness is tallied over hoistable types AND values together - name ->
|
|
194
|
-
// { count, owning module }. Functions are excluded from hoisting and from
|
|
195
|
-
// this tally so they do not block a same-named type/class in another module.
|
|
196
|
-
//
|
|
197
|
-
// A generated module never claims a name a hand-written sibling also declares:
|
|
198
|
-
// it is counted only while no hand-written module owns the name, and it yields
|
|
199
|
-
// ownership as soon as one does. So a curated re-export
|
|
200
|
-
// (`genie-model.ts`'s `GenieMessage`, extending generated `dashboards.ts`)
|
|
201
|
-
// stays hoisted, while two HAND-WRITTEN modules claiming one name are still
|
|
202
|
-
// ambiguous and stay namespace-only.
|
|
203
131
|
const seen = new Map<string, { count: number; modulePath: string; generated: boolean }>();
|
|
204
132
|
const perModule = new Map<string, ModuleExport[]>();
|
|
205
|
-
for (const
|
|
133
|
+
for (const modulePath of modulePaths) {
|
|
134
|
+
if (!modulePath.startsWith("./src/")) continue;
|
|
206
135
|
const file = join(barrelDir, modulePath.replace(/^\.\//, ""));
|
|
207
|
-
const exports = moduleExports(file)
|
|
136
|
+
const exports = moduleExports(file);
|
|
208
137
|
perModule.set(modulePath, exports);
|
|
209
138
|
const generated = isGenerated(file);
|
|
210
139
|
for (const { name } of exports) {
|
|
@@ -213,7 +142,6 @@ function hoistUniqueExports(content: string, barrelDir: string, suppress: Set<st
|
|
|
213
142
|
seen.set(name, { count: 1, modulePath, generated });
|
|
214
143
|
continue;
|
|
215
144
|
}
|
|
216
|
-
// Hand-written beats generated, either direction, without counting as a clash.
|
|
217
145
|
if (prior.generated !== generated) {
|
|
218
146
|
if (prior.generated) seen.set(name, { count: 1, modulePath, generated });
|
|
219
147
|
continue;
|
|
@@ -223,13 +151,12 @@ function hoistUniqueExports(content: string, barrelDir: string, suppress: Set<st
|
|
|
223
151
|
}
|
|
224
152
|
|
|
225
153
|
const lines: string[] = [];
|
|
226
|
-
for (const
|
|
154
|
+
for (const modulePath of modulePaths) {
|
|
227
155
|
const types: string[] = [];
|
|
228
156
|
const values: string[] = [];
|
|
229
157
|
for (const { name, isType } of perModule.get(modulePath) ?? []) {
|
|
230
158
|
if (blocked.has(name)) continue;
|
|
231
159
|
const entry = seen.get(name);
|
|
232
|
-
// Unique across the package AND this is the module that owns it.
|
|
233
160
|
if (!entry || entry.count !== 1 || entry.modulePath !== modulePath) continue;
|
|
234
161
|
(isType ? types : values).push(name);
|
|
235
162
|
}
|
|
@@ -237,55 +164,7 @@ function hoistUniqueExports(content: string, barrelDir: string, suppress: Set<st
|
|
|
237
164
|
if (types.length) lines.push(`export type { ${types.join(", ")} } from "${modulePath}";`);
|
|
238
165
|
}
|
|
239
166
|
if (lines.length === 0) return content;
|
|
240
|
-
return `${content.replace(/\n+$/, "")}\n${lines.join("\n")}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** Hand-authored override barrel: a sibling of the generated `index.ts`. */
|
|
244
|
-
const CUSTOM_EXPORTS_FILE = "exports.ts";
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Best-effort set of the top-level export names a module declares - named
|
|
248
|
-
* declarations, `export { x }` specifiers, `export * as ns`, and default. A bare
|
|
249
|
-
* `export *` re-exports opaque names that can't be resolved statically, so a custom
|
|
250
|
-
* `exports.ts` should name what it means to override explicitly.
|
|
251
|
-
*/
|
|
252
|
-
function customExportNames(file: string): Set<string> {
|
|
253
|
-
const names = new Set<string>();
|
|
254
|
-
const body = moduleStatements(file) as ReadonlyArray<Record<string, any>>;
|
|
255
|
-
const add = (node: Record<string, any> | undefined | null): void => {
|
|
256
|
-
if (node && typeof node.name === "string") names.add(node.name);
|
|
257
|
-
else if (node && typeof node.value === "string") names.add(node.value);
|
|
258
|
-
};
|
|
259
|
-
for (const stmt of body) {
|
|
260
|
-
if (stmt.type === "ExportDefaultDeclaration") {
|
|
261
|
-
names.add("default");
|
|
262
|
-
} else if (stmt.type === "ExportAllDeclaration") {
|
|
263
|
-
add(stmt.exported); // `export * as ns from ...`; a bare `export *` has none
|
|
264
|
-
} else if (stmt.type === "ExportNamedDeclaration") {
|
|
265
|
-
for (const spec of stmt.specifiers ?? []) add(spec.exported);
|
|
266
|
-
const decl = stmt.declaration;
|
|
267
|
-
if (decl?.id) add(decl.id);
|
|
268
|
-
for (const d of decl?.declarations ?? []) if (d.id?.type === "Identifier") add(d.id);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return names;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Splice a hand-authored `<pkg>/exports.ts` into the barrel. Any generated
|
|
276
|
-
* `export * as <ns>` whose namespace the custom file also declares is dropped (so the
|
|
277
|
-
* custom export wins - a plain `export *` cannot otherwise override an explicit
|
|
278
|
-
* `export * as`), then the whole module is re-exported last.
|
|
279
|
-
*/
|
|
280
|
-
function mergeCustomExports(content: string, barrelDir: string): string {
|
|
281
|
-
const customPath = join(barrelDir, CUSTOM_EXPORTS_FILE);
|
|
282
|
-
if (!existsSync(customPath)) return content;
|
|
283
|
-
const overridden = customExportNames(customPath);
|
|
284
|
-
const kept = content.split("\n").filter((line) => {
|
|
285
|
-
const ns = /^export \* as (\w+) from /.exec(line)?.[1];
|
|
286
|
-
return !(ns && overridden.has(ns));
|
|
287
|
-
});
|
|
288
|
-
return `${kept.join("\n").replace(/\n+$/, "")}\nexport * from "./exports.ts";\n`;
|
|
167
|
+
return `${content.replace(/\n+$/, "")}\n${lines.join("\n")}`;
|
|
289
168
|
}
|
|
290
169
|
|
|
291
170
|
/** Read the authoritative npm package metadata emitted by the package project. */
|
|
@@ -295,7 +174,7 @@ function packageMetadata(pkgDir: string): {
|
|
|
295
174
|
} {
|
|
296
175
|
const manifestPath = join(pkgDir, "package.json");
|
|
297
176
|
const manifest = existsSync(manifestPath)
|
|
298
|
-
?
|
|
177
|
+
? parseRecord(readFileSync(manifestPath, "utf8"))
|
|
299
178
|
: undefined;
|
|
300
179
|
const name = manifest?.name;
|
|
301
180
|
if (typeof name !== "string" || !name.trim()) {
|
|
@@ -337,148 +216,157 @@ export function isGeneratedBarrel(file: string): boolean {
|
|
|
337
216
|
return existsSync(file) && readFileSync(file, "utf8").startsWith(header(BARREL_HEADER));
|
|
338
217
|
}
|
|
339
218
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const childIndexes = [...find.findFilesSync("*/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];
|
|
219
|
+
interface BarrelModule {
|
|
220
|
+
readonly specifier: string;
|
|
221
|
+
readonly namespacePath: string;
|
|
222
|
+
readonly reexport: boolean;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Root-level barrel filename for one source-relative folder. */
|
|
226
|
+
export function folderBarrelFile(directory: string): string {
|
|
227
|
+
return `index.${toPosix(directory).replaceAll("/", ".")}.ts`;
|
|
355
228
|
}
|
|
356
229
|
|
|
357
230
|
/** Write or remove one generated barrel and return whether its file changed. */
|
|
358
231
|
function generateBarrel(
|
|
359
232
|
pkgDir: string,
|
|
360
|
-
|
|
361
|
-
|
|
233
|
+
barrel: string,
|
|
234
|
+
modules: readonly BarrelModule[],
|
|
362
235
|
includePackageMetadata: boolean,
|
|
363
236
|
): number {
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
237
|
+
const beforeStat = statSync(barrel, { throwIfNoEntry: false });
|
|
238
|
+
if (beforeStat && !isGeneratedBarrel(barrel)) {
|
|
239
|
+
throw new Error(`Generated barrel path is owned by a hand-written file: ${barrel}`);
|
|
240
|
+
}
|
|
241
|
+
const before = beforeStat ? readFileSync(barrel, "utf8") : undefined;
|
|
242
|
+
|
|
243
|
+
const bySourceStem = new Map<string, BarrelModule>();
|
|
244
|
+
for (const module of modules) {
|
|
245
|
+
const stem = module.specifier.replace(MODULE_EXT_RE, "");
|
|
246
|
+
const existing = bySourceStem.get(stem);
|
|
247
|
+
if (!existing || (!isSourceExt(existing.specifier) && isSourceExt(module.specifier))) {
|
|
248
|
+
bySourceStem.set(stem, module);
|
|
249
|
+
}
|
|
373
250
|
}
|
|
374
|
-
const
|
|
251
|
+
const selected = [...bySourceStem.values()].sort((left, right) =>
|
|
252
|
+
left.specifier.localeCompare(right.specifier),
|
|
253
|
+
);
|
|
375
254
|
|
|
376
|
-
if (
|
|
377
|
-
if (before !== undefined
|
|
255
|
+
if (selected.length === 0) {
|
|
256
|
+
if (!includePackageMetadata && before !== undefined) {
|
|
378
257
|
makeWritable(barrel);
|
|
379
258
|
rmSync(barrel, { force: true });
|
|
380
|
-
return
|
|
259
|
+
return 1;
|
|
381
260
|
}
|
|
382
|
-
return 0;
|
|
261
|
+
if (!includePackageMetadata) return 0;
|
|
383
262
|
}
|
|
384
263
|
|
|
385
|
-
const
|
|
386
|
-
.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
264
|
+
const exportedNames = new Set(
|
|
265
|
+
selected.flatMap(({ specifier }) =>
|
|
266
|
+
moduleExports(join(pkgDir, specifier.replace(/^\.\//, ""))).map(({ name }) => name),
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
const namespaceExports = selected.flatMap(({ specifier, namespacePath, reexport }) => {
|
|
270
|
+
const namespace = modulePathToNamespace(`./${namespacePath}`);
|
|
271
|
+
return [
|
|
272
|
+
...(exportedNames.has(namespace) ? [] : [`export * as ${namespace} from "${specifier}";`]),
|
|
273
|
+
...(reexport ? [`export * from "${specifier}";`] : []),
|
|
274
|
+
];
|
|
275
|
+
});
|
|
392
276
|
let content = includePackageMetadata
|
|
393
|
-
? `${PACKAGE_IDENTIFIER_LINE}\n${PACKAGE_VERSION_LINE}\n${namespaceExports}`
|
|
394
|
-
: namespaceExports;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
content = hoistUniqueExports(content, includePackageMetadata ? pkgDir : barrelDir, suppress);
|
|
403
|
-
content = mergeCustomExports(content, customDir);
|
|
404
|
-
content = `${content.replace(/\n+$/, "")}\n`;
|
|
405
|
-
const template = `${header(BARREL_HEADER)}\n${content}`;
|
|
277
|
+
? `${PACKAGE_IDENTIFIER_LINE}\n${PACKAGE_VERSION_LINE}\n${namespaceExports.join("\n")}`
|
|
278
|
+
: namespaceExports.join("\n");
|
|
279
|
+
content = hoistUniqueExports(
|
|
280
|
+
content,
|
|
281
|
+
pkgDir,
|
|
282
|
+
selected.map(({ specifier }) => specifier),
|
|
283
|
+
new Set(includePackageMetadata ? [PACKAGE_IDENTIFIER_EXPORT, PACKAGE_VERSION_EXPORT] : []),
|
|
284
|
+
);
|
|
285
|
+
const template = `${header(BARREL_HEADER)}\n${content.replace(/\n+$/, "")}\n`;
|
|
406
286
|
const next = includePackageMetadata ? withPackageMetadata(template, pkgDir) : template;
|
|
407
287
|
const structurallySame = includePackageMetadata
|
|
408
288
|
? before !== undefined && withoutPackageMetadata(before) === template && before === next
|
|
409
289
|
: before === next;
|
|
410
|
-
if (structurallySame)
|
|
290
|
+
if (structurallySame) {
|
|
291
|
+
if (beforeStat && (beforeStat.mode & 0o777) !== 0o444) makeReadonly(barrel);
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
411
294
|
|
|
412
295
|
writeBarrel(barrel, next);
|
|
413
296
|
makeReadonly(barrel);
|
|
414
297
|
return 1;
|
|
415
298
|
}
|
|
416
299
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
300
|
+
function removeGeneratedBarrel(file: string): number {
|
|
301
|
+
if (!isGeneratedBarrel(file)) return 0;
|
|
302
|
+
makeWritable(file);
|
|
303
|
+
rmSync(file, { force: true });
|
|
304
|
+
return 1;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Rebuild one package's root and folder barrels. */
|
|
423
308
|
function generateForPackage(pkgDir: string): number {
|
|
424
309
|
const srcDir = join(pkgDir, "src");
|
|
425
|
-
if (!existsSync(srcDir))
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
!
|
|
436
|
-
|
|
310
|
+
if (!existsSync(srcDir)) {
|
|
311
|
+
return generateBarrel(pkgDir, join(pkgDir, "index.ts"), [], true);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const discoveredFiles = [...findFilesSync("**/*", { cwd: srcDir })].map(toPosix);
|
|
315
|
+
const nestedIndexes = discoveredFiles.filter((file) =>
|
|
316
|
+
/(^|\/)index\.(?:tsx?|jsx?|mts|cts)$/.test(file),
|
|
317
|
+
);
|
|
318
|
+
for (const index of nestedIndexes) {
|
|
319
|
+
const path = join(srcDir, index);
|
|
320
|
+
if (!isGeneratedBarrel(path)) {
|
|
321
|
+
throw new Error(`Nested source index is unsupported: ${path}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
let changed = nestedIndexes.reduce(
|
|
326
|
+
(count, index) => count + removeGeneratedBarrel(join(srcDir, index)),
|
|
327
|
+
0,
|
|
328
|
+
);
|
|
329
|
+
const sourceFiles = discoveredFiles
|
|
330
|
+
.filter((file) => isModuleFile(file))
|
|
331
|
+
.filter((file) => !isExcluded(file))
|
|
332
|
+
.filter((file) => hasExport(join(srcDir, file)));
|
|
437
333
|
const directories = new Set<string>();
|
|
438
334
|
for (const file of sourceFiles) {
|
|
439
335
|
const fileDir = dirname(file);
|
|
440
336
|
if (fileDir === "." || /(^|\/)_[^/]+/.test(fileDir)) continue;
|
|
441
|
-
|
|
442
|
-
while (current !== ".") {
|
|
443
|
-
directories.add(current);
|
|
444
|
-
current = dirname(current);
|
|
445
|
-
}
|
|
337
|
+
directories.add(fileDir);
|
|
446
338
|
}
|
|
447
339
|
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
}
|
|
340
|
+
const folderBarrels = new Set([...directories].map(folderBarrelFile));
|
|
341
|
+
for (const file of [...findFilesSync("index.*.ts", { cwd: pkgDir })].map(toPosix)) {
|
|
342
|
+
if (file.includes("/") || file.endsWith(".d.ts") || folderBarrels.has(file)) continue;
|
|
343
|
+
const path = join(pkgDir, file);
|
|
344
|
+
if (!isGeneratedBarrel(path)) {
|
|
345
|
+
throw new Error(`Folder barrel path is owned by a hand-written file: ${path}`);
|
|
464
346
|
}
|
|
347
|
+
changed += removeGeneratedBarrel(path);
|
|
465
348
|
}
|
|
466
349
|
|
|
467
|
-
const
|
|
468
|
-
.filter((
|
|
469
|
-
.
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
350
|
+
const rootModules = sourceFiles
|
|
351
|
+
.filter((source) => dirname(source) === ".")
|
|
352
|
+
.map((source) => ({
|
|
353
|
+
specifier: `./src/${source}`,
|
|
354
|
+
namespacePath: source,
|
|
355
|
+
reexport: moduleExports(join(srcDir, source)).length === 0,
|
|
356
|
+
}));
|
|
357
|
+
changed += generateBarrel(pkgDir, join(pkgDir, "index.ts"), rootModules, true);
|
|
358
|
+
|
|
359
|
+
for (const directory of [...directories].sort((left, right) => left.localeCompare(right))) {
|
|
360
|
+
const modules: BarrelModule[] = sourceFiles
|
|
361
|
+
.filter((source) => dirname(source) === directory)
|
|
362
|
+
.map((source) => ({
|
|
363
|
+
specifier: `./src/${source}`,
|
|
364
|
+
namespacePath: source.slice(directory.length + 1),
|
|
365
|
+
reexport: moduleExports(join(srcDir, source)).length === 0,
|
|
366
|
+
}));
|
|
367
|
+
changed += generateBarrel(pkgDir, join(pkgDir, folderBarrelFile(directory)), modules, false);
|
|
368
|
+
}
|
|
369
|
+
return changed;
|
|
482
370
|
}
|
|
483
371
|
|
|
484
372
|
/**
|
|
@@ -549,7 +437,7 @@ export function generateBarrels(
|
|
|
549
437
|
const names = failures.map((f) => relative(projectRoot, f.dir) || f.dir);
|
|
550
438
|
throw new AggregateError(
|
|
551
439
|
failures.map((f) => f.err),
|
|
552
|
-
`${
|
|
440
|
+
`${pluralize(failures.length, "barrel")} failed: ${names.join(", ")}`,
|
|
553
441
|
);
|
|
554
442
|
}
|
|
555
443
|
return total;
|