everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
1,033 lines (1,005 loc) • 38.1 kB
JavaScript
import { fetchBosConfigFromFastKv } from "../fastkv.mjs";
import { resolveExtendsRef } from "../merge.mjs";
import { loadManifestNormalizationSpec, normalizePackageManifestsInTree } from "../internal/manifest-normalizer.mjs";
import { saveBosConfig } from "../utils/save-config.mjs";
import { writeSnapshot } from "./snapshot.mjs";
import { createRequire } from "node:module";
import { createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { dirname, join, relative, resolve } from "node:path";
import { createHash } from "node:crypto";
import { tmpdir } from "node:os";
import { pipeline } from "node:stream/promises";
import { execa } from "execa";
import { glob } from "glob";
//#region src/cli/init.ts
const require = createRequire(import.meta.url);
const INIT_ROOT_PATTERNS = [
"bos.config.json",
"package.json",
".env.example",
".gitignore",
"biome.json",
"bunfig.toml",
"Dockerfile",
"railway.json",
"railway.toml",
"AGENTS.md",
".changeset/config.json",
".changeset/README.md",
"README.md",
"CONTRIBUTING.md",
".github/templates/**"
];
const OVERRIDE_WORKSPACE_MAP = {
ui: ["ui"],
api: ["api"],
host: ["host"],
plugins: []
};
function getExtendsRef(config) {
if (typeof config.extends === "string") return config.extends;
if (config.extends && typeof config.extends === "object") return resolveExtendsRef(config.extends, "production");
}
function parseBosRef(ref) {
const match = ref.match(/^bos:\/\/([^/]+)\/(.+)$/);
if (!match?.[1] || !match[2]) return null;
return {
account: match[1],
gateway: match[2]
};
}
function readWorkspaceCatalog(sourceDir) {
const pkgPath = join(sourceDir, "package.json");
if (!existsSync(pkgPath)) return {};
return { ...readJsonFile(pkgPath).workspaces?.catalog ?? {} };
}
async function resolveCatalogChainSource(opts) {
const catalogs = [];
const cleanups = [];
const extendsChain = [];
const visited = /* @__PURE__ */ new Set();
let repository;
let currentRef = `bos://${opts.extendsAccount}/${opts.extendsGateway}`;
let sourceDir = opts.sourceDir ? resolve(opts.sourceDir) : void 0;
let configPath = sourceDir ? join(sourceDir, "bos.config.json") : void 0;
try {
while (true) {
if (visited.has(currentRef)) throw new Error(`Circular extends detected while resolving catalog source: ${currentRef}`);
visited.add(currentRef);
extendsChain.push(currentRef);
let config;
let currentSourceDir = sourceDir;
let cleanup = async () => {};
if (configPath) {
config = readJsonFile(configPath);
currentSourceDir = dirname(configPath);
} else {
const parsed = parseBosRef(currentRef);
if (!parsed) break;
const sourceResult = await resolveSourceDir({
extendsAccount: parsed.account,
extendsGateway: parsed.gateway
});
config = sourceResult.parentConfig;
currentSourceDir = sourceResult.sourceDir || void 0;
cleanup = sourceResult.cleanup;
}
cleanups.push(cleanup);
catalogs.push(currentSourceDir ? readWorkspaceCatalog(currentSourceDir) : {});
if (typeof config.repository === "string") repository = config.repository;
const nextExtendsRef = getExtendsRef(config);
if (!nextExtendsRef) break;
if (nextExtendsRef.startsWith("bos://")) {
currentRef = nextExtendsRef;
sourceDir = void 0;
configPath = void 0;
continue;
}
if (!currentSourceDir) break;
const nextConfigPath = resolve(currentSourceDir, nextExtendsRef);
if (!existsSync(nextConfigPath)) break;
currentRef = nextConfigPath;
sourceDir = dirname(nextConfigPath);
configPath = nextConfigPath;
}
} finally {
for (const cleanup of cleanups.reverse()) await cleanup();
}
return {
catalog: Object.assign({}, ...catalogs.reverse()),
repository,
extendsChain
};
}
async function resolveSourceDir(opts) {
if (opts.source) {
const sourceDir = resolve(opts.source);
if (!existsSync(join(sourceDir, "bos.config.json"))) throw new Error(`No bos.config.json found in source directory: ${sourceDir}`);
return {
sourceDir,
parentConfig: JSON.parse(readFileSync(join(sourceDir, "bos.config.json"), "utf-8")),
cleanup: async () => {}
};
}
const parentConfig = await fetchParentConfig(opts.extendsAccount, opts.extendsGateway);
if (parentConfig.repository) {
const { dir: sourceDir, cleanup } = await downloadTarball(parentConfig.repository);
return {
sourceDir,
parentConfig,
cleanup
};
}
const chainResult = await resolveRepositoryViaExtendsChain(opts.extendsAccount, opts.extendsGateway);
if (chainResult?.repository) {
const { dir: sourceDir, cleanup } = await downloadTarball(chainResult.repository);
return {
sourceDir,
parentConfig: chainResult.config,
cleanup
};
}
return {
sourceDir: "",
parentConfig,
cleanup: async () => {}
};
}
function buildInitPatterns(overrides, plugins) {
const has = (section) => overrides.includes(section);
const patterns = [...INIT_ROOT_PATTERNS];
if (has("ui")) patterns.push("ui/**");
if (has("api")) patterns.push("api/**");
if (has("host")) patterns.push("host/**");
if (has("plugins")) for (const plugin of plugins ?? []) patterns.push(`plugins/${plugin}/**`);
return patterns;
}
function sourcePathToDestinationPath(filePath) {
return filePath.startsWith(".github/templates/") ? filePath.replace(/^\.github\/templates\//, ".github/") : filePath;
}
async function fetchParentConfig(extendsAccount, extendsGateway) {
return fetchBosConfigFromFastKv(`bos://${extendsAccount}/${extendsGateway}`);
}
async function resolveRepositoryViaExtendsChain(extendsAccount, extendsGateway, visited = /* @__PURE__ */ new Set()) {
const key = `bos://${extendsAccount}/${extendsGateway}`;
if (visited.has(key)) return null;
visited.add(key);
try {
const config = await fetchParentConfig(extendsAccount, extendsGateway);
if (config.repository) return {
repository: config.repository,
config
};
const extendsRef = getExtendsRef(config);
if (extendsRef) {
const parsed = parseBosRef(extendsRef.startsWith("bos://") ? extendsRef : `bos://${extendsRef}`);
if (parsed) {
const result = await resolveRepositoryViaExtendsChain(parsed.account, parsed.gateway, visited);
if (result) return result;
}
}
return null;
} catch {
return null;
}
}
async function detectGitRemoteUrl(directory) {
try {
const { stdout } = await execa("git", [
"remote",
"get-url",
"origin"
], {
cwd: directory,
stdio: "pipe"
});
const url = stdout.trim();
if (!url) return void 0;
return normalizeGitUrl(url);
} catch {
return;
}
}
function normalizeGitUrl(url) {
const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
if (sshMatch) return `https://github.com/${sshMatch[1]}/${sshMatch[2]}`;
const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
if (httpsMatch) return `https://github.com/${httpsMatch[1]}/${httpsMatch[2]}`;
return url.endsWith(".git") ? url.slice(0, -4) : url;
}
async function downloadTarball(repoUrl) {
const parsed = parseGitHubUrl(repoUrl);
if (!parsed) throw new Error(`Cannot parse repository URL: ${repoUrl}`);
const { owner, repo } = parsed;
let response = null;
for (const branch of ["main", "master"]) {
const candidate = await fetch(`https://api.github.com/repos/${owner}/${repo}/tarball/${branch}`, {
headers: { "User-Agent": "everything-dev" },
redirect: "follow"
});
if (candidate.ok) {
response = candidate;
break;
}
if (candidate.status !== 404) throw new Error(`GitHub tarball download failed: ${candidate.status} ${candidate.statusText}`);
}
if (!response) throw new Error(`GitHub tarball download failed for ${repoUrl}: tried main and master`);
if (!response.body) throw new Error("GitHub tarball download returned empty body");
const tmpDir = mkTmpDir("bos-init-tarball-");
const tarballPath = join(tmpDir, "source.tar.gz");
const fileStream = createWriteStream(tarballPath);
const reader = response.body;
await pipeline(reader, fileStream);
const extractDir = mkTmpDir("bos-init-extract-");
try {
await require("tar").extract({
cwd: extractDir,
file: tarballPath,
strip: 1
});
} catch {
await execCommand("tar", [
"-xzf",
tarballPath,
"--strip-components=1",
"-C",
extractDir
]);
}
rmSync(tmpDir, {
recursive: true,
force: true
});
return {
dir: extractDir,
cleanup: async () => {
rmSync(extractDir, {
recursive: true,
force: true
});
}
};
}
function parseGitHubUrl(url) {
const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/.*)?$/);
if (httpsMatch) return {
owner: httpsMatch[1],
repo: httpsMatch[2]
};
const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/);
if (sshMatch) return {
owner: sshMatch[1],
repo: sshMatch[2]
};
return null;
}
async function copyFilteredFiles(sourceDir, destination, patterns, _options) {
if (patterns.length === 0) return 0;
const allFiles = /* @__PURE__ */ new Set();
for (const pattern of patterns) {
const matches = await glob(pattern, {
cwd: sourceDir,
nodir: true,
dot: true,
absolute: false,
ignore: [
"**/node_modules/**",
"**/.git/**",
"**/dist/**",
"**/.bos/**"
]
});
for (const match of matches) allFiles.add(match);
}
mkdirSync(destination, { recursive: true });
let count = 0;
for (const filePath of allFiles) {
const src = join(sourceDir, filePath);
if (!lstatSync(src).isFile()) continue;
const dest = join(destination, sourcePathToDestinationPath(filePath));
mkdirSync(dirname(dest), { recursive: true });
writeFileSync(dest, readFileSync(src));
count++;
}
return count;
}
function stripProductionFields(entry) {
delete entry.production;
delete entry.integrity;
delete entry.ssr;
delete entry.ssrIntegrity;
}
function buildRootTypecheckScript(sections) {
const commands = ["bun run types:gen"];
if (sections.ui) commands.push("if [ -d ui ]; then bun run --cwd ui typecheck; fi");
if (sections.api) commands.push("if [ -d api ]; then bun run --cwd api typecheck; fi");
if (sections.host) commands.push("if [ -d host ]; then bun run --cwd host typecheck; fi");
if (sections.plugins) commands.push("if [ -d plugins ]; then for dir in plugins/*; do if [ -f \"$dir/package.json\" ]; then bun run --cwd \"$dir\" typecheck; fi; done; fi");
return commands.join(" && ");
}
function buildChildRootScripts(sections) {
const scripts = {
dev: "node_modules/.bin/bos dev",
"dev:proxy": "node_modules/.bin/bos dev --proxy",
build: "node_modules/.bin/bos build",
deploy: "node_modules/.bin/bos build --deploy",
publish: "node_modules/.bin/bos publish",
start: "node_modules/.bin/bos start",
typecheck: buildRootTypecheckScript(sections),
lint: "biome check .",
"lint:fix": "biome check --write .",
format: "biome format --write .",
"format:check": "biome format .",
changeset: "changeset",
version: "changeset version",
release: "echo 'Packages versioned - app release handled by workflow'",
postinstall: "node_modules/.bin/bos types gen || true",
"types:gen": "node_modules/.bin/bos types gen",
bos: "node_modules/.bin/bos"
};
if (sections.api) {
scripts["db:push"] = "bun run --cwd api drizzle-kit push";
scripts["db:studio"] = "bun run --cwd api drizzle-kit studio";
scripts["db:generate"] = "bun run --cwd api drizzle-kit generate";
scripts["db:migrate"] = "bun run --cwd api drizzle-kit migrate";
scripts["test:api"] = "cd api && bun run test tests/integration/ tests/unit/";
scripts["test:integration"] = "cd api && bun run test tests/integration/";
}
if (sections.host) scripts["test:e2e"] = "bun run --cwd host test:e2e";
if (sections.api && sections.host) scripts.test = "bun run test:api && bun run test:e2e";
else if (sections.api) scripts.test = "bun run test:api";
else if (sections.host) scripts.test = "bun run test:e2e";
if (sections.api || sections.host) {
scripts["dev:postgres"] = "docker compose up -d --wait && bun run dev";
scripts["dev:postgres:down"] = "docker compose down";
scripts["dev:postgres:reset"] = "docker compose down -v && docker compose up -d --wait";
}
if (sections.ui) scripts["dev:ui"] = "node_modules/.bin/bos dev --ui local --api remote";
if (sections.api) scripts["dev:api"] = "node_modules/.bin/bos dev --ui remote --api local";
return scripts;
}
async function personalizeConfig(destination, opts) {
const has = (section) => opts.overrides.includes(section);
const preservedAuth = (opts.mode === "sync" && opts.existingConfig?.app && typeof opts.existingConfig.app === "object" ? opts.existingConfig.app : void 0)?.auth;
const explicitRootKeys = new Set(Object.entries(opts).filter(([key, value]) => value !== void 0 && ![
"extendsAccount",
"extendsGateway",
"plugins",
"overrides",
"pluginRoutes",
"workspaceOpts",
"mode",
"existingConfig"
].includes(key)).map(([key]) => key));
const configPath = join(destination, "bos.config.json");
if (existsSync(configPath)) {
const config = JSON.parse(readFileSync(configPath, "utf-8"));
config.extends = `bos://${opts.extendsAccount}/${opts.extendsGateway}`;
if (opts.account) config.account = opts.account;
if (opts.domain) config.domain = opts.domain;
if (opts.repository) config.repository = opts.repository;
else delete config.repository;
for (const field of [
"title",
"description",
"testnet",
"staging"
]) if (!(field in opts)) delete config[field];
if (config.app && typeof config.app === "object") {
const app = config.app;
for (const entryKey of Object.keys(app)) {
if (!has(entryKey) && (entryKey === "host" || entryKey === "ui" || entryKey === "api")) {
delete app[entryKey];
continue;
}
if (entryKey === "auth") {
delete app[entryKey];
continue;
}
const entry = app[entryKey];
if (entry && typeof entry === "object") stripProductionFields(entry);
}
if (preservedAuth !== void 0) app.auth = preservedAuth;
if (Object.keys(app).length === 0) delete config.app;
}
if (has("plugins")) {
if (config.plugins && typeof config.plugins === "object") {
const plugins = config.plugins;
if (opts.plugins !== void 0) {
for (const pluginKey of Object.keys(plugins)) if (!opts.plugins.includes(pluginKey)) delete plugins[pluginKey];
}
for (const pluginKey of Object.keys(plugins)) {
const plugin = plugins[pluginKey];
let pluginObj;
if (typeof plugin === "string") {
pluginObj = { extends: plugin };
plugins[pluginKey] = pluginObj;
} else if (plugin && typeof plugin === "object") {
pluginObj = { ...plugin };
plugins[pluginKey] = pluginObj;
} else continue;
stripProductionFields(pluginObj);
}
if (Object.keys(plugins).length === 0) config.plugins = {};
}
} else config.plugins = {};
if (opts.mode === "sync" && opts.existingConfig) {
const managedRootKeys = new Set([
"extends",
"account",
"domain",
"app",
"plugins"
]);
const preservedRootKeys = new Set([
...managedRootKeys,
...Object.keys(opts.existingConfig),
...explicitRootKeys
]);
for (const key of Object.keys(config)) if (!preservedRootKeys.has(key)) delete config[key];
for (const [key, value] of Object.entries(opts.existingConfig)) if (!(key in config) && !managedRootKeys.has(key) && !explicitRootKeys.has(key)) config[key] = value;
}
await saveBosConfig(destination, config);
}
const pkgPath = join(destination, "package.json");
if (existsSync(pkgPath)) {
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
const childScripts = buildChildRootScripts({
ui: has("ui"),
api: has("api"),
host: has("host"),
plugins: has("plugins")
});
if (typeof pkg.name !== "string" || pkg.name.length === 0) pkg.name = "monorepo";
pkg.private = true;
pkg.type = "module";
delete pkg.module;
delete pkg.peerDependencies;
if (pkg.workspaces && typeof pkg.workspaces === "object") {
const ws = pkg.workspaces;
if (Array.isArray(ws.packages)) {
ws.packages = ws.packages.filter((p) => {
if (p.startsWith("packages/")) return false;
if (p === "ui") return has("ui");
if (p === "api") return has("api");
if (p === "host") return has("host");
if (p.startsWith("plugins/")) return false;
return true;
});
if (has("plugins")) {
if (!ws.packages.includes("plugins/*")) ws.packages.push("plugins/*");
}
}
}
if (!pkg.scripts || typeof pkg.scripts !== "object") pkg.scripts = {};
const scripts = pkg.scripts;
for (const [key, value] of Object.entries(childScripts)) scripts[key] = value;
for (const obsoleteScript of [
"init",
"sync-catalog",
"db:push",
"db:studio",
"db:generate",
"db:migrate",
"test",
"test:api",
"test:integration",
"test:e2e",
"dev:postgres",
"dev:postgres:down",
"dev:postgres:reset",
"dev:ui",
"dev:api"
]) if (!(obsoleteScript in childScripts)) delete scripts[obsoleteScript];
if (pkg.devDependencies && typeof pkg.devDependencies === "object") {
const deps = pkg.devDependencies;
delete deps["every-plugin"];
delete deps["everything-dev"];
}
if (!pkg.workspaces || typeof pkg.workspaces !== "object") pkg.workspaces = {
packages: [],
catalog: {}
};
const workspaces = pkg.workspaces;
if (!workspaces.catalog || typeof workspaces.catalog !== "object") workspaces.catalog = {};
if (!pkg.dependencies) pkg.dependencies = {};
const deps = pkg.dependencies;
const spec = opts.workspaceOpts?.sourceDir ? loadManifestNormalizationSpec(opts.workspaceOpts.sourceDir) : null;
if (spec) {
workspaces.catalog["everything-dev"] = spec.rootCatalog["everything-dev"];
workspaces.catalog["every-plugin"] = spec.rootCatalog["every-plugin"];
}
const frameworkCatalog = (await resolveCatalogChainSource({
extendsAccount: opts.extendsAccount,
extendsGateway: opts.extendsGateway,
sourceDir: opts.workspaceOpts?.sourceDir
})).catalog;
for (const [name, version] of Object.entries(frameworkCatalog)) workspaces.catalog[name] = version;
if (!deps["everything-dev"]) deps["everything-dev"] = "catalog:";
if (!deps["every-plugin"]) deps["every-plugin"] = "catalog:";
writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
}
const apiTsConfigPath = join(destination, "api", "tsconfig.json");
if (existsSync(apiTsConfigPath)) {
const apiTsConfig = JSON.parse(readFileSync(apiTsConfigPath, "utf-8"));
if (apiTsConfig.files) {
const validFiles = apiTsConfig.files.filter((f) => existsSync(join(destination, "api", f)));
if (validFiles.length !== apiTsConfig.files.length) {
if (validFiles.length === 0) delete apiTsConfig.files;
else apiTsConfig.files = validFiles;
writeFileSync(apiTsConfigPath, `${JSON.stringify(apiTsConfig, null, 2)}\n`);
}
}
}
await resolveWorkspaceRefs(destination, opts.workspaceOpts);
if (has("ui")) {
const genContractPath = join(destination, "ui", "src", "lib", "api-types.gen.ts");
if (!existsSync(genContractPath)) {
mkdirSync(dirname(genContractPath), { recursive: true });
writeFileSync(genContractPath, `export type ApiContract = Record<string, never>;\n`);
}
}
if (has("api")) {
const pluginsClientGenPath = join(destination, "api", "src", "lib", "plugins-types.gen.ts");
if (!existsSync(pluginsClientGenPath)) {
mkdirSync(dirname(pluginsClientGenPath), { recursive: true });
writeFileSync(pluginsClientGenPath, `import type { ContractRouterClient, AnyContractRouter } from "@orpc/contract";\ntype ClientFactory<C extends AnyContractRouter> = (context?: Record<string, unknown>) => ContractRouterClient<C>;\nexport type PluginsClient = Record<string, never>;\n`);
}
}
const authTypesPaths = [];
if (has("ui")) authTypesPaths.push(join(destination, "ui", "src", "lib", "auth-types.gen.ts"));
if (has("api")) authTypesPaths.push(join(destination, "api", "src", "lib", "auth-types.gen.ts"));
if (has("host") && existsSync(join(destination, "host", "src"))) authTypesPaths.push(join(destination, "host", "src", "lib", "auth-types.gen.ts"));
for (const authTypesGenPath of authTypesPaths) if (!existsSync(authTypesGenPath)) {
mkdirSync(dirname(authTypesGenPath), { recursive: true });
writeFileSync(authTypesGenPath, generateAuthTypesContent(authTypesGenPath, destination));
}
if (authTypesPaths.length > 0) {
const authDir = join(destination, ".bos", "generated", "auth");
if (!existsSync(authDir)) mkdirSync(authDir, { recursive: true });
const authExportStubPath = join(authDir, "auth-export.d.ts");
if (!existsSync(authExportStubPath)) writeFileSync(authExportStubPath, `export type Auth = any;
export type AuthOrganizationContext = any;
export type AuthOrganization = any;
export type AuthOrganizationSummary = any;
export type AuthOrganizationMember = any;
export type AuthApiKey = any;
export type AuthInvitation = any;
export type GetActiveMemberInput = any;
export type GetOrganizationInput = any;
export type ListMembersInput = any;
export type ListInvitationsInput = any;
export type ListApiKeysInput = any;
export type AuthServices = any;
export type createAuthInstance = any;
`);
const contractStubPath = join(authDir, "contract.d.ts");
if (!existsSync(contractStubPath)) writeFileSync(contractStubPath, `export type ContractType = any;
export type InferOutput<_TRoute extends string> = any;
`);
}
if (has("plugins")) for (const plugin of opts.plugins ?? []) {
const pluginSrcDir = join(destination, "plugins", plugin, "src");
const pluginIndexPath = join(pluginSrcDir, "index.ts");
const pluginClientGenPath = join(pluginSrcDir, "plugins-client.gen.ts");
if (!existsSync(pluginIndexPath) || existsSync(pluginClientGenPath)) continue;
if (!readFileSync(pluginIndexPath, "utf-8").includes("./plugins-client.gen")) continue;
writeFileSync(pluginClientGenPath, "export type PluginsClient = Record<string, never>;\n");
}
}
function generateAuthTypesContent(targetPath, configDir) {
const authExportRel = toRelativeImportPath(join(configDir, ".bos", "generated", "auth", "auth-export.d.ts"), targetPath);
return `export type {
Auth,
AuthOrganizationContext,
AuthOrganization,
AuthOrganizationSummary,
AuthOrganizationMember,
AuthApiKey,
AuthInvitation,
GetActiveMemberInput,
GetOrganizationInput,
ListMembersInput,
ListInvitationsInput,
ListApiKeysInput,
AuthServices,
createAuthInstance,
} from "${authExportRel}";
import type { InferOutput, ContractType as AuthContract } from "${toRelativeImportPath(join(configDir, ".bos", "generated", "auth", "contract.d.ts"), targetPath)}";
import type { Auth as BaseAuth } from "${authExportRel}";
type RawAuthSession = InferOutput<"getSession">;
type RawAuthRequestContext = InferOutput<"getContext">;
type RawAuthActiveMember = InferOutput<"getActiveMember">;
export type AuthSessionUser = NonNullable<RawAuthSession["user"]> & {
role?: string | null;
isAnonymous?: boolean | null;
walletAddress?: string | null;
banned?: boolean | null;
};
export type AuthSessionData = NonNullable<RawAuthSession["session"]> & {
activeOrganizationId?: string | null;
};
export type AuthSession = {
user: AuthSessionUser | null;
session: AuthSessionData | null;
};
export type AuthRequestContext = RawAuthRequestContext & {
organization?: { activeOrganizationId?: string | null } | null;
apiKey?: { id: string; permissions?: Record<string, string[]> | null } | null;
};
export type AuthActiveMember = RawAuthActiveMember;
export type AuthBaseSession = BaseAuth["$Infer"]["Session"];
export type AuthContractType = AuthContract;
`;
}
function toRelativeImportPath(fromPath, toPath) {
const rel = relative(dirname(toPath), fromPath);
return rel.startsWith(".") ? rel : `./${rel}`;
}
async function runBunInstall(destination, spinner) {
await runWithProgress("bun", ["install", "--ignore-scripts"], destination, spinner, "Installing dependencies");
}
async function runBunInstallForUpgrade(destination, spinner) {
await runWithProgress("bun", ["install", "--force"], destination, spinner, "Installing dependencies");
}
async function runTypesGen(destination, spinner) {
if (existsSync(join(destination, "node_modules", ".bin", "bos"))) {
await runWithProgress("node_modules/.bin/bos", ["types", "gen"], destination, spinner, "Generating types");
return;
}
throw new Error("Unable to locate bos CLI for types generation");
}
async function runDockerComposeUp(destination) {
await execCommand("docker", [
"compose",
"up",
"-d",
"--wait"
], destination, { stdio: "inherit" });
}
async function runWithProgress(command, args, cwd, spinner, label) {
const child = execa(command, args, {
cwd,
stdio: "inherit",
timeout: COMMAND_TIMEOUTS[command] ?? 2 * 6e4
});
if (spinner) {
const start = Date.now();
const interval = setInterval(() => {
const elapsed = Math.round((Date.now() - start) / 1e3);
spinner.message(`${label}... (${elapsed}s)`);
}, 2e3);
try {
await child;
} finally {
clearInterval(interval);
}
} else await child;
}
function stripOrphanedWorkspacesFromLockfile(lockfilePath, allowedWorkspaces) {
if (!existsSync(lockfilePath)) return;
const content = readFileSync(lockfilePath, "utf-8");
let lockfile;
try {
lockfile = JSON.parse(content);
} catch {
return;
}
const workspaces = lockfile.workspaces;
if (!workspaces || typeof workspaces !== "object") return;
const workspaceMap = workspaces;
const allowed = new Set(["", ...allowedWorkspaces]);
const keys = Object.keys(workspaceMap);
let changed = false;
for (const key of keys) {
if (allowed.has(key)) continue;
if (allowedWorkspaces.some((pattern) => pattern.endsWith("/*") && key.startsWith(pattern.slice(0, -1)))) continue;
delete workspaceMap[key];
changed = true;
}
if (changed) writeFileSync(lockfilePath, `${JSON.stringify(lockfile, null, 2)}\n`);
}
function removeInitLockfile(lockfilePath) {
if (!existsSync(lockfilePath)) return;
rmSync(lockfilePath, { force: true });
}
function readJsonFile(filePath) {
return JSON.parse(readFileSync(filePath, "utf-8"));
}
async function scaffoldMinimalProject(destination, parentConfig, opts) {
mkdirSync(destination, { recursive: true });
const has = (section) => opts.overrides.includes(section);
const config = {
extends: `bos://${opts.extendsAccount}/${opts.extendsGateway}`,
account: opts.account || opts.extendsAccount,
...opts.domain ? { domain: opts.domain } : {},
...opts.repository ? { repository: opts.repository } : {},
...opts.title ? { title: opts.title } : {},
...opts.description ? { description: opts.description } : {}
};
if (parentConfig.app && typeof parentConfig.app === "object") {
const app = {};
const parentApp = parentConfig.app;
if (has("host") && parentApp.host) {
app.host = { ...parentApp.host };
stripProductionFields(app.host);
}
if (has("ui") && parentApp.ui) {
app.ui = { ...parentApp.ui };
stripProductionFields(app.ui);
}
if (has("api") && parentApp.api) {
app.api = { ...parentApp.api };
stripProductionFields(app.api);
}
if (Object.keys(app).length > 0) config.app = app;
}
if (has("plugins") && opts.plugins && opts.plugins.length > 0 && parentConfig.plugins) {
const plugins = {};
for (const key of opts.plugins) {
const parentPlugin = parentConfig.plugins?.[key];
if (parentPlugin) if (typeof parentPlugin === "string") plugins[key] = { extends: parentPlugin };
else {
const pluginCopy = { ...parentPlugin };
stripProductionFields(pluginCopy);
plugins[key] = pluginCopy;
}
}
config.plugins = plugins;
} else if (has("plugins")) config.plugins = {};
await saveBosConfig(destination, config);
const workspacePackages = [];
for (const section of opts.overrides) workspacePackages.push(...OVERRIDE_WORKSPACE_MAP[section]);
if (has("plugins")) workspacePackages.push("plugins/*");
const catalog = (await resolveCatalogChainSource({
extendsAccount: opts.extendsAccount,
extendsGateway: opts.extendsGateway
})).catalog;
const pkg = {
name: "monorepo",
private: true,
type: "module",
scripts: buildChildRootScripts({
ui: has("ui"),
api: has("api"),
host: has("host"),
plugins: has("plugins")
}),
dependencies: {
"everything-dev": "catalog:",
"every-plugin": "catalog:"
},
devDependencies: {},
workspaces: {
packages: workspacePackages,
catalog
}
};
writeFileSync(join(destination, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`);
writeFileSync(join(destination, ".gitignore"), generateGitignore());
return 4;
}
async function resolveWorkspaceRefs(destination, options) {
await normalizePackageManifestsInTree({
sourceRootDir: options?.sourceDir ?? destination,
targetDir: destination,
resolveCatalogRefs: false,
preserveCatalogRefs: true,
removeWorkspaceDeps: ["host"]
});
}
async function writeInitSnapshot(destination, extendsAccount, extendsGateway, sourceDir, patterns, _options) {
const allFiles = /* @__PURE__ */ new Set();
for (const pattern of patterns) {
const matches = await glob(pattern, {
cwd: sourceDir,
nodir: true,
dot: true,
absolute: false,
ignore: [
"**/node_modules/**",
"**/.git/**",
"**/dist/**",
"**/.bos/**"
]
});
for (const match of matches) allFiles.add(match);
}
const fileHashes = {};
for (const filePath of allFiles) {
const src = join(sourceDir, filePath);
if (!lstatSync(src).isFile()) continue;
const content = readFileSync(src);
const destPath = sourcePathToDestinationPath(filePath);
fileHashes[destPath] = computeHash(content);
}
await writeSnapshot(destination, {
parentRef: `bos://${extendsAccount}/${extendsGateway}`,
files: fileHashes
});
}
function computeHash(data) {
return createHash("sha256").update(data).digest("hex").substring(0, 16);
}
function mkTmpDir(prefix) {
return mkdtempSync(join(tmpdir(), `${prefix}-`));
}
async function generateDatabaseMigrations(destination) {
const drizzleConfigs = await glob("**/drizzle.config.ts", {
cwd: destination,
nodir: true,
dot: false,
absolute: false,
ignore: ["**/node_modules/**"]
});
for (const configPath of drizzleConfigs) {
const workspaceDir = dirname(configPath);
const pkgPath = join(destination, workspaceDir, "package.json");
if (!existsSync(pkgPath)) continue;
if (!JSON.parse(readFileSync(pkgPath, "utf-8")).scripts?.["db:generate"]) continue;
await execCommand("bun", ["run", "db:generate"], join(destination, workspaceDir));
}
}
const COMMAND_TIMEOUTS = {
bun: 5 * 6e4,
docker: 5 * 6e4,
node_modules: 2 * 6e4,
tar: 6e4
};
async function execCommand(command, args, cwd, options) {
const timeout = COMMAND_TIMEOUTS[command] ?? 2 * 6e4;
await execa(command, args, {
cwd,
stdio: options?.stdio ?? "pipe",
timeout
});
}
function extractSkillsBlock(content) {
const match = content.match(/<!-- intent-skills:start -->[\s\S]*?<!-- intent-skills:end -->/);
return match ? match[0] : "";
}
function buildChildAgentsInstructions(opts) {
const has = (section) => opts.overrides.includes(section);
const parts = [];
parts.push(`# Agent Instructions
This document provides operational guidance for AI agents working in this everything.dev project.
## Quick Reference
**Start Development:**
\`\`\`bash
cp .env.example .env # First time only
bun install
bun run dev
\`\`\`
**Check Status:**
\`\`\`bash
bos ps # List running processes
bos status # Project health check
bos info # Show configuration
\`\`\``);
const archLines = ["This is an everything.dev child project. Depending on your overrides, it may include:"];
if (has("ui")) archLines.push("- **UI** — React 19 + TanStack Router frontend, loaded via Module Federation");
if (has("api")) archLines.push("- **API** — Hono.js + oRPC backend with Effect services");
if (has("host")) archLines.push("- **Host** — Server-side runtime with Module Federation orchestration");
if (has("plugins")) archLines.push("- **Plugins** — Self-contained business logic modules with oRPC contracts");
archLines.push("", "The parent runtime provides the shared framework; your project provides custom overrides.");
parts.push(`## Architecture\n\n${archLines.join("\n")}`);
parts.push(`## Development Workflow
### Starting Development
1. \`cp .env.example .env\` (first time)
2. \`bun install\`
3. \`bun run dev\``);
parts.push(`### Debugging Issues
**API not responding:**
- Check \`bos ps\` to see if the API process is running
- Check \`.bos/logs/api.log\` for errors
**UI not loading:**
- Verify the dev server is running: \`bos ps\`
- Check browser console for Module Federation errors
- Clear browser cache and retry
**Type errors:**
- Run \`bun run typecheck\``);
const changeLines = ["### Making Changes"];
if (has("ui")) changeLines.push("- **UI Changes**: Edit `ui/src/` files → hot reload automatically");
if (has("api")) changeLines.push("- **API Changes**: Edit `api/src/` files → hot reload automatically");
if (has("host")) changeLines.push("- **Host Changes**: Edit `host/src/` when changing runtime resolution, auth wiring, SSR, proxying, or plugin mounting");
changeLines.push("- **New Components**: Create in `ui/src/components/ui/`, export from `ui/src/components/index.ts`");
changeLines.push("- **New Routes**: Create file in `ui/src/routes/`, TanStack Router auto-generates tree");
parts.push(`## Code Changes\n\n${changeLines.join("\n")}`);
parts.push(`### Style Requirements
- Use semantic Tailwind classes: \`bg-background\`, \`text-foreground\`, \`text-muted-foreground\`
- No hardcoded colors like \`bg-blue-600\`
- No code comments in implementation
- Component file naming: lowercase kebab-case (\`data-table.tsx\`, \`user-profile.tsx\`)
- Follow existing patterns in neighboring files`);
if (has("api")) parts.push(`### Adding API Endpoints
1. Define in \`api/src/contract.ts\` — the oRPC route definitions and Zod schemas
2. Implement in \`api/src/index.ts\` — the \`createRouter\` function
3. Use in UI via \`apiClient\` from \`useApiClient()\` in \`@/app\``);
if (has("plugins")) parts.push(`### Plugin Architecture
Business logic is organized into independent plugins loaded via Module Federation:
- Each plugin has its own \`contract.ts\` — oRPC route definitions and Zod schemas
- Each plugin has its own \`index.ts\` — \`createPlugin\` with variables, secrets, context, router
- Each plugin has its own rspack config for independent deployment
The UI accesses plugin routes via namespaced clients: \`apiClient.<plugin>.<method>()\`.
### Plugin Client (pluginsClient)
The API plugin receives typed client factories for all other plugins via \`createPlugin.withPlugins<PluginsClient>()\`, enabling in-process composition without HTTP roundtrips.
### Generated Types
\`api/src/lib/plugins-types.gen.ts\`, \`api/src/lib/auth-types.gen.ts\`, \`ui/src/lib/api-types.gen.ts\`, and \`ui/src/lib/auth-types.gen.ts\` are generated by \`bos types gen\` from \`bos.config.json\`. These files are gitignored and auto-regenerated on \`bun install\`, \`typecheck\`, \`bos dev\`, \`bos build\`, and bos plugin management commands.
If you hand-edit \`bos.config.json\`, run \`bos types gen\` or restart \`bos dev\` to regenerate.`);
const testCommands = [];
if (has("api") || has("host") || has("ui")) {
testCommands.push("bun run test # Run all tests");
testCommands.push("bun typecheck # Type check all packages");
testCommands.push("bun lint # Run linting");
}
if (testCommands.length > 0) parts.push(`## Testing & Quality
**Before committing:**
\`\`\`bash
${testCommands.join("\n")}
\`\`\``);
parts.push(`## Common Patterns
### Authentication Check
Routes requiring auth use \`_authenticated.tsx\` layout:
\`\`\`typescript
export const Route = createFileRoute('/_layout/_authenticated')({
beforeLoad: async ({ location }) => {
const { data: session } = await authClient.getSession();
if (!session?.user) {
throw redirect({ to: '/login', search: { redirect: location.pathname } });
}
},
});
\`\`\``);
if (has("ui")) parts.push(`### API Client Usage
\`\`\`typescript
import { useApiClient } from "@/app";
function MyComponent() {
const apiClient = useApiClient();
const { data } = await apiClient.ping();
}
\`\`\``);
parts.push(`## Troubleshooting
**Process won't start:**
\`\`\`bash
bos kill # Kill all tracked processes
bun install # Ensure dependencies
bun run dev # Restart
\`\`\`
**Module Federation errors:**
- Check \`bos.config.json\` URLs are accessible
- Verify shared dependency versions match in package.json
- Clear browser cache
**Database issues:**
\`\`\`bash
bun run db:push # Push schema changes
bun run db:studio # Open Drizzle Studio
\`\`\`
## Environment
**Required files:**
- \`.env\` — Secrets (see \`.env.example\`)
- \`bos.config.json\` — Runtime configuration (committed)`);
return `${parts.join("\n\n")}\n`;
}
function buildChildAgentsMd(skillsBlock, opts) {
return `${skillsBlock}\n\n${buildChildAgentsInstructions(opts)}`;
}
async function personalizeAgentsMd(destination, opts) {
const agentsMdPath = join(destination, "AGENTS.md");
if (!existsSync(agentsMdPath)) return;
const skillsBlock = extractSkillsBlock(readFileSync(agentsMdPath, "utf-8"));
if (!skillsBlock) return;
writeFileSync(agentsMdPath, buildChildAgentsMd(skillsBlock, opts));
}
function generateGitignore() {
return `node_modules/
dist/
.env
.bos/
docker-compose.yml
*.gen.ts
*.gen.tsx
`;
}
//#endregion
export { INIT_ROOT_PATTERNS, buildChildAgentsMd, buildChildRootScripts, buildInitPatterns, copyFilteredFiles, detectGitRemoteUrl, downloadTarball, execCommand, extractSkillsBlock, fetchParentConfig, generateDatabaseMigrations, personalizeAgentsMd, personalizeConfig, removeInitLockfile, resolveCatalogChainSource, resolveRepositoryViaExtendsChain, resolveSourceDir, runBunInstall, runBunInstallForUpgrade, runDockerComposeUp, runTypesGen, scaffoldMinimalProject, sourcePathToDestinationPath, stripOrphanedWorkspacesFromLockfile, writeInitSnapshot };
//# sourceMappingURL=init.mjs.map