{"version":3,"file":"plugins.cjs","names":["Context","BosDepsTag","MISSING_CONFIG_MESSAGE","saveBosConfig","generateCodeArtifacts","existsSync","join","publishToFastKv","openResolution","fetchRemotePluginManifest","basename","getPluginRef"],"sources":["../../src/commands/plugins.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { Context } from \"effect\";\nimport { getPluginRef } from \"../build\";\nimport { generateCodeArtifacts } from \"../code-artifacts\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport type { PluginListResult } from \"../contract\";\nimport { fetchRemotePluginManifest } from \"../fastkv\";\nimport { publishToFastKv } from \"../publish\";\nimport { openResolution } from \"../resolution/session\";\nimport type { BosConfig } from \"../types\";\nimport { saveBosConfig } from \"../utils/save-config\";\nimport { type BosBuilder, BosDepsTag } from \"./shared\";\n\nexport function registerPlugins(builder: BosBuilder) {\n  return {\n    pluginAdd: builder.pluginAdd.handler(async ({ input, context }) => {\n      const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n      const session = deps.session;\n      if (!session?.config) {\n        return {\n          status: \"error\" as const,\n          key: \"\",\n          error: MISSING_CONFIG_MESSAGE,\n        };\n      }\n\n      const isBosRef = input.source.startsWith(\"bos://\");\n      const isLocal = input.source.startsWith(\"local:\");\n      const key = sanitizePluginKey(\n        input.as ??\n          (isBosRef ? (input.source.split(\"/\").pop() ?? \"plugin\") : defaultPluginKey(input.source)),\n      );\n      const existing = session.config.plugins?.[key];\n      const existingEntry = existing && typeof existing === \"object\" ? existing : {};\n      // Adding a source is a switch, not an additive patch: a remote URL (or\n      // bos:// ref) replaces a conflicting local development ref. Dual-mode\n      // (local in dev, production URL in prod) remains expressible by\n      // hand-editing the authored config.\n      const applyEntryDelta = (base: Record<string, unknown>): Record<string, unknown> => {\n        if (isBosRef) {\n          const { development: _dropped, ...rest } = base;\n          return { ...rest, extends: input.source };\n        }\n        if (isLocal) {\n          const { extends: _dropped, ...rest } = base;\n          return { ...rest, development: input.source };\n        }\n        const { development: _dropped, ...rest } = base;\n        return { ...rest, production: input.production ?? input.source };\n      };\n      const nextPlugins = { ...session.config.plugins };\n      nextPlugins[key] = applyEntryDelta(existingEntry);\n\n      const nextConfig: BosConfig = {\n        ...session.config,\n        plugins: nextPlugins,\n      };\n\n      const authored = session.rawConfig;\n      if (authored) {\n        // Authoring-surface save: the authored leaf input + delta, never the\n        // resolved config (which would bake inherited values into the file).\n        const authoredExisting = authored.plugins?.[key];\n        const authoredBase: Record<string, unknown> =\n          typeof authoredExisting === \"string\"\n            ? { extends: authoredExisting }\n            : authoredExisting && typeof authoredExisting === \"object\"\n              ? (authoredExisting as Record<string, unknown>)\n              : {};\n        await saveBosConfig(session.root, {\n          ...authored,\n          plugins: {\n            ...authored.plugins,\n            [key]: applyEntryDelta(authoredBase),\n          },\n        });\n      } else {\n        await saveBosConfig(session.root, nextConfig);\n      }\n      await generateCodeArtifacts(session.root, nextConfig);\n\n      const stored = nextConfig.plugins?.[key];\n      const storedObj = stored && typeof stored === \"object\" ? stored : {};\n\n      return {\n        status: \"added\" as const,\n        key,\n        development: storedObj.development,\n        production: storedObj.production,\n        integrity: storedObj.integrity,\n        version: storedObj.version,\n      };\n    }),\n\n    pluginRemove: builder.pluginRemove.handler(async ({ input, context }) => {\n      const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n      const session = deps.session;\n      if (!session?.config) {\n        return {\n          status: \"error\" as const,\n          key: input.key,\n          error: MISSING_CONFIG_MESSAGE,\n        };\n      }\n\n      if (!session.config.plugins?.[input.key]) {\n        return {\n          status: \"error\" as const,\n          key: input.key,\n          error: `Plugin '${input.key}' is not configured`,\n        };\n      }\n\n      const nextPlugins = { ...session.config.plugins };\n      delete nextPlugins[input.key];\n      const nextConfig: BosConfig = {\n        ...session.config,\n        plugins: Object.keys(nextPlugins).length > 0 ? nextPlugins : undefined,\n      };\n\n      const authored = session.rawConfig;\n      if (authored) {\n        const authoredPlugins: Record<string, unknown> = { ...authored.plugins };\n        if (authoredPlugins[input.key] === undefined) {\n          // Inherited from the parent runtime. JSON-form children can express\n          // removal with a null sentinel (dropped by the extends merge); the\n          // TS descriptor schema cannot, so only those error out.\n          if (existsSync(join(session.root, \"bos.app.ts\"))) {\n            return {\n              status: \"error\" as const,\n              key: input.key,\n              error: `'${input.key}' is inherited from the parent runtime — remove it in the parent config or override it there (the authored descriptor cannot express removal)`,\n            };\n          }\n          authoredPlugins[input.key] = null;\n        } else {\n          delete authoredPlugins[input.key];\n        }\n        await saveBosConfig(session.root, {\n          ...authored,\n          plugins: Object.keys(authoredPlugins).length > 0 ? authoredPlugins : undefined,\n        });\n      } else {\n        await saveBosConfig(session.root, nextConfig);\n      }\n      await generateCodeArtifacts(session.root, nextConfig);\n\n      return {\n        status: \"removed\" as const,\n        key: input.key,\n      };\n    }),\n\n    pluginList: builder.pluginList.handler(async ({ context }) => {\n      const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n      const plugins: PluginListResult[\"plugins\"] = listPluginAttachments(\n        deps.session?.config ?? null,\n      );\n      return {\n        status: \"listed\" as const,\n        plugins,\n      };\n    }),\n\n    pluginPublish: builder.pluginPublish.handler(async ({ input, context }) => {\n      const deps = Context.get(context[\"effect/context\"], BosDepsTag);\n      const session = deps.session;\n      if (!session?.config) {\n        return {\n          status: \"error\" as const,\n          key: input.key,\n          error: MISSING_CONFIG_MESSAGE,\n        };\n      }\n\n      const attachment = session.config?.plugins?.[input.key];\n      if (!attachment) {\n        return {\n          status: \"error\" as const,\n          key: input.key,\n          error: `Plugin '${input.key}' is not configured`,\n        };\n      }\n\n      const localPath = pluginLocalPath(session.root, attachment);\n      if (!localPath) {\n        return {\n          status: \"error\" as const,\n          key: input.key,\n          error: `Plugin '${input.key}' does not have a local development path`,\n        };\n      }\n\n      const result = await publishToFastKv({\n        bosConfig: session.config,\n        runtimeConfig: session.runtime,\n        configDir: session.root,\n        env: \"production\",\n        build: true,\n        dryRun: false,\n        verbose: false,\n        packages: input.key,\n      });\n      if (result.status === \"error\") {\n        return {\n          status: \"error\" as const,\n          key: input.key,\n          error: result.error,\n        };\n      }\n\n      const nextSession = (await openResolution({ cwd: session.root })) ?? session;\n      const production = (\n        nextSession.config?.plugins?.[input.key] as\n          | {\n              production?: string;\n            }\n          | undefined\n      )?.production;\n\n      const manifest = production ? await fetchRemotePluginManifest(production) : null;\n      if (production && nextSession.config) {\n        await generateCodeArtifacts(session.root, nextSession.config);\n      }\n\n      return {\n        status: \"published\" as const,\n        key: input.key,\n        path: localPath,\n        production,\n        fingerprint: result.fingerprint,\n        version: manifest?.plugin.version,\n      };\n    }),\n  };\n}\n\ntype PluginAttachmentConfig = NonNullable<BosConfig[\"plugins\"]>[string];\n\nfunction sanitizePluginKey(value: string): string {\n  return value\n    .replace(/[^A-Za-z0-9/_-]/g, \"-\")\n    .replace(/\\/+/g, \"/\")\n    .split(\"/\")\n    .filter(Boolean)\n    .map((segment) => segment.replace(/[^A-Za-z0-9_-]/g, \"-\"))\n    .join(\"/\")\n    .replace(/^\\/+|\\/+$/g, \"\");\n}\n\nfunction defaultPluginKey(source: string): string {\n  const normalized = source.replace(/^local:/, \"\").replace(/\\/$/, \"\");\n  if (source.startsWith(\"local:\")) {\n    return sanitizePluginKey(basename(normalized)) || \"plugin\";\n  }\n\n  try {\n    const url = new URL(source);\n    return sanitizePluginKey(basename(url.pathname) || url.hostname) || \"plugin\";\n  } catch {\n    return sanitizePluginKey(source) || \"plugin\";\n  }\n}\n\nfunction pluginLocalPath(configDir: string, attachment: PluginAttachmentConfig): string | null {\n  const ref = getPluginRef(attachment);\n  const source = ref?.development ?? ref?.production;\n  if (!source?.startsWith(\"local:\")) {\n    return null;\n  }\n\n  return join(configDir, source.slice(\"local:\".length));\n}\n\nfunction listPluginAttachments(config: BosConfig | null) {\n  return (Object.entries(config?.plugins ?? {}) as Array<[string, PluginAttachmentConfig]>)\n    .map(([key, attachment]) => {\n      const ref = getPluginRef(attachment);\n      return {\n        key,\n        development: ref?.development,\n        production: ref?.production,\n        localPath: ref?.development?.startsWith(\"local:\")\n          ? ref.development.slice(\"local:\".length)\n          : undefined,\n        source: ref?.development?.startsWith(\"local:\") ? (\"local\" as const) : (\"remote\" as const),\n        integrity: ref?.integrity,\n        version: ref?.version,\n        name: ref?.name,\n      };\n    })\n    .sort((a, b) => a.key.localeCompare(b.key));\n}\n"],"mappings":"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"}