@savvy-web/bundler 0.5.0 → 0.6.1

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/config.js CHANGED
@@ -26,14 +26,17 @@ function defineBuild(input = {}) {
26
26
  function parseArgs(argv) {
27
27
  let target = "dev";
28
28
  let watch = false;
29
+ let noExe = false;
29
30
  for (let i = 0; i < argv.length; i++) if (argv[i] === "--target") {
30
31
  const v = argv[i + 1];
31
32
  if (v === "dev" || v === "prod" || v === "meta" || v === "exe") target = v;
32
33
  i++;
33
34
  } else if (argv[i] === "--watch") watch = true;
35
+ else if (argv[i] === "--no-exe") noExe = true;
34
36
  return {
35
37
  target,
36
- watch
38
+ watch,
39
+ noExe
37
40
  };
38
41
  }
39
42
 
package/index.d.ts CHANGED
@@ -173,6 +173,8 @@ declare function defineBuild(input?: BuildConfigInput): BuildConfig;
173
173
  interface ParsedArgs {
174
174
  readonly target: "dev" | "prod" | "meta" | "exe";
175
175
  readonly watch: boolean;
176
+ /** Skip the SEA compile step of a dev/prod build (the manifest is still programmed). Used by `prepare`. */
177
+ readonly noExe: boolean;
176
178
  }
177
179
  declare function parseArgs(argv: ReadonlyArray<string>): ParsedArgs;
178
180
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/bundler",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "private": false,
5
5
  "description": "Zero-config tsdown-based bundler for Silk Suite TypeScript packages",
6
6
  "homepage": "https://github.com/savvy-web/systems/tree/main/packages/bundler",
@@ -29,9 +29,9 @@
29
29
  "./package.json": "./package.json"
30
30
  },
31
31
  "dependencies": {
32
- "@savvy-web/tsdown-plugins": "0.5.0",
32
+ "@savvy-web/tsdown-plugins": "0.6.0",
33
33
  "@tsdown/exe": "^0.22.1",
34
- "tsdown": "^0.22.2"
34
+ "tsdown": "^0.22.3"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "effect": ">=3.21.0"
package/run.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { parseArgs } from "./config.js";
2
- import { ConfigValidator, ConfigValidatorLive, ReportPipelineLive, buildTargetGroups, createEntryName, generateMeta, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveTargets, runExeBuild, writeResolvedTsconfig, writeTargetsBinding } from "@savvy-web/tsdown-plugins";
3
- import { readFileSync } from "node:fs";
2
+ import { ConfigValidator, ConfigValidatorLive, ReportPipelineLive, buildEmittedManifest, buildTargetGroups, computeExeFileName, createEntryName, generateMeta, normalizeExeOptions, normalizeLooseFiles, normalizeMetaOptions, packageJsonEntries, readTsconfigJsx, removeDeclarationMaps, renderReport, resolveJsxConfig, resolveTargets, runExeBuild, writeResolvedTsconfig, writeTargetsBinding } from "@savvy-web/tsdown-plugins";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { Effect } from "effect";
6
6
 
@@ -74,7 +74,7 @@ function applySubdirMetaEntries(overrides, dtsBasenames, exportPaths) {
74
74
  }
75
75
  /** Run a build from a normalized config. Pure orchestration; all IO injectable. */
76
76
  async function runBuild(config, options) {
77
- const { target } = parseArgs(options.argv);
77
+ const { target, noExe } = parseArgs(options.argv);
78
78
  const build = options.buildTargetGroups ?? buildTargetGroups;
79
79
  const cwd = options.cwd;
80
80
  const pkg = readPackageJson(cwd);
@@ -89,10 +89,6 @@ async function runBuild(config, options) {
89
89
  } : {},
90
90
  ...jsx?.runtime === "classic" ? { jsx: "react" } : {}
91
91
  })))(cwd);
92
- const entries = packageJsonEntries({
93
- pkg,
94
- cwd
95
- });
96
92
  const exportsMap = options.readExports ? options.readExports() : pkg.exports;
97
93
  const runGenerateMeta = options.generateMeta ?? generateMeta;
98
94
  const publishTargets = (options.readPublishTargets ?? (() => {
@@ -113,6 +109,23 @@ async function runBuild(config, options) {
113
109
  ...config.meta !== void 0 && config.meta !== false ? { meta: config.meta } : {},
114
110
  ...config.looseFiles !== void 0 ? { looseFiles: config.looseFiles } : {}
115
111
  })).pipe(Effect.provide(ConfigValidatorLive)));
112
+ const exeSpecs = config.exe !== void 0 ? normalizeExeOptions(config.exe, osCpuForValidate) : [];
113
+ if (config.exe !== void 0 && (exeSpecs.length !== 1 || (exeSpecs[0]?.targets.length ?? 0) !== 1)) throw new Error(`exe build requires exactly one binary with one target (got ${exeSpecs.length} spec(s), ${exeSpecs[0]?.targets.length ?? 0} target(s) on the first). A package's exports["."] resolves to a single SEA — cross-platform binaries must each ship as their own per-platform package.`);
114
+ const exeSpec = exeSpecs[0];
115
+ const exeTarget = exeSpec?.targets[0];
116
+ const exeFileName = exeSpec && exeTarget ? computeExeFileName(exeSpec.fileName, exeTarget) : void 0;
117
+ const exeEntrySource = exeSpec?.entry ?? "./src/bin.ts";
118
+ const exeRewrite = config.exe !== void 0 && exeFileName !== void 0 ? {
119
+ source: exeEntrySource,
120
+ fileName: exeFileName,
121
+ dir: "bin"
122
+ } : void 0;
123
+ const entries = packageJsonEntries({
124
+ pkg,
125
+ cwd,
126
+ ...config.exe !== void 0 ? { excludeSources: [exeEntrySource] } : {}
127
+ });
128
+ const hasJsEntries = Object.keys(entries).length > 0;
116
129
  validateSubdirOverrides(config.overrides, entries, packageName);
