everything-dev 1.51.7 → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/api-contract.cjs +66 -16
  2. package/dist/api-contract.cjs.map +1 -1
  3. package/dist/api-contract.mjs +65 -17
  4. package/dist/api-contract.mjs.map +1 -1
  5. package/dist/app.cjs +1 -1
  6. package/dist/app.cjs.map +1 -1
  7. package/dist/app.mjs +1 -1
  8. package/dist/app.mjs.map +1 -1
  9. package/dist/cli/infra.cjs +0 -67
  10. package/dist/cli/infra.cjs.map +1 -1
  11. package/dist/cli/infra.mjs +1 -65
  12. package/dist/cli/infra.mjs.map +1 -1
  13. package/dist/cli/init.cjs +76 -52
  14. package/dist/cli/init.cjs.map +1 -1
  15. package/dist/cli/init.d.cts +15 -7
  16. package/dist/cli/init.d.cts.map +1 -1
  17. package/dist/cli/init.d.mts +15 -7
  18. package/dist/cli/init.d.mts.map +1 -1
  19. package/dist/cli/init.mjs +70 -47
  20. package/dist/cli/init.mjs.map +1 -1
  21. package/dist/cli/sync.cjs +34 -5
  22. package/dist/cli/sync.cjs.map +1 -1
  23. package/dist/cli/sync.mjs +35 -6
  24. package/dist/cli/sync.mjs.map +1 -1
  25. package/dist/cli/upgrade.cjs +151 -93
  26. package/dist/cli/upgrade.cjs.map +1 -1
  27. package/dist/cli/upgrade.mjs +138 -80
  28. package/dist/cli/upgrade.mjs.map +1 -1
  29. package/dist/cli/utils/helpers.cjs +25 -0
  30. package/dist/cli/utils/helpers.cjs.map +1 -0
  31. package/dist/cli/utils/helpers.mjs +23 -0
  32. package/dist/cli/utils/helpers.mjs.map +1 -0
  33. package/dist/cli.cjs +84 -72
  34. package/dist/cli.cjs.map +1 -1
  35. package/dist/cli.mjs +84 -72
  36. package/dist/cli.mjs.map +1 -1
  37. package/dist/code-artifacts.cjs +1 -1
  38. package/dist/code-artifacts.mjs +1 -1
  39. package/dist/config.cjs +47 -5
  40. package/dist/config.cjs.map +1 -1
  41. package/dist/config.d.cts +1 -1
  42. package/dist/config.d.cts.map +1 -1
  43. package/dist/config.d.mts +1 -1
  44. package/dist/config.d.mts.map +1 -1
  45. package/dist/config.mjs +47 -5
  46. package/dist/config.mjs.map +1 -1
  47. package/dist/contract.cjs +9 -4
  48. package/dist/contract.cjs.map +1 -1
  49. package/dist/contract.d.cts +42 -24
  50. package/dist/contract.d.cts.map +1 -1
  51. package/dist/contract.d.mts +42 -24
  52. package/dist/contract.d.mts.map +1 -1
  53. package/dist/contract.meta.cjs +2 -1
  54. package/dist/contract.meta.cjs.map +1 -1
  55. package/dist/contract.meta.d.cts +3 -0
  56. package/dist/contract.meta.d.mts +3 -0
  57. package/dist/contract.meta.mjs +2 -1
  58. package/dist/contract.meta.mjs.map +1 -1
  59. package/dist/contract.mjs +9 -4
  60. package/dist/contract.mjs.map +1 -1
  61. package/dist/dag.cjs +177 -0
  62. package/dist/dag.cjs.map +1 -0
  63. package/dist/dag.d.cts +24 -0
  64. package/dist/dag.d.cts.map +1 -0
  65. package/dist/dag.d.mts +24 -0
  66. package/dist/dag.d.mts.map +1 -0
  67. package/dist/dag.mjs +168 -0
  68. package/dist/dag.mjs.map +1 -0
  69. package/dist/index.cjs +2 -0
  70. package/dist/index.d.cts +2 -2
  71. package/dist/index.d.mts +2 -2
  72. package/dist/index.mjs +2 -2
  73. package/dist/infra/planner.cjs +1 -1
  74. package/dist/infra/planner.mjs +1 -1
  75. package/dist/merge.cjs +1 -1
  76. package/dist/merge.cjs.map +1 -1
  77. package/dist/merge.mjs +1 -1
  78. package/dist/merge.mjs.map +1 -1
  79. package/dist/near-cli.cjs +1 -1
  80. package/dist/near-cli.mjs +1 -1
  81. package/dist/orchestrator.cjs +1 -1
  82. package/dist/orchestrator.cjs.map +1 -1
  83. package/dist/orchestrator.mjs +1 -1
  84. package/dist/orchestrator.mjs.map +1 -1
  85. package/dist/plugin.cjs +28 -30
  86. package/dist/plugin.cjs.map +1 -1
  87. package/dist/plugin.d.cts +67 -11
  88. package/dist/plugin.d.cts.map +1 -1
  89. package/dist/plugin.d.mts +67 -11
  90. package/dist/plugin.d.mts.map +1 -1
  91. package/dist/plugin.mjs +30 -32
  92. package/dist/plugin.mjs.map +1 -1
  93. package/dist/types.cjs +52 -9
  94. package/dist/types.cjs.map +1 -1
  95. package/dist/types.d.cts +131 -3
  96. package/dist/types.d.cts.map +1 -1
  97. package/dist/types.d.mts +131 -3
  98. package/dist/types.d.mts.map +1 -1
  99. package/dist/types.mjs +51 -10
  100. package/dist/types.mjs.map +1 -1
  101. package/package.json +8 -3
@@ -254,70 +254,6 @@ function renderDockerCompose(databases, redisConfigs, projectName) {
254
254
  }
255
255
  return `${lines.join("\n")}\n`;
256
256
  }
