@agimon-ai/doompi-build 0.0.1-alpha.2

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 (140) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +82 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/browserFile.cjs +1 -0
  5. package/dist/browserFile.d.cts +2 -0
  6. package/dist/browserFile.d.mts +2 -0
  7. package/dist/browserFile.mjs +1 -0
  8. package/dist/constants/contributions.cjs +2 -0
  9. package/dist/constants/contributions.cjs.map +1 -0
  10. package/dist/constants/contributions.mjs +2 -0
  11. package/dist/constants/contributions.mjs.map +1 -0
  12. package/dist/constants/layout.cjs +2 -0
  13. package/dist/constants/layout.cjs.map +1 -0
  14. package/dist/constants/layout.d.cts +59 -0
  15. package/dist/constants/layout.d.cts.map +1 -0
  16. package/dist/constants/layout.d.mts +59 -0
  17. package/dist/constants/layout.d.mts.map +1 -0
  18. package/dist/constants/layout.mjs +2 -0
  19. package/dist/constants/layout.mjs.map +1 -0
  20. package/dist/index.cjs +1 -0
  21. package/dist/index.d.cts +16 -0
  22. package/dist/index.d.mts +16 -0
  23. package/dist/index.mjs +1 -0
  24. package/dist/services/browserFile/index.cjs +2 -0
  25. package/dist/services/browserFile/index.cjs.map +1 -0
  26. package/dist/services/browserFile/index.d.cts +28 -0
  27. package/dist/services/browserFile/index.d.cts.map +1 -0
  28. package/dist/services/browserFile/index.d.mts +28 -0
  29. package/dist/services/browserFile/index.d.mts.map +1 -0
  30. package/dist/services/browserFile/index.mjs +2 -0
  31. package/dist/services/browserFile/index.mjs.map +1 -0
  32. package/dist/services/filename/index.cjs +2 -0
  33. package/dist/services/filename/index.cjs.map +1 -0
  34. package/dist/services/filename/index.d.cts +29 -0
  35. package/dist/services/filename/index.d.cts.map +1 -0
  36. package/dist/services/filename/index.d.mts +29 -0
  37. package/dist/services/filename/index.d.mts.map +1 -0
  38. package/dist/services/filename/index.mjs +2 -0
  39. package/dist/services/filename/index.mjs.map +1 -0
  40. package/dist/services/generate/index.cjs +2 -0
  41. package/dist/services/generate/index.cjs.map +1 -0
  42. package/dist/services/generate/index.d.cts +15 -0
  43. package/dist/services/generate/index.d.cts.map +1 -0
  44. package/dist/services/generate/index.d.mts +15 -0
  45. package/dist/services/generate/index.d.mts.map +1 -0
  46. package/dist/services/generate/index.mjs +2 -0
  47. package/dist/services/generate/index.mjs.map +1 -0
  48. package/dist/services/generate/type.d.cts +26 -0
  49. package/dist/services/generate/type.d.cts.map +1 -0
  50. package/dist/services/generate/type.d.mts +26 -0
  51. package/dist/services/generate/type.d.mts.map +1 -0
  52. package/dist/services/identity/index.cjs +2 -0
  53. package/dist/services/identity/index.cjs.map +1 -0
  54. package/dist/services/identity/index.mjs +2 -0
  55. package/dist/services/identity/index.mjs.map +1 -0
  56. package/dist/services/renderClient/index.cjs +3 -0
  57. package/dist/services/renderClient/index.cjs.map +1 -0
  58. package/dist/services/renderClient/index.mjs +3 -0
  59. package/dist/services/renderClient/index.mjs.map +1 -0
  60. package/dist/services/renderEntry/index.cjs +7 -0
  61. package/dist/services/renderEntry/index.cjs.map +1 -0
  62. package/dist/services/renderEntry/index.d.cts +24 -0
  63. package/dist/services/renderEntry/index.d.cts.map +1 -0
  64. package/dist/services/renderEntry/index.d.mts +24 -0
  65. package/dist/services/renderEntry/index.d.mts.map +1 -0
  66. package/dist/services/renderEntry/index.mjs +7 -0
  67. package/dist/services/renderEntry/index.mjs.map +1 -0
  68. package/dist/services/renderEntry/type.d.cts +14 -0
  69. package/dist/services/renderEntry/type.d.cts.map +1 -0
  70. package/dist/services/renderEntry/type.d.mts +14 -0
  71. package/dist/services/renderEntry/type.d.mts.map +1 -0
  72. package/dist/services/resolveTarget/index.cjs +2 -0
  73. package/dist/services/resolveTarget/index.cjs.map +1 -0
  74. package/dist/services/resolveTarget/index.d.cts +16 -0
  75. package/dist/services/resolveTarget/index.d.cts.map +1 -0
  76. package/dist/services/resolveTarget/index.d.mts +16 -0
  77. package/dist/services/resolveTarget/index.d.mts.map +1 -0
  78. package/dist/services/resolveTarget/index.mjs +2 -0
  79. package/dist/services/resolveTarget/index.mjs.map +1 -0
  80. package/dist/services/resolveTarget/type.d.cts +34 -0
  81. package/dist/services/resolveTarget/type.d.cts.map +1 -0
  82. package/dist/services/resolveTarget/type.d.mts +34 -0
  83. package/dist/services/resolveTarget/type.d.mts.map +1 -0
  84. package/dist/services/scan/index.cjs +2 -0
  85. package/dist/services/scan/index.cjs.map +1 -0
  86. package/dist/services/scan/index.d.cts +19 -0
  87. package/dist/services/scan/index.d.cts.map +1 -0
  88. package/dist/services/scan/index.d.mts +19 -0
  89. package/dist/services/scan/index.d.mts.map +1 -0
  90. package/dist/services/scan/index.mjs +2 -0
  91. package/dist/services/scan/index.mjs.map +1 -0
  92. package/dist/services/scan/type.d.cts +25 -0
  93. package/dist/services/scan/type.d.cts.map +1 -0
  94. package/dist/services/scan/type.d.mts +25 -0
  95. package/dist/services/scan/type.d.mts.map +1 -0
  96. package/dist/services/segments/index.cjs +2 -0
  97. package/dist/services/segments/index.cjs.map +1 -0
  98. package/dist/services/segments/index.d.cts +26 -0
  99. package/dist/services/segments/index.d.cts.map +1 -0
  100. package/dist/services/segments/index.d.mts +26 -0
  101. package/dist/services/segments/index.d.mts.map +1 -0
  102. package/dist/services/segments/index.mjs +2 -0
  103. package/dist/services/segments/index.mjs.map +1 -0
  104. package/dist/services/syncManifest/index.cjs +2 -0
  105. package/dist/services/syncManifest/index.cjs.map +1 -0
  106. package/dist/services/syncManifest/index.d.cts +21 -0
  107. package/dist/services/syncManifest/index.d.cts.map +1 -0
  108. package/dist/services/syncManifest/index.d.mts +21 -0
  109. package/dist/services/syncManifest/index.d.mts.map +1 -0
  110. package/dist/services/syncManifest/index.mjs +2 -0
  111. package/dist/services/syncManifest/index.mjs.map +1 -0
  112. package/dist/services/syncManifest/type.d.cts +15 -0
  113. package/dist/services/syncManifest/type.d.cts.map +1 -0
  114. package/dist/services/syncManifest/type.d.mts +15 -0
  115. package/dist/services/syncManifest/type.d.mts.map +1 -0
  116. package/dist/services/tsdownPreset/index.cjs +2 -0
  117. package/dist/services/tsdownPreset/index.cjs.map +1 -0
  118. package/dist/services/tsdownPreset/index.d.cts +63 -0
  119. package/dist/services/tsdownPreset/index.d.cts.map +1 -0
  120. package/dist/services/tsdownPreset/index.d.mts +63 -0
  121. package/dist/services/tsdownPreset/index.d.mts.map +1 -0
  122. package/dist/services/tsdownPreset/index.mjs +2 -0
  123. package/dist/services/tsdownPreset/index.mjs.map +1 -0
  124. package/dist/services/writeGenerated/index.cjs +2 -0
  125. package/dist/services/writeGenerated/index.cjs.map +1 -0
  126. package/dist/services/writeGenerated/index.d.cts +23 -0
  127. package/dist/services/writeGenerated/index.d.cts.map +1 -0
  128. package/dist/services/writeGenerated/index.d.mts +23 -0
  129. package/dist/services/writeGenerated/index.d.mts.map +1 -0
  130. package/dist/services/writeGenerated/index.mjs +2 -0
  131. package/dist/services/writeGenerated/index.mjs.map +1 -0
  132. package/dist/tsdown.cjs +1 -0
  133. package/dist/tsdown.d.cts +2 -0
  134. package/dist/tsdown.d.mts +2 -0
  135. package/dist/tsdown.mjs +1 -0
  136. package/dist/types/extensionGraph.d.cts +75 -0
  137. package/dist/types/extensionGraph.d.cts.map +1 -0
  138. package/dist/types/extensionGraph.d.mts +75 -0
  139. package/dist/types/extensionGraph.d.mts.map +1 -0
  140. package/package.json +72 -0
