everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
214 lines (212 loc) • 9.62 kB
JavaScript
const require_process_env = require('../../env/process-env.cjs');
const require_service_descriptor = require('./service-descriptor.cjs');
const require_run = require('../../utils/run.cjs');
const require_services_resolution_session = require('../resolution/session.cjs');
const require_project_env = require('../../env/project-env.cjs');
const require_artifacts = require('../codegen/artifacts.cjs');
const require_workspace = require('../workspace/workspace.cjs');
const require_shared_deps = require('../workspace/shared-deps.cjs');
const require_materializer = require('../../infra/materializer.cjs');
const require_planner = require('../../infra/planner.cjs');
const require_logs = require('./logs.cjs');
const require_docker = require('../../infra/docker.cjs');
const require_preflight = require('../../infra/preflight.cjs');
const require_linked_packages = require('../workspace/linked-packages.cjs');
const require_orchestrator = require('./orchestrator.cjs');
const require_progress = require('./progress.cjs');
let effect = require("effect");
let node_crypto = require("node:crypto");
//#region src/services/dev/boot.ts
var DevStepError = class extends effect.Data.TaggedError("DevStepError") {};
var DevConfigMissing = class extends effect.Data.TaggedError("DevConfigMissing") {};
var DevProxyMissing = class extends effect.Data.TaggedError("DevProxyMissing") {};
var DevPreflightFailed = class extends effect.Data.TaggedError("DevPreflightFailed") {};
function parseSourceMode(value, defaultValue) {
if (value === "local" || value === "remote") return value;
return defaultValue;
}
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;
}
const step = (timings, name, fn) => require_progress.timedPhase(timings, name, effect.Effect.tryPromise({
try: fn,
catch: (cause) => new DevStepError({
phase: name,
cause
})
}));
const loadEnvStep = (projectEnv, configDir) => projectEnv.load(configDir).pipe(effect.Effect.mapError((cause) => new DevStepError({
phase: "load env",
cause
})));
const devBootstrap = (deps, input, timings) => effect.Effect.gen(function* () {
const shell = yield* require_project_env.captureShellEnv;
const projectEnv = yield* require_project_env.ProjectEnv;
if (!deps.session) return yield* new DevConfigMissing({});
let session = deps.session;
yield* loadEnvStep(projectEnv, session.root);
const operatorEnv = require_process_env.operatorEnvRecord();
const localPackages = require_linked_packages.detectLocalPackages(session.config ?? void 0, session.runtime ?? void 0, session.root);
const localPackageNames = new Set(localPackages.map((entry) => entry.name));
const localDirs = localPackages.map((entry) => entry.dir);
const hostSource = localPackageNames.has("host") ? parseSourceMode(input.host, "local") : "remote";
const uiSource = localPackageNames.has("ui") ? parseSourceMode(input.ui, "local") : "remote";
const apiSource = localPackageNames.has("api") ? parseSourceMode(input.api, "local") : "remote";
const authSource = localPackageNames.has("auth") ? parseSourceMode(input.auth, "local") : "remote";
const ssr = input.ssr ?? false;
const proxy = input.proxy ?? false;
if (input.logLevel) yield* effect.Effect.sync(() => {
require_process_env.setProcessEnv("BOS_LOG_LEVEL", input.logLevel);
});
if (ssr) yield* effect.Effect.sync(() => {
require_process_env.setProcessEnv("BOS_SSR", "1");
});
const sharedSync = yield* step(timings, "shared deps", () => require_shared_deps.syncResolvedSharedDeps({
configDir: session.root,
hostMode: hostSource,
bosConfig: session.config ?? void 0,
extendsChain: []
}));
let configMayHaveChanged = false;
if (sharedSync.catalogChanged) {
yield* step(timings, "install", () => require_run.run("pnpm", ["install"], { cwd: require_workspace.findWorkspaceRoot(session.root)?.dir ?? session.root }));
configMayHaveChanged = true;
}
yield* step(timings, "build", async () => {
if ((await require_workspace.ensureFreshDeps(session.root, localDirs)).rebuilt.some((member) => member.name === "everything-dev")) {
if (require("url").pathToFileURL(__filename).href.includes("/dist/")) console.log("[dev] everything-dev was rebuilt — this session still runs the previous orchestrator build. Restart `bos dev` once to pick it up.");
}
});
let devExtendsChain;
if (configMayHaveChanged || input.remotePlugins !== void 0) yield* step(timings, "resolve config", async () => {
const opened = await require_services_resolution_session.openResolution({
cwd: session.root,
remotePlugins: input.remotePlugins
});
if (opened) {
session = opened;
devExtendsChain = [...opened.chain];
}
});
if (!session.config) return yield* new DevConfigMissing({});
const bosConfig = session.config;
if (proxy && !resolveProxyUrl(bosConfig)) return yield* new DevProxyMissing({});
const developmentRuntime = yield* effect.Effect.tryPromise({
try: async () => {
return session.buildRuntime({
uiSource,
apiSource,
authSource,
hostSource,
env: "development",
plugins: session.runtime?.plugins
});
},
catch: (cause) => new DevStepError({
phase: "build runtime config",
cause
})
});
for (const message of session.warnings) yield* effect.Effect.logWarning(message);
const plan = yield* require_progress.timedPhase(timings, "ports", require_planner.planInfra({
configDir: session.root,
bosConfig: developmentRuntime,
operatorEnv,
cli: {
port: input.port,
apiPort: input.apiPort,
authPort: input.authPort,
uiPort: input.uiPort,
pluginPortStart: input.pluginPortStart,
ssr,
proxy,
hostSource,
uiSource,
apiSource,
authSource,
interactive: input.interactive
}
}));
yield* effect.Effect.tryPromise({
try: () => require_materializer.materializeViaLayer(session.root, plan.runtimeConfig),
catch: (cause) => new DevStepError({
phase: "materialize infra",
cause
})
});
yield* loadEnvStep(projectEnv, session.root);
yield* projectEnv.sync(session.root, plan.envGenerated, shell).pipe(effect.Effect.catchTag("EnvSyncError", (error) => effect.Effect.logWarning(`[env] failed to refresh .env from resolved ports: ${String(error.cause)}`)));
const needsEphemeralSecret = !("BETTER_AUTH_SECRET" in operatorEnv) || operatorEnv.BETTER_AUTH_SECRET === "";
const mergedEnv = yield* effect.Effect.sync(() => {
if (needsEphemeralSecret) plan.envGenerated.BETTER_AUTH_SECRET = (0, node_crypto.randomBytes)(32).toString("base64url");
return require_orchestrator.mergeGeneratedOverFileEnv(plan.envGenerated, require_process_env.operatorEnvRecord(), shell);
});
if (needsEphemeralSecret) yield* effect.Effect.logInfo("[env] BETTER_AUTH_SECRET generated per-run (dev-only, not persisted — create a .env to keep it stable)");
yield* effect.Effect.sync(() => {
for (const [key, value] of Object.entries(mergedEnv)) if (key === "BASE_URL" || key === "CORS_ORIGIN") require_process_env.setProcessEnv(key, value);
});
let preflightFailures = yield* require_preflight.preflightLocalInfra(plan.envGenerated, mergedEnv);
if (preflightFailures.length > 0 && require_docker.shouldAutoStartDocker(preflightFailures, {
...require_docker.detectAutoStartContext(session.root),
dockerAvailable: yield* effect.Effect.promise(require_docker.isDockerAvailable)
})) {
const compose = yield* step(timings, "docker compose up", () => require_docker.runDockerComposeUp(session.root));
if (compose.ok) preflightFailures = yield* require_preflight.preflightLocalInfra(plan.envGenerated, mergedEnv);
else preflightFailures = [...preflightFailures, {
secret: "docker-compose",
host: "localhost",
port: 0,
error: `docker compose up -d --wait failed${compose.tail ? `:\n${compose.tail}` : ""}`,
tcpReachable: false
}];
}
if (preflightFailures.length > 0) return yield* new DevPreflightFailed({ messages: preflightFailures.map((f) => f.error) });
const services = require_service_descriptor.buildServiceDescriptorMapFromPlan(plan, {
ssr,
proxy
});
const packages = [...plan.serviceDescriptors.keys()];
if (require_logs.isDebug(require_process_env.getProcessEnv("DEBUG"))) yield* effect.Effect.logError(`[DEBUG dev] services keys: ${packages.join(", ")}`);
if (services.get("api")?.proxy) {
const proxyUrl = resolveProxyUrl(bosConfig);
if (proxyUrl) plan.orchestrator.env.API_PROXY = proxyUrl;
}
yield* step(timings, "generate artifacts", () => require_artifacts.generateCodeArtifacts(session.root, bosConfig, {
env: "development",
extendsChain: devExtendsChain,
runtimeConfig: plan.runtimeConfig
}));
return {
session: {
orchestrator: plan.orchestrator,
services,
runtimeConfig: plan.runtimeConfig,
envGenerated: plan.envGenerated,
shellEnv: shell
},
description: require_service_descriptor.buildDescription(services) || plan.description,
processes: packages
};
});
//#endregion
exports.DevConfigMissing = DevConfigMissing;
exports.DevPreflightFailed = DevPreflightFailed;
exports.DevProxyMissing = DevProxyMissing;
exports.DevStepError = DevStepError;
exports.devBootstrap = devBootstrap;
exports.loadEnvStep = loadEnvStep;
exports.resolveProxyUrl = resolveProxyUrl;
//# sourceMappingURL=boot.cjs.map