{"version":3,"file":"webPluginGenerate.mjs","names":[],"sources":["../../../src/builders/web/webPluginGenerate.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport type { DeclaredWebPlugin } from './webPluginManifest';\nimport { scanWebPlugins } from './webPluginScan';\n\nconst HEADER = '// Generated by doompi web builder. Do not edit by hand.';\nexport const BUILTIN_CLIENT_MODULE = path.join('src', 'web', 'app', 'webPlugins.generated.ts');\nexport const BUILTIN_CSS_MODULE = path.join('src', 'web', 'styles', 'webPluginSources.generated.css');\n\nfunction camel(pluginId: string): string {\n  return pluginId.replace(/-([a-z0-9])/g, (_, ch: string) => ch.toUpperCase());\n}\n\nfunction relativeImport(fromDir: string, toFile: string): string {\n  const spec = path.relative(fromDir, toFile).split(path.sep).join('/');\n  return spec.startsWith('.') ? spec : `./${spec}`;\n}\n\nfunction renderClientModule(\n  plugins: readonly DeclaredWebPlugin[],\n  style: 'relative' | 'absolute',\n  packageRoot: string,\n): string {\n  const lines = [HEADER, \"import type { WebPluginDefinition } from '@agimon-ai/doompi-core/web';\"];\n  const names: string[] = [];\n  const fromDir = path.join(packageRoot, 'src', 'web', 'app');\n  for (const plugin of plugins) {\n    const name = `${camel(plugin.pluginId)}Plugin`;\n    names.push(name);\n    const entry = path.join(plugin.packageDir, plugin.client.entry);\n    // The committed builtin module keeps machine-neutral relative imports; the\n    // sync-generated module lives outside src, is aliased over the committed\n    // path, and resolves imports against its real location, so every entry\n    // becomes an absolute file URL into the installed packages.\n    const specifier = style === 'relative' ? relativeImport(fromDir, entry) : pathToFileURL(entry).href;\n    lines.push(`import { webPlugin as ${name} } from '${specifier}';`);\n  }\n  lines.push('', `export const webPlugins: readonly WebPluginDefinition[] = [${names.join(', ')}];`, '');\n  return lines.join('\\n');\n}\n\n/**\n * The directory Tailwind scans for a plugin's class names.\n *\n * `src/extensions` wins, because a plugin's components colocate with the routed\n * file that renders them, in private `_components` and `_lib` folders. The\n * legacy `src/web` root is only a fallback: `existsSync` is true for a\n * half-emptied directory holding nothing but a tsconfig, and packages have been\n * in exactly that state, so preferring it would hand Tailwind a directory with\n * no components in it and silently drop every class.\n *\n * The client entry's own directory is the last resort. It is published through\n * `src/exports/webClient.ts`, a one-line re-export, or generated outside `src`\n * entirely, so it holds no components either.\n */\nfunction pluginSourceRoot(plugin: DeclaredWebPlugin): string {\n  const routingRoot = path.join(plugin.packageDir, 'src', 'extensions');\n  if (fs.existsSync(routingRoot)) return routingRoot;\n  const webRoot = path.join(plugin.packageDir, 'src', 'web');\n  if (fs.existsSync(webRoot)) return webRoot;\n  return path.dirname(path.join(plugin.packageDir, plugin.client.entry));\n}\n\nfunction renderCssModule(plugins: readonly DeclaredWebPlugin[], leadingSourceRoots: readonly string[]): string {\n  // Sync CSS is compiled from a generated directory, so Tailwind cannot infer\n  // the stable shell tree from app.css. Its caller names that tree explicitly\n  // so later plugin utilities do not override missing responsive shell utilities.\n  const lines = [`/* ${HEADER.slice(3)} */`, ...leadingSourceRoots.map((root) => `@source \"${root}\";`)];\n  for (const plugin of plugins) {\n    if (plugin.isHost) continue;\n    lines.push(`@source \"${pluginSourceRoot(plugin)}\";`);\n  }\n  lines.push('');\n  return lines.join('\\n');\n}\n\n/** The committed client registry, rendered from the host package alone. */\nexport function renderBuiltinWebPluginModules(packageRoot: string): Map<string, string> {\n  const plugins = scanWebPlugins(packageRoot);\n  return new Map([\n    [BUILTIN_CLIENT_MODULE, renderClientModule(plugins, 'relative', packageRoot)],\n    [BUILTIN_CSS_MODULE, renderCssModule(plugins, [])],\n  ]);\n}\n\n/**\n * Keeps the committed client registry fresh: write-if-changed locally,\n * check-only (throw) in CI. Both vite.config.ts and tsdown.config.ts call\n * this at config-load time.\n */\nexport function ensureBuiltinWebPluginModules({\n  packageRoot,\n  check = false,\n}: {\n  packageRoot: string;\n  check?: boolean;\n}): void {\n  const stale: string[] = [];\n  for (const [relativePath, content] of renderBuiltinWebPluginModules(packageRoot)) {\n    const filePath = path.join(packageRoot, relativePath);\n    const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : undefined;\n    if (existing === content) continue;\n    if (check) {\n      stale.push(relativePath);\n      continue;\n    }\n    fs.mkdirSync(path.dirname(filePath), { recursive: true });\n    fs.writeFileSync(filePath, content);\n  }\n  if (stale.length > 0) {\n    throw new Error(\n      `Stale generated web plugin modules: ${stale.join(', ')}. Run pnpm build in doompi-web and commit them.`,\n    );\n  }\n}\n\nexport interface SyncGeneratedModules {\n  clientModulePath: string;\n  compositionModulePath: string;\n  cssModulePath: string;\n}\n\n/**\n * The sync-time variant: renders the full client plugin set (host built-ins plus\n * the installed composition's packages) into a generation directory the bundler\n * aliases over the committed builtin modules.\n */\nexport function writeSyncWebPluginModules(\n  plugins: readonly DeclaredWebPlugin[],\n  generatedDir: string,\n  packageRoot: string,\n): SyncGeneratedModules {\n  fs.mkdirSync(generatedDir, { recursive: true });\n  const clientModulePath = path.join(generatedDir, 'webPlugins.generated.ts');\n  const compositionModulePath = path.join(generatedDir, 'webPluginComposition.generated.ts');\n  const cssModulePath = path.join(generatedDir, 'webPluginSources.generated.css');\n  fs.writeFileSync(clientModulePath, renderClientModule(plugins, 'absolute', packageRoot));\n  fs.writeFileSync(\n    compositionModulePath,\n    `${HEADER}\\nimport '${pathToFileURL(path.join(packageRoot, 'src', 'web', 'styles', 'app.css')).href}';\\nimport { webPlugins } from './webPlugins.generated.ts';\\n\\nglobalThis.DoomPiWebPluginComposition = webPlugins;\\n`,\n  );\n  fs.writeFileSync(cssModulePath, renderCssModule(plugins, [path.join(packageRoot, 'src', 'web')]));\n  return {\n    clientModulePath,\n    compositionModulePath,\n    cssModulePath,\n  };\n}\n"],"mappings":";;;;;AAOA,MAAM,SAAS;AACf,MAAa,wBAAwB,KAAK,KAAK,OAAO,OAAO,OAAO,yBAAyB;AAC7F,MAAa,qBAAqB,KAAK,KAAK,OAAO,OAAO,UAAU,gCAAgC;AAEpG,SAAS,MAAM,UAA0B;CACvC,OAAO,SAAS,QAAQ,iBAAiB,GAAG,OAAe,GAAG,YAAY,CAAC;AAC7E;AAEA,SAAS,eAAe,SAAiB,QAAwB;CAC/D,MAAM,OAAO,KAAK,SAAS,SAAS,MAAM,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;CACpE,OAAO,KAAK,WAAW,GAAG,IAAI,OAAO,KAAK;AAC5C;AAEA,SAAS,mBACP,SACA,OACA,aACQ;CACR,MAAM,QAAQ,CAAC,QAAQ,wEAAwE;CAC/F,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAU,KAAK,KAAK,aAAa,OAAO,OAAO,KAAK;CAC1D,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,OAAO,GAAG,MAAM,OAAO,QAAQ,EAAE;EACvC,MAAM,KAAK,IAAI;EACf,MAAM,QAAQ,KAAK,KAAK,OAAO,YAAY,OAAO,OAAO,KAAK;EAK9D,MAAM,YAAY,UAAU,aAAa,eAAe,SAAS,KAAK,IAAI,cAAc,KAAK,CAAC,CAAC;EAC/F,MAAM,KAAK,yBAAyB,KAAK,WAAW,UAAU,GAAG;CACnE;CACA,MAAM,KAAK,IAAI,8DAA8D,MAAM,KAAK,IAAI,EAAE,KAAK,EAAE;CACrG,OAAO,MAAM,KAAK,IAAI;AACxB;;;;;;;;;;;;;;;AAgBA,SAAS,iBAAiB,QAAmC;CAC3D,MAAM,cAAc,KAAK,KAAK,OAAO,YAAY,OAAO,YAAY;CACpE,IAAI,GAAG,WAAW,WAAW,GAAG,OAAO;CACvC,MAAM,UAAU,KAAK,KAAK,OAAO,YAAY,OAAO,KAAK;CACzD,IAAI,GAAG,WAAW,OAAO,GAAG,OAAO;CACnC,OAAO,KAAK,QAAQ,KAAK,KAAK,OAAO,YAAY,OAAO,OAAO,KAAK,CAAC;AACvE;AAEA,SAAS,gBAAgB,SAAuC,oBAA+C;CAI7G,MAAM,QAAQ,CAAC,MAAM,OAAO,MAAM,CAAC,EAAE,MAAM,GAAG,mBAAmB,KAAK,SAAS,YAAY,KAAK,GAAG,CAAC;CACpG,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,OAAO,QAAQ;EACnB,MAAM,KAAK,YAAY,iBAAiB,MAAM,EAAE,GAAG;CACrD;CACA,MAAM,KAAK,EAAE;CACb,OAAO,MAAM,KAAK,IAAI;AACxB;;AAGA,SAAgB,8BAA8B,aAA0C;CACtF,MAAM,UAAU,eAAe,WAAW;CAC1C,uBAAO,IAAI,IAAI,CACb,CAAC,uBAAuB,mBAAmB,SAAS,YAAY,WAAW,CAAC,GAC5E,CAAC,oBAAoB,gBAAgB,SAAS,CAAC,CAAC,CAAC,CACnD,CAAC;AACH;;;;;;AAOA,SAAgB,8BAA8B,EAC5C,aACA,QAAQ,SAID;CACP,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,CAAC,cAAc,YAAY,8BAA8B,WAAW,GAAG;EAChF,MAAM,WAAW,KAAK,KAAK,aAAa,YAAY;EAEpD,KADiB,GAAG,WAAW,QAAQ,IAAI,GAAG,aAAa,UAAU,MAAM,IAAI,KAAA,OAC9D,SAAS;EAC1B,IAAI,OAAO;GACT,MAAM,KAAK,YAAY;GACvB;EACF;EACA,GAAG,UAAU,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EACxD,GAAG,cAAc,UAAU,OAAO;CACpC;CACA,IAAI,MAAM,SAAS,GACjB,MAAM,IAAI,MACR,uCAAuC,MAAM,KAAK,IAAI,EAAE,gDAC1D;AAEJ;;;;;;AAaA,SAAgB,0BACd,SACA,cACA,aACsB;CACtB,GAAG,UAAU,cAAc,EAAE,WAAW,KAAK,CAAC;CAC9C,MAAM,mBAAmB,KAAK,KAAK,cAAc,yBAAyB;CAC1E,MAAM,wBAAwB,KAAK,KAAK,cAAc,mCAAmC;CACzF,MAAM,gBAAgB,KAAK,KAAK,cAAc,gCAAgC;CAC9E,GAAG,cAAc,kBAAkB,mBAAmB,SAAS,YAAY,WAAW,CAAC;CACvF,GAAG,cACD,uBACA,GAAG,OAAO,YAAY,cAAc,KAAK,KAAK,aAAa,OAAO,OAAO,UAAU,SAAS,CAAC,CAAC,CAAC,KAAK,qHACtG;CACA,GAAG,cAAc,eAAe,gBAAgB,SAAS,CAAC,KAAK,KAAK,aAAa,OAAO,KAAK,CAAC,CAAC,CAAC;CAChG,OAAO;EACL;EACA;EACA;CACF;AACF"}