@codefast/cli 0.3.16-canary.2 → 0.3.16-canary.3
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 +195 -34
- package/dist/core/config/schema.mjs +31 -10
- package/dist/mirror/domain/exports.mjs +14 -17
- package/dist/mirror/supplement-exports.mjs +114 -0
- package/dist/mirror/sync-reporter.mjs +1 -1
- package/dist/mirror/sync-workspace-package.mjs +33 -27
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -21,6 +21,10 @@ A small developer CLI for maintenance tasks in a TypeScript monorepo — Tailwin
|
|
|
21
21
|
- [`mirror sync`](#mirror-sync)
|
|
22
22
|
- [`tag` / `annotate`](#tag--annotate)
|
|
23
23
|
- [Configuration (`codefast.config.*`)](#configuration-codefastconfig)
|
|
24
|
+
- [Full skeleton](#full-skeleton)
|
|
25
|
+
- [`mirror` configuration](#mirror-configuration)
|
|
26
|
+
- [`tag` configuration](#tag-configuration)
|
|
27
|
+
- [`arrange` configuration](#arrange-configuration)
|
|
24
28
|
- [Lifecycle hooks](#lifecycle-hooks)
|
|
25
29
|
- [Grouping philosophy — Render Pipeline Order](#grouping-philosophy--render-pipeline-order)
|
|
26
30
|
- [Troubleshooting](#troubleshooting)
|
|
@@ -228,37 +232,46 @@ What it updates:
|
|
|
228
232
|
|
|
229
233
|
## Configuration (`codefast.config.*`)
|
|
230
234
|
|
|
231
|
-
Create
|
|
235
|
+
Create a config file at the **monorepo root** (next to `pnpm-workspace.yaml`). Supported names, in priority order:
|
|
236
|
+
|
|
237
|
+
| File name | Format |
|
|
238
|
+
| ---------------------- | --------------------------------------- |
|
|
239
|
+
| `codefast.config.mjs` | ES module — `export default { … }` |
|
|
240
|
+
| `codefast.config.js` | ES module (if `"type":"module"`) or CJS |
|
|
241
|
+
| `codefast.config.cjs` | CommonJS — `module.exports = { … }` |
|
|
242
|
+
| `codefast.config.json` | Plain JSON (no functions — no hooks) |
|
|
243
|
+
|
|
244
|
+
The file is found by walking up from the working directory, so running the CLI from any sub-directory still picks up the root config.
|
|
245
|
+
|
|
246
|
+
> **Security.** `.js`, `.mjs`, and `.cjs` files are executed via `import()`. Only run `codefast` inside repositories you trust.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
### Full skeleton
|
|
232
251
|
|
|
233
252
|
```javascript
|
|
234
253
|
// codefast.config.mjs
|
|
235
254
|
import { execSync } from "node:child_process";
|
|
236
255
|
|
|
237
256
|
export default {
|
|
257
|
+
// ─── mirror ────────────────────────────────────────────────────────────────
|
|
258
|
+
// Keys are package names (from package.json#name).
|
|
259
|
+
// Set a package to `false` to skip it entirely.
|
|
260
|
+
// Omit a package to process it with default settings.
|
|
238
261
|
mirror: {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
cssExports: {
|
|
249
|
-
// Shorthand: `true` enables default CSS export detection
|
|
250
|
-
"@acme/theme": true,
|
|
251
|
-
// Or configure explicitly:
|
|
252
|
-
"@acme/ui": {
|
|
253
|
-
enabled: true,
|
|
254
|
-
forceExportFiles: false,
|
|
255
|
-
customExports: {
|
|
256
|
-
"./tokens.css": "./dist/tokens.css",
|
|
257
|
-
},
|
|
258
|
-
},
|
|
262
|
+
"@acme/ui": {
|
|
263
|
+
strip: "./components/",
|
|
264
|
+
exports: { "./css/*": "./src/css/*" },
|
|
265
|
+
source: true, // default: true
|
|
266
|
+
types: true, // default: true
|
|
267
|
+
import: true, // default: true
|
|
268
|
+
css: true,
|
|
259
269
|
},
|
|
270
|
+
"@acme/internal": false,
|
|
271
|
+
"@acme/docs": false,
|
|
260
272
|
},
|
|
261
273
|
|
|
274
|
+
// ─── tag ───────────────────────────────────────────────────────────────────
|
|
262
275
|
tag: {
|
|
263
276
|
skipPackages: ["@acme/internal"],
|
|
264
277
|
onAfterWrite: ({ files }) => {
|
|
@@ -266,6 +279,7 @@ export default {
|
|
|
266
279
|
},
|
|
267
280
|
},
|
|
268
281
|
|
|
282
|
+
// ─── arrange ───────────────────────────────────────────────────────────────
|
|
269
283
|
arrange: {
|
|
270
284
|
onAfterWrite: ({ files }) => {
|
|
271
285
|
execSync(`oxfmt ${files.join(" ")}`, { stdio: "inherit" });
|
|
@@ -274,31 +288,177 @@ export default {
|
|
|
274
288
|
};
|
|
275
289
|
```
|
|
276
290
|
|
|
277
|
-
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### `mirror` configuration
|
|
294
|
+
|
|
295
|
+
`mirror` is a record keyed by **package name** (the `name` field in the package's `package.json`, e.g. `"@acme/ui"`).
|
|
296
|
+
|
|
297
|
+
#### Skipping a package
|
|
298
|
+
|
|
299
|
+
Set a package to `false` to exclude it from `codefast mirror sync` entirely:
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
mirror: {
|
|
303
|
+
"@acme/internal": false,
|
|
304
|
+
"@acme/docs": false,
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Packages not mentioned in the config are processed with default settings.
|
|
309
|
+
|
|
310
|
+
#### Per-package options
|
|
311
|
+
|
|
312
|
+
Each package entry is an object with the following fields:
|
|
313
|
+
|
|
314
|
+
| Field | Type | Default | Description |
|
|
315
|
+
| ---------- | ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
316
|
+
| `source` | `boolean \| string` | `true` | Add a `source` condition to each export entry pointing to the original `.ts` file. `true` auto-derives the path (`./src/<module>.ts`). Pass a string to set the root-export path explicitly (`"./src/index.tsx"`). Set to `false` to omit. |
|
|
317
|
+
| `types` | `boolean` | `true` | Include the `types` condition when a `.d.ts` file is present. Set to `false` to omit. |
|
|
318
|
+
| `import` | `boolean` | `true` | Include the `import` condition. Set to `false` to omit (useful for CJS-only packages). |
|
|
319
|
+
| `strip` | `string` | — | Strip a leading path segment from generated export specifiers. See below. |
|
|
320
|
+
| `exports` | `Record<string, string>` | — | Add or override specific export specifiers. See below. |
|
|
321
|
+
| `preserve` | `boolean` | — | Keep the existing `package.json#exports` and only fill in missing conditions. |
|
|
322
|
+
| `css` | `boolean \| CssConfig` | — | Enable CSS export detection. See below. |
|
|
323
|
+
|
|
324
|
+
#### `strip`
|
|
325
|
+
|
|
326
|
+
Removes a fixed prefix from every generated export specifier. Use this when a package's `dist/` mirrors deep directory structure that you want to flatten in the public API.
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
// Without strip, dist/components/button.mjs → "./components/button"
|
|
330
|
+
// With strip: "./components/", it becomes → "./button"
|
|
331
|
+
"@acme/ui": {
|
|
332
|
+
strip: "./components/",
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The original file path is preserved for sorting — only the public specifier changes.
|
|
337
|
+
|
|
338
|
+
#### `exports`
|
|
278
339
|
|
|
279
|
-
|
|
280
|
-
- `cssExports[pkg]` accepts a boolean shorthand or the full `{ enabled, customExports, forceExportFiles }` object.
|
|
281
|
-
- `tag.skipPackages` lists package names to skip entirely when `codefast tag` is run without an explicit target.
|
|
340
|
+
Adds or overrides specific specifiers in the final export map. Keys and values are the exact strings written into `package.json#exports`.
|
|
282
341
|
|
|
283
|
-
|
|
342
|
+
```javascript
|
|
343
|
+
"@acme/ui": {
|
|
344
|
+
exports: {
|
|
345
|
+
"./css/*": "./src/css/*", // wildcard passthrough to sources
|
|
346
|
+
"./tokens": "./dist/tokens.js", // explicit extra entry
|
|
347
|
+
},
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Extra entries are merged after auto-generation. They win over anything the scanner would produce for the same specifier. `./package.json` cannot be overridden.
|
|
352
|
+
|
|
353
|
+
#### `preserve`
|
|
354
|
+
|
|
355
|
+
Keeps the existing `package.json#exports` map exactly as-is and only fills in missing conditions (`source`, `types`, `import`) for each entry — no `dist/` scan is performed. Use this when you maintain the exports map by hand and only want the CLI to supplement missing conditions.
|
|
356
|
+
|
|
357
|
+
```javascript
|
|
358
|
+
"@acme/tailwind-variants": {
|
|
359
|
+
preserve: true,
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
#### `css`
|
|
364
|
+
|
|
365
|
+
Controls CSS file export generation. `mirror sync` scans `dist/` for `.css` files and writes wildcard or per-file export entries.
|
|
366
|
+
|
|
367
|
+
```javascript
|
|
368
|
+
// Shorthand: auto-detect all CSS files in dist/
|
|
369
|
+
"@acme/theme": { css: true }
|
|
370
|
+
|
|
371
|
+
// Full config:
|
|
372
|
+
"@acme/ui": {
|
|
373
|
+
css: {
|
|
374
|
+
enabled: true,
|
|
375
|
+
// Force individual file entries instead of directory wildcards:
|
|
376
|
+
forceExportFiles: false,
|
|
377
|
+
// Manually add or override individual CSS specifiers:
|
|
378
|
+
customExports: {
|
|
379
|
+
"./tokens.css": "./dist/tokens.css",
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Explicitly disable CSS exports for this package:
|
|
385
|
+
"@acme/legacy": { css: false }
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
When `css` is omitted, CSS files found in `dist/` are still exported by default.
|
|
389
|
+
|
|
390
|
+
#### What `mirror sync` writes
|
|
391
|
+
|
|
392
|
+
For a package with `dist/button.mjs`, `dist/button.d.ts`, and `source: true`, the generated export entry looks like:
|
|
393
|
+
|
|
394
|
+
```json
|
|
395
|
+
{
|
|
396
|
+
"./button": {
|
|
397
|
+
"source": "./src/button.ts",
|
|
398
|
+
"types": "./dist/button.d.ts",
|
|
399
|
+
"import": "./dist/button.mjs"
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
It also updates the top-level `main`, `module`, and `types` fields from the root (`.`) export, and ensures `"dist"` is listed in `files`.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
### `tag` configuration
|
|
409
|
+
|
|
410
|
+
```javascript
|
|
411
|
+
tag: {
|
|
412
|
+
// Package names to skip when running without an explicit target
|
|
413
|
+
skipPackages: ["@acme/internal", "@acme/docs"],
|
|
414
|
+
|
|
415
|
+
// Called after files are written — use it to format or lint-fix
|
|
416
|
+
onAfterWrite: ({ files }) => {
|
|
417
|
+
execSync(`prettier --write ${files.join(" ")}`, { stdio: "inherit" });
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
| Field | Type | Description |
|
|
423
|
+
| -------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
424
|
+
| `skipPackages` | `string[]` | Package names to skip when `codefast tag` is run without an explicit target. Has no effect when a target path is provided directly. |
|
|
425
|
+
| `onAfterWrite` | `(ctx: { files: string[] }) => void \| Promise<void>` | Lifecycle hook — runs after files are written. |
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
### `arrange` configuration
|
|
430
|
+
|
|
431
|
+
```javascript
|
|
432
|
+
arrange: {
|
|
433
|
+
// Called after files are written by `codefast arrange apply`
|
|
434
|
+
onAfterWrite: ({ files }) => {
|
|
435
|
+
execSync(`oxfmt ${files.join(" ")}`, { stdio: "inherit" });
|
|
436
|
+
},
|
|
437
|
+
}
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
| Field | Type | Description |
|
|
441
|
+
| -------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
442
|
+
| `onAfterWrite` | `(ctx: { files: string[] }) => void \| Promise<void>` | Lifecycle hook — runs after `arrange apply` writes files. Not called by `arrange preview`. |
|
|
284
443
|
|
|
285
444
|
---
|
|
286
445
|
|
|
287
446
|
## Lifecycle hooks
|
|
288
447
|
|
|
289
|
-
Both `tag` and `arrange apply`
|
|
448
|
+
Both `tag` and `arrange apply` call `onAfterWrite` immediately after writing files to disk. The hook receives the list of written file paths and can run any synchronous or asynchronous work — formatters, linters, codegen, notifications.
|
|
290
449
|
|
|
291
450
|
```javascript
|
|
292
451
|
export default {
|
|
293
452
|
tag: {
|
|
294
453
|
onAfterWrite: async ({ files }) => {
|
|
295
|
-
|
|
296
|
-
|
|
454
|
+
// async is supported
|
|
455
|
+
await runFormatter(files);
|
|
297
456
|
},
|
|
298
457
|
},
|
|
299
458
|
arrange: {
|
|
300
459
|
onAfterWrite: ({ files }) => {
|
|
301
|
-
|
|
460
|
+
// sync is fine too
|
|
461
|
+
execSync(`oxfmt ${files.join(" ")}`, { stdio: "inherit" });
|
|
302
462
|
},
|
|
303
463
|
},
|
|
304
464
|
};
|
|
@@ -306,10 +466,11 @@ export default {
|
|
|
306
466
|
|
|
307
467
|
Contract:
|
|
308
468
|
|
|
309
|
-
- `tag.onAfterWrite
|
|
310
|
-
- `arrange.onAfterWrite
|
|
311
|
-
-
|
|
312
|
-
-
|
|
469
|
+
- `tag.onAfterWrite` fires after `codefast tag` writes JSDoc annotations.
|
|
470
|
+
- `arrange.onAfterWrite` fires after `codefast arrange apply` rewrites class strings.
|
|
471
|
+
- Hook is **not** called on `--dry-run` or `arrange preview`.
|
|
472
|
+
- Hooks may be synchronous or `async` (`void | Promise<void>`).
|
|
473
|
+
- If the hook throws or rejects, the command reports the error on stderr and exits with code `1`.
|
|
313
474
|
|
|
314
475
|
---
|
|
315
476
|
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
//#region src/core/config/schema.ts
|
|
3
3
|
const afterWriteHookSchema = z.custom((value) => typeof value === "function", { message: "Expected a function" });
|
|
4
|
+
const mirrorCssConfigSchema = z.union([z.boolean(), z.object({
|
|
5
|
+
enabled: z.boolean().optional(),
|
|
6
|
+
customExports: z.record(z.string(), z.string()).optional(),
|
|
7
|
+
forceExportFiles: z.boolean().optional()
|
|
8
|
+
}).strict()]);
|
|
4
9
|
/**
|
|
10
|
+
* Per-package mirror configuration. Setting a package to `false` skips it entirely.
|
|
11
|
+
*
|
|
12
|
+
* - `source` — include a `source` condition pointing to the original `.ts` file (default `true`).
|
|
13
|
+
* Pass a string to override the root-export source path explicitly.
|
|
14
|
+
* - `types` — include the `types` condition when `.d.ts` files exist (default `true`).
|
|
15
|
+
* - `import` — include the `import` condition (default `true`).
|
|
16
|
+
* - `css` — CSS export configuration (wildcard or per-file).
|
|
17
|
+
*
|
|
5
18
|
* @since 0.3.16-canary.0
|
|
6
19
|
*/
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
20
|
+
const mirrorPackageConfigSchema = z.object({
|
|
21
|
+
/** Preserve the existing `package.json#exports` map and only add missing conditions
|
|
22
|
+
* (`source`, `types`, `import`). No dist/ scan is performed. */
|
|
23
|
+
preserve: z.boolean().optional(),
|
|
24
|
+
strip: z.string().optional(),
|
|
25
|
+
exports: z.record(z.string(), z.string()).optional(),
|
|
26
|
+
source: z.union([z.boolean(), z.string()]).default(true),
|
|
27
|
+
types: z.boolean().default(true),
|
|
28
|
+
import: z.boolean().default(true),
|
|
29
|
+
css: mirrorCssConfigSchema.optional()
|
|
16
30
|
}).strict();
|
|
17
31
|
/**
|
|
32
|
+
* Mirror config: a record keyed by package name. Set a package to `false` to skip it;
|
|
33
|
+
* omit it entirely to process it with default settings.
|
|
34
|
+
*
|
|
35
|
+
* @since 0.3.16-canary.0
|
|
36
|
+
*/
|
|
37
|
+
const mirrorConfigSchema = z.record(z.string(), z.union([z.literal(false), mirrorPackageConfigSchema]));
|
|
38
|
+
/**
|
|
18
39
|
* @since 0.3.16-canary.0
|
|
19
40
|
*/
|
|
20
41
|
const codefastTagConfigSchema = z.object({
|
|
@@ -36,4 +57,4 @@ const codefastConfigRootSchema = z.object({
|
|
|
36
57
|
arrange: codefastArrangeConfigSchema.optional()
|
|
37
58
|
}).strict();
|
|
38
59
|
//#endregion
|
|
39
|
-
export { codefastArrangeConfigSchema, codefastConfigRootSchema, codefastTagConfigSchema, mirrorConfigSchema };
|
|
60
|
+
export { codefastArrangeConfigSchema, codefastConfigRootSchema, codefastTagConfigSchema, mirrorConfigSchema, mirrorPackageConfigSchema };
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { PACKAGE_JSON_EXPORT, VALID_DTS_EXTENSIONS, VALID_JS_EXTENSIONS } from "./constants.mjs";
|
|
2
2
|
import * as nodePath from "node:path";
|
|
3
3
|
//#region src/mirror/domain/exports.ts
|
|
4
|
-
function resolvePackageScopedConfig(configMap, pkgMeta) {
|
|
5
|
-
if (!configMap) return;
|
|
6
|
-
return configMap[pkgMeta.packageName];
|
|
7
|
-
}
|
|
8
4
|
function groupDistFilesByModule(relativeDistFiles) {
|
|
9
5
|
const distModulesByPath = /* @__PURE__ */ new Map();
|
|
10
6
|
for (const relativeDistFile of relativeDistFiles) {
|
|
@@ -110,14 +106,11 @@ function getExportSortGroup(exportPath, pathTransform) {
|
|
|
110
106
|
/**
|
|
111
107
|
* @since 0.3.16-canary.0
|
|
112
108
|
*/
|
|
113
|
-
function createPathTransform(
|
|
114
|
-
|
|
115
|
-
if (!pathConfig) return null;
|
|
116
|
-
const { removePrefix } = pathConfig;
|
|
117
|
-
if (!removePrefix) return null;
|
|
109
|
+
function createPathTransform(strip) {
|
|
110
|
+
if (!strip) return null;
|
|
118
111
|
return (exportPath) => {
|
|
119
|
-
if (!exportPath.startsWith(
|
|
120
|
-
const trimmedExportPath = exportPath.slice(
|
|
112
|
+
if (!exportPath.startsWith(strip)) return exportPath;
|
|
113
|
+
const trimmedExportPath = exportPath.slice(strip.length);
|
|
121
114
|
if (trimmedExportPath && trimmedExportPath !== "." && !trimmedExportPath.startsWith("./")) return `./${trimmedExportPath}`;
|
|
122
115
|
return trimmedExportPath;
|
|
123
116
|
};
|
|
@@ -174,7 +167,7 @@ async function generateCssExports(fileSystemService, distDir, cssConfig) {
|
|
|
174
167
|
*
|
|
175
168
|
* @since 0.3.16-canary.0
|
|
176
169
|
*/
|
|
177
|
-
async function generateExports(fileSystemService, distDir, pathTransform, cssConfig,
|
|
170
|
+
async function generateExports(fileSystemService, distDir, pathTransform, cssConfig, extraExports, options) {
|
|
178
171
|
const relativeDistFiles = await fileSystemService.listRelativeFilesRecursively(distDir);
|
|
179
172
|
if (!relativeDistFiles.length) return {
|
|
180
173
|
exports: { [PACKAGE_JSON_EXPORT]: PACKAGE_JSON_EXPORT },
|
|
@@ -197,10 +190,14 @@ async function generateExports(fileSystemService, distDir, pathTransform, cssCon
|
|
|
197
190
|
let exportPath = originalExportPath;
|
|
198
191
|
if (pathTransform) exportPath = pathTransform(exportPath);
|
|
199
192
|
const exportEntry = {};
|
|
193
|
+
if (options?.source) if (typeof options.source === "string" && exportPath === ".") exportEntry.source = options.source;
|
|
194
|
+
else exportEntry.source = (options.resolveSourcePath ?? ((p) => `./src/${p}.ts`))(distModuleEntry.path);
|
|
200
195
|
const declarationFile = distModuleEntry.files.dts;
|
|
201
|
-
if (declarationFile) exportEntry.types = `./dist/${declarationFile}`;
|
|
202
|
-
if (
|
|
203
|
-
|
|
196
|
+
if (declarationFile && options?.types !== false) exportEntry.types = `./dist/${declarationFile}`;
|
|
197
|
+
if (options?.import !== false) {
|
|
198
|
+
if (distModuleEntry.files.mjs) exportEntry.import = `./dist/${distModuleEntry.files.mjs}`;
|
|
199
|
+
else if (distModuleEntry.files.js) exportEntry.import = `./dist/${distModuleEntry.files.js}`;
|
|
200
|
+
}
|
|
204
201
|
if (distModuleEntry.files.cjs) exportEntry.require = `./dist/${distModuleEntry.files.cjs}`;
|
|
205
202
|
moduleExportsBySpecifier[exportPath] = exportEntry;
|
|
206
203
|
originalPathBySpecifier[exportPath] = originalExportPath;
|
|
@@ -210,9 +207,9 @@ async function generateExports(fileSystemService, distDir, pathTransform, cssCon
|
|
|
210
207
|
for (const exportKey of sortedSpecifiers) sortedExports[exportKey] = moduleExportsBySpecifier[exportKey];
|
|
211
208
|
const cssExports = await generateCssExports(fileSystemService, distDir, cssConfig ?? { enabled: true });
|
|
212
209
|
Object.assign(sortedExports, cssExports);
|
|
213
|
-
for (const [specifier, mappedPath] of Object.entries(
|
|
210
|
+
for (const [specifier, mappedPath] of Object.entries(extraExports || {})) if (specifier !== "./package.json") sortedExports[specifier] = mappedPath;
|
|
214
211
|
for (const cssSpecifier of Object.keys(cssExports)) if (!(cssSpecifier in originalPathBySpecifier)) originalPathBySpecifier[cssSpecifier] = cssSpecifier;
|
|
215
|
-
for (const
|
|
212
|
+
for (const extraSpecifier of Object.keys(extraExports || {})) if (extraSpecifier !== "./package.json" && !(extraSpecifier in originalPathBySpecifier)) originalPathBySpecifier[extraSpecifier] = extraSpecifier;
|
|
216
213
|
sortedSpecifiers = Object.keys(sortedExports).filter((exportKey) => exportKey !== PACKAGE_JSON_EXPORT).sort((leftSpecifier, rightSpecifier) => compareExportSortKeys(getExportSortKey(leftSpecifier, pathTransform), getExportSortKey(rightSpecifier, pathTransform)));
|
|
217
214
|
const finalExports = {};
|
|
218
215
|
for (const exportKey of sortedSpecifiers) finalExports[exportKey] = sortedExports[exportKey];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { DIST_DIR } from "./domain/constants.mjs";
|
|
2
|
+
import { writePackageJsonExportsAtomic } from "./write-exports.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
//#region src/mirror/supplement-exports.ts
|
|
5
|
+
const DTS_EXTENSIONS = [
|
|
6
|
+
".d.mts",
|
|
7
|
+
".d.ts",
|
|
8
|
+
".d.cts"
|
|
9
|
+
];
|
|
10
|
+
const JS_EXTENSIONS = [".mjs", ".js"];
|
|
11
|
+
/**
|
|
12
|
+
* Infer the dist module path (e.g. `"index"`, `"components/button"`) from an existing
|
|
13
|
+
* conditional export entry. Prefers `import` / `require` values because their extensions
|
|
14
|
+
* are simpler than `.d.mts` etc.
|
|
15
|
+
*/
|
|
16
|
+
function inferModulePath(specifier, entry) {
|
|
17
|
+
for (const key of ["import", "require"]) {
|
|
18
|
+
const value = entry[key];
|
|
19
|
+
if (typeof value === "string" && value.startsWith("./dist/")) {
|
|
20
|
+
const relative = value.slice(7);
|
|
21
|
+
for (const ext of JS_EXTENSIONS) if (relative.endsWith(ext)) return relative.slice(0, -ext.length);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const typesValue = entry.types;
|
|
25
|
+
if (typeof typesValue === "string" && typesValue.startsWith("./dist/")) {
|
|
26
|
+
const relative = typesValue.slice(7);
|
|
27
|
+
for (const ext of DTS_EXTENSIONS) if (relative.endsWith(ext)) return relative.slice(0, -ext.length);
|
|
28
|
+
}
|
|
29
|
+
if (specifier === ".") return "index";
|
|
30
|
+
if (specifier.startsWith("./")) return specifier.slice(2);
|
|
31
|
+
return specifier;
|
|
32
|
+
}
|
|
33
|
+
function findDtsSpecifier(fs, distDir, modulePath) {
|
|
34
|
+
for (const ext of DTS_EXTENSIONS) if (fs.existsSync(path.join(distDir, `${modulePath}${ext}`))) return `./dist/${modulePath}${ext}`;
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function findImportSpecifier(fs, distDir, modulePath) {
|
|
38
|
+
for (const ext of JS_EXTENSIONS) if (fs.existsSync(path.join(distDir, `${modulePath}${ext}`))) return `./dist/${modulePath}${ext}`;
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Rebuild an export entry in canonical key order, adding any missing conditions.
|
|
43
|
+
* Key order: source → types → import → require → (remaining original keys).
|
|
44
|
+
*/
|
|
45
|
+
function buildSupplementedEntry(specifier, existing, modulePath, distDir, fs, options) {
|
|
46
|
+
const result = {};
|
|
47
|
+
const knownKeys = new Set([
|
|
48
|
+
"source",
|
|
49
|
+
"types",
|
|
50
|
+
"import",
|
|
51
|
+
"require"
|
|
52
|
+
]);
|
|
53
|
+
if ("source" in existing) result.source = existing.source;
|
|
54
|
+
else if (options.source) if (typeof options.source === "string" && specifier === ".") result.source = options.source;
|
|
55
|
+
else result.source = options.resolveSourcePath(modulePath);
|
|
56
|
+
if ("types" in existing) result.types = existing.types;
|
|
57
|
+
else if (options.types) {
|
|
58
|
+
const found = findDtsSpecifier(fs, distDir, modulePath);
|
|
59
|
+
if (found) result.types = found;
|
|
60
|
+
}
|
|
61
|
+
if ("import" in existing) result.import = existing.import;
|
|
62
|
+
else if (options.import) {
|
|
63
|
+
const found = findImportSpecifier(fs, distDir, modulePath);
|
|
64
|
+
if (found) result.import = found;
|
|
65
|
+
}
|
|
66
|
+
if ("require" in existing) result.require = existing.require;
|
|
67
|
+
for (const [key, value] of Object.entries(existing)) if (!knownKeys.has(key) && !(key in result)) result[key] = value;
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* @since 0.3.16-canary.0
|
|
72
|
+
*/
|
|
73
|
+
async function supplementExportsInPackageJson(fs, packageJsonPath, packageDir, options) {
|
|
74
|
+
const raw = await fs.readFile(packageJsonPath, "utf8");
|
|
75
|
+
const existingExports = JSON.parse(raw).exports;
|
|
76
|
+
if (!existingExports || typeof existingExports !== "object" || Array.isArray(existingExports)) return { supplementedSpecifiers: [] };
|
|
77
|
+
const distDir = path.join(packageDir, DIST_DIR);
|
|
78
|
+
const supplementedSpecifiers = [];
|
|
79
|
+
const supplementedExports = {};
|
|
80
|
+
const originalPathBySpecifier = {};
|
|
81
|
+
for (const [specifier, entry] of Object.entries(existingExports)) {
|
|
82
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
83
|
+
supplementedExports[specifier] = entry;
|
|
84
|
+
originalPathBySpecifier[specifier] = specifier;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const entryRecord = entry;
|
|
88
|
+
const supplemented = buildSupplementedEntry(specifier, entryRecord, inferModulePath(specifier, entryRecord), distDir, fs, options);
|
|
89
|
+
if (JSON.stringify(supplemented) !== JSON.stringify(entryRecord)) supplementedSpecifiers.push(specifier);
|
|
90
|
+
supplementedExports[specifier] = supplemented;
|
|
91
|
+
originalPathBySpecifier[specifier] = specifier;
|
|
92
|
+
}
|
|
93
|
+
await writePackageJsonExportsAtomic(fs, packageJsonPath, {
|
|
94
|
+
generatedExports: supplementedExports,
|
|
95
|
+
managedExportSpecifiers: Object.keys(supplementedExports),
|
|
96
|
+
originalPathBySpecifier
|
|
97
|
+
});
|
|
98
|
+
return { supplementedSpecifiers };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Build a `resolveSourcePath` closure that checks the filesystem for `.tsx` before
|
|
102
|
+
* falling back to `.ts`. Mirrors the logic used in normal (non-custom) mode.
|
|
103
|
+
*
|
|
104
|
+
* @since 0.3.16-canary.0
|
|
105
|
+
*/
|
|
106
|
+
function buildSourcePathResolver(fs, packageDir) {
|
|
107
|
+
const srcDir = path.join(packageDir, "src");
|
|
108
|
+
return (modulePath) => {
|
|
109
|
+
const tsxPath = path.join(srcDir, `${modulePath}.tsx`);
|
|
110
|
+
return fs.existsSync(tsxPath) ? `./src/${modulePath}.tsx` : `./src/${modulePath}.ts`;
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { buildSourcePathResolver, supplementExportsInPackageJson };
|
|
@@ -65,7 +65,7 @@ var MirrorSyncReporter = class {
|
|
|
65
65
|
const breakdown = [];
|
|
66
66
|
if (generatedDistAssetCounts.jsCount > 0) breakdown.push(this.paint(`${generatedDistAssetCounts.jsCount} modules`, ANSI.green));
|
|
67
67
|
if (generatedDistAssetCounts.cssCount > 0) breakdown.push(this.paint(`${generatedDistAssetCounts.cssCount} CSS`, ANSI.magenta));
|
|
68
|
-
if (pkgStats.
|
|
68
|
+
if (pkgStats.extraExports > 0) breakdown.push(this.paint(`${pkgStats.extraExports} custom`, ANSI.yellow));
|
|
69
69
|
const totalExportsText = this.paint(`${pkgStats.totalExports} exports`, ANSI.brightCyan);
|
|
70
70
|
if (breakdown.length === 0) logger.out(` ${this.paint("└─", ANSI.dim)} ${totalExportsText}`);
|
|
71
71
|
else logger.out(` ${this.paint("└─", ANSI.dim)} ${breakdown.join(" + ")} = ${totalExportsText}`);
|
|
@@ -4,6 +4,7 @@ import { createPathTransform, generateExports } from "./domain/exports.mjs";
|
|
|
4
4
|
import { resolvePackageDisplayName } from "./domain/package-display-name.mjs";
|
|
5
5
|
import { writePackageJsonExportsAtomic } from "./write-exports.mjs";
|
|
6
6
|
import { createMirrorDistFilesystem } from "./dist-filesystem-impl.mjs";
|
|
7
|
+
import { buildSourcePathResolver, supplementExportsInPackageJson } from "./supplement-exports.mjs";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
//#region src/mirror/sync-workspace-package.ts
|
|
9
10
|
/**
|
|
@@ -23,7 +24,7 @@ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, confi
|
|
|
23
24
|
path: packageDir,
|
|
24
25
|
jsModules: 0,
|
|
25
26
|
cssExports: 0,
|
|
26
|
-
|
|
27
|
+
extraExports: 0,
|
|
27
28
|
totalExports: 0,
|
|
28
29
|
hasTransform: false,
|
|
29
30
|
cssConfigStatus: "",
|
|
@@ -49,8 +50,8 @@ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, confi
|
|
|
49
50
|
pkgStats.name = folderBasename;
|
|
50
51
|
packageJsonParseError = caughtError;
|
|
51
52
|
}
|
|
52
|
-
const
|
|
53
|
-
if (
|
|
53
|
+
const pkgConfig = config[pkgStats.name];
|
|
54
|
+
if (pkgConfig === false) {
|
|
54
55
|
pkgStats.skipped = true;
|
|
55
56
|
pkgStats.skipReason = "configured to skip";
|
|
56
57
|
return pkgStats;
|
|
@@ -70,34 +71,39 @@ async function syncExportsForWorkspacePackage(fs, rootDir, packagePathStr, confi
|
|
|
70
71
|
return pkgStats;
|
|
71
72
|
}
|
|
72
73
|
try {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
74
|
+
const resolveSourcePath = buildSourcePathResolver(fs, packageDir);
|
|
75
|
+
const exportOptions = {
|
|
76
|
+
source: pkgConfig?.source ?? true,
|
|
77
|
+
types: pkgConfig?.types ?? true,
|
|
78
|
+
import: pkgConfig?.import ?? true,
|
|
79
|
+
resolveSourcePath
|
|
80
|
+
};
|
|
81
|
+
if (pkgConfig?.preserve) {
|
|
82
|
+
const { supplementedSpecifiers } = await supplementExportsInPackageJson(fs, packageJsonPath, packageDir, exportOptions);
|
|
83
|
+
pkgStats.totalExports = supplementedSpecifiers.length;
|
|
84
|
+
} else {
|
|
85
|
+
const pathTransform = createPathTransform(pkgConfig?.strip);
|
|
86
|
+
pkgStats.hasTransform = !!pathTransform;
|
|
87
|
+
const cssConfig = pkgConfig?.css;
|
|
88
|
+
if (cssConfig === false) pkgStats.cssConfigStatus = "disabled";
|
|
89
|
+
else if (cssConfig !== void 0) pkgStats.cssConfigStatus = "configured";
|
|
90
|
+
const extraExports = pkgConfig?.exports ?? {};
|
|
91
|
+
const generatedExports = await generateExports(distFilesystem, distDir, pathTransform, cssConfig, extraExports, exportOptions);
|
|
92
|
+
const { prunedKeys } = await writePackageJsonExportsAtomic(fs, packageJsonPath, {
|
|
93
|
+
generatedExports: generatedExports.exports,
|
|
94
|
+
managedExportSpecifiers: Object.keys(generatedExports.exports),
|
|
95
|
+
originalPathBySpecifier: generatedExports.originalPathBySpecifier
|
|
96
|
+
});
|
|
97
|
+
pkgStats.jsModules = generatedExports.jsCount;
|
|
98
|
+
pkgStats.cssExports = generatedExports.cssCount;
|
|
99
|
+
pkgStats.extraExports = Object.keys(extraExports).length;
|
|
100
|
+
pkgStats.totalExports = Object.keys(generatedExports.exports).length;
|
|
101
|
+
pkgStats.prunedExportKeys = prunedKeys;
|
|
102
|
+
}
|
|
90
103
|
} catch (caughtError) {
|
|
91
104
|
pkgStats.error = messageFrom(caughtError);
|
|
92
105
|
}
|
|
93
106
|
return pkgStats;
|
|
94
107
|
}
|
|
95
|
-
function resolvePackageScopedConfig(configMap, packageMeta) {
|
|
96
|
-
if (!configMap) return;
|
|
97
|
-
return configMap[packageMeta.packageName];
|
|
98
|
-
}
|
|
99
|
-
function isPackageSkipped(skipPackagesList, packageMeta) {
|
|
100
|
-
return !!skipPackagesList?.includes(packageMeta.packageName);
|
|
101
|
-
}
|
|
102
108
|
//#endregion
|
|
103
109
|
export { syncExportsForWorkspacePackage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codefast/cli",
|
|
3
|
-
"version": "0.3.16-canary.
|
|
3
|
+
"version": "0.3.16-canary.3",
|
|
4
4
|
"description": "Developer CLI for the Codefast monorepo (arrange, mirror, tag)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -59,17 +59,17 @@
|
|
|
59
59
|
"zod": "^4.4.3"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@types/node": "^25.
|
|
62
|
+
"@types/node": "^25.9.1",
|
|
63
63
|
"@types/picomatch": "^4.0.3",
|
|
64
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
65
|
-
"@vitest/coverage-v8": "^4.1.
|
|
64
|
+
"@typescript/native-preview": "7.0.0-dev.20260526.1",
|
|
65
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
66
66
|
"tsdown": "^0.22.0",
|
|
67
|
-
"vite": "^8.0.
|
|
68
|
-
"vitest": "^4.1.
|
|
69
|
-
"@codefast/typescript-config": "0.3.16-canary.
|
|
67
|
+
"vite": "^8.0.14",
|
|
68
|
+
"vitest": "^4.1.7",
|
|
69
|
+
"@codefast/typescript-config": "0.3.16-canary.3"
|
|
70
70
|
},
|
|
71
71
|
"engines": {
|
|
72
|
-
"node": ">=
|
|
72
|
+
"node": ">=24.0.0"
|
|
73
73
|
},
|
|
74
74
|
"scripts": {
|
|
75
75
|
"build": "tsdown",
|