{"version":3,"file":"materializer.mjs","names":[],"sources":["../../src/infra/materializer.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { Context, Effect, Layer } from \"effect\";\nimport { getSecretGroups, renderEnvFile, renderEnvTestFile } from \"../cli/infra\";\nimport type { RuntimeConfig } from \"../types\";\nimport { InfraError, type InfraPhase } from \"./types\";\n\nexport interface InfraMaterializerShape {\n  readonly materializeTemplate: (\n    configDir: string,\n    runtimeConfig: RuntimeConfig,\n  ) => Effect.Effect<void, InfraError>;\n  readonly materializeTestInfra: (\n    configDir: string,\n    runtimeConfig: RuntimeConfig,\n  ) => Effect.Effect<void, InfraError>;\n}\n\nexport class InfraMaterializer extends Context.Service<InfraMaterializer, InfraMaterializerShape>()(\n  \"InfraMaterializer\",\n) {}\n\nfunction ensureDir(filePath: string): void {\n  mkdirSync(dirname(filePath), { recursive: true });\n}\n\nfunction writeIfChanged(filePath: string, content: string): void {\n  if (existsSync(filePath) && readFileSync(filePath, \"utf-8\") === content) return;\n  ensureDir(filePath);\n  writeFileSync(filePath, content);\n}\n\nfunction writeContentEffect(\n  filePath: string,\n  content: string,\n  phase: InfraPhase,\n): Effect.Effect<void, InfraError> {\n  return Effect.try({\n    try: () => writeIfChanged(filePath, content),\n    catch: (cause) =>\n      new InfraError({\n        phase,\n        message: `failed to write ${filePath}`,\n        cause,\n      }),\n  });\n}\n\nconst makeMaterializer = (): InfraMaterializerShape => ({\n  materializeTemplate: (configDir, runtimeConfig) =>\n    Effect.gen(function* () {\n      const groups = getSecretGroups(runtimeConfig);\n      const filePath = join(configDir, \".env.example\");\n      const content = renderEnvFile(groups, { forExample: true });\n      yield* writeContentEffect(filePath, content, \"materialize-env\");\n    }),\n\n  materializeTestInfra: (configDir, runtimeConfig) =>\n    Effect.gen(function* () {\n      const groups = getSecretGroups(runtimeConfig);\n      const filePath = join(configDir, \".env.test\");\n      const content = renderEnvTestFile(groups);\n      yield* writeContentEffect(filePath, content, \"materialize-env\");\n    }),\n});\n\nexport const InfraMaterializerLive: Layer.Layer<InfraMaterializer> = Layer.succeed(\n  InfraMaterializer,\n  makeMaterializer(),\n);\n\n/**\n * Single source of truth for \"should we persist port state?\".\n * Lives next to the only thing that still persists dev ports (the planner).\n */\nexport function shouldPersistPortState(): boolean {\n  return (\n    process.env.BOS_NO_PERSIST_PORTS !== \"1\" &&\n    process.env.BOS_TEST !== \"1\" &&\n    process.env.NODE_ENV !== \"test\"\n  );\n}\n\n/**\n * True when the run is ephemeral (regression, CI, local test).\n * In ephemeral mode we must not rewrite `.env.example` because it's a\n * committed reference template — every regression run was previously\n * baking the resolved regression port into it, polluting the file.\n */\nexport function isEphemeralRun(): boolean {\n  return (\n    process.env.BOS_NO_PERSIST_PORTS === \"1\" ||\n    process.env.BOS_TEST === \"1\" ||\n    process.env.NODE_ENV === \"test\"\n  );\n}\n\nexport interface MaterializeOptions {\n  /**\n   * Override the ephemeral detection. Defaults to the env-var check.\n   * Pass `ephemeral: false` to force-template-write, e.g. for opt-in\n   * smoke runs that explicitly want a deterministic template.\n   */\n  readonly ephemeral?: boolean;\n}\n\n/**\n * Orchestration helper: materializes the env templates in the canonical\n * order. docker-compose.yml is a static committed file — nothing here\n * generates or rewrites it.\n */\nexport async function materializeViaLayer(\n  configDir: string,\n  runtimeConfig: RuntimeConfig,\n  options: MaterializeOptions = {},\n): Promise<void> {\n  const ephemeral = options.ephemeral ?? isEphemeralRun();\n  await Effect.runPromise(\n    Effect.gen(function* () {\n      const m = yield* InfraMaterializer;\n      if (!ephemeral) {\n        yield* m.materializeTemplate(configDir, runtimeConfig);\n      }\n      yield* m.materializeTestInfra(configDir, runtimeConfig);\n    }).pipe(Effect.provide(InfraMaterializerLive)),\n  );\n}\n"],"mappings":";;;;;;;AAkBA,IAAa,oBAAb,cAAuC,QAAQ,QAAmD,CAAC,CACjG,mBACF,CAAC,CAAC,CAAC;AAEH,SAAS,UAAU,UAAwB;CACzC,UAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD;AAEA,SAAS,eAAe,UAAkB,SAAuB;CAC/D,IAAI,WAAW,QAAQ,KAAK,aAAa,UAAU,OAAO,MAAM,SAAS;CACzE,UAAU,QAAQ;CAClB,cAAc,UAAU,OAAO;AACjC;AAEA,SAAS,mBACP,UACA,SACA,OACiC;CACjC,OAAO,OAAO,IAAI;EAChB,WAAW,eAAe,UAAU,OAAO;EAC3C,QAAQ,UACN,IAAI,WAAW;GACb;GACA,SAAS,mBAAmB;GAC5B;EACF,CAAC;CACL,CAAC;AACH;AAEA,MAAM,0BAAkD;CACtD,sBAAsB,WAAW,kBAC/B,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,gBAAgB,aAAa;EAG5C,OAAO,mBAFU,KAAK,WAAW,cAEA,GADjB,cAAc,QAAQ,EAAE,YAAY,KAAK,CACf,GAAG,iBAAiB;CAChE,CAAC;CAEH,uBAAuB,WAAW,kBAChC,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,gBAAgB,aAAa;EAG5C,OAAO,mBAFU,KAAK,WAAW,WAEA,GADjB,kBAAkB,MACQ,GAAG,iBAAiB;CAChE,CAAC;AACL;AAEA,MAAa,wBAAwD,MAAM,QACzE,mBACA,iBAAiB,CACnB;;;;;AAMA,SAAgB,yBAAkC;CAChD,OACE,QAAQ,IAAI,yBAAyB,OACrC,QAAQ,IAAI,aAAa,OACzB,QAAQ,IAAI,aAAa;AAE7B;;;;;;;AAQA,SAAgB,iBAA0B;CACxC,OACE,QAAQ,IAAI,yBAAyB,OACrC,QAAQ,IAAI,aAAa,OACzB,QAAQ,IAAI,aAAa;AAE7B;;;;;;AAgBA,eAAsB,oBACpB,WACA,eACA,UAA8B,CAAC,GAChB;CACf,MAAM,YAAY,QAAQ,aAAa,eAAe;CACtD,MAAM,OAAO,WACX,OAAO,IAAI,aAAa;EACtB,MAAM,IAAI,OAAO;EACjB,IAAI,CAAC,WACH,OAAO,EAAE,oBAAoB,WAAW,aAAa;EAEvD,OAAO,EAAE,qBAAqB,WAAW,aAAa;CACxD,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,qBAAqB,CAAC,CAC/C;AACF"}