257
- function renderEnvFileFromPlan(env, devHostPort) {
258
- const lines = [
259
- "# Generated from bos dev infra plan",
260
- "# Update values as needed for your local environment",
261
- ""
262
- ];
263
- const sortedKeys = Object.keys(env).sort();
264
- for (const key of sortedKeys) {
265
- let value = env[key];
266
- if (key === "CORS_ORIGIN" && devHostPort && !value) value = `http://localhost:${devHostPort}`;
267
- lines.push(`${key}=${value}`);
268
- }
269
- lines.push("");
270
- return `${lines.join("\n")}\n`;
271
- }
272
- function renderDockerComposeFromPlan(databases, redis, projectName) {
273
- const lines = [`name: ${projectName}`, ""];
274
- if (databases.length > 0) lines.push("x-pg-common: &pg-common", " image: postgres:17-alpine", " environment: &pg-env", ` POSTGRES_USER: ${POSTGRES_USER}`, ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`, " healthcheck:", " test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]", " interval: 3s", " timeout: 3s", " retries: 5", "");
275
- if (redis.length > 0) lines.push("x-redis-common: &redis-common", " image: redis:7-alpine", " command: redis-server --appendonly yes", " healthcheck:", " test: [\"CMD\", \"redis-cli\", \"ping\"]", " interval: 3s", " timeout: 3s", " retries: 5", "");
276
- lines.push("services:");
277
- for (const db of databases) {
278
- lines.push(` ${db.serviceName}:`);
279
- lines.push(" <<: *pg-common");
280
- lines.push(` container_name: ${db.containerName}`);
281
- lines.push(" environment:");
282
- lines.push(" <<: *pg-env");
283
- lines.push(` POSTGRES_DB: ${db.databaseName}`);
284
- lines.push(" ports:");
285
- lines.push(` - "${db.port}:5432"`);
286
- lines.push(" volumes:");
287
- lines.push(` - ${db.volumeName}:/var/lib/postgresql/data`);
288
- lines.push("");
289
- }
290
- for (const r of redis) {
291
- lines.push(` ${r.serviceName}:`);
292
- lines.push(" <<: *redis-common");
293
- lines.push(` container_name: ${r.containerName}`);
294
- lines.push(" ports:");
295
- lines.push(` - "${r.port}:6379"`);
296
- lines.push(" volumes:");
297
- lines.push(` - ${r.volumeName}:/data`);
298
- lines.push("");
299
- }
300
- lines.push("volumes:");
301
- for (const db of databases) {
302
- lines.push(` ${db.volumeName}:`);
303
- lines.push(` name: ${db.volumeName}`);
304
- }
305
- for (const r of redis) {
306
- lines.push(` ${r.volumeName}:`);
307
- lines.push(` name: ${r.volumeName}`);
308
- }
309
- return `${lines.join("\n")}\n`;
310
- }
311
- function materializeInfraPlan(configDir, planEnv, planDatabases, planRedis, projectName, devHostPort) {
312
- const envContent = renderEnvFileFromPlan(planEnv, devHostPort);
313
- const dockerContent = renderDockerComposeFromPlan(planDatabases, planRedis, projectName);
314
- const envExamplePath = join(configDir, ".env.example");
315
- const dockerComposePath = join(configDir, "docker-compose.yml");
316
- return {
317
- envExampleChanged: syncTextFile(envExamplePath, envContent),
318
- dockerComposeChanged: syncTextFile(dockerComposePath, dockerContent)
319
- };
320
- }
321
257
  function syncTextFile(filePath, nextContent) {
322
258
  if (existsSync(filePath) && readFileSync(filePath, "utf-8") === nextContent) return false;
323
259
  writeFileSync(filePath, nextContent);
@@ -401,5 +337,5 @@ function loadProjectEnv(configDir) {
401
337
  }
402
338
 
403
339
  //#endregion
404
- export { buildDatabaseConfigs, buildOriginMap, buildRedisConfigs, ensureEnvFile, getSecretGroups, loadPortState, loadProjectEnv, materializeInfraPlan, normalizeDatabaseSlug, normalizeRedisSlug, renderDockerComposeFromPlan, renderEnvFileFromPlan, savePortState, syncGeneratedInfra, writeGeneratedInfra };
340
+ export { buildDatabaseConfigs, buildOriginMap, buildRedisConfigs, ensureEnvFile, getSecretGroups, loadPortState, loadProjectEnv, normalizeDatabaseSlug, normalizeRedisSlug, savePortState, syncGeneratedInfra, writeGeneratedInfra };
405
341
  //# sourceMappingURL=infra.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"infra.mjs","names":[],"sources":["../../src/cli/infra.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { config as loadDotenv } from \"dotenv\";\nimport type { RuntimeConfig } from \"../types\";\n\nconst POSTGRES_USER = \"everythingdev\";\nconst POSTGRES_PASSWORD = \"everythingdev\";\nconst API_DATABASE_SECRET = \"API_DATABASE_URL\";\nconst AUTH_DATABASE_SECRET = \"AUTH_DATABASE_URL\";\nconst HOST_SECRET = \"CORS_ORIGIN\";\nconst BASE_POSTGRES_PORT = 5434;\nconst BASE_REDIS_PORT = 6379;\n\nexport interface DatabaseSecretConfig {\n secret: string;\n slug: string;\n fromKey: string;\n port: number;\n serviceName: string;\n containerName: string;\n databaseName: string;\n volumeName: string;\n url: string;\n}\n\nexport interface RedisSecretConfig {\n secret: string;\n slug: string;\n fromKey: string;\n port: number;\n serviceName: string;\n containerName: string;\n volumeName: string;\n url: string;\n}\n\nexport interface SecretGroup {\n section: string;\n secrets: string[];\n}\n\ninterface DevPortState {\n host?: number;\n api?: number;\n ui?: number;\n auth?: number;\n pluginPortStart?: number;\n}\n\nexport interface PortState {\n postgresPorts: Record<string, number>;\n redisPorts: Record<string, number>;\n devPorts?: DevPortState;\n}\n\nexport interface GeneratedInfraSpec {\n groups: SecretGroup[];\n databases: DatabaseSecretConfig[];\n redis: RedisSecretConfig[];\n}\n\ninterface SyncGeneratedInfraResult {\n secrets: string[];\n envExampleChanged: boolean;\n dockerComposeChanged: boolean;\n staleEnvWarnings: string[];\n}\n\nfunction uniqueSecrets(values: Array<string | undefined>): string[] {\n const secrets: string[] = [];\n const seen = new Set<string>();\n\n for (const value of values) {\n if (!value || seen.has(value)) continue;\n seen.add(value);\n secrets.push(value);\n }\n\n return secrets;\n}\n\nexport function loadPortState(configDir?: string): PortState {\n if (!configDir) return { postgresPorts: {}, redisPorts: {} };\n const statePath = join(configDir, \".bos\", \"infra-state.json\");\n if (!existsSync(statePath)) return { postgresPorts: {}, redisPorts: {} };\n try {\n const raw = JSON.parse(readFileSync(statePath, \"utf-8\")) as Partial<PortState>;\n return {\n postgresPorts: raw.postgresPorts ?? {},\n redisPorts: raw.redisPorts ?? {},\n devPorts: raw.devPorts,\n };\n } catch {\n return { postgresPorts: {}, redisPorts: {} };\n }\n}\n\nexport function savePortState(configDir: string, state: PortState): void {\n const statePath = join(configDir, \".bos\", \"infra-state.json\");\n mkdirSync(dirname(statePath), { recursive: true });\n writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\\n`);\n}\n\nexport type { DevPortState };\n\nfunction resolvePort(slug: string, portMap: Record<string, number>, basePort: number): number {\n if (portMap[slug] !== undefined) return portMap[slug];\n const assigned = Object.values(portMap);\n const maxAssigned = assigned.length > 0 ? Math.max(...assigned) : basePort - 1;\n const next = Math.max(maxAssigned + 1, basePort);\n portMap[slug] = next;\n return next;\n}\n\nexport function normalizeRedisSlug(secret: string): string {\n return secret.replace(/_REDIS_URL$/, \"\").toLowerCase();\n}\n\nexport function getSecretGroups(runtimeConfig: RuntimeConfig): SecretGroup[] {\n const groups: SecretGroup[] = [];\n const seen = new Set<string>();\n\n const addGroup = (section: string, secrets: string[]) => {\n const filtered = secrets.filter((s) => {\n if (seen.has(s)) return false;\n seen.add(s);\n return true;\n });\n if (filtered.length > 0) {\n groups.push({ section, secrets: filtered });\n }\n };\n\n addGroup(\"app.host\", uniqueSecrets([...(runtimeConfig.host.secrets ?? []), HOST_SECRET]));\n\n addGroup(\"app.api\", uniqueSecrets(runtimeConfig.api.secrets ?? []));\n\n if (runtimeConfig.auth) {\n addGroup(\"app.auth\", uniqueSecrets(runtimeConfig.auth.secrets ?? []));\n }\n\n if (runtimeConfig.plugins) {\n for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) {\n if (plugin.secrets && plugin.secrets.length > 0) {\n addGroup(`plugins.${pluginKey}`, plugin.secrets);\n }\n }\n }\n\n return groups;\n}\n\nfunction buildGeneratedInfraSpec(\n runtimeConfig: RuntimeConfig,\n configDir?: string,\n): { spec: GeneratedInfraSpec; portState: PortState } {\n const groups = getSecretGroups(runtimeConfig);\n const allSecrets = groups.flatMap((group) => group.secrets);\n const originMap = configDir ? buildOriginMap(configDir, runtimeConfig) : new Map();\n const portState = loadPortState(configDir);\n\n const databases = buildDatabaseConfigs(allSecrets, originMap, portState.postgresPorts);\n const redis = buildRedisConfigs(allSecrets, originMap, portState.redisPorts);\n\n return { spec: { groups, databases, redis }, portState };\n}\n\nexport function normalizeDatabaseSlug(secret: string): string {\n return secret.replace(/_DATABASE_URL$/, \"\").toLowerCase();\n}\n\nexport function buildOriginMap(\n configDir: string,\n runtimeConfig: RuntimeConfig,\n): Map<string, string> {\n const configPath = join(configDir, \"bos.config.json\");\n\n const originMap = new Map<string, string>();\n const account = runtimeConfig.account;\n\n const resolveOrigin = (extendsRef: unknown): string | null => {\n if (typeof extendsRef === \"string\") {\n const match = extendsRef.match(/^bos:\\/\\/([^/]+)\\//);\n return match?.[1] ?? null;\n }\n return null;\n };\n\n const rawConfig = existsSync(configPath)\n ? (JSON.parse(readFileSync(configPath, \"utf-8\")) as Record<string, unknown>)\n : null;\n const rawPlugins = rawConfig?.plugins as Record<string, unknown> | undefined;\n\n for (const secret of runtimeConfig.api.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, account);\n }\n\n const rawApp = rawConfig?.app as Record<string, unknown> | undefined;\n const authExtends = (rawApp?.auth as Record<string, unknown> | undefined)?.extends;\n const authOrigin = resolveOrigin(authExtends) ?? account;\n for (const secret of runtimeConfig.auth?.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, authOrigin);\n }\n\n for (const [pluginKey, pluginEntry] of Object.entries(runtimeConfig.plugins ?? {})) {\n const rawPlugin = rawPlugins?.[pluginKey];\n let pluginOrigin: string;\n if (typeof rawPlugin === \"string\") {\n pluginOrigin = resolveOrigin(rawPlugin) ?? account;\n } else if (rawPlugin && typeof rawPlugin === \"object\") {\n pluginOrigin = resolveOrigin((rawPlugin as Record<string, unknown>).extends) ?? account;\n } else {\n pluginOrigin = account;\n }\n for (const secret of pluginEntry.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, pluginOrigin);\n }\n }\n\n for (const secret of runtimeConfig.host.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, account);\n }\n\n return originMap;\n}\n\nexport function buildDatabaseConfigs(\n secrets: string[],\n originMap: Map<string, string>,\n portMap: Record<string, number>,\n): DatabaseSecretConfig[] {\n const databaseSecrets = uniqueSecrets(\n secrets.filter((secret) => secret.endsWith(\"_DATABASE_URL\")),\n );\n\n const orderedSecrets = [...databaseSecrets];\n\n // Prune stale entries from removed plugins\n const currentSlugs = new Set(orderedSecrets.map(normalizeDatabaseSlug));\n for (const slug of Object.keys(portMap)) {\n if (!currentSlugs.has(slug)) delete portMap[slug];\n }\n\n // Sort by slug for deterministic assignment order\n orderedSecrets.sort((a, b) => normalizeDatabaseSlug(a).localeCompare(normalizeDatabaseSlug(b)));\n\n for (const secret of orderedSecrets) {\n const slug = normalizeDatabaseSlug(secret);\n if (secret === API_DATABASE_SECRET) {\n portMap[slug] = 5432;\n } else if (secret === AUTH_DATABASE_SECRET) {\n portMap[slug] = 5433;\n } else {\n resolvePort(slug, portMap, BASE_POSTGRES_PORT);\n }\n }\n\n return orderedSecrets.map((secret) => {\n const slug = normalizeDatabaseSlug(secret);\n const fromKey = originMap.get(secret) ?? \"\";\n const port = portMap[slug];\n\n const volumeName = fromKey\n ? `${fromKey.replace(/\\./g, \"_\")}_postgres_${slug}_data`\n : `postgres_${slug}_data`;\n\n const containerName = fromKey ? `${fromKey}-postgres-${slug}` : `postgres-${slug}`;\n\n return {\n secret,\n slug,\n fromKey,\n port,\n serviceName: `postgres-${slug.replace(/_/g, \"-\")}`,\n containerName,\n databaseName: `${slug}_db`,\n volumeName,\n url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`,\n };\n });\n}\n\nexport function buildRedisConfigs(\n secrets: string[],\n originMap: Map<string, string>,\n portMap: Record<string, number>,\n): RedisSecretConfig[] {\n const redisSecrets = uniqueSecrets(secrets.filter((secret) => secret.endsWith(\"_REDIS_URL\")));\n const orderedSecrets = [...redisSecrets];\n\n // Prune stale entries from removed plugins\n const currentSlugs = new Set(orderedSecrets.map(normalizeRedisSlug));\n for (const slug of Object.keys(portMap)) {\n if (!currentSlugs.has(slug)) delete portMap[slug];\n }\n\n // Sort by slug for deterministic assignment order\n orderedSecrets.sort((a, b) => normalizeRedisSlug(a).localeCompare(normalizeRedisSlug(b)));\n\n for (const secret of orderedSecrets) {\n const slug = normalizeRedisSlug(secret);\n resolvePort(slug, portMap, BASE_REDIS_PORT);\n }\n\n return orderedSecrets.map((secret) => {\n const slug = normalizeRedisSlug(secret);\n const fromKey = originMap.get(secret) ?? \"\";\n const port = portMap[slug];\n\n const volumeName = fromKey\n ? `${fromKey.replace(/\\./g, \"_\")}_redis_${slug}_data`\n : `redis_${slug}_data`;\n\n const containerName = fromKey ? `${fromKey}-redis-${slug}` : `redis-${slug}`;\n\n return {\n secret,\n slug,\n fromKey,\n port,\n serviceName: `redis-${slug.replace(/_/g, \"-\")}`,\n containerName,\n volumeName,\n url: `redis://localhost:${port}`,\n };\n });\n}\n\nfunction extractPortFromUrl(url: string): string | null {\n const match = url.match(/:(\\d{4,5})(?:\\/|$)/);\n return match?.[1] ?? null;\n}\n\nfunction resolveDevHostPort(runtimeConfig: RuntimeConfig): number {\n if (typeof runtimeConfig.host?.port === \"number\") return runtimeConfig.host.port;\n const fromUrl = runtimeConfig.host?.url ? extractPortFromUrl(runtimeConfig.host.url) : null;\n if (fromUrl) {\n const parsed = Number.parseInt(fromUrl, 10);\n if (!Number.isNaN(parsed)) return parsed;\n }\n return 3000;\n}\n\nfunction defaultSecretValue(\n secret: string,\n databases: Map<string, DatabaseSecretConfig>,\n redisConfigs: Map<string, RedisSecretConfig>,\n options: { forExample: boolean; devHostPort?: number },\n): string {\n if (secret === \"BETTER_AUTH_SECRET\") {\n return options.forExample ? \"\" : randomBytes(32).toString(\"base64url\");\n }\n\n if (secret === \"CORS_ORIGIN\") {\n if (typeof options.devHostPort === \"number\") {\n return `http://localhost:${options.devHostPort}`;\n }\n return \"http://localhost:3000\";\n }\n\n return databases.get(secret)?.url ?? redisConfigs.get(secret)?.url ?? \"\";\n}\n\nfunction renderEnvFile(\n groups: SecretGroup[],\n databases: DatabaseSecretConfig[],\n redisConfigs: RedisSecretConfig[],\n options: { forExample: boolean; devHostPort?: number },\n): string {\n const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));\n const redisMap = new Map(redisConfigs.map((entry) => [entry.secret, entry]));\n const lines: string[] = [\n \"# Generated from configured bos secrets\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n\n for (const group of groups) {\n lines.push(`# ${group.section}`);\n for (const secret of group.secrets) {\n lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, redisMap, options)}`);\n }\n lines.push(\"\");\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction renderDockerCompose(\n databases: DatabaseSecretConfig[],\n redisConfigs: RedisSecretConfig[],\n projectName: string,\n): string {\n const lines = [`name: ${projectName}`, \"\"];\n\n if (databases.length > 0) {\n lines.push(\n \"x-pg-common: &pg-common\",\n \" image: postgres:17-alpine\",\n \" environment: &pg-env\",\n ` POSTGRES_USER: ${POSTGRES_USER}`,\n ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,\n \" healthcheck:\",\n ' test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n );\n }\n\n if (redisConfigs.length > 0) {\n lines.push(\n \"x-redis-common: &redis-common\",\n \" image: redis:7-alpine\",\n \" command: redis-server --appendonly yes\",\n \" healthcheck:\",\n ' test: [\"CMD\", \"redis-cli\", \"ping\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n );\n }\n\n lines.push(\"services:\");\n\n for (const database of databases) {\n lines.push(` ${database.serviceName}:`);\n lines.push(\" <<: *pg-common\");\n lines.push(` container_name: ${database.containerName}`);\n lines.push(\" environment:\");\n lines.push(\" <<: *pg-env\");\n lines.push(` POSTGRES_DB: ${database.databaseName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${database.port}:5432\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${database.volumeName}:/var/lib/postgresql/data`);\n lines.push(\"\");\n }\n\n for (const redis of redisConfigs) {\n lines.push(` ${redis.serviceName}:`);\n lines.push(\" <<: *redis-common\");\n lines.push(` container_name: ${redis.containerName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${redis.port}:6379\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${redis.volumeName}:/data`);\n lines.push(\"\");\n }\n\n lines.push(\"volumes:\");\n for (const database of databases) {\n lines.push(` ${database.volumeName}:`);\n lines.push(` name: ${database.volumeName}`);\n }\n for (const redis of redisConfigs) {\n lines.push(` ${redis.volumeName}:`);\n lines.push(` name: ${redis.volumeName}`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function renderEnvFileFromPlan(env: Record<string, string>, devHostPort?: number): string {\n const lines: string[] = [\n \"# Generated from bos dev infra plan\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n const sortedKeys = Object.keys(env).sort();\n for (const key of sortedKeys) {\n let value = env[key];\n if (key === \"CORS_ORIGIN\" && devHostPort && !value) {\n value = `http://localhost:${devHostPort}`;\n }\n lines.push(`${key}=${value}`);\n }\n lines.push(\"\");\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function renderDockerComposeFromPlan(\n databases: {\n serviceName: string;\n containerName: string;\n port: number;\n volumeName: string;\n databaseName: string;\n }[],\n redis: { serviceName: string; containerName: string; port: number; volumeName: string }[],\n projectName: string,\n): string {\n const lines = [`name: ${projectName}`, \"\"];\n\n if (databases.length > 0) {\n lines.push(\n \"x-pg-common: &pg-common\",\n \" image: postgres:17-alpine\",\n \" environment: &pg-env\",\n ` POSTGRES_USER: ${POSTGRES_USER}`,\n ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,\n \" healthcheck:\",\n ' test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n );\n }\n\n if (redis.length > 0) {\n lines.push(\n \"x-redis-common: &redis-common\",\n \" image: redis:7-alpine\",\n \" command: redis-server --appendonly yes\",\n \" healthcheck:\",\n ' test: [\"CMD\", \"redis-cli\", \"ping\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n );\n }\n\n lines.push(\"services:\");\n\n for (const db of databases) {\n lines.push(` ${db.serviceName}:`);\n lines.push(\" <<: *pg-common\");\n lines.push(` container_name: ${db.containerName}`);\n lines.push(\" environment:\");\n lines.push(\" <<: *pg-env\");\n lines.push(` POSTGRES_DB: ${db.databaseName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${db.port}:5432\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${db.volumeName}:/var/lib/postgresql/data`);\n lines.push(\"\");\n }\n\n for (const r of redis) {\n lines.push(` ${r.serviceName}:`);\n lines.push(\" <<: *redis-common\");\n lines.push(` container_name: ${r.containerName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${r.port}:6379\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${r.volumeName}:/data`);\n lines.push(\"\");\n }\n\n lines.push(\"volumes:\");\n for (const db of databases) {\n lines.push(` ${db.volumeName}:`);\n lines.push(` name: ${db.volumeName}`);\n }\n for (const r of redis) {\n lines.push(` ${r.volumeName}:`);\n lines.push(` name: ${r.volumeName}`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function materializeInfraPlan(\n configDir: string,\n planEnv: Record<string, string>,\n planDatabases: {\n serviceName: string;\n containerName: string;\n port: number;\n volumeName: string;\n databaseName: string;\n }[],\n planRedis: { serviceName: string; containerName: string; port: number; volumeName: string }[],\n projectName: string,\n devHostPort?: number,\n): { envExampleChanged: boolean; dockerComposeChanged: boolean } {\n const envContent = renderEnvFileFromPlan(planEnv, devHostPort);\n const dockerContent = renderDockerComposeFromPlan(planDatabases, planRedis, projectName);\n\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n return {\n envExampleChanged: syncTextFile(envExamplePath, envContent),\n dockerComposeChanged: syncTextFile(dockerComposePath, dockerContent),\n };\n}\n\nfunction syncTextFile(filePath: string, nextContent: string): boolean {\n if (existsSync(filePath) && readFileSync(filePath, \"utf-8\") === nextContent) {\n return false;\n }\n\n writeFileSync(filePath, nextContent);\n return true;\n}\n\nexport function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {\n const result = syncGeneratedInfra(configDir, runtimeConfig);\n\n if (result.staleEnvWarnings.length > 0) {\n p.log.warn(\n `.env has ${result.staleEnvWarnings.length} stale value(s) compared to .env.example:`,\n );\n for (const warning of result.staleEnvWarnings) {\n p.log.message(` ${warning}`);\n }\n }\n\n return result.secrets;\n}\n\nexport function syncGeneratedInfra(\n configDir: string,\n runtimeConfig: RuntimeConfig,\n): SyncGeneratedInfraResult {\n const { spec, portState } = buildGeneratedInfraSpec(runtimeConfig, configDir);\n const secrets = spec.groups.flatMap((group) => group.secrets);\n const envOptions: { forExample: true; devHostPort?: number } = { forExample: true };\n if (runtimeConfig.env === \"development\") {\n envOptions.devHostPort = resolveDevHostPort(runtimeConfig);\n }\n const newEnvContent = renderEnvFile(spec.groups, spec.databases, spec.redis, envOptions);\n const newDockerContent = renderDockerCompose(spec.databases, spec.redis, runtimeConfig.account);\n\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n const staleWarnings = checkEnvStaleness(configDir, spec.databases, spec.redis);\n\n if (configDir) {\n savePortState(configDir, portState);\n }\n\n return {\n secrets,\n envExampleChanged: syncTextFile(envExamplePath, newEnvContent),\n dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent),\n staleEnvWarnings: staleWarnings,\n };\n}\n\nfunction checkEnvStaleness(\n configDir: string,\n databases: DatabaseSecretConfig[],\n redisConfigs: RedisSecretConfig[],\n): string[] {\n const envPath = join(configDir, \".env\");\n if (!existsSync(envPath)) return [];\n\n const existingEnv = readFileSync(envPath, \"utf-8\");\n const envMap = new Map<string, string>();\n for (const line of existingEnv.split(\"\\n\")) {\n const match = line.match(/^([A-Z_]+)=(.*)$/);\n if (match) envMap.set(match[1], match[2]);\n }\n\n const stale: string[] = [];\n\n for (const db of databases) {\n const existing = envMap.get(db.secret);\n if (existing && existing !== db.url) {\n const oldPort = extractPortFromUrl(existing) ?? \"?\";\n stale.push(`${db.secret}: port ${oldPort} → ${db.port}`);\n }\n }\n\n for (const redis of redisConfigs) {\n const existing = envMap.get(redis.secret);\n if (existing && existing !== redis.url) {\n const oldPort = extractPortFromUrl(existing) ?? \"?\";\n stale.push(`${redis.secret}: port ${oldPort} → ${redis.port}`);\n }\n }\n\n return stale;\n}\n\nexport function ensureEnvFile(configDir: string): void {\n const envPath = join(configDir, \".env\");\n const examplePath = join(configDir, \".env.example\");\n\n if (existsSync(envPath) || !existsSync(examplePath)) return;\n\n const content = readFileSync(examplePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n const secret = randomBytes(32).toString(\"base64url\");\n const updated = lines\n .map((line) => {\n if (/^BETTER_AUTH_SECRET=/.test(line)) {\n return `BETTER_AUTH_SECRET=${secret}`;\n }\n return line;\n })\n .join(\"\\n\");\n\n writeFileSync(envPath, updated);\n p.log.info(\"Created .env from generated .env.example with generated BETTER_AUTH_SECRET\");\n}\n\nlet envLoadedDir: string | null = null;\n\nexport function loadProjectEnv(configDir: string): void {\n if (envLoadedDir === configDir) return;\n const envPath = join(configDir, \".env\");\n if (!existsSync(envPath)) return;\n\n loadDotenv({ path: envPath, processEnv: process.env, quiet: true });\n envLoadedDir = configDir;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"infra.mjs","names":[],"sources":["../../src/cli/infra.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport * as p from \"@clack/prompts\";\nimport { config as loadDotenv } from \"dotenv\";\nimport type { RuntimeConfig } from \"../types\";\n\nconst POSTGRES_USER = \"everythingdev\";\nconst POSTGRES_PASSWORD = \"everythingdev\";\nconst API_DATABASE_SECRET = \"API_DATABASE_URL\";\nconst AUTH_DATABASE_SECRET = \"AUTH_DATABASE_URL\";\nconst HOST_SECRET = \"CORS_ORIGIN\";\nconst BASE_POSTGRES_PORT = 5434;\nconst BASE_REDIS_PORT = 6379;\n\nexport interface DatabaseSecretConfig {\n secret: string;\n slug: string;\n fromKey: string;\n port: number;\n serviceName: string;\n containerName: string;\n databaseName: string;\n volumeName: string;\n url: string;\n}\n\nexport interface RedisSecretConfig {\n secret: string;\n slug: string;\n fromKey: string;\n port: number;\n serviceName: string;\n containerName: string;\n volumeName: string;\n url: string;\n}\n\nexport interface SecretGroup {\n section: string;\n secrets: string[];\n}\n\ninterface DevPortState {\n host?: number;\n api?: number;\n ui?: number;\n auth?: number;\n pluginPortStart?: number;\n}\n\nexport interface PortState {\n postgresPorts: Record<string, number>;\n redisPorts: Record<string, number>;\n devPorts?: DevPortState;\n}\n\nexport interface GeneratedInfraSpec {\n groups: SecretGroup[];\n databases: DatabaseSecretConfig[];\n redis: RedisSecretConfig[];\n}\n\ninterface SyncGeneratedInfraResult {\n secrets: string[];\n envExampleChanged: boolean;\n dockerComposeChanged: boolean;\n staleEnvWarnings: string[];\n}\n\nfunction uniqueSecrets(values: Array<string | undefined>): string[] {\n const secrets: string[] = [];\n const seen = new Set<string>();\n\n for (const value of values) {\n if (!value || seen.has(value)) continue;\n seen.add(value);\n secrets.push(value);\n }\n\n return secrets;\n}\n\nexport function loadPortState(configDir?: string): PortState {\n if (!configDir) return { postgresPorts: {}, redisPorts: {} };\n const statePath = join(configDir, \".bos\", \"infra-state.json\");\n if (!existsSync(statePath)) return { postgresPorts: {}, redisPorts: {} };\n try {\n const raw = JSON.parse(readFileSync(statePath, \"utf-8\")) as Partial<PortState>;\n return {\n postgresPorts: raw.postgresPorts ?? {},\n redisPorts: raw.redisPorts ?? {},\n devPorts: raw.devPorts,\n };\n } catch {\n return { postgresPorts: {}, redisPorts: {} };\n }\n}\n\nexport function savePortState(configDir: string, state: PortState): void {\n const statePath = join(configDir, \".bos\", \"infra-state.json\");\n mkdirSync(dirname(statePath), { recursive: true });\n writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\\n`);\n}\n\nexport type { DevPortState };\n\nfunction resolvePort(slug: string, portMap: Record<string, number>, basePort: number): number {\n if (portMap[slug] !== undefined) return portMap[slug];\n const assigned = Object.values(portMap);\n const maxAssigned = assigned.length > 0 ? Math.max(...assigned) : basePort - 1;\n const next = Math.max(maxAssigned + 1, basePort);\n portMap[slug] = next;\n return next;\n}\n\nexport function normalizeRedisSlug(secret: string): string {\n return secret.replace(/_REDIS_URL$/, \"\").toLowerCase();\n}\n\nexport function getSecretGroups(runtimeConfig: RuntimeConfig): SecretGroup[] {\n const groups: SecretGroup[] = [];\n const seen = new Set<string>();\n\n const addGroup = (section: string, secrets: string[]) => {\n const filtered = secrets.filter((s) => {\n if (seen.has(s)) return false;\n seen.add(s);\n return true;\n });\n if (filtered.length > 0) {\n groups.push({ section, secrets: filtered });\n }\n };\n\n addGroup(\"app.host\", uniqueSecrets([...(runtimeConfig.host.secrets ?? []), HOST_SECRET]));\n\n addGroup(\"app.api\", uniqueSecrets(runtimeConfig.api.secrets ?? []));\n\n if (runtimeConfig.auth) {\n addGroup(\"app.auth\", uniqueSecrets(runtimeConfig.auth.secrets ?? []));\n }\n\n if (runtimeConfig.plugins) {\n for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) {\n if (plugin.secrets && plugin.secrets.length > 0) {\n addGroup(`plugins.${pluginKey}`, plugin.secrets);\n }\n }\n }\n\n return groups;\n}\n\nfunction buildGeneratedInfraSpec(\n runtimeConfig: RuntimeConfig,\n configDir?: string,\n): { spec: GeneratedInfraSpec; portState: PortState } {\n const groups = getSecretGroups(runtimeConfig);\n const allSecrets = groups.flatMap((group) => group.secrets);\n const originMap = configDir ? buildOriginMap(configDir, runtimeConfig) : new Map();\n const portState = loadPortState(configDir);\n\n const databases = buildDatabaseConfigs(allSecrets, originMap, portState.postgresPorts);\n const redis = buildRedisConfigs(allSecrets, originMap, portState.redisPorts);\n\n return { spec: { groups, databases, redis }, portState };\n}\n\nexport function normalizeDatabaseSlug(secret: string): string {\n return secret.replace(/_DATABASE_URL$/, \"\").toLowerCase();\n}\n\nexport function buildOriginMap(\n configDir: string,\n runtimeConfig: RuntimeConfig,\n): Map<string, string> {\n const configPath = join(configDir, \"bos.config.json\");\n\n const originMap = new Map<string, string>();\n const account = runtimeConfig.account;\n\n const resolveOrigin = (extendsRef: unknown): string | null => {\n if (typeof extendsRef === \"string\") {\n const match = extendsRef.match(/^bos:\\/\\/([^/]+)\\//);\n return match?.[1] ?? null;\n }\n return null;\n };\n\n const rawConfig = existsSync(configPath)\n ? (JSON.parse(readFileSync(configPath, \"utf-8\")) as Record<string, unknown>)\n : null;\n const rawPlugins = rawConfig?.plugins as Record<string, unknown> | undefined;\n\n for (const secret of runtimeConfig.api.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, account);\n }\n\n const rawApp = rawConfig?.app as Record<string, unknown> | undefined;\n const authExtends = (rawApp?.auth as Record<string, unknown> | undefined)?.extends;\n const authOrigin = resolveOrigin(authExtends) ?? account;\n for (const secret of runtimeConfig.auth?.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, authOrigin);\n }\n\n for (const [pluginKey, pluginEntry] of Object.entries(runtimeConfig.plugins ?? {})) {\n const rawPlugin = rawPlugins?.[pluginKey];\n let pluginOrigin: string;\n if (typeof rawPlugin === \"string\") {\n pluginOrigin = resolveOrigin(rawPlugin) ?? account;\n } else if (rawPlugin && typeof rawPlugin === \"object\") {\n pluginOrigin = resolveOrigin((rawPlugin as Record<string, unknown>).extends) ?? account;\n } else {\n pluginOrigin = account;\n }\n for (const secret of pluginEntry.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, pluginOrigin);\n }\n }\n\n for (const secret of runtimeConfig.host.secrets ?? []) {\n if (!originMap.has(secret)) originMap.set(secret, account);\n }\n\n return originMap;\n}\n\nexport function buildDatabaseConfigs(\n secrets: string[],\n originMap: Map<string, string>,\n portMap: Record<string, number>,\n): DatabaseSecretConfig[] {\n const databaseSecrets = uniqueSecrets(\n secrets.filter((secret) => secret.endsWith(\"_DATABASE_URL\")),\n );\n\n const orderedSecrets = [...databaseSecrets];\n\n // Prune stale entries from removed plugins\n const currentSlugs = new Set(orderedSecrets.map(normalizeDatabaseSlug));\n for (const slug of Object.keys(portMap)) {\n if (!currentSlugs.has(slug)) delete portMap[slug];\n }\n\n // Sort by slug for deterministic assignment order\n orderedSecrets.sort((a, b) => normalizeDatabaseSlug(a).localeCompare(normalizeDatabaseSlug(b)));\n\n for (const secret of orderedSecrets) {\n const slug = normalizeDatabaseSlug(secret);\n if (secret === API_DATABASE_SECRET) {\n portMap[slug] = 5432;\n } else if (secret === AUTH_DATABASE_SECRET) {\n portMap[slug] = 5433;\n } else {\n resolvePort(slug, portMap, BASE_POSTGRES_PORT);\n }\n }\n\n return orderedSecrets.map((secret) => {\n const slug = normalizeDatabaseSlug(secret);\n const fromKey = originMap.get(secret) ?? \"\";\n const port = portMap[slug];\n\n const volumeName = fromKey\n ? `${fromKey.replace(/\\./g, \"_\")}_postgres_${slug}_data`\n : `postgres_${slug}_data`;\n\n const containerName = fromKey ? `${fromKey}-postgres-${slug}` : `postgres-${slug}`;\n\n return {\n secret,\n slug,\n fromKey,\n port,\n serviceName: `postgres-${slug.replace(/_/g, \"-\")}`,\n containerName,\n databaseName: `${slug}_db`,\n volumeName,\n url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`,\n };\n });\n}\n\nexport function buildRedisConfigs(\n secrets: string[],\n originMap: Map<string, string>,\n portMap: Record<string, number>,\n): RedisSecretConfig[] {\n const redisSecrets = uniqueSecrets(secrets.filter((secret) => secret.endsWith(\"_REDIS_URL\")));\n const orderedSecrets = [...redisSecrets];\n\n // Prune stale entries from removed plugins\n const currentSlugs = new Set(orderedSecrets.map(normalizeRedisSlug));\n for (const slug of Object.keys(portMap)) {\n if (!currentSlugs.has(slug)) delete portMap[slug];\n }\n\n // Sort by slug for deterministic assignment order\n orderedSecrets.sort((a, b) => normalizeRedisSlug(a).localeCompare(normalizeRedisSlug(b)));\n\n for (const secret of orderedSecrets) {\n const slug = normalizeRedisSlug(secret);\n resolvePort(slug, portMap, BASE_REDIS_PORT);\n }\n\n return orderedSecrets.map((secret) => {\n const slug = normalizeRedisSlug(secret);\n const fromKey = originMap.get(secret) ?? \"\";\n const port = portMap[slug];\n\n const volumeName = fromKey\n ? `${fromKey.replace(/\\./g, \"_\")}_redis_${slug}_data`\n : `redis_${slug}_data`;\n\n const containerName = fromKey ? `${fromKey}-redis-${slug}` : `redis-${slug}`;\n\n return {\n secret,\n slug,\n fromKey,\n port,\n serviceName: `redis-${slug.replace(/_/g, \"-\")}`,\n containerName,\n volumeName,\n url: `redis://localhost:${port}`,\n };\n });\n}\n\nfunction extractPortFromUrl(url: string): string | null {\n const match = url.match(/:(\\d{4,5})(?:\\/|$)/);\n return match?.[1] ?? null;\n}\n\nfunction resolveDevHostPort(runtimeConfig: RuntimeConfig): number {\n if (typeof runtimeConfig.host?.port === \"number\") return runtimeConfig.host.port;\n const fromUrl = runtimeConfig.host?.url ? extractPortFromUrl(runtimeConfig.host.url) : null;\n if (fromUrl) {\n const parsed = Number.parseInt(fromUrl, 10);\n if (!Number.isNaN(parsed)) return parsed;\n }\n return 3000;\n}\n\nfunction defaultSecretValue(\n secret: string,\n databases: Map<string, DatabaseSecretConfig>,\n redisConfigs: Map<string, RedisSecretConfig>,\n options: { forExample: boolean; devHostPort?: number },\n): string {\n if (secret === \"BETTER_AUTH_SECRET\") {\n return options.forExample ? \"\" : randomBytes(32).toString(\"base64url\");\n }\n\n if (secret === \"CORS_ORIGIN\") {\n if (typeof options.devHostPort === \"number\") {\n return `http://localhost:${options.devHostPort}`;\n }\n return \"http://localhost:3000\";\n }\n\n return databases.get(secret)?.url ?? redisConfigs.get(secret)?.url ?? \"\";\n}\n\nfunction renderEnvFile(\n groups: SecretGroup[],\n databases: DatabaseSecretConfig[],\n redisConfigs: RedisSecretConfig[],\n options: { forExample: boolean; devHostPort?: number },\n): string {\n const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));\n const redisMap = new Map(redisConfigs.map((entry) => [entry.secret, entry]));\n const lines: string[] = [\n \"# Generated from configured bos secrets\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n\n for (const group of groups) {\n lines.push(`# ${group.section}`);\n for (const secret of group.secrets) {\n lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, redisMap, options)}`);\n }\n lines.push(\"\");\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nfunction renderDockerCompose(\n databases: DatabaseSecretConfig[],\n redisConfigs: RedisSecretConfig[],\n projectName: string,\n): string {\n const lines = [`name: ${projectName}`, \"\"];\n\n if (databases.length > 0) {\n lines.push(\n \"x-pg-common: &pg-common\",\n \" image: postgres:17-alpine\",\n \" environment: &pg-env\",\n ` POSTGRES_USER: ${POSTGRES_USER}`,\n ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,\n \" healthcheck:\",\n ' test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n );\n }\n\n if (redisConfigs.length > 0) {\n lines.push(\n \"x-redis-common: &redis-common\",\n \" image: redis:7-alpine\",\n \" command: redis-server --appendonly yes\",\n \" healthcheck:\",\n ' test: [\"CMD\", \"redis-cli\", \"ping\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n );\n }\n\n lines.push(\"services:\");\n\n for (const database of databases) {\n lines.push(` ${database.serviceName}:`);\n lines.push(\" <<: *pg-common\");\n lines.push(` container_name: ${database.containerName}`);\n lines.push(\" environment:\");\n lines.push(\" <<: *pg-env\");\n lines.push(` POSTGRES_DB: ${database.databaseName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${database.port}:5432\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${database.volumeName}:/var/lib/postgresql/data`);\n lines.push(\"\");\n }\n\n for (const redis of redisConfigs) {\n lines.push(` ${redis.serviceName}:`);\n lines.push(\" <<: *redis-common\");\n lines.push(` container_name: ${redis.containerName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${redis.port}:6379\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${redis.volumeName}:/data`);\n lines.push(\"\");\n }\n\n lines.push(\"volumes:\");\n for (const database of databases) {\n lines.push(` ${database.volumeName}:`);\n lines.push(` name: ${database.volumeName}`);\n }\n for (const redis of redisConfigs) {\n lines.push(` ${redis.volumeName}:`);\n lines.push(` name: ${redis.volumeName}`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function renderEnvFileFromPlan(env: Record<string, string>, devHostPort?: number): string {\n const lines: string[] = [\n \"# Generated from bos dev infra plan\",\n \"# Update values as needed for your local environment\",\n \"\",\n ];\n const sortedKeys = Object.keys(env).sort();\n for (const key of sortedKeys) {\n let value = env[key];\n if (key === \"CORS_ORIGIN\" && devHostPort && !value) {\n value = `http://localhost:${devHostPort}`;\n }\n lines.push(`${key}=${value}`);\n }\n lines.push(\"\");\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function renderDockerComposeFromPlan(\n databases: {\n serviceName: string;\n containerName: string;\n port: number;\n volumeName: string;\n databaseName: string;\n }[],\n redis: { serviceName: string; containerName: string; port: number; volumeName: string }[],\n projectName: string,\n): string {\n const lines = [`name: ${projectName}`, \"\"];\n\n if (databases.length > 0) {\n lines.push(\n \"x-pg-common: &pg-common\",\n \" image: postgres:17-alpine\",\n \" environment: &pg-env\",\n ` POSTGRES_USER: ${POSTGRES_USER}`,\n ` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,\n \" healthcheck:\",\n ' test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n );\n }\n\n if (redis.length > 0) {\n lines.push(\n \"x-redis-common: &redis-common\",\n \" image: redis:7-alpine\",\n \" command: redis-server --appendonly yes\",\n \" healthcheck:\",\n ' test: [\"CMD\", \"redis-cli\", \"ping\"]',\n \" interval: 3s\",\n \" timeout: 3s\",\n \" retries: 5\",\n \"\",\n );\n }\n\n lines.push(\"services:\");\n\n for (const db of databases) {\n lines.push(` ${db.serviceName}:`);\n lines.push(\" <<: *pg-common\");\n lines.push(` container_name: ${db.containerName}`);\n lines.push(\" environment:\");\n lines.push(\" <<: *pg-env\");\n lines.push(` POSTGRES_DB: ${db.databaseName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${db.port}:5432\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${db.volumeName}:/var/lib/postgresql/data`);\n lines.push(\"\");\n }\n\n for (const r of redis) {\n lines.push(` ${r.serviceName}:`);\n lines.push(\" <<: *redis-common\");\n lines.push(` container_name: ${r.containerName}`);\n lines.push(\" ports:\");\n lines.push(` - \"${r.port}:6379\"`);\n lines.push(\" volumes:\");\n lines.push(` - ${r.volumeName}:/data`);\n lines.push(\"\");\n }\n\n lines.push(\"volumes:\");\n for (const db of databases) {\n lines.push(` ${db.volumeName}:`);\n lines.push(` name: ${db.volumeName}`);\n }\n for (const r of redis) {\n lines.push(` ${r.volumeName}:`);\n lines.push(` name: ${r.volumeName}`);\n }\n\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function materializeInfraPlan(\n configDir: string,\n planEnv: Record<string, string>,\n planDatabases: {\n serviceName: string;\n containerName: string;\n port: number;\n volumeName: string;\n databaseName: string;\n }[],\n planRedis: { serviceName: string; containerName: string; port: number; volumeName: string }[],\n projectName: string,\n devHostPort?: number,\n): { envExampleChanged: boolean; dockerComposeChanged: boolean } {\n const envContent = renderEnvFileFromPlan(planEnv, devHostPort);\n const dockerContent = renderDockerComposeFromPlan(planDatabases, planRedis, projectName);\n\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n return {\n envExampleChanged: syncTextFile(envExamplePath, envContent),\n dockerComposeChanged: syncTextFile(dockerComposePath, dockerContent),\n };\n}\n\nfunction syncTextFile(filePath: string, nextContent: string): boolean {\n if (existsSync(filePath) && readFileSync(filePath, \"utf-8\") === nextContent) {\n return false;\n }\n\n writeFileSync(filePath, nextContent);\n return true;\n}\n\nexport function writeGeneratedInfra(configDir: string, runtimeConfig: RuntimeConfig): string[] {\n const result = syncGeneratedInfra(configDir, runtimeConfig);\n\n if (result.staleEnvWarnings.length > 0) {\n p.log.warn(\n `.env has ${result.staleEnvWarnings.length} stale value(s) compared to .env.example:`,\n );\n for (const warning of result.staleEnvWarnings) {\n p.log.message(` ${warning}`);\n }\n }\n\n return result.secrets;\n}\n\nexport function syncGeneratedInfra(\n configDir: string,\n runtimeConfig: RuntimeConfig,\n): SyncGeneratedInfraResult {\n const { spec, portState } = buildGeneratedInfraSpec(runtimeConfig, configDir);\n const secrets = spec.groups.flatMap((group) => group.secrets);\n const envOptions: { forExample: true; devHostPort?: number } = { forExample: true };\n if (runtimeConfig.env === \"development\") {\n envOptions.devHostPort = resolveDevHostPort(runtimeConfig);\n }\n const newEnvContent = renderEnvFile(spec.groups, spec.databases, spec.redis, envOptions);\n const newDockerContent = renderDockerCompose(spec.databases, spec.redis, runtimeConfig.account);\n\n const envExamplePath = join(configDir, \".env.example\");\n const dockerComposePath = join(configDir, \"docker-compose.yml\");\n\n const staleWarnings = checkEnvStaleness(configDir, spec.databases, spec.redis);\n\n if (configDir) {\n savePortState(configDir, portState);\n }\n\n return {\n secrets,\n envExampleChanged: syncTextFile(envExamplePath, newEnvContent),\n dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent),\n staleEnvWarnings: staleWarnings,\n };\n}\n\nfunction checkEnvStaleness(\n configDir: string,\n databases: DatabaseSecretConfig[],\n redisConfigs: RedisSecretConfig[],\n): string[] {\n const envPath = join(configDir, \".env\");\n if (!existsSync(envPath)) return [];\n\n const existingEnv = readFileSync(envPath, \"utf-8\");\n const envMap = new Map<string, string>();\n for (const line of existingEnv.split(\"\\n\")) {\n const match = line.match(/^([A-Z_]+)=(.*)$/);\n if (match) envMap.set(match[1], match[2]);\n }\n\n const stale: string[] = [];\n\n for (const db of databases) {\n const existing = envMap.get(db.secret);\n if (existing && existing !== db.url) {\n const oldPort = extractPortFromUrl(existing) ?? \"?\";\n stale.push(`${db.secret}: port ${oldPort} → ${db.port}`);\n }\n }\n\n for (const redis of redisConfigs) {\n const existing = envMap.get(redis.secret);\n if (existing && existing !== redis.url) {\n const oldPort = extractPortFromUrl(existing) ?? \"?\";\n stale.push(`${redis.secret}: port ${oldPort} → ${redis.port}`);\n }\n }\n\n return stale;\n}\n\nexport function ensureEnvFile(configDir: string): void {\n const envPath = join(configDir, \".env\");\n const examplePath = join(configDir, \".env.example\");\n\n if (existsSync(envPath) || !existsSync(examplePath)) return;\n\n const content = readFileSync(examplePath, \"utf-8\");\n const lines = content.split(\"\\n\");\n const secret = randomBytes(32).toString(\"base64url\");\n const updated = lines\n .map((line) => {\n if (/^BETTER_AUTH_SECRET=/.test(line)) {\n return `BETTER_AUTH_SECRET=${secret}`;\n }\n return line;\n })\n .join(\"\\n\");\n\n writeFileSync(envPath, updated);\n p.log.info(\"Created .env from generated .env.example with generated BETTER_AUTH_SECRET\");\n}\n\nlet envLoadedDir: string | null = null;\n\nexport function loadProjectEnv(configDir: string): void {\n if (envLoadedDir === configDir) return;\n const envPath = join(configDir, \".env\");\n if (!existsSync(envPath)) return;\n\n loadDotenv({ path: envPath, processEnv: process.env, quiet: true });\n envLoadedDir = configDir;\n}\n"],"mappings":"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"}