@@ -0,0 +1,14 @@
1
+ //#region src/services/renderEntry/type.d.ts
2
+ /** What the renderers need beyond the resolved contributions themselves. */
3
+ export interface RenderOptions {
4
+ /** The package's npm name, used as the extension and facet name. */
5
+ readonly packageName: string;
6
+ /** The cockpit plugin id. */
7
+ readonly pluginId: string;
8
+ /** Routing root, package-relative. */
9
+ readonly root: string;
10
+ /** Where the generated entry is written, package-relative, so its imports can be made relative to it. */
11
+ readonly entryDir: string;
12
+ }
13
+ //#endregion
14
+ //# sourceMappingURL=type.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.cts","names":[],"sources":["../../../src/services/renderEntry/type.ts"],"mappings":";;iBACiB;;WAEN;;WAEA;;WAEA;;WAEA"}
@@ -0,0 +1,14 @@
1
+ //#region src/services/renderEntry/type.d.ts
2
+ /** What the renderers need beyond the resolved contributions themselves. */
3
+ export interface RenderOptions {
4
+ /** The package's npm name, used as the extension and facet name. */
5
+ readonly packageName: string;
6
+ /** The cockpit plugin id. */
7
+ readonly pluginId: string;
8
+ /** Routing root, package-relative. */
9
+ readonly root: string;
10
+ /** Where the generated entry is written, package-relative, so its imports can be made relative to it. */
11
+ readonly entryDir: string;
12
+ }
13
+ //#endregion
14
+ //# sourceMappingURL=type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.mts","names":[],"sources":["../../../src/services/renderEntry/type.ts"],"mappings":";;iBACiB;;WAEN;;WAEA;;WAEA;;WAEA"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../constants/contributions.cjs"),t=require("../identity/index.cjs"),n={global:0,workspace:1,session:2};function r(e,t){return t===`web`?e.side===`frontend`:e.side===`backend`||t===`cli`&&e.platform===`cli`}const i={cli:{backend:e.CLI_FIELDS,frontend:e.CLI_FRONTEND_FIELDS},server:{backend:e.SERVER_FIELDS,frontend:{}},web:{backend:{},frontend:e.WEB_FIELDS}};function a(e){let t=e.gates.map(e=>`${e.kind}:${e.id}`).join(`/`),n=e.route.map(e=>`literal`in e?e.literal:`[${e.param.name}]`).join(`/`);return[e.side,e.scope,t,e.surface??``,n,e.name,e.target??``].join(`|`)}function o(e,t,n){return n===void 0?t:e[n]??t}function s(t,n){let r=t.surface;if(r===void 0)return;let a=i[n][t.side][r];if(a!==void 0&&a!==``&&(n!==`web`||r!==`lifecycle`||t.name===`start`))return n===`web`&&r===`setting`?o(e.SETTING_FIELDS,a,t.target):n===`web`&&r===`action`?o(e.ACTION_FIELDS,a,t.target):a}function c(e,n){let r=new Map,i=[];for(let n of e)n.field===`tool-renderers`?r.set(t.toSnake(n.entry.name),n.entry):i.push(n);if(r.size===0)return i;let a=i.map(e=>{if(e.field!==`tools`)return e;let n=t.toSnake(e.entry.name),i=r.get(n);return i===void 0?e:(r.delete(n),{...e,renderers:i})});for(let e of r.values())n.push({path:e.file,message:`renders a tool no backend file contributes; nothing will draw it`});return a}function l(e,t){let i=[],o=[],l=[],u=new Map;for(let n of e.entries){if(!r(n,t)||n.platform!==void 0&&n.platform!==t)continue;if(n.role===`escape-hatch`){o.push(n);continue}if(n.role===`root`){if(n.side===`frontend`){i.push({path:n.file,message:`a scope root is a backend file; the cockpit has no mount context`});continue}l.push(n);continue}let e=a(n),s=u.get(e);if(s===void 0){u.set(e,n);continue}if(s.platform===n.platform){i.push({path:n.file,message:`duplicates ${s.file} for ${t}; the first one wins`});continue}n.platform!==void 0&&u.set(e,n)}let d=[];for(let e of u.values()){let n=s(e,t);n!==void 0&&d.push({entry:e,field:n})}return{target:t,contributions:c(d,i),escapeHatches:o,roots:l.sort((e,t)=>n[e.scope]-n[t.scope]),notices:i}}exports.resolveTarget=l;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["CLI_FIELDS","CLI_FRONTEND_FIELDS","SERVER_FIELDS","WEB_FIELDS","SETTING_FIELDS","ACTION_FIELDS","toSnake"],"sources":["../../../src/services/resolveTarget/index.ts"],"sourcesContent":["import {\n ACTION_FIELDS,\n CLI_FIELDS,\n CLI_FRONTEND_FIELDS,\n MERGED_INTO_TOOL,\n NOT_A_CONTRIBUTION,\n SERVER_FIELDS,\n SETTING_FIELDS,\n WEB_FIELDS,\n} from '../../constants/contributions';\nimport type { ExtensionEntry, ExtensionGraph, ExtensionNotice, ExtensionSide } from '../../types/extensionGraph';\nimport { toSnake } from '../identity';\nimport type { BuildTarget, ResolvedContribution, TargetResolution } from './type';\n\n/** Nesting order, so roots compose outermost first. */\nconst SCOPE_DEPTH: Readonly<Record<string, number>> = { global: 0, workspace: 1, session: 2 };\n\n/**\n * Which sides a host reads, and which of their files.\n *\n * The side axis is logic against presentation, not Node against browser, so\n * the terminal reads both: every backend file, plus the frontend files that\n * name it. A frontend file that does not name the terminal is cockpit code,\n * which a terminal cannot render, so the CLI takes only explicit `.cli` ones.\n * The headless server has no presentation at all.\n */\nfunction drawsFrom(entry: ExtensionEntry, target: BuildTarget): boolean {\n if (target === 'web') return entry.side === 'frontend';\n if (entry.side === 'backend') return true;\n return target === 'cli' && entry.platform === 'cli';\n}\n\nconst FIELDS_OF: Readonly<Record<BuildTarget, Readonly<Record<ExtensionSide, Readonly<Record<string, string>>>>>> = {\n cli: { backend: CLI_FIELDS, frontend: CLI_FRONTEND_FIELDS },\n server: { backend: SERVER_FIELDS, frontend: {} },\n web: { backend: {}, frontend: WEB_FIELDS },\n};\n\n/** Everything but the platform: two files sharing this are the same contribution. */\nfunction logicalKey(entry: ExtensionEntry): string {\n const gates = entry.gates.map((gate) => `${gate.kind}:${gate.id}`).join('/');\n const route = entry.route\n .map((segment) => ('literal' in segment ? segment.literal : `[${segment.param.name}]`))\n .join('/');\n // The side is part of it, because the terminal reads both. Without it a\n // tool's logic and its TUI would look like one contribution overriding the\n // other, and only one of the pair would survive.\n return [entry.side, entry.scope, gates, entry.surface ?? '', route, entry.name, entry.target ?? ''].join('|');\n}\n\n/** Resolves the field for a surface whose filename target picks between two shapes. */\nfunction variantField(\n table: Readonly<Record<string, string>>,\n fallback: string,\n entryTarget: string | undefined,\n): string {\n if (entryTarget === undefined) return fallback;\n return table[entryTarget] ?? fallback;\n}\n\nfunction fieldFor(entry: ExtensionEntry, target: BuildTarget): string | undefined {\n const surface = entry.surface;\n if (surface === undefined) return undefined;\n\n const declared = FIELDS_OF[target][entry.side][surface];\n if (declared === undefined || declared === NOT_A_CONTRIBUTION) return undefined;\n\n if (target === 'web' && surface === 'lifecycle' && entry.name !== 'start') return undefined;\n if (target === 'web' && surface === 'setting') return variantField(SETTING_FIELDS, declared, entry.target);\n if (target === 'web' && surface === 'action') return variantField(ACTION_FIELDS, declared, entry.target);\n return declared;\n}\n\n/**\n * Folds terminal tool renderers into the tool declarations they draw.\n *\n * Paired by the snake-cased filename, which is the tool's name on both sides.\n * A renderer naming no tool this package contributes is reported rather than\n * dropped: it is almost always a rename that only half landed, and a silently\n * missing renderer is the kind of thing nobody notices until a demo.\n */\nfunction mergeRenderers(\n contributions: readonly ResolvedContribution[],\n notices: ExtensionNotice[],\n): ResolvedContribution[] {\n const renderers = new Map<string, ExtensionEntry>();\n const rest: ResolvedContribution[] = [];\n for (const contribution of contributions) {\n if (contribution.field !== MERGED_INTO_TOOL) rest.push(contribution);\n else renderers.set(toSnake(contribution.entry.name), contribution.entry);\n }\n if (renderers.size === 0) return rest;\n\n const merged = rest.map((contribution) => {\n if (contribution.field !== 'tools') return contribution;\n const name = toSnake(contribution.entry.name);\n const found = renderers.get(name);\n if (found === undefined) return contribution;\n renderers.delete(name);\n return { ...contribution, renderers: found };\n });\n\n for (const orphan of renderers.values()) {\n notices.push({ path: orphan.file, message: `renders a tool no backend file contributes; nothing will draw it` });\n }\n return merged;\n}\n\n/**\n * Picks the winning file per logical contribution for one host.\n *\n * Metro and Expo semantics: a file naming this platform replaces a sibling\n * that names none, and a file naming none serves every platform on its side\n * that has no more specific sibling. The scan reports the unnamed one, so this\n * is a fallback rather than a supported way to author. Two files naming the\n * same platform for the same contribution is an authoring mistake, so the\n * first wins and the second becomes a notice rather than silently disappearing.\n */\nexport function resolveTarget(graph: ExtensionGraph, target: BuildTarget): TargetResolution {\n const notices: ExtensionNotice[] = [];\n const escapeHatches: ExtensionEntry[] = [];\n const roots: ExtensionEntry[] = [];\n const winners = new Map<string, ExtensionEntry>();\n\n for (const entry of graph.entries) {\n if (!drawsFrom(entry, target)) continue;\n if (entry.platform !== undefined && entry.platform !== target) continue;\n\n if (entry.role === 'escape-hatch') {\n escapeHatches.push(entry);\n continue;\n }\n\n if (entry.role === 'root') {\n // Backend only. A root is constructed from a mount context, and the\n // cockpit has none: it builds its plugin definition as data and starts\n // it separately, so there is nothing for a frontend root to receive.\n if (entry.side === 'frontend') {\n notices.push({ path: entry.file, message: 'a scope root is a backend file; the cockpit has no mount context' });\n continue;\n }\n roots.push(entry);\n continue;\n }\n\n const key = logicalKey(entry);\n const held = winners.get(key);\n if (held === undefined) {\n winners.set(key, entry);\n continue;\n }\n if (held.platform === entry.platform) {\n notices.push({ path: entry.file, message: `duplicates ${held.file} for ${target}; the first one wins` });\n continue;\n }\n // A platform file always beats the unsuffixed sibling it overrides.\n if (entry.platform !== undefined) winners.set(key, entry);\n }\n\n const contributions: ResolvedContribution[] = [];\n for (const entry of winners.values()) {\n const field = fieldFor(entry, target);\n if (field === undefined) continue;\n contributions.push({ entry, field });\n }\n\n return {\n target,\n contributions: mergeRenderers(contributions, notices),\n escapeHatches,\n roots: roots.sort((left, right) => SCOPE_DEPTH[left.scope] - SCOPE_DEPTH[right.scope]),\n notices,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { ExtensionGraph } from "../../types/extensionGraph.cjs";
2
+ import { BuildTarget, TargetResolution } from "./type.cjs";
3
+ //#region src/services/resolveTarget/index.d.ts
4
+ /**
5
+ * Picks the winning file per logical contribution for one host.
6
+ *
7
+ * Metro and Expo semantics: a file naming this platform replaces a sibling
8
+ * that names none, and a file naming none serves every platform on its side
9
+ * that has no more specific sibling. The scan reports the unnamed one, so this
10
+ * is a fallback rather than a supported way to author. Two files naming the
11
+ * same platform for the same contribution is an authoring mistake, so the
12
+ * first wins and the second becomes a notice rather than silently disappearing.
13
+ */
14
+ export declare function resolveTarget(graph: ExtensionGraph, target: BuildTarget): TargetResolution;
15
+ //#endregion
16
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/services/resolveTarget/index.ts"],"mappings":";;;;;;;;;;;;;wBAsHgB,cAAc,OAAO,gBAAgB,QAAQ,cAAc"}
@@ -0,0 +1,16 @@
1
+ import { ExtensionGraph } from "../../types/extensionGraph.mjs";
2
+ import { BuildTarget, TargetResolution } from "./type.mjs";
3
+ //#region src/services/resolveTarget/index.d.ts
4
+ /**
5
+ * Picks the winning file per logical contribution for one host.
6
+ *
7
+ * Metro and Expo semantics: a file naming this platform replaces a sibling
8
+ * that names none, and a file naming none serves every platform on its side
9
+ * that has no more specific sibling. The scan reports the unnamed one, so this
10
+ * is a fallback rather than a supported way to author. Two files naming the
11
+ * same platform for the same contribution is an authoring mistake, so the
12
+ * first wins and the second becomes a notice rather than silently disappearing.
13
+ */
14
+ export declare function resolveTarget(graph: ExtensionGraph, target: BuildTarget): TargetResolution;
15
+ //#endregion
16
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/resolveTarget/index.ts"],"mappings":";;;;;;;;;;;;;wBAsHgB,cAAc,OAAO,gBAAgB,QAAQ,cAAc"}
@@ -0,0 +1,2 @@
1
+ import{ACTION_FIELDS as e,CLI_FIELDS as t,CLI_FRONTEND_FIELDS as n,SERVER_FIELDS as r,SETTING_FIELDS as i,WEB_FIELDS as a}from"../../constants/contributions.mjs";import{toSnake as o}from"../identity/index.mjs";const s={global:0,workspace:1,session:2};function c(e,t){return t===`web`?e.side===`frontend`:e.side===`backend`||t===`cli`&&e.platform===`cli`}const l={cli:{backend:t,frontend:n},server:{backend:r,frontend:{}},web:{backend:{},frontend:a}};function u(e){let t=e.gates.map(e=>`${e.kind}:${e.id}`).join(`/`),n=e.route.map(e=>`literal`in e?e.literal:`[${e.param.name}]`).join(`/`);return[e.side,e.scope,t,e.surface??``,n,e.name,e.target??``].join(`|`)}function d(e,t,n){return n===void 0?t:e[n]??t}function f(t,n){let r=t.surface;if(r===void 0)return;let a=l[n][t.side][r];if(a!==void 0&&a!==``&&(n!==`web`||r!==`lifecycle`||t.name===`start`))return n===`web`&&r===`setting`?d(i,a,t.target):n===`web`&&r===`action`?d(e,a,t.target):a}function p(e,t){let n=new Map,r=[];for(let t of e)t.field===`tool-renderers`?n.set(o(t.entry.name),t.entry):r.push(t);if(n.size===0)return r;let i=r.map(e=>{if(e.field!==`tools`)return e;let t=o(e.entry.name),r=n.get(t);return r===void 0?e:(n.delete(t),{...e,renderers:r})});for(let e of n.values())t.push({path:e.file,message:`renders a tool no backend file contributes; nothing will draw it`});return i}function m(e,t){let n=[],r=[],i=[],a=new Map;for(let o of e.entries){if(!c(o,t)||o.platform!==void 0&&o.platform!==t)continue;if(o.role===`escape-hatch`){r.push(o);continue}if(o.role===`root`){if(o.side===`frontend`){n.push({path:o.file,message:`a scope root is a backend file; the cockpit has no mount context`});continue}i.push(o);continue}let e=u(o),s=a.get(e);if(s===void 0){a.set(e,o);continue}if(s.platform===o.platform){n.push({path:o.file,message:`duplicates ${s.file} for ${t}; the first one wins`});continue}o.platform!==void 0&&a.set(e,o)}let o=[];for(let e of a.values()){let n=f(e,t);n!==void 0&&o.push({entry:e,field:n})}return{target:t,contributions:p(o,n),escapeHatches:r,roots:i.sort((e,t)=>s[e.scope]-s[t.scope]),notices:n}}export{m as resolveTarget};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/services/resolveTarget/index.ts"],"sourcesContent":["import {\n ACTION_FIELDS,\n CLI_FIELDS,\n CLI_FRONTEND_FIELDS,\n MERGED_INTO_TOOL,\n NOT_A_CONTRIBUTION,\n SERVER_FIELDS,\n SETTING_FIELDS,\n WEB_FIELDS,\n} from '../../constants/contributions';\nimport type { ExtensionEntry, ExtensionGraph, ExtensionNotice, ExtensionSide } from '../../types/extensionGraph';\nimport { toSnake } from '../identity';\nimport type { BuildTarget, ResolvedContribution, TargetResolution } from './type';\n\n/** Nesting order, so roots compose outermost first. */\nconst SCOPE_DEPTH: Readonly<Record<string, number>> = { global: 0, workspace: 1, session: 2 };\n\n/**\n * Which sides a host reads, and which of their files.\n *\n * The side axis is logic against presentation, not Node against browser, so\n * the terminal reads both: every backend file, plus the frontend files that\n * name it. A frontend file that does not name the terminal is cockpit code,\n * which a terminal cannot render, so the CLI takes only explicit `.cli` ones.\n * The headless server has no presentation at all.\n */\nfunction drawsFrom(entry: ExtensionEntry, target: BuildTarget): boolean {\n if (target === 'web') return entry.side === 'frontend';\n if (entry.side === 'backend') return true;\n return target === 'cli' && entry.platform === 'cli';\n}\n\nconst FIELDS_OF: Readonly<Record<BuildTarget, Readonly<Record<ExtensionSide, Readonly<Record<string, string>>>>>> = {\n cli: { backend: CLI_FIELDS, frontend: CLI_FRONTEND_FIELDS },\n server: { backend: SERVER_FIELDS, frontend: {} },\n web: { backend: {}, frontend: WEB_FIELDS },\n};\n\n/** Everything but the platform: two files sharing this are the same contribution. */\nfunction logicalKey(entry: ExtensionEntry): string {\n const gates = entry.gates.map((gate) => `${gate.kind}:${gate.id}`).join('/');\n const route = entry.route\n .map((segment) => ('literal' in segment ? segment.literal : `[${segment.param.name}]`))\n .join('/');\n // The side is part of it, because the terminal reads both. Without it a\n // tool's logic and its TUI would look like one contribution overriding the\n // other, and only one of the pair would survive.\n return [entry.side, entry.scope, gates, entry.surface ?? '', route, entry.name, entry.target ?? ''].join('|');\n}\n\n/** Resolves the field for a surface whose filename target picks between two shapes. */\nfunction variantField(\n table: Readonly<Record<string, string>>,\n fallback: string,\n entryTarget: string | undefined,\n): string {\n if (entryTarget === undefined) return fallback;\n return table[entryTarget] ?? fallback;\n}\n\nfunction fieldFor(entry: ExtensionEntry, target: BuildTarget): string | undefined {\n const surface = entry.surface;\n if (surface === undefined) return undefined;\n\n const declared = FIELDS_OF[target][entry.side][surface];\n if (declared === undefined || declared === NOT_A_CONTRIBUTION) return undefined;\n\n if (target === 'web' && surface === 'lifecycle' && entry.name !== 'start') return undefined;\n if (target === 'web' && surface === 'setting') return variantField(SETTING_FIELDS, declared, entry.target);\n if (target === 'web' && surface === 'action') return variantField(ACTION_FIELDS, declared, entry.target);\n return declared;\n}\n\n/**\n * Folds terminal tool renderers into the tool declarations they draw.\n *\n * Paired by the snake-cased filename, which is the tool's name on both sides.\n * A renderer naming no tool this package contributes is reported rather than\n * dropped: it is almost always a rename that only half landed, and a silently\n * missing renderer is the kind of thing nobody notices until a demo.\n */\nfunction mergeRenderers(\n contributions: readonly ResolvedContribution[],\n notices: ExtensionNotice[],\n): ResolvedContribution[] {\n const renderers = new Map<string, ExtensionEntry>();\n const rest: ResolvedContribution[] = [];\n for (const contribution of contributions) {\n if (contribution.field !== MERGED_INTO_TOOL) rest.push(contribution);\n else renderers.set(toSnake(contribution.entry.name), contribution.entry);\n }\n if (renderers.size === 0) return rest;\n\n const merged = rest.map((contribution) => {\n if (contribution.field !== 'tools') return contribution;\n const name = toSnake(contribution.entry.name);\n const found = renderers.get(name);\n if (found === undefined) return contribution;\n renderers.delete(name);\n return { ...contribution, renderers: found };\n });\n\n for (const orphan of renderers.values()) {\n notices.push({ path: orphan.file, message: `renders a tool no backend file contributes; nothing will draw it` });\n }\n return merged;\n}\n\n/**\n * Picks the winning file per logical contribution for one host.\n *\n * Metro and Expo semantics: a file naming this platform replaces a sibling\n * that names none, and a file naming none serves every platform on its side\n * that has no more specific sibling. The scan reports the unnamed one, so this\n * is a fallback rather than a supported way to author. Two files naming the\n * same platform for the same contribution is an authoring mistake, so the\n * first wins and the second becomes a notice rather than silently disappearing.\n */\nexport function resolveTarget(graph: ExtensionGraph, target: BuildTarget): TargetResolution {\n const notices: ExtensionNotice[] = [];\n const escapeHatches: ExtensionEntry[] = [];\n const roots: ExtensionEntry[] = [];\n const winners = new Map<string, ExtensionEntry>();\n\n for (const entry of graph.entries) {\n if (!drawsFrom(entry, target)) continue;\n if (entry.platform !== undefined && entry.platform !== target) continue;\n\n if (entry.role === 'escape-hatch') {\n escapeHatches.push(entry);\n continue;\n }\n\n if (entry.role === 'root') {\n // Backend only. A root is constructed from a mount context, and the\n // cockpit has none: it builds its plugin definition as data and starts\n // it separately, so there is nothing for a frontend root to receive.\n if (entry.side === 'frontend') {\n notices.push({ path: entry.file, message: 'a scope root is a backend file; the cockpit has no mount context' });\n continue;\n }\n roots.push(entry);\n continue;\n }\n\n const key = logicalKey(entry);\n const held = winners.get(key);\n if (held === undefined) {\n winners.set(key, entry);\n continue;\n }\n if (held.platform === entry.platform) {\n notices.push({ path: entry.file, message: `duplicates ${held.file} for ${target}; the first one wins` });\n continue;\n }\n // A platform file always beats the unsuffixed sibling it overrides.\n if (entry.platform !== undefined) winners.set(key, entry);\n }\n\n const contributions: ResolvedContribution[] = [];\n for (const entry of winners.values()) {\n const field = fieldFor(entry, target);\n if (field === undefined) continue;\n contributions.push({ entry, field });\n }\n\n return {\n target,\n contributions: mergeRenderers(contributions, notices),\n escapeHatches,\n roots: roots.sort((left, right) => SCOPE_DEPTH[left.scope] - SCOPE_DEPTH[right.scope]),\n notices,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { ExtensionEntry, ExtensionNotice } from "../../types/extensionGraph.cjs";
2
+ //#region src/services/resolveTarget/type.d.ts
3
+ /** The three hosts an entry can be generated for. */
4
+ export type BuildTarget = 'cli' | 'server' | 'web';
5
+ /** One file and the host contribution array it belongs in. */
6
+ export interface ResolvedContribution {
7
+ /** The winning file for this logical contribution on this host. */
8
+ readonly entry: ExtensionEntry;
9
+ /** The host contribution array this belongs in. */
10
+ readonly field: string;
11
+ /**
12
+ * A presentation file folded into this contribution rather than emitted
13
+ * beside it. Only the terminal has one: its tool renderers are fields of the
14
+ * tool declaration, so the pair becomes a single registration.
15
+ */
16
+ readonly renderers?: ExtensionEntry;
17
+ }
18
+ /** Everything one host needs generated, plus anything skipped on the way. */
19
+ export interface TargetResolution {
20
+ readonly target: BuildTarget;
21
+ readonly contributions: readonly ResolvedContribution[];
22
+ /** Escape-hatch files whose raw contributions are merged in wholesale. */
23
+ readonly escapeHatches: readonly ExtensionEntry[];
24
+ /**
25
+ * Scope constructors, outermost first.
26
+ *
27
+ * Each one runs before the contributions beneath it and its value joins
28
+ * their mount context, so the order is the nesting order and nothing else.
29
+ */
30
+ readonly roots: readonly ExtensionEntry[];
31
+ readonly notices: readonly ExtensionNotice[];
32
+ }
33
+ //#endregion
34
+ //# sourceMappingURL=type.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.cts","names":[],"sources":["../../../src/services/resolveTarget/type.ts"],"mappings":";;;YAGY;;iBAGK;;WAEN,OAAO;;WAEP;;;;;;WAMA,YAAY;;;iBAIN;WACN,QAAQ;WACR,wBAAwB;;WAExB,wBAAwB;;;;;;;WAOxB,gBAAgB;WAChB,kBAAkB"}
@@ -0,0 +1,34 @@
1
+ import { ExtensionEntry, ExtensionNotice } from "../../types/extensionGraph.mjs";
2
+ //#region src/services/resolveTarget/type.d.ts
3
+ /** The three hosts an entry can be generated for. */
4
+ export type BuildTarget = 'cli' | 'server' | 'web';
5
+ /** One file and the host contribution array it belongs in. */
6
+ export interface ResolvedContribution {
7
+ /** The winning file for this logical contribution on this host. */
8
+ readonly entry: ExtensionEntry;
9
+ /** The host contribution array this belongs in. */
10
+ readonly field: string;
11
+ /**
12
+ * A presentation file folded into this contribution rather than emitted
13
+ * beside it. Only the terminal has one: its tool renderers are fields of the
14
+ * tool declaration, so the pair becomes a single registration.
15
+ */
16
+ readonly renderers?: ExtensionEntry;
17
+ }
18
+ /** Everything one host needs generated, plus anything skipped on the way. */
19
+ export interface TargetResolution {
20
+ readonly target: BuildTarget;
21
+ readonly contributions: readonly ResolvedContribution[];
22
+ /** Escape-hatch files whose raw contributions are merged in wholesale. */
23
+ readonly escapeHatches: readonly ExtensionEntry[];
24
+ /**
25
+ * Scope constructors, outermost first.
26
+ *
27
+ * Each one runs before the contributions beneath it and its value joins
28
+ * their mount context, so the order is the nesting order and nothing else.
29
+ */
30
+ readonly roots: readonly ExtensionEntry[];
31
+ readonly notices: readonly ExtensionNotice[];
32
+ }
33
+ //#endregion
34
+ //# sourceMappingURL=type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.mts","names":[],"sources":["../../../src/services/resolveTarget/type.ts"],"mappings":";;;YAGY;;iBAGK;;WAEN,OAAO;;WAEP;;;;;;WAMA,YAAY;;;iBAIN;WACN,QAAQ;WACR,wBAAwB;;WAExB,wBAAwB;;;;;;;WAOxB,gBAAgB;WAChB,kBAAkB"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../constants/layout.cjs"),n=require("../filename/index.cjs"),r=require("../segments/index.cjs");let i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);const o={scope:`global`,side:void 0,gates:[],surface:void 0,route:[],pendingGate:void 0};function s(e){return e===`backend`?t.BACKEND_SURFACES:t.FRONTEND_SURFACES}function c(e,n){return(e===`backend`?n.platforms?.backend:n.platforms?.frontend)??(e===`backend`?t.BACKEND_PLATFORMS:t.FRONTEND_PLATFORMS)}function l(e,t){let n=s(t);if(n.includes(e))return;let r=e.endsWith(`s`)?e.slice(0,-1):`${e}s`;return n.includes(r)?r:void 0}function u(e){return/export\s+default\s+defineRoutedContribution\s*\([\s\S]*,\s*\{\s*cardinality\s*:\s*['"](optional|many|collection)['"]/u.exec(e)?.[1]}function d(e,n){return(n===void 0?[t.DEFAULT_ROUTING_ROOT,t.ROUTING_ROOT_ALIAS]:[n]).find(t=>i.default.existsSync(a.default.join(e,t)))}function f(e){let f=d(e.packageDir,e.root);if(f===void 0)return{root:e.root??`src/extensions`,entries:[],notices:[]};let p=e.sides?.backend??`backend`,m=e.sides?.frontend??`frontend`,h=e.generatedEntries??t.GENERATED_ENTRY_NAMES,g=[],_=[],v=(e,t)=>void _.push({path:e,message:t}),y=(t,n,r)=>{n.platform===void 0&&v(t,`names no platform; add one of ${c(r,e).join(`, `)} before .${n.extension} (without one the host is inferred from the side rather than declared)`)},b=(t,n)=>{let r=a.default.join(e.packageDir,t),o=i.default.readdirSync(r,{withFileTypes:!0}).sort((e,t)=>e.name.localeCompare(t.name));for(let e of o){let r=`${t}/${e.name}`;e.isDirectory()?x(r,e.name,n):e.isFile()&&S(r,e.name,n)}},x=(e,n,i)=>{let a=r.classifySegment(n);if(a.kind===`private`)return;if(a.kind===`group`)return i.side===void 0&&a.name===p?b(e,{...i,side:`backend`}):i.side===void 0&&a.name===m?b(e,{...i,side:`frontend`}):b(e,i);if(a.kind===`dynamic`)return i.surface!==void 0&&t.ROUTED_SURFACES.includes(i.surface)?b(e,{...i,route:[...i.route,{param:a.param}]}):v(e,`a dynamic segment is only meaningful inside a routed surface such as api/`);if(i.pendingGate!==void 0){let t={kind:i.pendingGate,id:a.name};return b(e,{...i,gates:[...i.gates,t],pendingGate:void 0})}if(i.surface!==void 0)return t.ROUTED_SURFACES.includes(i.surface)?b(e,{...i,route:[...i.route,{literal:a.name}]}):v(e,`${i.surface}/ takes files directly; put helpers in a _private folder`);if(i.side===void 0)return a.name===`workspaces`&&i.scope===`global`?b(e,{...i,scope:`workspace`}):a.name===`sessions`&&i.scope===`workspace`?b(e,{...i,scope:`session`}):v(e,`expected a scope segment or a side group, such as (${p})`);if(t.GATE_SEGMENTS.includes(a.name))return b(e,{...i,pendingGate:a.name});if(s(i.side).includes(a.name))return b(e,{...i,surface:a.name});let o=l(a.name,i.side);return v(e,o===void 0?`'${a.name}' is not a ${i.side} surface`:`'${a.name}' is not a surface, did you mean '${o}'`)},S=(r,o,s)=>{if(s.side===void 0){let e=o.slice(0,o.lastIndexOf(`.`));return s.scope===`global`&&h.includes(e)||s.scope===`global`||n.parseFilename(o,[])===void 0?void 0:v(r,`a contribution needs a side group and a surface folder`)}let l=n.parseFilename(o,c(s.side,e));if(l===void 0)return;let d=u(i.default.readFileSync(a.default.join(e.packageDir,r),`utf8`));if(s.surface===void 0){let e=s.gates[s.gates.length-1];if(e!==void 0&&l.name===e.kind){y(r,l,s.side),g.push({file:r,scope:s.scope,side:s.side,gates:s.gates.slice(0,-1),surface:e.kind,route:[],name:e.id,target:l.target,platform:l.platform,...d?{cardinality:d}:{},role:`contribution`});return}if(l.name===`root`&&e===void 0){y(r,l,s.side),g.push({file:r,scope:s.scope,side:s.side,gates:[],surface:void 0,route:[],name:l.name,target:l.target,platform:l.platform,role:`root`});return}return v(r,e===void 0?`only ${t.ROOT_FILE_NAME}.* is read at a side root; move each contribution into its named surface folder`:`inside ${e.kind}/${e.id}/ a contribution needs a surface folder, or name the file ${e.kind}.* to declare the gate`)}t.ROUTED_SURFACES.includes(s.surface)&&l.name!==`route`||(y(r,l,s.side),g.push({file:r,scope:s.scope,side:s.side,gates:s.gates,surface:s.surface,route:s.route,name:l.name,target:l.target,platform:l.platform,...d?{cardinality:d}:{},role:`contribution`}))};return b(f,o),{root:f,entries:g,notices:_}}exports.resolveRoutingRoot=d,exports.scanExtensions=f;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["BACKEND_SURFACES","FRONTEND_SURFACES","BACKEND_PLATFORMS","FRONTEND_PLATFORMS","DEFAULT_ROUTING_ROOT","ROUTING_ROOT_ALIAS","fs","path","GENERATED_ENTRY_NAMES","classifySegment","ROUTED_SURFACES","GATE_SEGMENTS","parseFilename","ROOT_FILE_NAME"],"sources":["../../../src/services/scan/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n BACKEND_GROUP,\n BACKEND_PLATFORMS,\n BACKEND_SURFACES,\n DEFAULT_ROUTING_ROOT,\n FRONTEND_GROUP,\n FRONTEND_PLATFORMS,\n FRONTEND_SURFACES,\n GATE_SEGMENTS,\n GENERATED_ENTRY_NAMES,\n ROOT_FILE_NAME,\n ROUTE_FILE_NAME,\n ROUTED_SURFACES,\n ROUTING_ROOT_ALIAS,\n SESSION_SEGMENT,\n WORKSPACE_SEGMENT,\n} from '../../constants/layout';\nimport type {\n ExtensionEntry,\n ExtensionGate,\n ExtensionGraph,\n ExtensionNotice,\n ExtensionScope,\n ExtensionSide,\n RouteSegment,\n} from '../../types/extensionGraph';\nimport { type ParsedFilename, parseFilename } from '../filename';\nimport { classifySegment } from '../segments';\nimport type { ScanOptions } from './type';\n\ninterface WalkState {\n readonly scope: ExtensionScope;\n readonly side: ExtensionSide | undefined;\n readonly gates: readonly ExtensionGate[];\n readonly surface: string | undefined;\n readonly route: readonly RouteSegment[];\n /** A gate folder has been entered and the next plain segment names its id. */\n readonly pendingGate: string | undefined;\n}\n\nconst INITIAL: WalkState = {\n scope: 'global',\n side: undefined,\n gates: [],\n surface: undefined,\n route: [],\n pendingGate: undefined,\n};\n\nfunction surfacesFor(side: ExtensionSide): readonly string[] {\n return side === 'backend' ? BACKEND_SURFACES : FRONTEND_SURFACES;\n}\n\nfunction platformsFor(side: ExtensionSide, options: ScanOptions): readonly string[] {\n const configured = side === 'backend' ? options.platforms?.backend : options.platforms?.frontend;\n return configured ?? (side === 'backend' ? BACKEND_PLATFORMS : FRONTEND_PLATFORMS);\n}\n\n/** The nearest surface a misspelled folder probably meant, or undefined. */\nfunction nearestSurface(name: string, side: ExtensionSide): string | undefined {\n const surfaces = surfacesFor(side);\n if (surfaces.includes(name)) return undefined;\n const singular = name.endsWith('s') ? name.slice(0, -1) : `${name}s`;\n return surfaces.includes(singular) ? singular : undefined;\n}\n\n/** Reads cardinality only from the routed helper's options argument. */\nfunction routedCardinality(source: string): ExtensionEntry['cardinality'] | undefined {\n const declaration =\n /export\\s+default\\s+defineRoutedContribution\\s*\\([\\s\\S]*,\\s*\\{\\s*cardinality\\s*:\\s*['\"](optional|many|collection)['\"]/u.exec(\n source,\n );\n return declaration?.[1] as ExtensionEntry['cardinality'] | undefined;\n}\n\n/** Resolves the routing root, accepting the singular alias when only it exists. */\nexport function resolveRoutingRoot(packageDir: string, root: string | undefined): string | undefined {\n const candidates = root !== undefined ? [root] : [DEFAULT_ROUTING_ROOT, ROUTING_ROOT_ALIAS];\n return candidates.find((candidate) => fs.existsSync(path.join(packageDir, candidate)));\n}\n\n/**\n * Reads one package's routing root into a description of what it declares.\n *\n * Filesystem in, data out. No globbing, which is what keeps parenthesised\n * group folders safe: tsdown's globber would read a bare `(name)` as an\n * extglob group, and a directory walk never sees a pattern at all.\n *\n * Nothing here throws. A folder the convention does not recognise becomes a\n * notice and its subtree is skipped, so one bad directory never costs a\n * package its other contributions.\n */\nexport function scanExtensions(options: ScanOptions): ExtensionGraph {\n const root = resolveRoutingRoot(options.packageDir, options.root);\n if (root === undefined) return { root: options.root ?? DEFAULT_ROUTING_ROOT, entries: [], notices: [] };\n\n const backendGroup = options.sides?.backend ?? BACKEND_GROUP;\n const frontendGroup = options.sides?.frontend ?? FRONTEND_GROUP;\n const generated = options.generatedEntries ?? GENERATED_ENTRY_NAMES;\n\n const entries: ExtensionEntry[] = [];\n const notices: ExtensionNotice[] = [];\n const note = (relativePath: string, message: string): void => void notices.push({ path: relativePath, message });\n\n /**\n * Every public routed file names its platform, so a host is never inferred.\n *\n * Checked at each push rather than once at the top of readFile, because a\n * file sitting somewhere the convention does not define has a position\n * problem, and reporting the missing suffix instead would hide it.\n */\n const notePlatform = (relative: string, parsed: ParsedFilename, side: ExtensionSide): void => {\n if (parsed.platform !== undefined) return;\n note(\n relative,\n `names no platform; add one of ${platformsFor(side, options).join(', ')} before .${parsed.extension} (without one the host is inferred from the side rather than declared)`,\n );\n };\n\n const walk = (relativeDir: string, state: WalkState): void => {\n const absolute = path.join(options.packageDir, relativeDir);\n const listing = fs.readdirSync(absolute, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));\n\n for (const item of listing) {\n const relative = `${relativeDir}/${item.name}`;\n if (item.isDirectory()) walkDirectory(relative, item.name, state);\n else if (item.isFile()) readFile(relative, item.name, state);\n }\n };\n\n const walkDirectory = (relative: string, name: string, state: WalkState): void => {\n const segment = classifySegment(name);\n\n if (segment.kind === 'private') return;\n\n if (segment.kind === 'group') {\n if (state.side === undefined && segment.name === backendGroup)\n return walk(relative, { ...state, side: 'backend' });\n if (state.side === undefined && segment.name === frontendGroup)\n return walk(relative, { ...state, side: 'frontend' });\n return walk(relative, state);\n }\n\n if (segment.kind === 'dynamic') {\n if (state.surface !== undefined && ROUTED_SURFACES.includes(state.surface)) {\n return walk(relative, { ...state, route: [...state.route, { param: segment.param }] });\n }\n return note(relative, 'a dynamic segment is only meaningful inside a routed surface such as api/');\n }\n\n if (state.pendingGate !== undefined) {\n const gate: ExtensionGate = { kind: state.pendingGate, id: segment.name };\n return walk(relative, { ...state, gates: [...state.gates, gate], pendingGate: undefined });\n }\n\n if (state.surface !== undefined) {\n if (ROUTED_SURFACES.includes(state.surface)) {\n return walk(relative, { ...state, route: [...state.route, { literal: segment.name }] });\n }\n return note(relative, `${state.surface}/ takes files directly; put helpers in a _private folder`);\n }\n\n if (state.side === undefined) {\n if (segment.name === WORKSPACE_SEGMENT && state.scope === 'global') {\n return walk(relative, { ...state, scope: 'workspace' });\n }\n if (segment.name === SESSION_SEGMENT && state.scope === 'workspace') {\n return walk(relative, { ...state, scope: 'session' });\n }\n return note(relative, `expected a scope segment or a side group, such as (${backendGroup})`);\n }\n\n if (GATE_SEGMENTS.includes(segment.name)) return walk(relative, { ...state, pendingGate: segment.name });\n\n if (surfacesFor(state.side).includes(segment.name)) return walk(relative, { ...state, surface: segment.name });\n\n const nearest = nearestSurface(segment.name, state.side);\n return note(\n relative,\n nearest === undefined\n ? `'${segment.name}' is not a ${state.side} surface`\n : `'${segment.name}' is not a surface, did you mean '${nearest}'`,\n );\n };\n\n const readFile = (relative: string, fileName: string, state: WalkState): void => {\n if (state.side === undefined) {\n const base = fileName.slice(0, fileName.lastIndexOf('.'));\n if (state.scope === 'global' && generated.includes(base)) return;\n // A direct child of the routing root is a host entry, not a\n // contribution: either one this build generates, or one the package\n // hand-writes and names itself. doompi-profile ships a second Pi entry\n // the composition resolver activates by name, and a package that has\n // not adopted the layout has all of its entries here. A contribution\n // always sits at least one directory deep, inside a side group.\n if (state.scope === 'global') return;\n if (parseFilename(fileName, []) === undefined) return;\n return note(relative, 'a contribution needs a side group and a surface folder');\n }\n\n const parsed = parseFilename(fileName, platformsFor(state.side, options));\n if (parsed === undefined) return;\n const source = fs.readFileSync(path.join(options.packageDir, relative), 'utf8');\n const cardinality = routedCardinality(source);\n\n if (state.surface === undefined) {\n // Inside `mode/plan/`, a file called `mode.*` declares the gate itself\n // rather than a contribution gated by it, and the gate's id names it.\n const gate = state.gates[state.gates.length - 1];\n if (gate !== undefined && parsed.name === gate.kind) {\n notePlatform(relative, parsed, state.side);\n entries.push({\n file: relative,\n scope: state.scope,\n side: state.side,\n gates: state.gates.slice(0, -1),\n surface: gate.kind,\n route: [],\n name: gate.id,\n target: parsed.target,\n platform: parsed.platform,\n ...(cardinality ? { cardinality } : {}),\n role: 'contribution',\n });\n return;\n }\n\n // The scope's constructor. Not gated: it owns the whole subtree's\n // lifetime, and a gate that could switch it off would leave everything\n // below it holding a context nothing built.\n if (parsed.name === ROOT_FILE_NAME && gate === undefined) {\n notePlatform(relative, parsed, state.side);\n entries.push({\n file: relative,\n scope: state.scope,\n side: state.side,\n gates: [],\n surface: undefined,\n route: [],\n name: parsed.name,\n target: parsed.target,\n platform: parsed.platform,\n role: 'root',\n });\n return;\n }\n\n return note(\n relative,\n gate === undefined\n ? `only ${ROOT_FILE_NAME}.* is read at a side root; move each contribution into its named surface folder`\n : `inside ${gate.kind}/${gate.id}/ a contribution needs a surface folder, or name the file ${gate.kind}.* to declare the gate`,\n );\n }\n\n // Inside a routed surface only the leaf file is a route. Anything beside\n // it is colocation, which is the whole reason Next.js reserves the name.\n if (ROUTED_SURFACES.includes(state.surface) && parsed.name !== ROUTE_FILE_NAME) return;\n\n notePlatform(relative, parsed, state.side);\n entries.push({\n file: relative,\n scope: state.scope,\n side: state.side,\n gates: state.gates,\n surface: state.surface,\n route: state.route,\n name: parsed.name,\n target: parsed.target,\n platform: parsed.platform,\n ...(cardinality ? { cardinality } : {}),\n role: 'contribution',\n });\n };\n\n walk(root, INITIAL);\n return { root, entries, notices };\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { ExtensionGraph } from "../../types/extensionGraph.cjs";
2
+ import { ScanOptions } from "./type.cjs";
3
+ //#region src/services/scan/index.d.ts
4
+ /** Resolves the routing root, accepting the singular alias when only it exists. */
5
+ export declare function resolveRoutingRoot(packageDir: string, root: string | undefined): string | undefined;
6
+ /**
7
+ * Reads one package's routing root into a description of what it declares.
8
+ *
9
+ * Filesystem in, data out. No globbing, which is what keeps parenthesised
10
+ * group folders safe: tsdown's globber would read a bare `(name)` as an
11
+ * extglob group, and a directory walk never sees a pattern at all.
12
+ *
13
+ * Nothing here throws. A folder the convention does not recognise becomes a
14
+ * notice and its subtree is skipped, so one bad directory never costs a
15
+ * package its other contributions.
16
+ */
17
+ export declare function scanExtensions(options: ScanOptions): ExtensionGraph;
18
+ //#endregion
19
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/services/scan/index.ts"],"mappings":";;;;wBA+EgB,mBAAmB,oBAAoB;;;;;;;;;;;;wBAgBvC,eAAe,SAAS,cAAc"}
@@ -0,0 +1,19 @@
1
+ import { ExtensionGraph } from "../../types/extensionGraph.mjs";
2
+ import { ScanOptions } from "./type.mjs";
3
+ //#region src/services/scan/index.d.ts
4
+ /** Resolves the routing root, accepting the singular alias when only it exists. */
5
+ export declare function resolveRoutingRoot(packageDir: string, root: string | undefined): string | undefined;
6
+ /**
7
+ * Reads one package's routing root into a description of what it declares.
8
+ *
9
+ * Filesystem in, data out. No globbing, which is what keeps parenthesised
10
+ * group folders safe: tsdown's globber would read a bare `(name)` as an
11
+ * extglob group, and a directory walk never sees a pattern at all.
12
+ *
13
+ * Nothing here throws. A folder the convention does not recognise becomes a
14
+ * notice and its subtree is skipped, so one bad directory never costs a
15
+ * package its other contributions.
16
+ */
17
+ export declare function scanExtensions(options: ScanOptions): ExtensionGraph;
18
+ //#endregion
19
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/scan/index.ts"],"mappings":";;;;wBA+EgB,mBAAmB,oBAAoB;;;;;;;;;;;;wBAgBvC,eAAe,SAAS,cAAc"}
@@ -0,0 +1,2 @@
1
+ import{BACKEND_PLATFORMS as e,BACKEND_SURFACES as t,DEFAULT_ROUTING_ROOT as n,FRONTEND_PLATFORMS as r,FRONTEND_SURFACES as i,GATE_SEGMENTS as a,GENERATED_ENTRY_NAMES as o,ROOT_FILE_NAME as s,ROUTED_SURFACES as c,ROUTING_ROOT_ALIAS as l}from"../../constants/layout.mjs";import{parseFilename as u}from"../filename/index.mjs";import{classifySegment as d}from"../segments/index.mjs";import f from"node:fs";import p from"node:path";const m={scope:`global`,side:void 0,gates:[],surface:void 0,route:[],pendingGate:void 0};function h(e){return e===`backend`?t:i}function g(t,n){return(t===`backend`?n.platforms?.backend:n.platforms?.frontend)??(t===`backend`?e:r)}function _(e,t){let n=h(t);if(n.includes(e))return;let r=e.endsWith(`s`)?e.slice(0,-1):`${e}s`;return n.includes(r)?r:void 0}function v(e){return/export\s+default\s+defineRoutedContribution\s*\([\s\S]*,\s*\{\s*cardinality\s*:\s*['"](optional|many|collection)['"]/u.exec(e)?.[1]}function y(e,t){return(t===void 0?[n,l]:[t]).find(t=>f.existsSync(p.join(e,t)))}function b(e){let t=y(e.packageDir,e.root);if(t===void 0)return{root:e.root??`src/extensions`,entries:[],notices:[]};let n=e.sides?.backend??`backend`,r=e.sides?.frontend??`frontend`,i=e.generatedEntries??o,l=[],b=[],x=(e,t)=>void b.push({path:e,message:t}),S=(t,n,r)=>{n.platform===void 0&&x(t,`names no platform; add one of ${g(r,e).join(`, `)} before .${n.extension} (without one the host is inferred from the side rather than declared)`)},C=(t,n)=>{let r=p.join(e.packageDir,t),i=f.readdirSync(r,{withFileTypes:!0}).sort((e,t)=>e.name.localeCompare(t.name));for(let e of i){let r=`${t}/${e.name}`;e.isDirectory()?w(r,e.name,n):e.isFile()&&T(r,e.name,n)}},w=(e,t,i)=>{let o=d(t);if(o.kind===`private`)return;if(o.kind===`group`)return i.side===void 0&&o.name===n?C(e,{...i,side:`backend`}):i.side===void 0&&o.name===r?C(e,{...i,side:`frontend`}):C(e,i);if(o.kind===`dynamic`)return i.surface!==void 0&&c.includes(i.surface)?C(e,{...i,route:[...i.route,{param:o.param}]}):x(e,`a dynamic segment is only meaningful inside a routed surface such as api/`);if(i.pendingGate!==void 0){let t={kind:i.pendingGate,id:o.name};return C(e,{...i,gates:[...i.gates,t],pendingGate:void 0})}if(i.surface!==void 0)return c.includes(i.surface)?C(e,{...i,route:[...i.route,{literal:o.name}]}):x(e,`${i.surface}/ takes files directly; put helpers in a _private folder`);if(i.side===void 0)return o.name===`workspaces`&&i.scope===`global`?C(e,{...i,scope:`workspace`}):o.name===`sessions`&&i.scope===`workspace`?C(e,{...i,scope:`session`}):x(e,`expected a scope segment or a side group, such as (${n})`);if(a.includes(o.name))return C(e,{...i,pendingGate:o.name});if(h(i.side).includes(o.name))return C(e,{...i,surface:o.name});let s=_(o.name,i.side);return x(e,s===void 0?`'${o.name}' is not a ${i.side} surface`:`'${o.name}' is not a surface, did you mean '${s}'`)},T=(t,n,r)=>{if(r.side===void 0){let e=n.slice(0,n.lastIndexOf(`.`));return r.scope===`global`&&i.includes(e)||r.scope===`global`||u(n,[])===void 0?void 0:x(t,`a contribution needs a side group and a surface folder`)}let a=u(n,g(r.side,e));if(a===void 0)return;let o=v(f.readFileSync(p.join(e.packageDir,t),`utf8`));if(r.surface===void 0){let e=r.gates[r.gates.length-1];if(e!==void 0&&a.name===e.kind){S(t,a,r.side),l.push({file:t,scope:r.scope,side:r.side,gates:r.gates.slice(0,-1),surface:e.kind,route:[],name:e.id,target:a.target,platform:a.platform,...o?{cardinality:o}:{},role:`contribution`});return}if(a.name===`root`&&e===void 0){S(t,a,r.side),l.push({file:t,scope:r.scope,side:r.side,gates:[],surface:void 0,route:[],name:a.name,target:a.target,platform:a.platform,role:`root`});return}return x(t,e===void 0?`only ${s}.* is read at a side root; move each contribution into its named surface folder`:`inside ${e.kind}/${e.id}/ a contribution needs a surface folder, or name the file ${e.kind}.* to declare the gate`)}c.includes(r.surface)&&a.name!==`route`||(S(t,a,r.side),l.push({file:t,scope:r.scope,side:r.side,gates:r.gates,surface:r.surface,route:r.route,name:a.name,target:a.target,platform:a.platform,...o?{cardinality:o}:{},role:`contribution`}))};return C(t,m),{root:t,entries:l,notices:b}}export{y as resolveRoutingRoot,b as scanExtensions};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/services/scan/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n BACKEND_GROUP,\n BACKEND_PLATFORMS,\n BACKEND_SURFACES,\n DEFAULT_ROUTING_ROOT,\n FRONTEND_GROUP,\n FRONTEND_PLATFORMS,\n FRONTEND_SURFACES,\n GATE_SEGMENTS,\n GENERATED_ENTRY_NAMES,\n ROOT_FILE_NAME,\n ROUTE_FILE_NAME,\n ROUTED_SURFACES,\n ROUTING_ROOT_ALIAS,\n SESSION_SEGMENT,\n WORKSPACE_SEGMENT,\n} from '../../constants/layout';\nimport type {\n ExtensionEntry,\n ExtensionGate,\n ExtensionGraph,\n ExtensionNotice,\n ExtensionScope,\n ExtensionSide,\n RouteSegment,\n} from '../../types/extensionGraph';\nimport { type ParsedFilename, parseFilename } from '../filename';\nimport { classifySegment } from '../segments';\nimport type { ScanOptions } from './type';\n\ninterface WalkState {\n readonly scope: ExtensionScope;\n readonly side: ExtensionSide | undefined;\n readonly gates: readonly ExtensionGate[];\n readonly surface: string | undefined;\n readonly route: readonly RouteSegment[];\n /** A gate folder has been entered and the next plain segment names its id. */\n readonly pendingGate: string | undefined;\n}\n\nconst INITIAL: WalkState = {\n scope: 'global',\n side: undefined,\n gates: [],\n surface: undefined,\n route: [],\n pendingGate: undefined,\n};\n\nfunction surfacesFor(side: ExtensionSide): readonly string[] {\n return side === 'backend' ? BACKEND_SURFACES : FRONTEND_SURFACES;\n}\n\nfunction platformsFor(side: ExtensionSide, options: ScanOptions): readonly string[] {\n const configured = side === 'backend' ? options.platforms?.backend : options.platforms?.frontend;\n return configured ?? (side === 'backend' ? BACKEND_PLATFORMS : FRONTEND_PLATFORMS);\n}\n\n/** The nearest surface a misspelled folder probably meant, or undefined. */\nfunction nearestSurface(name: string, side: ExtensionSide): string | undefined {\n const surfaces = surfacesFor(side);\n if (surfaces.includes(name)) return undefined;\n const singular = name.endsWith('s') ? name.slice(0, -1) : `${name}s`;\n return surfaces.includes(singular) ? singular : undefined;\n}\n\n/** Reads cardinality only from the routed helper's options argument. */\nfunction routedCardinality(source: string): ExtensionEntry['cardinality'] | undefined {\n const declaration =\n /export\\s+default\\s+defineRoutedContribution\\s*\\([\\s\\S]*,\\s*\\{\\s*cardinality\\s*:\\s*['\"](optional|many|collection)['\"]/u.exec(\n source,\n );\n return declaration?.[1] as ExtensionEntry['cardinality'] | undefined;\n}\n\n/** Resolves the routing root, accepting the singular alias when only it exists. */\nexport function resolveRoutingRoot(packageDir: string, root: string | undefined): string | undefined {\n const candidates = root !== undefined ? [root] : [DEFAULT_ROUTING_ROOT, ROUTING_ROOT_ALIAS];\n return candidates.find((candidate) => fs.existsSync(path.join(packageDir, candidate)));\n}\n\n/**\n * Reads one package's routing root into a description of what it declares.\n *\n * Filesystem in, data out. No globbing, which is what keeps parenthesised\n * group folders safe: tsdown's globber would read a bare `(name)` as an\n * extglob group, and a directory walk never sees a pattern at all.\n *\n * Nothing here throws. A folder the convention does not recognise becomes a\n * notice and its subtree is skipped, so one bad directory never costs a\n * package its other contributions.\n */\nexport function scanExtensions(options: ScanOptions): ExtensionGraph {\n const root = resolveRoutingRoot(options.packageDir, options.root);\n if (root === undefined) return { root: options.root ?? DEFAULT_ROUTING_ROOT, entries: [], notices: [] };\n\n const backendGroup = options.sides?.backend ?? BACKEND_GROUP;\n const frontendGroup = options.sides?.frontend ?? FRONTEND_GROUP;\n const generated = options.generatedEntries ?? GENERATED_ENTRY_NAMES;\n\n const entries: ExtensionEntry[] = [];\n const notices: ExtensionNotice[] = [];\n const note = (relativePath: string, message: string): void => void notices.push({ path: relativePath, message });\n\n /**\n * Every public routed file names its platform, so a host is never inferred.\n *\n * Checked at each push rather than once at the top of readFile, because a\n * file sitting somewhere the convention does not define has a position\n * problem, and reporting the missing suffix instead would hide it.\n */\n const notePlatform = (relative: string, parsed: ParsedFilename, side: ExtensionSide): void => {\n if (parsed.platform !== undefined) return;\n note(\n relative,\n `names no platform; add one of ${platformsFor(side, options).join(', ')} before .${parsed.extension} (without one the host is inferred from the side rather than declared)`,\n );\n };\n\n const walk = (relativeDir: string, state: WalkState): void => {\n const absolute = path.join(options.packageDir, relativeDir);\n const listing = fs.readdirSync(absolute, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));\n\n for (const item of listing) {\n const relative = `${relativeDir}/${item.name}`;\n if (item.isDirectory()) walkDirectory(relative, item.name, state);\n else if (item.isFile()) readFile(relative, item.name, state);\n }\n };\n\n const walkDirectory = (relative: string, name: string, state: WalkState): void => {\n const segment = classifySegment(name);\n\n if (segment.kind === 'private') return;\n\n if (segment.kind === 'group') {\n if (state.side === undefined && segment.name === backendGroup)\n return walk(relative, { ...state, side: 'backend' });\n if (state.side === undefined && segment.name === frontendGroup)\n return walk(relative, { ...state, side: 'frontend' });\n return walk(relative, state);\n }\n\n if (segment.kind === 'dynamic') {\n if (state.surface !== undefined && ROUTED_SURFACES.includes(state.surface)) {\n return walk(relative, { ...state, route: [...state.route, { param: segment.param }] });\n }\n return note(relative, 'a dynamic segment is only meaningful inside a routed surface such as api/');\n }\n\n if (state.pendingGate !== undefined) {\n const gate: ExtensionGate = { kind: state.pendingGate, id: segment.name };\n return walk(relative, { ...state, gates: [...state.gates, gate], pendingGate: undefined });\n }\n\n if (state.surface !== undefined) {\n if (ROUTED_SURFACES.includes(state.surface)) {\n return walk(relative, { ...state, route: [...state.route, { literal: segment.name }] });\n }\n return note(relative, `${state.surface}/ takes files directly; put helpers in a _private folder`);\n }\n\n if (state.side === undefined) {\n if (segment.name === WORKSPACE_SEGMENT && state.scope === 'global') {\n return walk(relative, { ...state, scope: 'workspace' });\n }\n if (segment.name === SESSION_SEGMENT && state.scope === 'workspace') {\n return walk(relative, { ...state, scope: 'session' });\n }\n return note(relative, `expected a scope segment or a side group, such as (${backendGroup})`);\n }\n\n if (GATE_SEGMENTS.includes(segment.name)) return walk(relative, { ...state, pendingGate: segment.name });\n\n if (surfacesFor(state.side).includes(segment.name)) return walk(relative, { ...state, surface: segment.name });\n\n const nearest = nearestSurface(segment.name, state.side);\n return note(\n relative,\n nearest === undefined\n ? `'${segment.name}' is not a ${state.side} surface`\n : `'${segment.name}' is not a surface, did you mean '${nearest}'`,\n );\n };\n\n const readFile = (relative: string, fileName: string, state: WalkState): void => {\n if (state.side === undefined) {\n const base = fileName.slice(0, fileName.lastIndexOf('.'));\n if (state.scope === 'global' && generated.includes(base)) return;\n // A direct child of the routing root is a host entry, not a\n // contribution: either one this build generates, or one the package\n // hand-writes and names itself. doompi-profile ships a second Pi entry\n // the composition resolver activates by name, and a package that has\n // not adopted the layout has all of its entries here. A contribution\n // always sits at least one directory deep, inside a side group.\n if (state.scope === 'global') return;\n if (parseFilename(fileName, []) === undefined) return;\n return note(relative, 'a contribution needs a side group and a surface folder');\n }\n\n const parsed = parseFilename(fileName, platformsFor(state.side, options));\n if (parsed === undefined) return;\n const source = fs.readFileSync(path.join(options.packageDir, relative), 'utf8');\n const cardinality = routedCardinality(source);\n\n if (state.surface === undefined) {\n // Inside `mode/plan/`, a file called `mode.*` declares the gate itself\n // rather than a contribution gated by it, and the gate's id names it.\n const gate = state.gates[state.gates.length - 1];\n if (gate !== undefined && parsed.name === gate.kind) {\n notePlatform(relative, parsed, state.side);\n entries.push({\n file: relative,\n scope: state.scope,\n side: state.side,\n gates: state.gates.slice(0, -1),\n surface: gate.kind,\n route: [],\n name: gate.id,\n target: parsed.target,\n platform: parsed.platform,\n ...(cardinality ? { cardinality } : {}),\n role: 'contribution',\n });\n return;\n }\n\n // The scope's constructor. Not gated: it owns the whole subtree's\n // lifetime, and a gate that could switch it off would leave everything\n // below it holding a context nothing built.\n if (parsed.name === ROOT_FILE_NAME && gate === undefined) {\n notePlatform(relative, parsed, state.side);\n entries.push({\n file: relative,\n scope: state.scope,\n side: state.side,\n gates: [],\n surface: undefined,\n route: [],\n name: parsed.name,\n target: parsed.target,\n platform: parsed.platform,\n role: 'root',\n });\n return;\n }\n\n return note(\n relative,\n gate === undefined\n ? `only ${ROOT_FILE_NAME}.* is read at a side root; move each contribution into its named surface folder`\n : `inside ${gate.kind}/${gate.id}/ a contribution needs a surface folder, or name the file ${gate.kind}.* to declare the gate`,\n );\n }\n\n // Inside a routed surface only the leaf file is a route. Anything beside\n // it is colocation, which is the whole reason Next.js reserves the name.\n if (ROUTED_SURFACES.includes(state.surface) && parsed.name !== ROUTE_FILE_NAME) return;\n\n notePlatform(relative, parsed, state.side);\n entries.push({\n file: relative,\n scope: state.scope,\n side: state.side,\n gates: state.gates,\n surface: state.surface,\n route: state.route,\n name: parsed.name,\n target: parsed.target,\n platform: parsed.platform,\n ...(cardinality ? { cardinality } : {}),\n role: 'contribution',\n });\n };\n\n walk(root, INITIAL);\n return { root, entries, notices };\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ //#region src/services/scan/type.d.ts
2
+ /** What the scan needs to know before it walks a package. */
3
+ export interface ScanOptions {
4
+ /** Absolute path of the package being scanned. */
5
+ readonly packageDir: string;
6
+ /**
7
+ * Routing root, package-relative. Defaults to `src/extensions`, falling back
8
+ * to `src/extension` when only that one exists.
9
+ */
10
+ readonly root?: string;
11
+ /** Group folder names that select a build side. */
12
+ readonly sides?: {
13
+ readonly backend?: string;
14
+ readonly frontend?: string;
15
+ };
16
+ /** Recognised platform suffixes per side. */
17
+ readonly platforms?: {
18
+ readonly backend?: readonly string[];
19
+ readonly frontend?: readonly string[];
20
+ };
21
+ /** Base names of the generated entries at the root, which the scan skips. */
22
+ readonly generatedEntries?: readonly string[];
23
+ }
24
+ //#endregion
25
+ //# sourceMappingURL=type.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.cts","names":[],"sources":["../../../src/services/scan/type.ts"],"mappings":";;iBACiB;;WAEN;;;;;WAKA;;WAEA;aAAmB;aAA2B;;;WAE9C;aAAuB;aAAsC;;;WAE7D"}
@@ -0,0 +1,25 @@
1
+ //#region src/services/scan/type.d.ts
2
+ /** What the scan needs to know before it walks a package. */
3
+ export interface ScanOptions {
4
+ /** Absolute path of the package being scanned. */
5
+ readonly packageDir: string;
6
+ /**
7
+ * Routing root, package-relative. Defaults to `src/extensions`, falling back
8
+ * to `src/extension` when only that one exists.
9
+ */
10
+ readonly root?: string;
11
+ /** Group folder names that select a build side. */
12
+ readonly sides?: {
13
+ readonly backend?: string;
14
+ readonly frontend?: string;
15
+ };
16
+ /** Recognised platform suffixes per side. */
17
+ readonly platforms?: {
18
+ readonly backend?: readonly string[];
19
+ readonly frontend?: readonly string[];
20
+ };
21
+ /** Base names of the generated entries at the root, which the scan skips. */
22
+ readonly generatedEntries?: readonly string[];
23
+ }
24
+ //#endregion
25
+ //# sourceMappingURL=type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.mts","names":[],"sources":["../../../src/services/scan/type.ts"],"mappings":";;iBACiB;;WAEN;;;;;WAKA;;WAEA;aAAmB;aAA2B;;;WAE9C;aAAuB;aAAsC;;;WAE7D"}
@@ -0,0 +1,2 @@
1
+ function e(e,t,n){if(!(e.length<=t.length+n.length)&&e.startsWith(t)&&e.endsWith(n))return e.slice(t.length,e.length-n.length)}function t(t){if(t.startsWith(`_`))return{kind:`private`,name:t.slice(1)};let n=e(t,`(`,`)`);if(n!==void 0)return{kind:`group`,name:n};let r=e(t,`[`,`]`);if(r!==void 0){let e=r.startsWith(`...`);return{kind:`dynamic`,param:{name:e?r.slice(3):r,catchAll:e}}}return{kind:`plain`,name:t}}exports.classifySegment=t;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/services/segments/index.ts"],"sourcesContent":["import {\n CATCH_ALL_MARKER,\n DYNAMIC_PREFIX,\n DYNAMIC_SUFFIX,\n GROUP_PREFIX,\n GROUP_SUFFIX,\n PRIVATE_PREFIX,\n} from '../../constants/layout';\nimport type { RouteParam } from '../../types/extensionGraph';\n\n/**\n * What one folder name means, using Next.js spelling throughout.\n *\n * A group is organisational and invisible to the path. A private folder is\n * colocation and is never scanned. A dynamic segment is a route parameter.\n * Everything else is a plain segment whose meaning depends on where it sits.\n */\nexport type Segment =\n | { readonly kind: 'group'; readonly name: string }\n | { readonly kind: 'private'; readonly name: string }\n | { readonly kind: 'dynamic'; readonly param: RouteParam }\n | { readonly kind: 'plain'; readonly name: string };\n\nfunction wrapped(segment: string, open: string, close: string): string | undefined {\n if (segment.length <= open.length + close.length) return undefined;\n if (!segment.startsWith(open) || !segment.endsWith(close)) return undefined;\n return segment.slice(open.length, segment.length - close.length);\n}\n\n/** Classifies one folder name. Never throws: an unusable name is simply plain. */\nexport function classifySegment(segment: string): Segment {\n if (segment.startsWith(PRIVATE_PREFIX)) return { kind: 'private', name: segment.slice(PRIVATE_PREFIX.length) };\n\n const group = wrapped(segment, GROUP_PREFIX, GROUP_SUFFIX);\n if (group !== undefined) return { kind: 'group', name: group };\n\n const dynamic = wrapped(segment, DYNAMIC_PREFIX, DYNAMIC_SUFFIX);\n if (dynamic !== undefined) {\n const catchAll = dynamic.startsWith(CATCH_ALL_MARKER);\n return {\n kind: 'dynamic',\n param: { name: catchAll ? dynamic.slice(CATCH_ALL_MARKER.length) : dynamic, catchAll },\n };\n }\n\n return { kind: 'plain', name: segment };\n}\n"],"mappings":"AAuBA,SAAS,EAAQ,EAAiB,EAAc,EAAmC,CAC7E,OAAQ,QAAU,EAAK,OAAS,EAAM,SACrC,EAAQ,WAAW,CAAI,GAAM,EAAQ,SAAS,CAAK,EACxD,OAAO,EAAQ,MAAM,EAAK,OAAQ,EAAQ,OAAS,EAAM,MAAM,CACjE,CAGA,SAAgB,EAAgB,EAA0B,CACxD,GAAI,EAAQ,WAAA,GAAyB,EAAG,MAAO,CAAE,KAAM,UAAW,KAAM,EAAQ,MAAA,CAA2B,CAAE,EAE7G,IAAM,EAAQ,EAAQ,EAAA,IAAA,GAAmC,EACzD,GAAI,IAAU,IAAA,GAAW,MAAO,CAAE,KAAM,QAAS,KAAM,CAAM,EAE7D,IAAM,EAAU,EAAQ,EAAA,IAAA,GAAuC,EAC/D,GAAI,IAAY,IAAA,GAAW,CACzB,IAAM,EAAW,EAAQ,WAAA,KAA2B,EACpD,MAAO,CACL,KAAM,UACN,MAAO,CAAE,KAAM,EAAW,EAAQ,MAAA,CAA6B,EAAI,EAAS,UAAS,CACvF,CACF,CAEA,MAAO,CAAE,KAAM,QAAS,KAAM,CAAQ,CACxC"}
@@ -0,0 +1,26 @@
1
+ import { RouteParam } from "../../types/extensionGraph.cjs";
2
+ //#region src/services/segments/index.d.ts
3
+ /**
4
+ * What one folder name means, using Next.js spelling throughout.
5
+ *
6
+ * A group is organisational and invisible to the path. A private folder is
7
+ * colocation and is never scanned. A dynamic segment is a route parameter.
8
+ * Everything else is a plain segment whose meaning depends on where it sits.
9
+ */
10
+ export type Segment = {
11
+ readonly kind: 'group';
12
+ readonly name: string;
13
+ } | {
14
+ readonly kind: 'private';
15
+ readonly name: string;
16
+ } | {
17
+ readonly kind: 'dynamic';
18
+ readonly param: RouteParam;
19
+ } | {
20
+ readonly kind: 'plain';
21
+ readonly name: string;
22
+ };
23
+ /** Classifies one folder name. Never throws: an unusable name is simply plain. */
24
+ export declare function classifySegment(segment: string): Segment;
25
+ //#endregion
26
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/services/segments/index.ts"],"mappings":";;;;;;;;;YAiBY;WACG;WAAwB;;WACxB;WAA0B;;WAC1B;WAA0B,OAAO;;WACjC;WAAwB;;;wBASvB,gBAAgB,kBAAkB"}
@@ -0,0 +1,26 @@
1
+ import { RouteParam } from "../../types/extensionGraph.mjs";
2
+ //#region src/services/segments/index.d.ts
3
+ /**
4
+ * What one folder name means, using Next.js spelling throughout.
5
+ *
6
+ * A group is organisational and invisible to the path. A private folder is
7
+ * colocation and is never scanned. A dynamic segment is a route parameter.
8
+ * Everything else is a plain segment whose meaning depends on where it sits.
9
+ */
10
+ export type Segment = {
11
+ readonly kind: 'group';
12
+ readonly name: string;
13
+ } | {
14
+ readonly kind: 'private';
15
+ readonly name: string;
16
+ } | {
17
+ readonly kind: 'dynamic';
18
+ readonly param: RouteParam;
19
+ } | {
20
+ readonly kind: 'plain';
21
+ readonly name: string;
22
+ };
23
+ /** Classifies one folder name. Never throws: an unusable name is simply plain. */
24
+ export declare function classifySegment(segment: string): Segment;
25
+ //#endregion
26
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/segments/index.ts"],"mappings":";;;;;;;;;YAiBY;WACG;WAAwB;;WACxB;WAA0B;;WAC1B;WAA0B,OAAO;;WACjC;WAAwB;;;wBASvB,gBAAgB,kBAAkB"}
@@ -0,0 +1,2 @@
1
+ function e(e,t,n){if(!(e.length<=t.length+n.length)&&e.startsWith(t)&&e.endsWith(n))return e.slice(t.length,e.length-n.length)}function t(t){if(t.startsWith(`_`))return{kind:`private`,name:t.slice(1)};let n=e(t,`(`,`)`);if(n!==void 0)return{kind:`group`,name:n};let r=e(t,`[`,`]`);if(r!==void 0){let e=r.startsWith(`...`);return{kind:`dynamic`,param:{name:e?r.slice(3):r,catchAll:e}}}return{kind:`plain`,name:t}}export{t as classifySegment};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/services/segments/index.ts"],"sourcesContent":["import {\n CATCH_ALL_MARKER,\n DYNAMIC_PREFIX,\n DYNAMIC_SUFFIX,\n GROUP_PREFIX,\n GROUP_SUFFIX,\n PRIVATE_PREFIX,\n} from '../../constants/layout';\nimport type { RouteParam } from '../../types/extensionGraph';\n\n/**\n * What one folder name means, using Next.js spelling throughout.\n *\n * A group is organisational and invisible to the path. A private folder is\n * colocation and is never scanned. A dynamic segment is a route parameter.\n * Everything else is a plain segment whose meaning depends on where it sits.\n */\nexport type Segment =\n | { readonly kind: 'group'; readonly name: string }\n | { readonly kind: 'private'; readonly name: string }\n | { readonly kind: 'dynamic'; readonly param: RouteParam }\n | { readonly kind: 'plain'; readonly name: string };\n\nfunction wrapped(segment: string, open: string, close: string): string | undefined {\n if (segment.length <= open.length + close.length) return undefined;\n if (!segment.startsWith(open) || !segment.endsWith(close)) return undefined;\n return segment.slice(open.length, segment.length - close.length);\n}\n\n/** Classifies one folder name. Never throws: an unusable name is simply plain. */\nexport function classifySegment(segment: string): Segment {\n if (segment.startsWith(PRIVATE_PREFIX)) return { kind: 'private', name: segment.slice(PRIVATE_PREFIX.length) };\n\n const group = wrapped(segment, GROUP_PREFIX, GROUP_SUFFIX);\n if (group !== undefined) return { kind: 'group', name: group };\n\n const dynamic = wrapped(segment, DYNAMIC_PREFIX, DYNAMIC_SUFFIX);\n if (dynamic !== undefined) {\n const catchAll = dynamic.startsWith(CATCH_ALL_MARKER);\n return {\n kind: 'dynamic',\n param: { name: catchAll ? dynamic.slice(CATCH_ALL_MARKER.length) : dynamic, catchAll },\n };\n }\n\n return { kind: 'plain', name: segment };\n}\n"],"mappings":"AAuBA,SAAS,EAAQ,EAAiB,EAAc,EAAmC,CAC7E,OAAQ,QAAU,EAAK,OAAS,EAAM,SACrC,EAAQ,WAAW,CAAI,GAAM,EAAQ,SAAS,CAAK,EACxD,OAAO,EAAQ,MAAM,EAAK,OAAQ,EAAQ,OAAS,EAAM,MAAM,CACjE,CAGA,SAAgB,EAAgB,EAA0B,CACxD,GAAI,EAAQ,WAAA,GAAyB,EAAG,MAAO,CAAE,KAAM,UAAW,KAAM,EAAQ,MAAA,CAA2B,CAAE,EAE7G,IAAM,EAAQ,EAAQ,EAAA,IAAA,GAAmC,EACzD,GAAI,IAAU,IAAA,GAAW,MAAO,CAAE,KAAM,QAAS,KAAM,CAAM,EAE7D,IAAM,EAAU,EAAQ,EAAA,IAAA,GAAuC,EAC/D,GAAI,IAAY,IAAA,GAAW,CACzB,IAAM,EAAW,EAAQ,WAAA,KAA2B,EACpD,MAAO,CACL,KAAM,UACN,MAAO,CAAE,KAAM,EAAW,EAAQ,MAAA,CAA6B,EAAI,EAAS,UAAS,CACvF,CACF,CAEA,MAAO,CAAE,KAAM,QAAS,KAAM,CAAQ,CACxC"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../constants/layout.cjs"),n=require("../identity/index.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);const a=[`global`,`workspace`,`session`],o=`src/exports/apiContracts.ts`,s=`./extensions/web`,c=`./dist/extensions/web.mjs`;function l(e){let t=e.entries.filter(e=>e.side===`backend`);if(t.length===0)return[];let n=Math.min(...t.map(e=>a.indexOf(e.scope)));return a.slice(n)}function u(e){let t=new Set(e.entries.filter(e=>e.side===`frontend`).map(e=>e.scope));return a.filter(e=>t.has(e))}function d(e,t){return[...new Set(e.entries.filter(e=>e.side===`frontend`&&e.surface===`channel`).map(e=>n.toSnake(e.name)))].sort()}function f(e,t){let n={};for(let[a,o]of Object.entries(t)){if(a===`./package.json`||typeof o!=`object`||!o){n[a]=o;continue}let t=o,s=t.import??t.default,c=s?.endsWith(`.mjs`)===!0?`${s.slice(0,-4)}.d.mts`:void 0;n[a]=t.types===void 0&&c!==void 0&&r.default.existsSync(i.default.join(e,c))?{types:c,...t}:t}return n}function p(e){let{packageDir:n,graph:a,targets:p,pluginId:m}=e,h={...e.manifest};if(h.exports!==void 0&&typeof h.exports==`object`){let e=f(n,h.exports);p.includes(`web`)?e[s]={import:c}:delete e[s],h.exports=Object.fromEntries(Object.entries(e).sort(([e],[t])=>e===`./package.json`?1:t===`./package.json`?-1:e.localeCompare(t)))}if(p.includes(`cli`)?h.pi={extensions:[`./dist/extensions/pi.mjs`]}:delete h.pi,p.includes(`server`)){let e=r.default.existsSync(i.default.join(n,o))?{contracts:{entry:`./${o}`,dist:`./dist/api-contracts.mjs`}}:{};h.doompiServer={entry:`./${t.GENERATED_DIR}/server.ts`,dist:`./dist/extensions/server.mjs`,scopes:l(a),...e}}else delete h.doompiServer;return p.includes(`web`)?h.doompiWeb={pluginId:m,channels:d(a,h),client:c,scopes:u(a)}:delete h.doompiWeb,h}function m(e){let t=i.default.join(e.packageDir,`package.json`),n=`${JSON.stringify(p(e),null,2)}\n`;return r.default.readFileSync(t,`utf8`)!==n&&(r.default.writeFileSync(t,n),!0)}exports.syncManifest=p,exports.writeManifest=m;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["toSnake","fs","path","GENERATED_DIR"],"sources":["../../../src/services/syncManifest/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { GENERATED_DIR } from '../../constants/layout';\nimport type { ExtensionGraph, ExtensionScope } from '../../types/extensionGraph';\nimport { toSnake } from '../identity';\nimport type { BuildTarget } from '../resolveTarget/type';\nimport type { ManifestSync } from './type';\n\nconst SCOPE_ORDER: readonly ExtensionScope[] = ['global', 'workspace', 'session'];\n\nconst API_CONTRACTS_SOURCE = 'src/exports/apiContracts.ts';\nconst API_CONTRACTS_DIST = './dist/api-contracts.mjs';\n\nconst WEB_EXPORT = './extensions/web';\nconst WEB_DIST = './dist/extensions/web.mjs';\n\n/**\n * Which scopes a server facet declares.\n *\n * The generated facet cascades: a contribution declared at global registers at\n * global, workspace and session alike, because the server reads exactly one\n * scope declaration and nothing above it. So the declared set runs from the\n * shallowest contribution downward.\n */\nfunction serverScopes(graph: ExtensionGraph): ExtensionScope[] {\n const declared = graph.entries.filter((entry) => entry.side === 'backend');\n if (declared.length === 0) return [];\n const shallowest = Math.min(...declared.map((entry) => SCOPE_ORDER.indexOf(entry.scope)));\n return SCOPE_ORDER.slice(shallowest);\n}\n\n/**\n * Which scopes a cockpit plugin declares.\n *\n * The cockpit does not cascade the way the server does. It merges the scopes\n * it is given, so a plugin declares exactly the ones it contributes to.\n */\nfunction webScopes(graph: ExtensionGraph): ExtensionScope[] {\n const declared = new Set(graph.entries.filter((entry) => entry.side === 'frontend').map((entry) => entry.scope));\n return SCOPE_ORDER.filter((scope) => declared.has(scope));\n}\n\n/** The frame types the browser half claims, which the cockpit reads before loading it. */\nfunction channels(graph: ExtensionGraph, _manifest: Record<string, unknown>): string[] {\n return [\n ...new Set(\n graph.entries\n .filter((entry) => entry.side === 'frontend' && entry.surface === 'channel')\n .map((entry) => toSnake(entry.name)),\n ),\n ].sort();\n}\n\n/**\n * Restores the `types` condition tsdown's exports generation omits.\n *\n * tsdown derives the map from the built chunks, which is what makes it follow\n * the folder tree. But its `genSubExport` only ever emits `import`, `require`\n * or `default`: a declaration chunk contributes nothing to a subpath, so only\n * the root gets a top-level `types`. TypeScript still resolves a sibling\n * `.d.mts` under node16 and bundler resolution, but publint and attw read the\n * condition, so it belongs in the map.\n */\nfunction withTypeConditions(packageDir: string, exportsMap: Record<string, unknown>): Record<string, unknown> {\n const next: Record<string, unknown> = {};\n for (const [subpath, target] of Object.entries(exportsMap)) {\n if (subpath === './package.json' || target === null || typeof target !== 'object') {\n next[subpath] = target;\n continue;\n }\n const conditions = target as Record<string, string>;\n const esm = conditions.import ?? conditions.default;\n const types = esm?.endsWith('.mjs') === true ? `${esm.slice(0, -'.mjs'.length)}.d.mts` : undefined;\n // Ahead of import and require: a resolver takes the first condition it\n // matches, so a types-aware one must not fall through to the runtime file.\n next[subpath] =\n conditions.types === undefined && types !== undefined && fs.existsSync(path.join(packageDir, types))\n ? { types, ...conditions }\n : conditions;\n }\n return next;\n}\n\n/**\n * Everything the manifest can say about a package that its tree already says.\n *\n * The three host blocks are pure restatements of the folder tree: which hosts\n * the package contributes to, at which scopes, and which frame types the\n * browser claims. Leaving them hand-written is what made a package declare the\n * same thing in six places, so the build writes them.\n *\n * Returns the manifest rather than writing it, so a caller can diff it and a\n * test can read it without a filesystem.\n */\nexport function syncManifest(input: ManifestSync): Record<string, unknown> {\n const { packageDir, graph, targets, pluginId } = input;\n const manifest = { ...input.manifest };\n\n if (manifest.exports !== undefined && typeof manifest.exports === 'object') {\n const exportsMap = withTypeConditions(packageDir, manifest.exports as Record<string, unknown>);\n // The browser bundle is a real entry point now that it is built rather\n // than shipped as source, so it is exported like the other two. Import\n // only: its own config emits ESM and no declarations, because nothing\n // imports it as a typed module.\n //\n // Added from the target rather than from the file existing, because this\n // runs on the node build's build:done and the browser build follows it.\n if (targets.includes('web')) exportsMap[WEB_EXPORT] = { import: WEB_DIST };\n else delete exportsMap[WEB_EXPORT];\n manifest.exports = Object.fromEntries(\n Object.entries(exportsMap).sort(([left], [right]) =>\n left === './package.json' ? 1 : right === './package.json' ? -1 : left.localeCompare(right),\n ),\n );\n }\n\n if (targets.includes('cli')) manifest.pi = { extensions: ['./dist/extensions/pi.mjs'] };\n else delete manifest.pi;\n\n if (targets.includes('server')) {\n const contracts = fs.existsSync(path.join(packageDir, API_CONTRACTS_SOURCE))\n ? { contracts: { entry: `./${API_CONTRACTS_SOURCE}`, dist: API_CONTRACTS_DIST } }\n : {};\n manifest.doompiServer = {\n entry: `./${GENERATED_DIR}/server.ts`,\n dist: './dist/extensions/server.mjs',\n scopes: serverScopes(graph),\n ...contracts,\n };\n } else delete manifest.doompiServer;\n\n if (targets.includes('web')) {\n manifest.doompiWeb = {\n pluginId,\n channels: channels(graph, manifest),\n // The built browser bundle, not the source. Its own tsdown config\n // targets a browser and leaves every bare specifier external, so the\n // cockpit resolves one React rather than bundling a copy per plugin.\n client: WEB_DIST,\n scopes: webScopes(graph),\n };\n } else delete manifest.doompiWeb;\n\n return manifest;\n}\n\n/** Writes the synced manifest when it differs, so a second build is a no-op. */\nexport function writeManifest(input: ManifestSync & { readonly targets: readonly BuildTarget[] }): boolean {\n const manifestPath = path.join(input.packageDir, 'package.json');\n const next = `${JSON.stringify(syncManifest(input), null, 2)}\\n`;\n if (fs.readFileSync(manifestPath, 'utf8') === next) return false;\n fs.writeFileSync(manifestPath, next);\n return true;\n}\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { BuildTarget } from "../resolveTarget/type.cjs";
2
+ import { ManifestSync } from "./type.cjs";
3
+ //#region src/services/syncManifest/index.d.ts
4
+ /**
5
+ * Everything the manifest can say about a package that its tree already says.
6
+ *
7
+ * The three host blocks are pure restatements of the folder tree: which hosts
8
+ * the package contributes to, at which scopes, and which frame types the
9
+ * browser claims. Leaving them hand-written is what made a package declare the
10
+ * same thing in six places, so the build writes them.
11
+ *
12
+ * Returns the manifest rather than writing it, so a caller can diff it and a
13
+ * test can read it without a filesystem.
14
+ */
15
+ export declare function syncManifest(input: ManifestSync): Record<string, unknown>;
16
+ /** Writes the synced manifest when it differs, so a second build is a no-op. */
17
+ export declare function writeManifest(input: ManifestSync & {
18
+ readonly targets: readonly BuildTarget[];
19
+ }): boolean;
20
+ //#endregion
21
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../../../src/services/syncManifest/index.ts"],"mappings":";;;;;;;;;;;;;;wBA+FgB,aAAa,OAAO,eAAe;;wBAqDnC,cAAc,OAAO;WAA0B,kBAAkB"}