UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

1,197 lines (1,195 loc) 40.6 kB
import { buildRegistryConfigUrl, buildRegistryConfigUrlForNetwork, fetchBosConfigFromFastKv, fetchRemotePluginManifest, getRegistryNamespaceForAccount, getRegistryNamespaceForNetwork } from "./fastkv.mjs"; import { getNetworkIdForAccount } from "./network.mjs"; import { buildRuntimePluginsForConfig, findConfigPath, getHostDevelopmentPort, getProjectRoot, loadConfig, resolveLocalDevelopmentPath, writeResolvedConfig } from "./config.mjs"; import { createPlugin, z } from "./sdk.mjs"; import { bosContract } from "./contract.mjs"; import { writePluginSidebarGen } from "./sidebar.mjs"; import { computeSriHashForUrl } from "./integrity.mjs"; import { syncApiContractBridge } from "./api-contract.mjs"; import { buildRuntimeConfig, detectLocalPackages, prepareDevelopmentRuntimeConfig } from "./app.mjs"; import { ensureEnvFile, loadProjectEnv, writeGeneratedInfra } from "./cli/infra.mjs"; import { saveBosConfig } from "./utils/save-config.mjs"; import { buildInitPatterns, copyFilteredFiles, detectGitRemoteUrl, fetchParentConfig, generateDatabaseMigrations, personalizeConfig, removeInitLockfile, resolveSourceDir, runBunInstall, runTypesGen, scaffoldMinimalProject, stripOrphanedWorkspacesFromLockfile, writeInitSnapshot } from "./cli/init.mjs"; import { getStatus } from "./cli/status.mjs"; import { syncTemplate } from "./cli/sync.mjs"; import { upgradeTemplate } from "./cli/upgrade.mjs"; import { addFunctionCallAccessKey, ensureNearCli, executeTransaction } from "./near-cli.mjs"; import { buildDescription, buildServiceDescriptorMap } from "./service-descriptor.mjs"; import { syncAndGenerateSharedUi } from "./shared.mjs"; import { run } from "./utils/run.mjs"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { basename, dirname, join, resolve } from "node:path"; import { EventEmitter } from "node:events"; import { access, readFile } from "node:fs/promises"; import process from "node:process"; import { Effect } from "effect"; //#region src/plugin.ts const pluginEvents = new EventEmitter(); let pendingSession = null; let pendingStartSummary = null; function consumeDevSession() { const data = pendingSession; const summary = pendingStartSummary; pendingSession = null; pendingStartSummary = null; if (!data) return null; return summary ? { ...data, summary } : data; } async function timePhase(timings, name, fn) { pluginEvents.emit("progress", { phase: name, status: "running" }); const startedAt = Date.now(); try { const result = await fn(); timings.push({ name, durationMs: Date.now() - startedAt }); pluginEvents.emit("progress", { phase: name, status: "done", durationMs: Date.now() - startedAt }); return result; } catch (error) { pluginEvents.emit("progress", { phase: name, status: "error", durationMs: Date.now() - startedAt }); throw error; } } const buildCommands = { host: { cmd: "bun", args: ["run", "build"] }, ui: { cmd: "bun", args: ["run", "build"] }, api: { cmd: "bun", args: ["run", "build"] } }; const PUBLISH_FUNCTION_NAMES = ["__fastdata_kv"]; function getPluginRef(entry) { if (!entry || typeof entry === "string") return null; return entry; } function parseSourceMode(value, defaultValue) { if (value === "local" || value === "remote") return value; return defaultValue; } function buildConfigResult(bosConfig) { const packages = bosConfig ? Object.keys(bosConfig.app) : []; return { config: bosConfig, packages, remotes: packages.filter((name) => name !== "host") }; } async function fileExists(path) { try { await access(path); return true; } catch { return false; } } async function readJsonFile(path) { return JSON.parse(await readFile(path, "utf8")); } function resolveWorkspaceTarget(key, bosConfig, runtimeConfig, configDir) { if (bosConfig?.app && key in bosConfig.app) { const appEntry = bosConfig.app[key]; const devPath = resolveLocalDevelopmentPath(appEntry?.development, configDir); if (devPath) return { key, kind: "app", path: devPath }; return { key, kind: "app", path: `${configDir}/${key}` }; } const pluginPath = (runtimeConfig?.plugins?.[key])?.localPath ?? resolveLocalDevelopmentPath(getPluginRef(bosConfig?.plugins?.[key])?.development, configDir); if (pluginPath) return { key, kind: "plugin", path: pluginPath }; return null; } function isValidProxyUrl(url) { try { const parsed = new URL(url); return parsed.protocol === "http:" || parsed.protocol === "https:"; } catch { return false; } } function resolveProxyUrl(bosConfig) { if (!bosConfig) return null; const apiConfig = bosConfig.app.api; if (!apiConfig) return null; if (apiConfig.proxy && isValidProxyUrl(apiConfig.proxy)) return apiConfig.proxy; if (apiConfig.production && isValidProxyUrl(apiConfig.production)) return apiConfig.production; return null; } function sanitizePluginKey(value) { return value.replace(/[^A-Za-z0-9/_-]/g, "-").replace(/\/+/g, "/").split("/").filter(Boolean).map((segment) => segment.replace(/[^A-Za-z0-9_-]/g, "-")).join("/").replace(/^\/+|\/+$/g, ""); } function defaultPluginKey(source) { const normalized = source.replace(/^local:/, "").replace(/\/$/, ""); if (source.startsWith("local:")) return sanitizePluginKey(basename(normalized)) || "plugin"; try { const url = new URL(source); return sanitizePluginKey(basename(url.pathname) || url.hostname) || "plugin"; } catch { return sanitizePluginKey(source) || "plugin"; } } function pluginLocalPath(configDir, attachment) { const ref = getPluginRef(attachment); const source = ref?.development ?? ref?.production; if (!source?.startsWith("local:")) return null; return join(configDir, source.slice(6)); } function listPluginAttachments(config) { return Object.entries(config?.plugins ?? {}).map(([key, attachment]) => { const ref = getPluginRef(attachment); return { key, development: ref?.development, production: ref?.production, localPath: ref?.development?.startsWith("local:") ? ref.development.slice(6) : void 0, source: ref?.development?.startsWith("local:") ? "local" : "remote", integrity: ref?.integrity, version: ref?.version, name: ref?.name }; }).sort((a, b) => a.key.localeCompare(b.key)); } async function generateCodeArtifacts(configDir, config, opts) { if (opts?.env) writeResolvedConfig(configDir, config, opts.env, opts.extendsChain); const runtimeConfig = opts?.runtimeConfig ?? (await loadConfig({ cwd: configDir }))?.runtime; if (!runtimeConfig) return null; writePluginSidebarGen(configDir, runtimeConfig); const bridge = await syncApiContractBridge({ configDir, runtimeConfig, apiBaseUrl: runtimeConfig.api.url }); return { sidebarPath: join(configDir, "ui/src/lib/plugin-sidebar.gen.ts"), resolvedConfigPath: opts?.env ? join(configDir, ".bos/bos.resolved-config.json") : void 0, contractBridgePath: bridge.bridgePath }; } function extractPublishedUrl(output) { const match = output.match(/https?:\/\/[^\s"'<>]+/g); if (!match || match.length === 0) return null; return match[match.length - 1] ?? null; } async function buildEveryPluginQuietly(cwd) { if (!await fileExists(`${`${cwd}/packages/every-plugin`}/package.json`)) return; if (await fileExists(`${cwd}/packages/every-plugin/dist/build/rspack/plugin.mjs`)) return; const result = await run("bun", [ "run", "--cwd", "packages/every-plugin", "build" ], { cwd, capture: true }); if (result.exitCode === 0) { console.log("[build:ssr] build succeeded"); return; } if (result.stdout.trim()) process.stdout.write(result.stdout); if (result.stderr.trim()) process.stderr.write(result.stderr); throw new Error(`bun run --cwd packages/every-plugin build failed with exit code ${result.exitCode}`); } async function buildEverythingDevQuietly(cwd) { if (!await fileExists(`${`${cwd}/packages/everything-dev`}/package.json`)) return; if (await fileExists(`${cwd}/packages/everything-dev/dist/index.mjs`)) return; const result = await run("bun", [ "run", "--cwd", "packages/everything-dev", "build" ], { cwd, capture: true }); if (result.exitCode === 0) { console.log("[everything-dev] build succeeded"); return; } if (result.stdout.trim()) process.stdout.write(result.stdout); if (result.stderr.trim()) process.stderr.write(result.stderr); throw new Error(`bun run --cwd packages/everything-dev build failed with exit code ${result.exitCode}`); } async function fetchPublishedConfig(accountId, gatewayId) { try { return await fetchBosConfigFromFastKv(`bos://${accountId}/${gatewayId}`); } catch { return null; } } function selectWorkspaceTargets(packages, bosConfig) { const allPackages = [...Object.keys(bosConfig?.app ?? {}), ...Object.keys(bosConfig?.plugins ?? {})]; if (packages === "all") return allPackages; return packages.split(",").map((pkg) => pkg.trim()).filter((pkg) => allPackages.includes(pkg)); } async function buildWorkspaceTargets(opts) { const existing = []; const skipped = []; for (const target of opts.targets) { const resolved = resolveWorkspaceTarget(target, opts.bosConfig, opts.runtimeConfig, opts.configDir); if (!resolved) { skipped.push(target); continue; } if (await fileExists(`${resolved.path}/package.json`)) existing.push(resolved); else skipped.push(target); } if (existing.length === 0) return { built: [], skipped }; if ((await syncAndGenerateSharedUi({ configDir: opts.configDir, hostMode: "local", bosConfig: opts.bosConfig ?? void 0, extendsChain: [] })).catalogChanged) await run("bun", ["install"], { cwd: opts.configDir }); if (existing.some((entry) => entry.key === "api")) await buildEveryPluginQuietly(opts.configDir); await buildEverythingDevQuietly(opts.configDir); const env = { ...process.env, NODE_ENV: opts.deploy ? "production" : "development" }; if (opts.deploy) env.DEPLOY = "true"; else delete env.DEPLOY; const orderedExisting = opts.deploy ? [ ...existing.filter((entry) => entry.kind === "app" && entry.key !== "host"), ...existing.filter((entry) => entry.kind === "plugin"), ...existing.filter((entry) => entry.kind === "app" && entry.key === "host") ] : existing; const built = []; for (const resolved of orderedExisting) { const pkgJson = await readJsonFile(`${resolved.path}/package.json`); const buildConfig = opts.deploy && pkgJson.scripts?.deploy ? { cmd: "bun", args: ["run", "deploy"] } : buildCommands[resolved.key] ?? { cmd: "bun", args: ["run", "build"] }; await run(buildConfig.cmd, buildConfig.args, { cwd: resolved.path, env }); built.push(resolved.key); } return { built, skipped }; } var plugin_default = createPlugin({ variables: z.object({ configPath: z.string().optional() }), secrets: z.object({}), contract: bosContract, initialize: (config) => Effect.promise(async () => { const configResult = await loadConfig({ path: config.variables.configPath }); return { bosConfig: configResult?.config ?? null, runtimeConfig: configResult?.runtime ?? null, configDir: getProjectRoot() }; }), shutdown: () => Effect.void, createRouter: (deps, builder) => ({ config: builder.config.handler(async () => buildConfigResult(deps.bosConfig)), pluginAdd: builder.pluginAdd.handler(async ({ input }) => { if (!deps.bosConfig) return { status: "error", key: "", error: "No bos.config.json found" }; const isBosRef = input.source.startsWith("bos://"); const isLocal = input.source.startsWith("local:"); const key = sanitizePluginKey(input.as ?? (isBosRef ? input.source.split("/").pop() ?? "plugin" : defaultPluginKey(input.source))); const existing = deps.bosConfig.plugins?.[key]; const existingEntry = existing && typeof existing === "object" ? existing : {}; const nextPlugins = { ...deps.bosConfig.plugins ?? {} }; if (isBosRef) nextPlugins[key] = { ...existingEntry, extends: input.source }; else if (isLocal) nextPlugins[key] = { ...existingEntry, development: input.source, ...existingEntry.extends ? {} : {} }; else nextPlugins[key] = { ...existingEntry, production: input.production ?? input.source }; deps.bosConfig = { ...deps.bosConfig, plugins: nextPlugins }; await saveBosConfig(deps.configDir, deps.bosConfig); await generateCodeArtifacts(deps.configDir, deps.bosConfig); const stored = deps.bosConfig.plugins?.[key]; const storedObj = stored && typeof stored === "object" ? stored : {}; return { status: "added", key, development: storedObj.development, production: storedObj.production, integrity: storedObj.integrity, version: storedObj.version }; }), pluginRemove: builder.pluginRemove.handler(async ({ input }) => { if (!deps.bosConfig) return { status: "error", key: input.key, error: "No bos.config.json found" }; if (!deps.bosConfig.plugins?.[input.key]) return { status: "error", key: input.key, error: `Plugin '${input.key}' is not configured` }; const nextPlugins = { ...deps.bosConfig.plugins ?? {} }; delete nextPlugins[input.key]; deps.bosConfig = { ...deps.bosConfig, plugins: Object.keys(nextPlugins).length > 0 ? nextPlugins : void 0 }; await saveBosConfig(deps.configDir, deps.bosConfig); await generateCodeArtifacts(deps.configDir, deps.bosConfig); return { status: "removed", key: input.key }; }), pluginList: builder.pluginList.handler(async () => { return { status: "listed", plugins: listPluginAttachments(deps.bosConfig) }; }), pluginPublish: builder.pluginPublish.handler(async ({ input }) => { if (!deps.bosConfig) return { status: "error", key: input.key, error: "No bos.config.json found" }; const attachment = deps.bosConfig.plugins?.[input.key]; if (!attachment) return { status: "error", key: input.key, error: `Plugin '${input.key}' is not configured` }; const attachmentRef = getPluginRef(attachment); const localPath = pluginLocalPath(deps.configDir, attachment); if (!localPath) return { status: "error", key: input.key, error: `Plugin '${input.key}' does not have a local development path` }; const pkgPath = join(localPath, "package.json"); if (!await fileExists(pkgPath)) return { status: "error", key: input.key, error: `Missing package.json at ${localPath}` }; const pkgJson = await readJsonFile(pkgPath); const script = pkgJson.scripts?.deploy ? "deploy" : "build"; const { stdout, stderr, exitCode } = await run("bun", ["run", script], { cwd: localPath, capture: true }); if (exitCode !== 0) { if (stdout.trim()) process.stdout.write(stdout); if (stderr.trim()) process.stderr.write(stderr); return { status: "error", key: input.key, error: `Publish failed with exit code ${exitCode}` }; } if (stdout.trim()) process.stdout.write(stdout); if (stderr.trim()) process.stderr.write(stderr); let publishedUrl = extractPublishedUrl(`${stdout}\n${stderr}`); let manifest = null; if (publishedUrl) manifest = await fetchRemotePluginManifest(publishedUrl); else if (attachmentRef?.production) { manifest = await fetchRemotePluginManifest(attachmentRef.production); if (manifest) publishedUrl = attachmentRef.production; } const integrity = publishedUrl ? await computeSriHashForUrl(publishedUrl) : null; const version = manifest?.plugin.version ?? pkgJson.version; if (publishedUrl) { const rootConfigPath = join(deps.configDir, "bos.config.json"); try { const rootConfig = JSON.parse(readFileSync(rootConfigPath, "utf-8")); if (!rootConfig.plugins || typeof rootConfig.plugins !== "object") rootConfig.plugins = {}; const plugins = rootConfig.plugins; if (!plugins[input.key] || typeof plugins[input.key] !== "object") plugins[input.key] = {}; const entry = plugins[input.key]; entry.production = publishedUrl; if (integrity) entry.integrity = integrity; else delete entry.integrity; writeFileSync(rootConfigPath, `${JSON.stringify(rootConfig, null, 2)}\n`); console.log(` ✅ Updated bos.config.json: plugins.${input.key}.production`); } catch (err) { console.error(` ❌ Failed to update bos.config.json:`, err instanceof Error ? err.message : err); } await generateCodeArtifacts(deps.configDir, deps.bosConfig); } return { status: "published", key: input.key, path: localPath, script, production: publishedUrl ?? attachmentRef?.production, integrity: integrity ?? void 0, version: version ?? void 0 }; }), dev: builder.dev.handler(async ({ input }) => { ensureEnvFile(deps.configDir); loadProjectEnv(deps.configDir); pluginEvents.emit("progress", { phase: "config", status: "running" }); const localPackages = detectLocalPackages(deps.bosConfig ?? void 0, deps.runtimeConfig ?? void 0); const hostSource = localPackages.includes("host") ? parseSourceMode(input.host, "local") : "remote"; const uiSource = localPackages.includes("ui") ? parseSourceMode(input.ui, "local") : "remote"; const apiSource = localPackages.includes("api") ? parseSourceMode(input.api, "local") : "remote"; const authSource = localPackages.includes("auth") ? parseSourceMode(input.auth, "local") : "remote"; const ssr = input.ssr ?? false; const proxy = input.proxy ?? false; if ((await syncAndGenerateSharedUi({ configDir: deps.configDir, hostMode: hostSource, bosConfig: deps.bosConfig ?? void 0, extendsChain: [] })).catalogChanged) { pluginEvents.emit("progress", { phase: "install", status: "running" }); await run("bun", ["install"], { cwd: deps.configDir }); pluginEvents.emit("progress", { phase: "install", status: "done" }); } if (apiSource === "local" && !proxy || localPackages.some((pkg) => pkg.startsWith("plugin:"))) { pluginEvents.emit("progress", { phase: "build plugin", status: "running" }); await buildEveryPluginQuietly(deps.configDir); pluginEvents.emit("progress", { phase: "build plugin", status: "done" }); } pluginEvents.emit("progress", { phase: "build", status: "running" }); await buildEverythingDevQuietly(deps.configDir); pluginEvents.emit("progress", { phase: "build", status: "done" }); pluginEvents.emit("progress", { phase: "config", status: "done" }); const refreshed = await loadConfig({ cwd: deps.configDir }); deps.bosConfig = refreshed?.config ?? deps.bosConfig; deps.runtimeConfig = refreshed?.runtime ?? deps.runtimeConfig; if (!deps.bosConfig) return { status: "error", description: "No bos.config.json found", processes: [] }; if (proxy && !resolveProxyUrl(deps.bosConfig)) return { status: "error", description: "No valid proxy URL configured in bos.config.json", processes: [] }; const hostPort = input.port ?? getHostDevelopmentPort(deps.bosConfig.app.host.development); const runtimeConfig = await prepareDevelopmentRuntimeConfig(buildRuntimeConfig(deps.bosConfig, { uiSource, apiSource, authSource, hostSource, env: "development", plugins: deps.runtimeConfig?.plugins }), { hostPort, ssr }); await generateCodeArtifacts(deps.configDir, deps.bosConfig, { env: "development", extendsChain: refreshed?.source.extended, runtimeConfig }); const services = buildServiceDescriptorMap(runtimeConfig, { ssr, proxy }); const packages = [...services.keys()]; const displayEnv = {}; if (services.get("api")?.proxy) { const proxyUrl = resolveProxyUrl(deps.bosConfig); if (proxyUrl) displayEnv.API_PROXY = proxyUrl; } const orchestrator = { packages, env: displayEnv, description: buildDescription(services), port: runtimeConfig.host.port, interactive: input.interactive }; pendingSession = { orchestrator, services, runtimeConfig }; return { status: "started", description: orchestrator.description, processes: packages }; }), start: builder.start.handler(async ({ input }) => { ensureEnvFile(deps.configDir); loadProjectEnv(deps.configDir); pluginEvents.emit("progress", { phase: "config", status: "running" }); const account = input.account ?? process.env.BOS_ACCOUNT; const domain = input.domain ?? process.env.BOS_GATEWAY; let config = null; let remoteConfig = null; if (account && domain) { remoteConfig = await fetchPublishedConfig(account, domain); if (remoteConfig) config = remoteConfig; else console.warn(`[Start] Failed to fetch remote config for ${account}/${domain}, falling back to local bos.config.json`); } if (!config) config = deps.bosConfig; if (!config) return { status: "error", url: "", error: "No configuration found. Set BOS_ACCOUNT and BOS_GATEWAY environment variables, or provide a local bos.config.json." }; if (account) config = { ...config, account }; if (domain) config = { ...config, domain }; const port = input.port ?? getHostDevelopmentPort(config.app.host.development); const isStaging = input.env === "staging"; const runtimePlugins = await buildRuntimePluginsForConfig(config, deps.configDir, "production"); const runtimeConfig = buildRuntimeConfig(config, { uiSource: "remote", apiSource: "remote", authSource: "remote", hostSource: "remote", env: "production", plugins: runtimePlugins }); pluginEvents.emit("progress", { phase: "generate artifacts", status: "running" }); await generateCodeArtifacts(deps.configDir, config, { env: "production", runtimeConfig }); pluginEvents.emit("progress", { phase: "generate artifacts", status: "done" }); const productionEnv = {}; const warnings = []; if (!process.env.CORS_ORIGIN && config.domain) { const defaultOrigin = `https://${config.domain}`; productionEnv.CORS_ORIGIN = defaultOrigin; warnings.push(`CORS_ORIGIN defaulting to ${defaultOrigin}`); } const requiredSecrets = /* @__PURE__ */ new Set(); const missingSecrets = []; if (runtimeConfig.auth?.secrets) for (const s of runtimeConfig.auth.secrets) requiredSecrets.add(s); if (runtimeConfig.api?.secrets) for (const s of runtimeConfig.api.secrets) requiredSecrets.add(s); for (const plugin of Object.values(runtimeConfig.plugins ?? {})) if (plugin.secrets) for (const s of plugin.secrets) requiredSecrets.add(s); for (const secret of requiredSecrets) { const value = process.env[secret]; if (!value || value.length === 0) missingSecrets.push(secret); } if (missingSecrets.length > 0) warnings.push(`Missing ${missingSecrets.length} secret(s): ${missingSecrets.join(", ")}`); const services = buildServiceDescriptorMap(runtimeConfig); const stagingEnvVars = isStaging ? { BOS_GATEWAY: config.staging?.domain ?? config.domain ?? "" } : {}; const summary = { configSource: remoteConfig ? `bos://${account}/${domain}` : findConfigPath() ?? "bos.config.json", configSourceHttp: remoteConfig && account && domain ? buildRegistryConfigUrl(account, domain) : void 0, account: config.account, domain: config.domain ?? void 0, modules: { host: runtimeConfig.host.remoteUrl ?? runtimeConfig.host.url ?? "local", ui: runtimeConfig.ui.url ?? "local", api: runtimeConfig.api.url ?? "local", auth: runtimeConfig.auth?.url ?? void 0 }, warnings }; pendingSession = { orchestrator: { packages: ["host"], env: { NODE_ENV: "production", ...productionEnv, ...stagingEnvVars }, description: `${isStaging ? "Staging" : "Production"} Mode (${config.account})`, port, interactive: input.interactive, noLogs: true }, services, runtimeConfig }; pendingStartSummary = summary; pluginEvents.emit("progress", { phase: "config", status: "done" }); return { status: "running", url: `http://localhost:${port}` }; }), build: builder.build.handler(async ({ input }) => { if (!deps.bosConfig) return { status: "error", built: [], skipped: [] }; const buildEnv = input.deploy ? "production" : "development"; const targets = selectWorkspaceTargets(input.packages, deps.bosConfig); if (targets.length === 0) return { status: "error", built: [], skipped: [] }; const runtimeConfig = buildRuntimeConfig(deps.bosConfig, { uiSource: deps.bosConfig.app.ui?.development ? "local" : "remote", apiSource: deps.bosConfig.app.api?.development ? "local" : "remote", authSource: deps.bosConfig.app.auth?.development ? "local" : "remote", hostSource: deps.bosConfig.app.host?.development ? "local" : "remote", env: buildEnv, plugins: deps.runtimeConfig?.plugins }); await generateCodeArtifacts(deps.configDir, deps.bosConfig, { env: buildEnv, runtimeConfig }); const { built, skipped } = await buildWorkspaceTargets({ configDir: deps.configDir, bosConfig: deps.bosConfig, runtimeConfig, targets, deploy: input.deploy }); if (built.length === 0) return { status: "error", built: [], skipped }; return { status: "success", built, skipped, deployed: input.deploy }; }), publish: builder.publish.handler(async ({ input }) => { if (!deps.bosConfig) return { status: "error", registryUrl: "", error: "No bos.config.json found" }; const account = deps.bosConfig.account; const gateway = deps.bosConfig.domain; if (!gateway) return { status: "error", registryUrl: "", error: "bos.config.json must define domain to publish" }; const network = input.network ?? getNetworkIdForAccount(account); const bosUrl = `bos://${account}/${gateway}`; const registryUrl = buildRegistryConfigUrlForNetwork(network, account, gateway); const targets = selectWorkspaceTargets(input.packages, deps.bosConfig); let publishConfig = deps.bosConfig; let built; let skipped; if (input.dryRun) return { status: "dry-run", registryUrl, built, skipped }; if (input.deploy) { await generateCodeArtifacts(deps.configDir, deps.bosConfig, { env: "production", runtimeConfig: deps.runtimeConfig ?? void 0 }); const result = await buildWorkspaceTargets({ configDir: deps.configDir, bosConfig: deps.bosConfig, runtimeConfig: deps.runtimeConfig, targets, deploy: true }); built = result.built; skipped = result.skipped; const refreshed = await loadConfig({ cwd: deps.configDir }); if (refreshed?.config) { deps.bosConfig = refreshed.config; deps.runtimeConfig = refreshed.runtime; publishConfig = refreshed.config; } } const registryEntries = { [`apps/${account}/${gateway}/bos.config.json`]: JSON.stringify(publishConfig) }; const payload = JSON.stringify(registryEntries); const argsBase64 = Buffer.from(payload).toString("base64"); const privateKey = input.privateKey || process.env.NEAR_PRIVATE_KEY || process.env.BOS_NEAR_PRIVATE_KEY; try { await Effect.runPromise(ensureNearCli); let txHash; try { txHash = (await Effect.runPromise(executeTransaction({ account, contract: getRegistryNamespaceForNetwork(network), method: "__fastdata_kv", argsBase64, network, privateKey, gas: "300Tgas", deposit: "0NEAR" }))).txHash; } catch (error) { txHash = extractTransactionHash(error); if (!txHash) throw error; try { const verifiedConfig = await fetchBosConfigFromFastKv(bosUrl); if (JSON.stringify(verifiedConfig) !== JSON.stringify(publishConfig)) throw error; } catch {} } return { status: "published", registryUrl, txHash, built, skipped }; } catch (error) { return { status: "error", registryUrl, error: error instanceof Error ? error.message : "Unknown error", built, skipped }; } }), keyPublish: builder.keyPublish.handler(async ({ input }) => { if (!deps.bosConfig) return { status: "error", account: "", network: "mainnet", contract: "", allowance: input.allowance, functionNames: PUBLISH_FUNCTION_NAMES, error: "No bos.config.json found" }; const account = deps.bosConfig.account; const network = getNetworkIdForAccount(account); const contract = getRegistryNamespaceForAccount(account); try { await Effect.runPromise(ensureNearCli); const keyPair = await addFunctionCallAccessKey({ account, contract, allowance: input.allowance, functionNames: PUBLISH_FUNCTION_NAMES, network }); return { status: "published", account, network, contract, allowance: input.allowance, functionNames: PUBLISH_FUNCTION_NAMES, publicKey: keyPair.publicKey, privateKey: keyPair.privateKey }; } catch (error) { return { status: "error", account, network, contract, allowance: input.allowance, functionNames: PUBLISH_FUNCTION_NAMES, error: error instanceof Error ? error.message : "Unknown error" }; } }), init: builder.init.handler(async ({ input }) => { try { const timings = []; let extendsAccount = ""; let extendsGateway = ""; let directory = input.directory; const account = input.account; const domain = input.domain; let overrides = input.overrides; let plugins = input.plugins; if (input.extends) { const match = (input.extends.startsWith("bos://") ? input.extends : `bos://${input.extends}`).match(/^bos:\/\/([^/]+)\/(.+)$/); if (match) { extendsAccount = match[1]; extendsGateway = match[2]; } } extendsAccount = extendsAccount || "dev.everything.near"; extendsGateway = extendsGateway || "everything.dev"; let parentPluginKeys = []; let parentConfig = null; try { parentConfig = await timePhase(timings, "parent config", () => fetchParentConfig(extendsAccount, extendsGateway)); if (parentConfig?.plugins && typeof parentConfig.plugins === "object") parentPluginKeys = Object.keys(parentConfig.plugins); } catch {} overrides = overrides?.length ? overrides : ["ui", "api"]; if (overrides.includes("plugins") && plugins === void 0) plugins = parentPluginKeys; plugins = plugins ?? []; directory = directory || domain || extendsGateway; const targetDir = resolve(directory); const extendsRef = `bos://${extendsAccount}/${extendsGateway}`; const repository = await detectGitRemoteUrl(process.cwd()).catch(() => void 0) ?? parentConfig?.repository; if (!parentConfig) try { parentConfig = await timePhase(timings, "parent config", () => fetchParentConfig(extendsAccount, extendsGateway)); } catch { return { status: "error", directory, extendsRef, account, domain, extends: extendsRef, plugins, overrides, filesCopied: 0, timings, error: `No config found at ${extendsRef} — are you sure this is the right parent?` }; } const { sourceDir, parentConfig: resolvedParentConfig, cleanup } = await timePhase(timings, "template source", () => resolveSourceDir({ extendsAccount, extendsGateway, source: input.source })); parentConfig = resolvedParentConfig; const isMinimalScaffold = sourceDir === ""; try { let filesCopied; if (isMinimalScaffold) { filesCopied = await timePhase(timings, "scaffold project", () => scaffoldMinimalProject(targetDir, parentConfig, { extendsAccount, extendsGateway, account: account || extendsAccount, domain, plugins, overrides, repository, title: parentConfig?.title, description: parentConfig?.description })); await timePhase(timings, "personalize config", () => personalizeConfig(targetDir, { extendsAccount, extendsGateway, account: account || extendsAccount, domain: domain || extendsGateway, plugins, overrides, mode: "init", repository, title: parentConfig?.title, description: parentConfig?.description, testnet: parentConfig?.testnet, staging: parentConfig?.staging })); } else { const patterns = buildInitPatterns(overrides, plugins); filesCopied = await timePhase(timings, "copy files", () => copyFilteredFiles(sourceDir, targetDir, patterns, { overrides, plugins })); await timePhase(timings, "personalize config", () => personalizeConfig(targetDir, { extendsAccount, extendsGateway, account: account || extendsAccount, domain: domain || extendsGateway, plugins, overrides, workspaceOpts: { sourceDir }, repository, title: parentConfig?.title, description: parentConfig?.description, testnet: parentConfig?.testnet, staging: parentConfig?.staging })); await timePhase(timings, "write snapshot", () => writeInitSnapshot(targetDir, extendsAccount, extendsGateway, sourceDir, patterns, { overrides, plugins })); } const lockfilePath = join(targetDir, "bun.lock"); stripOrphanedWorkspacesFromLockfile(lockfilePath, computeAllowedWorkspaces(overrides, plugins)); removeInitLockfile(lockfilePath); const initConfig = await timePhase(timings, "resolve config", () => loadConfig({ cwd: targetDir })); if (initConfig?.runtime) await timePhase(timings, "generate env/docker", async () => { writeGeneratedInfra(targetDir, initConfig.runtime); }); await timePhase(timings, "create env file", async () => { ensureEnvFile(targetDir); }); if (!input.noInstall) { await timePhase(timings, "install dependencies", () => runBunInstall(targetDir)); await timePhase(timings, "generate types", () => runTypesGen(targetDir)); await timePhase(timings, "generate migrations", () => generateDatabaseMigrations(targetDir)); } if (input.noInstall && initConfig?.config) await timePhase(timings, "generate code artifacts", () => generateCodeArtifacts(targetDir, initConfig.config)); return { status: "initialized", directory, extendsRef, account, domain, extends: extendsRef, plugins, overrides, filesCopied, timings, targetDir }; } finally { await cleanup(); } } catch (error) { const extendsRef = input.extends ? input.extends.startsWith("bos://") ? input.extends : `bos://${input.extends}` : "bos://dev.everything.near/everything.dev"; return { status: "error", directory: input.directory ?? "", extendsRef, account: input.account, domain: input.domain, extends: extendsRef, plugins: input.plugins ?? [], overrides: input.overrides, filesCopied: 0, timings: [], error: error instanceof Error ? error.message : "Unknown error" }; } }), sync: builder.sync.handler(async ({ input }) => { try { const configPath = findConfigPath(); if (!configPath) return { status: "error", updated: [], skipped: [], added: [], error: "No bos.config.json found in current directory" }; const projectDir = resolve(dirname(configPath)); const result = await syncTemplate(projectDir, input); if (result.status === "synced" || result.status === "dry-run") { const syncedConfig = await loadConfig({ cwd: projectDir }); if (syncedConfig?.config) await generateCodeArtifacts(projectDir, syncedConfig.config); } return result; } catch (error) { return { status: "error", updated: [], skipped: [], added: [], error: error instanceof Error ? error.message : "Unknown error" }; } }), upgrade: builder.upgrade.handler(async ({ input }) => { try { const configPath = findConfigPath(); if (!configPath) return { status: "error", packages: [], error: "No bos.config.json found in current directory" }; return await upgradeTemplate(resolve(dirname(configPath)), input); } catch (error) { return { status: "error", packages: [], error: error instanceof Error ? error.message : "Unknown error" }; } }), typesGen: builder.typesGen.handler(async ({ input }) => { try { const configPath = findConfigPath(); if (!configPath) return { status: "error", generated: [], fetched: [], skipped: [], failed: [], error: "No bos.config.json found in current directory" }; const projectDir = resolve(dirname(configPath)); const refreshed = await loadConfig({ cwd: projectDir, env: input.env ?? (process.env.NODE_ENV === "production" ? "production" : "development") }); if (!refreshed) return { status: "error", generated: [], fetched: [], skipped: [], failed: [], error: "Failed to load bos.config.json" }; if (input.dryRun) { const pluginEntries = Object.entries(refreshed.runtime.plugins ?? {}); const fetched = []; const skipped = []; if (refreshed.runtime.api.source !== "local") fetched.push(refreshed.runtime.api.url); else skipped.push("api (local)"); if (refreshed.runtime.auth) if (refreshed.runtime.auth.source !== "local") fetched.push(refreshed.runtime.auth.url); else skipped.push("auth (local)"); for (const [key, plugin] of pluginEntries) if (plugin.url && plugin.source !== "local") fetched.push(plugin.url); else if (plugin.localPath) skipped.push(`${key} (local)`); const generated = [ "ui/src/lib/api-types.gen.ts", "ui/src/lib/auth-types.gen.ts", "api/src/lib/plugins-types.gen.ts", "api/src/lib/auth-types.gen.ts" ]; if (existsSync(join(projectDir, "host", "src"))) generated.push("host/src/lib/auth-types.gen.ts"); return { status: "success", generated, fetched, skipped, failed: [], source: refreshed.runtime.api.source }; } await generateCodeArtifacts(projectDir, refreshed.config, { runtimeConfig: refreshed.runtime }); const generated = [ "ui/src/lib/plugin-sidebar.gen.ts", "ui/src/lib/api-types.gen.ts", "api/src/lib/plugins-types.gen.ts", "api/src/lib/auth-types.gen.ts" ]; if (refreshed.runtime.auth && (refreshed.runtime.auth.source !== "local" || refreshed.runtime.auth.localPath)) generated.push("ui/src/lib/auth-types.gen.ts"); if (existsSync(join(projectDir, "host", "src"))) generated.push("host/src/lib/auth-types.gen.ts"); return { status: "success", generated, fetched: refreshed.runtime.api.source === "remote" ? [refreshed.runtime.api.url] : [], skipped: refreshed.runtime.api.source === "local" ? ["api (local)"] : [], failed: [], source: refreshed.runtime.api.source }; } catch (error) { return { status: "error", generated: [], fetched: [], skipped: [], failed: [], error: error instanceof Error ? error.message : "Unknown error" }; } }), status: builder.status.handler(async () => { try { const configPath = findConfigPath(); if (!configPath) return { status: "error", packages: [], envFile: "missing", error: "No bos.config.json found in current directory" }; return await getStatus(resolve(dirname(configPath))); } catch (error) { return { status: "error", packages: [], envFile: "missing", error: error instanceof Error ? error.message : "Unknown error" }; } }) }) }); function extractTransactionHash(error) { return (error instanceof Error ? error.message : String(error)).match(/Transaction ID:\s*([A-Za-z0-9]+)/i)?.[1]; } function computeAllowedWorkspaces(overrides, plugins) { const workspaces = []; for (const section of overrides) { if (section === "host") workspaces.push("host"); if (section === "ui") workspaces.push("ui"); if (section === "api") workspaces.push("api"); } if (plugins && plugins.length > 0) workspaces.push("plugins/*"); return workspaces; } //#endregion export { consumeDevSession, plugin_default as default, pluginEvents }; //# sourceMappingURL=plugin.mjs.map