package/dist/cli/init.cjs CHANGED
@@ -1,10 +1,10 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_http_client = require('../http-client.cjs');
3
3
  const require_fastkv = require('../fastkv.cjs');
4
- const require_merge = require('../merge.cjs');
5
4
  const require_manifest_normalizer = require('../internal/manifest-normalizer.cjs');
6
5
  const require_save_config = require('../utils/save-config.cjs');
7
6
  const require_snapshot = require('./snapshot.cjs');
7
+ const require_helpers = require('./utils/helpers.cjs');
8
8
  let node_fs = require("node:fs");
9
9
  let node_path = require("node:path");
10
10
  let node_crypto = require("node:crypto");
@@ -39,22 +39,10 @@ const OVERRIDE_WORKSPACE_MAP = {
39
39
  host: ["host"],
40
40
  plugins: []
41
41
  };
42
- function getExtendsRef(config) {
43
- if (typeof config.extends === "string") return config.extends;
44
- if (config.extends && typeof config.extends === "object") return require_merge.resolveExtendsRef(config.extends, "production");
45
- }
46
- function parseBosRef(ref) {
47
- const match = ref.match(/^bos:\/\/([^/]+)\/(.+)$/);
48
- if (!match?.[1] || !match[2]) return null;
49
- return {
50
- account: match[1],
51
- gateway: match[2]
52
- };
53
- }
54
42
  function readWorkspaceCatalog(sourceDir) {
55
43
  const pkgPath = (0, node_path.join)(sourceDir, "package.json");
56
44
  if (!(0, node_fs.existsSync)(pkgPath)) return {};
57
- return { ...readJsonFile(pkgPath).workspaces?.catalog ?? {} };
45
+ return { ...require_helpers.readJsonFile(pkgPath).workspaces?.catalog ?? {} };
58
46
  }
