everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
1 lines • 17.7 kB
Source Map (JSON)
{"version":3,"file":"plugins-admin.mjs","names":[],"sources":["../../src/services/plugins-admin.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { MISSING_CONFIG_MESSAGE } from \"../config\";\nimport type {\n PluginAddResult,\n PluginListResult,\n PluginPublishResult,\n PluginRemoveResult,\n} from \"../contract\";\nimport type { BosConfig } from \"../types\";\nimport { saveBosConfig } from \"../utils/save-config\";\nimport { generateCodeArtifacts } from \"./codegen/artifacts\";\nimport { DeployTrainService } from \"./deploy/train\";\nimport { fetchRemotePluginManifest } from \"./registry/fastkv\";\nimport { openResolution, type ResolutionSession } from \"./resolution/session\";\nimport { getPluginRef } from \"./workspace/build-train\";\n\nexport class PluginAdminService extends Context.Service<\n PluginAdminService,\n {\n add: (\n session: ResolutionSession | null,\n input: { source: string; as?: string | undefined; production?: string | undefined },\n ) => Effect.Effect<PluginAddResult, unknown>;\n remove: (\n session: ResolutionSession | null,\n input: { key: string },\n ) => Effect.Effect<PluginRemoveResult, unknown>;\n list: (session: ResolutionSession | null) => Effect.Effect<PluginListResult[\"plugins\"], never>;\n publish: (\n session: ResolutionSession | null,\n input: { key: string },\n ) => Effect.Effect<PluginPublishResult, unknown>;\n }\n>()(\"bos/PluginAdminService\") {\n static Live = Layer.effect(\n PluginAdminService,\n Effect.map(DeployTrainService, (train) =>\n PluginAdminService.of({\n add: (session, input) =>\n Effect.gen(function* () {\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\n ? (input.source.split(\"/\").pop() ?? \"plugin\")\n : 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 yield* Effect.tryPromise(() =>\n saveBosConfig(session.root, {\n ...authored,\n plugins: {\n ...authored.plugins,\n [key]: applyEntryDelta(authoredBase),\n },\n }),\n );\n } else {\n yield* Effect.tryPromise(() => saveBosConfig(session.root, nextConfig));\n }\n yield* Effect.tryPromise(async () => {\n await generateCodeArtifacts(session.root, nextConfig);\n });\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 remove: (session, input) =>\n Effect.gen(function* () {\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 yield* Effect.tryPromise(() =>\n saveBosConfig(session.root, {\n ...authored,\n plugins: Object.keys(authoredPlugins).length > 0 ? authoredPlugins : undefined,\n }),\n );\n } else {\n yield* Effect.tryPromise(() => saveBosConfig(session.root, nextConfig));\n }\n yield* Effect.tryPromise(async () => {\n await generateCodeArtifacts(session.root, nextConfig);\n });\n\n return {\n status: \"removed\" as const,\n key: input.key,\n };\n }),\n\n list: (session) => Effect.succeed(listPluginAttachments(session?.config ?? null)),\n\n publish: (session, input) =>\n Effect.gen(function* () {\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 = yield* train\n .publish({\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 .pipe(\n Effect.catchTag(\"PublishTrainError\", (error) => Effect.succeed(error.toResult())),\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 =\n (yield* Effect.tryPromise({\n try: () => openResolution({ cwd: session.root }),\n catch: () => null,\n })) ?? 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\n ? yield* Effect.tryPromise({\n try: () => fetchRemotePluginManifest(production),\n catch: () => null,\n })\n : null;\n const nextConfig = nextSession.config;\n if (production && nextConfig) {\n yield* Effect.tryPromise({\n try: async () => {\n await generateCodeArtifacts(session.root, nextConfig);\n },\n catch: () => undefined,\n });\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 );\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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"}