everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
218 lines (216 loc) • 8.38 kB
JavaScript
const require_services_registry_fastkv = require('./registry/fastkv.cjs');
const require_load = require('./resolution/load.cjs');
const require_services_resolution_session = require('./resolution/session.cjs');
const require_artifacts = require('./codegen/artifacts.cjs');
const require_build_train = require('./workspace/build-train.cjs');
const require_save_config = require('../utils/save-config.cjs');
const require_train = require('./deploy/train.cjs');
let node_fs = require("node:fs");
let node_path = require("node:path");
let effect = require("effect");
//#region src/services/plugins-admin.ts
var PluginAdminService = class PluginAdminService extends effect.Context.Service()("bos/PluginAdminService") {
static Live = effect.Layer.effect(PluginAdminService, effect.Effect.map(require_train.DeployTrainService, (train) => PluginAdminService.of({
add: (session, input) => effect.Effect.gen(function* () {
if (!session?.config) return {
status: "error",
key: "",
error: require_load.MISSING_CONFIG_MESSAGE
};
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 = session.config.plugins?.[key];
const existingEntry = existing && typeof existing === "object" ? existing : {};
const applyEntryDelta = (base) => {
if (isBosRef) {
const { development: _dropped, ...rest } = base;
return {
...rest,
extends: input.source
};
}
if (isLocal) {
const { extends: _dropped, ...rest } = base;
return {
...rest,
development: input.source
};
}
const { development: _dropped, ...rest } = base;
return {
...rest,
production: input.production ?? input.source
};
};
const nextPlugins = { ...session.config.plugins };
nextPlugins[key] = applyEntryDelta(existingEntry);
const nextConfig = {
...session.config,
plugins: nextPlugins
};
const authored = session.rawConfig;
if (authored) {
const authoredExisting = authored.plugins?.[key];
const authoredBase = typeof authoredExisting === "string" ? { extends: authoredExisting } : authoredExisting && typeof authoredExisting === "object" ? authoredExisting : {};
yield* effect.Effect.tryPromise(() => require_save_config.saveBosConfig(session.root, {
...authored,
plugins: {
...authored.plugins,
[key]: applyEntryDelta(authoredBase)
}
}));
} else yield* effect.Effect.tryPromise(() => require_save_config.saveBosConfig(session.root, nextConfig));
yield* effect.Effect.tryPromise(async () => {
await require_artifacts.generateCodeArtifacts(session.root, nextConfig);
});
const stored = nextConfig.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
};
}),
remove: (session, input) => effect.Effect.gen(function* () {
if (!session?.config) return {
status: "error",
key: input.key,
error: require_load.MISSING_CONFIG_MESSAGE
};
if (!session.config.plugins?.[input.key]) return {
status: "error",
key: input.key,
error: `Plugin '${input.key}' is not configured`
};
const nextPlugins = { ...session.config.plugins };
delete nextPlugins[input.key];
const nextConfig = {
...session.config,
plugins: Object.keys(nextPlugins).length > 0 ? nextPlugins : void 0
};
const authored = session.rawConfig;
if (authored) {
const authoredPlugins = { ...authored.plugins };
if (authoredPlugins[input.key] === void 0) {
if ((0, node_fs.existsSync)((0, node_path.join)(session.root, "bos.app.ts"))) return {
status: "error",
key: input.key,
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)`
};
authoredPlugins[input.key] = null;
} else delete authoredPlugins[input.key];
yield* effect.Effect.tryPromise(() => require_save_config.saveBosConfig(session.root, {
...authored,
plugins: Object.keys(authoredPlugins).length > 0 ? authoredPlugins : void 0
}));
} else yield* effect.Effect.tryPromise(() => require_save_config.saveBosConfig(session.root, nextConfig));
yield* effect.Effect.tryPromise(async () => {
await require_artifacts.generateCodeArtifacts(session.root, nextConfig);
});
return {
status: "removed",
key: input.key
};
}),
list: (session) => effect.Effect.succeed(listPluginAttachments(session?.config ?? null)),
publish: (session, input) => effect.Effect.gen(function* () {
if (!session?.config) return {
status: "error",
key: input.key,
error: require_load.MISSING_CONFIG_MESSAGE
};
const attachment = session.config?.plugins?.[input.key];
if (!attachment) return {
status: "error",
key: input.key,
error: `Plugin '${input.key}' is not configured`
};
const localPath = pluginLocalPath(session.root, attachment);
if (!localPath) return {
status: "error",
key: input.key,
error: `Plugin '${input.key}' does not have a local development path`
};
const result = yield* train.publish({
bosConfig: session.config,
runtimeConfig: session.runtime,
configDir: session.root,
env: "production",
build: true,
dryRun: false,
verbose: false,
packages: input.key
}).pipe(effect.Effect.catchTag("PublishTrainError", (error) => effect.Effect.succeed(error.toResult())));
if (result.status === "error") return {
status: "error",
key: input.key,
error: result.error
};
const nextSession = (yield* effect.Effect.tryPromise({
try: () => require_services_resolution_session.openResolution({ cwd: session.root }),
catch: () => null
})) ?? session;
const production = (nextSession.config?.plugins?.[input.key])?.production;
const manifest = production ? yield* effect.Effect.tryPromise({
try: () => require_services_registry_fastkv.fetchRemotePluginManifest(production),
catch: () => null
}) : null;
const nextConfig = nextSession.config;
if (production && nextConfig) yield* effect.Effect.tryPromise({
try: async () => {
await require_artifacts.generateCodeArtifacts(session.root, nextConfig);
},
catch: () => void 0
});
return {
status: "published",
key: input.key,
path: localPath,
production,
fingerprint: result.fingerprint,
version: manifest?.plugin.version
};
})
})));
};
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((0, node_path.basename)(normalized)) || "plugin";
try {
const url = new URL(source);
return sanitizePluginKey((0, node_path.basename)(url.pathname) || url.hostname) || "plugin";
} catch {
return sanitizePluginKey(source) || "plugin";
}
}
function pluginLocalPath(configDir, attachment) {
const ref = require_build_train.getPluginRef(attachment);
const source = ref?.development ?? ref?.production;
if (!source?.startsWith("local:")) return null;
return (0, node_path.join)(configDir, source.slice(6));
}
function listPluginAttachments(config) {
return Object.entries(config?.plugins ?? {}).map(([key, attachment]) => {
const ref = require_build_train.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));
}
//#endregion
exports.PluginAdminService = PluginAdminService;
//# sourceMappingURL=plugins-admin.cjs.map