@savvy-web/tsdown-plugins 0.9.2 → 0.11.0
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/build/build-target-groups.js +40 -1
- package/build/target-groups.js +36 -1
- package/index.d.ts +14 -0
- package/meta/api-extractor.js +8 -8
- package/meta/generate.js +54 -4
- package/meta/run-pass.js +2 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { createTsdownLogger } from "../report/tsdown-logger.js";
|
|
|
5
5
|
import { cjsDefaultInterop } from "./cjs-default-interop.js";
|
|
6
6
|
import { nodeBuiltinDefaultInterop } from "./node-builtin-default-interop.js";
|
|
7
7
|
import { syncPublicDir } from "./sync-public.js";
|
|
8
|
-
import { deriveDtsPassOptions, deriveTargetGroupOptions, outDirFor } from "./target-groups.js";
|
|
8
|
+
import { deriveDeclarationsPassOptions, deriveDtsPassOptions, deriveTargetGroupOptions, outDirFor } from "./target-groups.js";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
|
|
11
11
|
//#region src/build/build-target-groups.ts
|
|
@@ -80,6 +80,7 @@ async function buildTargetGroups(options) {
|
|
|
80
80
|
};
|
|
81
81
|
const js = deriveTargetGroupOptions(deriveInput);
|
|
82
82
|
const dts = deriveDtsPassOptions(deriveInput);
|
|
83
|
+
const decl = deriveDeclarationsPassOptions(deriveInput);
|
|
83
84
|
const partOutDir = part.outSubdir !== void 0 ? join(js.outDir, part.outSubdir) : js.outDir;
|
|
84
85
|
const targetGroup = {
|
|
85
86
|
id: group.id,
|
|
@@ -165,6 +166,44 @@ async function buildTargetGroups(options) {
|
|
|
165
166
|
...metricsPlugins(group.id, "dts")
|
|
166
167
|
]
|
|
167
168
|
}));
|
|
169
|
+
if (js.isProd && options.emitDeclarations === true && Object.keys(decl.entry).length > 0) {
|
|
170
|
+
const partDeclDir = part.outSubdir !== void 0 ? join(decl.outDir, part.outSubdir) : decl.outDir;
|
|
171
|
+
try {
|
|
172
|
+
await build({
|
|
173
|
+
config: false,
|
|
174
|
+
cwd: options.cwd,
|
|
175
|
+
entry: decl.entry,
|
|
176
|
+
outDir: partDeclDir,
|
|
177
|
+
format: decl.format,
|
|
178
|
+
platform: decl.platform,
|
|
179
|
+
sourcemap: decl.sourcemap,
|
|
180
|
+
unbundle: decl.unbundle,
|
|
181
|
+
clean: isBase,
|
|
182
|
+
fixedExtension: decl.fixedExtension,
|
|
183
|
+
dts: decl.dts,
|
|
184
|
+
define: decl.define,
|
|
185
|
+
logLevel: "silent",
|
|
186
|
+
...dtsNeverBundle.length > 0 || partBundleNodeModules || decl.bundledPackages ? { deps: {
|
|
187
|
+
...dtsNeverBundle.length > 0 ? { neverBundle: dtsNeverBundle } : {},
|
|
188
|
+
...partBundleNodeModules ? {
|
|
189
|
+
skipNodeModulesBundle: false,
|
|
190
|
+
...decl.bundledPackages ? { dts: { alwaysBundle: decl.bundledPackages } } : {}
|
|
191
|
+
} : decl.bundledPackages ? {
|
|
192
|
+
skipNodeModulesBundle: true,
|
|
193
|
+
dts: { alwaysBundle: decl.bundledPackages }
|
|
194
|
+
} : {}
|
|
195
|
+
} } : {},
|
|
196
|
+
...decl.jsx !== void 0 ? { inputOptions: { jsx: decl.jsx } } : {},
|
|
197
|
+
...options.extraPlugins !== void 0 ? { plugins: [...options.extraPlugins] } : {}
|
|
198
|
+
});
|
|
199
|
+
} catch (err) {
|
|
200
|
+
collector?.recordWarning(group.id, {
|
|
201
|
+
source: "tsdown",
|
|
202
|
+
level: "warn",
|
|
203
|
+
text: `Could not emit per-module declarations for API Extractor diagnostics: ${String(err)}`
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
168
207
|
}
|
|
169
208
|
const looseOutDir = outDirFor(options.cwd, group.id);
|
|
170
209
|
const isProdGroup = group.id !== "dev";
|
package/build/target-groups.js
CHANGED
|
@@ -4,6 +4,12 @@ import { join } from "node:path";
|
|
|
4
4
|
/** The output dir for a group: dev -> dist/dev/pkg, prod -> dist/prod/<group>/pkg. */
|
|
5
5
|
const outDirFor = (cwd, group) => group === "dev" ? join(cwd, "dist/dev/pkg") : join(cwd, "dist/prod", group, "pkg");
|
|
6
6
|
/**
|
|
7
|
+
* Per-module declarations dir for a prod group: a sibling of `pkg/`, NOT published, used only as
|
|
8
|
+
* API Extractor's diagnostics-run input so locations resolve to per-file source. Kept (not stripped)
|
|
9
|
+
* as a cacheable, inspectable build artifact (issue #162).
|
|
10
|
+
*/
|
|
11
|
+
const declarationsDirFor = (cwd, group) => join(cwd, "dist/prod", group, "declarations");
|
|
12
|
+
/**
|
|
7
13
|
* Derive the JS-pass tsdown options for one TargetGroup (per-module JS, no dts).
|
|
8
14
|
*
|
|
9
15
|
* @public
|
|
@@ -59,6 +65,35 @@ function deriveDtsPassOptions(options) {
|
|
|
59
65
|
...options.bundledPackages !== void 0 ? { bundledPackages: options.bundledPackages } : {}
|
|
60
66
|
};
|
|
61
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Derive the per-module declarations pass (API Extractor diagnostics input). Mirrors
|
|
70
|
+
* {@link deriveDtsPassOptions} but `unbundle: true` (preserveModules → 1:1 source positions),
|
|
71
|
+
* esm-only (AE reads the `.d.ts` entry only), into the `declarations/` sibling dir. Bin entries
|
|
72
|
+
* are dropped (no exports).
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
function deriveDeclarationsPassOptions(options) {
|
|
76
|
+
const entry = Object.fromEntries(Object.entries(options.entry).filter(([name]) => !name.startsWith("bin/")));
|
|
77
|
+
return {
|
|
78
|
+
outDir: declarationsDirFor(options.cwd, options.group),
|
|
79
|
+
sourcemap: false,
|
|
80
|
+
format: ["esm"],
|
|
81
|
+
unbundle: true,
|
|
82
|
+
platform: "node",
|
|
83
|
+
fixedExtension: false,
|
|
84
|
+
entry,
|
|
85
|
+
dts: {
|
|
86
|
+
tsconfig: options.tsconfigPath,
|
|
87
|
+
emitDtsOnly: true
|
|
88
|
+
},
|
|
89
|
+
define: {
|
|
90
|
+
"process.env.__PACKAGE_VERSION__": JSON.stringify(options.version),
|
|
91
|
+
...options.define
|
|
92
|
+
},
|
|
93
|
+
...options.bundledPackages !== void 0 ? { bundledPackages: options.bundledPackages } : {},
|
|
94
|
+
...options.jsx !== void 0 ? { jsx: options.jsx } : {}
|
|
95
|
+
};
|
|
96
|
+
}
|
|
62
97
|
|
|
63
98
|
//#endregion
|
|
64
|
-
export { deriveDtsPassOptions, deriveTargetGroupOptions, outDirFor };
|
|
99
|
+
export { declarationsDirFor, deriveDeclarationsPassOptions, deriveDtsPassOptions, deriveTargetGroupOptions, outDirFor };
|
package/index.d.ts
CHANGED
|
@@ -689,6 +689,13 @@ interface BuildTargetGroupsOptions {
|
|
|
689
689
|
readonly collector?: BuildCollector | undefined;
|
|
690
690
|
/** Compute gzip sizes for emitted files (verbose render). Forwarded to the metrics plugin. */
|
|
691
691
|
readonly verbose?: boolean | undefined;
|
|
692
|
+
/**
|
|
693
|
+
* Emit a per-module (`unbundle: true`) declaration tree into `dist/prod/<id>/declarations/` per
|
|
694
|
+
* group, in addition to the bundled dts in `pkg/`. API Extractor's diagnostics-run input for the
|
|
695
|
+
* meta pass. Prod-only; the bundler sets it for `--target prod`. Absent → no third pass
|
|
696
|
+
* (byte-identical to the two-pass default). Not captured by the collector.
|
|
697
|
+
*/
|
|
698
|
+
readonly emitDeclarations?: boolean | undefined;
|
|
692
699
|
}
|
|
693
700
|
/**
|
|
694
701
|
* Run tsdown.build() per TargetGroup. Composable so the escape hatch gets multi-group too.
|
|
@@ -1273,6 +1280,13 @@ interface GenerateMetaOptions {
|
|
|
1273
1280
|
readonly tsconfigPath: string;
|
|
1274
1281
|
/** Directory holding the tsdown-emitted per-file .d.ts (e.g. dist/dev/pkg). */
|
|
1275
1282
|
readonly dtsDir: string;
|
|
1283
|
+
/**
|
|
1284
|
+
* Per-module declarations dir used ONLY for a second, diagnostics-only API Extractor run that
|
|
1285
|
+
* resolves accurate per-file source locations. The shipped api-model is still produced from
|
|
1286
|
+
* `dtsDir` (the bundled dts), so it is unchanged. When omitted or equal to `dtsDir`, a single
|
|
1287
|
+
* run over `dtsDir` produces both model and diagnostics (legacy behavior).
|
|
1288
|
+
*/
|
|
1289
|
+
readonly aeInputDir?: string | undefined;
|
|
1276
1290
|
/** Map of entry name to the .d.ts basename (without extension) inside dtsDir. */
|
|
1277
1291
|
readonly entries: Record<string, string>;
|
|
1278
1292
|
/** Map of entry name to its export path (".", "./sub"). */
|
package/meta/api-extractor.js
CHANGED
|
@@ -12,12 +12,9 @@ const CI_FATAL_MESSAGE_IDS = /* @__PURE__ */ new Set(["ae-forgotten-export"]);
|
|
|
12
12
|
/**
|
|
13
13
|
* Map an API Extractor message to a collector DiagnosticInput, or undefined if not warn/error.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* position — wrong for release-tag codes, unresolved links, and the rest alike. A misleading
|
|
19
|
-
* `file:line` is worse than none, so it is omitted; the authoritative locator is the symbol
|
|
20
|
-
* name quoted in `text`. See systems#154.
|
|
15
|
+
* Location (`file`/`line`/`column`) is preserved: diagnostics now come from a per-module declaration
|
|
16
|
+
* run (see generateMeta's two-input split), where each `.d.ts.map` references only its own source, so
|
|
17
|
+
* positions resolve to the true source declaration. Reverts the systems#154 mitigation. See systems#162.
|
|
21
18
|
*/
|
|
22
19
|
function mapExtractorMessage(message) {
|
|
23
20
|
const isError = message.logLevel === ExtractorLogLevel.Error;
|
|
@@ -27,7 +24,10 @@ function mapExtractorMessage(message) {
|
|
|
27
24
|
source: "api-extractor",
|
|
28
25
|
level: isError ? "error" : "warn",
|
|
29
26
|
text: message.text,
|
|
30
|
-
...message.messageId !== void 0 ? { code: message.messageId } : {}
|
|
27
|
+
...message.messageId !== void 0 ? { code: message.messageId } : {},
|
|
28
|
+
...message.sourceFilePath !== void 0 ? { file: message.sourceFilePath } : {},
|
|
29
|
+
...message.sourceFileLine !== void 0 ? { line: message.sourceFileLine } : {},
|
|
30
|
+
...message.sourceFileColumn !== void 0 ? { column: message.sourceFileColumn } : {}
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
/** Run API Extractor over a single entry's .d.ts, writing the .api.json (and optionally tsdoc-metadata.json). Throws on failure. */
|
|
@@ -41,7 +41,7 @@ function runApiExtractor(options) {
|
|
|
41
41
|
mainEntryPointFilePath: options.entryDtsPath,
|
|
42
42
|
enumMemberOrder: "preserve",
|
|
43
43
|
compiler: { tsconfigFilePath: options.tsconfigPath },
|
|
44
|
-
docModel: {
|
|
44
|
+
docModel: options.emitDocModel === false ? { enabled: false } : {
|
|
45
45
|
enabled: true,
|
|
46
46
|
apiJsonFilePath: options.apiJsonPath
|
|
47
47
|
},
|
package/meta/generate.js
CHANGED
|
@@ -20,6 +20,8 @@ function unscopedName(name) {
|
|
|
20
20
|
*/
|
|
21
21
|
async function generateMeta(options) {
|
|
22
22
|
const { cwd, packageName, tsconfigPath, dtsDir, entries, exportPaths, outMetaDir, localPaths, tsdoc, manifestTransform, onMessage, ci, onSuppressed } = options;
|
|
23
|
+
const aeInputDir = options.aeInputDir ?? dtsDir;
|
|
24
|
+
const splitDiagnostics = aeInputDir !== dtsDir;
|
|
23
25
|
const tsdocConfigPath = writeTsdocConfig(cwd, tsdoc);
|
|
24
26
|
const packageJsonPath = join(cwd, "package.json");
|
|
25
27
|
mkdirSync(outMetaDir, { recursive: true });
|
|
@@ -29,24 +31,72 @@ async function generateMeta(options) {
|
|
|
29
31
|
const perEntryModels = /* @__PURE__ */ new Map();
|
|
30
32
|
const intermediateApiJsons = [];
|
|
31
33
|
let mainEntryDidTsdocMetadata = false;
|
|
34
|
+
const diagnosticKey = (e) => `${e.code ?? ""}\0${e.text}`;
|
|
35
|
+
const stripLocation = (e) => ({
|
|
36
|
+
source: e.source,
|
|
37
|
+
level: e.level,
|
|
38
|
+
text: e.text,
|
|
39
|
+
...e.code !== void 0 ? { code: e.code } : {},
|
|
40
|
+
...e.ciFatal !== void 0 ? { ciFatal: e.ciFatal } : {}
|
|
41
|
+
});
|
|
32
42
|
for (const entryName of entryNames) {
|
|
33
|
-
const
|
|
43
|
+
const modelEntryDts = join(dtsDir, `${entries[entryName]}.d.ts`);
|
|
34
44
|
const perEntryApiJson = join(outMetaDir, `${entryName.replace(/[\\/]/g, "__")}.entry.api.json`);
|
|
35
45
|
intermediateApiJsons.push(perEntryApiJson);
|
|
36
46
|
const isMain = (exportPaths[entryName] ?? (entryName === "index" ? "." : `./${entryName}`)) === ".";
|
|
47
|
+
const captureRollupOnlyFatal = splitDiagnostics && onMessage !== void 0 && ci !== true;
|
|
48
|
+
const rollupFatal = [];
|
|
49
|
+
const runBReported = /* @__PURE__ */ new Set();
|
|
50
|
+
const runAOnMessage = captureRollupOnlyFatal ? (entry) => {
|
|
51
|
+
if (entry.ciFatal === true) rollupFatal.push(entry);
|
|
52
|
+
} : ci === true && onMessage !== void 0 ? (entry) => {
|
|
53
|
+
if (entry.level === "error") onMessage(stripLocation(entry));
|
|
54
|
+
} : () => {};
|
|
37
55
|
runApiExtractor({
|
|
38
56
|
cwd,
|
|
39
57
|
packageJsonPath,
|
|
40
|
-
entryDtsPath,
|
|
58
|
+
entryDtsPath: modelEntryDts,
|
|
41
59
|
tsconfigPath,
|
|
42
60
|
tsdocConfigPath,
|
|
43
61
|
apiJsonPath: perEntryApiJson,
|
|
44
62
|
...isMain && !mainEntryDidTsdocMetadata ? { tsdocMetadataPath } : {},
|
|
45
63
|
suppressWarnings: tsdoc.suppressWarnings,
|
|
46
64
|
...ci !== void 0 ? { ci } : {},
|
|
47
|
-
...
|
|
48
|
-
|
|
65
|
+
...splitDiagnostics ? { onMessage: runAOnMessage } : {
|
|
66
|
+
...onMessage !== void 0 ? { onMessage } : {},
|
|
67
|
+
...onSuppressed !== void 0 ? { onSuppressed } : {}
|
|
68
|
+
}
|
|
49
69
|
});
|
|
70
|
+
if (splitDiagnostics) {
|
|
71
|
+
const runBOnMessage = captureRollupOnlyFatal && onMessage !== void 0 ? (entry) => {
|
|
72
|
+
if (entry.ciFatal === true) runBReported.add(diagnosticKey(entry));
|
|
73
|
+
onMessage(entry);
|
|
74
|
+
} : onMessage;
|
|
75
|
+
try {
|
|
76
|
+
runApiExtractor({
|
|
77
|
+
cwd,
|
|
78
|
+
packageJsonPath,
|
|
79
|
+
entryDtsPath: join(aeInputDir, `${entries[entryName]}.d.ts`),
|
|
80
|
+
tsconfigPath,
|
|
81
|
+
tsdocConfigPath,
|
|
82
|
+
apiJsonPath: perEntryApiJson,
|
|
83
|
+
emitDocModel: false,
|
|
84
|
+
suppressWarnings: tsdoc.suppressWarnings,
|
|
85
|
+
...runBOnMessage !== void 0 ? { onMessage: runBOnMessage } : {},
|
|
86
|
+
...onSuppressed !== void 0 ? { onSuppressed } : {}
|
|
87
|
+
});
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (onMessage !== void 0) onMessage({
|
|
90
|
+
source: "api-extractor",
|
|
91
|
+
level: "warn",
|
|
92
|
+
text: `Could not harvest per-module source locations for "${entryName}": ${String(err)}`
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (captureRollupOnlyFatal && onMessage !== void 0) for (const entry of rollupFatal) {
|
|
96
|
+
if (runBReported.has(diagnosticKey(entry))) continue;
|
|
97
|
+
onMessage(stripLocation(entry));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
50
100
|
if (isMain) mainEntryDidTsdocMetadata = true;
|
|
51
101
|
perEntryModels.set(entryName, JSON.parse(readFileSync(perEntryApiJson, "utf-8")));
|
|
52
102
|
}
|
package/meta/run-pass.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createEntryName } from "../entry/extract.js";
|
|
2
|
+
import { declarationsDirFor } from "../build/target-groups.js";
|
|
2
3
|
import { resolveNextVersions } from "../changesets/next-versions.js";
|
|
3
4
|
import { normalizeMetaOptions } from "./config.js";
|
|
4
5
|
import { generateMeta } from "./generate.js";
|
|
@@ -31,6 +32,7 @@ async function runMetaPass(o) {
|
|
|
31
32
|
packageName: o.packageName,
|
|
32
33
|
tsconfigPath: o.tsconfigPath,
|
|
33
34
|
dtsDir: join(o.cwd, "dist", "prod", g.id, "pkg"),
|
|
35
|
+
aeInputDir: declarationsDirFor(o.cwd, g.id),
|
|
34
36
|
entries: dtsBasenames,
|
|
35
37
|
exportPaths,
|
|
36
38
|
outMetaDir: join(o.cwd, "dist", "prod", g.id, "meta"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@savvy-web/tsdown-plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Interface-only tsdown/rolldown plugin pack powering @savvy-web/bundler",
|
|
6
6
|
"homepage": "https://github.com/savvy-web/systems/tree/main/packages/tsdown-plugins",
|