plgg-bundle 0.0.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.
Files changed (235) hide show
  1. package/README.md +68 -0
  2. package/bin/appAliasHook.mjs +89 -0
  3. package/bin/hook.mjs +94 -0
  4. package/bin/plgg-bundle.mjs +16 -0
  5. package/dist/Dev/fixtures/devPoc.spec.d.ts +2 -0
  6. package/dist/Dev/fixtures/devPoc.spec.d.ts.map +1 -0
  7. package/dist/Dev/fixtures/devPoc.spec.js +145 -0
  8. package/dist/Dev/fixtures/devPoc.spec.js.map +1 -0
  9. package/dist/Dev/model/Fetch.d.ts +20 -0
  10. package/dist/Dev/model/Fetch.d.ts.map +1 -0
  11. package/dist/Dev/model/Fetch.js +8 -0
  12. package/dist/Dev/model/Fetch.js.map +1 -0
  13. package/dist/Dev/model/ModuleGraph.d.ts +27 -0
  14. package/dist/Dev/model/ModuleGraph.d.ts.map +1 -0
  15. package/dist/Dev/model/ModuleGraph.js +2 -0
  16. package/dist/Dev/model/ModuleGraph.js.map +1 -0
  17. package/dist/Dev/model/Protocol.d.ts +22 -0
  18. package/dist/Dev/model/Protocol.d.ts.map +1 -0
  19. package/dist/Dev/model/Protocol.js +31 -0
  20. package/dist/Dev/model/Protocol.js.map +1 -0
  21. package/dist/Dev/node/devServer.d.ts +21 -0
  22. package/dist/Dev/node/devServer.d.ts.map +1 -0
  23. package/dist/Dev/node/devServer.js +298 -0
  24. package/dist/Dev/node/devServer.js.map +1 -0
  25. package/dist/Dev/node/httpAdapter.d.ts +19 -0
  26. package/dist/Dev/node/httpAdapter.d.ts.map +1 -0
  27. package/dist/Dev/node/httpAdapter.js +76 -0
  28. package/dist/Dev/node/httpAdapter.js.map +1 -0
  29. package/dist/Dev/node/scanGraph.d.ts +21 -0
  30. package/dist/Dev/node/scanGraph.d.ts.map +1 -0
  31. package/dist/Dev/node/scanGraph.js +145 -0
  32. package/dist/Dev/node/scanGraph.js.map +1 -0
  33. package/dist/Dev/node/watch.d.ts +10 -0
  34. package/dist/Dev/node/watch.d.ts.map +1 -0
  35. package/dist/Dev/node/watch.js +44 -0
  36. package/dist/Dev/node/watch.js.map +1 -0
  37. package/dist/Dev/usecase/allowedHost.d.ts +16 -0
  38. package/dist/Dev/usecase/allowedHost.d.ts.map +1 -0
  39. package/dist/Dev/usecase/allowedHost.js +33 -0
  40. package/dist/Dev/usecase/allowedHost.js.map +1 -0
  41. package/dist/Dev/usecase/allowedHost.spec.d.ts +2 -0
  42. package/dist/Dev/usecase/allowedHost.spec.d.ts.map +1 -0
  43. package/dist/Dev/usecase/allowedHost.spec.js +13 -0
  44. package/dist/Dev/usecase/allowedHost.spec.js.map +1 -0
  45. package/dist/Dev/usecase/buildGraph.d.ts +12 -0
  46. package/dist/Dev/usecase/buildGraph.d.ts.map +1 -0
  47. package/dist/Dev/usecase/buildGraph.js +24 -0
  48. package/dist/Dev/usecase/buildGraph.js.map +1 -0
  49. package/dist/Dev/usecase/buildGraph.spec.d.ts +2 -0
  50. package/dist/Dev/usecase/buildGraph.spec.d.ts.map +1 -0
  51. package/dist/Dev/usecase/buildGraph.spec.js +16 -0
  52. package/dist/Dev/usecase/buildGraph.spec.js.map +1 -0
  53. package/dist/Dev/usecase/decorateDevHtml.d.ts +12 -0
  54. package/dist/Dev/usecase/decorateDevHtml.d.ts.map +1 -0
  55. package/dist/Dev/usecase/decorateDevHtml.js +15 -0
  56. package/dist/Dev/usecase/decorateDevHtml.js.map +1 -0
  57. package/dist/Dev/usecase/decorateDevHtml.spec.d.ts +2 -0
  58. package/dist/Dev/usecase/decorateDevHtml.spec.d.ts.map +1 -0
  59. package/dist/Dev/usecase/decorateDevHtml.spec.js +19 -0
  60. package/dist/Dev/usecase/decorateDevHtml.spec.js.map +1 -0
  61. package/dist/Dev/usecase/invalidate.d.ts +17 -0
  62. package/dist/Dev/usecase/invalidate.d.ts.map +1 -0
  63. package/dist/Dev/usecase/invalidate.js +57 -0
  64. package/dist/Dev/usecase/invalidate.js.map +1 -0
  65. package/dist/Dev/usecase/invalidate.spec.d.ts +2 -0
  66. package/dist/Dev/usecase/invalidate.spec.d.ts.map +1 -0
  67. package/dist/Dev/usecase/invalidate.spec.js +64 -0
  68. package/dist/Dev/usecase/invalidate.spec.js.map +1 -0
  69. package/dist/Dev/usecase/parseImports.d.ts +24 -0
  70. package/dist/Dev/usecase/parseImports.d.ts.map +1 -0
  71. package/dist/Dev/usecase/parseImports.js +40 -0
  72. package/dist/Dev/usecase/parseImports.js.map +1 -0
  73. package/dist/Dev/usecase/parseImports.spec.d.ts +2 -0
  74. package/dist/Dev/usecase/parseImports.spec.d.ts.map +1 -0
  75. package/dist/Dev/usecase/parseImports.spec.js +24 -0
  76. package/dist/Dev/usecase/parseImports.spec.js.map +1 -0
  77. package/dist/Dev/usecase/reloadDecision.d.ts +20 -0
  78. package/dist/Dev/usecase/reloadDecision.d.ts.map +1 -0
  79. package/dist/Dev/usecase/reloadDecision.js +52 -0
  80. package/dist/Dev/usecase/reloadDecision.js.map +1 -0
  81. package/dist/Dev/usecase/reloadDecision.spec.d.ts +2 -0
  82. package/dist/Dev/usecase/reloadDecision.spec.d.ts.map +1 -0
  83. package/dist/Dev/usecase/reloadDecision.spec.js +17 -0
  84. package/dist/Dev/usecase/reloadDecision.spec.js.map +1 -0
  85. package/dist/domain/model/BundleConfig.d.ts +136 -0
  86. package/dist/domain/model/BundleConfig.d.ts.map +1 -0
  87. package/dist/domain/model/BundleConfig.js +18 -0
  88. package/dist/domain/model/BundleConfig.js.map +1 -0
  89. package/dist/domain/model/BundleConfig.spec.d.ts +2 -0
  90. package/dist/domain/model/BundleConfig.spec.d.ts.map +1 -0
  91. package/dist/domain/model/BundleConfig.spec.js +8 -0
  92. package/dist/domain/model/BundleConfig.spec.js.map +1 -0
  93. package/dist/domain/usecase/asBundleConfig.d.ts +17 -0
  94. package/dist/domain/usecase/asBundleConfig.d.ts.map +1 -0
  95. package/dist/domain/usecase/asBundleConfig.js +179 -0
  96. package/dist/domain/usecase/asBundleConfig.js.map +1 -0
  97. package/dist/domain/usecase/asBundleConfig.spec.d.ts +2 -0
  98. package/dist/domain/usecase/asBundleConfig.spec.d.ts.map +1 -0
  99. package/dist/domain/usecase/asBundleConfig.spec.js +119 -0
  100. package/dist/domain/usecase/asBundleConfig.spec.js.map +1 -0
  101. package/dist/domain/usecase/build.d.ts +14 -0
  102. package/dist/domain/usecase/build.d.ts.map +1 -0
  103. package/dist/domain/usecase/build.js +177 -0
  104. package/dist/domain/usecase/build.js.map +1 -0
  105. package/dist/domain/usecase/collectModules.d.ts +53 -0
  106. package/dist/domain/usecase/collectModules.d.ts.map +1 -0
  107. package/dist/domain/usecase/collectModules.js +118 -0
  108. package/dist/domain/usecase/collectModules.js.map +1 -0
  109. package/dist/domain/usecase/deriveExternal.d.ts +25 -0
  110. package/dist/domain/usecase/deriveExternal.d.ts.map +1 -0
  111. package/dist/domain/usecase/deriveExternal.js +79 -0
  112. package/dist/domain/usecase/deriveExternal.js.map +1 -0
  113. package/dist/domain/usecase/deriveExternal.spec.d.ts +2 -0
  114. package/dist/domain/usecase/deriveExternal.spec.d.ts.map +1 -0
  115. package/dist/domain/usecase/deriveExternal.spec.js +56 -0
  116. package/dist/domain/usecase/deriveExternal.spec.js.map +1 -0
  117. package/dist/domain/usecase/discoverWorkspace.d.ts +27 -0
  118. package/dist/domain/usecase/discoverWorkspace.d.ts.map +1 -0
  119. package/dist/domain/usecase/discoverWorkspace.js +116 -0
  120. package/dist/domain/usecase/discoverWorkspace.js.map +1 -0
  121. package/dist/domain/usecase/discoverWorkspace.spec.d.ts +2 -0
  122. package/dist/domain/usecase/discoverWorkspace.spec.d.ts.map +1 -0
  123. package/dist/domain/usecase/discoverWorkspace.spec.js +23 -0
  124. package/dist/domain/usecase/discoverWorkspace.spec.js.map +1 -0
  125. package/dist/domain/usecase/emitBundle.d.ts +43 -0
  126. package/dist/domain/usecase/emitBundle.d.ts.map +1 -0
  127. package/dist/domain/usecase/emitBundle.js +157 -0
  128. package/dist/domain/usecase/emitBundle.js.map +1 -0
  129. package/dist/domain/usecase/emitBundle.spec.d.ts +2 -0
  130. package/dist/domain/usecase/emitBundle.spec.d.ts.map +1 -0
  131. package/dist/domain/usecase/emitBundle.spec.js +80 -0
  132. package/dist/domain/usecase/emitBundle.spec.js.map +1 -0
  133. package/dist/domain/usecase/emitDts.d.ts +32 -0
  134. package/dist/domain/usecase/emitDts.d.ts.map +1 -0
  135. package/dist/domain/usecase/emitDts.js +99 -0
  136. package/dist/domain/usecase/emitDts.js.map +1 -0
  137. package/dist/domain/usecase/isExternal.d.ts +12 -0
  138. package/dist/domain/usecase/isExternal.d.ts.map +1 -0
  139. package/dist/domain/usecase/isExternal.js +16 -0
  140. package/dist/domain/usecase/isExternal.js.map +1 -0
  141. package/dist/domain/usecase/isExternal.spec.d.ts +2 -0
  142. package/dist/domain/usecase/isExternal.spec.d.ts.map +1 -0
  143. package/dist/domain/usecase/isExternal.spec.js +16 -0
  144. package/dist/domain/usecase/isExternal.spec.js.map +1 -0
  145. package/dist/domain/usecase/resolveSpecifier.d.ts +35 -0
  146. package/dist/domain/usecase/resolveSpecifier.d.ts.map +1 -0
  147. package/dist/domain/usecase/resolveSpecifier.js +83 -0
  148. package/dist/domain/usecase/resolveSpecifier.js.map +1 -0
  149. package/dist/domain/usecase/resolveSpecifier.spec.d.ts +2 -0
  150. package/dist/domain/usecase/resolveSpecifier.spec.d.ts.map +1 -0
  151. package/dist/domain/usecase/resolveSpecifier.spec.js +44 -0
  152. package/dist/domain/usecase/resolveSpecifier.spec.js.map +1 -0
  153. package/dist/domain/usecase/resolveWorkspaceSpecifier.d.ts +29 -0
  154. package/dist/domain/usecase/resolveWorkspaceSpecifier.d.ts.map +1 -0
  155. package/dist/domain/usecase/resolveWorkspaceSpecifier.js +89 -0
  156. package/dist/domain/usecase/resolveWorkspaceSpecifier.js.map +1 -0
  157. package/dist/domain/usecase/resolveWorkspaceSpecifier.spec.d.ts +2 -0
  158. package/dist/domain/usecase/resolveWorkspaceSpecifier.spec.d.ts.map +1 -0
  159. package/dist/domain/usecase/resolveWorkspaceSpecifier.spec.js +27 -0
  160. package/dist/domain/usecase/resolveWorkspaceSpecifier.spec.js.map +1 -0
  161. package/dist/domain/usecase/rewriteDtsAliases.d.ts +25 -0
  162. package/dist/domain/usecase/rewriteDtsAliases.d.ts.map +1 -0
  163. package/dist/domain/usecase/rewriteDtsAliases.js +71 -0
  164. package/dist/domain/usecase/rewriteDtsAliases.js.map +1 -0
  165. package/dist/domain/usecase/rewriteDtsAliases.spec.d.ts +2 -0
  166. package/dist/domain/usecase/rewriteDtsAliases.spec.d.ts.map +1 -0
  167. package/dist/domain/usecase/rewriteDtsAliases.spec.js +35 -0
  168. package/dist/domain/usecase/rewriteDtsAliases.spec.js.map +1 -0
  169. package/dist/entrypoints/cli.d.ts +2 -0
  170. package/dist/entrypoints/cli.d.ts.map +1 -0
  171. package/dist/entrypoints/cli.js +79 -0
  172. package/dist/entrypoints/cli.js.map +1 -0
  173. package/dist/index.d.ts +21 -0
  174. package/dist/index.d.ts.map +1 -0
  175. package/dist/index.js +28 -0
  176. package/dist/index.js.map +1 -0
  177. package/dist/vendors/runner.d.ts +20 -0
  178. package/dist/vendors/runner.d.ts.map +1 -0
  179. package/dist/vendors/runner.js +60 -0
  180. package/dist/vendors/runner.js.map +1 -0
  181. package/dist/vendors/runner.spec.d.ts +2 -0
  182. package/dist/vendors/runner.spec.d.ts.map +1 -0
  183. package/dist/vendors/runner.spec.js +20 -0
  184. package/dist/vendors/runner.spec.js.map +1 -0
  185. package/dist/vendors/transpiler.d.ts +19 -0
  186. package/dist/vendors/transpiler.d.ts.map +1 -0
  187. package/dist/vendors/transpiler.js +42 -0
  188. package/dist/vendors/transpiler.js.map +1 -0
  189. package/package.json +31 -0
  190. package/src/Dev/fixtures/devPoc.spec.ts +235 -0
  191. package/src/Dev/model/Fetch.ts +31 -0
  192. package/src/Dev/model/ModuleGraph.ts +30 -0
  193. package/src/Dev/model/Protocol.ts +35 -0
  194. package/src/Dev/node/devServer.ts +503 -0
  195. package/src/Dev/node/httpAdapter.ts +162 -0
  196. package/src/Dev/node/scanGraph.ts +203 -0
  197. package/src/Dev/node/watch.ts +54 -0
  198. package/src/Dev/usecase/allowedHost.spec.ts +50 -0
  199. package/src/Dev/usecase/allowedHost.ts +39 -0
  200. package/src/Dev/usecase/buildGraph.spec.ts +30 -0
  201. package/src/Dev/usecase/buildGraph.ts +31 -0
  202. package/src/Dev/usecase/decorateDevHtml.spec.ts +39 -0
  203. package/src/Dev/usecase/decorateDevHtml.ts +21 -0
  204. package/src/Dev/usecase/invalidate.spec.ts +88 -0
  205. package/src/Dev/usecase/invalidate.ts +67 -0
  206. package/src/Dev/usecase/parseImports.spec.ts +58 -0
  207. package/src/Dev/usecase/parseImports.ts +50 -0
  208. package/src/Dev/usecase/reloadDecision.spec.ts +50 -0
  209. package/src/Dev/usecase/reloadDecision.ts +65 -0
  210. package/src/domain/model/BundleConfig.spec.ts +35 -0
  211. package/src/domain/model/BundleConfig.ts +162 -0
  212. package/src/domain/usecase/asBundleConfig.spec.ts +230 -0
  213. package/src/domain/usecase/asBundleConfig.ts +257 -0
  214. package/src/domain/usecase/build.ts +259 -0
  215. package/src/domain/usecase/collectModules.ts +226 -0
  216. package/src/domain/usecase/deriveExternal.spec.ts +125 -0
  217. package/src/domain/usecase/deriveExternal.ts +108 -0
  218. package/src/domain/usecase/discoverWorkspace.spec.ts +63 -0
  219. package/src/domain/usecase/discoverWorkspace.ts +183 -0
  220. package/src/domain/usecase/emitBundle.spec.ts +162 -0
  221. package/src/domain/usecase/emitBundle.ts +215 -0
  222. package/src/domain/usecase/emitDts.ts +156 -0
  223. package/src/domain/usecase/isExternal.spec.ts +53 -0
  224. package/src/domain/usecase/isExternal.ts +20 -0
  225. package/src/domain/usecase/resolveSpecifier.spec.ts +112 -0
  226. package/src/domain/usecase/resolveSpecifier.ts +127 -0
  227. package/src/domain/usecase/resolveWorkspaceSpecifier.spec.ts +79 -0
  228. package/src/domain/usecase/resolveWorkspaceSpecifier.ts +120 -0
  229. package/src/domain/usecase/rewriteDtsAliases.spec.ts +123 -0
  230. package/src/domain/usecase/rewriteDtsAliases.ts +133 -0
  231. package/src/entrypoints/cli.ts +105 -0
  232. package/src/index.ts +28 -0
  233. package/src/vendors/runner.spec.ts +50 -0
  234. package/src/vendors/runner.ts +78 -0
  235. package/src/vendors/transpiler.ts +48 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Extract the import specifiers from a TypeScript/ESM
