tonnage 0.3.0 → 0.4.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/AGENTS.md +7 -8
- package/CHANGELOG.md +6 -0
- package/README.md +84 -118
- package/README.mdx +78 -109
- package/dist/index.d.mts +31 -30
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +226 -3
- package/dist/index.mjs.map +1 -1
- package/examples/tonnage.config.ts +0 -1
- package/package.json +5 -13
- package/src/index.ts +2 -11
- package/src/report-content.ts +1 -1
- package/src/report.ts +0 -15
- package/src/types.ts +0 -10
- package/dist/cli.mjs +0 -113
- package/dist/cli.mjs.map +0 -1
- package/dist/galley.d.mts +0 -12
- package/dist/galley.d.mts.map +0 -1
- package/dist/galley.mjs +0 -12
- package/dist/galley.mjs.map +0 -1
- package/dist/report-B3HrJ4UQ.mjs +0 -237
- package/dist/report-B3HrJ4UQ.mjs.map +0 -1
- package/dist/run-BJN1MXRf.mjs +0 -44
- package/dist/run-BJN1MXRf.mjs.map +0 -1
- package/dist/types-Ddtp95Z3.d.mts +0 -49
- package/dist/types-Ddtp95Z3.d.mts.map +0 -1
- package/src/cli.ts +0 -96
- package/src/command-line.ts +0 -70
- package/src/readme.ts +0 -48
- package/src/run.ts +0 -28
package/dist/report-B3HrJ4UQ.mjs
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { renderMarkdown } from "galley.md";
|
|
4
|
-
import { gzipSync } from "node:zlib";
|
|
5
|
-
import { build } from "esbuild";
|
|
6
|
-
import os from "node:os";
|
|
7
|
-
import { jsx } from "galley.md/jsx-runtime";
|
|
8
|
-
//#region src/measure.ts
|
|
9
|
-
async function measureEntry(entry, options) {
|
|
10
|
-
return measureEntryWithPlugins(entry, options);
|
|
11
|
-
}
|
|
12
|
-
async function measureEntryWithPlugins(entry, options) {
|
|
13
|
-
return measureBuild({ entryPoints: [path.resolve(options.resolveDirectory, entry)] }, options);
|
|
14
|
-
}
|
|
15
|
-
async function measureImports(imports, options) {
|
|
16
|
-
return measureImportsWithPlugins(imports, options);
|
|
17
|
-
}
|
|
18
|
-
async function measureImportsWithPlugins(imports, options) {
|
|
19
|
-
if (imports.length === 0) throw new Error(`A tonnage entry must import at least one module.`);
|
|
20
|
-
return measureBuild({ stdin: {
|
|
21
|
-
contents: imports.map((specifier, index) => `export * as entry${index} from ${JSON.stringify(specifier)}`).join(`\n`),
|
|
22
|
-
loader: `js`,
|
|
23
|
-
resolveDir: options.resolveDirectory,
|
|
24
|
-
sourcefile: `tonnage-entry.js`
|
|
25
|
-
} }, options);
|
|
26
|
-
}
|
|
27
|
-
async function measureBuild(entry, options) {
|
|
28
|
-
const platform = options.platform ?? `neutral`;
|
|
29
|
-
const outputFiles = [...(await build({
|
|
30
|
-
bundle: true,
|
|
31
|
-
charset: `utf8`,
|
|
32
|
-
external: [...options.external ?? [], ...platform === `browser` ? [] : [`node:*`]],
|
|
33
|
-
format: `esm`,
|
|
34
|
-
legalComments: `none`,
|
|
35
|
-
logLevel: `silent`,
|
|
36
|
-
...platform === `neutral` ? { mainFields: [`module`, `main`] } : {},
|
|
37
|
-
minify: true,
|
|
38
|
-
outdir: `out`,
|
|
39
|
-
platform,
|
|
40
|
-
plugins: [...options.plugins ?? []],
|
|
41
|
-
...entry,
|
|
42
|
-
target: options.target ?? `es2022`,
|
|
43
|
-
treeShaking: true,
|
|
44
|
-
write: false
|
|
45
|
-
})).outputFiles ?? []].filter((output) => output.path.endsWith(`.js`)).sort((left, right) => left.path.localeCompare(right.path));
|
|
46
|
-
if (outputFiles.length === 0) throw new Error(`The tonnage entry did not emit any runtime JavaScript.`);
|
|
47
|
-
const rawBytes = outputFiles.reduce((total, output) => total + output.contents.byteLength, 0);
|
|
48
|
-
return {
|
|
49
|
-
gzipBytes: outputFiles.reduce((total, output) => total + gzipSync(output.contents, { level: 9 }).byteLength, 0),
|
|
50
|
-
rawBytes
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
//#endregion
|
|
54
|
-
//#region src/package-resolution.ts
|
|
55
|
-
async function withPackageResolver(packageName, packageDirectory, useResolver) {
|
|
56
|
-
const temporaryDirectory = await fs.mkdtemp(path.join(os.tmpdir(), `tonnage-package-`));
|
|
57
|
-
try {
|
|
58
|
-
const nodeModulesDirectory = path.join(temporaryDirectory, `node_modules`);
|
|
59
|
-
const packageLinkPath = path.resolve(nodeModulesDirectory, packageName);
|
|
60
|
-
if (!packageLinkPath.startsWith(`${nodeModulesDirectory}${path.sep}`)) throw new Error(`Invalid package name: ${packageName}`);
|
|
61
|
-
await fs.mkdir(path.dirname(packageLinkPath), { recursive: true });
|
|
62
|
-
await fs.symlink(packageDirectory, packageLinkPath, process.platform === `win32` ? `junction` : `dir`);
|
|
63
|
-
const filter = new RegExp(`^${escapeRegExp(packageName)}(?:/.*)?$`);
|
|
64
|
-
return await useResolver({
|
|
65
|
-
name: `tonnage-package-resolution`,
|
|
66
|
-
setup(build) {
|
|
67
|
-
build.onResolve({ filter }, async (args) => {
|
|
68
|
-
if (args.resolveDir === temporaryDirectory) return;
|
|
69
|
-
return build.resolve(args.path, {
|
|
70
|
-
importer: args.importer,
|
|
71
|
-
kind: args.kind,
|
|
72
|
-
namespace: args.namespace,
|
|
73
|
-
resolveDir: temporaryDirectory,
|
|
74
|
-
with: args.with
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
} finally {
|
|
80
|
-
await fs.rm(temporaryDirectory, {
|
|
81
|
-
force: true,
|
|
82
|
-
recursive: true
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function escapeRegExp(value) {
|
|
87
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, `\\$&`);
|
|
88
|
-
}
|
|
89
|
-
//#endregion
|
|
90
|
-
//#region src/report-content.ts
|
|
91
|
-
/** Use Galley's JSX runtime directly so the source CLI stays executable by Node. */
|
|
92
|
-
function reportContent(report, config = {}) {
|
|
93
|
-
const hasExports = report.exports.length > 0;
|
|
94
|
-
const hasRecipes = report.recipes.length > 0;
|
|
95
|
-
const hasMultipleSections = hasExports && hasRecipes;
|
|
96
|
-
const content = [jsx("h2", { children: config.heading ?? "Bundle size" })];
|
|
97
|
-
if (hasExports) content.push(jsx("p", { children: "Package export sizes include complete runtime export surfaces." }));
|
|
98
|
-
if (hasRecipes) content.push(jsx("p", { children: "Usage example sizes bundle their entry files and tree-shake unused exports." }));
|
|
99
|
-
content.push(jsx("p", { children: "Sizes are exact minified and level-9 gzip JavaScript byte counts. Declarations, source maps, CSS, and other assets are excluded. Peer dependencies stay external, and shared modules are counted once per bundle." }));
|
|
100
|
-
if (hasExports) {
|
|
101
|
-
if (hasMultipleSections) content.push(jsx("h3", { children: "Package exports" }));
|
|
102
|
-
content.push(table([
|
|
103
|
-
{ title: "Import" },
|
|
104
|
-
{
|
|
105
|
-
title: "Minified JS",
|
|
106
|
-
align: "right"
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
title: "Gzip JS",
|
|
110
|
-
align: "right"
|
|
111
|
-
}
|
|
112
|
-
], report.exports.map((row) => [
|
|
113
|
-
code(row.name),
|
|
114
|
-
formatBytes(row.rawBytes),
|
|
115
|
-
formatBytes(row.gzipBytes)
|
|
116
|
-
])));
|
|
117
|
-
}
|
|
118
|
-
if (hasRecipes) {
|
|
119
|
-
if (hasMultipleSections) content.push(jsx("h3", { children: "Usage examples" }));
|
|
120
|
-
content.push(table([
|
|
121
|
-
{ title: "Recipe" },
|
|
122
|
-
{ title: "Entry" },
|
|
123
|
-
{
|
|
124
|
-
title: "Minified JS",
|
|
125
|
-
align: "right"
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
title: "Gzip JS",
|
|
129
|
-
align: "right"
|
|
130
|
-
}
|
|
131
|
-
], report.recipes.map((row) => [
|
|
132
|
-
row.name,
|
|
133
|
-
code(row.entry),
|
|
134
|
-
formatBytes(row.rawBytes),
|
|
135
|
-
formatBytes(row.gzipBytes)
|
|
136
|
-
])));
|
|
137
|
-
}
|
|
138
|
-
content.push(jsx("p", { children: [
|
|
139
|
-
"Report maintained with ",
|
|
140
|
-
jsx("a", {
|
|
141
|
-
href: "https://github.com/jeremybanka/tonnage",
|
|
142
|
-
children: "tonnage"
|
|
143
|
-
}),
|
|
144
|
-
"."
|
|
145
|
-
] }));
|
|
146
|
-
return content;
|
|
147
|
-
}
|
|
148
|
-
function code(value) {
|
|
149
|
-
return {
|
|
150
|
-
type: "inlineCode",
|
|
151
|
-
value
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
function formatBytes(bytes) {
|
|
155
|
-
return `${new Intl.NumberFormat("en-US").format(bytes)} B`;
|
|
156
|
-
}
|
|
157
|
-
function table(columns, rows) {
|
|
158
|
-
return jsx("table", { children: [jsx("thead", { children: jsx("tr", { children: columns.map(({ title, align }) => jsx("th", {
|
|
159
|
-
children: title,
|
|
160
|
-
align
|
|
161
|
-
})) }) }), jsx("tbody", { children: rows.map((cells) => jsx("tr", { children: cells.map((children) => jsx("td", { children })) })) })] });
|
|
162
|
-
}
|
|
163
|
-
//#endregion
|
|
164
|
-
//#region src/report.ts
|
|
165
|
-
async function createTonnageReport(config, configDirectory) {
|
|
166
|
-
const packageJsonPath = path.resolve(configDirectory, config.packageJson ?? `package.json`);
|
|
167
|
-
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, `utf8`));
|
|
168
|
-
if (!packageJson.name) throw new Error(`${packageJsonPath} does not declare a package name.`);
|
|
169
|
-
const resolveDirectory = path.dirname(packageJsonPath);
|
|
170
|
-
const external = unique([...Object.keys(packageJson.peerDependencies ?? {}), ...config.external ?? []]);
|
|
171
|
-
const exportImports = selectExportImports(packageJson.name, packageJson.exports, config.exports);
|
|
172
|
-
const { exportRows, recipeRows } = await withPackageResolver(packageJson.name, resolveDirectory, async (packageResolver) => {
|
|
173
|
-
return {
|
|
174
|
-
exportRows: await Promise.all(exportImports.map(async (specifier) => ({
|
|
175
|
-
...await measureImportsWithPlugins([specifier], {
|
|
176
|
-
external,
|
|
177
|
-
platform: config.platform,
|
|
178
|
-
plugins: [packageResolver],
|
|
179
|
-
resolveDirectory,
|
|
180
|
-
target: config.target
|
|
181
|
-
}),
|
|
182
|
-
imports: [specifier],
|
|
183
|
-
name: specifier
|
|
184
|
-
}))),
|
|
185
|
-
recipeRows: await Promise.all((config.recipes ?? []).map(async (recipe) => ({
|
|
186
|
-
...await measureEntryWithPlugins(recipe.entry, {
|
|
187
|
-
external: unique([...external, ...recipe.external ?? []]),
|
|
188
|
-
platform: recipe.platform ?? config.platform,
|
|
189
|
-
plugins: [packageResolver],
|
|
190
|
-
resolveDirectory,
|
|
191
|
-
target: config.target
|
|
192
|
-
}),
|
|
193
|
-
entry: recipe.entry,
|
|
194
|
-
name: recipe.name
|
|
195
|
-
})))
|
|
196
|
-
};
|
|
197
|
-
});
|
|
198
|
-
return {
|
|
199
|
-
exports: exportRows,
|
|
200
|
-
packageName: packageJson.name,
|
|
201
|
-
recipes: recipeRows
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
/** Serialize a report with the same Galley elements used by the document component. */
|
|
205
|
-
async function renderTonnageMarkdown(report, config = {}) {
|
|
206
|
-
const directory = process.cwd();
|
|
207
|
-
return renderMarkdown(reportContent(report, config), {
|
|
208
|
-
directory,
|
|
209
|
-
path: path.resolve(directory, config.readme ?? "README.md")
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
function selectExportImports(packageName, exportsValue, selection) {
|
|
213
|
-
if (selection === false) return [];
|
|
214
|
-
const exportKeys = getExportKeys(exportsValue);
|
|
215
|
-
const options = typeof selection === `object` ? selection : {};
|
|
216
|
-
const included = options.include ? [...options.include] : exportKeys.filter((exportKey) => exportKey !== `./package.json`);
|
|
217
|
-
const excluded = new Set(options.exclude ?? []);
|
|
218
|
-
return included.filter((exportKey) => !excluded.has(exportKey)).map((exportKey) => toPackageImport(packageName, exportKey));
|
|
219
|
-
}
|
|
220
|
-
function getExportKeys(exportsValue) {
|
|
221
|
-
if (typeof exportsValue === `string` || Array.isArray(exportsValue)) return [`.`];
|
|
222
|
-
if (!exportsValue || typeof exportsValue !== `object`) return [`.`];
|
|
223
|
-
const keys = Object.keys(exportsValue);
|
|
224
|
-
return keys.some((key) => key.startsWith(`.`)) ? keys : [`.`];
|
|
225
|
-
}
|
|
226
|
-
function toPackageImport(packageName, exportKey) {
|
|
227
|
-
if (exportKey === `.`) return packageName;
|
|
228
|
-
if (!exportKey.startsWith(`./`)) throw new Error(`Invalid package export key: ${exportKey}`);
|
|
229
|
-
return `${packageName}/${exportKey.slice(2)}`;
|
|
230
|
-
}
|
|
231
|
-
function unique(values) {
|
|
232
|
-
return [...new Set(values)];
|
|
233
|
-
}
|
|
234
|
-
//#endregion
|
|
235
|
-
export { measureImports as a, measureEntry as i, renderTonnageMarkdown as n, reportContent as r, createTonnageReport as t };
|
|
236
|
-
|
|
237
|
-
//# sourceMappingURL=report-B3HrJ4UQ.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"report-B3HrJ4UQ.mjs","names":[],"sources":["../src/measure.ts","../src/package-resolution.ts","../src/report-content.ts","../src/report.ts"],"sourcesContent":["import path from \"node:path\"\nimport { gzipSync } from \"node:zlib\"\n\nimport { build, type BuildOptions, type Plugin } from \"esbuild\"\n\nimport type { BundleMeasurement, BundlePlatform } from \"./types.ts\"\n\nexport type MeasureImportsOptions = {\n\texternal?: readonly string[] | undefined\n\tplatform?: BundlePlatform | undefined\n\tresolveDirectory: string\n\ttarget?: string | undefined\n}\n\nexport type MeasureEntryOptions = MeasureImportsOptions\n\ntype InternalMeasureOptions = MeasureImportsOptions & {\n\tplugins?: readonly Plugin[]\n}\n\nexport async function measureEntry(\n\tentry: string,\n\toptions: MeasureEntryOptions,\n): Promise<BundleMeasurement> {\n\treturn measureEntryWithPlugins(entry, options)\n}\n\nexport async function measureEntryWithPlugins(\n\tentry: string,\n\toptions: InternalMeasureOptions,\n): Promise<BundleMeasurement> {\n\treturn measureBuild(\n\t\t{ entryPoints: [path.resolve(options.resolveDirectory, entry)] },\n\t\toptions,\n\t)\n}\n\nexport async function measureImports(\n\timports: readonly string[],\n\toptions: MeasureImportsOptions,\n): Promise<BundleMeasurement> {\n\treturn measureImportsWithPlugins(imports, options)\n}\n\nexport async function measureImportsWithPlugins(\n\timports: readonly string[],\n\toptions: InternalMeasureOptions,\n): Promise<BundleMeasurement> {\n\tif (imports.length === 0) {\n\t\tthrow new Error(`A tonnage entry must import at least one module.`)\n\t}\n\n\tconst entry = imports\n\t\t.map(\n\t\t\t(specifier, index) =>\n\t\t\t\t`export * as entry${index} from ${JSON.stringify(specifier)}`,\n\t\t)\n\t\t.join(`\\n`)\n\treturn measureBuild(\n\t\t{\n\t\t\tstdin: {\n\t\t\t\tcontents: entry,\n\t\t\t\tloader: `js`,\n\t\t\t\tresolveDir: options.resolveDirectory,\n\t\t\t\tsourcefile: `tonnage-entry.js`,\n\t\t\t},\n\t\t},\n\t\toptions,\n\t)\n}\n\nasync function measureBuild(\n\tentry: Pick<BuildOptions, `entryPoints` | `stdin`>,\n\toptions: InternalMeasureOptions,\n): Promise<BundleMeasurement> {\n\tconst platform = options.platform ?? `neutral`\n\n\tconst result = await build({\n\t\tbundle: true,\n\t\tcharset: `utf8`,\n\t\texternal: [\n\t\t\t...(options.external ?? []),\n\t\t\t...(platform === `browser` ? [] : [`node:*`]),\n\t\t],\n\t\tformat: `esm`,\n\t\tlegalComments: `none`,\n\t\tlogLevel: `silent`,\n\t\t...(platform === `neutral` ? { mainFields: [`module`, `main`] } : {}),\n\t\tminify: true,\n\t\toutdir: `out`,\n\t\tplatform,\n\t\tplugins: [...(options.plugins ?? [])],\n\t\t...entry,\n\t\ttarget: options.target ?? `es2022`,\n\t\ttreeShaking: true,\n\t\twrite: false,\n\t})\n\n\tconst outputFiles = [...(result.outputFiles ?? [])]\n\t\t.filter((output) => output.path.endsWith(`.js`))\n\t\t.sort((left, right) => left.path.localeCompare(right.path))\n\tif (outputFiles.length === 0) {\n\t\tthrow new Error(`The tonnage entry did not emit any runtime JavaScript.`)\n\t}\n\tconst rawBytes = outputFiles.reduce(\n\t\t(total, output) => total + output.contents.byteLength,\n\t\t0,\n\t)\n\tconst gzipBytes = outputFiles.reduce(\n\t\t(total, output) =>\n\t\t\ttotal + gzipSync(output.contents, { level: 9 }).byteLength,\n\t\t0,\n\t)\n\n\treturn { gzipBytes, rawBytes }\n}\n","import fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nimport type { Plugin } from \"esbuild\"\n\nexport async function withPackageResolver<Result>(\n\tpackageName: string,\n\tpackageDirectory: string,\n\tuseResolver: (plugin: Plugin) => Promise<Result>,\n): Promise<Result> {\n\tconst temporaryDirectory = await fs.mkdtemp(\n\t\tpath.join(os.tmpdir(), `tonnage-package-`),\n\t)\n\ttry {\n\t\tconst nodeModulesDirectory = path.join(temporaryDirectory, `node_modules`)\n\t\tconst packageLinkPath = path.resolve(nodeModulesDirectory, packageName)\n\t\tif (!packageLinkPath.startsWith(`${nodeModulesDirectory}${path.sep}`)) {\n\t\t\tthrow new Error(`Invalid package name: ${packageName}`)\n\t\t}\n\n\t\tawait fs.mkdir(path.dirname(packageLinkPath), { recursive: true })\n\t\tawait fs.symlink(\n\t\t\tpackageDirectory,\n\t\t\tpackageLinkPath,\n\t\t\tprocess.platform === `win32` ? `junction` : `dir`,\n\t\t)\n\n\t\tconst filter = new RegExp(`^${escapeRegExp(packageName)}(?:/.*)?$`)\n\t\tconst plugin: Plugin = {\n\t\t\tname: `tonnage-package-resolution`,\n\t\t\tsetup(build) {\n\t\t\t\tbuild.onResolve({ filter }, async (args) => {\n\t\t\t\t\tif (args.resolveDir === temporaryDirectory) {\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\treturn build.resolve(args.path, {\n\t\t\t\t\t\timporter: args.importer,\n\t\t\t\t\t\tkind: args.kind,\n\t\t\t\t\t\tnamespace: args.namespace,\n\t\t\t\t\t\tresolveDir: temporaryDirectory,\n\t\t\t\t\t\twith: args.with,\n\t\t\t\t\t})\n\t\t\t\t})\n\t\t\t},\n\t\t}\n\n\t\treturn await useResolver(plugin)\n\t} finally {\n\t\tawait fs.rm(temporaryDirectory, { force: true, recursive: true })\n\t}\n}\n\nfunction escapeRegExp(value: string): string {\n\treturn value.replace(/[.*+?^${}()|[\\]\\\\]/g, `\\\\$&`)\n}\n","import type { Content, Element } from \"galley.md\"\nimport { jsx } from \"galley.md/jsx-runtime\"\n\nimport type { TonnageConfig, TonnageReport } from \"./types.ts\"\n\n/** Use Galley's JSX runtime directly so the source CLI stays executable by Node. */\nexport function reportContent(\n\treport: TonnageReport,\n\tconfig: TonnageConfig = {},\n): Element[] {\n\tconst hasExports = report.exports.length > 0\n\tconst hasRecipes = report.recipes.length > 0\n\tconst hasMultipleSections = hasExports && hasRecipes\n\tconst content = [jsx(\"h2\", { children: config.heading ?? \"Bundle size\" })]\n\n\tif (hasExports)\n\t\tcontent.push(\n\t\t\tjsx(\"p\", {\n\t\t\t\tchildren:\n\t\t\t\t\t\"Package export sizes include complete runtime export surfaces.\",\n\t\t\t}),\n\t\t)\n\tif (hasRecipes)\n\t\tcontent.push(\n\t\t\tjsx(\"p\", {\n\t\t\t\tchildren:\n\t\t\t\t\t\"Usage example sizes bundle their entry files and tree-shake unused exports.\",\n\t\t\t}),\n\t\t)\n\tcontent.push(\n\t\tjsx(\"p\", {\n\t\t\tchildren:\n\t\t\t\t\"Sizes are exact minified and level-9 gzip JavaScript byte counts. Declarations, source maps, CSS, and other assets are excluded. Peer dependencies stay external, and shared modules are counted once per bundle.\",\n\t\t}),\n\t)\n\n\tif (hasExports) {\n\t\tif (hasMultipleSections)\n\t\t\tcontent.push(jsx(\"h3\", { children: \"Package exports\" }))\n\t\tcontent.push(\n\t\t\ttable(\n\t\t\t\t[\n\t\t\t\t\t{ title: \"Import\" },\n\t\t\t\t\t{ title: \"Minified JS\", align: \"right\" },\n\t\t\t\t\t{ title: \"Gzip JS\", align: \"right\" },\n\t\t\t\t],\n\t\t\t\treport.exports.map((row) => [\n\t\t\t\t\tcode(row.name),\n\t\t\t\t\tformatBytes(row.rawBytes),\n\t\t\t\t\tformatBytes(row.gzipBytes),\n\t\t\t\t]),\n\t\t\t),\n\t\t)\n\t}\n\n\tif (hasRecipes) {\n\t\tif (hasMultipleSections)\n\t\t\tcontent.push(jsx(\"h3\", { children: \"Usage examples\" }))\n\t\tcontent.push(\n\t\t\ttable(\n\t\t\t\t[\n\t\t\t\t\t{ title: \"Recipe\" },\n\t\t\t\t\t{ title: \"Entry\" },\n\t\t\t\t\t{ title: \"Minified JS\", align: \"right\" },\n\t\t\t\t\t{ title: \"Gzip JS\", align: \"right\" },\n\t\t\t\t],\n\t\t\t\treport.recipes.map((row) => [\n\t\t\t\t\trow.name,\n\t\t\t\t\tcode(row.entry),\n\t\t\t\t\tformatBytes(row.rawBytes),\n\t\t\t\t\tformatBytes(row.gzipBytes),\n\t\t\t\t]),\n\t\t\t),\n\t\t)\n\t}\n\n\tcontent.push(\n\t\tjsx(\"p\", {\n\t\t\tchildren: [\n\t\t\t\t\"Report maintained with \",\n\t\t\t\tjsx(\"a\", {\n\t\t\t\t\thref: \"https://github.com/jeremybanka/tonnage\",\n\t\t\t\t\tchildren: \"tonnage\",\n\t\t\t\t}),\n\t\t\t\t\".\",\n\t\t\t],\n\t\t}),\n\t)\n\treturn content\n}\n\nfunction code(value: string): Content {\n\treturn { type: \"inlineCode\", value }\n}\n\nfunction formatBytes(bytes: number): string {\n\treturn `${new Intl.NumberFormat(\"en-US\").format(bytes)} B`\n}\n\nfunction table(\n\tcolumns: readonly { title: string; align?: \"right\" }[],\n\trows: readonly (readonly Content[])[],\n): Element {\n\treturn jsx(\"table\", {\n\t\tchildren: [\n\t\t\tjsx(\"thead\", {\n\t\t\t\tchildren: jsx(\"tr\", {\n\t\t\t\t\tchildren: columns.map(({ title, align }) =>\n\t\t\t\t\t\tjsx(\"th\", { children: title, align }),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t}),\n\t\t\tjsx(\"tbody\", {\n\t\t\t\tchildren: rows.map((cells) =>\n\t\t\t\t\tjsx(\"tr\", {\n\t\t\t\t\t\tchildren: cells.map((children) => jsx(\"td\", { children })),\n\t\t\t\t\t}),\n\t\t\t\t),\n\t\t\t}),\n\t\t],\n\t})\n}\n","import fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { renderMarkdown } from \"galley.md\"\n\nimport {\n\tmeasureEntryWithPlugins,\n\tmeasureImportsWithPlugins,\n} from \"./measure.ts\"\nimport { withPackageResolver } from \"./package-resolution.ts\"\nimport { reportContent } from \"./report-content.ts\"\nimport type {\n\tTonnageConfig,\n\tTonnageExports,\n\tTonnageReport,\n\tTonnageRow,\n} from \"./types.ts\"\n\ntype PackageJson = {\n\texports?: unknown\n\tname?: string\n\tpeerDependencies?: Record<string, string>\n}\n\nexport async function createTonnageReport(\n\tconfig: TonnageConfig,\n\tconfigDirectory: string,\n): Promise<TonnageReport> {\n\tconst packageJsonPath = path.resolve(\n\t\tconfigDirectory,\n\t\tconfig.packageJson ?? `package.json`,\n\t)\n\tconst packageJson = JSON.parse(\n\t\tawait fs.readFile(packageJsonPath, `utf8`),\n\t) as PackageJson\n\tif (!packageJson.name) {\n\t\tthrow new Error(`${packageJsonPath} does not declare a package name.`)\n\t}\n\n\tconst resolveDirectory = path.dirname(packageJsonPath)\n\tconst peerDependencies = Object.keys(packageJson.peerDependencies ?? {})\n\tconst external = unique([...peerDependencies, ...(config.external ?? [])])\n\tconst exportImports = selectExportImports(\n\t\tpackageJson.name,\n\t\tpackageJson.exports,\n\t\tconfig.exports,\n\t)\n\n\tconst { exportRows, recipeRows } = await withPackageResolver(\n\t\tpackageJson.name,\n\t\tresolveDirectory,\n\t\tasync (packageResolver) => {\n\t\t\tconst exportRows = await Promise.all(\n\t\t\t\texportImports.map(async (specifier): Promise<TonnageRow> => ({\n\t\t\t\t\t...(await measureImportsWithPlugins([specifier], {\n\t\t\t\t\t\texternal,\n\t\t\t\t\t\tplatform: config.platform,\n\t\t\t\t\t\tplugins: [packageResolver],\n\t\t\t\t\t\tresolveDirectory,\n\t\t\t\t\t\ttarget: config.target,\n\t\t\t\t\t})),\n\t\t\t\t\timports: [specifier],\n\t\t\t\t\tname: specifier,\n\t\t\t\t})),\n\t\t\t)\n\t\t\tconst recipeRows = await Promise.all(\n\t\t\t\t(config.recipes ?? []).map(async (recipe) => ({\n\t\t\t\t\t...(await measureEntryWithPlugins(recipe.entry, {\n\t\t\t\t\t\texternal: unique([...external, ...(recipe.external ?? [])]),\n\t\t\t\t\t\tplatform: recipe.platform ?? config.platform,\n\t\t\t\t\t\tplugins: [packageResolver],\n\t\t\t\t\t\tresolveDirectory,\n\t\t\t\t\t\ttarget: config.target,\n\t\t\t\t\t})),\n\t\t\t\t\tentry: recipe.entry,\n\t\t\t\t\tname: recipe.name,\n\t\t\t\t})),\n\t\t\t)\n\n\t\t\treturn { exportRows, recipeRows }\n\t\t},\n\t)\n\n\treturn {\n\t\texports: exportRows,\n\t\tpackageName: packageJson.name,\n\t\trecipes: recipeRows,\n\t}\n}\n\n/** Serialize a report with the same Galley elements used by the document component. */\nexport async function renderTonnageMarkdown(\n\treport: TonnageReport,\n\tconfig: TonnageConfig = {},\n): Promise<string> {\n\tconst directory = process.cwd()\n\treturn renderMarkdown(reportContent(report, config), {\n\t\tdirectory,\n\t\tpath: path.resolve(directory, config.readme ?? \"README.md\"),\n\t})\n}\n\nfunction selectExportImports(\n\tpackageName: string,\n\texportsValue: unknown,\n\tselection: TonnageConfig[`exports`],\n): string[] {\n\tif (selection === false) {\n\t\treturn []\n\t}\n\n\tconst exportKeys = getExportKeys(exportsValue)\n\tconst options: TonnageExports = typeof selection === `object` ? selection : {}\n\tconst included = options.include\n\t\t? [...options.include]\n\t\t: exportKeys.filter((exportKey) => exportKey !== `./package.json`)\n\tconst excluded = new Set(options.exclude ?? [])\n\n\treturn included\n\t\t.filter((exportKey) => !excluded.has(exportKey))\n\t\t.map((exportKey) => toPackageImport(packageName, exportKey))\n}\n\nfunction getExportKeys(exportsValue: unknown): string[] {\n\tif (typeof exportsValue === `string` || Array.isArray(exportsValue)) {\n\t\treturn [`.`]\n\t}\n\tif (!exportsValue || typeof exportsValue !== `object`) {\n\t\treturn [`.`]\n\t}\n\n\tconst keys = Object.keys(exportsValue)\n\treturn keys.some((key) => key.startsWith(`.`)) ? keys : [`.`]\n}\n\nfunction toPackageImport(packageName: string, exportKey: string): string {\n\tif (exportKey === `.`) {\n\t\treturn packageName\n\t}\n\tif (!exportKey.startsWith(`./`)) {\n\t\tthrow new Error(`Invalid package export key: ${exportKey}`)\n\t}\n\treturn `${packageName}/${exportKey.slice(2)}`\n}\n\nfunction unique(values: readonly string[]): string[] {\n\treturn [...new Set(values)]\n}\n"],"mappings":"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"}
|
package/dist/run-BJN1MXRf.mjs
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { n as renderTonnageMarkdown, t as createTonnageReport } from "./report-B3HrJ4UQ.mjs";
|
|
2
|
-
import fs from "node:fs/promises";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
//#region src/readme.ts
|
|
5
|
-
function tonnageMarkers(marker = `default`) {
|
|
6
|
-
validateMarker(marker);
|
|
7
|
-
return {
|
|
8
|
-
end: `<!-- tonnage:${marker}:end -->`,
|
|
9
|
-
start: `<!-- tonnage:${marker}:start -->`
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
function updateGeneratedSection(readme, markdown, marker) {
|
|
13
|
-
const { end, start } = tonnageMarkers(marker);
|
|
14
|
-
const startIndex = readme.indexOf(start);
|
|
15
|
-
const endIndex = readme.indexOf(end);
|
|
16
|
-
if (startIndex === -1 || endIndex === -1) throw new Error(`README must contain exactly one ${start} and one ${end} marker.`);
|
|
17
|
-
if (readme.slice(startIndex + start.length).includes(start) || readme.slice(endIndex + end.length).includes(end)) throw new Error(`README must contain exactly one ${start} and one ${end} marker.`);
|
|
18
|
-
if (endIndex < startIndex) throw new Error(`${end} must appear after ${start}.`);
|
|
19
|
-
const contentStart = startIndex + start.length;
|
|
20
|
-
return `${readme.slice(0, contentStart)}\n\n${markdown.trim()}\n\n${readme.slice(endIndex)}`;
|
|
21
|
-
}
|
|
22
|
-
function validateMarker(marker) {
|
|
23
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(marker)) throw new Error(`Tonnage marker names may contain letters, numbers, dots, underscores, and hyphens.`);
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/run.ts
|
|
27
|
-
async function runTonnage(config, options) {
|
|
28
|
-
const configDirectory = path.resolve(options.configDirectory ?? process.cwd());
|
|
29
|
-
const readmePath = path.resolve(configDirectory, config.readme ?? `README.md`);
|
|
30
|
-
const oldReadme = await fs.readFile(readmePath, `utf8`);
|
|
31
|
-
const report = await createTonnageReport(config, configDirectory);
|
|
32
|
-
const newReadme = updateGeneratedSection(oldReadme, await renderTonnageMarkdown(report, config), config.marker);
|
|
33
|
-
const changed = oldReadme !== newReadme;
|
|
34
|
-
if (options.mode === `write` && changed) await fs.writeFile(readmePath, newReadme);
|
|
35
|
-
return {
|
|
36
|
-
changed,
|
|
37
|
-
readmePath,
|
|
38
|
-
report
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
//#endregion
|
|
42
|
-
export { tonnageMarkers as n, updateGeneratedSection as r, runTonnage as t };
|
|
43
|
-
|
|
44
|
-
//# sourceMappingURL=run-BJN1MXRf.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-BJN1MXRf.mjs","names":[],"sources":["../src/readme.ts","../src/run.ts"],"sourcesContent":["export function tonnageMarkers(marker = `default`): {\n\tend: string\n\tstart: string\n} {\n\tvalidateMarker(marker)\n\treturn {\n\t\tend: `<!-- tonnage:${marker}:end -->`,\n\t\tstart: `<!-- tonnage:${marker}:start -->`,\n\t}\n}\n\nexport function updateGeneratedSection(\n\treadme: string,\n\tmarkdown: string,\n\tmarker?: string,\n): string {\n\tconst { end, start } = tonnageMarkers(marker)\n\tconst startIndex = readme.indexOf(start)\n\tconst endIndex = readme.indexOf(end)\n\n\tif (startIndex === -1 || endIndex === -1) {\n\t\tthrow new Error(\n\t\t\t`README must contain exactly one ${start} and one ${end} marker.`,\n\t\t)\n\t}\n\tif (\n\t\treadme.slice(startIndex + start.length).includes(start) ||\n\t\treadme.slice(endIndex + end.length).includes(end)\n\t) {\n\t\tthrow new Error(\n\t\t\t`README must contain exactly one ${start} and one ${end} marker.`,\n\t\t)\n\t}\n\tif (endIndex < startIndex) {\n\t\tthrow new Error(`${end} must appear after ${start}.`)\n\t}\n\n\tconst contentStart = startIndex + start.length\n\treturn `${readme.slice(0, contentStart)}\\n\\n${markdown.trim()}\\n\\n${readme.slice(endIndex)}`\n}\n\nfunction validateMarker(marker: string): void {\n\tif (!/^[a-z0-9][a-z0-9._-]*$/i.test(marker)) {\n\t\tthrow new Error(\n\t\t\t`Tonnage marker names may contain letters, numbers, dots, underscores, and hyphens.`,\n\t\t)\n\t}\n}\n","import fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport { updateGeneratedSection } from \"./readme.ts\"\nimport { createTonnageReport, renderTonnageMarkdown } from \"./report.ts\"\nimport type { TonnageConfig, TonnageMode, TonnageRunResult } from \"./types.ts\"\n\nexport async function runTonnage(\n\tconfig: TonnageConfig,\n\toptions: {\n\t\tconfigDirectory?: string\n\t\tmode: TonnageMode\n\t},\n): Promise<TonnageRunResult> {\n\tconst configDirectory = path.resolve(options.configDirectory ?? process.cwd())\n\tconst readmePath = path.resolve(configDirectory, config.readme ?? `README.md`)\n\tconst oldReadme = await fs.readFile(readmePath, `utf8`)\n\tconst report = await createTonnageReport(config, configDirectory)\n\tconst markdown = await renderTonnageMarkdown(report, config)\n\tconst newReadme = updateGeneratedSection(oldReadme, markdown, config.marker)\n\tconst changed = oldReadme !== newReadme\n\n\tif (options.mode === `write` && changed) {\n\t\tawait fs.writeFile(readmePath, newReadme)\n\t}\n\n\treturn { changed, readmePath, report }\n}\n"],"mappings":";;;;AAAA,SAAgB,eAAe,SAAS,WAGtC;CACD,eAAe,MAAM;CACrB,OAAO;EACN,KAAK,gBAAgB,OAAO;EAC5B,OAAO,gBAAgB,OAAO;CAC/B;AACD;AAEA,SAAgB,uBACf,QACA,UACA,QACS;CACT,MAAM,EAAE,KAAK,UAAU,eAAe,MAAM;CAC5C,MAAM,aAAa,OAAO,QAAQ,KAAK;CACvC,MAAM,WAAW,OAAO,QAAQ,GAAG;CAEnC,IAAI,eAAe,MAAM,aAAa,IACrC,MAAM,IAAI,MACT,mCAAmC,MAAM,WAAW,IAAI,SACzD;CAED,IACC,OAAO,MAAM,aAAa,MAAM,MAAM,CAAC,CAAC,SAAS,KAAK,KACtD,OAAO,MAAM,WAAW,IAAI,MAAM,CAAC,CAAC,SAAS,GAAG,GAEhD,MAAM,IAAI,MACT,mCAAmC,MAAM,WAAW,IAAI,SACzD;CAED,IAAI,WAAW,YACd,MAAM,IAAI,MAAM,GAAG,IAAI,qBAAqB,MAAM,EAAE;CAGrD,MAAM,eAAe,aAAa,MAAM;CACxC,OAAO,GAAG,OAAO,MAAM,GAAG,YAAY,EAAE,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,MAAM,QAAQ;AAC1F;AAEA,SAAS,eAAe,QAAsB;CAC7C,IAAI,CAAC,0BAA0B,KAAK,MAAM,GACzC,MAAM,IAAI,MACT,oFACD;AAEF;;;ACxCA,eAAsB,WACrB,QACA,SAI4B;CAC5B,MAAM,kBAAkB,KAAK,QAAQ,QAAQ,mBAAmB,QAAQ,IAAI,CAAC;CAC7E,MAAM,aAAa,KAAK,QAAQ,iBAAiB,OAAO,UAAU,WAAW;CAC7E,MAAM,YAAY,MAAM,GAAG,SAAS,YAAY,MAAM;CACtD,MAAM,SAAS,MAAM,oBAAoB,QAAQ,eAAe;CAEhE,MAAM,YAAY,uBAAuB,WAAW,MAD7B,sBAAsB,QAAQ,MAAM,GACG,OAAO,MAAM;CAC3E,MAAM,UAAU,cAAc;CAE9B,IAAI,QAAQ,SAAS,WAAW,SAC/B,MAAM,GAAG,UAAU,YAAY,SAAS;CAGzC,OAAO;EAAE;EAAS;EAAY;CAAO;AACtC"}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
//#region src/types.d.ts
|
|
2
|
-
type BundlePlatform = `browser` | `neutral` | `node`;
|
|
3
|
-
type TonnageExports = {
|
|
4
|
-
exclude?: readonly string[];
|
|
5
|
-
include?: readonly string[];
|
|
6
|
-
};
|
|
7
|
-
type TonnageRecipe = {
|
|
8
|
-
entry: string;
|
|
9
|
-
external?: readonly string[];
|
|
10
|
-
name: string;
|
|
11
|
-
platform?: BundlePlatform;
|
|
12
|
-
};
|
|
13
|
-
type TonnageConfig = {
|
|
14
|
-
exports?: boolean | TonnageExports;
|
|
15
|
-
external?: readonly string[];
|
|
16
|
-
heading?: string;
|
|
17
|
-
marker?: string;
|
|
18
|
-
packageJson?: string;
|
|
19
|
-
platform?: BundlePlatform;
|
|
20
|
-
readme?: string;
|
|
21
|
-
recipes?: readonly TonnageRecipe[];
|
|
22
|
-
target?: string;
|
|
23
|
-
};
|
|
24
|
-
type BundleMeasurement = {
|
|
25
|
-
gzipBytes: number;
|
|
26
|
-
rawBytes: number;
|
|
27
|
-
};
|
|
28
|
-
type TonnageRow = BundleMeasurement & {
|
|
29
|
-
imports: readonly string[];
|
|
30
|
-
name: string;
|
|
31
|
-
};
|
|
32
|
-
type TonnageRecipeRow = BundleMeasurement & {
|
|
33
|
-
entry: string;
|
|
34
|
-
name: string;
|
|
35
|
-
};
|
|
36
|
-
type TonnageReport = {
|
|
37
|
-
exports: readonly TonnageRow[];
|
|
38
|
-
packageName: string;
|
|
39
|
-
recipes: readonly TonnageRecipeRow[];
|
|
40
|
-
};
|
|
41
|
-
type TonnageMode = `check` | `write`;
|
|
42
|
-
type TonnageRunResult = {
|
|
43
|
-
changed: boolean;
|
|
44
|
-
readmePath: string;
|
|
45
|
-
report: TonnageReport;
|
|
46
|
-
};
|
|
47
|
-
//#endregion
|
|
48
|
-
export { TonnageMode as a, TonnageReport as c, TonnageExports as i, TonnageRow as l, BundlePlatform as n, TonnageRecipe as o, TonnageConfig as r, TonnageRecipeRow as s, BundleMeasurement as t, TonnageRunResult as u };
|
|
49
|
-
//# sourceMappingURL=types-Ddtp95Z3.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types-Ddtp95Z3.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";KAAY;KAEA;EACX;EACA;;KAGW;EACX;EACA;EACA;EACA,WAAW;;KAGA;EACX,oBAAoB;EACpB;EACA;EACA;EACA;EACA,WAAW;EACX;EACA,mBAAmB;EACnB;;KAGW;EACX;EACA;;KAGW,aAAa;EACxB;EACA;;KAGW,mBAAmB;EAC9B;EACA;;KAGW;EACX,kBAAkB;EAClB;EACA,kBAAkB;;KAGP;KAEA;EACX;EACA;EACA,QAAQ"}
|
package/src/cli.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs/promises"
|
|
4
|
-
import path from "node:path"
|
|
5
|
-
import { pathToFileURL } from "node:url"
|
|
6
|
-
|
|
7
|
-
import { logWarnings } from "comline"
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
completeTonnageCli,
|
|
11
|
-
parseTonnageCli,
|
|
12
|
-
renderTonnageCliHelp,
|
|
13
|
-
} from "./command-line.ts"
|
|
14
|
-
import { runTonnage } from "./run.ts"
|
|
15
|
-
import type { TonnageConfig } from "./types.ts"
|
|
16
|
-
|
|
17
|
-
const DEFAULT_CONFIG_FILES = [
|
|
18
|
-
`tonnage.config.ts`,
|
|
19
|
-
`tonnage.config.mjs`,
|
|
20
|
-
`tonnage.config.js`,
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
async function main(): Promise<void> {
|
|
24
|
-
const completion = await completeTonnageCli(process.argv)
|
|
25
|
-
if (completion !== undefined) {
|
|
26
|
-
process.stdout.write(completion)
|
|
27
|
-
return
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const invocation = parseTonnageCli(process.argv)
|
|
31
|
-
logWarnings(invocation.warnings)
|
|
32
|
-
if (invocation.kind === `help`) {
|
|
33
|
-
process.stdout.write(renderTonnageCliHelp())
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const { configArgument, mode } = invocation
|
|
38
|
-
const configPath = await findConfig(configArgument)
|
|
39
|
-
const config = await loadConfig(configPath)
|
|
40
|
-
const result = await runTonnage(config, {
|
|
41
|
-
configDirectory: path.dirname(configPath),
|
|
42
|
-
mode,
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
if (mode === `check` && result.changed) {
|
|
46
|
-
process.stderr.write(
|
|
47
|
-
`${path.relative(process.cwd(), result.readmePath)} is out of date. Run tonnage write.\n`,
|
|
48
|
-
)
|
|
49
|
-
process.exitCode = 1
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const readme = path.relative(process.cwd(), result.readmePath)
|
|
54
|
-
process.stdout.write(
|
|
55
|
-
result.changed
|
|
56
|
-
? `Updated ${readme}.\n`
|
|
57
|
-
: `${readme} is already up to date.\n`,
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async function findConfig(configArgument: string | undefined): Promise<string> {
|
|
62
|
-
if (configArgument) {
|
|
63
|
-
return path.resolve(configArgument)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (const filename of DEFAULT_CONFIG_FILES) {
|
|
67
|
-
const candidate = path.resolve(filename)
|
|
68
|
-
try {
|
|
69
|
-
await fs.access(candidate)
|
|
70
|
-
return candidate
|
|
71
|
-
} catch {
|
|
72
|
-
// Keep looking for the next supported config filename.
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
throw new Error(
|
|
77
|
-
`Could not find a tonnage config (${DEFAULT_CONFIG_FILES.join(`, `)}).`,
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async function loadConfig(configPath: string): Promise<TonnageConfig> {
|
|
82
|
-
const imported = (await import(pathToFileURL(configPath).href)) as {
|
|
83
|
-
default?: unknown
|
|
84
|
-
}
|
|
85
|
-
if (!imported.default || typeof imported.default !== `object`) {
|
|
86
|
-
throw new Error(`${configPath} must default-export a tonnage config.`)
|
|
87
|
-
}
|
|
88
|
-
return imported.default
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
main().catch((error: unknown) => {
|
|
92
|
-
process.stderr.write(
|
|
93
|
-
`${error instanceof Error ? error.message : String(error)}\n`,
|
|
94
|
-
)
|
|
95
|
-
process.exitCode = 1
|
|
96
|
-
})
|
package/src/command-line.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type CliWarning,
|
|
3
|
-
cli,
|
|
4
|
-
completionResponse,
|
|
5
|
-
help,
|
|
6
|
-
helpOption,
|
|
7
|
-
optional,
|
|
8
|
-
} from "comline"
|
|
9
|
-
|
|
10
|
-
import type { TonnageMode } from "./types.ts"
|
|
11
|
-
|
|
12
|
-
const ROOT_MANUAL = helpOption(`Show help for the tonnage command line.`)
|
|
13
|
-
const WRITE_MANUAL = helpOption(`Update the configured README report.`)
|
|
14
|
-
const CHECK_MANUAL = helpOption(
|
|
15
|
-
`Check that the configured README report is up to date.`,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
const tonnageCli = cli({
|
|
19
|
-
cliName: `tonnage`,
|
|
20
|
-
cliDescription: `Keep deterministic bundle size reports in README files and CI.`,
|
|
21
|
-
routes: optional({
|
|
22
|
-
check: optional({ $configPath: null }),
|
|
23
|
-
write: optional({ $configPath: null }),
|
|
24
|
-
}),
|
|
25
|
-
routeOptions: {
|
|
26
|
-
"": ROOT_MANUAL,
|
|
27
|
-
check: CHECK_MANUAL,
|
|
28
|
-
"check/$configPath": CHECK_MANUAL,
|
|
29
|
-
write: WRITE_MANUAL,
|
|
30
|
-
"write/$configPath": WRITE_MANUAL,
|
|
31
|
-
},
|
|
32
|
-
positionalCompletions: {
|
|
33
|
-
"check/$configPath": { fileSystem: `files` },
|
|
34
|
-
"write/$configPath": { fileSystem: `files` },
|
|
35
|
-
},
|
|
36
|
-
discoverConfigPath: () => undefined,
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
export type TonnageCliInvocation = { warnings: CliWarning[] } & (
|
|
40
|
-
| { kind: `help` }
|
|
41
|
-
| { configArgument?: string; kind: `run`; mode: TonnageMode }
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
export function completeTonnageCli(
|
|
45
|
-
args: string[],
|
|
46
|
-
): Promise<string | undefined> {
|
|
47
|
-
return completionResponse(tonnageCli.definition, args)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function parseTonnageCli(args: string[]): TonnageCliInvocation {
|
|
51
|
-
const { inputs, warnings } = tonnageCli(args)
|
|
52
|
-
|
|
53
|
-
if (inputs.case === `` || inputs.opts.help) {
|
|
54
|
-
return { kind: `help`, warnings }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const mode =
|
|
58
|
-
inputs.case === `write` || inputs.case === `write/$configPath`
|
|
59
|
-
? `write`
|
|
60
|
-
: `check`
|
|
61
|
-
const configArgument = inputs.path[1]
|
|
62
|
-
|
|
63
|
-
return configArgument === undefined
|
|
64
|
-
? { kind: `run`, mode, warnings }
|
|
65
|
-
: { configArgument, kind: `run`, mode, warnings }
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function renderTonnageCliHelp(): string {
|
|
69
|
-
return help(tonnageCli.definition)
|
|
70
|
-
}
|
package/src/readme.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
export function tonnageMarkers(marker = `default`): {
|
|
2
|
-
end: string
|
|
3
|
-
start: string
|
|
4
|
-
} {
|
|
5
|
-
validateMarker(marker)
|
|
6
|
-
return {
|
|
7
|
-
end: `<!-- tonnage:${marker}:end -->`,
|
|
8
|
-
start: `<!-- tonnage:${marker}:start -->`,
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function updateGeneratedSection(
|
|
13
|
-
readme: string,
|
|
14
|
-
markdown: string,
|
|
15
|
-
marker?: string,
|
|
16
|
-
): string {
|
|
17
|
-
const { end, start } = tonnageMarkers(marker)
|
|
18
|
-
const startIndex = readme.indexOf(start)
|
|
19
|
-
const endIndex = readme.indexOf(end)
|
|
20
|
-
|
|
21
|
-
if (startIndex === -1 || endIndex === -1) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
`README must contain exactly one ${start} and one ${end} marker.`,
|
|
24
|
-
)
|
|
25
|
-
}
|
|
26
|
-
if (
|
|
27
|
-
readme.slice(startIndex + start.length).includes(start) ||
|
|
28
|
-
readme.slice(endIndex + end.length).includes(end)
|
|
29
|
-
) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
`README must contain exactly one ${start} and one ${end} marker.`,
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
if (endIndex < startIndex) {
|
|
35
|
-
throw new Error(`${end} must appear after ${start}.`)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const contentStart = startIndex + start.length
|
|
39
|
-
return `${readme.slice(0, contentStart)}\n\n${markdown.trim()}\n\n${readme.slice(endIndex)}`
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function validateMarker(marker: string): void {
|
|
43
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(marker)) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
`Tonnage marker names may contain letters, numbers, dots, underscores, and hyphens.`,
|
|
46
|
-
)
|
|
47
|
-
}
|
|
48
|
-
}
|
package/src/run.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises"
|
|
2
|
-
import path from "node:path"
|
|
3
|
-
|
|
4
|
-
import { updateGeneratedSection } from "./readme.ts"
|
|
5
|
-
import { createTonnageReport, renderTonnageMarkdown } from "./report.ts"
|
|
6
|
-
import type { TonnageConfig, TonnageMode, TonnageRunResult } from "./types.ts"
|
|
7
|
-
|
|
8
|
-
export async function runTonnage(
|
|
9
|
-
config: TonnageConfig,
|
|
10
|
-
options: {
|
|
11
|
-
configDirectory?: string
|
|
12
|
-
mode: TonnageMode
|
|
13
|
-
},
|
|
14
|
-
): Promise<TonnageRunResult> {
|
|
15
|
-
const configDirectory = path.resolve(options.configDirectory ?? process.cwd())
|
|
16
|
-
const readmePath = path.resolve(configDirectory, config.readme ?? `README.md`)
|
|
17
|
-
const oldReadme = await fs.readFile(readmePath, `utf8`)
|
|
18
|
-
const report = await createTonnageReport(config, configDirectory)
|
|
19
|
-
const markdown = await renderTonnageMarkdown(report, config)
|
|
20
|
-
const newReadme = updateGeneratedSection(oldReadme, markdown, config.marker)
|
|
21
|
-
const changed = oldReadme !== newReadme
|
|
22
|
-
|
|
23
|
-
if (options.mode === `write` && changed) {
|
|
24
|
-
await fs.writeFile(readmePath, newReadme)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return { changed, readmePath, report }
|
|
28
|
-
}
|