{"version":3,"file":"connect-build-BMaR9w4G.mjs","sources":["../src/services/connect-build.ts"],"sourcesContent":["import {\n  DEFAULT_VENDOR_INCLUDE,\n  DYNAMIC_BASE_PLACEHOLDER,\n  getConnectBaseViteConfig,\n  missingVendorEntries,\n  prebuildConnectVendor,\n  type PrebuiltVendor,\n} from \"@powerhousedao/builder-tools\";\nimport { getConfig } from \"@powerhousedao/shared/clis\";\nimport {\n  normalizeBasePath,\n  SHARED_DEP_SPECIFIERS,\n  SHARED_SUBPATHS,\n} from \"@powerhousedao/shared/connect\";\nimport { existsSync, mkdirSync, readdirSync, rmSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { InlineConfig } from \"vite\";\nimport { build, mergeConfig } from \"vite\";\nimport type { ConnectBuildArgs } from \"../types.js\";\nimport { buildCliConnectOverride } from \"../utils/cli-connect-override.js\";\nimport { runBuild } from \"./build.js\";\n\nexport async function runConnectBuild(args: ConnectBuildArgs) {\n  const { outDir, debug, dynamicBase, favicon } = args;\n\n  const mode = \"production\";\n  const dirname = process.cwd();\n\n  // Build has no read mode; a bare positional `<key>` is a user error. The\n  // 2-positional `<key> <value>` form is handled inside buildCliConnectOverride\n  // so it layers on top of --json + flags like any other override input.\n  if (args.keyPositional !== undefined && args.valuePositional === undefined) {\n    throw new Error(\n      \"ph connect build: positional override requires both <key> and <value> (e.g. `ph connect build connect.renown.url https://renown.staging`). To read a value, use `ph connect config <key>`.\",\n    );\n  }\n\n  // Fail fast if any package marked as provider: \"local\" is missing from\n  // node_modules — the Vite plugin that bundles them needs them on disk.\n  assertLocalPackagesInstalled(dirname);\n\n  // Build the CLI override layers (--json + individual flags + positional)\n  // once here so a bad payload fails before we waste a build.\n  // `--packages-registry` lands at the top-level `packageRegistryUrl`\n  // (mirrors source-config shape); every other flag feeds the connect-block\n  // precedence ladder.\n  const { connectOverride, packageRegistryUrl } = buildCliConnectOverride(args);\n\n  await runBuild({\n    outDir: \"dist\",\n    debug,\n    // Local packages built for Connect share deps with the app vendor, same\n    // as a plain `ph build`.\n    noSharedDeps: false,\n    ignoreTypeErrors: false,\n  });\n\n  // Production shared-dependency vendor: prebuilt into <outDir>/__vendor__\n  // before the app build. On by default; PH_CONNECT_VENDOR=0|false disables\n  // it. A failed prebuild fails the build — production must not ship import\n  // map entries pointing at a vendor that was never built (the dev server\n  // keeps its own soft fallback in the dev plugin).\n  const outDirAbs = resolve(dirname, outDir);\n  let vendor: PrebuiltVendor | null = null;\n  // The deploy base the vendor's import-map addresses are written against.\n  // Only meaningful when the vendor is built; \"/\" is the inert default.\n  let appBase = \"/\";\n  if (isVendorEnabled()) {\n    // The vendor dir's parent must exist before the prebuild: its build\n    // lock is a sibling of the vendor dir, and the package build (runBuild)\n    // writes to a different out dir, so nothing else creates it yet.\n    mkdirSync(outDirAbs, { recursive: true });\n    const errorRef: { message?: string } = {};\n    // The same base string the app build uses below: the dynamic-base\n    // placeholder, or the normalized deploy base (CLI override wins over the\n    // source config). The vendor worker appends its own segment.\n    const phConfig = getConfig(join(dirname, \"powerhouse.config.json\"));\n    const connectBasePath =\n      connectOverride?.app?.basePath ?? phConfig.connect?.app?.basePath;\n    appBase = dynamicBase\n      ? DYNAMIC_BASE_PLACEHOLDER\n      : connectBasePath\n        ? normalizeBasePath(connectBasePath)\n        : \"/\";\n    vendor = await prebuildConnectVendor({\n      dirname,\n      include: productionVendorInclude(),\n      vendorDir: join(outDirAbs, \"__vendor__\"),\n      base: appBase,\n      nodeEnv: \"production\",\n      errorRef,\n    });\n    if (!vendor) {\n      console.error(\n        `ph connect build: the shared-dependency vendor failed to build${\n          errorRef.message ? `:\\n${errorRef.message}` : \"\"\n        }`,\n      );\n      throw new Error(\"shared-dependency vendor prebuild failed\");\n    }\n    // Stale top-level output goes, the vendor stays: the app build below\n    // runs with emptyOutDir: false and must not wipe it.\n    cleanDistExcept(outDirAbs, [\"__vendor__\"]);\n  }\n\n  const baseConfig = getConnectBaseViteConfig({\n    mode,\n    dirname,\n    cliConnectOverride: connectOverride,\n    cliPackageRegistryUrl: packageRegistryUrl,\n    dynamicBase,\n    favicon,\n    // Vendor import-map entries are relative (leading slash stripped) so\n    // they resolve against the page URL under any deploy base — root or\n    // subpath, dynamic or concrete.\n    vendor: vendor\n      ? {\n          imports: vendorImportMapEntries(vendor.imports, appBase),\n          versions: vendor.versions,\n        }\n      : undefined,\n  });\n\n  const buildConfig: InlineConfig = {\n    build: {\n      outDir,\n      // The vendor dir was prebuilt into the out dir above; the build must\n      // not empty it.\n      ...(vendor ? { emptyOutDir: false } : {}),\n    },\n  };\n\n  const config = mergeConfig(baseConfig, buildConfig);\n\n  await build(config);\n\n  // Last word on the shipped artifact: every specifier the page's import map\n  // publishes must have a file next to it. The prebuild verifies its own\n  // output, but it can return a cache hit it never rebuilt, and both the\n  // stale-output clean and the app build run afterwards. An entry with no\n  // file behind it ships as a URL that 404s, and a SPA answers that with\n  // index.html — so the browser reports only an opaque MIME-type error with\n  // no filename. Fail here instead, naming the entries.\n  if (vendor) {\n    const unbacked = missingVendorEntries(\n      join(outDirAbs, \"__vendor__\"),\n      vendor.imports,\n    );\n    if (unbacked.length > 0) {\n      throw new Error(\n        `ph connect build: the shared-dependency vendor is missing ` +\n          `${unbacked.length} of the ${Object.keys(vendor.imports).length} ` +\n          `entries its import map publishes:\\n` +\n          unbacked.map((e) => `  ${e.spec} -> ${e.url}`).join(\"\\n\"),\n      );\n    }\n  }\n}\n\n/**\n * The specifier set the production vendor bundles: the dev-proven heavy set\n * ∪ the package-shared set — everything the app and loaded packages\n * externalize onto the vendor.\n *\n * Two specifiers are deliberately dropped:\n *\n * - `@powerhousedao/connect`. It is in DEFAULT_VENDOR_INCLUDE because the dev\n *   server vendors the app to keep rebuilds cheap, but in a production build\n *   the app *is* Connect: `SHARED_DEP_SPECIFIERS` omits it, so the app build\n *   never externalizes it and nothing would ever resolve its import-map\n *   entry. Vendoring it is not merely dead weight — bundling Connect's dist\n *   for the browser pulls in its node-only `@powerhousedao/config/node`\n *   import, and so `read-pkg` -> `unicorn-magic`, whose browser entry has no\n *   `toPath`. That fails the vendor build, and a failed prebuild fails\n *   `ph connect build`.\n * - the bare `@powerhousedao/shared` root, whose barrel likewise reaches\n *   node-only modules. Its browser-safe subpaths (SHARED_SUBPATHS) are\n *   listed instead; a package importing the bare root bundles its own copy.\n */\nexport function productionVendorInclude(): string[] {\n  return [\n    ...new Set([\n      ...DEFAULT_VENDOR_INCLUDE.filter((s) => s !== \"@powerhousedao/connect\"),\n      ...SHARED_DEP_SPECIFIERS.filter((s) => s !== \"@powerhousedao/shared\"),\n      ...SHARED_SUBPATHS.map((s) => `@powerhousedao/shared/${s}`),\n    ]),\n  ];\n}\n\n/**\n * Turn the vendor's base-less URLs (`/__vendor__/x.js`) into the addresses\n * the page's import map ships, by prefixing the deploy base.\n *\n * An import map *address* must be a URL or begin with `/`, `./` or `../`;\n * anything else is a bare specifier, which the browser rejects — it drops the\n * entry, resolves the specifier to null, and every shared import then fails\n * with \"blocked by a null value\". So the base is applied rather than the\n * leading slash stripped: a root-relative address is both valid and correct\n * under a subpath deploy, and a dynamic-base build keeps its placeholder for\n * the serving proxy to substitute (the dynamic-base plugin leaves HTML\n * tokens alone by design).\n */\nexport function vendorImportMapEntries(\n  imports: Record<string, string>,\n  appBase: string,\n): Record<string, string> {\n  return Object.fromEntries(\n    Object.entries(imports).map(([spec, url]) => [\n      spec,\n      `${appBase}/${url}`.replace(/\\/{2,}/g, \"/\"),\n    ]),\n  );\n}\n\n/**\n * The production vendor prebuild runs on every `ph connect build` unless\n * disabled with PH_CONNECT_VENDOR=0 or false.\n */\nexport function isVendorEnabled(env: NodeJS.ProcessEnv = process.env): boolean {\n  const raw = env.PH_CONNECT_VENDOR ?? \"1\";\n  return raw !== \"0\" && raw !== \"false\";\n}\n\n/**\n * Remove every top-level entry of `dist` except the kept names. Used between\n * the vendor prebuild and the app build so stale app output goes but the\n * just-built vendor stays (the build then runs with emptyOutDir: false).\n * Returns the number of entries removed.\n */\nexport function cleanDistExcept(dist: string, keep: string[] = []): number {\n  if (!existsSync(dist)) return 0;\n  let removed = 0;\n  for (const entry of readdirSync(dist)) {\n    if (keep.includes(entry)) continue;\n    rmSync(join(dist, entry), { recursive: true, force: true });\n    removed += 1;\n  }\n  return removed;\n}\n\nfunction assertLocalPackagesInstalled(projectPath: string) {\n  const config = getConfig(join(projectPath, \"powerhouse.config.json\"));\n  const localPackages = (config.packages ?? []).filter(\n    (p) => p.provider === \"local\",\n  );\n  if (localPackages.length === 0) return;\n\n  const missing = localPackages.filter(\n    (p) =>\n      !existsSync(\n        join(projectPath, \"node_modules\", p.packageName, \"package.json\"),\n      ),\n  );\n  if (missing.length === 0) return;\n\n  const names = missing.map((p) => p.packageName);\n  throw new Error(\n    `ph connect build requires these packages to be installed in node_modules (they are declared with provider: \"local\" in powerhouse.config.json):\\n` +\n      names.map((n) => `  - ${n}`).join(\"\\n\") +\n      `\\n\\nInstall them with:\\n  ph install --local ${names.join(\" \")}`,\n  );\n}\n"],"names":[],"mappings":"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","debug_id":"728222f4-2bd6-56f2-9a5d-8558a9fbfcb8"}