3
+ * source string. Pure string analysis — no fs, no
4
+ * `typescript` AST — matching the three import forms the
5
+ * monorepo actually uses:
6
+ *
7
+ * - static `import ... from "spec"`,
8
+ * - re-export `export ... from "spec"`,
9
+ * - dynamic `import("spec")`.
10
+ *
11
+ * The node adapter feeds each watched file's contents
12
+ * through this to build the local module graph. Returns
13
+ * specifiers in source order (duplicates preserved);
14
+ * resolution + local-only filtering is the adapter's job.
15
+ *
16
+ * This is intentionally a lightweight lexical scan, not a
17
+ * full parser: the specifiers it yields drive only the
18
+ * dev server's *reload decision* (which files are part of
19
+ * the served graph), never the production build, so an
20
+ * exotic un-matched form costs at most one conservative
21
+ * extra reload — never a wrong bundle.
22
+ */
23
+ export const parseImports = (
24
+ source: string,
25
+ ): ReadonlyArray<string> =>
26
+ // Each pattern's first capture group is the quoted
27
+ // specifier's inner text. `Array.from(..., mapFn)` +
28
+ // a type-guard `filter` extracts it without an
29
+ // `if`/`??` branch on the (unreachable) `undefined` a
30
+ // capture group would only take if it were optional.
31
+ PATTERNS.flatMap((pattern) =>
32
+ Array.from(
33
+ source.matchAll(pattern),
34
+ (m) => m[1],
35
+ ).filter(
36
+ (spec): spec is string =>
37
+ spec !== undefined,
38
+ ),
39
+ );
40
+
41
+ /**
42
+ * The specifier-bearing forms. A `from "…"` clause covers
43
+ * both `import` and `export`; a bare `import("…")` covers
44
+ * dynamic import. `["']` matches either quote style; the
45
+ * inner `[^"']+` is the specifier.
46
+ */
47
+ const PATTERNS: ReadonlyArray<RegExp> = [
48
+ /\bfrom\s*["']([^"']+)["']/g,
49
+ /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g,
50
+ ];
@@ -0,0 +1,50 @@
1
+ import {
2
+ test,
3
+ check,
4
+ all,
5
+ toBe,
6
+ } from "plgg-test";
7
+ import { buildGraph } from "plgg-bundle/Dev/usecase/buildGraph";
8
+ import { shouldReload } from "plgg-bundle/Dev/usecase/reloadDecision";
9
+
10
+ const graph = buildGraph([
11
+ { from: "/a.ts", to: "/b.ts" },
12
+ ]);
13
+
14
+ test("shouldReload is true for a file that imports (graph key)", () =>
15
+ check(
16
+ shouldReload(graph, "/a.ts"),
17
+ toBe(true),
18
+ ));
19
+
20
+ test("shouldReload is true for an imported file (graph value)", () =>
21
+ check(
22
+ shouldReload(graph, "/b.ts"),
23
+ toBe(true),
24
+ ));
25
+
26
+ test("shouldReload is false for a source file outside the served graph", () =>
27
+ check(
28
+ shouldReload(graph, "/unrelated.ts"),
29
+ toBe(false),
30
+ ));
31
+
32
+ test("shouldReload is true for a content (non-code) file edit", () =>
33
+ all([
34
+ check(
35
+ shouldReload(graph, "/docs/guide.md"),
36
+ toBe(true),
37
+ ),
38
+ check(
39
+ shouldReload(graph, "/assets/x.css"),
40
+ toBe(true),
41
+ ),
42
+ ]));
43
+
44
+ test("shouldReload falls back to true for an empty graph", () =>
45
+ all([
46
+ check(
47
+ shouldReload(buildGraph([]), "/x.ts"),
48
+ toBe(true),
49
+ ),
50
+ ]));
@@ -0,0 +1,65 @@
1
+ import { type ModuleGraph } from "plgg-bundle/Dev/model/ModuleGraph";
2
+
3
+ /**
4
+ * Source-code extensions — the files the module graph
5
+ * tracks. A changed file with any OTHER extension is
6
+ * content (Markdown, an asset) whose edit the app re-reads
7
+ * at render time, so it always warrants a reload.
8
+ */
9
+ const SOURCE_EXT: ReadonlyArray<string> = [
10
+ ".ts",
11
+ ".tsx",
12
+ ".mts",
13
+ ".js",
14
+ ".mjs",
15
+ ];
16
+
17
+ /**
18
+ * Whether a changed file warrants a hot-reload:
19
+ *
20
+ * - content (non-source-code, e.g. `.md`) → always, since
21
+ * the render re-reads it;
22
+ * - source code that participates in the app's served
23
+ * {@link ModuleGraph} (a graph key it imports from, or a
24
+ * graph value something imports) → yes;
25
+ * - source code the app never imports (a stray scratch
26
+ * `.ts`) → ignored, so the browser only reloads on edits
27
+ * that can change the output.
28
+ *
29
+ * Conservative fallback: an EMPTY graph means the scan
30
+ * could not map the app, so every change reloads rather
31
+ * than silently going stale. Pure decision over the graph —
32
+ * the adapter owns the fs.
33
+ */
34
+ export const shouldReload = (
35
+ graph: ModuleGraph,
36
+ changed: string,
37
+ ): boolean =>
38
+ !isSourceCode(changed) ||
39
+ graph.size === 0 ||
40
+ graph.has(changed) ||
41
+ importsSomewhere(graph, changed);
42
+
43
+ /** Whether a path is a tracked source-code file (by extension). */
44
+ const isSourceCode = (
45
+ changed: string,
46
+ ): boolean =>
47
+ SOURCE_EXT.some((e) => changed.endsWith(e));
48
+
49
+ /**
50
+ * Whether any node in the graph imports `changed` (i.e. it
51
+ * appears as an edge target). Local scan over the forward
52
+ * edges — the reverse lookup is only needed once per
53
+ * decision, so it is not memoised.
54
+ */
55
+ const importsSomewhere = (
56
+ graph: ModuleGraph,
57
+ changed: string,
58
+ ): boolean => {
59
+ for (const tos of graph.values()) {
60
+ if (tos.includes(changed)) {
61
+ return true;
62
+ }
63
+ }
64
+ return false;
65
+ };
@@ -0,0 +1,35 @@
1
+ import {
2
+ test,
3
+ check,
4
+ all,
5
+ toBe,
6
+ } from "plgg-test";
7
+ import { applyFileName } from "plgg-bundle/domain/model/BundleConfig";
8
+
9
+ test("applyFileName substitutes name and format", () =>
10
+ all([
11
+ check(
12
+ applyFileName(
13
+ "[name].[format].js",
14
+ "index",
15
+ "es",
16
+ ),
17
+ toBe("index.es.js"),
18
+ ),
19
+ check(
20
+ applyFileName(
21
+ "[name].[format].js",
22
+ "index",
23
+ "cjs",
24
+ ),
25
+ toBe("index.cjs.js"),
26
+ ),
27
+ check(
28
+ applyFileName(
29
+ "[name].[format].js",
30
+ "styleEntry",
31
+ "es",
32
+ ),
33
+ toBe("styleEntry.es.js"),
34
+ ),
35
+ ]));
@@ -0,0 +1,162 @@
1
+ // Plain-TS build tool: a foundation bundler must not
2
+ // depend on the library it builds (plgg), so this
3
+ // package is written in plain TypeScript — native
4
+ // control flow, throw-on-failure caught at the bin
5
+ // boundary — rather than plgg's Result/Option idiom.
6
+ // See README. The CLAUDE.md no-`as`/`any`/`ts-ignore`
7
+ // rule still holds.
8
+
9
+ /**
10
+ * The output module formats. `"es"` → ESM, `"cjs"` →
11
+ * CommonJS — both emitted today by every library
12
+ * package.
13
+ */
14
+ export type Format = "es" | "cjs";
15
+
16
+ /**
17
+ * What kind of artifact a config builds.
18
+ * - `"library"` (default): a consumed package — dual
19
+ * `es`/`cjs` per entry, a per-file `.d.ts` tree, and
20
+ * sibling/`node:*` deps left EXTERNAL (ruling B), so the
21
+ * bundle stays faithful to its declared dependencies.
22
+ * - `"app"`: the leaf application (the `example` client) —
23
+ * a single self-contained `es` bundle with every
24
+ * workspace sibling INLINED from source (the mirror of
25
+ * the library decision: the app is where bundling deps
26
+ * is correct, since a browser cannot resolve a bare
27
+ * `import "plgg"`). No `.d.ts` (nothing consumes it) and
28
+ * only `node:*` stays external.
29
+ */
30
+ export type Target = "library" | "app";
31
+
32
+ /**
33
+ * A way to decide whether an imported specifier is
34
+ * *external* (left as a runtime `import`/`require`,
35
+ * not inlined into the bundle). Mirrors the three
36
+ * shapes used across the vite configs:
37
+ * - a string array of exact specifiers,
38
+ * - a `RegExp` (e.g. `/^node:/`),
39
+ * - a predicate function (plgg-fetch's
40
+ * `isFrameworkDep`).
41
+ */
42
+ export type External =
43
+ | ReadonlyArray<string>
44
+ | RegExp
45
+ | ((specifier: string) => boolean);
46
+
47
+ /**
48
+ * One build entry. `name` is the output key — for most
49
+ * packages it equals the file stem, but `plgg-view`'s
50
+ * `styleEntry` / `plgg-server`'s `ssgEntry` use a
51
+ * distinct output key so `dist/styleEntry.*` does not
52
+ * case-collide with the `dist/Style/` declaration tree
53
+ * on case-insensitive filesystems (the U0 fix). The
54
+ * emitted JS file is `name` run through the config's
55
+ * `fileNamePattern`.
56
+ */
57
+ export type Entry = {
58
+ name: string;
59
+ input: string;
60
+ };
61
+
62
+ /**
63
+ * The optional `dev` section: everything `plgg-bundle dev`
64
+ * needs to serve + hot-reload an app. Absent for a plain
65
+ * library config (build-only), so existing configs need no
66
+ * change.
67
+ *
68
+ * - `entry` — the app's dev-entry module (absolute or
69
+ * `root`-relative), default-exporting a `FetchFactory`.
70
+ * - `port` — the port the node:http server binds.
71
+ * - `watch` — source roots (absolute or `root`-relative)
72
+ * watched for code/content edits.
73
+ * - `allowedHosts` — extra `Host` headers accepted beyond
74
+ * loopback (e.g. a tunnel domain).
75
+ * - `sourceAliases` — cross-package self-aliases to resolve
76
+ * to SOURCE during dev, so a dependency's `<prefix>/*`
77
+ * imports (e.g. a theme package's) load from its `.ts`
78
+ * and hot-reload, instead of its built dist. Empty for a
79
+ * self-contained app.
80
+ */
81
+ export type DevConfig = Readonly<{
82
+ entry: string;
83
+ port: number;
84
+ watch: ReadonlyArray<string>;
85
+ allowedHosts: ReadonlyArray<string>;
86
+ sourceAliases: ReadonlyArray<SourceAlias>;
87
+ }>;
88
+
89
+ /**
90
+ * A dev-time source resolution rule: resolve `<prefix>`
91
+ * and `<prefix>/<sub>` specifiers to `<srcDir>` /
92
+ * `<srcDir>/<sub>` (`.ts`/`/index.ts`), so a dependency
93
+ * package's source participates in hot-reload.
94
+ */
95
+ export type SourceAlias = Readonly<{
96
+ prefix: string;
97
+ srcDir: string;
98
+ }>;
99
+
100
+ /**
101
+ * The bundler's typed configuration, replacing
102
+ * vite.config's `lib` + `rollupOptions`. Parsed and
103
+ * validated at the `unknown` boundary by
104
+ * `asBundleConfig` before any build runs.
105
+ */
106
+ export type BundleConfig = Readonly<{
107
+ /**
108
+ * The artifact kind (see {@link Target}). Optional in
109
+ * the config file; defaults to `"library"`.
110
+ */
111
+ target: Target;
112
+ /** Package root (absolute). */
113
+ root: string;
114
+ /** Source root under `root` (e.g. "src"). */
115
+ rootDir: string;
116
+ /** Output dir under `root` (e.g. "dist"). */
117
+ outDir: string;
118
+ /** One entry (single-entry) or several (multi). */
119
+ entries: ReadonlyArray<Entry>;
120
+ /** Formats to emit (es and/or cjs). */
121
+ formats: ReadonlyArray<Format>;
122
+ /**
123
+ * Output file-name template with `[name]` and
124
+ * `[format]` placeholders, e.g.
125
+ * `"[name].[format].js"` → `index.es.js` /
126
+ * `index.cjs.js` for the `index` entry,
127
+ * `styleEntry.es.js` for `styleEntry`. Reproduces
128
+ * vite's `index.${format}.js` / `${name}.${format}.js`.
129
+ */
130
+ fileNamePattern: string;
131
+ /**
132
+ * The package self-alias prefix and its source root,
133
+ * e.g. `{ prefix: "plgg", srcRoot: "src" }` so
134
+ * `plgg/Atomics/Num` resolves to `src/Atomics/Num`.
135
+ */
136
+ alias: Readonly<{
137
+ prefix: string;
138
+ srcRoot: string;
139
+ }>;
140
+ /**
141
+ * The dev-server section. Present only for a config that
142
+ * `plgg-bundle dev` serves; a build-only library omits
143
+ * it. See {@link DevConfig}.
144
+ */
145
+ dev?: DevConfig;
146
+ }>;
147
+
148
+ /**
149
+ * Apply a {@link BundleConfig#fileNamePattern} to an
150
+ * entry name and format, substituting `[name]` and
151
+ * `[format]`. Pure string templating.
152
+ */
153
+ export const applyFileName = (
154
+ pattern: string,
155
+ name: string,
156
+ format: Format,
157
+ ): string =>
158
+ pattern
159
+ .split("[name]")
160
+ .join(name)
161
+ .split("[format]")
162
+ .join(format);
@@ -0,0 +1,230 @@
1
+ import {
2
+ test,
3
+ check,
4
+ all,
5
+ toBe,
6
+ } from "plgg-test";
7
+ import { asBundleConfig } from "plgg-bundle/domain/usecase/asBundleConfig";
8
+
9
+ const valid = {
10
+ root: "/pkg",
11
+ rootDir: "src",
12
+ outDir: "dist",
13
+ fileNamePattern: "[name].[format].js",
14
+ entries: [{ name: "index", input: "index.ts" }],
15
+ formats: ["es", "cjs"],
16
+ alias: { prefix: "plgg", srcRoot: "src" },
17
+ };
18
+
19
+ /**
20
+ * True when `asBundleConfig(input)` throws a ConfigError
21
+ * (plain-TS validators throw rather than return a
22
+ * `Result`, so the spec catches at the boundary).
23
+ */
24
+ const rejects = (input: unknown): boolean => {
25
+ try {
26
+ asBundleConfig(input);
27
+ return false;
28
+ } catch (e) {
29
+ return (
30
+ e instanceof Error &&
31
+ e.message.startsWith("ConfigError")
32
+ );
33
+ }
34
+ };
35
+
36
+ test("asBundleConfig accepts a well-formed config", () => {
37
+ const c = asBundleConfig(valid);
38
+ return all([
39
+ check(c.root, toBe("/pkg")),
40
+ check(
41
+ c.fileNamePattern,
42
+ toBe("[name].[format].js"),
43
+ ),
44
+ check(c.formats.length, toBe(2)),
45
+ check(c.entries.length, toBe(1)),
46
+ check(c.alias.prefix, toBe("plgg")),
47
+ ]);
48
+ });
49
+
50
+ test("asBundleConfig rejects a non-object", () =>
51
+ check(rejects(42), toBe(true)));
52
+
53
+ test("asBundleConfig rejects a missing field", () =>
54
+ check(
55
+ rejects({ ...valid, formats: undefined }),
56
+ toBe(true),
57
+ ));
58
+
59
+ test("asBundleConfig rejects an invalid format", () =>
60
+ check(
61
+ rejects({ ...valid, formats: ["es", "umd"] }),
62
+ toBe(true),
63
+ ));
64
+
65
+ const validDev = {
66
+ entry: "devEntry.ts",
67
+ port: 5181,
68
+ watch: ["src", "docs"],
69
+ allowedHosts: ["plgg-guide.qmu.dev"],
70
+ };
71
+
72
+ test("asBundleConfig leaves dev absent for a build-only config", () =>
73
+ check(
74
+ asBundleConfig(valid).dev,
75
+ toBe(undefined),
76
+ ));
77
+
78
+ test("asBundleConfig accepts and narrows a dev section", () => {
79
+ const c = asBundleConfig({
80
+ ...valid,
81
+ dev: validDev,
82
+ });
83
+ return all([
84
+ check(c.dev?.entry, toBe("devEntry.ts")),
85
+ check(c.dev?.port, toBe(5181)),
86
+ check(c.dev?.watch.length, toBe(2)),
87
+ check(
88
+ c.dev?.allowedHosts[0],
89
+ toBe("plgg-guide.qmu.dev"),
90
+ ),
91
+ ]);
92
+ });
93
+
94
+ test("asBundleConfig rejects a non-object dev section", () =>
95
+ check(
96
+ rejects({ ...valid, dev: 42 }),
97
+ toBe(true),
98
+ ));
99
+
100
+ test("asBundleConfig rejects a dev section with a bad port", () =>
101
+ check(
102
+ rejects({
103
+ ...valid,
104
+ dev: { ...validDev, port: "5181" },
105
+ }),
106
+ toBe(true),
107
+ ));
108
+
109
+ test("asBundleConfig rejects a dev section with a non-string watch entry", () =>
110
+ check(
111
+ rejects({
112
+ ...valid,
113
+ dev: { ...validDev, watch: ["src", 7] },
114
+ }),
115
+ toBe(true),
116
+ ));
117
+
118
+ test("asBundleConfig rejects a dev section with non-array allowedHosts", () =>
119
+ check(
120
+ rejects({
121
+ ...valid,
122
+ dev: {
123
+ ...validDev,
124
+ allowedHosts: "nope",
125
+ },
126
+ }),
127
+ toBe(true),
128
+ ));
129
+
130
+ test("asBundleConfig accepts target \"app\"", () =>
131
+ check(
132
+ asBundleConfig({ ...valid, target: "app" })
133
+ .target,
134
+ toBe("app"),
135
+ ));
136
+
137
+ test("asBundleConfig rejects an invalid target", () =>
138
+ check(
139
+ rejects({ ...valid, target: "umd" }),
140
+ toBe(true),
141
+ ));
142
+
143
+ test("asBundleConfig rejects a non-string required field", () =>
144
+ check(
145
+ rejects({ ...valid, root: 7 }),
146
+ toBe(true),
147
+ ));
148
+
149
+ test("asBundleConfig rejects a malformed entry", () =>
150
+ all([
151
+ check(
152
+ rejects({
153
+ ...valid,
154
+ entries: [{ name: "x" }],
155
+ }),
156
+ toBe(true),
157
+ ),
158
+ check(
159
+ rejects({ ...valid, entries: [42] }),
160
+ toBe(true),
161
+ ),
162
+ ]));
163
+
164
+ test("asBundleConfig rejects a malformed alias", () =>
165
+ all([
166
+ check(
167
+ rejects({ ...valid, alias: { prefix: "p" } }),
168
+ toBe(true),
169
+ ),
170
+ check(
171
+ rejects({ ...valid, alias: "nope" }),
172
+ toBe(true),
173
+ ),
174
+ ]));
175
+
176
+ test("asBundleConfig rejects a non-string format element", () =>
177
+ check(
178
+ rejects({ ...valid, formats: [7] }),
179
+ toBe(true),
180
+ ));
181
+
182
+ test("asBundleConfig defaults dev.sourceAliases to [] when absent", () =>
183
+ check(
184
+ asBundleConfig({ ...valid, dev: validDev })
185
+ .dev?.sourceAliases.length,
186
+ toBe(0),
187
+ ));
188
+
189
+ test("asBundleConfig accepts dev.sourceAliases", () => {
190
+ const c = asBundleConfig({
191
+ ...valid,
192
+ dev: {
193
+ ...validDev,
194
+ sourceAliases: [
195
+ { prefix: "plggpress", srcDir: "../plggpress/src" },
196
+ ],
197
+ },
198
+ });
199
+ return all([
200
+ check(
201
+ c.dev?.sourceAliases[0]?.prefix,
202
+ toBe("plggpress"),
203
+ ),
204
+ check(
205
+ c.dev?.sourceAliases[0]?.srcDir,
206
+ toBe("../plggpress/src"),
207
+ ),
208
+ ]);
209
+ });
210
+
211
+ test("asBundleConfig rejects a non-array sourceAliases", () =>
212
+ check(
213
+ rejects({
214
+ ...valid,
215
+ dev: { ...validDev, sourceAliases: "nope" },
216
+ }),
217
+ toBe(true),
218
+ ));
219
+
220
+ test("asBundleConfig rejects a malformed sourceAlias element", () =>
221
+ check(
222
+ rejects({
223
+ ...valid,
224
+ dev: {
225
+ ...validDev,
226
+ sourceAliases: [{ prefix: "x" }],
227
+ },
228
+ }),
229
+ toBe(true),
230
+ ));