@agimon-ai/po-build 0.0.0-alpha.45 → 0.0.0-alpha.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/generate/index.cjs.map +1 -1
- package/dist/services/generate/index.d.mts.map +1 -1
- package/dist/services/generate/index.mjs.map +1 -1
- package/dist/services/scan/index.cjs.map +1 -1
- package/dist/services/scan/index.d.mts.map +1 -1
- package/dist/services/scan/index.mjs.map +1 -1
- package/dist/services/syncManifest/index.cjs.map +1 -1
- package/dist/services/syncManifest/index.d.mts.map +1 -1
- package/dist/services/syncManifest/index.mjs.map +1 -1
- package/dist/services/tsdownPreset/index.cjs +1 -1
- package/dist/services/tsdownPreset/index.cjs.map +1 -1
- package/dist/services/tsdownPreset/index.d.mts +1 -1
- package/dist/services/tsdownPreset/index.d.mts.map +1 -1
- package/dist/services/tsdownPreset/index.mjs +1 -1
- package/dist/services/tsdownPreset/index.mjs.map +1 -1
- package/dist/services/unistylesBabel/index.cjs +1 -1
- package/dist/services/unistylesBabel/index.cjs.map +1 -1
- package/dist/services/unistylesBabel/index.mjs +1 -1
- package/dist/services/unistylesBabel/index.mjs.map +1 -1
- package/dist/services/writeGenerated/index.cjs.map +1 -1
- package/dist/services/writeGenerated/index.d.mts.map +1 -1
- package/dist/services/writeGenerated/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["fs","path","renderServerEntry","renderWebEntry","renderNativeEntry","renderMcpEntry","GENERATED_ENTRY_FILENAMES","scanExtensions","renderMcpWidgets","GENERATED_DIR","BUILD_TARGET_PLATFORMS","RETIRED_PLATFORM_NOTICE","resolveTarget","renderApiClient","GENERATED_CLIENT_NAME","renderNativeApiClient","GENERATED_NATIVE_CLIENT_NAME","API_ROUTES_MODULE","GENERATED_ENTRY_NAMES","writeGenerated","require_runtime","require_layout","require_index","require_index$1","require_index$2","require_index$3","require_index$4","require_index$5","node_fs","node_path","defaultPluginId","packageName","readPackageName","manifest","packageDir","RENDERERS","ENTRY_FILENAME","generateExtension","options","pluginId","graph","notices","files","targets","mcpWidgets","entry","retired","notice","requestedTargets","target","resolution","render","source","hasRouteTable","hasBrowserHalf","client","hasNativeHalf","nativeClient","managed","name"],"sources":["../../../src/services/generate/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n API_ROUTES_MODULE,\n BUILD_TARGET_PLATFORMS,\n GENERATED_CLIENT_NAME,\n GENERATED_DIR,\n GENERATED_ENTRY_FILENAMES,\n GENERATED_ENTRY_NAMES,\n GENERATED_NATIVE_CLIENT_NAME,\n} from '../../constants/layout';\nimport type { ExtensionGraph, ExtensionNotice } from '../../types/extensionGraph';\nimport { renderApiClient, renderNativeApiClient } from '../renderClient';\nimport { renderMcpEntry, renderNativeEntry, renderServerEntry, renderWebEntry } from '../renderEntry';\nimport { renderMcpWidgets } from '../renderMcpWidgets';\nimport { resolveTarget } from '../resolveTarget';\nimport type { BuildTarget } from '../resolveTarget/type';\nimport { RETIRED_PLATFORM_NOTICE, scanExtensions } from '../scan';\nimport { writeGenerated } from '../writeGenerated';\nimport type { GenerateOptions, GenerateResult } from './type';\n\n/** `@agimon-ai/po-plan` becomes `plan`. */\nexport function defaultPluginId(packageName: string): string {\n return packageName.replace(/^@[^/]+\\//u, '').replace(/^po-/u, '');\n}\n\nfunction readPackageName(packageDir: string): string {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name !== 'string') throw new Error(`${packageDir}/package.json has no name`);\n return manifest.name;\n}\n\nconst RENDERERS: Readonly<Record<BuildTarget, typeof renderServerEntry>> = {\n server: renderServerEntry,\n web: renderWebEntry,\n native: renderNativeEntry,\n mcp: renderMcpEntry,\n};\n\nconst ENTRY_FILENAME: Readonly<Record<BuildTarget, string>> = GENERATED_ENTRY_FILENAMES;\n\n/**\n * Scans a package and renders every entry its tree calls for.\n *\n * A target with nothing in it produces no file, so a backend-only package\n * never grows an empty cockpit entry. Notices from the scan and from each\n * resolution are gathered rather than thrown: an unrecognised folder costs\n * that folder, never the build.\n */\nexport function generateExtension(options: GenerateOptions): GenerateResult {\n const packageName = options.packageName ?? readPackageName(options.packageDir);\n const pluginId = options.pluginId ?? defaultPluginId(packageName);\n\n const graph: ExtensionGraph = scanExtensions(options);\n const notices: ExtensionNotice[] = [...graph.notices];\n const files = new Map<string, string>();\n const targets: BuildTarget[] = [];\n const mcpWidgets = options.target === 'mcp' ? renderMcpWidgets(graph, packageName, GENERATED_DIR) : undefined;\n if (mcpWidgets?.source !== undefined) files.set(`${GENERATED_DIR}/mcp-ui.ts`, mcpWidgets.source);\n\n // Here rather than in the scan, deliberately. The poRoutedFilePosition lint\n // rule calls scanExtensions() directly, so a scan notice would make `.ios` a\n // hard preflight failure and cost us the forward-looking platforms we kept on\n // purpose. resolveTarget runs once per target, which would repeat it per target.\n for (const entry of graph.entries) {\n if (entry.platform === undefined || BUILD_TARGET_PLATFORMS.includes(entry.platform)) continue;\n notices.push({\n path: entry.file,\n message: `'${entry.platform}' is a forward-looking platform with no build target; this file is not emitted for server, web, native or mcp`,\n });\n }\n\n const retired = graph.notices.filter((notice) => notice.message === RETIRED_PLATFORM_NOTICE);\n if (retired.length > 0) {\n throw new Error(\n `${packageName}: ${RETIRED_PLATFORM_NOTICE}\\n${retired.map((notice) => ` ${notice.path}`).join('\\n')}`,\n );\n }\n\n const requestedTargets = options.target === undefined ? (['server', 'web', 'native'] as const) : [options.target];\n for (const target of requestedTargets) {\n const resolution = resolveTarget(graph, target);\n notices.push(...resolution.notices);\n if (resolution.contributions.length === 0 && resolution.escapeHatches.length === 0 && resolution.roots.length === 0)\n continue;\n\n targets.push(target);\n const render = RENDERERS[target];\n const source = render(resolution, {\n packageName,\n pluginId,\n root: graph.root,\n entryDir: GENERATED_DIR,\n mcpWidgets: mcpWidgets?.declarations,\n });\n files.set(`${GENERATED_DIR}/${ENTRY_FILENAME[target]}.ts`, source);\n }\n\n // Not another build target: no host loads it as an entry, and adding one to\n // the target loop would mean a resolution pass and a contribution field for\n // something that is only ever imported by a routed frontend file.\n //\n // Only for a package that has a browser half. The client exists to be called\n // from a page and it carries the sealed transport, so emitting one into a\n // node-only package asks that package to depend on a browser runtime it never\n // loads, to satisfy a file nothing imports.\n const hasRouteTable = options.target === undefined && fs.existsSync(path.join(options.packageDir, API_ROUTES_MODULE));\n const hasBrowserHalf = targets.includes('web');\n const client =\n hasRouteTable && hasBrowserHalf\n ? renderApiClient(graph, { packageName, pluginId, root: graph.root, entryDir: GENERATED_DIR })\n : undefined;\n if (client !== undefined) files.set(`${GENERATED_DIR}/${GENERATED_CLIENT_NAME}.ts`, client);\n\n // The native half gets its own file rather than a shared one, because the\n // browser client's two imports are exactly what the native host refuses.\n const hasNativeHalf = targets.includes('native');\n const nativeClient =\n hasRouteTable && hasNativeHalf\n ? renderNativeApiClient(graph, { packageName, pluginId, root: graph.root, entryDir: GENERATED_DIR })\n : undefined;\n if (nativeClient !== undefined) files.set(`${GENERATED_DIR}/${GENERATED_NATIVE_CLIENT_NAME}.ts`, nativeClient);\n if (hasRouteTable && (hasBrowserHalf || hasNativeHalf) && client === undefined && nativeClient === undefined) {\n notices.push({\n path: API_ROUTES_MODULE,\n message: 'declares API routes, but no (backend)/api/<base-path>/ folder says where they are mounted',\n });\n }\n\n const managed =\n options.target === undefined\n ? GENERATED_ENTRY_NAMES.filter(\n (name) => name !== 'mcp' || !fs.existsSync(path.join(options.packageDir, 'tsdown.mcp.config.ts')),\n ).map((name) => `${GENERATED_DIR}/${name}.ts`)\n : [\n `${GENERATED_DIR}/${ENTRY_FILENAME[options.target]}.ts`,\n ...(options.target === 'mcp' ? [`${GENERATED_DIR}/mcp-ui.ts`] : []),\n ];\n const { changed } = writeGenerated(options.packageDir, files, options.check, managed);\n return { graph, packageName, pluginId, files, changed, notices, targets };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["fs","path","renderServerEntry","renderWebEntry","renderNativeEntry","renderMcpEntry","GENERATED_ENTRY_FILENAMES","scanExtensions","renderMcpWidgets","GENERATED_DIR","BUILD_TARGET_PLATFORMS","RETIRED_PLATFORM_NOTICE","resolveTarget","renderApiClient","GENERATED_CLIENT_NAME","renderNativeApiClient","GENERATED_NATIVE_CLIENT_NAME","API_ROUTES_MODULE","GENERATED_ENTRY_NAMES","writeGenerated","require_runtime","require_layout","require_index","require_index$1","require_index$2","require_index$3","require_index$4","require_index$5","node_fs","node_path","defaultPluginId","packageName","readPackageName","manifest","packageDir","RENDERERS","ENTRY_FILENAME","generateExtension","options","pluginId","graph","notices","files","targets","mcpWidgets","entry","retired","notice","requestedTargets","target","resolution","render","source","hasRouteTable","hasBrowserHalf","client","hasNativeHalf","nativeClient","managed","name"],"sources":["../../../src/services/generate/index.ts"],"sourcesContent":["// sync: Public generateExtension contract is synchronous, deferred with its cross-package callers to phase 2.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n API_ROUTES_MODULE,\n BUILD_TARGET_PLATFORMS,\n GENERATED_CLIENT_NAME,\n GENERATED_DIR,\n GENERATED_ENTRY_FILENAMES,\n GENERATED_ENTRY_NAMES,\n GENERATED_NATIVE_CLIENT_NAME,\n} from '../../constants/layout';\nimport type { ExtensionGraph, ExtensionNotice } from '../../types/extensionGraph';\nimport { renderApiClient, renderNativeApiClient } from '../renderClient';\nimport { renderMcpEntry, renderNativeEntry, renderServerEntry, renderWebEntry } from '../renderEntry';\nimport { renderMcpWidgets } from '../renderMcpWidgets';\nimport { resolveTarget } from '../resolveTarget';\nimport type { BuildTarget } from '../resolveTarget/type';\nimport { RETIRED_PLATFORM_NOTICE, scanExtensions } from '../scan';\nimport { writeGenerated } from '../writeGenerated';\nimport type { GenerateOptions, GenerateResult } from './type';\n\n/** `@agimon-ai/po-plan` becomes `plan`. */\nexport function defaultPluginId(packageName: string): string {\n return packageName.replace(/^@[^/]+\\//u, '').replace(/^po-/u, '');\n}\n\nfunction readPackageName(packageDir: string): string {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name !== 'string') throw new Error(`${packageDir}/package.json has no name`);\n return manifest.name;\n}\n\nconst RENDERERS: Readonly<Record<BuildTarget, typeof renderServerEntry>> = {\n server: renderServerEntry,\n web: renderWebEntry,\n native: renderNativeEntry,\n mcp: renderMcpEntry,\n};\n\nconst ENTRY_FILENAME: Readonly<Record<BuildTarget, string>> = GENERATED_ENTRY_FILENAMES;\n\n/**\n * Scans a package and renders every entry its tree calls for.\n *\n * A target with nothing in it produces no file, so a backend-only package\n * never grows an empty cockpit entry. Notices from the scan and from each\n * resolution are gathered rather than thrown: an unrecognised folder costs\n * that folder, never the build.\n */\nexport function generateExtension(options: GenerateOptions): GenerateResult {\n const packageName = options.packageName ?? readPackageName(options.packageDir);\n const pluginId = options.pluginId ?? defaultPluginId(packageName);\n\n const graph: ExtensionGraph = scanExtensions(options);\n const notices: ExtensionNotice[] = [...graph.notices];\n const files = new Map<string, string>();\n const targets: BuildTarget[] = [];\n const mcpWidgets = options.target === 'mcp' ? renderMcpWidgets(graph, packageName, GENERATED_DIR) : undefined;\n if (mcpWidgets?.source !== undefined) files.set(`${GENERATED_DIR}/mcp-ui.ts`, mcpWidgets.source);\n\n // Here rather than in the scan, deliberately. The poRoutedFilePosition lint\n // rule calls scanExtensions() directly, so a scan notice would make `.ios` a\n // hard preflight failure and cost us the forward-looking platforms we kept on\n // purpose. resolveTarget runs once per target, which would repeat it per target.\n for (const entry of graph.entries) {\n if (entry.platform === undefined || BUILD_TARGET_PLATFORMS.includes(entry.platform)) continue;\n notices.push({\n path: entry.file,\n message: `'${entry.platform}' is a forward-looking platform with no build target; this file is not emitted for server, web, native or mcp`,\n });\n }\n\n const retired = graph.notices.filter((notice) => notice.message === RETIRED_PLATFORM_NOTICE);\n if (retired.length > 0) {\n throw new Error(\n `${packageName}: ${RETIRED_PLATFORM_NOTICE}\\n${retired.map((notice) => ` ${notice.path}`).join('\\n')}`,\n );\n }\n\n const requestedTargets = options.target === undefined ? (['server', 'web', 'native'] as const) : [options.target];\n for (const target of requestedTargets) {\n const resolution = resolveTarget(graph, target);\n notices.push(...resolution.notices);\n if (resolution.contributions.length === 0 && resolution.escapeHatches.length === 0 && resolution.roots.length === 0)\n continue;\n\n targets.push(target);\n const render = RENDERERS[target];\n const source = render(resolution, {\n packageName,\n pluginId,\n root: graph.root,\n entryDir: GENERATED_DIR,\n mcpWidgets: mcpWidgets?.declarations,\n });\n files.set(`${GENERATED_DIR}/${ENTRY_FILENAME[target]}.ts`, source);\n }\n\n // Not another build target: no host loads it as an entry, and adding one to\n // the target loop would mean a resolution pass and a contribution field for\n // something that is only ever imported by a routed frontend file.\n //\n // Only for a package that has a browser half. The client exists to be called\n // from a page and it carries the sealed transport, so emitting one into a\n // node-only package asks that package to depend on a browser runtime it never\n // loads, to satisfy a file nothing imports.\n const hasRouteTable = options.target === undefined && fs.existsSync(path.join(options.packageDir, API_ROUTES_MODULE));\n const hasBrowserHalf = targets.includes('web');\n const client =\n hasRouteTable && hasBrowserHalf\n ? renderApiClient(graph, { packageName, pluginId, root: graph.root, entryDir: GENERATED_DIR })\n : undefined;\n if (client !== undefined) files.set(`${GENERATED_DIR}/${GENERATED_CLIENT_NAME}.ts`, client);\n\n // The native half gets its own file rather than a shared one, because the\n // browser client's two imports are exactly what the native host refuses.\n const hasNativeHalf = targets.includes('native');\n const nativeClient =\n hasRouteTable && hasNativeHalf\n ? renderNativeApiClient(graph, { packageName, pluginId, root: graph.root, entryDir: GENERATED_DIR })\n : undefined;\n if (nativeClient !== undefined) files.set(`${GENERATED_DIR}/${GENERATED_NATIVE_CLIENT_NAME}.ts`, nativeClient);\n if (hasRouteTable && (hasBrowserHalf || hasNativeHalf) && client === undefined && nativeClient === undefined) {\n notices.push({\n path: API_ROUTES_MODULE,\n message: 'declares API routes, but no (backend)/api/<base-path>/ folder says where they are mounted',\n });\n }\n\n const managed =\n options.target === undefined\n ? GENERATED_ENTRY_NAMES.filter(\n (name) => name !== 'mcp' || !fs.existsSync(path.join(options.packageDir, 'tsdown.mcp.config.ts')),\n ).map((name) => `${GENERATED_DIR}/${name}.ts`)\n : [\n `${GENERATED_DIR}/${ENTRY_FILENAME[options.target]}.ts`,\n ...(options.target === 'mcp' ? [`${GENERATED_DIR}/mcp-ui.ts`] : []),\n ];\n const { changed } = writeGenerated(options.packageDir, files, options.check, managed);\n return { graph, packageName, pluginId, files, changed, notices, targets };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/generate/index.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/generate/index.ts"],"mappings":";;;wBAwBgB,gBAAgB;;;;;;;;;wBA2BhB,kBAAkB,SAAS,kBAAkB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs","path","defaultPluginId","packageName","readPackageName","manifest","packageDir","RENDERERS","renderServerEntry","renderWebEntry","renderNativeEntry","renderMcpEntry","ENTRY_FILENAME","GENERATED_ENTRY_FILENAMES","generateExtension","options","pluginId","graph","scanExtensions","notices","files","targets","mcpWidgets","renderMcpWidgets","GENERATED_DIR","entry","BUILD_TARGET_PLATFORMS","retired","notice","RETIRED_PLATFORM_NOTICE","requestedTargets","target","resolution","resolveTarget","render","source","hasRouteTable","hasBrowserHalf","client","renderApiClient","GENERATED_CLIENT_NAME","hasNativeHalf","nativeClient","renderNativeApiClient","GENERATED_NATIVE_CLIENT_NAME","API_ROUTES_MODULE","managed","GENERATED_ENTRY_NAMES","name","writeGenerated"],"sources":["../../../src/services/generate/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n API_ROUTES_MODULE,\n BUILD_TARGET_PLATFORMS,\n GENERATED_CLIENT_NAME,\n GENERATED_DIR,\n GENERATED_ENTRY_FILENAMES,\n GENERATED_ENTRY_NAMES,\n GENERATED_NATIVE_CLIENT_NAME,\n} from '../../constants/layout';\nimport type { ExtensionGraph, ExtensionNotice } from '../../types/extensionGraph';\nimport { renderApiClient, renderNativeApiClient } from '../renderClient';\nimport { renderMcpEntry, renderNativeEntry, renderServerEntry, renderWebEntry } from '../renderEntry';\nimport { renderMcpWidgets } from '../renderMcpWidgets';\nimport { resolveTarget } from '../resolveTarget';\nimport type { BuildTarget } from '../resolveTarget/type';\nimport { RETIRED_PLATFORM_NOTICE, scanExtensions } from '../scan';\nimport { writeGenerated } from '../writeGenerated';\nimport type { GenerateOptions, GenerateResult } from './type';\n\n/** `@agimon-ai/po-plan` becomes `plan`. */\nexport function defaultPluginId(packageName: string): string {\n return packageName.replace(/^@[^/]+\\//u, '').replace(/^po-/u, '');\n}\n\nfunction readPackageName(packageDir: string): string {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name !== 'string') throw new Error(`${packageDir}/package.json has no name`);\n return manifest.name;\n}\n\nconst RENDERERS: Readonly<Record<BuildTarget, typeof renderServerEntry>> = {\n server: renderServerEntry,\n web: renderWebEntry,\n native: renderNativeEntry,\n mcp: renderMcpEntry,\n};\n\nconst ENTRY_FILENAME: Readonly<Record<BuildTarget, string>> = GENERATED_ENTRY_FILENAMES;\n\n/**\n * Scans a package and renders every entry its tree calls for.\n *\n * A target with nothing in it produces no file, so a backend-only package\n * never grows an empty cockpit entry. Notices from the scan and from each\n * resolution are gathered rather than thrown: an unrecognised folder costs\n * that folder, never the build.\n */\nexport function generateExtension(options: GenerateOptions): GenerateResult {\n const packageName = options.packageName ?? readPackageName(options.packageDir);\n const pluginId = options.pluginId ?? defaultPluginId(packageName);\n\n const graph: ExtensionGraph = scanExtensions(options);\n const notices: ExtensionNotice[] = [...graph.notices];\n const files = new Map<string, string>();\n const targets: BuildTarget[] = [];\n const mcpWidgets = options.target === 'mcp' ? renderMcpWidgets(graph, packageName, GENERATED_DIR) : undefined;\n if (mcpWidgets?.source !== undefined) files.set(`${GENERATED_DIR}/mcp-ui.ts`, mcpWidgets.source);\n\n // Here rather than in the scan, deliberately. The poRoutedFilePosition lint\n // rule calls scanExtensions() directly, so a scan notice would make `.ios` a\n // hard preflight failure and cost us the forward-looking platforms we kept on\n // purpose. resolveTarget runs once per target, which would repeat it per target.\n for (const entry of graph.entries) {\n if (entry.platform === undefined || BUILD_TARGET_PLATFORMS.includes(entry.platform)) continue;\n notices.push({\n path: entry.file,\n message: `'${entry.platform}' is a forward-looking platform with no build target; this file is not emitted for server, web, native or mcp`,\n });\n }\n\n const retired = graph.notices.filter((notice) => notice.message === RETIRED_PLATFORM_NOTICE);\n if (retired.length > 0) {\n throw new Error(\n `${packageName}: ${RETIRED_PLATFORM_NOTICE}\\n${retired.map((notice) => ` ${notice.path}`).join('\\n')}`,\n );\n }\n\n const requestedTargets = options.target === undefined ? (['server', 'web', 'native'] as const) : [options.target];\n for (const target of requestedTargets) {\n const resolution = resolveTarget(graph, target);\n notices.push(...resolution.notices);\n if (resolution.contributions.length === 0 && resolution.escapeHatches.length === 0 && resolution.roots.length === 0)\n continue;\n\n targets.push(target);\n const render = RENDERERS[target];\n const source = render(resolution, {\n packageName,\n pluginId,\n root: graph.root,\n entryDir: GENERATED_DIR,\n mcpWidgets: mcpWidgets?.declarations,\n });\n files.set(`${GENERATED_DIR}/${ENTRY_FILENAME[target]}.ts`, source);\n }\n\n // Not another build target: no host loads it as an entry, and adding one to\n // the target loop would mean a resolution pass and a contribution field for\n // something that is only ever imported by a routed frontend file.\n //\n // Only for a package that has a browser half. The client exists to be called\n // from a page and it carries the sealed transport, so emitting one into a\n // node-only package asks that package to depend on a browser runtime it never\n // loads, to satisfy a file nothing imports.\n const hasRouteTable = options.target === undefined && fs.existsSync(path.join(options.packageDir, API_ROUTES_MODULE));\n const hasBrowserHalf = targets.includes('web');\n const client =\n hasRouteTable && hasBrowserHalf\n ? renderApiClient(graph, { packageName, pluginId, root: graph.root, entryDir: GENERATED_DIR })\n : undefined;\n if (client !== undefined) files.set(`${GENERATED_DIR}/${GENERATED_CLIENT_NAME}.ts`, client);\n\n // The native half gets its own file rather than a shared one, because the\n // browser client's two imports are exactly what the native host refuses.\n const hasNativeHalf = targets.includes('native');\n const nativeClient =\n hasRouteTable && hasNativeHalf\n ? renderNativeApiClient(graph, { packageName, pluginId, root: graph.root, entryDir: GENERATED_DIR })\n : undefined;\n if (nativeClient !== undefined) files.set(`${GENERATED_DIR}/${GENERATED_NATIVE_CLIENT_NAME}.ts`, nativeClient);\n if (hasRouteTable && (hasBrowserHalf || hasNativeHalf) && client === undefined && nativeClient === undefined) {\n notices.push({\n path: API_ROUTES_MODULE,\n message: 'declares API routes, but no (backend)/api/<base-path>/ folder says where they are mounted',\n });\n }\n\n const managed =\n options.target === undefined\n ? GENERATED_ENTRY_NAMES.filter(\n (name) => name !== 'mcp' || !fs.existsSync(path.join(options.packageDir, 'tsdown.mcp.config.ts')),\n ).map((name) => `${GENERATED_DIR}/${name}.ts`)\n : [\n `${GENERATED_DIR}/${ENTRY_FILENAME[options.target]}.ts`,\n ...(options.target === 'mcp' ? [`${GENERATED_DIR}/mcp-ui.ts`] : []),\n ];\n const { changed } = writeGenerated(options.packageDir, files, options.check, managed);\n return { graph, packageName, pluginId, files, changed, notices, targets };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fs","path","defaultPluginId","packageName","readPackageName","manifest","packageDir","RENDERERS","renderServerEntry","renderWebEntry","renderNativeEntry","renderMcpEntry","ENTRY_FILENAME","GENERATED_ENTRY_FILENAMES","generateExtension","options","pluginId","graph","scanExtensions","notices","files","targets","mcpWidgets","renderMcpWidgets","GENERATED_DIR","entry","BUILD_TARGET_PLATFORMS","retired","notice","RETIRED_PLATFORM_NOTICE","requestedTargets","target","resolution","resolveTarget","render","source","hasRouteTable","hasBrowserHalf","client","renderApiClient","GENERATED_CLIENT_NAME","hasNativeHalf","nativeClient","renderNativeApiClient","GENERATED_NATIVE_CLIENT_NAME","API_ROUTES_MODULE","managed","GENERATED_ENTRY_NAMES","name","writeGenerated"],"sources":["../../../src/services/generate/index.ts"],"sourcesContent":["// sync: Public generateExtension contract is synchronous, deferred with its cross-package callers to phase 2.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n API_ROUTES_MODULE,\n BUILD_TARGET_PLATFORMS,\n GENERATED_CLIENT_NAME,\n GENERATED_DIR,\n GENERATED_ENTRY_FILENAMES,\n GENERATED_ENTRY_NAMES,\n GENERATED_NATIVE_CLIENT_NAME,\n} from '../../constants/layout';\nimport type { ExtensionGraph, ExtensionNotice } from '../../types/extensionGraph';\nimport { renderApiClient, renderNativeApiClient } from '../renderClient';\nimport { renderMcpEntry, renderNativeEntry, renderServerEntry, renderWebEntry } from '../renderEntry';\nimport { renderMcpWidgets } from '../renderMcpWidgets';\nimport { resolveTarget } from '../resolveTarget';\nimport type { BuildTarget } from '../resolveTarget/type';\nimport { RETIRED_PLATFORM_NOTICE, scanExtensions } from '../scan';\nimport { writeGenerated } from '../writeGenerated';\nimport type { GenerateOptions, GenerateResult } from './type';\n\n/** `@agimon-ai/po-plan` becomes `plan`. */\nexport function defaultPluginId(packageName: string): string {\n return packageName.replace(/^@[^/]+\\//u, '').replace(/^po-/u, '');\n}\n\nfunction readPackageName(packageDir: string): string {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name !== 'string') throw new Error(`${packageDir}/package.json has no name`);\n return manifest.name;\n}\n\nconst RENDERERS: Readonly<Record<BuildTarget, typeof renderServerEntry>> = {\n server: renderServerEntry,\n web: renderWebEntry,\n native: renderNativeEntry,\n mcp: renderMcpEntry,\n};\n\nconst ENTRY_FILENAME: Readonly<Record<BuildTarget, string>> = GENERATED_ENTRY_FILENAMES;\n\n/**\n * Scans a package and renders every entry its tree calls for.\n *\n * A target with nothing in it produces no file, so a backend-only package\n * never grows an empty cockpit entry. Notices from the scan and from each\n * resolution are gathered rather than thrown: an unrecognised folder costs\n * that folder, never the build.\n */\nexport function generateExtension(options: GenerateOptions): GenerateResult {\n const packageName = options.packageName ?? readPackageName(options.packageDir);\n const pluginId = options.pluginId ?? defaultPluginId(packageName);\n\n const graph: ExtensionGraph = scanExtensions(options);\n const notices: ExtensionNotice[] = [...graph.notices];\n const files = new Map<string, string>();\n const targets: BuildTarget[] = [];\n const mcpWidgets = options.target === 'mcp' ? renderMcpWidgets(graph, packageName, GENERATED_DIR) : undefined;\n if (mcpWidgets?.source !== undefined) files.set(`${GENERATED_DIR}/mcp-ui.ts`, mcpWidgets.source);\n\n // Here rather than in the scan, deliberately. The poRoutedFilePosition lint\n // rule calls scanExtensions() directly, so a scan notice would make `.ios` a\n // hard preflight failure and cost us the forward-looking platforms we kept on\n // purpose. resolveTarget runs once per target, which would repeat it per target.\n for (const entry of graph.entries) {\n if (entry.platform === undefined || BUILD_TARGET_PLATFORMS.includes(entry.platform)) continue;\n notices.push({\n path: entry.file,\n message: `'${entry.platform}' is a forward-looking platform with no build target; this file is not emitted for server, web, native or mcp`,\n });\n }\n\n const retired = graph.notices.filter((notice) => notice.message === RETIRED_PLATFORM_NOTICE);\n if (retired.length > 0) {\n throw new Error(\n `${packageName}: ${RETIRED_PLATFORM_NOTICE}\\n${retired.map((notice) => ` ${notice.path}`).join('\\n')}`,\n );\n }\n\n const requestedTargets = options.target === undefined ? (['server', 'web', 'native'] as const) : [options.target];\n for (const target of requestedTargets) {\n const resolution = resolveTarget(graph, target);\n notices.push(...resolution.notices);\n if (resolution.contributions.length === 0 && resolution.escapeHatches.length === 0 && resolution.roots.length === 0)\n continue;\n\n targets.push(target);\n const render = RENDERERS[target];\n const source = render(resolution, {\n packageName,\n pluginId,\n root: graph.root,\n entryDir: GENERATED_DIR,\n mcpWidgets: mcpWidgets?.declarations,\n });\n files.set(`${GENERATED_DIR}/${ENTRY_FILENAME[target]}.ts`, source);\n }\n\n // Not another build target: no host loads it as an entry, and adding one to\n // the target loop would mean a resolution pass and a contribution field for\n // something that is only ever imported by a routed frontend file.\n //\n // Only for a package that has a browser half. The client exists to be called\n // from a page and it carries the sealed transport, so emitting one into a\n // node-only package asks that package to depend on a browser runtime it never\n // loads, to satisfy a file nothing imports.\n const hasRouteTable = options.target === undefined && fs.existsSync(path.join(options.packageDir, API_ROUTES_MODULE));\n const hasBrowserHalf = targets.includes('web');\n const client =\n hasRouteTable && hasBrowserHalf\n ? renderApiClient(graph, { packageName, pluginId, root: graph.root, entryDir: GENERATED_DIR })\n : undefined;\n if (client !== undefined) files.set(`${GENERATED_DIR}/${GENERATED_CLIENT_NAME}.ts`, client);\n\n // The native half gets its own file rather than a shared one, because the\n // browser client's two imports are exactly what the native host refuses.\n const hasNativeHalf = targets.includes('native');\n const nativeClient =\n hasRouteTable && hasNativeHalf\n ? renderNativeApiClient(graph, { packageName, pluginId, root: graph.root, entryDir: GENERATED_DIR })\n : undefined;\n if (nativeClient !== undefined) files.set(`${GENERATED_DIR}/${GENERATED_NATIVE_CLIENT_NAME}.ts`, nativeClient);\n if (hasRouteTable && (hasBrowserHalf || hasNativeHalf) && client === undefined && nativeClient === undefined) {\n notices.push({\n path: API_ROUTES_MODULE,\n message: 'declares API routes, but no (backend)/api/<base-path>/ folder says where they are mounted',\n });\n }\n\n const managed =\n options.target === undefined\n ? GENERATED_ENTRY_NAMES.filter(\n (name) => name !== 'mcp' || !fs.existsSync(path.join(options.packageDir, 'tsdown.mcp.config.ts')),\n ).map((name) => `${GENERATED_DIR}/${name}.ts`)\n : [\n `${GENERATED_DIR}/${ENTRY_FILENAME[options.target]}.ts`,\n ...(options.target === 'mcp' ? [`${GENERATED_DIR}/mcp-ui.ts`] : []),\n ];\n const { changed } = writeGenerated(options.packageDir, files, options.check, managed);\n return { graph, packageName, pluginId, files, changed, notices, targets };\n}\n"],"mappings":"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"}
|
|
@@ -1 +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","require_runtime","require_layout","require_index","require_index$1","node_fs","node_path","INITIAL","surfacesFor","side","platformsFor","options","nearestSurface","surfaces","name","singular","routedCardinality","source","resolveRoutingRoot","root","packageDir","candidate","RETIRED_PLATFORM_NOTICE","scanExtensions","backendGroup","frontendGroup","generated","entries","notices","note","relativePath","notePlatform","parsed","relative","walk","absolute","relativeDir","listing","a","b","item","walkDirectory","state","readFile","segment","gate","nearest","base","fileName","platforms","cardinality"],"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 RETIRED_PLATFORM,\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 */\n/** Reported for a routed file that names the retired terminal platform; generate refuses it. */\nexport const RETIRED_PLATFORM_NOTICE = `the .${RETIRED_PLATFORM} platform is retired: Po has no terminal host, so move this file to .server or delete it`;\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. A package that has not adopted the\n // layout has all of its native host 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 platforms = platformsFor(state.side, options);\n const parsed = parseFilename(fileName, [...platforms, 'mcp', RETIRED_PLATFORM]);\n if (parsed === undefined) return;\n if (parsed.platform === RETIRED_PLATFORM) return note(relative, RETIRED_PLATFORM_NOTICE);\n if (\n parsed.platform === 'mcp' &&\n (state.scope !== 'session' ||\n (state.side === 'frontend'\n ? state.surface !== 'tool' || !fileName.endsWith('.mcp.tsx')\n : state.surface !== 'tool' && state.surface !== 'skill' && state.surface !== 'resource'))\n ) {\n return note(\n relative,\n 'MCP declarations belong in a session backend tool/, skill/, resource/, or frontend tool/*.mcp.tsx surface',\n );\n }\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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"}
|
|
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","require_runtime","require_layout","require_index","require_index$1","node_fs","node_path","INITIAL","surfacesFor","side","platformsFor","options","nearestSurface","surfaces","name","singular","routedCardinality","source","resolveRoutingRoot","root","packageDir","candidate","RETIRED_PLATFORM_NOTICE","scanExtensions","backendGroup","frontendGroup","generated","entries","notices","note","relativePath","notePlatform","parsed","relative","walk","absolute","relativeDir","listing","a","b","item","walkDirectory","state","readFile","segment","gate","nearest","base","fileName","platforms","cardinality"],"sources":["../../../src/services/scan/index.ts"],"sourcesContent":["// sync: Public scanExtensions/resolveRoutingRoot contracts are deferred with cross-package callers to phase 2.\nimport 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 RETIRED_PLATFORM,\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 */\n/** Reported for a routed file that names the retired terminal platform; generate refuses it. */\nexport const RETIRED_PLATFORM_NOTICE = `the .${RETIRED_PLATFORM} platform is retired: Po has no terminal host, so move this file to .server or delete it`;\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. A package that has not adopted the\n // layout has all of its native host 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 platforms = platformsFor(state.side, options);\n const parsed = parseFilename(fileName, [...platforms, 'mcp', RETIRED_PLATFORM]);\n if (parsed === undefined) return;\n if (parsed.platform === RETIRED_PLATFORM) return note(relative, RETIRED_PLATFORM_NOTICE);\n if (\n parsed.platform === 'mcp' &&\n (state.scope !== 'session' ||\n (state.side === 'frontend'\n ? state.surface !== 'tool' || !fileName.endsWith('.mcp.tsx')\n : state.surface !== 'tool' && state.surface !== 'skill' && state.surface !== 'resource'))\n ) {\n return note(\n relative,\n 'MCP declarations belong in a session backend tool/, skill/, resource/, or frontend tool/*.mcp.tsx surface',\n );\n }\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/scan/index.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/scan/index.ts"],"mappings":";;;;wBAiFgB,mBAAmB,oBAAoB;wBAmBvC,eAAe,SAAS,cAAc"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs","path","INITIAL","surfacesFor","side","BACKEND_SURFACES","FRONTEND_SURFACES","platformsFor","options","BACKEND_PLATFORMS","FRONTEND_PLATFORMS","nearestSurface","surfaces","name","singular","routedCardinality","source","resolveRoutingRoot","root","DEFAULT_ROUTING_ROOT","ROUTING_ROOT_ALIAS","packageDir","candidate","RETIRED_PLATFORM_NOTICE","scanExtensions","backendGroup","frontendGroup","generated","GENERATED_ENTRY_NAMES","entries","notices","note","relativePath","notePlatform","parsed","relative","walk","absolute","relativeDir","listing","a","b","item","walkDirectory","state","readFile","segment","classifySegment","ROUTED_SURFACES","gate","GATE_SEGMENTS","nearest","base","fileName","parseFilename","platforms","cardinality","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 RETIRED_PLATFORM,\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 */\n/** Reported for a routed file that names the retired terminal platform; generate refuses it. */\nexport const RETIRED_PLATFORM_NOTICE = `the .${RETIRED_PLATFORM} platform is retired: Po has no terminal host, so move this file to .server or delete it`;\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. A package that has not adopted the\n // layout has all of its native host 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 platforms = platformsFor(state.side, options);\n const parsed = parseFilename(fileName, [...platforms, 'mcp', RETIRED_PLATFORM]);\n if (parsed === undefined) return;\n if (parsed.platform === RETIRED_PLATFORM) return note(relative, RETIRED_PLATFORM_NOTICE);\n if (\n parsed.platform === 'mcp' &&\n (state.scope !== 'session' ||\n (state.side === 'frontend'\n ? state.surface !== 'tool' || !fileName.endsWith('.mcp.tsx')\n : state.surface !== 'tool' && state.surface !== 'skill' && state.surface !== 'resource'))\n ) {\n return note(\n relative,\n 'MCP declarations belong in a session backend tool/, skill/, resource/, or frontend tool/*.mcp.tsx surface',\n );\n }\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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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fs","path","INITIAL","surfacesFor","side","BACKEND_SURFACES","FRONTEND_SURFACES","platformsFor","options","BACKEND_PLATFORMS","FRONTEND_PLATFORMS","nearestSurface","surfaces","name","singular","routedCardinality","source","resolveRoutingRoot","root","DEFAULT_ROUTING_ROOT","ROUTING_ROOT_ALIAS","packageDir","candidate","RETIRED_PLATFORM_NOTICE","scanExtensions","backendGroup","frontendGroup","generated","GENERATED_ENTRY_NAMES","entries","notices","note","relativePath","notePlatform","parsed","relative","walk","absolute","relativeDir","listing","a","b","item","walkDirectory","state","readFile","segment","classifySegment","ROUTED_SURFACES","gate","GATE_SEGMENTS","nearest","base","fileName","parseFilename","platforms","cardinality","ROOT_FILE_NAME"],"sources":["../../../src/services/scan/index.ts"],"sourcesContent":["// sync: Public scanExtensions/resolveRoutingRoot contracts are deferred with cross-package callers to phase 2.\nimport 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 RETIRED_PLATFORM,\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 */\n/** Reported for a routed file that names the retired terminal platform; generate refuses it. */\nexport const RETIRED_PLATFORM_NOTICE = `the .${RETIRED_PLATFORM} platform is retired: Po has no terminal host, so move this file to .server or delete it`;\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. A package that has not adopted the\n // layout has all of its native host 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 platforms = platformsFor(state.side, options);\n const parsed = parseFilename(fileName, [...platforms, 'mcp', RETIRED_PLATFORM]);\n if (parsed === undefined) return;\n if (parsed.platform === RETIRED_PLATFORM) return note(relative, RETIRED_PLATFORM_NOTICE);\n if (\n parsed.platform === 'mcp' &&\n (state.scope !== 'session' ||\n (state.side === 'frontend'\n ? state.surface !== 'tool' || !fileName.endsWith('.mcp.tsx')\n : state.surface !== 'tool' && state.surface !== 'skill' && state.surface !== 'resource'))\n ) {\n return note(\n relative,\n 'MCP declarations belong in a session backend tool/, skill/, resource/, or frontend tool/*.mcp.tsx surface',\n );\n }\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["GENERATED_DIR","toSnake","fs","path","SERVER_DIST","require_runtime","require_layout","require_index","node_fs","node_path","SCOPE_ORDER","API_CONTRACTS_ENTRY","API_CONTRACTS_SOURCE","WEB_EXPORT","WEB_DIST","NATIVE_EXPORT","NATIVE_DIST","MCP_ENTRY","serverScopes","declared","graph","entry","shallowest","frontendEntries","host","frontendScopes","entries","scope","channels","withTypeConditions","next","subpath","target","exportsMap","conditions","esm","types","packageDir","syncManifest","input","manifest","targets","left","right","contracts","provides","injects","syncMcpManifest","enabled","widgets","writeManifest","manifestPath","writeMcpManifest"],"sources":["../../../src/services/syncManifest/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { GENERATED_DIR, NATIVE_PLATFORM, SERVER_DIST } from '../../constants/layout';\nimport type { ExtensionEntry, 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_ENTRY = 'apiContracts';\nconst API_CONTRACTS_SOURCE = `src/exports/${API_CONTRACTS_ENTRY}.ts`;\n\n/** The retired terminal entry, removed from any manifest built before the terminal host was. */\nconst PI_EXPORT = './extensions/pi';\nconst WEB_EXPORT = './extensions/web';\nconst WEB_DIST = './dist/extensions/web.mjs';\nconst NATIVE_EXPORT = './extensions/native';\nconst NATIVE_DIST = './dist/extensions/native.mjs';\nconst MCP_DIST = './dist/extensions/mcp.mjs';\nconst MCP_ENTRY = `./${GENERATED_DIR}/mcp.ts`;\n\n/**\n * Which scopes a server plugin declares.\n *\n * The generated plugin 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 * The frontend files one frontend host reads.\n *\n * Native reads only `.native` files, and the cockpit never reads them, so the\n * two hosts' blocks describe disjoint halves of the same `(frontend)` side.\n */\nfunction frontendEntries(graph: ExtensionGraph, host: 'web' | 'native'): ExtensionEntry[] {\n return graph.entries.filter(\n (entry) => entry.side === 'frontend' && (entry.platform === NATIVE_PLATFORM) === (host === NATIVE_PLATFORM),\n );\n}\n\n/**\n * Which scopes a frontend 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. The\n * native app reuses the same registry, so the same holds there.\n */\nfunction frontendScopes(entries: readonly ExtensionEntry[]): ExtensionScope[] {\n const declared = new Set(entries.map((entry) => entry.scope));\n return SCOPE_ORDER.filter((scope) => declared.has(scope));\n}\n\n/** The frame types a frontend half claims, which its host reads before loading it. */\nfunction channels(entries: readonly ExtensionEntry[]): string[] {\n return [\n ...new Set(entries.filter((entry) => entry.surface === 'channel').map((entry) => toSnake(entry.name))),\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 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 delete exportsMap[PI_EXPORT];\n if (targets.includes('web')) exportsMap[WEB_EXPORT] = { import: WEB_DIST };\n else delete exportsMap[WEB_EXPORT];\n if (targets.includes('native')) exportsMap[NATIVE_EXPORT] = { import: NATIVE_DIST };\n else delete exportsMap[NATIVE_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 delete manifest.pi;\n\n if (targets.includes('server')) {\n const contracts = fs.existsSync(path.join(packageDir, API_CONTRACTS_SOURCE))\n ? {\n contracts: {\n entry: `./${API_CONTRACTS_SOURCE}`,\n dist: `./dist/${API_CONTRACTS_ENTRY}.mjs`,\n },\n }\n : {};\n // Read back from the built plugin, so the manifest says what the code the host imports does.\n const provides = input.serverContracts?.provides ?? {};\n const injects = input.serverContracts?.injects ?? {};\n manifest.poServer = {\n entry: `./${GENERATED_DIR}/server.ts`,\n dist: SERVER_DIST,\n scopes: serverScopes(graph),\n ...contracts,\n ...(Object.keys(provides).length > 0 ? { provides } : {}),\n ...(Object.keys(injects).length > 0 ? { injects } : {}),\n };\n } else delete manifest.poServer;\n\n if (targets.includes('web')) {\n const entries = frontendEntries(graph, 'web');\n manifest.poWeb = {\n pluginId,\n channels: channels(entries),\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: frontendScopes(entries),\n };\n } else delete manifest.poWeb;\n\n if (targets.includes('native')) {\n const entries = frontendEntries(graph, 'native');\n manifest.poNative = {\n pluginId,\n channels: channels(entries),\n // The bundled native half. Every bare specifier stays external, and the\n // CLI's composition build checks each one against the host module list.\n client: NATIVE_DIST,\n scopes: frontendScopes(entries),\n };\n } else delete manifest.poNative;\n\n return manifest;\n}\n\n/** Updates only the MCP-owned manifest block, leaving normal build metadata untouched. */\nexport function syncMcpManifest(\n manifest: Record<string, unknown>,\n enabled: boolean,\n widgets: readonly string[] = [],\n): Record<string, unknown> {\n const next = { ...manifest };\n if (enabled)\n next.poMcp = {\n entry: MCP_ENTRY,\n dist: MCP_DIST,\n scopes: ['session'],\n ...(widgets.length === 0\n ? {}\n : { ui: { entry: `./${GENERATED_DIR}/mcp-ui.ts`, dist: './dist/extensions/mcp-ui.mjs', widgets } }),\n };\n else delete next.poMcp;\n return next;\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\n/** Writes only MCP metadata after an isolated build. */\nexport function writeMcpManifest(packageDir: string, enabled: boolean, widgets: readonly string[] = []): boolean {\n const manifestPath = path.join(packageDir, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as Record<string, unknown>;\n const next = `${JSON.stringify(syncMcpManifest(manifest, enabled, widgets), 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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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["GENERATED_DIR","toSnake","fs","path","SERVER_DIST","require_runtime","require_layout","require_index","node_fs","node_path","SCOPE_ORDER","API_CONTRACTS_ENTRY","API_CONTRACTS_SOURCE","WEB_EXPORT","WEB_DIST","NATIVE_EXPORT","NATIVE_DIST","MCP_ENTRY","serverScopes","declared","graph","entry","shallowest","frontendEntries","host","frontendScopes","entries","scope","channels","withTypeConditions","next","subpath","target","exportsMap","conditions","esm","types","packageDir","syncManifest","input","manifest","targets","left","right","contracts","provides","injects","syncMcpManifest","enabled","widgets","writeManifest","manifestPath","writeMcpManifest"],"sources":["../../../src/services/syncManifest/index.ts"],"sourcesContent":["// sync: Public syncManifest/writeManifest and synchronous poExtension callers are deferred to phase 2.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { GENERATED_DIR, NATIVE_PLATFORM, SERVER_DIST } from '../../constants/layout';\nimport type { ExtensionEntry, 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_ENTRY = 'apiContracts';\nconst API_CONTRACTS_SOURCE = `src/exports/${API_CONTRACTS_ENTRY}.ts`;\n\n/** The retired terminal entry, removed from any manifest built before the terminal host was. */\nconst PI_EXPORT = './extensions/pi';\nconst WEB_EXPORT = './extensions/web';\nconst WEB_DIST = './dist/extensions/web.mjs';\nconst NATIVE_EXPORT = './extensions/native';\nconst NATIVE_DIST = './dist/extensions/native.mjs';\nconst MCP_DIST = './dist/extensions/mcp.mjs';\nconst MCP_ENTRY = `./${GENERATED_DIR}/mcp.ts`;\n\n/**\n * Which scopes a server plugin declares.\n *\n * The generated plugin 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 * The frontend files one frontend host reads.\n *\n * Native reads only `.native` files, and the cockpit never reads them, so the\n * two hosts' blocks describe disjoint halves of the same `(frontend)` side.\n */\nfunction frontendEntries(graph: ExtensionGraph, host: 'web' | 'native'): ExtensionEntry[] {\n return graph.entries.filter(\n (entry) => entry.side === 'frontend' && (entry.platform === NATIVE_PLATFORM) === (host === NATIVE_PLATFORM),\n );\n}\n\n/**\n * Which scopes a frontend 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. The\n * native app reuses the same registry, so the same holds there.\n */\nfunction frontendScopes(entries: readonly ExtensionEntry[]): ExtensionScope[] {\n const declared = new Set(entries.map((entry) => entry.scope));\n return SCOPE_ORDER.filter((scope) => declared.has(scope));\n}\n\n/** The frame types a frontend half claims, which its host reads before loading it. */\nfunction channels(entries: readonly ExtensionEntry[]): string[] {\n return [\n ...new Set(entries.filter((entry) => entry.surface === 'channel').map((entry) => toSnake(entry.name))),\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 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 delete exportsMap[PI_EXPORT];\n if (targets.includes('web')) exportsMap[WEB_EXPORT] = { import: WEB_DIST };\n else delete exportsMap[WEB_EXPORT];\n if (targets.includes('native')) exportsMap[NATIVE_EXPORT] = { import: NATIVE_DIST };\n else delete exportsMap[NATIVE_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 delete manifest.pi;\n\n if (targets.includes('server')) {\n const contracts = fs.existsSync(path.join(packageDir, API_CONTRACTS_SOURCE))\n ? {\n contracts: {\n entry: `./${API_CONTRACTS_SOURCE}`,\n dist: `./dist/${API_CONTRACTS_ENTRY}.mjs`,\n },\n }\n : {};\n // Read back from the built plugin, so the manifest says what the code the host imports does.\n const provides = input.serverContracts?.provides ?? {};\n const injects = input.serverContracts?.injects ?? {};\n manifest.poServer = {\n entry: `./${GENERATED_DIR}/server.ts`,\n dist: SERVER_DIST,\n scopes: serverScopes(graph),\n ...contracts,\n ...(Object.keys(provides).length > 0 ? { provides } : {}),\n ...(Object.keys(injects).length > 0 ? { injects } : {}),\n };\n } else delete manifest.poServer;\n\n if (targets.includes('web')) {\n const entries = frontendEntries(graph, 'web');\n manifest.poWeb = {\n pluginId,\n channels: channels(entries),\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: frontendScopes(entries),\n };\n } else delete manifest.poWeb;\n\n if (targets.includes('native')) {\n const entries = frontendEntries(graph, 'native');\n manifest.poNative = {\n pluginId,\n channels: channels(entries),\n // The bundled native half. Every bare specifier stays external, and the\n // CLI's composition build checks each one against the host module list.\n client: NATIVE_DIST,\n scopes: frontendScopes(entries),\n };\n } else delete manifest.poNative;\n\n return manifest;\n}\n\n/** Updates only the MCP-owned manifest block, leaving normal build metadata untouched. */\nexport function syncMcpManifest(\n manifest: Record<string, unknown>,\n enabled: boolean,\n widgets: readonly string[] = [],\n): Record<string, unknown> {\n const next = { ...manifest };\n if (enabled)\n next.poMcp = {\n entry: MCP_ENTRY,\n dist: MCP_DIST,\n scopes: ['session'],\n ...(widgets.length === 0\n ? {}\n : { ui: { entry: `./${GENERATED_DIR}/mcp-ui.ts`, dist: './dist/extensions/mcp-ui.mjs', widgets } }),\n };\n else delete next.poMcp;\n return next;\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\n/** Writes only MCP metadata after an isolated build. */\nexport function writeMcpManifest(packageDir: string, enabled: boolean, widgets: readonly string[] = []): boolean {\n const manifestPath = path.join(packageDir, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as Record<string, unknown>;\n const next = `${JSON.stringify(syncMcpManifest(manifest, enabled, widgets), 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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/syncManifest/index.ts"],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/syncManifest/index.ts"],"mappings":";;;;;;;;;;;;;;wBA+GgB,aAAa,OAAO,eAAe;;wBAkGnC,cAAc,OAAO;WAA0B,kBAAkB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs","path","SCOPE_ORDER","API_CONTRACTS_ENTRY","API_CONTRACTS_SOURCE","WEB_EXPORT","WEB_DIST","NATIVE_EXPORT","NATIVE_DIST","MCP_ENTRY","GENERATED_DIR","serverScopes","declared","graph","entry","shallowest","frontendEntries","host","frontendScopes","entries","scope","channels","toSnake","withTypeConditions","next","subpath","target","exportsMap","conditions","esm","types","packageDir","syncManifest","input","manifest","targets","left","right","contracts","provides","injects","SERVER_DIST","syncMcpManifest","enabled","widgets","writeManifest","manifestPath","writeMcpManifest"],"sources":["../../../src/services/syncManifest/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { GENERATED_DIR, NATIVE_PLATFORM, SERVER_DIST } from '../../constants/layout';\nimport type { ExtensionEntry, 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_ENTRY = 'apiContracts';\nconst API_CONTRACTS_SOURCE = `src/exports/${API_CONTRACTS_ENTRY}.ts`;\n\n/** The retired terminal entry, removed from any manifest built before the terminal host was. */\nconst PI_EXPORT = './extensions/pi';\nconst WEB_EXPORT = './extensions/web';\nconst WEB_DIST = './dist/extensions/web.mjs';\nconst NATIVE_EXPORT = './extensions/native';\nconst NATIVE_DIST = './dist/extensions/native.mjs';\nconst MCP_DIST = './dist/extensions/mcp.mjs';\nconst MCP_ENTRY = `./${GENERATED_DIR}/mcp.ts`;\n\n/**\n * Which scopes a server plugin declares.\n *\n * The generated plugin 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 * The frontend files one frontend host reads.\n *\n * Native reads only `.native` files, and the cockpit never reads them, so the\n * two hosts' blocks describe disjoint halves of the same `(frontend)` side.\n */\nfunction frontendEntries(graph: ExtensionGraph, host: 'web' | 'native'): ExtensionEntry[] {\n return graph.entries.filter(\n (entry) => entry.side === 'frontend' && (entry.platform === NATIVE_PLATFORM) === (host === NATIVE_PLATFORM),\n );\n}\n\n/**\n * Which scopes a frontend 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. The\n * native app reuses the same registry, so the same holds there.\n */\nfunction frontendScopes(entries: readonly ExtensionEntry[]): ExtensionScope[] {\n const declared = new Set(entries.map((entry) => entry.scope));\n return SCOPE_ORDER.filter((scope) => declared.has(scope));\n}\n\n/** The frame types a frontend half claims, which its host reads before loading it. */\nfunction channels(entries: readonly ExtensionEntry[]): string[] {\n return [\n ...new Set(entries.filter((entry) => entry.surface === 'channel').map((entry) => toSnake(entry.name))),\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 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 delete exportsMap[PI_EXPORT];\n if (targets.includes('web')) exportsMap[WEB_EXPORT] = { import: WEB_DIST };\n else delete exportsMap[WEB_EXPORT];\n if (targets.includes('native')) exportsMap[NATIVE_EXPORT] = { import: NATIVE_DIST };\n else delete exportsMap[NATIVE_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 delete manifest.pi;\n\n if (targets.includes('server')) {\n const contracts = fs.existsSync(path.join(packageDir, API_CONTRACTS_SOURCE))\n ? {\n contracts: {\n entry: `./${API_CONTRACTS_SOURCE}`,\n dist: `./dist/${API_CONTRACTS_ENTRY}.mjs`,\n },\n }\n : {};\n // Read back from the built plugin, so the manifest says what the code the host imports does.\n const provides = input.serverContracts?.provides ?? {};\n const injects = input.serverContracts?.injects ?? {};\n manifest.poServer = {\n entry: `./${GENERATED_DIR}/server.ts`,\n dist: SERVER_DIST,\n scopes: serverScopes(graph),\n ...contracts,\n ...(Object.keys(provides).length > 0 ? { provides } : {}),\n ...(Object.keys(injects).length > 0 ? { injects } : {}),\n };\n } else delete manifest.poServer;\n\n if (targets.includes('web')) {\n const entries = frontendEntries(graph, 'web');\n manifest.poWeb = {\n pluginId,\n channels: channels(entries),\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: frontendScopes(entries),\n };\n } else delete manifest.poWeb;\n\n if (targets.includes('native')) {\n const entries = frontendEntries(graph, 'native');\n manifest.poNative = {\n pluginId,\n channels: channels(entries),\n // The bundled native half. Every bare specifier stays external, and the\n // CLI's composition build checks each one against the host module list.\n client: NATIVE_DIST,\n scopes: frontendScopes(entries),\n };\n } else delete manifest.poNative;\n\n return manifest;\n}\n\n/** Updates only the MCP-owned manifest block, leaving normal build metadata untouched. */\nexport function syncMcpManifest(\n manifest: Record<string, unknown>,\n enabled: boolean,\n widgets: readonly string[] = [],\n): Record<string, unknown> {\n const next = { ...manifest };\n if (enabled)\n next.poMcp = {\n entry: MCP_ENTRY,\n dist: MCP_DIST,\n scopes: ['session'],\n ...(widgets.length === 0\n ? {}\n : { ui: { entry: `./${GENERATED_DIR}/mcp-ui.ts`, dist: './dist/extensions/mcp-ui.mjs', widgets } }),\n };\n else delete next.poMcp;\n return next;\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\n/** Writes only MCP metadata after an isolated build. */\nexport function writeMcpManifest(packageDir: string, enabled: boolean, widgets: readonly string[] = []): boolean {\n const manifestPath = path.join(packageDir, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as Record<string, unknown>;\n const next = `${JSON.stringify(syncMcpManifest(manifest, enabled, widgets), null, 2)}\\n`;\n if (fs.readFileSync(manifestPath, 'utf8') === next) return false;\n fs.writeFileSync(manifestPath, next);\n return true;\n}\n"],"mappings":"4KASA,MAAME,EAAyC,CAAC,SAAU,YAAa,SAAS,EAE1EC,EAAsB,eACtBC,EAAuB,eAAeD,EAAoB,KAI1DE,EAAa,mBACbC,EAAW,4BACXC,EAAgB,sBAChBC,EAAc,+BAEdC,EAAY,KAAKC,EAAc,SAUrC,SAASC,EAAa,EAAyC,CAC7D,IAAMC,EAAWC,EAAM,QAAQ,OAAQ,GAAUC,EAAM,OAAS,SAAS,EACzE,GAAIF,EAAS,SAAW,EAAG,MAAO,CAAC,EACnC,IAAMG,EAAa,KAAK,IAAI,GAAGH,EAAS,IAAK,GAAUV,EAAY,QAAQY,EAAM,KAAK,CAAC,CAAC,EACxF,OAAOZ,EAAY,MAAMa,CAAU,CACrC,CAQA,SAASC,EAAgB,EAAuB,EAA0C,CACxF,OAAOH,EAAM,QAAQ,OAClB,GAAUC,EAAM,OAAS,YAAeA,EAAM,WAAA,WAAmCG,IAAA,SACpF,CACF,CASA,SAASC,EAAe,EAAsD,CAC5E,IAAMN,EAAW,IAAI,IAAIO,EAAQ,IAAK,GAAUL,EAAM,KAAK,CAAC,EAC5D,OAAOZ,EAAY,OAAQ,GAAUU,EAAS,IAAIQ,CAAK,CAAC,CAC1D,CAGA,SAASC,EAAS,EAA8C,CAC9D,MAAO,CACL,GAAG,IAAI,IAAIF,EAAQ,OAAQ,GAAUL,EAAM,UAAY,SAAS,CAAC,CAAC,IAAK,GAAUQ,EAAQR,EAAM,IAAI,CAAC,CAAC,CACvG,CAAC,CAAC,KAAK,CACT,CAYA,SAASS,EAAmB,EAAoB,EAA8D,CAC5G,IAAMC,EAAgC,CAAC,EACvC,IAAK,GAAM,CAACC,EAASC,KAAW,OAAO,QAAQC,CAAU,EAAG,CAC1D,GAAIF,IAAY,kBAAuC,OAAOC,GAAW,WAArCA,EAA+C,CACjF,EAAKD,GAAWC,EAChB,QACF,CACA,IAAME,EAAaF,EACbG,EAAMD,EAAW,QAAUA,EAAW,QACtCE,EAAQD,GAAK,SAAS,MAAM,IAAM,GAAO,GAAGA,EAAI,MAAM,EAAG,EAAc,EAAE,QAAU,IAAA,GAGzF,EAAKJ,GACHG,EAAW,QAAU,IAAA,IAAaE,IAAU,IAAA,IAAa9B,EAAG,WAAWC,EAAK,KAAK8B,EAAYD,CAAK,CAAC,EAC/F,CAAE,QAAO,GAAGF,CAAW,EACvBA,CACR,CACA,OAAOJ,CACT,CAaA,SAAgBQ,EAAa,EAA8C,CACzE,GAAM,CAAE,aAAY,QAAO,UAAS,YAAaC,EAC3CC,EAAW,CAAE,GAAGD,EAAM,QAAS,EAErC,GAAIC,EAAS,UAAY,IAAA,IAAa,OAAOA,EAAS,SAAY,SAAU,CAC1E,IAAMP,EAAaJ,EAAmBQ,EAAYG,EAAS,OAAkC,EAQ7F,OAAOP,EAAW,mBACdQ,EAAQ,SAAS,KAAK,EAAG,EAAW9B,GAAc,CAAE,OAAQC,CAAS,EACpE,OAAOqB,EAAWtB,GACnB8B,EAAQ,SAAS,QAAQ,EAAG,EAAW5B,GAAiB,CAAE,OAAQC,CAAY,EAC7E,OAAOmB,EAAWpB,GACvB,EAAS,QAAU,OAAO,YACxB,OAAO,QAAQoB,CAAU,CAAC,CAAC,MAAM,CAACS,GAAO,CAACC,KACxCD,IAAS,iBAAmB,EAAIC,IAAU,iBAAmB,GAAKD,EAAK,cAAcC,CAAK,CAC5F,CACF,CACF,CAIA,GAFA,OAAOH,EAAS,GAEZC,EAAQ,SAAS,QAAQ,EAAG,CAC9B,IAAMG,EAAYtC,EAAG,WAAWC,EAAK,KAAK8B,EAAY3B,CAAoB,CAAC,EACvE,CACE,UAAW,CACT,MAAO,KAAKA,IACZ,KAAM,UAAUD,EAAoB,KACtC,CACF,EACA,CAAC,EAECoC,EAAWN,EAAM,iBAAiB,UAAY,CAAC,EAC/CO,EAAUP,EAAM,iBAAiB,SAAW,CAAC,EACnD,EAAS,SAAW,CAClB,MAAO,KAAKvB,EAAc,YAC1B,KAAM+B,EACN,OAAQ9B,EAAaE,CAAK,EAC1B,GAAGyB,EACH,GAAI,OAAO,KAAKC,CAAQ,CAAC,CAAC,OAAS,EAAI,CAAE,UAAS,EAAI,CAAC,EACvD,GAAI,OAAO,KAAKC,CAAO,CAAC,CAAC,OAAS,EAAI,CAAE,SAAQ,EAAI,CAAC,CACvD,CACF,MAAO,OAAON,EAAS,SAEvB,GAAIC,EAAQ,SAAS,KAAK,EAAG,CAC3B,IAAMhB,EAAUH,EAAgBH,EAAO,KAAK,EAC5C,EAAS,MAAQ,CACf,WACA,SAAUQ,EAASF,CAAO,EAI1B,OAAQb,EACR,OAAQY,EAAeC,CAAO,CAChC,CACF,MAAO,OAAOe,EAAS,MAEvB,GAAIC,EAAQ,SAAS,QAAQ,EAAG,CAC9B,IAAMhB,EAAUH,EAAgBH,EAAO,QAAQ,EAC/C,EAAS,SAAW,CAClB,WACA,SAAUQ,EAASF,CAAO,EAG1B,OAAQX,EACR,OAAQU,EAAeC,CAAO,CAChC,CACF,MAAO,OAAOe,EAAS,SAEvB,OAAOA,CACT,CAGA,SAAgBQ,EACd,EACA,EACA,EAA6B,CAAC,EACL,CACzB,IAAMlB,EAAO,CAAE,GAAGU,CAAS,EAW3B,OAVIS,EACF,EAAK,MAAQ,CACX,MAAOlC,EACP,KAAM,4BACN,OAAQ,CAAC,SAAS,EAClB,GAAImC,EAAQ,SAAW,EACnB,CAAC,EACD,CAAE,GAAI,CAAE,MAAO,KAAKlC,EAAc,YAAa,KAAM,+BAAgC,SAAQ,CAAE,CACrG,EACG,OAAOc,EAAK,MACVA,CACT,CAGA,SAAgBqB,EAAc,EAA6E,CACzG,IAAMC,EAAe7C,EAAK,KAAKgC,EAAM,WAAY,cAAc,EACzDT,EAAO,GAAG,KAAK,UAAUQ,EAAaC,CAAK,EAAG,KAAM,CAAC,EAAE,IAG7D,OAFIjC,EAAG,aAAa8C,EAAc,MAAM,IAAMtB,IAC9CxB,EAAG,cAAc8C,EAActB,CAAI,EAC5B,GACT,CAGA,SAAgBuB,EAAiB,EAAoB,EAAkB,EAA6B,CAAC,EAAY,CAC/G,IAAMD,EAAe7C,EAAK,KAAK8B,EAAY,cAAc,EACnDG,EAAW,KAAK,MAAMlC,EAAG,aAAa8C,EAAc,MAAM,CAAC,EAC3DtB,EAAO,GAAG,KAAK,UAAUkB,EAAgBR,EAAUS,EAASC,CAAO,EAAG,KAAM,CAAC,EAAE,IAGrF,OAFI5C,EAAG,aAAa8C,EAAc,MAAM,IAAMtB,IAC9CxB,EAAG,cAAc8C,EAActB,CAAI,EAC5B,GACT"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fs","path","SCOPE_ORDER","API_CONTRACTS_ENTRY","API_CONTRACTS_SOURCE","WEB_EXPORT","WEB_DIST","NATIVE_EXPORT","NATIVE_DIST","MCP_ENTRY","GENERATED_DIR","serverScopes","declared","graph","entry","shallowest","frontendEntries","host","frontendScopes","entries","scope","channels","toSnake","withTypeConditions","next","subpath","target","exportsMap","conditions","esm","types","packageDir","syncManifest","input","manifest","targets","left","right","contracts","provides","injects","SERVER_DIST","syncMcpManifest","enabled","widgets","writeManifest","manifestPath","writeMcpManifest"],"sources":["../../../src/services/syncManifest/index.ts"],"sourcesContent":["// sync: Public syncManifest/writeManifest and synchronous poExtension callers are deferred to phase 2.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport { GENERATED_DIR, NATIVE_PLATFORM, SERVER_DIST } from '../../constants/layout';\nimport type { ExtensionEntry, 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_ENTRY = 'apiContracts';\nconst API_CONTRACTS_SOURCE = `src/exports/${API_CONTRACTS_ENTRY}.ts`;\n\n/** The retired terminal entry, removed from any manifest built before the terminal host was. */\nconst PI_EXPORT = './extensions/pi';\nconst WEB_EXPORT = './extensions/web';\nconst WEB_DIST = './dist/extensions/web.mjs';\nconst NATIVE_EXPORT = './extensions/native';\nconst NATIVE_DIST = './dist/extensions/native.mjs';\nconst MCP_DIST = './dist/extensions/mcp.mjs';\nconst MCP_ENTRY = `./${GENERATED_DIR}/mcp.ts`;\n\n/**\n * Which scopes a server plugin declares.\n *\n * The generated plugin 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 * The frontend files one frontend host reads.\n *\n * Native reads only `.native` files, and the cockpit never reads them, so the\n * two hosts' blocks describe disjoint halves of the same `(frontend)` side.\n */\nfunction frontendEntries(graph: ExtensionGraph, host: 'web' | 'native'): ExtensionEntry[] {\n return graph.entries.filter(\n (entry) => entry.side === 'frontend' && (entry.platform === NATIVE_PLATFORM) === (host === NATIVE_PLATFORM),\n );\n}\n\n/**\n * Which scopes a frontend 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. The\n * native app reuses the same registry, so the same holds there.\n */\nfunction frontendScopes(entries: readonly ExtensionEntry[]): ExtensionScope[] {\n const declared = new Set(entries.map((entry) => entry.scope));\n return SCOPE_ORDER.filter((scope) => declared.has(scope));\n}\n\n/** The frame types a frontend half claims, which its host reads before loading it. */\nfunction channels(entries: readonly ExtensionEntry[]): string[] {\n return [\n ...new Set(entries.filter((entry) => entry.surface === 'channel').map((entry) => toSnake(entry.name))),\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 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 delete exportsMap[PI_EXPORT];\n if (targets.includes('web')) exportsMap[WEB_EXPORT] = { import: WEB_DIST };\n else delete exportsMap[WEB_EXPORT];\n if (targets.includes('native')) exportsMap[NATIVE_EXPORT] = { import: NATIVE_DIST };\n else delete exportsMap[NATIVE_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 delete manifest.pi;\n\n if (targets.includes('server')) {\n const contracts = fs.existsSync(path.join(packageDir, API_CONTRACTS_SOURCE))\n ? {\n contracts: {\n entry: `./${API_CONTRACTS_SOURCE}`,\n dist: `./dist/${API_CONTRACTS_ENTRY}.mjs`,\n },\n }\n : {};\n // Read back from the built plugin, so the manifest says what the code the host imports does.\n const provides = input.serverContracts?.provides ?? {};\n const injects = input.serverContracts?.injects ?? {};\n manifest.poServer = {\n entry: `./${GENERATED_DIR}/server.ts`,\n dist: SERVER_DIST,\n scopes: serverScopes(graph),\n ...contracts,\n ...(Object.keys(provides).length > 0 ? { provides } : {}),\n ...(Object.keys(injects).length > 0 ? { injects } : {}),\n };\n } else delete manifest.poServer;\n\n if (targets.includes('web')) {\n const entries = frontendEntries(graph, 'web');\n manifest.poWeb = {\n pluginId,\n channels: channels(entries),\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: frontendScopes(entries),\n };\n } else delete manifest.poWeb;\n\n if (targets.includes('native')) {\n const entries = frontendEntries(graph, 'native');\n manifest.poNative = {\n pluginId,\n channels: channels(entries),\n // The bundled native half. Every bare specifier stays external, and the\n // CLI's composition build checks each one against the host module list.\n client: NATIVE_DIST,\n scopes: frontendScopes(entries),\n };\n } else delete manifest.poNative;\n\n return manifest;\n}\n\n/** Updates only the MCP-owned manifest block, leaving normal build metadata untouched. */\nexport function syncMcpManifest(\n manifest: Record<string, unknown>,\n enabled: boolean,\n widgets: readonly string[] = [],\n): Record<string, unknown> {\n const next = { ...manifest };\n if (enabled)\n next.poMcp = {\n entry: MCP_ENTRY,\n dist: MCP_DIST,\n scopes: ['session'],\n ...(widgets.length === 0\n ? {}\n : { ui: { entry: `./${GENERATED_DIR}/mcp-ui.ts`, dist: './dist/extensions/mcp-ui.mjs', widgets } }),\n };\n else delete next.poMcp;\n return next;\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\n/** Writes only MCP metadata after an isolated build. */\nexport function writeMcpManifest(packageDir: string, enabled: boolean, widgets: readonly string[] = []): boolean {\n const manifestPath = path.join(packageDir, 'package.json');\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as Record<string, unknown>;\n const next = `${JSON.stringify(syncMcpManifest(manifest, enabled, widgets), 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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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../constants/layout.cjs"),n=require("../renderMcpWidgets/index.cjs"),r=require("../generate/index.cjs"),i=require("../syncManifest/index.cjs"),a=require("../serverContracts/index.cjs"),o=require("../unistylesBabel/index.cjs");let s=require("node:fs");s=e.__toESM(s,1);let c=require("node:path");c=e.__toESM(c,1);const
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../constants/layout.cjs"),n=require("../renderMcpWidgets/index.cjs"),r=require("../generate/index.cjs"),i=require("../syncManifest/index.cjs"),a=require("../serverContracts/index.cjs"),o=require("../unistylesBabel/index.cjs");let s=require("node:fs");s=e.__toESM(s,1);let c=require("node:path");c=e.__toESM(c,1);let l=require("node:fs/promises");l=e.__toESM(l,1);const u=`src/exports`,d=`apiContracts`;function f(e,t){let n=c.default.join(e,t);if(!s.default.existsSync(n))return{};let r={};for(let e of s.default.readdirSync(n)){if(!e.endsWith(`.ts`)||e.endsWith(`.d.ts`))continue;let n=e.slice(0,-3);r[n]=`${t}/${e}`}return r}function p(e,n=u){let r=`${t.GENERATED_DIR}/server.ts`;return{...f(e,n),...s.default.existsSync(c.default.join(e,r))?{"extensions/server":r}:{}}}function m(e,t,n){let r=c.default.join(e,t);if(!s.default.existsSync(r))return{};let i={};for(let e of s.default.readdirSync(r,{encoding:`utf8`,recursive:!0})){if(!n(e))continue;let r=e.split(c.default.sep).join(`/`);i[`./${t}/${r}`]=`./${t}/${r}`}return i}function h(e){let t=JSON.parse(s.default.readFileSync(c.default.join(e,`package.json`),`utf8`)),n=t.exports!==null&&typeof t.exports==`object`&&!Array.isArray(t.exports)?Object.fromEntries(Object.entries(t.exports).filter(([,e])=>typeof e==`string`)):{};return{...m(e,`skills`,e=>c.default.basename(e)===`SKILL.md`),...m(e,`themes`,e=>e.endsWith(`.json`)),...n}}function g(){return{name:`po-query-assets`,resolveId(e,t){let n=e.indexOf(`?`);if(n<0||t===void 0||!e.startsWith(`.`))return;let r=t.split(`?`)[0];return`${c.default.resolve(c.default.dirname(r),e.slice(0,n))}${e.slice(n)}`},async load(e){let t=e.indexOf(`?`);if(t<0)return;let n=e.slice(0,t),r=new URLSearchParams(e.slice(t+1));if(!r.has(`url`))return;let i=r.has(`worker`);return`export default import.meta.ROLLUP_FILE_URL_${this.emitFile(i?{type:`chunk`,id:n,name:c.default.basename(n,c.default.extname(n))}:{type:`asset`,name:c.default.basename(n),source:await l.default.readFile(n)})};`}}}function _(e=`web`){return{entry:{[`extensions/${e}`]:`${t.GENERATED_DIR}/${e}.ts`},clean:!1,dts:!1,exports:!1,format:[`esm`],platform:`browser`,fixedExtension:!0,external:e=>!e.startsWith(`.`)&&!c.default.isAbsolute(e),plugins:[g()],sourcemap:!0,unbundle:!1,tsconfig:e===`web`?`tsconfig.web.json`:`tsconfig.mcp.json`}}function v(e){return{entry:{"extensions/native":`${t.GENERATED_DIR}/native.ts`},clean:!1,dts:!1,exports:!1,format:[`esm`],platform:`neutral`,fixedExtension:!0,external:e=>!e.startsWith(`.`)&&!c.default.isAbsolute(e),plugins:[o.unistylesBabel(e)],sourcemap:!0,unbundle:!1,tsconfig:`tsconfig.native.json`}}const y=/^(?:extensions\/mcp(?:-ui)?|.+\.mcp)\.(?:mjs|cjs|d\.mts|d\.cts)(?:\.map)?$/u;function b(e,t){let n=c.default.join(e,`dist`);if(s.default.existsSync(n))for(let e of s.default.readdirSync(n,{recursive:!0,encoding:`utf8`})){if(y.test(e.split(c.default.sep).join(`/`))!==(t===`mcp`))continue;let r=c.default.join(n,e);s.default.lstatSync(r,{throwIfNoEntry:!1})?.isFile()&&s.default.rmSync(r)}}function x(e={packageDir:process.cwd()}){let o=e.packageDir??process.cwd(),s=r.generateExtension({...e,packageDir:o,check:e.check??!1});for(let e of s.notices)process.stderr.write(`[po-build] ${e.path}: ${e.message}\n`);let p=e.target===`mcp`;e.check||b(o,p?`mcp`:`normal`);let m=p&&!s.targets.includes(`mcp`),g=`\0po-empty-mcp`;m&&!e.check&&i.writeMcpManifest(o,!1);let y=p?s.targets.includes(`mcp`)?{"extensions/mcp":`${t.GENERATED_DIR}/mcp.ts`}:{"extensions/mcp":g}:{...f(o,e.exportsDir??u),...s.targets.includes(`server`)?{"extensions/server":`${t.GENERATED_DIR}/server.ts`}:{},...e.entry},x={clean:!1,dts:!m&&{incremental:!0,parallel:!1,eager:!0},...m?{write:!1,plugins:[{name:`po-empty-mcp`,resolveId:e=>e===g?g:void 0,load:e=>e===g?`export {};`:void 0}]}:{},exports:!p&&{customExports:e=>{let t=Object.keys(y),n=t[0];return{...h(o),...e,...t.length===1&&n!==void 0&&n!==`index`&&!n.startsWith(`extensions/`)?{[`./${n}`]:e[`.`]}:{}}}},format:{esm:{},cjs:{dts:!1}},platform:`node`,outExtensions:({format:e})=>({js:e===`es`?`.mjs`:`.cjs`,dts:e===`es`?`.d.mts`:`.d.cts`}),sourcemap:!0},S=(e,r)=>({...x,entry:e,unbundle:!1,hooks:{"build:done":async()=>{p?i.writeMcpManifest(o,s.targets.includes(`mcp`),n.renderMcpWidgets(s.graph,s.packageName,t.GENERATED_DIR).names):i.writeManifest({packageDir:o,manifest:JSON.parse(await l.default.readFile(c.default.join(o,`package.json`),`utf8`)),graph:s.graph,targets:s.targets,pluginId:s.pluginId,...s.targets.includes(`server`)?{serverContracts:await a.readServerContracts(o,t.SERVER_DIST)}:{}})}}}),C=!p&&d in y&&Object.keys(y).length>1?[((e,t)=>({...x,entry:e,unbundle:t}))({[d]:y[d]},!1),S(Object.fromEntries(Object.entries(y).filter(([e])=>e!==d)),!0)]:[S(y,!p)];if(p&&s.files.has(`generated/mcp-ui.ts`))return[...C,_(`mcp-ui`)];let w=p?[]:[...s.targets.includes(`web`)?[_()]:[],...s.targets.includes(`native`)?[v(o)]:[]];if(w.length>0){if(Object.keys(y).length>0)return[...C,...w];let[e,...t]=w;return[{...e,hooks:C[0].hooks},...t]}return C.length===1?C[0]:C}exports.extensionSourceEntries=p,exports.poExtension=x;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["path","fs","GENERATED_DIR","unistylesBabel","generateExtension","renderMcpWidgets","readServerContracts","SERVER_DIST","require_runtime","require_layout","require_index","require_index$1","require_index$2","require_index$3","require_index$4","node_fs","node_path","DEFAULT_EXPORTS_DIR","API_CONTRACTS_ENTRY","exportEntries","absolute","packageDir","exportsDir","entries","file","stem","extensionSourceEntries","server","resourceEntries","root","directory","relative","matches","normalized","staticExports","manifest","declared","target","queryAssetPlugin","queryAt","source","importer","importerFile","id","query","worker","browserConfig","nativeConfig","MCP_OUTPUT","cleanOutput","dist","owner","poExtension","options","result","notice","mcpOnly","emptyMcp","emptyEntry","entry","baseNode","names","name","generated","format","node","configs","frontends","first","rest"],"sources":["../../../src/services/tsdownPreset/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { GENERATED_DIR, SERVER_DIST } from '../../constants/layout';\nimport { generateExtension } from '../generate';\nimport type { GenerateOptions } from '../generate/type';\nimport { renderMcpWidgets } from '../renderMcpWidgets';\nimport { readServerContracts } from '../serverContracts';\nimport { writeManifest, writeMcpManifest } from '../syncManifest';\nimport { unistylesBabel } from '../unistylesBabel';\nimport type { UnistylesBabelPlugin } from '../unistylesBabel/type';\n\ninterface EmittedFile {\n readonly type: 'asset' | 'chunk';\n readonly id?: string;\n readonly name: string;\n readonly source?: Buffer;\n}\n\ninterface QueryAssetPlugin {\n readonly name: string;\n resolveId(source: string, importer: string | undefined): string | undefined;\n load(this: { emitFile(file: EmittedFile): string }, id: string): string | undefined;\n}\n\n/** The shape a tsdown config needs from this preset, without importing tsdown's types. */\ninterface BrowserPresetConfig {\n entry: Record<string, string>;\n clean: boolean;\n dts: false;\n exports: boolean;\n format: 'esm'[];\n platform: 'browser';\n fixedExtension: boolean;\n external: (id: string) => boolean;\n plugins: QueryAssetPlugin[];\n sourcemap: boolean;\n unbundle: boolean;\n tsconfig: string;\n hooks?: PresetConfig['hooks'];\n}\n\n/** The separately bundled native half, shaped like the browser one. */\ninterface NativePresetConfig extends Omit<BrowserPresetConfig, 'platform' | 'plugins'> {\n platform: 'neutral';\n plugins: UnistylesBabelPlugin[];\n}\n\ninterface GeneratedExports {\n customExports: (exports: Record<string, unknown>) => Record<string, unknown>;\n}\n\ninterface BasePresetConfig {\n entry: Record<string, string>;\n clean: boolean;\n dts: false | { incremental: boolean; parallel: boolean; eager: boolean };\n write?: false;\n plugins?: QueryAssetPlugin[];\n exports: boolean | GeneratedExports;\n format: { esm: Record<string, never>; cjs: { dts: false } };\n platform: 'node';\n sourcemap: boolean;\n unbundle: boolean;\n outExtensions: ({ format }: { format: string }) => {\n js: string;\n dts: string;\n };\n}\n\ninterface PresetConfig extends BasePresetConfig {\n hooks?: { 'build:done': () => void | Promise<void> };\n}\n\nconst DEFAULT_EXPORTS_DIR = 'src/exports';\nconst API_CONTRACTS_ENTRY = 'apiContracts';\n\n/** `src/exports/apiContracts.ts` becomes the `apiContracts` entry; index keeps its name. */\nfunction exportEntries(packageDir: string, exportsDir: string): Record<string, string> {\n const absolute = path.join(packageDir, exportsDir);\n if (!fs.existsSync(absolute)) return {};\n const entries: Record<string, string> = {};\n for (const file of fs.readdirSync(absolute)) {\n if (!file.endsWith('.ts') || file.endsWith('.d.ts')) continue;\n const stem = file.slice(0, -'.ts'.length);\n entries[stem] = `${exportsDir}/${file}`;\n }\n return entries;\n}\n\n/**\n * Source file behind each published subpath of a folder-routed extension, keyed like\n * tsdown entries (`index`, `apiContracts`, `extensions/server`). Read from disk without\n * regenerating, so `generated/server.ts` must already exist. The private Worker build\n * uses this to bundle workspace sources instead of `dist/`.\n */\nexport function extensionSourceEntries(\n packageDir: string,\n exportsDir: string = DEFAULT_EXPORTS_DIR,\n): Record<string, string> {\n const server = `${GENERATED_DIR}/server.ts`;\n return {\n ...exportEntries(packageDir, exportsDir),\n ...(fs.existsSync(path.join(packageDir, server)) ? { 'extensions/server': server } : {}),\n };\n}\n\n/** Static resources receive package export entries without becoming tsdown inputs. */\nfunction resourceEntries(\n packageDir: string,\n directory: string,\n matches: (relative: string) => boolean,\n): Record<string, string> {\n const root = path.join(packageDir, directory);\n if (!fs.existsSync(root)) return {};\n const entries: Record<string, string> = {};\n for (const relative of fs.readdirSync(root, { encoding: 'utf8', recursive: true })) {\n if (!matches(relative)) continue;\n const normalized = relative.split(path.sep).join('/');\n entries[`./${directory}/${normalized}`] = `./${directory}/${normalized}`;\n }\n return entries;\n}\n\n/** Preserves declared resources and discovers default skill and theme resource exports. */\nfunction staticExports(packageDir: string): Record<string, unknown> {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >;\n const declared =\n manifest.exports !== null && typeof manifest.exports === 'object' && !Array.isArray(manifest.exports)\n ? Object.fromEntries(Object.entries(manifest.exports).filter(([, target]) => typeof target === 'string'))\n : {};\n return {\n ...resourceEntries(packageDir, 'skills', (relative) => path.basename(relative) === 'SKILL.md'),\n ...resourceEntries(packageDir, 'themes', (relative) => relative.endsWith('.json')),\n ...declared,\n };\n}\n/**\n * One tsdown config for a folder-routed extension.\n *\n * Entries come from the tree rather than a hand-written list, and the manifest\n * blocks that restate the tree are written after the build rather than kept in\n * step by hand. Browser contributions are emitted through a second bundled\n * config because their routed source is not published.\n */\nexport interface ExtensionPresetOptions extends GenerateOptions {\n /** Extra tsdown entries beyond the derived ones. */\n readonly entry?: Record<string, string>;\n /** Public export modules, package-relative. Defaults to `src/exports`. */\n readonly exportsDir?: string;\n}\n\n/**\n * Implements the Vite query imports extension browser code already uses.\n *\n * `?url` copies an opaque asset and `?worker&url` emits a separately bundled\n * worker chunk. Rolldown then replaces its file reference with an URL relative\n * to the extension bundle, which remains valid when the cockpit composes it.\n */\nfunction queryAssetPlugin(): QueryAssetPlugin {\n return {\n name: 'po-query-assets',\n resolveId(source, importer) {\n const queryAt = source.indexOf('?');\n if (queryAt < 0 || importer === undefined || !source.startsWith('.')) return undefined;\n const importerFile = importer.split('?')[0] as string;\n return `${path.resolve(path.dirname(importerFile), source.slice(0, queryAt))}${source.slice(queryAt)}`;\n },\n load(id) {\n const queryAt = id.indexOf('?');\n if (queryAt < 0) return undefined;\n const file = id.slice(0, queryAt);\n const query = new URLSearchParams(id.slice(queryAt + 1));\n if (!query.has('url')) return undefined;\n const worker = query.has('worker');\n const reference = this.emitFile(\n worker\n ? { type: 'chunk', id: file, name: path.basename(file, path.extname(file)) }\n : { type: 'asset', name: path.basename(file), source: fs.readFileSync(file) },\n );\n return `export default import.meta.ROLLUP_FILE_URL_${reference};`;\n },\n };\n}\n\n/** The separately bundled browser half of a routed extension. */\nfunction browserConfig(target: 'web' | 'mcp-ui' = 'web'): BrowserPresetConfig {\n return {\n entry: { [`extensions/${target}`]: `${GENERATED_DIR}/${target}.ts` },\n clean: false,\n // No declarations. Nothing imports this as a typed module: it has no\n // export subpath, and the cockpit consumes it as a bundle. The browser\n // half is still typechecked, by tsconfig.web.json in the typecheck script.\n dts: false,\n exports: false,\n format: ['esm'],\n platform: 'browser',\n // platform browser turns tsdown's fixedExtension off, which would emit\n // .js. The manifest names .mjs like the other two entries, so ask for it.\n fixedExtension: true,\n // Every bare specifier stays external and the cockpit resolves it, which\n // is what keeps one React and one store in the page no matter how many\n // plugins are installed. An allowlist would have to be kept in step with\n // the cockpit's; this cannot drift.\n //\n // A predicate rather than a pattern: rolldown tests `external` against the\n // resolved id, so /^[^.]/ also matches the absolute path of this package's\n // own routed file and would leave a dangling import to unbuilt .tsx.\n external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),\n plugins: [queryAssetPlugin()],\n sourcemap: true,\n // Bundled, unlike the node half. Unbundling would emit an import to the\n // routed source file, which is not published and is .tsx besides. One\n // module with only bare specifiers external is what the cockpit wants.\n unbundle: false,\n // Browser routes are excluded from the node tsconfig. Every browser-capable\n // extension owns this focused config for its matching typecheck.\n tsconfig: target === 'web' ? 'tsconfig.web.json' : 'tsconfig.mcp.json',\n };\n}\n\n/**\n * The separately bundled native half of a routed extension.\n *\n * Neutral rather than browser: React Native has neither the DOM nor Node, so\n * no condition set fits it better, and every bare specifier stays external\n * anyway. The CLI bundles this module into the signed composition and refuses\n * any import outside the native host module list, so nothing here has to.\n */\nfunction nativeConfig(packageDir: string): NativePresetConfig {\n return {\n entry: { 'extensions/native': `${GENERATED_DIR}/native.ts` },\n clean: false,\n dts: false,\n exports: false,\n format: ['esm'],\n platform: 'neutral',\n fixedExtension: true,\n // The same predicate as the browser half, for the same reason: the host\n // supplies React, React Native and Unistyles, one copy each.\n external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),\n plugins: [unistylesBabel(packageDir)],\n sourcemap: true,\n unbundle: false,\n // Native routes import react-native, which the DOM-typed web config must\n // not see, so they typecheck under their own config.\n tsconfig: 'tsconfig.native.json',\n };\n}\n\nconst MCP_OUTPUT = /^(?:extensions\\/mcp(?:-ui)?|.+\\.mcp)\\.(?:mjs|cjs|d\\.mts|d\\.cts)(?:\\.map)?$/u;\n\n/**\n * Removes files from dist that one build owns, so an entry deleted from the source tree does\n * not live on as a stale file (and in the Nx cache). The MCP build removes only its own files;\n * the normal build removes everything else and keeps the MCP files the other build wrote.\n */\nfunction cleanOutput(packageDir: string, owner: 'mcp' | 'normal'): void {\n const dist = path.join(packageDir, 'dist');\n if (!fs.existsSync(dist)) return;\n for (const relative of fs.readdirSync(dist, { recursive: true, encoding: 'utf8' })) {\n const isMcp = MCP_OUTPUT.test(relative.split(path.sep).join('/'));\n if (isMcp !== (owner === 'mcp')) continue;\n const absolute = path.join(dist, relative);\n if (fs.lstatSync(absolute, { throwIfNoEntry: false })?.isFile()) fs.rmSync(absolute);\n }\n}\n\nexport function poExtension(\n options: ExtensionPresetOptions = { packageDir: process.cwd() },\n): PresetConfig | (PresetConfig | BrowserPresetConfig | NativePresetConfig)[] {\n const packageDir = options.packageDir ?? process.cwd();\n const result = generateExtension({ ...options, packageDir, check: options.check ?? false });\n\n for (const notice of result.notices) process.stderr.write(`[po-build] ${notice.path}: ${notice.message}\\n`);\n\n const mcpOnly = options.target === 'mcp';\n // ponytail: the normal build cleans at config load, so it must run before the MCP build (every\n // build script does). Hashed assets of an mcp-ui bundle are not recognised and are rebuilt after.\n if (!options.check) cleanOutput(packageDir, mcpOnly ? 'mcp' : 'normal');\n const emptyMcp = mcpOnly && !result.targets.includes('mcp');\n const emptyEntry = '\\0po-empty-mcp';\n if (emptyMcp && !options.check) writeMcpManifest(packageDir, false);\n const entry: Record<string, string> = mcpOnly\n ? result.targets.includes('mcp')\n ? { 'extensions/mcp': `${GENERATED_DIR}/mcp.ts` }\n : { 'extensions/mcp': emptyEntry }\n : {\n ...exportEntries(packageDir, options.exportsDir ?? DEFAULT_EXPORTS_DIR),\n ...(result.targets.includes('server') ? { 'extensions/server': `${GENERATED_DIR}/server.ts` } : {}),\n ...options.entry,\n };\n\n const baseNode: Omit<BasePresetConfig, 'entry' | 'unbundle'> = {\n // MCP shares dist/ with the normal build, so neither build may erase the other.\n clean: false,\n dts: emptyMcp ? false : { incremental: true, parallel: false, eager: true },\n // tsdown rejects both an empty entry and an empty config array. A virtual\n // input completes its normal lifecycle without publishing an empty facet.\n ...(emptyMcp\n ? {\n write: false as const,\n plugins: [\n {\n name: 'po-empty-mcp',\n resolveId: (source: string) => (source === emptyEntry ? emptyEntry : undefined),\n load: (id: string) => (id === emptyEntry ? 'export {};' : undefined),\n },\n ],\n }\n : {}),\n exports: mcpOnly\n ? false\n : {\n customExports: (generated) => {\n const names = Object.keys(entry);\n const name = names[0];\n return {\n ...staticExports(packageDir),\n ...generated,\n ...(names.length === 1 && name !== undefined && name !== 'index' && !name.startsWith('extensions/')\n ? { [`./${name}`]: generated['.'] }\n : {}),\n };\n },\n },\n // CommonJS keeps its JavaScript but emits no declarations. Every export map\n // resolves types through .d.mts, and a CJS declaration build would rerun\n // the same whole-program tsgo emit a second time.\n format: { esm: {}, cjs: { dts: false } },\n // No minify. This is a library build, and a mangled stack trace inside a\n // published extension is far more expensive than the bytes it saves.\n // Native server entries use the .mjs filenames their hosts require.\n platform: 'node',\n outExtensions: ({ format }) => ({\n js: format === 'es' ? '.mjs' : '.cjs',\n dts: format === 'es' ? '.d.mts' : '.d.cts',\n }),\n sourcemap: true,\n };\n\n // Contract bundles are published as a self-contained graph. Normal extension\n // entries remain unbundled for routed runtime resources, while MCP remains\n // isolated from the shared dist directory.\n const node = (entries: Record<string, string>, _unbundle: boolean): PresetConfig => ({\n ...baseNode,\n entry: entries,\n // Public entry facades must remain bundled. Otherwise their relative\n // imports target unbundled implementation chunks, which Vite treats as\n // source files when resolving a workspace package.\n unbundle: false,\n hooks: {\n 'build:done': async () => {\n if (mcpOnly) {\n writeMcpManifest(\n packageDir,\n result.targets.includes('mcp'),\n renderMcpWidgets(result.graph, result.packageName, GENERATED_DIR).names,\n );\n return;\n }\n writeManifest({\n packageDir,\n manifest: JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >,\n graph: result.graph,\n targets: result.targets,\n pluginId: result.pluginId,\n ...(result.targets.includes('server')\n ? { serverContracts: await readServerContracts(packageDir, SERVER_DIST) }\n : {}),\n });\n },\n },\n });\n const unsyncedNode = (entries: Record<string, string>, unbundle: boolean): PresetConfig => ({\n ...baseNode,\n entry: entries,\n unbundle,\n });\n const configs =\n !mcpOnly && API_CONTRACTS_ENTRY in entry && Object.keys(entry).length > 1\n ? [\n unsyncedNode({ [API_CONTRACTS_ENTRY]: entry[API_CONTRACTS_ENTRY] }, false),\n node(Object.fromEntries(Object.entries(entry).filter(([name]) => name !== API_CONTRACTS_ENTRY)), true),\n ]\n : [node(entry, !mcpOnly)];\n if (mcpOnly && result.files.has(`${GENERATED_DIR}/mcp-ui.ts`)) return [...configs, browserConfig('mcp-ui')];\n const frontends: (BrowserPresetConfig | NativePresetConfig)[] = mcpOnly\n ? []\n : [\n ...(result.targets.includes('web') ? [browserConfig()] : []),\n ...(result.targets.includes('native') ? [nativeConfig(packageDir)] : []),\n ];\n if (frontends.length > 0) {\n if (Object.keys(entry).length > 0) return [...configs, ...frontends];\n // No node entry to carry the manifest sync, so the first frontend build does.\n const [first, ...rest] = frontends;\n return [{ ...first!, hooks: configs[0]!.hooks }, ...rest];\n }\n return configs.length === 1 ? configs[0]! : configs;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["path","fs","GENERATED_DIR","fsPromises","unistylesBabel","generateExtension","renderMcpWidgets","readServerContracts","SERVER_DIST","require_runtime","require_layout","require_index","require_index$1","require_index$2","require_index$3","require_index$4","node_fs","node_path","node_fs_promises","DEFAULT_EXPORTS_DIR","API_CONTRACTS_ENTRY","exportEntries","absolute","packageDir","exportsDir","entries","file","stem","extensionSourceEntries","server","resourceEntries","root","directory","relative","matches","normalized","staticExports","manifest","declared","target","queryAssetPlugin","queryAt","source","importer","importerFile","id","query","worker","browserConfig","nativeConfig","MCP_OUTPUT","cleanOutput","dist","owner","poExtension","options","result","notice","mcpOnly","emptyMcp","emptyEntry","entry","baseNode","names","name","generated","format","node","configs","frontends","first","rest"],"sources":["../../../src/services/tsdownPreset/index.ts"],"sourcesContent":["// sync: Exported poExtension/extensionSourceEntries and the host customExports callback retain synchronous contracts.\nimport fs from 'node:fs';\nimport fsPromises from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { GENERATED_DIR, SERVER_DIST } from '../../constants/layout';\nimport { generateExtension } from '../generate';\nimport type { GenerateOptions } from '../generate/type';\nimport { renderMcpWidgets } from '../renderMcpWidgets';\nimport { readServerContracts } from '../serverContracts';\nimport { writeManifest, writeMcpManifest } from '../syncManifest';\nimport { unistylesBabel } from '../unistylesBabel';\nimport type { UnistylesBabelPlugin } from '../unistylesBabel/type';\n\ninterface EmittedFile {\n readonly type: 'asset' | 'chunk';\n readonly id?: string;\n readonly name: string;\n readonly source?: Buffer;\n}\n\ninterface QueryAssetPlugin {\n readonly name: string;\n resolveId(source: string, importer: string | undefined): string | undefined;\n load(this: { emitFile(file: EmittedFile): string }, id: string): string | undefined | Promise<string | undefined>;\n}\n\n/** The shape a tsdown config needs from this preset, without importing tsdown's types. */\ninterface BrowserPresetConfig {\n entry: Record<string, string>;\n clean: boolean;\n dts: false;\n exports: boolean;\n format: 'esm'[];\n platform: 'browser';\n fixedExtension: boolean;\n external: (id: string) => boolean;\n plugins: QueryAssetPlugin[];\n sourcemap: boolean;\n unbundle: boolean;\n tsconfig: string;\n hooks?: PresetConfig['hooks'];\n}\n\n/** The separately bundled native half, shaped like the browser one. */\ninterface NativePresetConfig extends Omit<BrowserPresetConfig, 'platform' | 'plugins'> {\n platform: 'neutral';\n plugins: UnistylesBabelPlugin[];\n}\n\ninterface GeneratedExports {\n customExports: (exports: Record<string, unknown>) => Record<string, unknown>;\n}\n\ninterface BasePresetConfig {\n entry: Record<string, string>;\n clean: boolean;\n dts: false | { incremental: boolean; parallel: boolean; eager: boolean };\n write?: false;\n plugins?: QueryAssetPlugin[];\n exports: boolean | GeneratedExports;\n format: { esm: Record<string, never>; cjs: { dts: false } };\n platform: 'node';\n sourcemap: boolean;\n unbundle: boolean;\n outExtensions: ({ format }: { format: string }) => {\n js: string;\n dts: string;\n };\n}\n\ninterface PresetConfig extends BasePresetConfig {\n hooks?: { 'build:done': () => void | Promise<void> };\n}\n\nconst DEFAULT_EXPORTS_DIR = 'src/exports';\nconst API_CONTRACTS_ENTRY = 'apiContracts';\n\n/** `src/exports/apiContracts.ts` becomes the `apiContracts` entry; index keeps its name. */\nfunction exportEntries(packageDir: string, exportsDir: string): Record<string, string> {\n const absolute = path.join(packageDir, exportsDir);\n if (!fs.existsSync(absolute)) return {};\n const entries: Record<string, string> = {};\n for (const file of fs.readdirSync(absolute)) {\n if (!file.endsWith('.ts') || file.endsWith('.d.ts')) continue;\n const stem = file.slice(0, -'.ts'.length);\n entries[stem] = `${exportsDir}/${file}`;\n }\n return entries;\n}\n\n/**\n * Source file behind each published subpath of a folder-routed extension, keyed like\n * tsdown entries (`index`, `apiContracts`, `extensions/server`). Read from disk without\n * regenerating, so `generated/server.ts` must already exist. The private Worker build\n * uses this to bundle workspace sources instead of `dist/`.\n */\nexport function extensionSourceEntries(\n packageDir: string,\n exportsDir: string = DEFAULT_EXPORTS_DIR,\n): Record<string, string> {\n const server = `${GENERATED_DIR}/server.ts`;\n return {\n ...exportEntries(packageDir, exportsDir),\n ...(fs.existsSync(path.join(packageDir, server)) ? { 'extensions/server': server } : {}),\n };\n}\n\n/** Static resources receive package export entries without becoming tsdown inputs. */\nfunction resourceEntries(\n packageDir: string,\n directory: string,\n matches: (relative: string) => boolean,\n): Record<string, string> {\n const root = path.join(packageDir, directory);\n if (!fs.existsSync(root)) return {};\n const entries: Record<string, string> = {};\n for (const relative of fs.readdirSync(root, { encoding: 'utf8', recursive: true })) {\n if (!matches(relative)) continue;\n const normalized = relative.split(path.sep).join('/');\n entries[`./${directory}/${normalized}`] = `./${directory}/${normalized}`;\n }\n return entries;\n}\n\n/** Preserves declared resources and discovers default skill and theme resource exports. */\nfunction staticExports(packageDir: string): Record<string, unknown> {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >;\n const declared =\n manifest.exports !== null && typeof manifest.exports === 'object' && !Array.isArray(manifest.exports)\n ? Object.fromEntries(Object.entries(manifest.exports).filter(([, target]) => typeof target === 'string'))\n : {};\n return {\n ...resourceEntries(packageDir, 'skills', (relative) => path.basename(relative) === 'SKILL.md'),\n ...resourceEntries(packageDir, 'themes', (relative) => relative.endsWith('.json')),\n ...declared,\n };\n}\n/**\n * One tsdown config for a folder-routed extension.\n *\n * Entries come from the tree rather than a hand-written list, and the manifest\n * blocks that restate the tree are written after the build rather than kept in\n * step by hand. Browser contributions are emitted through a second bundled\n * config because their routed source is not published.\n */\nexport interface ExtensionPresetOptions extends GenerateOptions {\n /** Extra tsdown entries beyond the derived ones. */\n readonly entry?: Record<string, string>;\n /** Public export modules, package-relative. Defaults to `src/exports`. */\n readonly exportsDir?: string;\n}\n\n/**\n * Implements the Vite query imports extension browser code already uses.\n *\n * `?url` copies an opaque asset and `?worker&url` emits a separately bundled\n * worker chunk. Rolldown then replaces its file reference with an URL relative\n * to the extension bundle, which remains valid when the cockpit composes it.\n */\nfunction queryAssetPlugin(): QueryAssetPlugin {\n return {\n name: 'po-query-assets',\n resolveId(source, importer) {\n const queryAt = source.indexOf('?');\n if (queryAt < 0 || importer === undefined || !source.startsWith('.')) return undefined;\n const importerFile = importer.split('?')[0] as string;\n return `${path.resolve(path.dirname(importerFile), source.slice(0, queryAt))}${source.slice(queryAt)}`;\n },\n async load(id) {\n const queryAt = id.indexOf('?');\n if (queryAt < 0) return undefined;\n const file = id.slice(0, queryAt);\n const query = new URLSearchParams(id.slice(queryAt + 1));\n if (!query.has('url')) return undefined;\n const worker = query.has('worker');\n const reference = this.emitFile(\n worker\n ? { type: 'chunk', id: file, name: path.basename(file, path.extname(file)) }\n : { type: 'asset', name: path.basename(file), source: await fsPromises.readFile(file) },\n );\n return `export default import.meta.ROLLUP_FILE_URL_${reference};`;\n },\n };\n}\n\n/** The separately bundled browser half of a routed extension. */\nfunction browserConfig(target: 'web' | 'mcp-ui' = 'web'): BrowserPresetConfig {\n return {\n entry: { [`extensions/${target}`]: `${GENERATED_DIR}/${target}.ts` },\n clean: false,\n // No declarations. Nothing imports this as a typed module: it has no\n // export subpath, and the cockpit consumes it as a bundle. The browser\n // half is still typechecked, by tsconfig.web.json in the typecheck script.\n dts: false,\n exports: false,\n format: ['esm'],\n platform: 'browser',\n // platform browser turns tsdown's fixedExtension off, which would emit\n // .js. The manifest names .mjs like the other two entries, so ask for it.\n fixedExtension: true,\n // Every bare specifier stays external and the cockpit resolves it, which\n // is what keeps one React and one store in the page no matter how many\n // plugins are installed. An allowlist would have to be kept in step with\n // the cockpit's; this cannot drift.\n //\n // A predicate rather than a pattern: rolldown tests `external` against the\n // resolved id, so /^[^.]/ also matches the absolute path of this package's\n // own routed file and would leave a dangling import to unbuilt .tsx.\n external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),\n plugins: [queryAssetPlugin()],\n sourcemap: true,\n // Bundled, unlike the node half. Unbundling would emit an import to the\n // routed source file, which is not published and is .tsx besides. One\n // module with only bare specifiers external is what the cockpit wants.\n unbundle: false,\n // Browser routes are excluded from the node tsconfig. Every browser-capable\n // extension owns this focused config for its matching typecheck.\n tsconfig: target === 'web' ? 'tsconfig.web.json' : 'tsconfig.mcp.json',\n };\n}\n\n/**\n * The separately bundled native half of a routed extension.\n *\n * Neutral rather than browser: React Native has neither the DOM nor Node, so\n * no condition set fits it better, and every bare specifier stays external\n * anyway. The CLI bundles this module into the signed composition and refuses\n * any import outside the native host module list, so nothing here has to.\n */\nfunction nativeConfig(packageDir: string): NativePresetConfig {\n return {\n entry: { 'extensions/native': `${GENERATED_DIR}/native.ts` },\n clean: false,\n dts: false,\n exports: false,\n format: ['esm'],\n platform: 'neutral',\n fixedExtension: true,\n // The same predicate as the browser half, for the same reason: the host\n // supplies React, React Native and Unistyles, one copy each.\n external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),\n plugins: [unistylesBabel(packageDir)],\n sourcemap: true,\n unbundle: false,\n // Native routes import react-native, which the DOM-typed web config must\n // not see, so they typecheck under their own config.\n tsconfig: 'tsconfig.native.json',\n };\n}\n\nconst MCP_OUTPUT = /^(?:extensions\\/mcp(?:-ui)?|.+\\.mcp)\\.(?:mjs|cjs|d\\.mts|d\\.cts)(?:\\.map)?$/u;\n\n/**\n * Removes files from dist that one build owns, so an entry deleted from the source tree does\n * not live on as a stale file (and in the Nx cache). The MCP build removes only its own files;\n * the normal build removes everything else and keeps the MCP files the other build wrote.\n */\nfunction cleanOutput(packageDir: string, owner: 'mcp' | 'normal'): void {\n const dist = path.join(packageDir, 'dist');\n if (!fs.existsSync(dist)) return;\n for (const relative of fs.readdirSync(dist, { recursive: true, encoding: 'utf8' })) {\n const isMcp = MCP_OUTPUT.test(relative.split(path.sep).join('/'));\n if (isMcp !== (owner === 'mcp')) continue;\n const absolute = path.join(dist, relative);\n if (fs.lstatSync(absolute, { throwIfNoEntry: false })?.isFile()) fs.rmSync(absolute);\n }\n}\n\nexport function poExtension(\n options: ExtensionPresetOptions = { packageDir: process.cwd() },\n): PresetConfig | (PresetConfig | BrowserPresetConfig | NativePresetConfig)[] {\n const packageDir = options.packageDir ?? process.cwd();\n const result = generateExtension({ ...options, packageDir, check: options.check ?? false });\n\n for (const notice of result.notices) process.stderr.write(`[po-build] ${notice.path}: ${notice.message}\\n`);\n\n const mcpOnly = options.target === 'mcp';\n // ponytail: the normal build cleans at config load, so it must run before the MCP build (every\n // build script does). Hashed assets of an mcp-ui bundle are not recognised and are rebuilt after.\n if (!options.check) cleanOutput(packageDir, mcpOnly ? 'mcp' : 'normal');\n const emptyMcp = mcpOnly && !result.targets.includes('mcp');\n const emptyEntry = '\\0po-empty-mcp';\n if (emptyMcp && !options.check) writeMcpManifest(packageDir, false);\n const entry: Record<string, string> = mcpOnly\n ? result.targets.includes('mcp')\n ? { 'extensions/mcp': `${GENERATED_DIR}/mcp.ts` }\n : { 'extensions/mcp': emptyEntry }\n : {\n ...exportEntries(packageDir, options.exportsDir ?? DEFAULT_EXPORTS_DIR),\n ...(result.targets.includes('server') ? { 'extensions/server': `${GENERATED_DIR}/server.ts` } : {}),\n ...options.entry,\n };\n\n const baseNode: Omit<BasePresetConfig, 'entry' | 'unbundle'> = {\n // MCP shares dist/ with the normal build, so neither build may erase the other.\n clean: false,\n dts: emptyMcp ? false : { incremental: true, parallel: false, eager: true },\n // tsdown rejects both an empty entry and an empty config array. A virtual\n // input completes its normal lifecycle without publishing an empty facet.\n ...(emptyMcp\n ? {\n write: false as const,\n plugins: [\n {\n name: 'po-empty-mcp',\n resolveId: (source: string) => (source === emptyEntry ? emptyEntry : undefined),\n load: (id: string) => (id === emptyEntry ? 'export {};' : undefined),\n },\n ],\n }\n : {}),\n exports: mcpOnly\n ? false\n : {\n customExports: (generated) => {\n const names = Object.keys(entry);\n const name = names[0];\n return {\n ...staticExports(packageDir),\n ...generated,\n ...(names.length === 1 && name !== undefined && name !== 'index' && !name.startsWith('extensions/')\n ? { [`./${name}`]: generated['.'] }\n : {}),\n };\n },\n },\n // CommonJS keeps its JavaScript but emits no declarations. Every export map\n // resolves types through .d.mts, and a CJS declaration build would rerun\n // the same whole-program tsgo emit a second time.\n format: { esm: {}, cjs: { dts: false } },\n // No minify. This is a library build, and a mangled stack trace inside a\n // published extension is far more expensive than the bytes it saves.\n // Native server entries use the .mjs filenames their hosts require.\n platform: 'node',\n outExtensions: ({ format }) => ({\n js: format === 'es' ? '.mjs' : '.cjs',\n dts: format === 'es' ? '.d.mts' : '.d.cts',\n }),\n sourcemap: true,\n };\n\n // Contract bundles are published as a self-contained graph. Normal extension\n // entries remain unbundled for routed runtime resources, while MCP remains\n // isolated from the shared dist directory.\n const node = (entries: Record<string, string>, _unbundle: boolean): PresetConfig => ({\n ...baseNode,\n entry: entries,\n // Public entry facades must remain bundled. Otherwise their relative\n // imports target unbundled implementation chunks, which Vite treats as\n // source files when resolving a workspace package.\n unbundle: false,\n hooks: {\n 'build:done': async () => {\n if (mcpOnly) {\n writeMcpManifest(\n packageDir,\n result.targets.includes('mcp'),\n renderMcpWidgets(result.graph, result.packageName, GENERATED_DIR).names,\n );\n return;\n }\n writeManifest({\n packageDir,\n manifest: JSON.parse(await fsPromises.readFile(path.join(packageDir, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >,\n graph: result.graph,\n targets: result.targets,\n pluginId: result.pluginId,\n ...(result.targets.includes('server')\n ? { serverContracts: await readServerContracts(packageDir, SERVER_DIST) }\n : {}),\n });\n },\n },\n });\n const unsyncedNode = (entries: Record<string, string>, unbundle: boolean): PresetConfig => ({\n ...baseNode,\n entry: entries,\n unbundle,\n });\n const configs =\n !mcpOnly && API_CONTRACTS_ENTRY in entry && Object.keys(entry).length > 1\n ? [\n unsyncedNode({ [API_CONTRACTS_ENTRY]: entry[API_CONTRACTS_ENTRY] }, false),\n node(Object.fromEntries(Object.entries(entry).filter(([name]) => name !== API_CONTRACTS_ENTRY)), true),\n ]\n : [node(entry, !mcpOnly)];\n if (mcpOnly && result.files.has(`${GENERATED_DIR}/mcp-ui.ts`)) return [...configs, browserConfig('mcp-ui')];\n const frontends: (BrowserPresetConfig | NativePresetConfig)[] = mcpOnly\n ? []\n : [\n ...(result.targets.includes('web') ? [browserConfig()] : []),\n ...(result.targets.includes('native') ? [nativeConfig(packageDir)] : []),\n ];\n if (frontends.length > 0) {\n if (Object.keys(entry).length > 0) return [...configs, ...frontends];\n // No node entry to carry the manifest sync, so the first frontend build does.\n const [first, ...rest] = frontends;\n return [{ ...first!, hooks: configs[0]!.hooks }, ...rest];\n }\n return configs.length === 1 ? configs[0]! : configs;\n}\n"],"mappings":"ibA2EA,MAAMmB,EAAsB,cACtBC,EAAsB,eAG5B,SAASC,EAAc,EAAoB,EAA4C,CACrF,IAAMC,EAAWtB,EAAAA,QAAK,KAAKuB,EAAYC,CAAU,EACjD,GAAI,CAACvB,EAAAA,QAAG,WAAWqB,CAAQ,EAAG,MAAO,CAAC,EACtC,IAAMG,EAAkC,CAAC,EACzC,IAAK,IAAMC,KAAQzB,EAAAA,QAAG,YAAYqB,CAAQ,EAAG,CAC3C,GAAI,CAACI,EAAK,SAAS,KAAK,GAAKA,EAAK,SAAS,OAAO,EAAG,SACrD,IAAMC,EAAOD,EAAK,MAAM,EAAG,EAAa,EACxC,EAAQC,GAAQ,GAAGH,EAAW,GAAGE,GACnC,CACA,OAAOD,CACT,CAQA,SAAgBG,EACd,EACA,EAAqBT,EACG,CACxB,IAAMU,EAAS,GAAG3B,EAAAA,cAAc,YAChC,MAAO,CACL,GAAGmB,EAAcE,EAAYC,CAAU,EACvC,GAAIvB,EAAAA,QAAG,WAAWD,EAAAA,QAAK,KAAKuB,EAAYM,CAAM,CAAC,EAAI,CAAE,oBAAqBA,CAAO,EAAI,CAAC,CACxF,CACF,CAGA,SAASC,EACP,EACA,EACA,EACwB,CACxB,IAAMC,EAAO/B,EAAAA,QAAK,KAAKuB,EAAYS,CAAS,EAC5C,GAAI,CAAC/B,EAAAA,QAAG,WAAW8B,CAAI,EAAG,MAAO,CAAC,EAClC,IAAMN,EAAkC,CAAC,EACzC,IAAK,IAAMQ,KAAYhC,EAAAA,QAAG,YAAY8B,EAAM,CAAE,SAAU,OAAQ,UAAW,EAAK,CAAC,EAAG,CAClF,GAAI,CAACG,EAAQD,CAAQ,EAAG,SACxB,IAAME,EAAaF,EAAS,MAAMjC,EAAAA,QAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EACpD,EAAQ,KAAKgC,EAAU,GAAGG,KAAgB,KAAKH,EAAU,GAAGG,GAC9D,CACA,OAAOV,CACT,CAGA,SAASW,EAAc,EAA6C,CAClE,IAAMC,EAAW,KAAK,MAAMpC,EAAAA,QAAG,aAAaD,EAAAA,QAAK,KAAKuB,EAAY,cAAc,EAAG,MAAM,CAAC,EAIpFe,EACJD,EAAS,UAAY,MAAQ,OAAOA,EAAS,SAAY,UAAY,CAAC,MAAM,QAAQA,EAAS,OAAO,EAChG,OAAO,YAAY,OAAO,QAAQA,EAAS,OAAO,CAAC,CAAC,QAAQ,EAAGE,KAAY,OAAOA,GAAW,QAAQ,CAAC,EACtG,CAAC,EACP,MAAO,CACL,GAAGT,EAAgBP,EAAY,SAAW,GAAavB,EAAAA,QAAK,SAASiC,CAAQ,IAAM,UAAU,EAC7F,GAAGH,EAAgBP,EAAY,SAAW,GAAaU,EAAS,SAAS,OAAO,CAAC,EACjF,GAAGK,CACL,CACF,CAuBA,SAASE,GAAqC,CAC5C,MAAO,CACL,KAAM,kBACN,UAAU,EAAQ,EAAU,CAC1B,IAAMC,EAAUC,EAAO,QAAQ,GAAG,EAClC,GAAID,EAAU,GAAKE,IAAa,IAAA,IAAa,CAACD,EAAO,WAAW,GAAG,EAAG,OACtE,IAAME,EAAeD,EAAS,MAAM,GAAG,CAAC,CAAC,GACzC,MAAO,GAAG3C,EAAAA,QAAK,QAAQA,EAAAA,QAAK,QAAQ4C,CAAY,EAAGF,EAAO,MAAM,EAAGD,CAAO,CAAC,IAAIC,EAAO,MAAMD,CAAO,GACrG,EACA,MAAM,KAAK,EAAI,CACb,IAAMA,EAAUI,EAAG,QAAQ,GAAG,EAC9B,GAAIJ,EAAU,EAAG,OACjB,IAAMf,EAAOmB,EAAG,MAAM,EAAGJ,CAAO,EAC1BK,EAAQ,IAAI,gBAAgBD,EAAG,MAAMJ,EAAU,CAAC,CAAC,EACvD,GAAI,CAACK,EAAM,IAAI,KAAK,EAAG,OACvB,IAAMC,EAASD,EAAM,IAAI,QAAQ,EAMjC,MAAO,8CALW,KAAK,SACrBC,EACI,CAAE,KAAM,QAAS,GAAIrB,EAAM,KAAM1B,EAAAA,QAAK,SAAS0B,EAAM1B,EAAAA,QAAK,QAAQ0B,CAAI,CAAC,CAAE,EACzE,CAAE,KAAM,QAAS,KAAM1B,EAAAA,QAAK,SAAS0B,CAAI,EAAG,OAAQ,MAAMvB,EAAAA,QAAW,SAASuB,CAAI,CAAE,CAE7B,EAAE,EACjE,CACF,CACF,CAGA,SAASsB,EAAc,EAA2B,MAA4B,CAC5E,MAAO,CACL,MAAO,EAAG,cAAcT,KAAW,GAAGrC,EAAAA,cAAc,GAAGqC,EAAO,IAAK,EACnE,MAAO,GAIP,IAAK,GACL,QAAS,GACT,OAAQ,CAAC,KAAK,EACd,SAAU,UAGV,eAAgB,GAShB,SAAW,GAAe,CAACM,EAAG,WAAW,GAAG,GAAK,CAAC7C,EAAAA,QAAK,WAAW6C,CAAE,EACpE,QAAS,CAACL,EAAiB,CAAC,EAC5B,UAAW,GAIX,SAAU,GAGV,SAAUD,IAAW,MAAQ,oBAAsB,mBACrD,CACF,CAUA,SAASU,EAAa,EAAwC,CAC5D,MAAO,CACL,MAAO,CAAE,oBAAqB,GAAG/C,EAAAA,cAAc,WAAY,EAC3D,MAAO,GACP,IAAK,GACL,QAAS,GACT,OAAQ,CAAC,KAAK,EACd,SAAU,UACV,eAAgB,GAGhB,SAAW,GAAe,CAAC2C,EAAG,WAAW,GAAG,GAAK,CAAC7C,EAAAA,QAAK,WAAW6C,CAAE,EACpE,QAAS,CAACzC,EAAAA,eAAemB,CAAU,CAAC,EACpC,UAAW,GACX,SAAU,GAGV,SAAU,sBACZ,CACF,CAEA,MAAM2B,EAAa,8EAOnB,SAASC,EAAY,EAAoB,EAA+B,CACtE,IAAMC,EAAOpD,EAAAA,QAAK,KAAKuB,EAAY,MAAM,EACzC,GAAKtB,EAAAA,QAAG,WAAWmD,CAAI,EACvB,IAAK,IAAMnB,KAAYhC,EAAAA,QAAG,YAAYmD,EAAM,CAAE,UAAW,GAAM,SAAU,MAAO,CAAC,EAAG,CAElF,GADcF,EAAW,KAAKjB,EAAS,MAAMjC,EAAAA,QAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CACvD,KAAOqD,IAAU,OAAQ,SACjC,IAAM/B,EAAWtB,EAAAA,QAAK,KAAKoD,EAAMnB,CAAQ,EACrChC,EAAAA,QAAG,UAAUqB,EAAU,CAAE,eAAgB,EAAM,CAAC,CAAC,EAAE,OAAO,GAAGN,EAAA,QAAG,OAAOM,CAAQ,CACrF,CACF,CAEA,SAAgBgC,EACd,EAAkC,CAAE,WAAY,QAAQ,IAAI,CAAE,EACc,CAC5E,IAAM/B,EAAagC,EAAQ,YAAc,QAAQ,IAAI,EAC/CC,EAASnD,EAAAA,kBAAkB,CAAE,GAAGkD,EAAS,aAAY,MAAOA,EAAQ,OAAS,EAAM,CAAC,EAE1F,IAAK,IAAME,KAAUD,EAAO,QAAS,QAAQ,OAAO,MAAM,cAAcC,EAAO,KAAK,IAAIA,EAAO,QAAQ,GAAG,EAE1G,IAAMC,EAAUH,EAAQ,SAAW,MAG9BA,EAAQ,OAAOJ,EAAY5B,EAAYmC,EAAU,MAAQ,QAAQ,EACtE,IAAMC,EAAWD,GAAW,CAACF,EAAO,QAAQ,SAAS,KAAK,EACpDI,EAAa,iBACfD,GAAY,CAACJ,EAAQ,OAAO1C,EAAA,iBAAiBU,EAAY,EAAK,EAClE,IAAMsC,EAAgCH,EAClCF,EAAO,QAAQ,SAAS,KAAK,EAC3B,CAAE,iBAAkB,GAAGtD,EAAAA,cAAc,QAAS,EAC9C,CAAE,iBAAkB0D,CAAW,EACjC,CACE,GAAGvC,EAAcE,EAAYgC,EAAQ,YAAcpC,CAAmB,EACtE,GAAIqC,EAAO,QAAQ,SAAS,QAAQ,EAAI,CAAE,oBAAqB,GAAGtD,EAAAA,cAAc,WAAY,EAAI,CAAC,EACjG,GAAGqD,EAAQ,KACb,EAEEO,EAAyD,CAE7D,MAAO,GACP,IAAK,IAAmB,CAAE,YAAa,GAAM,SAAU,GAAO,MAAO,EAAK,EAG1E,GAAIH,EACA,CACE,MAAO,GACP,QAAS,CACP,CACE,KAAM,eACN,UAAY,GAAoBjB,IAAWkB,EAAaA,EAAa,IAAA,GACrE,KAAO,GAAgBf,IAAOe,EAAa,aAAe,IAAA,EAC5D,CACF,CACF,EACA,CAAC,EACL,QAAS,IAEL,CACE,cAAgB,GAAc,CAC5B,IAAMG,EAAQ,OAAO,KAAKF,CAAK,EACzBG,EAAOD,EAAM,GACnB,MAAO,CACL,GAAG3B,EAAcb,CAAU,EAC3B,GAAG0C,EACH,GAAIF,EAAM,SAAW,GAAKC,IAAS,IAAA,IAAaA,IAAS,SAAW,CAACA,EAAK,WAAW,aAAa,EAC9F,EAAG,KAAKA,KAASC,EAAU,IAAK,EAChC,CAAC,CACP,CACF,CACF,EAIJ,OAAQ,CAAE,IAAK,CAAC,EAAG,IAAK,CAAE,IAAK,EAAM,CAAE,EAIvC,SAAU,OACV,eAAgB,CAAE,aAAc,CAC9B,GAAIC,IAAW,KAAO,OAAS,OAC/B,IAAKA,IAAW,KAAO,SAAW,QACpC,GACA,UAAW,EACb,EAKMC,GAAQ,EAAiC,KAAsC,CACnF,GAAGL,EACH,MAAOrC,EAIP,SAAU,GACV,MAAO,CACL,aAAc,SAAY,CACpBiC,EACF7C,EAAA,iBACEU,EACAiC,EAAO,QAAQ,SAAS,KAAK,EAC7BlD,EAAAA,iBAAiBkD,EAAO,MAAOA,EAAO,YAAatD,EAAAA,aAAa,CAAC,CAAC,KACpE,EAGFW,EAAA,cAAc,CACZ,aACA,SAAU,KAAK,MAAM,MAAMV,EAAAA,QAAW,SAASH,EAAAA,QAAK,KAAKuB,EAAY,cAAc,EAAG,MAAM,CAAC,EAI7F,MAAOiC,EAAO,MACd,QAASA,EAAO,QAChB,SAAUA,EAAO,SACjB,GAAIA,EAAO,QAAQ,SAAS,QAAQ,EAChC,CAAE,gBAAiB,MAAMjD,EAAAA,oBAAoBgB,EAAYf,EAAAA,WAAW,CAAE,EACtE,CAAC,CACP,CAAC,CACH,CACF,CACF,GAMM4D,EACJ,CAACV,GAAWtC,KAAuByC,GAAS,OAAO,KAAKA,CAAK,CAAC,CAAC,OAAS,EACpE,GAPgB,EAAiC,KAAqC,CAC1F,GAAGC,EACH,MAAOrC,EACP,UACF,GAIQ,CAAa,EAAGL,GAAsByC,EAAMzC,EAAqB,EAAG,EAAK,EACzE+C,EAAK,OAAO,YAAY,OAAO,QAAQN,CAAK,CAAC,CAAC,QAAQ,CAACG,KAAUA,IAAS5C,CAAmB,CAAC,EAAG,EAAI,CACvG,EACA,CAAC+C,EAAKN,EAAO,CAACH,CAAO,CAAC,EAC5B,GAAIA,GAAWF,EAAO,MAAM,IAAI,qBAA4B,EAAG,MAAO,CAAC,GAAGY,EAASpB,EAAc,QAAQ,CAAC,EAC1G,IAAMqB,EAA0DX,EAC5D,CAAC,EACD,CACE,GAAIF,EAAO,QAAQ,SAAS,KAAK,EAAI,CAACR,EAAc,CAAC,EAAI,CAAC,EAC1D,GAAIQ,EAAO,QAAQ,SAAS,QAAQ,EAAI,CAACP,EAAa1B,CAAU,CAAC,EAAI,CAAC,CACxE,EACJ,GAAI8C,EAAU,OAAS,EAAG,CACxB,GAAI,OAAO,KAAKR,CAAK,CAAC,CAAC,OAAS,EAAG,MAAO,CAAC,GAAGO,EAAS,GAAGC,CAAS,EAEnE,GAAM,CAACC,EAAO,GAAGC,GAAQF,EACzB,MAAO,CAAC,CAAE,GAAGC,EAAQ,MAAOF,EAAQ,EAAE,CAAE,KAAM,EAAG,GAAGG,CAAI,CAC1D,CACA,OAAOH,EAAQ,SAAW,EAAIA,EAAQ,GAAMA,CAC9C"}
|
|
@@ -12,7 +12,7 @@ interface QueryAssetPlugin {
|
|
|
12
12
|
resolveId(source: string, importer: string | undefined): string | undefined;
|
|
13
13
|
load(this: {
|
|
14
14
|
emitFile(file: EmittedFile): string;
|
|
15
|
-
}, id: string): string | undefined
|
|
15
|
+
}, id: string): string | undefined | Promise<string | undefined>;
|
|
16
16
|
}
|
|
17
17
|
/** The shape a tsdown config needs from this preset, without importing tsdown's types. */
|
|
18
18
|
interface BrowserPresetConfig {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/tsdownPreset/index.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/tsdownPreset/index.ts"],"mappings":";;;UAcU;WACC;WACA;WACA;WACA,SAAS;;UAGV;WACC;EACT,UAAU,gBAAgB;EAC1B,KAAK;IAAQ,SAAS,MAAM;KAAwB,kCAAkC;;;UAI9E;EACR,OAAO;EACP;EACA;EACA;EACA;EACA;EACA;EACA,WAAW;EACX,SAAS;EACT;EACA;EACA;EACA,QAAQ;;;UAIA,2BAA2B,KAAK;EACxC;EACA,SAAS;;UAGD;EACR,gBAAgB,SAAS,4BAA4B;;UAG7C;EACR,OAAO;EACP;EACA;IAAe;IAAsB;IAAmB;;EACxD;EACA,UAAU;EACV,mBAAmB;EACnB;IAAU,KAAK;IAAuB;MAAO;;;EAC7C;EACA;EACA;EACA,kBAAkB;IAAY;;IAC5B;IACA;;;UAIM,qBAAqB;EAC7B;IAAU,2BAA2B;;;;;;;;;wBAyBvB,uBACd,oBACA,sBACC;;;;;;;;;iBAiDc,+BAA+B;;WAErC,QAAQ;;WAER;;wBAuHK,YACd,UAAS,yBACR,gBAAgB,eAAe,sBAAsB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{GENERATED_DIR as e,SERVER_DIST as t}from"../../constants/layout.mjs";import{renderMcpWidgets as n}from"../renderMcpWidgets/index.mjs";import{generateExtension as r}from"../generate/index.mjs";import{writeManifest as i,writeMcpManifest as a}from"../syncManifest/index.mjs";import{readServerContracts as o}from"../serverContracts/index.mjs";import{unistylesBabel as s}from"../unistylesBabel/index.mjs";import c from"node:fs";import l from"node:path";const
|
|
1
|
+
import{GENERATED_DIR as e,SERVER_DIST as t}from"../../constants/layout.mjs";import{renderMcpWidgets as n}from"../renderMcpWidgets/index.mjs";import{generateExtension as r}from"../generate/index.mjs";import{writeManifest as i,writeMcpManifest as a}from"../syncManifest/index.mjs";import{readServerContracts as o}from"../serverContracts/index.mjs";import{unistylesBabel as s}from"../unistylesBabel/index.mjs";import c from"node:fs";import l from"node:path";import u from"node:fs/promises";const d=`src/exports`,f=`apiContracts`;function p(e,t){let n=l.join(e,t);if(!c.existsSync(n))return{};let r={};for(let e of c.readdirSync(n)){if(!e.endsWith(`.ts`)||e.endsWith(`.d.ts`))continue;let n=e.slice(0,-3);r[n]=`${t}/${e}`}return r}function m(t,n=d){let r=`${e}/server.ts`;return{...p(t,n),...c.existsSync(l.join(t,r))?{"extensions/server":r}:{}}}function h(e,t,n){let r=l.join(e,t);if(!c.existsSync(r))return{};let i={};for(let e of c.readdirSync(r,{encoding:`utf8`,recursive:!0})){if(!n(e))continue;let r=e.split(l.sep).join(`/`);i[`./${t}/${r}`]=`./${t}/${r}`}return i}function g(e){let t=JSON.parse(c.readFileSync(l.join(e,`package.json`),`utf8`)),n=t.exports!==null&&typeof t.exports==`object`&&!Array.isArray(t.exports)?Object.fromEntries(Object.entries(t.exports).filter(([,e])=>typeof e==`string`)):{};return{...h(e,`skills`,e=>l.basename(e)===`SKILL.md`),...h(e,`themes`,e=>e.endsWith(`.json`)),...n}}function _(){return{name:`po-query-assets`,resolveId(e,t){let n=e.indexOf(`?`);if(n<0||t===void 0||!e.startsWith(`.`))return;let r=t.split(`?`)[0];return`${l.resolve(l.dirname(r),e.slice(0,n))}${e.slice(n)}`},async load(e){let t=e.indexOf(`?`);if(t<0)return;let n=e.slice(0,t),r=new URLSearchParams(e.slice(t+1));if(!r.has(`url`))return;let i=r.has(`worker`);return`export default import.meta.ROLLUP_FILE_URL_${this.emitFile(i?{type:`chunk`,id:n,name:l.basename(n,l.extname(n))}:{type:`asset`,name:l.basename(n),source:await u.readFile(n)})};`}}}function v(t=`web`){return{entry:{[`extensions/${t}`]:`${e}/${t}.ts`},clean:!1,dts:!1,exports:!1,format:[`esm`],platform:`browser`,fixedExtension:!0,external:e=>!e.startsWith(`.`)&&!l.isAbsolute(e),plugins:[_()],sourcemap:!0,unbundle:!1,tsconfig:t===`web`?`tsconfig.web.json`:`tsconfig.mcp.json`}}function y(t){return{entry:{"extensions/native":`${e}/native.ts`},clean:!1,dts:!1,exports:!1,format:[`esm`],platform:`neutral`,fixedExtension:!0,external:e=>!e.startsWith(`.`)&&!l.isAbsolute(e),plugins:[s(t)],sourcemap:!0,unbundle:!1,tsconfig:`tsconfig.native.json`}}const b=/^(?:extensions\/mcp(?:-ui)?|.+\.mcp)\.(?:mjs|cjs|d\.mts|d\.cts)(?:\.map)?$/u;function x(e,t){let n=l.join(e,`dist`);if(c.existsSync(n))for(let e of c.readdirSync(n,{recursive:!0,encoding:`utf8`})){if(b.test(e.split(l.sep).join(`/`))!==(t===`mcp`))continue;let r=l.join(n,e);c.lstatSync(r,{throwIfNoEntry:!1})?.isFile()&&c.rmSync(r)}}function S(s={packageDir:process.cwd()}){let c=s.packageDir??process.cwd(),m=r({...s,packageDir:c,check:s.check??!1});for(let e of m.notices)process.stderr.write(`[po-build] ${e.path}: ${e.message}\n`);let h=s.target===`mcp`;s.check||x(c,h?`mcp`:`normal`);let _=h&&!m.targets.includes(`mcp`),b=`\0po-empty-mcp`;_&&!s.check&&a(c,!1);let S=h?m.targets.includes(`mcp`)?{"extensions/mcp":`${e}/mcp.ts`}:{"extensions/mcp":b}:{...p(c,s.exportsDir??d),...m.targets.includes(`server`)?{"extensions/server":`${e}/server.ts`}:{},...s.entry},C={clean:!1,dts:!_&&{incremental:!0,parallel:!1,eager:!0},..._?{write:!1,plugins:[{name:`po-empty-mcp`,resolveId:e=>e===b?b:void 0,load:e=>e===b?`export {};`:void 0}]}:{},exports:!h&&{customExports:e=>{let t=Object.keys(S),n=t[0];return{...g(c),...e,...t.length===1&&n!==void 0&&n!==`index`&&!n.startsWith(`extensions/`)?{[`./${n}`]:e[`.`]}:{}}}},format:{esm:{},cjs:{dts:!1}},platform:`node`,outExtensions:({format:e})=>({js:e===`es`?`.mjs`:`.cjs`,dts:e===`es`?`.d.mts`:`.d.cts`}),sourcemap:!0},w=(r,s)=>({...C,entry:r,unbundle:!1,hooks:{"build:done":async()=>{h?a(c,m.targets.includes(`mcp`),n(m.graph,m.packageName,e).names):i({packageDir:c,manifest:JSON.parse(await u.readFile(l.join(c,`package.json`),`utf8`)),graph:m.graph,targets:m.targets,pluginId:m.pluginId,...m.targets.includes(`server`)?{serverContracts:await o(c,t)}:{}})}}}),T=!h&&f in S&&Object.keys(S).length>1?[((e,t)=>({...C,entry:e,unbundle:t}))({[f]:S[f]},!1),w(Object.fromEntries(Object.entries(S).filter(([e])=>e!==f)),!0)]:[w(S,!h)];if(h&&m.files.has(`generated/mcp-ui.ts`))return[...T,v(`mcp-ui`)];let E=h?[]:[...m.targets.includes(`web`)?[v()]:[],...m.targets.includes(`native`)?[y(c)]:[]];if(E.length>0){if(Object.keys(S).length>0)return[...T,...E];let[e,...t]=E;return[{...e,hooks:T[0].hooks},...t]}return T.length===1?T[0]:T}export{m as extensionSourceEntries,S as poExtension};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs","path","DEFAULT_EXPORTS_DIR","API_CONTRACTS_ENTRY","exportEntries","absolute","packageDir","exportsDir","entries","file","stem","extensionSourceEntries","server","GENERATED_DIR","resourceEntries","root","directory","relative","matches","normalized","staticExports","manifest","declared","target","queryAssetPlugin","queryAt","source","importer","importerFile","id","query","worker","browserConfig","nativeConfig","unistylesBabel","MCP_OUTPUT","cleanOutput","dist","owner","poExtension","options","result","generateExtension","notice","mcpOnly","emptyMcp","emptyEntry","writeMcpManifest","entry","baseNode","names","name","generated","format","node","renderMcpWidgets","writeManifest","readServerContracts","SERVER_DIST","configs","frontends","first","rest"],"sources":["../../../src/services/tsdownPreset/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\n\nimport { GENERATED_DIR, SERVER_DIST } from '../../constants/layout';\nimport { generateExtension } from '../generate';\nimport type { GenerateOptions } from '../generate/type';\nimport { renderMcpWidgets } from '../renderMcpWidgets';\nimport { readServerContracts } from '../serverContracts';\nimport { writeManifest, writeMcpManifest } from '../syncManifest';\nimport { unistylesBabel } from '../unistylesBabel';\nimport type { UnistylesBabelPlugin } from '../unistylesBabel/type';\n\ninterface EmittedFile {\n readonly type: 'asset' | 'chunk';\n readonly id?: string;\n readonly name: string;\n readonly source?: Buffer;\n}\n\ninterface QueryAssetPlugin {\n readonly name: string;\n resolveId(source: string, importer: string | undefined): string | undefined;\n load(this: { emitFile(file: EmittedFile): string }, id: string): string | undefined;\n}\n\n/** The shape a tsdown config needs from this preset, without importing tsdown's types. */\ninterface BrowserPresetConfig {\n entry: Record<string, string>;\n clean: boolean;\n dts: false;\n exports: boolean;\n format: 'esm'[];\n platform: 'browser';\n fixedExtension: boolean;\n external: (id: string) => boolean;\n plugins: QueryAssetPlugin[];\n sourcemap: boolean;\n unbundle: boolean;\n tsconfig: string;\n hooks?: PresetConfig['hooks'];\n}\n\n/** The separately bundled native half, shaped like the browser one. */\ninterface NativePresetConfig extends Omit<BrowserPresetConfig, 'platform' | 'plugins'> {\n platform: 'neutral';\n plugins: UnistylesBabelPlugin[];\n}\n\ninterface GeneratedExports {\n customExports: (exports: Record<string, unknown>) => Record<string, unknown>;\n}\n\ninterface BasePresetConfig {\n entry: Record<string, string>;\n clean: boolean;\n dts: false | { incremental: boolean; parallel: boolean; eager: boolean };\n write?: false;\n plugins?: QueryAssetPlugin[];\n exports: boolean | GeneratedExports;\n format: { esm: Record<string, never>; cjs: { dts: false } };\n platform: 'node';\n sourcemap: boolean;\n unbundle: boolean;\n outExtensions: ({ format }: { format: string }) => {\n js: string;\n dts: string;\n };\n}\n\ninterface PresetConfig extends BasePresetConfig {\n hooks?: { 'build:done': () => void | Promise<void> };\n}\n\nconst DEFAULT_EXPORTS_DIR = 'src/exports';\nconst API_CONTRACTS_ENTRY = 'apiContracts';\n\n/** `src/exports/apiContracts.ts` becomes the `apiContracts` entry; index keeps its name. */\nfunction exportEntries(packageDir: string, exportsDir: string): Record<string, string> {\n const absolute = path.join(packageDir, exportsDir);\n if (!fs.existsSync(absolute)) return {};\n const entries: Record<string, string> = {};\n for (const file of fs.readdirSync(absolute)) {\n if (!file.endsWith('.ts') || file.endsWith('.d.ts')) continue;\n const stem = file.slice(0, -'.ts'.length);\n entries[stem] = `${exportsDir}/${file}`;\n }\n return entries;\n}\n\n/**\n * Source file behind each published subpath of a folder-routed extension, keyed like\n * tsdown entries (`index`, `apiContracts`, `extensions/server`). Read from disk without\n * regenerating, so `generated/server.ts` must already exist. The private Worker build\n * uses this to bundle workspace sources instead of `dist/`.\n */\nexport function extensionSourceEntries(\n packageDir: string,\n exportsDir: string = DEFAULT_EXPORTS_DIR,\n): Record<string, string> {\n const server = `${GENERATED_DIR}/server.ts`;\n return {\n ...exportEntries(packageDir, exportsDir),\n ...(fs.existsSync(path.join(packageDir, server)) ? { 'extensions/server': server } : {}),\n };\n}\n\n/** Static resources receive package export entries without becoming tsdown inputs. */\nfunction resourceEntries(\n packageDir: string,\n directory: string,\n matches: (relative: string) => boolean,\n): Record<string, string> {\n const root = path.join(packageDir, directory);\n if (!fs.existsSync(root)) return {};\n const entries: Record<string, string> = {};\n for (const relative of fs.readdirSync(root, { encoding: 'utf8', recursive: true })) {\n if (!matches(relative)) continue;\n const normalized = relative.split(path.sep).join('/');\n entries[`./${directory}/${normalized}`] = `./${directory}/${normalized}`;\n }\n return entries;\n}\n\n/** Preserves declared resources and discovers default skill and theme resource exports. */\nfunction staticExports(packageDir: string): Record<string, unknown> {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >;\n const declared =\n manifest.exports !== null && typeof manifest.exports === 'object' && !Array.isArray(manifest.exports)\n ? Object.fromEntries(Object.entries(manifest.exports).filter(([, target]) => typeof target === 'string'))\n : {};\n return {\n ...resourceEntries(packageDir, 'skills', (relative) => path.basename(relative) === 'SKILL.md'),\n ...resourceEntries(packageDir, 'themes', (relative) => relative.endsWith('.json')),\n ...declared,\n };\n}\n/**\n * One tsdown config for a folder-routed extension.\n *\n * Entries come from the tree rather than a hand-written list, and the manifest\n * blocks that restate the tree are written after the build rather than kept in\n * step by hand. Browser contributions are emitted through a second bundled\n * config because their routed source is not published.\n */\nexport interface ExtensionPresetOptions extends GenerateOptions {\n /** Extra tsdown entries beyond the derived ones. */\n readonly entry?: Record<string, string>;\n /** Public export modules, package-relative. Defaults to `src/exports`. */\n readonly exportsDir?: string;\n}\n\n/**\n * Implements the Vite query imports extension browser code already uses.\n *\n * `?url` copies an opaque asset and `?worker&url` emits a separately bundled\n * worker chunk. Rolldown then replaces its file reference with an URL relative\n * to the extension bundle, which remains valid when the cockpit composes it.\n */\nfunction queryAssetPlugin(): QueryAssetPlugin {\n return {\n name: 'po-query-assets',\n resolveId(source, importer) {\n const queryAt = source.indexOf('?');\n if (queryAt < 0 || importer === undefined || !source.startsWith('.')) return undefined;\n const importerFile = importer.split('?')[0] as string;\n return `${path.resolve(path.dirname(importerFile), source.slice(0, queryAt))}${source.slice(queryAt)}`;\n },\n load(id) {\n const queryAt = id.indexOf('?');\n if (queryAt < 0) return undefined;\n const file = id.slice(0, queryAt);\n const query = new URLSearchParams(id.slice(queryAt + 1));\n if (!query.has('url')) return undefined;\n const worker = query.has('worker');\n const reference = this.emitFile(\n worker\n ? { type: 'chunk', id: file, name: path.basename(file, path.extname(file)) }\n : { type: 'asset', name: path.basename(file), source: fs.readFileSync(file) },\n );\n return `export default import.meta.ROLLUP_FILE_URL_${reference};`;\n },\n };\n}\n\n/** The separately bundled browser half of a routed extension. */\nfunction browserConfig(target: 'web' | 'mcp-ui' = 'web'): BrowserPresetConfig {\n return {\n entry: { [`extensions/${target}`]: `${GENERATED_DIR}/${target}.ts` },\n clean: false,\n // No declarations. Nothing imports this as a typed module: it has no\n // export subpath, and the cockpit consumes it as a bundle. The browser\n // half is still typechecked, by tsconfig.web.json in the typecheck script.\n dts: false,\n exports: false,\n format: ['esm'],\n platform: 'browser',\n // platform browser turns tsdown's fixedExtension off, which would emit\n // .js. The manifest names .mjs like the other two entries, so ask for it.\n fixedExtension: true,\n // Every bare specifier stays external and the cockpit resolves it, which\n // is what keeps one React and one store in the page no matter how many\n // plugins are installed. An allowlist would have to be kept in step with\n // the cockpit's; this cannot drift.\n //\n // A predicate rather than a pattern: rolldown tests `external` against the\n // resolved id, so /^[^.]/ also matches the absolute path of this package's\n // own routed file and would leave a dangling import to unbuilt .tsx.\n external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),\n plugins: [queryAssetPlugin()],\n sourcemap: true,\n // Bundled, unlike the node half. Unbundling would emit an import to the\n // routed source file, which is not published and is .tsx besides. One\n // module with only bare specifiers external is what the cockpit wants.\n unbundle: false,\n // Browser routes are excluded from the node tsconfig. Every browser-capable\n // extension owns this focused config for its matching typecheck.\n tsconfig: target === 'web' ? 'tsconfig.web.json' : 'tsconfig.mcp.json',\n };\n}\n\n/**\n * The separately bundled native half of a routed extension.\n *\n * Neutral rather than browser: React Native has neither the DOM nor Node, so\n * no condition set fits it better, and every bare specifier stays external\n * anyway. The CLI bundles this module into the signed composition and refuses\n * any import outside the native host module list, so nothing here has to.\n */\nfunction nativeConfig(packageDir: string): NativePresetConfig {\n return {\n entry: { 'extensions/native': `${GENERATED_DIR}/native.ts` },\n clean: false,\n dts: false,\n exports: false,\n format: ['esm'],\n platform: 'neutral',\n fixedExtension: true,\n // The same predicate as the browser half, for the same reason: the host\n // supplies React, React Native and Unistyles, one copy each.\n external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),\n plugins: [unistylesBabel(packageDir)],\n sourcemap: true,\n unbundle: false,\n // Native routes import react-native, which the DOM-typed web config must\n // not see, so they typecheck under their own config.\n tsconfig: 'tsconfig.native.json',\n };\n}\n\nconst MCP_OUTPUT = /^(?:extensions\\/mcp(?:-ui)?|.+\\.mcp)\\.(?:mjs|cjs|d\\.mts|d\\.cts)(?:\\.map)?$/u;\n\n/**\n * Removes files from dist that one build owns, so an entry deleted from the source tree does\n * not live on as a stale file (and in the Nx cache). The MCP build removes only its own files;\n * the normal build removes everything else and keeps the MCP files the other build wrote.\n */\nfunction cleanOutput(packageDir: string, owner: 'mcp' | 'normal'): void {\n const dist = path.join(packageDir, 'dist');\n if (!fs.existsSync(dist)) return;\n for (const relative of fs.readdirSync(dist, { recursive: true, encoding: 'utf8' })) {\n const isMcp = MCP_OUTPUT.test(relative.split(path.sep).join('/'));\n if (isMcp !== (owner === 'mcp')) continue;\n const absolute = path.join(dist, relative);\n if (fs.lstatSync(absolute, { throwIfNoEntry: false })?.isFile()) fs.rmSync(absolute);\n }\n}\n\nexport function poExtension(\n options: ExtensionPresetOptions = { packageDir: process.cwd() },\n): PresetConfig | (PresetConfig | BrowserPresetConfig | NativePresetConfig)[] {\n const packageDir = options.packageDir ?? process.cwd();\n const result = generateExtension({ ...options, packageDir, check: options.check ?? false });\n\n for (const notice of result.notices) process.stderr.write(`[po-build] ${notice.path}: ${notice.message}\\n`);\n\n const mcpOnly = options.target === 'mcp';\n // ponytail: the normal build cleans at config load, so it must run before the MCP build (every\n // build script does). Hashed assets of an mcp-ui bundle are not recognised and are rebuilt after.\n if (!options.check) cleanOutput(packageDir, mcpOnly ? 'mcp' : 'normal');\n const emptyMcp = mcpOnly && !result.targets.includes('mcp');\n const emptyEntry = '\\0po-empty-mcp';\n if (emptyMcp && !options.check) writeMcpManifest(packageDir, false);\n const entry: Record<string, string> = mcpOnly\n ? result.targets.includes('mcp')\n ? { 'extensions/mcp': `${GENERATED_DIR}/mcp.ts` }\n : { 'extensions/mcp': emptyEntry }\n : {\n ...exportEntries(packageDir, options.exportsDir ?? DEFAULT_EXPORTS_DIR),\n ...(result.targets.includes('server') ? { 'extensions/server': `${GENERATED_DIR}/server.ts` } : {}),\n ...options.entry,\n };\n\n const baseNode: Omit<BasePresetConfig, 'entry' | 'unbundle'> = {\n // MCP shares dist/ with the normal build, so neither build may erase the other.\n clean: false,\n dts: emptyMcp ? false : { incremental: true, parallel: false, eager: true },\n // tsdown rejects both an empty entry and an empty config array. A virtual\n // input completes its normal lifecycle without publishing an empty facet.\n ...(emptyMcp\n ? {\n write: false as const,\n plugins: [\n {\n name: 'po-empty-mcp',\n resolveId: (source: string) => (source === emptyEntry ? emptyEntry : undefined),\n load: (id: string) => (id === emptyEntry ? 'export {};' : undefined),\n },\n ],\n }\n : {}),\n exports: mcpOnly\n ? false\n : {\n customExports: (generated) => {\n const names = Object.keys(entry);\n const name = names[0];\n return {\n ...staticExports(packageDir),\n ...generated,\n ...(names.length === 1 && name !== undefined && name !== 'index' && !name.startsWith('extensions/')\n ? { [`./${name}`]: generated['.'] }\n : {}),\n };\n },\n },\n // CommonJS keeps its JavaScript but emits no declarations. Every export map\n // resolves types through .d.mts, and a CJS declaration build would rerun\n // the same whole-program tsgo emit a second time.\n format: { esm: {}, cjs: { dts: false } },\n // No minify. This is a library build, and a mangled stack trace inside a\n // published extension is far more expensive than the bytes it saves.\n // Native server entries use the .mjs filenames their hosts require.\n platform: 'node',\n outExtensions: ({ format }) => ({\n js: format === 'es' ? '.mjs' : '.cjs',\n dts: format === 'es' ? '.d.mts' : '.d.cts',\n }),\n sourcemap: true,\n };\n\n // Contract bundles are published as a self-contained graph. Normal extension\n // entries remain unbundled for routed runtime resources, while MCP remains\n // isolated from the shared dist directory.\n const node = (entries: Record<string, string>, _unbundle: boolean): PresetConfig => ({\n ...baseNode,\n entry: entries,\n // Public entry facades must remain bundled. Otherwise their relative\n // imports target unbundled implementation chunks, which Vite treats as\n // source files when resolving a workspace package.\n unbundle: false,\n hooks: {\n 'build:done': async () => {\n if (mcpOnly) {\n writeMcpManifest(\n packageDir,\n result.targets.includes('mcp'),\n renderMcpWidgets(result.graph, result.packageName, GENERATED_DIR).names,\n );\n return;\n }\n writeManifest({\n packageDir,\n manifest: JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >,\n graph: result.graph,\n targets: result.targets,\n pluginId: result.pluginId,\n ...(result.targets.includes('server')\n ? { serverContracts: await readServerContracts(packageDir, SERVER_DIST) }\n : {}),\n });\n },\n },\n });\n const unsyncedNode = (entries: Record<string, string>, unbundle: boolean): PresetConfig => ({\n ...baseNode,\n entry: entries,\n unbundle,\n });\n const configs =\n !mcpOnly && API_CONTRACTS_ENTRY in entry && Object.keys(entry).length > 1\n ? [\n unsyncedNode({ [API_CONTRACTS_ENTRY]: entry[API_CONTRACTS_ENTRY] }, false),\n node(Object.fromEntries(Object.entries(entry).filter(([name]) => name !== API_CONTRACTS_ENTRY)), true),\n ]\n : [node(entry, !mcpOnly)];\n if (mcpOnly && result.files.has(`${GENERATED_DIR}/mcp-ui.ts`)) return [...configs, browserConfig('mcp-ui')];\n const frontends: (BrowserPresetConfig | NativePresetConfig)[] = mcpOnly\n ? []\n : [\n ...(result.targets.includes('web') ? [browserConfig()] : []),\n ...(result.targets.includes('native') ? [nativeConfig(packageDir)] : []),\n ];\n if (frontends.length > 0) {\n if (Object.keys(entry).length > 0) return [...configs, ...frontends];\n // No node entry to carry the manifest sync, so the first frontend build does.\n const [first, ...rest] = frontends;\n return [{ ...first!, hooks: configs[0]!.hooks }, ...rest];\n }\n return configs.length === 1 ? configs[0]! : configs;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fsPromises","fs","path","fs$1","DEFAULT_EXPORTS_DIR","API_CONTRACTS_ENTRY","exportEntries","absolute","packageDir","exportsDir","entries","file","stem","extensionSourceEntries","server","GENERATED_DIR","resourceEntries","root","directory","relative","matches","normalized","staticExports","manifest","declared","target","queryAssetPlugin","queryAt","source","importer","importerFile","id","query","worker","browserConfig","nativeConfig","unistylesBabel","MCP_OUTPUT","cleanOutput","dist","owner","poExtension","options","result","generateExtension","notice","mcpOnly","emptyMcp","emptyEntry","writeMcpManifest","entry","baseNode","names","name","generated","format","node","renderMcpWidgets","writeManifest","readServerContracts","SERVER_DIST","configs","frontends","first","rest"],"sources":["../../../src/services/tsdownPreset/index.ts"],"sourcesContent":["// sync: Exported poExtension/extensionSourceEntries and the host customExports callback retain synchronous contracts.\nimport fs from 'node:fs';\nimport fsPromises from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { GENERATED_DIR, SERVER_DIST } from '../../constants/layout';\nimport { generateExtension } from '../generate';\nimport type { GenerateOptions } from '../generate/type';\nimport { renderMcpWidgets } from '../renderMcpWidgets';\nimport { readServerContracts } from '../serverContracts';\nimport { writeManifest, writeMcpManifest } from '../syncManifest';\nimport { unistylesBabel } from '../unistylesBabel';\nimport type { UnistylesBabelPlugin } from '../unistylesBabel/type';\n\ninterface EmittedFile {\n readonly type: 'asset' | 'chunk';\n readonly id?: string;\n readonly name: string;\n readonly source?: Buffer;\n}\n\ninterface QueryAssetPlugin {\n readonly name: string;\n resolveId(source: string, importer: string | undefined): string | undefined;\n load(this: { emitFile(file: EmittedFile): string }, id: string): string | undefined | Promise<string | undefined>;\n}\n\n/** The shape a tsdown config needs from this preset, without importing tsdown's types. */\ninterface BrowserPresetConfig {\n entry: Record<string, string>;\n clean: boolean;\n dts: false;\n exports: boolean;\n format: 'esm'[];\n platform: 'browser';\n fixedExtension: boolean;\n external: (id: string) => boolean;\n plugins: QueryAssetPlugin[];\n sourcemap: boolean;\n unbundle: boolean;\n tsconfig: string;\n hooks?: PresetConfig['hooks'];\n}\n\n/** The separately bundled native half, shaped like the browser one. */\ninterface NativePresetConfig extends Omit<BrowserPresetConfig, 'platform' | 'plugins'> {\n platform: 'neutral';\n plugins: UnistylesBabelPlugin[];\n}\n\ninterface GeneratedExports {\n customExports: (exports: Record<string, unknown>) => Record<string, unknown>;\n}\n\ninterface BasePresetConfig {\n entry: Record<string, string>;\n clean: boolean;\n dts: false | { incremental: boolean; parallel: boolean; eager: boolean };\n write?: false;\n plugins?: QueryAssetPlugin[];\n exports: boolean | GeneratedExports;\n format: { esm: Record<string, never>; cjs: { dts: false } };\n platform: 'node';\n sourcemap: boolean;\n unbundle: boolean;\n outExtensions: ({ format }: { format: string }) => {\n js: string;\n dts: string;\n };\n}\n\ninterface PresetConfig extends BasePresetConfig {\n hooks?: { 'build:done': () => void | Promise<void> };\n}\n\nconst DEFAULT_EXPORTS_DIR = 'src/exports';\nconst API_CONTRACTS_ENTRY = 'apiContracts';\n\n/** `src/exports/apiContracts.ts` becomes the `apiContracts` entry; index keeps its name. */\nfunction exportEntries(packageDir: string, exportsDir: string): Record<string, string> {\n const absolute = path.join(packageDir, exportsDir);\n if (!fs.existsSync(absolute)) return {};\n const entries: Record<string, string> = {};\n for (const file of fs.readdirSync(absolute)) {\n if (!file.endsWith('.ts') || file.endsWith('.d.ts')) continue;\n const stem = file.slice(0, -'.ts'.length);\n entries[stem] = `${exportsDir}/${file}`;\n }\n return entries;\n}\n\n/**\n * Source file behind each published subpath of a folder-routed extension, keyed like\n * tsdown entries (`index`, `apiContracts`, `extensions/server`). Read from disk without\n * regenerating, so `generated/server.ts` must already exist. The private Worker build\n * uses this to bundle workspace sources instead of `dist/`.\n */\nexport function extensionSourceEntries(\n packageDir: string,\n exportsDir: string = DEFAULT_EXPORTS_DIR,\n): Record<string, string> {\n const server = `${GENERATED_DIR}/server.ts`;\n return {\n ...exportEntries(packageDir, exportsDir),\n ...(fs.existsSync(path.join(packageDir, server)) ? { 'extensions/server': server } : {}),\n };\n}\n\n/** Static resources receive package export entries without becoming tsdown inputs. */\nfunction resourceEntries(\n packageDir: string,\n directory: string,\n matches: (relative: string) => boolean,\n): Record<string, string> {\n const root = path.join(packageDir, directory);\n if (!fs.existsSync(root)) return {};\n const entries: Record<string, string> = {};\n for (const relative of fs.readdirSync(root, { encoding: 'utf8', recursive: true })) {\n if (!matches(relative)) continue;\n const normalized = relative.split(path.sep).join('/');\n entries[`./${directory}/${normalized}`] = `./${directory}/${normalized}`;\n }\n return entries;\n}\n\n/** Preserves declared resources and discovers default skill and theme resource exports. */\nfunction staticExports(packageDir: string): Record<string, unknown> {\n const manifest = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >;\n const declared =\n manifest.exports !== null && typeof manifest.exports === 'object' && !Array.isArray(manifest.exports)\n ? Object.fromEntries(Object.entries(manifest.exports).filter(([, target]) => typeof target === 'string'))\n : {};\n return {\n ...resourceEntries(packageDir, 'skills', (relative) => path.basename(relative) === 'SKILL.md'),\n ...resourceEntries(packageDir, 'themes', (relative) => relative.endsWith('.json')),\n ...declared,\n };\n}\n/**\n * One tsdown config for a folder-routed extension.\n *\n * Entries come from the tree rather than a hand-written list, and the manifest\n * blocks that restate the tree are written after the build rather than kept in\n * step by hand. Browser contributions are emitted through a second bundled\n * config because their routed source is not published.\n */\nexport interface ExtensionPresetOptions extends GenerateOptions {\n /** Extra tsdown entries beyond the derived ones. */\n readonly entry?: Record<string, string>;\n /** Public export modules, package-relative. Defaults to `src/exports`. */\n readonly exportsDir?: string;\n}\n\n/**\n * Implements the Vite query imports extension browser code already uses.\n *\n * `?url` copies an opaque asset and `?worker&url` emits a separately bundled\n * worker chunk. Rolldown then replaces its file reference with an URL relative\n * to the extension bundle, which remains valid when the cockpit composes it.\n */\nfunction queryAssetPlugin(): QueryAssetPlugin {\n return {\n name: 'po-query-assets',\n resolveId(source, importer) {\n const queryAt = source.indexOf('?');\n if (queryAt < 0 || importer === undefined || !source.startsWith('.')) return undefined;\n const importerFile = importer.split('?')[0] as string;\n return `${path.resolve(path.dirname(importerFile), source.slice(0, queryAt))}${source.slice(queryAt)}`;\n },\n async load(id) {\n const queryAt = id.indexOf('?');\n if (queryAt < 0) return undefined;\n const file = id.slice(0, queryAt);\n const query = new URLSearchParams(id.slice(queryAt + 1));\n if (!query.has('url')) return undefined;\n const worker = query.has('worker');\n const reference = this.emitFile(\n worker\n ? { type: 'chunk', id: file, name: path.basename(file, path.extname(file)) }\n : { type: 'asset', name: path.basename(file), source: await fsPromises.readFile(file) },\n );\n return `export default import.meta.ROLLUP_FILE_URL_${reference};`;\n },\n };\n}\n\n/** The separately bundled browser half of a routed extension. */\nfunction browserConfig(target: 'web' | 'mcp-ui' = 'web'): BrowserPresetConfig {\n return {\n entry: { [`extensions/${target}`]: `${GENERATED_DIR}/${target}.ts` },\n clean: false,\n // No declarations. Nothing imports this as a typed module: it has no\n // export subpath, and the cockpit consumes it as a bundle. The browser\n // half is still typechecked, by tsconfig.web.json in the typecheck script.\n dts: false,\n exports: false,\n format: ['esm'],\n platform: 'browser',\n // platform browser turns tsdown's fixedExtension off, which would emit\n // .js. The manifest names .mjs like the other two entries, so ask for it.\n fixedExtension: true,\n // Every bare specifier stays external and the cockpit resolves it, which\n // is what keeps one React and one store in the page no matter how many\n // plugins are installed. An allowlist would have to be kept in step with\n // the cockpit's; this cannot drift.\n //\n // A predicate rather than a pattern: rolldown tests `external` against the\n // resolved id, so /^[^.]/ also matches the absolute path of this package's\n // own routed file and would leave a dangling import to unbuilt .tsx.\n external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),\n plugins: [queryAssetPlugin()],\n sourcemap: true,\n // Bundled, unlike the node half. Unbundling would emit an import to the\n // routed source file, which is not published and is .tsx besides. One\n // module with only bare specifiers external is what the cockpit wants.\n unbundle: false,\n // Browser routes are excluded from the node tsconfig. Every browser-capable\n // extension owns this focused config for its matching typecheck.\n tsconfig: target === 'web' ? 'tsconfig.web.json' : 'tsconfig.mcp.json',\n };\n}\n\n/**\n * The separately bundled native half of a routed extension.\n *\n * Neutral rather than browser: React Native has neither the DOM nor Node, so\n * no condition set fits it better, and every bare specifier stays external\n * anyway. The CLI bundles this module into the signed composition and refuses\n * any import outside the native host module list, so nothing here has to.\n */\nfunction nativeConfig(packageDir: string): NativePresetConfig {\n return {\n entry: { 'extensions/native': `${GENERATED_DIR}/native.ts` },\n clean: false,\n dts: false,\n exports: false,\n format: ['esm'],\n platform: 'neutral',\n fixedExtension: true,\n // The same predicate as the browser half, for the same reason: the host\n // supplies React, React Native and Unistyles, one copy each.\n external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id),\n plugins: [unistylesBabel(packageDir)],\n sourcemap: true,\n unbundle: false,\n // Native routes import react-native, which the DOM-typed web config must\n // not see, so they typecheck under their own config.\n tsconfig: 'tsconfig.native.json',\n };\n}\n\nconst MCP_OUTPUT = /^(?:extensions\\/mcp(?:-ui)?|.+\\.mcp)\\.(?:mjs|cjs|d\\.mts|d\\.cts)(?:\\.map)?$/u;\n\n/**\n * Removes files from dist that one build owns, so an entry deleted from the source tree does\n * not live on as a stale file (and in the Nx cache). The MCP build removes only its own files;\n * the normal build removes everything else and keeps the MCP files the other build wrote.\n */\nfunction cleanOutput(packageDir: string, owner: 'mcp' | 'normal'): void {\n const dist = path.join(packageDir, 'dist');\n if (!fs.existsSync(dist)) return;\n for (const relative of fs.readdirSync(dist, { recursive: true, encoding: 'utf8' })) {\n const isMcp = MCP_OUTPUT.test(relative.split(path.sep).join('/'));\n if (isMcp !== (owner === 'mcp')) continue;\n const absolute = path.join(dist, relative);\n if (fs.lstatSync(absolute, { throwIfNoEntry: false })?.isFile()) fs.rmSync(absolute);\n }\n}\n\nexport function poExtension(\n options: ExtensionPresetOptions = { packageDir: process.cwd() },\n): PresetConfig | (PresetConfig | BrowserPresetConfig | NativePresetConfig)[] {\n const packageDir = options.packageDir ?? process.cwd();\n const result = generateExtension({ ...options, packageDir, check: options.check ?? false });\n\n for (const notice of result.notices) process.stderr.write(`[po-build] ${notice.path}: ${notice.message}\\n`);\n\n const mcpOnly = options.target === 'mcp';\n // ponytail: the normal build cleans at config load, so it must run before the MCP build (every\n // build script does). Hashed assets of an mcp-ui bundle are not recognised and are rebuilt after.\n if (!options.check) cleanOutput(packageDir, mcpOnly ? 'mcp' : 'normal');\n const emptyMcp = mcpOnly && !result.targets.includes('mcp');\n const emptyEntry = '\\0po-empty-mcp';\n if (emptyMcp && !options.check) writeMcpManifest(packageDir, false);\n const entry: Record<string, string> = mcpOnly\n ? result.targets.includes('mcp')\n ? { 'extensions/mcp': `${GENERATED_DIR}/mcp.ts` }\n : { 'extensions/mcp': emptyEntry }\n : {\n ...exportEntries(packageDir, options.exportsDir ?? DEFAULT_EXPORTS_DIR),\n ...(result.targets.includes('server') ? { 'extensions/server': `${GENERATED_DIR}/server.ts` } : {}),\n ...options.entry,\n };\n\n const baseNode: Omit<BasePresetConfig, 'entry' | 'unbundle'> = {\n // MCP shares dist/ with the normal build, so neither build may erase the other.\n clean: false,\n dts: emptyMcp ? false : { incremental: true, parallel: false, eager: true },\n // tsdown rejects both an empty entry and an empty config array. A virtual\n // input completes its normal lifecycle without publishing an empty facet.\n ...(emptyMcp\n ? {\n write: false as const,\n plugins: [\n {\n name: 'po-empty-mcp',\n resolveId: (source: string) => (source === emptyEntry ? emptyEntry : undefined),\n load: (id: string) => (id === emptyEntry ? 'export {};' : undefined),\n },\n ],\n }\n : {}),\n exports: mcpOnly\n ? false\n : {\n customExports: (generated) => {\n const names = Object.keys(entry);\n const name = names[0];\n return {\n ...staticExports(packageDir),\n ...generated,\n ...(names.length === 1 && name !== undefined && name !== 'index' && !name.startsWith('extensions/')\n ? { [`./${name}`]: generated['.'] }\n : {}),\n };\n },\n },\n // CommonJS keeps its JavaScript but emits no declarations. Every export map\n // resolves types through .d.mts, and a CJS declaration build would rerun\n // the same whole-program tsgo emit a second time.\n format: { esm: {}, cjs: { dts: false } },\n // No minify. This is a library build, and a mangled stack trace inside a\n // published extension is far more expensive than the bytes it saves.\n // Native server entries use the .mjs filenames their hosts require.\n platform: 'node',\n outExtensions: ({ format }) => ({\n js: format === 'es' ? '.mjs' : '.cjs',\n dts: format === 'es' ? '.d.mts' : '.d.cts',\n }),\n sourcemap: true,\n };\n\n // Contract bundles are published as a self-contained graph. Normal extension\n // entries remain unbundled for routed runtime resources, while MCP remains\n // isolated from the shared dist directory.\n const node = (entries: Record<string, string>, _unbundle: boolean): PresetConfig => ({\n ...baseNode,\n entry: entries,\n // Public entry facades must remain bundled. Otherwise their relative\n // imports target unbundled implementation chunks, which Vite treats as\n // source files when resolving a workspace package.\n unbundle: false,\n hooks: {\n 'build:done': async () => {\n if (mcpOnly) {\n writeMcpManifest(\n packageDir,\n result.targets.includes('mcp'),\n renderMcpWidgets(result.graph, result.packageName, GENERATED_DIR).names,\n );\n return;\n }\n writeManifest({\n packageDir,\n manifest: JSON.parse(await fsPromises.readFile(path.join(packageDir, 'package.json'), 'utf8')) as Record<\n string,\n unknown\n >,\n graph: result.graph,\n targets: result.targets,\n pluginId: result.pluginId,\n ...(result.targets.includes('server')\n ? { serverContracts: await readServerContracts(packageDir, SERVER_DIST) }\n : {}),\n });\n },\n },\n });\n const unsyncedNode = (entries: Record<string, string>, unbundle: boolean): PresetConfig => ({\n ...baseNode,\n entry: entries,\n unbundle,\n });\n const configs =\n !mcpOnly && API_CONTRACTS_ENTRY in entry && Object.keys(entry).length > 1\n ? [\n unsyncedNode({ [API_CONTRACTS_ENTRY]: entry[API_CONTRACTS_ENTRY] }, false),\n node(Object.fromEntries(Object.entries(entry).filter(([name]) => name !== API_CONTRACTS_ENTRY)), true),\n ]\n : [node(entry, !mcpOnly)];\n if (mcpOnly && result.files.has(`${GENERATED_DIR}/mcp-ui.ts`)) return [...configs, browserConfig('mcp-ui')];\n const frontends: (BrowserPresetConfig | NativePresetConfig)[] = mcpOnly\n ? []\n : [\n ...(result.targets.includes('web') ? [browserConfig()] : []),\n ...(result.targets.includes('native') ? [nativeConfig(packageDir)] : []),\n ];\n if (frontends.length > 0) {\n if (Object.keys(entry).length > 0) return [...configs, ...frontends];\n // No node entry to carry the manifest sync, so the first frontend build does.\n const [first, ...rest] = frontends;\n return [{ ...first!, hooks: configs[0]!.hooks }, ...rest];\n }\n return configs.length === 1 ? configs[0]! : configs;\n}\n"],"mappings":"ueA2EA,MAAMI,EAAsB,cACtBC,EAAsB,eAG5B,SAASC,EAAc,EAAoB,EAA4C,CACrF,IAAMC,EAAWL,EAAK,KAAKM,EAAYC,CAAU,EACjD,GAAI,CAACR,EAAG,WAAWM,CAAQ,EAAG,MAAO,CAAC,EACtC,IAAMG,EAAkC,CAAC,EACzC,IAAK,IAAMC,KAAQV,EAAG,YAAYM,CAAQ,EAAG,CAC3C,GAAI,CAACI,EAAK,SAAS,KAAK,GAAKA,EAAK,SAAS,OAAO,EAAG,SACrD,IAAMC,EAAOD,EAAK,MAAM,EAAG,EAAa,EACxC,EAAQC,GAAQ,GAAGH,EAAW,GAAGE,GACnC,CACA,OAAOD,CACT,CAQA,SAAgBG,EACd,EACA,EAAqBT,EACG,CACxB,IAAMU,EAAS,GAAGC,EAAc,YAChC,MAAO,CACL,GAAGT,EAAcE,EAAYC,CAAU,EACvC,GAAIR,EAAG,WAAWC,EAAK,KAAKM,EAAYM,CAAM,CAAC,EAAI,CAAE,oBAAqBA,CAAO,EAAI,CAAC,CACxF,CACF,CAGA,SAASE,EACP,EACA,EACA,EACwB,CACxB,IAAMC,EAAOf,EAAK,KAAKM,EAAYU,CAAS,EAC5C,GAAI,CAACjB,EAAG,WAAWgB,CAAI,EAAG,MAAO,CAAC,EAClC,IAAMP,EAAkC,CAAC,EACzC,IAAK,IAAMS,KAAYlB,EAAG,YAAYgB,EAAM,CAAE,SAAU,OAAQ,UAAW,EAAK,CAAC,EAAG,CAClF,GAAI,CAACG,EAAQD,CAAQ,EAAG,SACxB,IAAME,EAAaF,EAAS,MAAMjB,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,EACpD,EAAQ,KAAKgB,EAAU,GAAGG,KAAgB,KAAKH,EAAU,GAAGG,GAC9D,CACA,OAAOX,CACT,CAGA,SAASY,EAAc,EAA6C,CAClE,IAAMC,EAAW,KAAK,MAAMtB,EAAG,aAAaC,EAAK,KAAKM,EAAY,cAAc,EAAG,MAAM,CAAC,EAIpFgB,EACJD,EAAS,UAAY,MAAQ,OAAOA,EAAS,SAAY,UAAY,CAAC,MAAM,QAAQA,EAAS,OAAO,EAChG,OAAO,YAAY,OAAO,QAAQA,EAAS,OAAO,CAAC,CAAC,QAAQ,EAAGE,KAAY,OAAOA,GAAW,QAAQ,CAAC,EACtG,CAAC,EACP,MAAO,CACL,GAAGT,EAAgBR,EAAY,SAAW,GAAaN,EAAK,SAASiB,CAAQ,IAAM,UAAU,EAC7F,GAAGH,EAAgBR,EAAY,SAAW,GAAaW,EAAS,SAAS,OAAO,CAAC,EACjF,GAAGK,CACL,CACF,CAuBA,SAASE,GAAqC,CAC5C,MAAO,CACL,KAAM,kBACN,UAAU,EAAQ,EAAU,CAC1B,IAAMC,EAAUC,EAAO,QAAQ,GAAG,EAClC,GAAID,EAAU,GAAKE,IAAa,IAAA,IAAa,CAACD,EAAO,WAAW,GAAG,EAAG,OACtE,IAAME,EAAeD,EAAS,MAAM,GAAG,CAAC,CAAC,GACzC,MAAO,GAAG3B,EAAK,QAAQA,EAAK,QAAQ4B,CAAY,EAAGF,EAAO,MAAM,EAAGD,CAAO,CAAC,IAAIC,EAAO,MAAMD,CAAO,GACrG,EACA,MAAM,KAAK,EAAI,CACb,IAAMA,EAAUI,EAAG,QAAQ,GAAG,EAC9B,GAAIJ,EAAU,EAAG,OACjB,IAAMhB,EAAOoB,EAAG,MAAM,EAAGJ,CAAO,EAC1BK,EAAQ,IAAI,gBAAgBD,EAAG,MAAMJ,EAAU,CAAC,CAAC,EACvD,GAAI,CAACK,EAAM,IAAI,KAAK,EAAG,OACvB,IAAMC,EAASD,EAAM,IAAI,QAAQ,EAMjC,MAAO,8CALW,KAAK,SACrBC,EACI,CAAE,KAAM,QAAS,GAAItB,EAAM,KAAMT,EAAK,SAASS,EAAMT,EAAK,QAAQS,CAAI,CAAC,CAAE,EACzE,CAAE,KAAM,QAAS,KAAMT,EAAK,SAASS,CAAI,EAAG,OAAQ,MAAMX,EAAW,SAASW,CAAI,CAAE,CAE7B,EAAE,EACjE,CACF,CACF,CAGA,SAASuB,EAAc,EAA2B,MAA4B,CAC5E,MAAO,CACL,MAAO,EAAG,cAAcT,KAAW,GAAGV,EAAc,GAAGU,EAAO,IAAK,EACnE,MAAO,GAIP,IAAK,GACL,QAAS,GACT,OAAQ,CAAC,KAAK,EACd,SAAU,UAGV,eAAgB,GAShB,SAAW,GAAe,CAACM,EAAG,WAAW,GAAG,GAAK,CAAC7B,EAAK,WAAW6B,CAAE,EACpE,QAAS,CAACL,EAAiB,CAAC,EAC5B,UAAW,GAIX,SAAU,GAGV,SAAUD,IAAW,MAAQ,oBAAsB,mBACrD,CACF,CAUA,SAASU,EAAa,EAAwC,CAC5D,MAAO,CACL,MAAO,CAAE,oBAAqB,GAAGpB,EAAc,WAAY,EAC3D,MAAO,GACP,IAAK,GACL,QAAS,GACT,OAAQ,CAAC,KAAK,EACd,SAAU,UACV,eAAgB,GAGhB,SAAW,GAAe,CAACgB,EAAG,WAAW,GAAG,GAAK,CAAC7B,EAAK,WAAW6B,CAAE,EACpE,QAAS,CAACK,EAAe5B,CAAU,CAAC,EACpC,UAAW,GACX,SAAU,GAGV,SAAU,sBACZ,CACF,CAEA,MAAM6B,EAAa,8EAOnB,SAASC,EAAY,EAAoB,EAA+B,CACtE,IAAMC,EAAOrC,EAAK,KAAKM,EAAY,MAAM,EACzC,GAAKP,EAAG,WAAWsC,CAAI,EACvB,IAAK,IAAMpB,KAAYlB,EAAG,YAAYsC,EAAM,CAAE,UAAW,GAAM,SAAU,MAAO,CAAC,EAAG,CAElF,GADcF,EAAW,KAAKlB,EAAS,MAAMjB,EAAK,GAAG,CAAC,CAAC,KAAK,GAAG,CACvD,KAAOsC,IAAU,OAAQ,SACjC,IAAMjC,EAAWL,EAAK,KAAKqC,EAAMpB,CAAQ,EACrClB,EAAG,UAAUM,EAAU,CAAE,eAAgB,EAAM,CAAC,CAAC,EAAE,OAAO,GAAGN,EAAG,OAAOM,CAAQ,CACrF,CACF,CAEA,SAAgBkC,EACd,EAAkC,CAAE,WAAY,QAAQ,IAAI,CAAE,EACc,CAC5E,IAAMjC,EAAakC,EAAQ,YAAc,QAAQ,IAAI,EAC/CC,EAASC,EAAkB,CAAE,GAAGF,EAAS,aAAY,MAAOA,EAAQ,OAAS,EAAM,CAAC,EAE1F,IAAK,IAAMG,KAAUF,EAAO,QAAS,QAAQ,OAAO,MAAM,cAAcE,EAAO,KAAK,IAAIA,EAAO,QAAQ,GAAG,EAE1G,IAAMC,EAAUJ,EAAQ,SAAW,MAG9BA,EAAQ,OAAOJ,EAAY9B,EAAYsC,EAAU,MAAQ,QAAQ,EACtE,IAAMC,EAAWD,GAAW,CAACH,EAAO,QAAQ,SAAS,KAAK,EACpDK,EAAa,iBACfD,GAAY,CAACL,EAAQ,OAAOO,EAAiBzC,EAAY,EAAK,EAClE,IAAM0C,EAAgCJ,EAClCH,EAAO,QAAQ,SAAS,KAAK,EAC3B,CAAE,iBAAkB,GAAG5B,EAAc,QAAS,EAC9C,CAAE,iBAAkBiC,CAAW,EACjC,CACE,GAAG1C,EAAcE,EAAYkC,EAAQ,YAActC,CAAmB,EACtE,GAAIuC,EAAO,QAAQ,SAAS,QAAQ,EAAI,CAAE,oBAAqB,GAAG5B,EAAc,WAAY,EAAI,CAAC,EACjG,GAAG2B,EAAQ,KACb,EAEES,EAAyD,CAE7D,MAAO,GACP,IAAK,IAAmB,CAAE,YAAa,GAAM,SAAU,GAAO,MAAO,EAAK,EAG1E,GAAIJ,EACA,CACE,MAAO,GACP,QAAS,CACP,CACE,KAAM,eACN,UAAY,GAAoBnB,IAAWoB,EAAaA,EAAa,IAAA,GACrE,KAAO,GAAgBjB,IAAOiB,EAAa,aAAe,IAAA,EAC5D,CACF,CACF,EACA,CAAC,EACL,QAAS,IAEL,CACE,cAAgB,GAAc,CAC5B,IAAMI,EAAQ,OAAO,KAAKF,CAAK,EACzBG,EAAOD,EAAM,GACnB,MAAO,CACL,GAAG9B,EAAcd,CAAU,EAC3B,GAAG8C,EACH,GAAIF,EAAM,SAAW,GAAKC,IAAS,IAAA,IAAaA,IAAS,SAAW,CAACA,EAAK,WAAW,aAAa,EAC9F,EAAG,KAAKA,KAASC,EAAU,IAAK,EAChC,CAAC,CACP,CACF,CACF,EAIJ,OAAQ,CAAE,IAAK,CAAC,EAAG,IAAK,CAAE,IAAK,EAAM,CAAE,EAIvC,SAAU,OACV,eAAgB,CAAE,aAAc,CAC9B,GAAIC,IAAW,KAAO,OAAS,OAC/B,IAAKA,IAAW,KAAO,SAAW,QACpC,GACA,UAAW,EACb,EAKMC,GAAQ,EAAiC,KAAsC,CACnF,GAAGL,EACH,MAAOzC,EAIP,SAAU,GACV,MAAO,CACL,aAAc,SAAY,CACpBoC,EACFG,EACEzC,EACAmC,EAAO,QAAQ,SAAS,KAAK,EAC7Bc,EAAiBd,EAAO,MAAOA,EAAO,YAAa5B,CAAa,CAAC,CAAC,KACpE,EAGF2C,EAAc,CACZ,aACA,SAAU,KAAK,MAAM,MAAM1D,EAAW,SAASE,EAAK,KAAKM,EAAY,cAAc,EAAG,MAAM,CAAC,EAI7F,MAAOmC,EAAO,MACd,QAASA,EAAO,QAChB,SAAUA,EAAO,SACjB,GAAIA,EAAO,QAAQ,SAAS,QAAQ,EAChC,CAAE,gBAAiB,MAAMgB,EAAoBnD,EAAYoD,CAAW,CAAE,EACtE,CAAC,CACP,CAAC,CACH,CACF,CACF,GAMMC,EACJ,CAACf,GAAWzC,KAAuB6C,GAAS,OAAO,KAAKA,CAAK,CAAC,CAAC,OAAS,EACpE,GAPgB,EAAiC,KAAqC,CAC1F,GAAGC,EACH,MAAOzC,EACP,UACF,GAIQ,CAAa,EAAGL,GAAsB6C,EAAM7C,EAAqB,EAAG,EAAK,EACzEmD,EAAK,OAAO,YAAY,OAAO,QAAQN,CAAK,CAAC,CAAC,QAAQ,CAACG,KAAUA,IAAShD,CAAmB,CAAC,EAAG,EAAI,CACvG,EACA,CAACmD,EAAKN,EAAO,CAACJ,CAAO,CAAC,EAC5B,GAAIA,GAAWH,EAAO,MAAM,IAAI,qBAA4B,EAAG,MAAO,CAAC,GAAGkB,EAAS3B,EAAc,QAAQ,CAAC,EAC1G,IAAM4B,EAA0DhB,EAC5D,CAAC,EACD,CACE,GAAIH,EAAO,QAAQ,SAAS,KAAK,EAAI,CAACT,EAAc,CAAC,EAAI,CAAC,EAC1D,GAAIS,EAAO,QAAQ,SAAS,QAAQ,EAAI,CAACR,EAAa3B,CAAU,CAAC,EAAI,CAAC,CACxE,EACJ,GAAIsD,EAAU,OAAS,EAAG,CACxB,GAAI,OAAO,KAAKZ,CAAK,CAAC,CAAC,OAAS,EAAG,MAAO,CAAC,GAAGW,EAAS,GAAGC,CAAS,EAEnE,GAAM,CAACC,EAAO,GAAGC,GAAQF,EACzB,MAAO,CAAC,CAAE,GAAGC,EAAQ,MAAOF,EAAQ,EAAE,CAAE,KAAM,EAAG,GAAGG,CAAI,CAC1D,CACA,OAAOH,EAAQ,SAAW,EAAIA,EAAQ,GAAMA,CAC9C"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:
|
|
1
|
+
const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs/promises");n=e.__toESM(n,1);let r=require("node:module");const i=`@babel/core`,a=`react-native-unistyles/plugin`,o=[`.ts`,`.tsx`];function s(e){let n=(0,r.createRequire)(t.default.join(e,`package.json`));try{let e=n(i),t=n(a),r=typeof t==`function`?t:t?.default;if(typeof r!=`function`)throw Error(`${a} does not export a Babel plugin`);return{babel:e,plugin:r}}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`[po-build] The native build needs ${i} and react-native-unistyles, resolved from ${e}. Add both to that package's devDependencies. ${n}`)}}function c(e){if(e.startsWith(`\0`))return;let t=e.indexOf(`?`);return t<0?e:e.slice(0,t)}function l(e,n){if(e.endsWith(`.d.ts`)||!o.includes(t.default.extname(e)))return!1;let r=t.default.relative(n,e);return r!==``&&!r.startsWith(`..`)&&!t.default.isAbsolute(r)}function u(e){let r,i;return{name:`po-unistyles-babel`,async transform(a,o){let u=c(o);if(u===void 0||!a.includes(`react-native`))return;let d=await(r??=n.default.realpath(e));if(!l(u,t.default.join(d,`src`)))return;i??=s(d);let f=await i.babel.transformAsync(a,{filename:u,cwd:d,root:d,babelrc:!1,configFile:!1,sourceMaps:!0,parserOpts:{plugins:[`typescript`,`jsx`]},plugins:[[i.plugin,{root:`src`}]]});if(typeof f?.code==`string`)return{code:f.code,map:f.map??null}}}}exports.unistylesBabel=u;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["createRequire","path","fs","require_runtime","
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["createRequire","path","fs","require_runtime","node_path","node_fs_promises","node_module","BABEL_CORE","UNISTYLES_PLUGIN","TRANSFORMED_EXTENSIONS","loadBabel","requireFromPackage","packageDir","babel","loaded","plugin","error","reason","fileOf","id","queryAt","isAuthoredSource","file","relative","sourceDir","unistylesBabel","canonicalRoot","code","root","result"],"sources":["../../../src/services/unistylesBabel/index.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\n\nimport type { BabelCore, TransformResult, UnistylesBabelPlugin } from './type';\n\n/** The authored source root, package-relative. Unistyles resolves it against Babel's root. */\nconst SOURCE_ROOT = 'src';\nconst BABEL_CORE = '@babel/core';\nconst UNISTYLES_PLUGIN = 'react-native-unistyles/plugin';\n/** Both specifiers the Unistyles rewrite reacts to contain this, so a file without it has nothing to rewrite. */\nconst REACT_NATIVE_MARKER = 'react-native';\nconst TRANSFORMED_EXTENSIONS: readonly string[] = ['.ts', '.tsx'];\n\ninterface LoadedBabel {\n readonly babel: BabelCore;\n readonly plugin: unknown;\n}\n\n/**\n * Loads Babel and the Unistyles plugin from the extension package, not from po-build.\n *\n * po-build stays free of both: only a package that ships `.native` files pays\n * for them, and the copy it resolves is the one its own lockfile pinned.\n */\nfunction loadBabel(packageDir: string): LoadedBabel {\n const requireFromPackage = createRequire(path.join(packageDir, 'package.json'));\n try {\n const babel = requireFromPackage(BABEL_CORE) as BabelCore;\n const loaded = requireFromPackage(UNISTYLES_PLUGIN) as unknown;\n const plugin =\n typeof loaded === 'function' ? loaded : (loaded as { default?: unknown } | null | undefined)?.default;\n if (typeof plugin !== 'function') throw new Error(`${UNISTYLES_PLUGIN} does not export a Babel plugin`);\n return { babel, plugin };\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n throw new Error(\n `[po-build] The native build needs ${BABEL_CORE} and react-native-unistyles, resolved from ${packageDir}. ` +\n `Add both to that package's devDependencies. ${reason}`,\n );\n }\n}\n\n/** The file a module id names, or undefined for a virtual module. */\nfunction fileOf(id: string): string | undefined {\n if (id.startsWith('\\0')) return undefined;\n const queryAt = id.indexOf('?');\n return queryAt < 0 ? id : id.slice(0, queryAt);\n}\n\n/** Whether a file is authored TypeScript under the package's own `src`. */\nfunction isAuthoredSource(file: string, sourceDir: string): boolean {\n if (file.endsWith('.d.ts') || !TRANSFORMED_EXTENSIONS.includes(path.extname(file))) return false;\n const relative = path.relative(sourceDir, file);\n return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);\n}\n\n/**\n * Runs the Unistyles Babel rewrite over a package's native source before rolldown bundles it.\n *\n * Unistyles finds `StyleSheet.create` and swaps React Native components for its\n * own at build time, which is what lets a theme change restyle without a React\n * render. Metro would run this for an app; a composition is bundled by\n * rolldown instead, so the rewrite runs here, in the transform hook, while the\n * source is still ESM.\n *\n * Only `.ts` and `.tsx` files under `<packageDir>/src` are touched. The TypeScript and JSX\n * syntax is parsed and printed back unchanged, and rolldown strips it after.\n */\nexport function unistylesBabel(packageDir: string): UnistylesBabelPlugin {\n // Real paths on both sides: rolldown hands the transform resolved ids, and\n // Unistyles matches the filename against `<root>/src` as a plain substring.\n let canonicalRoot: Promise<string> | undefined;\n let loaded: LoadedBabel | undefined;\n\n return {\n name: 'po-unistyles-babel',\n async transform(code: string, id: string): Promise<TransformResult | undefined> {\n const file = fileOf(id);\n if (file === undefined || !code.includes(REACT_NATIVE_MARKER)) return undefined;\n const root = await (canonicalRoot ??= fs.realpath(packageDir));\n if (!isAuthoredSource(file, path.join(root, SOURCE_ROOT))) return undefined;\n\n loaded ??= loadBabel(root);\n const result = await loaded.babel.transformAsync(code, {\n filename: file,\n cwd: root,\n root,\n babelrc: false,\n configFile: false,\n sourceMaps: true,\n parserOpts: { plugins: ['typescript', 'jsx'] },\n plugins: [[loaded.plugin, { root: SOURCE_ROOT }]],\n });\n if (typeof result?.code !== 'string') return undefined;\n return { code: result.code, map: result.map ?? null };\n },\n };\n}\n"],"mappings":"oLAOA,MACMO,EAAa,cACbC,EAAmB,gCAGnBC,EAA4C,CAAC,MAAO,MAAM,EAahE,SAASC,EAAU,EAAiC,CAClD,IAAMC,GAAA,EAAqBX,EAAAA,cAAAA,CAAcC,EAAAA,QAAK,KAAKW,EAAY,cAAc,CAAC,EAC9E,GAAI,CACF,IAAMC,EAAQF,EAAmBJ,CAAU,EACrCO,EAASH,EAAmBH,CAAgB,EAC5CO,EACJ,OAAOD,GAAW,WAAaA,EAAUA,GAAqD,QAChG,GAAI,OAAOC,GAAW,WAAY,MAAU,MAAM,GAAGP,EAAiB,gCAAgC,EACtG,MAAO,CAAE,QAAO,QAAO,CACzB,OAASQ,EAAO,CACd,IAAMC,EAASD,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACpE,MAAU,MACR,qCAAqCT,EAAW,6CAA6CK,EAAW,gDACvDK,GACnD,CACF,CACF,CAGA,SAASC,EAAO,EAAgC,CAC9C,GAAIC,EAAG,WAAW,IAAI,EAAG,OACzB,IAAMC,EAAUD,EAAG,QAAQ,GAAG,EAC9B,OAAOC,EAAU,EAAID,EAAKA,EAAG,MAAM,EAAGC,CAAO,CAC/C,CAGA,SAASC,EAAiB,EAAc,EAA4B,CAClE,GAAIC,EAAK,SAAS,OAAO,GAAK,CAACb,EAAuB,SAASR,EAAAA,QAAK,QAAQqB,CAAI,CAAC,EAAG,MAAO,GAC3F,IAAMC,EAAWtB,EAAAA,QAAK,SAASuB,EAAWF,CAAI,EAC9C,OAAOC,IAAa,IAAM,CAACA,EAAS,WAAW,IAAI,GAAK,CAACtB,EAAAA,QAAK,WAAWsB,CAAQ,CACnF,CAcA,SAAgBE,EAAe,EAA0C,CAGvE,IAAIC,EACAZ,EAEJ,MAAO,CACL,KAAM,qBACN,MAAM,UAAU,EAAc,EAAkD,CAC9E,IAAMQ,EAAOJ,EAAOC,CAAE,EACtB,GAAIG,IAAS,IAAA,IAAa,CAACK,EAAK,SAAS,cAAmB,EAAG,OAC/D,IAAMC,EAAO,MAAO,IAAkB1B,EAAAA,QAAG,SAASU,CAAU,GAC5D,GAAI,CAACS,EAAiBC,EAAMrB,EAAAA,QAAK,KAAK2B,EAAM,KAAW,CAAC,EAAG,OAE3D,IAAWlB,EAAUkB,CAAI,EACzB,IAAMC,EAAS,MAAMf,EAAO,MAAM,eAAea,EAAM,CACrD,SAAUL,EACV,IAAKM,EACL,OACA,QAAS,GACT,WAAY,GACZ,WAAY,GACZ,WAAY,CAAE,QAAS,CAAC,aAAc,KAAK,CAAE,EAC7C,QAAS,CAAC,CAACd,EAAO,OAAQ,CAAE,KAAM,KAAY,CAAC,CAAC,CAClD,CAAC,EACD,GAAI,OAAOe,GAAQ,MAAS,SAC5B,MAAO,CAAE,KAAMA,EAAO,KAAM,IAAKA,EAAO,KAAO,IAAK,CACtD,CACF,CACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{createRequire as e}from"node:module";import t from"node:
|
|
1
|
+
import{createRequire as e}from"node:module";import t from"node:path";import n from"node:fs/promises";const r=`@babel/core`,i=`react-native-unistyles/plugin`,a=[`.ts`,`.tsx`];function o(n){let a=e(t.join(n,`package.json`));try{let e=a(r),t=a(i),n=typeof t==`function`?t:t?.default;if(typeof n!=`function`)throw Error(`${i} does not export a Babel plugin`);return{babel:e,plugin:n}}catch(e){let t=e instanceof Error?e.message:String(e);throw Error(`[po-build] The native build needs ${r} and react-native-unistyles, resolved from ${n}. Add both to that package's devDependencies. ${t}`)}}function s(e){if(e.startsWith(`\0`))return;let t=e.indexOf(`?`);return t<0?e:e.slice(0,t)}function c(e,n){if(e.endsWith(`.d.ts`)||!a.includes(t.extname(e)))return!1;let r=t.relative(n,e);return r!==``&&!r.startsWith(`..`)&&!t.isAbsolute(r)}function l(e){let r,i;return{name:`po-unistyles-babel`,async transform(a,l){let u=s(l);if(u===void 0||!a.includes(`react-native`))return;let d=await(r??=n.realpath(e));if(!c(u,t.join(d,`src`)))return;i??=o(d);let f=await i.babel.transformAsync(a,{filename:u,cwd:d,root:d,babelrc:!1,configFile:!1,sourceMaps:!0,parserOpts:{plugins:[`typescript`,`jsx`]},plugins:[[i.plugin,{root:`src`}]]});if(typeof f?.code==`string`)return{code:f.code,map:f.map??null}}}}export{l as unistylesBabel};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["path","fs","BABEL_CORE","UNISTYLES_PLUGIN","TRANSFORMED_EXTENSIONS","loadBabel","requireFromPackage","createRequire","packageDir","babel","loaded","plugin","error","reason","fileOf","id","queryAt","isAuthoredSource","file","relative","sourceDir","unistylesBabel","canonicalRoot","code","root","result"],"sources":["../../../src/services/unistylesBabel/index.ts"],"sourcesContent":["import fs from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\n\nimport type { BabelCore, TransformResult, UnistylesBabelPlugin } from './type';\n\n/** The authored source root, package-relative. Unistyles resolves it against Babel's root. */\nconst SOURCE_ROOT = 'src';\nconst BABEL_CORE = '@babel/core';\nconst UNISTYLES_PLUGIN = 'react-native-unistyles/plugin';\n/** Both specifiers the Unistyles rewrite reacts to contain this, so a file without it has nothing to rewrite. */\nconst REACT_NATIVE_MARKER = 'react-native';\nconst TRANSFORMED_EXTENSIONS: readonly string[] = ['.ts', '.tsx'];\n\ninterface LoadedBabel {\n readonly babel: BabelCore;\n readonly plugin: unknown;\n}\n\n/**\n * Loads Babel and the Unistyles plugin from the extension package, not from po-build.\n *\n * po-build stays free of both: only a package that ships `.native` files pays\n * for them, and the copy it resolves is the one its own lockfile pinned.\n */\nfunction loadBabel(packageDir: string): LoadedBabel {\n const requireFromPackage = createRequire(path.join(packageDir, 'package.json'));\n try {\n const babel = requireFromPackage(BABEL_CORE) as BabelCore;\n const loaded = requireFromPackage(UNISTYLES_PLUGIN) as unknown;\n const plugin =\n typeof loaded === 'function' ? loaded : (loaded as { default?: unknown } | null | undefined)?.default;\n if (typeof plugin !== 'function') throw new Error(`${UNISTYLES_PLUGIN} does not export a Babel plugin`);\n return { babel, plugin };\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n throw new Error(\n `[po-build] The native build needs ${BABEL_CORE} and react-native-unistyles, resolved from ${packageDir}. ` +\n `Add both to that package's devDependencies. ${reason}`,\n );\n }\n}\n\n/** The file a module id names, or undefined for a virtual module. */\nfunction fileOf(id: string): string | undefined {\n if (id.startsWith('\\0')) return undefined;\n const queryAt = id.indexOf('?');\n return queryAt < 0 ? id : id.slice(0, queryAt);\n}\n\n/** Whether a file is authored TypeScript under the package's own `src`. */\nfunction isAuthoredSource(file: string, sourceDir: string): boolean {\n if (file.endsWith('.d.ts') || !TRANSFORMED_EXTENSIONS.includes(path.extname(file))) return false;\n const relative = path.relative(sourceDir, file);\n return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);\n}\n\n/**\n * Runs the Unistyles Babel rewrite over a package's native source before rolldown bundles it.\n *\n * Unistyles finds `StyleSheet.create` and swaps React Native components for its\n * own at build time, which is what lets a theme change restyle without a React\n * render. Metro would run this for an app; a composition is bundled by\n * rolldown instead, so the rewrite runs here, in the transform hook, while the\n * source is still ESM.\n *\n * Only `.ts` and `.tsx` files under `<packageDir>/src` are touched. The TypeScript and JSX\n * syntax is parsed and printed back unchanged, and rolldown strips it after.\n */\nexport function unistylesBabel(packageDir: string): UnistylesBabelPlugin {\n // Real paths on both sides: rolldown hands the transform resolved ids, and\n // Unistyles matches the filename against `<root>/src` as a plain substring.\n let canonicalRoot: Promise<string> | undefined;\n let loaded: LoadedBabel | undefined;\n\n return {\n name: 'po-unistyles-babel',\n async transform(code: string, id: string): Promise<TransformResult | undefined> {\n const file = fileOf(id);\n if (file === undefined || !code.includes(REACT_NATIVE_MARKER)) return undefined;\n const root = await (canonicalRoot ??= fs.realpath(packageDir));\n if (!isAuthoredSource(file, path.join(root, SOURCE_ROOT))) return undefined;\n\n loaded ??= loadBabel(root);\n const result = await loaded.babel.transformAsync(code, {\n filename: file,\n cwd: root,\n root,\n babelrc: false,\n configFile: false,\n sourceMaps: true,\n parserOpts: { plugins: ['typescript', 'jsx'] },\n plugins: [[loaded.plugin, { root: SOURCE_ROOT }]],\n });\n if (typeof result?.code !== 'string') return undefined;\n return { code: result.code, map: result.map ?? null };\n },\n };\n}\n"],"mappings":"qGAOA,MACME,EAAa,cACbC,EAAmB,gCAGnBC,EAA4C,CAAC,MAAO,MAAM,EAahE,SAASC,EAAU,EAAiC,CAClD,IAAMC,EAAqBC,EAAcP,EAAK,KAAKQ,EAAY,cAAc,CAAC,EAC9E,GAAI,CACF,IAAMC,EAAQH,EAAmBJ,CAAU,EACrCQ,EAASJ,EAAmBH,CAAgB,EAC5CQ,EACJ,OAAOD,GAAW,WAAaA,EAAUA,GAAqD,QAChG,GAAI,OAAOC,GAAW,WAAY,MAAU,MAAM,GAAGR,EAAiB,gCAAgC,EACtG,MAAO,CAAE,QAAO,QAAO,CACzB,OAASS,EAAO,CACd,IAAMC,EAASD,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACpE,MAAU,MACR,qCAAqCV,EAAW,6CAA6CM,EAAW,gDACvDK,GACnD,CACF,CACF,CAGA,SAASC,EAAO,EAAgC,CAC9C,GAAIC,EAAG,WAAW,IAAI,EAAG,OACzB,IAAMC,EAAUD,EAAG,QAAQ,GAAG,EAC9B,OAAOC,EAAU,EAAID,EAAKA,EAAG,MAAM,EAAGC,CAAO,CAC/C,CAGA,SAASC,EAAiB,EAAc,EAA4B,CAClE,GAAIC,EAAK,SAAS,OAAO,GAAK,CAACd,EAAuB,SAASJ,EAAK,QAAQkB,CAAI,CAAC,EAAG,MAAO,GAC3F,IAAMC,EAAWnB,EAAK,SAASoB,EAAWF,CAAI,EAC9C,OAAOC,IAAa,IAAM,CAACA,EAAS,WAAW,IAAI,GAAK,CAACnB,EAAK,WAAWmB,CAAQ,CACnF,CAcA,SAAgBE,EAAe,EAA0C,CAGvE,IAAIC,EACAZ,EAEJ,MAAO,CACL,KAAM,qBACN,MAAM,UAAU,EAAc,EAAkD,CAC9E,IAAMQ,EAAOJ,EAAOC,CAAE,EACtB,GAAIG,IAAS,IAAA,IAAa,CAACK,EAAK,SAAS,cAAmB,EAAG,OAC/D,IAAMC,EAAO,MAAO,IAAkBvB,EAAG,SAASO,CAAU,GAC5D,GAAI,CAACS,EAAiBC,EAAMlB,EAAK,KAAKwB,EAAM,KAAW,CAAC,EAAG,OAE3D,IAAWnB,EAAUmB,CAAI,EACzB,IAAMC,EAAS,MAAMf,EAAO,MAAM,eAAea,EAAM,CACrD,SAAUL,EACV,IAAKM,EACL,OACA,QAAS,GACT,WAAY,GACZ,WAAY,GACZ,WAAY,CAAE,QAAS,CAAC,aAAc,KAAK,CAAE,EAC7C,QAAS,CAAC,CAACd,EAAO,OAAQ,CAAE,KAAM,KAAY,CAAC,CAAC,CAClD,CAAC,EACD,GAAI,OAAOe,GAAQ,MAAS,SAC5B,MAAO,CAAE,KAAMA,EAAO,KAAM,IAAKA,EAAO,KAAO,IAAK,CACtD,CACF,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["path","fs","require_runtime","node_fs","node_path","StaleGeneratedError","stale","writeGenerated","changed","relative","contents","files","absolute","packageDir","managed","check"],"sources":["../../../src/services/writeGenerated/index.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["path","fs","require_runtime","node_fs","node_path","StaleGeneratedError","stale","writeGenerated","changed","relative","contents","files","absolute","packageDir","managed","check"],"sources":["../../../src/services/writeGenerated/index.ts"],"sourcesContent":["// sync: Public writeGenerated contract is synchronous, deferred with its cross-package callers to phase 2.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/** What a sync pass did, or would have done. */\nexport interface WriteResult {\n /** Package-relative paths whose contents changed. */\n readonly changed: readonly string[];\n}\n\nexport class StaleGeneratedError extends Error {\n constructor(readonly stale: readonly string[]) {\n super(`Stale generated files: ${stale.join(', ')}. Run the package's build to regenerate them.`);\n this.name = 'StaleGeneratedError';\n }\n}\n\n/**\n * Write-if-changed locally, throw on stale under an explicit `check`.\n *\n * Generated entries are ignored build inputs, so normal and CI builds create\n * them. Explicit check mode remains available to callers that need to compare\n * an existing generated tree without changing it.\n *\n * Writing only on a real change matters for watch mode: an unconditional\n * write would retrigger the watcher that called it.\n */\nexport function writeGenerated(\n packageDir: string,\n files: ReadonlyMap<string, string>,\n check = false,\n managed: readonly string[] = [],\n): WriteResult {\n const changed: string[] = [];\n\n for (const [relative, contents] of files) {\n const absolute = path.join(packageDir, relative);\n const existing = fs.existsSync(absolute) ? fs.readFileSync(absolute, 'utf8') : undefined;\n if (existing === contents) continue;\n changed.push(relative);\n if (check) continue;\n fs.mkdirSync(path.dirname(absolute), { recursive: true });\n fs.writeFileSync(absolute, contents);\n }\n\n for (const relative of managed) {\n if (files.has(relative)) continue;\n const absolute = path.join(packageDir, relative);\n if (!fs.existsSync(absolute)) continue;\n changed.push(relative);\n if (!check) fs.rmSync(absolute);\n }\n\n if (check && changed.length > 0) throw new StaleGeneratedError(changed);\n return { changed };\n}\n"],"mappings":"8IAUA,IAAaK,EAAb,cAAyC,KAAM,CACxB,MAArB,YAAY,EAAmC,CAC7C,MAAM,0BAA0BC,EAAM,KAAK,IAAI,EAAE,8CAA8C,EAD5E,KAAA,MAAAA,EAEnB,KAAK,KAAO,qBACd,CACF,EAYA,SAAgBC,EACd,EACA,EACA,EAAQ,GACR,EAA6B,CAAC,EACjB,CACb,IAAMC,EAAoB,CAAC,EAE3B,IAAK,GAAM,CAACC,EAAUC,KAAaC,EAAO,CACxC,IAAMC,EAAWZ,EAAAA,QAAK,KAAKa,EAAYJ,CAAQ,GAC9BR,EAAAA,QAAG,WAAWW,CAAQ,EAAIX,EAAAA,QAAG,aAAaW,EAAU,MAAM,EAAI,IAAA,MAC9DF,IACjBF,EAAQ,KAAKC,CAAQ,EACjB,KACJN,EAAA,QAAG,UAAUH,EAAAA,QAAK,QAAQY,CAAQ,EAAG,CAAE,UAAW,EAAK,CAAC,EACxDT,EAAA,QAAG,cAAcS,EAAUF,CAAQ,GACrC,CAEA,IAAK,IAAMD,KAAYK,EAAS,CAC9B,GAAIH,EAAM,IAAIF,CAAQ,EAAG,SACzB,IAAMG,EAAWZ,EAAAA,QAAK,KAAKa,EAAYJ,CAAQ,EAC1CR,EAAAA,QAAG,WAAWW,CAAQ,IAC3BJ,EAAQ,KAAKC,CAAQ,EAChBM,GAAOZ,EAAA,QAAG,OAAOS,CAAQ,EAChC,CAEA,GAAIG,GAASP,EAAQ,OAAS,EAAG,MAAM,IAAIH,EAAoBG,CAAO,EACtE,MAAO,CAAE,SAAQ,CACnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/writeGenerated/index.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/services/writeGenerated/index.ts"],"mappings":";;iBAKiB;;WAEN;;qBAGE,4BAA4B;WAClB;EAArB,YAAqB;;;;;;;;;;;;wBAgBP,eACd,oBACA,OAAO,6BACP,iBACA,8BACC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["fs","path","StaleGeneratedError","stale","writeGenerated","changed","relative","contents","files","absolute","packageDir","managed","check"],"sources":["../../../src/services/writeGenerated/index.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["fs","path","StaleGeneratedError","stale","writeGenerated","changed","relative","contents","files","absolute","packageDir","managed","check"],"sources":["../../../src/services/writeGenerated/index.ts"],"sourcesContent":["// sync: Public writeGenerated contract is synchronous, deferred with its cross-package callers to phase 2.\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/** What a sync pass did, or would have done. */\nexport interface WriteResult {\n /** Package-relative paths whose contents changed. */\n readonly changed: readonly string[];\n}\n\nexport class StaleGeneratedError extends Error {\n constructor(readonly stale: readonly string[]) {\n super(`Stale generated files: ${stale.join(', ')}. Run the package's build to regenerate them.`);\n this.name = 'StaleGeneratedError';\n }\n}\n\n/**\n * Write-if-changed locally, throw on stale under an explicit `check`.\n *\n * Generated entries are ignored build inputs, so normal and CI builds create\n * them. Explicit check mode remains available to callers that need to compare\n * an existing generated tree without changing it.\n *\n * Writing only on a real change matters for watch mode: an unconditional\n * write would retrigger the watcher that called it.\n */\nexport function writeGenerated(\n packageDir: string,\n files: ReadonlyMap<string, string>,\n check = false,\n managed: readonly string[] = [],\n): WriteResult {\n const changed: string[] = [];\n\n for (const [relative, contents] of files) {\n const absolute = path.join(packageDir, relative);\n const existing = fs.existsSync(absolute) ? fs.readFileSync(absolute, 'utf8') : undefined;\n if (existing === contents) continue;\n changed.push(relative);\n if (check) continue;\n fs.mkdirSync(path.dirname(absolute), { recursive: true });\n fs.writeFileSync(absolute, contents);\n }\n\n for (const relative of managed) {\n if (files.has(relative)) continue;\n const absolute = path.join(packageDir, relative);\n if (!fs.existsSync(absolute)) continue;\n changed.push(relative);\n if (!check) fs.rmSync(absolute);\n }\n\n if (check && changed.length > 0) throw new StaleGeneratedError(changed);\n return { changed };\n}\n"],"mappings":"gDAUA,IAAaE,EAAb,cAAyC,KAAM,CACxB,MAArB,YAAY,EAAmC,CAC7C,MAAM,0BAA0BC,EAAM,KAAK,IAAI,EAAE,8CAA8C,EAD5E,KAAA,MAAAA,EAEnB,KAAK,KAAO,qBACd,CACF,EAYA,SAAgBC,EACd,EACA,EACA,EAAQ,GACR,EAA6B,CAAC,EACjB,CACb,IAAMC,EAAoB,CAAC,EAE3B,IAAK,GAAM,CAACC,EAAUC,KAAaC,EAAO,CACxC,IAAMC,EAAWR,EAAK,KAAKS,EAAYJ,CAAQ,GAC9BN,EAAG,WAAWS,CAAQ,EAAIT,EAAG,aAAaS,EAAU,MAAM,EAAI,IAAA,MAC9DF,IACjBF,EAAQ,KAAKC,CAAQ,EACjB,KACJN,EAAG,UAAUC,EAAK,QAAQQ,CAAQ,EAAG,CAAE,UAAW,EAAK,CAAC,EACxDT,EAAG,cAAcS,EAAUF,CAAQ,GACrC,CAEA,IAAK,IAAMD,KAAYK,EAAS,CAC9B,GAAIH,EAAM,IAAIF,CAAQ,EAAG,SACzB,IAAMG,EAAWR,EAAK,KAAKS,EAAYJ,CAAQ,EAC1CN,EAAG,WAAWS,CAAQ,IAC3BJ,EAAQ,KAAKC,CAAQ,EAChBM,GAAOZ,EAAG,OAAOS,CAAQ,EAChC,CAEA,GAAIG,GAASP,EAAQ,OAAS,EAAG,MAAM,IAAIH,EAAoBG,CAAO,EACtE,MAAO,CAAE,SAAQ,CACnB"}
|