117
130
  if (target === "meta") {
118
131
  if (config.meta === false) {
@@ -148,16 +161,15 @@ async function runBuild(config, options) {
148
161
  }
149
162
  if (target === "exe") {
150
163
  if (config.exe === void 0) throw new Error("`savvy build --target exe` requires an `exe` option in the build config");
151
- const specs = normalizeExeOptions(config.exe, osCpuForValidate);
152
164
  await (options.runExeBuild ?? runExeBuild)({
153
165
  cwd,
154
166
  outDir: join(cwd, "dist", "dev", "pkg", "bin"),
155
- specs
167
+ specs: exeSpecs
156
168
  });
157
169
  (options.writeOutput ?? ((o) => process.stdout.write(`${o.content}\n`)))({
158
170
  target: "stdout",
159
171
  contentType: "text/plain",
160
- content: `exe: compiled ${specs.length} binary/binaries for ${packageName}`
172
+ content: `exe: compiled ${exeSpecs.length} binary/binaries for ${packageName}`
161
173
  });
162
174
  return;
163
175
  }
@@ -217,7 +229,7 @@ async function runBuild(config, options) {
217
229
  }],
218
230
  resolution: void 0
219
231
  } : deriveProdGroups(publishTargets, packageName);
220
- await build({
232
+ if (hasJsEntries || config.exe === void 0) await build({
221
233
  cwd,
222
234
  version,
223
235
  entry: config.overrides !== void 0 ? baseEntries : entries,
@@ -237,10 +249,11 @@ async function runBuild(config, options) {
237
249
  ...overridePartitions.length > 0 ? { overrides: overridePartitions } : {},
238
250
  ...dualExports !== void 0 ? { dualExports } : {},
239
251
  ...subdirExports.size > 0 ? { subdirExports } : {},
240
- ...looseFiles !== void 0 ? { looseFiles } : {}
252
+ ...looseFiles !== void 0 ? { looseFiles } : {},
253
+ ...exeRewrite !== void 0 ? { exeRewrite } : {}
241
254
  });
242
255
  if (target === "prod" && resolution !== void 0) writeBinding(cwd, resolution);
243
- if (target === "prod" && config.meta !== false) {
256
+ if (target === "prod" && config.meta !== false && (config.exe === void 0 || hasJsEntries)) {
244
257
  const metaGroupId = (groups.find((g) => g.name === packageName) ?? groups[0])?.id ?? "npm";
245
258
  const norm = normalizeMetaOptions(config.meta ?? {});
246
259
  const dtsBasenames = {};
@@ -260,6 +273,40 @@ async function runBuild(config, options) {
260
273
  });
261
274
  }
262
275
  if (target === "prod") for (const g of groups) removeDeclarationMaps(join(cwd, "dist", "prod", g.id, "pkg"));
276
+ if (config.exe !== void 0 && exeSpec !== void 0 && exeRewrite !== void 0 && exeFileName !== void 0) {
277
+ const runExe = options.runExeBuild ?? runExeBuild;
278
+ const groupOutDir = (g) => target === "dev" ? join(cwd, "dist", "dev", "pkg") : join(cwd, "dist", "prod", g.id, "pkg");
279
+ for (const g of groups) {
280
+ const outDir = groupOutDir(g);
281
+ if (!hasJsEntries) {
282
+ const manifest = await buildEmittedManifest({
283
+ pkg,
284
+ targetGroup: {
285
+ id: g.id,
286
+ name: g.name,
287
+ isProd: target === "prod"
288
+ },
289
+ devManifest: config.devManifest,
290
+ ...config.transform !== void 0 ? { transform: config.transform } : {},
291
+ exeRewrite
292
+ });
293
+ mkdirSync(outDir, { recursive: true });
294
+ writeFileSync(join(outDir, "package.json"), `${JSON.stringify(manifest, null, " ")}\n`);
295
+ for (const file of ["LICENSE", "README.md"]) try {
296
+ copyFileSync(join(cwd, file), join(outDir, file));
297
+ } catch {}
298
+ }
299
+ if (!noExe) {
300
+ const binDir = join(outDir, "bin");
301
+ await runExe({
302
+ cwd,
303
+ outDir: binDir,
304
+ specs: exeSpecs
305
+ });
306
+ if (options.runExeBuild === void 0 && !existsSync(join(binDir, exeFileName))) throw new Error(`exe: expected compiled binary at ${join(binDir, exeFileName)} but it is missing — tsdown's emitted name may have drifted from computeExeFileName`);
307
+ }
308
+ }
309
+ }
263
310
  const totalMs = Date.now() - startMs;
264
311
  const reportEntries = Object.keys(entries);
265
312
  const report = {