{"version":3,"file":"webPluginManifest.mjs","names":[],"sources":["../../../src/builders/web/webPluginManifest.ts"],"sourcesContent":["/**\n * The doompiWeb manifest vocabulary: how a package declares its web plugin in\n * package.json, validated and ordered without touching the filesystem. The\n * scan adapter feeds this from real manifests; the sync bundler feeds it from\n * the installed composition's package roots.\n *\n * Plugins are independent: a manifest names no other plugin, and the order\n * plugins install in is only a tiebreak (registrationOrder, then pluginId),\n * because every relation between two plugins resolves by name once all of\n * them are installed. A collision between two packages is a notice for the\n * sync log and the first package keeps the name; only a malformed block is an\n * error, which the scanner turns into a notice for that package alone.\n */\n\nconst PLUGIN_ID_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;\n\n/** The order a plugin installs at when its manifest names none. */\nexport const DEFAULT_REGISTRATION_ORDER = 1000;\n\nexport interface WebPluginEntryDeclaration {\n  /** Package-relative ./path to the source entry. */\n  entry: string;\n}\n\nexport interface DeclaredWebPlugin {\n  pluginId: string;\n  registrationOrder: number;\n  packageDir: string;\n  packageName: string;\n  isHost: boolean;\n  client: WebPluginEntryDeclaration;\n  scopes: readonly ('global' | 'workspace' | 'session')[];\n}\n\nexport class WebPluginManifestError extends Error {\n  constructor(packageDir: string, message: string) {\n    super(`doompiWeb manifest in ${packageDir}: ${message}`);\n    this.name = 'WebPluginManifestError';\n  }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction normalizeEntry(packageDir: string, kind: string, value: unknown): WebPluginEntryDeclaration {\n  const raw = typeof value === 'string' ? { entry: value } : value;\n  if (!isRecord(raw)) throw new WebPluginManifestError(packageDir, `${kind} must be a path or {entry}.`);\n  const { entry } = raw;\n  if (typeof entry !== 'string' || !entry.startsWith('./') || entry.includes('..')) {\n    throw new WebPluginManifestError(packageDir, `${kind}.entry must be a package-relative ./path with no '..'.`);\n  }\n  return { entry };\n}\n\n/** The doompiWeb value normalized to a list: one plugin object or an array of them. */\nexport function pluginBlocksOf(manifest: Record<string, unknown>): unknown[] {\n  if (manifest.doompiWeb === undefined) return [];\n  return Array.isArray(manifest.doompiWeb) ? manifest.doompiWeb : [manifest.doompiWeb];\n}\n\n/** Validates one manifest's blocks into declared plugins; throws WebPluginManifestError on bad shape. */\nexport function declaredPluginsOf(\n  packageDir: string,\n  manifest: Record<string, unknown>,\n  isHost: boolean,\n): DeclaredWebPlugin[] {\n  const plugins: DeclaredWebPlugin[] = [];\n  for (const block of pluginBlocksOf(manifest)) {\n    if (!isRecord(block)) throw new WebPluginManifestError(packageDir, 'each block must be an object.');\n    const { pluginId, registrationOrder = DEFAULT_REGISTRATION_ORDER, client, scopes } = block;\n    if (\n      !Array.isArray(scopes) ||\n      scopes.length === 0 ||\n      scopes.some((scope) => !['global', 'workspace', 'session'].includes(scope))\n    ) {\n      throw new WebPluginManifestError(\n        packageDir,\n        'scopes must explicitly name global, workspace, or session; resync the plugin.',\n      );\n    }\n    if (typeof pluginId !== 'string' || !PLUGIN_ID_PATTERN.test(pluginId)) {\n      throw new WebPluginManifestError(packageDir, `pluginId '${String(pluginId)}' must be kebab-case.`);\n    }\n    if (!Number.isInteger(registrationOrder) || (registrationOrder as number) < 0) {\n      throw new WebPluginManifestError(packageDir, `'${pluginId}' needs a non-negative integer registrationOrder.`);\n    }\n    plugins.push({\n      pluginId,\n      registrationOrder: registrationOrder as number,\n      packageDir,\n      packageName: typeof manifest.name === 'string' ? manifest.name : packageDir,\n      isHost,\n      client: normalizeEntry(packageDir, 'client', client),\n      scopes,\n    });\n  }\n  return plugins;\n}\n\nconst byInstallOrder = (left: DeclaredWebPlugin, right: DeclaredWebPlugin): number =>\n  left.registrationOrder - right.registrationOrder ||\n  left.pluginId.localeCompare(right.pluginId) ||\n  left.packageDir.localeCompare(right.packageDir);\n\n/**\n * The deterministic install order, (registrationOrder, pluginId), with the\n * cross-package collisions resolved by the browser client registry: the first\n * plugin keeps a shared pluginId and the later package is dropped with a notice.\n */\nexport function orderDeclaredPlugins(\n  plugins: readonly DeclaredWebPlugin[],\n  onNotice: (message: string) => void = () => undefined,\n): DeclaredWebPlugin[] {\n  const sorted = [...plugins].sort(byInstallOrder);\n  const byId = new Map<string, DeclaredWebPlugin>();\n  const kept: DeclaredWebPlugin[] = [];\n  for (const plugin of sorted) {\n    const holder = byId.get(plugin.pluginId);\n    if (holder !== undefined) {\n      onNotice(\n        `web plugin '${plugin.pluginId}' from ${plugin.packageDir} is skipped: ${holder.packageDir} already declares it.`,\n      );\n      continue;\n    }\n    byId.set(plugin.pluginId, plugin);\n    kept.push(plugin);\n  }\n  return kept;\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAM,oBAAoB;;AAG1B,MAAa,6BAA6B;AAiB1C,IAAa,yBAAb,cAA4C,MAAM;CAChD,YAAY,YAAoB,SAAiB;EAC/C,MAAM,yBAAyB,WAAW,IAAI,SAAS;EACvD,KAAK,OAAO;CACd;AACF;AAEA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,eAAe,YAAoB,MAAc,OAA2C;CACnG,MAAM,MAAM,OAAO,UAAU,WAAW,EAAE,OAAO,MAAM,IAAI;CAC3D,IAAI,CAAC,SAAS,GAAG,GAAG,MAAM,IAAI,uBAAuB,YAAY,GAAG,KAAK,4BAA4B;CACrG,MAAM,EAAE,UAAU;CAClB,IAAI,OAAO,UAAU,YAAY,CAAC,MAAM,WAAW,IAAI,KAAK,MAAM,SAAS,IAAI,GAC7E,MAAM,IAAI,uBAAuB,YAAY,GAAG,KAAK,uDAAuD;CAE9G,OAAO,EAAE,MAAM;AACjB;;AAGA,SAAgB,eAAe,UAA8C;CAC3E,IAAI,SAAS,cAAc,KAAA,GAAW,OAAO,CAAC;CAC9C,OAAO,MAAM,QAAQ,SAAS,SAAS,IAAI,SAAS,YAAY,CAAC,SAAS,SAAS;AACrF;;AAGA,SAAgB,kBACd,YACA,UACA,QACqB;CACrB,MAAM,UAA+B,CAAC;CACtC,KAAK,MAAM,SAAS,eAAe,QAAQ,GAAG;EAC5C,IAAI,CAAC,SAAS,KAAK,GAAG,MAAM,IAAI,uBAAuB,YAAY,+BAA+B;EAClG,MAAM,EAAE,UAAU,oBAAoB,4BAA4B,QAAQ,WAAW;EACrF,IACE,CAAC,MAAM,QAAQ,MAAM,KACrB,OAAO,WAAW,KAClB,OAAO,MAAM,UAAU,CAAC;GAAC;GAAU;GAAa;EAAS,CAAC,CAAC,SAAS,KAAK,CAAC,GAE1E,MAAM,IAAI,uBACR,YACA,+EACF;EAEF,IAAI,OAAO,aAAa,YAAY,CAAC,kBAAkB,KAAK,QAAQ,GAClE,MAAM,IAAI,uBAAuB,YAAY,aAAa,OAAO,QAAQ,EAAE,sBAAsB;EAEnG,IAAI,CAAC,OAAO,UAAU,iBAAiB,KAAM,oBAA+B,GAC1E,MAAM,IAAI,uBAAuB,YAAY,IAAI,SAAS,kDAAkD;EAE9G,QAAQ,KAAK;GACX;GACmB;GACnB;GACA,aAAa,OAAO,SAAS,SAAS,WAAW,SAAS,OAAO;GACjE;GACA,QAAQ,eAAe,YAAY,UAAU,MAAM;GACnD;EACF,CAAC;CACH;CACA,OAAO;AACT;AAEA,MAAM,kBAAkB,MAAyB,UAC/C,KAAK,oBAAoB,MAAM,qBAC/B,KAAK,SAAS,cAAc,MAAM,QAAQ,KAC1C,KAAK,WAAW,cAAc,MAAM,UAAU;;;;;;AAOhD,SAAgB,qBACd,SACA,iBAA4C,KAAA,GACvB;CACrB,MAAM,SAAS,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,cAAc;CAC/C,MAAM,uBAAO,IAAI,IAA+B;CAChD,MAAM,OAA4B,CAAC;CACnC,KAAK,MAAM,UAAU,QAAQ;EAC3B,MAAM,SAAS,KAAK,IAAI,OAAO,QAAQ;EACvC,IAAI,WAAW,KAAA,GAAW;GACxB,SACE,eAAe,OAAO,SAAS,SAAS,OAAO,WAAW,eAAe,OAAO,WAAW,sBAC7F;GACA;EACF;EACA,KAAK,IAAI,OAAO,UAAU,MAAM;EAChC,KAAK,KAAK,MAAM;CAClB;CACA,OAAO;AACT"}