59
47
  async function resolveCatalogChainSource(opts) {
60
48
  const catalogs = [];
@@ -74,10 +62,10 @@ async function resolveCatalogChainSource(opts) {
74
62
  let currentSourceDir = sourceDir;
75
63
  let cleanup = async () => {};
76
64
  if (configPath) {
77
- config = readJsonFile(configPath);
65
+ config = require_helpers.readJsonFile(configPath);
78
66
  currentSourceDir = (0, node_path.dirname)(configPath);
79
67
  } else {
80
- const parsed = parseBosRef(currentRef);
68
+ const parsed = require_helpers.parseBosRef(currentRef);
81
69
  if (!parsed) break;
82
70
  const sourceResult = await resolveSourceDir({
83
71
  extendsAccount: parsed.account,
@@ -90,7 +78,7 @@ async function resolveCatalogChainSource(opts) {
90
78
  cleanups.push(cleanup);
91
79
  catalogs.push(currentSourceDir ? readWorkspaceCatalog(currentSourceDir) : {});
92
80
  if (typeof config.repository === "string") repository = config.repository;
93
- const nextExtendsRef = getExtendsRef(config);
81
+ const nextExtendsRef = require_helpers.getExtendsRef(config);
94
82
  if (!nextExtendsRef) break;
95
83
  if (nextExtendsRef.startsWith("bos://")) {
96
84
  currentRef = nextExtendsRef;
@@ -195,9 +183,9 @@ async function resolveRepositoryViaExtendsChain(extendsAccount, extendsGateway,
195
183
  repository: config.repository,
196
184
  config
197
185
  };
198
- const extendsRef = getExtendsRef(config);
186
+ const extendsRef = require_helpers.getExtendsRef(config);
199
187
  if (extendsRef) {
200
- const parsed = parseBosRef(extendsRef.startsWith("bos://") ? extendsRef : `bos://${extendsRef}`);
188
+ const parsed = require_helpers.parseBosRef(extendsRef.startsWith("bos://") ? extendsRef : `bos://${extendsRef}`);
201
189
  if (parsed) {
202
190
  const result = await resolveRepositoryViaExtendsChain(parsed.account, parsed.gateway, visited);
203
191
  if (result) return result;
@@ -345,14 +333,31 @@ function buildRootTypecheckScript(sections) {
345
333
  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");
346
334
  return commands.join(" && ");
347
335
  }
336
+ function getParentOnlyScriptKeys(projectDir) {
337
+ const parentPkgPath = (0, node_path.join)(projectDir, "node_modules", "everything-dev", "package.json");
338
+ if (!(0, node_fs.existsSync)(parentPkgPath)) return [];
339
+ try {
340
+ const parentScripts = JSON.parse((0, node_fs.readFileSync)(parentPkgPath, "utf-8")).scripts ?? {};
341
+ const allChildKeys = new Set(Object.keys(buildChildRootScripts({
342
+ ui: true,
343
+ api: true,
344
+ host: true,
345
+ plugins: true
346
+ })));
347
+ allChildKeys.add("test:integration");
348
+ return Object.keys(parentScripts).filter((key) => !allChildKeys.has(key));
349
+ } catch {
350
+ return [];
351
+ }
352
+ }
348
353
  function buildChildRootScripts(sections) {
349
354
  const scripts = {
350
- dev: "node_modules/.bin/bos dev",
351
- "dev:proxy": "node_modules/.bin/bos dev --proxy",
352
- build: "node_modules/.bin/bos build",
353
- deploy: "node_modules/.bin/bos build --deploy",
354
- publish: "node_modules/.bin/bos publish",
355
- start: "node_modules/.bin/bos start",
355
+ dev: "bos dev",
356
+ "dev:proxy": "bos dev --proxy",
357
+ build: "bos build",
358
+ deploy: "bos build --deploy",
359
+ publish: "bos publish",
360
+ start: "bos start",
356
361
  typecheck: buildRootTypecheckScript(sections),
357
362
  lint: "biome check .",
358
363
  "lint:fix": "biome check --write .",
@@ -361,31 +366,34 @@ function buildChildRootScripts(sections) {
361
366
  changeset: "changeset",
362
367
  version: "changeset version",
363
368
  release: "echo 'Packages versioned - app release handled by workflow'",
364
- postinstall: "node_modules/.bin/bos types gen || true",
365
- "types:gen": "node_modules/.bin/bos types gen",
366
- bos: "node_modules/.bin/bos"
369
+ postinstall: "node node_modules/.bin/bos types gen || true",
370
+ "types:gen": "node node_modules/.bin/bos types gen",
371
+ bos: "bos"
367
372
  };
368
373
  if (sections.api) {
369
374
  scripts["db:push"] = "bun run --cwd api drizzle-kit push";
370
- scripts["db:studio"] = "node_modules/.bin/bos db:studio";
371
- scripts["db:doctor"] = "node_modules/.bin/bos db:doctor";
372
- scripts["db:repair"] = "node_modules/.bin/bos db:repair";
375
+ scripts["db:studio"] = "bos db:studio";
376
+ scripts["db:doctor"] = "bos db:doctor";
377
+ scripts["db:repair"] = "bos db:repair";
373
378
  scripts["db:generate"] = "bun run --cwd api drizzle-kit generate";
374
379
  scripts["db:migrate"] = "bun run --cwd api drizzle-kit migrate";
375
- scripts["test:api"] = "cd api && bun run test tests/integration/ tests/unit/";
376
- scripts["test:integration"] = "cd api && bun run test tests/integration/";
380
+ scripts["test:api"] = "bun run --cwd api test --if-present";
381
+ scripts["test:integration"] = "bun run --cwd api test tests/integration/ --if-present";
377
382
  }
378
- if (sections.host) scripts["test:e2e"] = "bun run --cwd host test:e2e";
379
- if (sections.api && sections.host) scripts.test = "bun run test:api && bun run test:e2e";
380
- else if (sections.api) scripts.test = "bun run test:api";
381
- else if (sections.host) scripts.test = "bun run test:e2e";
383
+ if (sections.host) scripts["test:e2e"] = "bun run --cwd host test --if-present";
384
+ const testTargets = [];
385
+ if (sections.api) testTargets.push("bun run --cwd api test --if-present");
386
+ if (sections.host) testTargets.push("bun run --cwd host test --if-present");
387
+ if (sections.ui) testTargets.push("bun run --cwd ui test --if-present");
388
+ if (sections.plugins) testTargets.push("for d in plugins/*; do [ -f \"$d/package.json\" ] && bun run --cwd \"$d\" test --if-present; done");
389
+ scripts.test = testTargets.length > 0 ? testTargets.join(" && ") : "echo \"No workspace directories configured\"";
382
390
  if (sections.api || sections.host) {
383
391
  scripts["dev:postgres"] = "docker compose up -d --wait && bun run dev";
384
392
  scripts["dev:postgres:down"] = "docker compose down";
385
393
  scripts["dev:postgres:reset"] = "docker compose down -v && docker compose up -d --wait";
386
394
  }
387
- if (sections.ui) scripts["dev:ui"] = "node_modules/.bin/bos dev --ui local --api remote";
388
- if (sections.api) scripts["dev:api"] = "node_modules/.bin/bos dev --ui remote --api local";
395
+ if (sections.ui) scripts["dev:ui"] = "bos dev --ui local --api remote";
396
+ if (sections.api) scripts["dev:api"] = "bos dev --ui remote --api local";
389
397
  return scripts;
390
398
  }
391
399
  async function personalizeConfig(destination, opts) {
@@ -471,6 +479,12 @@ async function personalizeConfig(destination, opts) {
471
479
  }
472
480
  await require_save_config.saveBosConfig(destination, config);
473
481
  }
482
+ for (const relPath of ["ui/src/lib/api-types.gen.ts", "api/src/lib/plugins-types.gen.ts"]) {
483
+ const absolutePath = (0, node_path.join)(destination, relPath);
484
+ try {
485
+ (0, node_fs.rmSync)(absolutePath, { force: true });
486
+ } catch {}
487
+ }
474
488
  const pkgPath = (0, node_path.join)(destination, "package.json");
475
489
  if ((0, node_fs.existsSync)(pkgPath)) {
476
490
  const pkg = JSON.parse((0, node_fs.readFileSync)(pkgPath, "utf-8"));
@@ -505,6 +519,7 @@ async function personalizeConfig(destination, opts) {
505
519
  const scripts = pkg.scripts;
506
520
  for (const [key, value] of Object.entries(childScripts)) scripts[key] = value;
507
521
  for (const obsoleteScript of [
522
+ ...getParentOnlyScriptKeys(destination),
508
523
  "init",
509
524
  "sync-catalog",
510
525
  "db:push",
@@ -613,9 +628,9 @@ export type InferOutput<_TRoute extends string> = any;
613
628
  if (has("plugins")) for (const plugin of opts.plugins ?? []) {
614
629
  const pluginSrcDir = (0, node_path.join)(destination, "plugins", plugin, "src");
615
630
  const pluginIndexPath = (0, node_path.join)(pluginSrcDir, "index.ts");
616
- const pluginClientGenPath = (0, node_path.join)(pluginSrcDir, "plugins-client.gen.ts");
631
+ const pluginClientGenPath = (0, node_path.join)(pluginSrcDir, "lib", "plugins-client.gen.ts");
617
632
  if (!(0, node_fs.existsSync)(pluginIndexPath) || (0, node_fs.existsSync)(pluginClientGenPath)) continue;
618
- if (!(0, node_fs.readFileSync)(pluginIndexPath, "utf-8").includes("./plugins-client.gen")) continue;
633
+ if (!(0, node_fs.readFileSync)(pluginIndexPath, "utf-8").includes("./lib/plugins-client.gen")) continue;
619
634
  (0, node_fs.writeFileSync)(pluginClientGenPath, "export type PluginsClient = Record<string, never>;\n");
620
635
  }
621
636
  }
@@ -664,15 +679,22 @@ function toRelativeImportPath(fromPath, toPath) {
664
679
  const rel = (0, node_path.relative)((0, node_path.dirname)(toPath), fromPath);
665
680
  return rel.startsWith(".") ? rel : `./${rel}`;
666
681
  }
667
- async function runBunInstall(destination, spinner) {
668
- await runWithProgress("bun", ["install", "--ignore-scripts"], destination, spinner, "Installing dependencies");
682
+ async function runBunInstall(destination, opts) {
683
+ await runWithProgress("bun", ["install", "--ignore-scripts"], destination, opts?.spinner, "Installing dependencies");
669
684
  }
670
- async function runBunInstallForUpgrade(destination, spinner) {
671
- await runWithProgress("bun", ["install", "--force"], destination, spinner, "Installing dependencies");
685
+ async function runBunInstallForUpgrade(destination, opts) {
686
+ await runWithProgress("bun", ["install", "--force"], destination, opts?.spinner, "Installing dependencies");
672
687
  }
673
- async function runTypesGen(destination, spinner) {
674
- if ((0, node_fs.existsSync)((0, node_path.join)(destination, "node_modules", ".bin", "bos"))) {
675
- await runWithProgress("node_modules/.bin/bos", ["types", "gen"], destination, spinner, "Generating types");
688
+ async function runTypesGen(destination, opts) {
689
+ const bosModule = (0, node_path.join)(destination, "node_modules", "everything-dev", "dist", "cli.mjs");
690
+ if ((0, node_fs.existsSync)(bosModule)) {
691
+ const args = [
692
+ bosModule,
693
+ "types",
694
+ "gen"
695
+ ];
696
+ if (opts?.remotePlugins && opts.remotePlugins.length > 0) args.push("--remote-plugins", opts.remotePlugins.join(","));
697
+ await runWithProgress(process.execPath, args, destination, opts?.spinner, "Generating types");
676
698
  return;
677
699
  }
678
700
  throw new Error("Unable to locate bos CLI for types generation");
@@ -689,7 +711,11 @@ async function runWithProgress(command, args, cwd, spinner, label) {
689
711
  const child = (0, execa.execa)(command, args, {
690
712
  cwd,
691
713
  stdio: "inherit",
692
- timeout: COMMAND_TIMEOUTS[command] ?? 2 * 6e4
714
+ timeout: COMMAND_TIMEOUTS[command] ?? 2 * 6e4,
715
+ env: {
716
+ ...process.env,
717
+ BOS_NO_BANNER: "1"
718
+ }
693
719
  });
694
720
  if (spinner) {
695
721
  const start = Date.now();
@@ -731,9 +757,6 @@ function removeInitLockfile(lockfilePath) {
731
757
  if (!(0, node_fs.existsSync)(lockfilePath)) return;
732
758
  (0, node_fs.rmSync)(lockfilePath, { force: true });
733
759
  }
734
- function readJsonFile(filePath) {
735
- return JSON.parse((0, node_fs.readFileSync)(filePath, "utf-8"));
736
- }
737
760
  async function scaffoldMinimalProject(destination, parentConfig, opts) {
738
761
  (0, node_fs.mkdirSync)(destination, { recursive: true });
739
762
  const has = (section) => opts.overrides.includes(section);
@@ -1068,6 +1091,7 @@ exports.execCommand = execCommand;
1068
1091
  exports.extractSkillsBlock = extractSkillsBlock;
1069
1092
  exports.fetchParentConfig = fetchParentConfig;
1070
1093
  exports.generateDatabaseMigrations = generateDatabaseMigrations;
1094
+ exports.getParentOnlyScriptKeys = getParentOnlyScriptKeys;
1071
1095
  exports.personalizeAgentsMd = personalizeAgentsMd;
1072
1096
  exports.personalizeConfig = personalizeConfig;
1073
1097
  exports.removeInitLockfile = removeInitLockfile;