MCPcopy Create free account
hub / github.com/ForestHubAI/edge-agents / writeOutput

Function writeOutput

ts/workflow-cli/cli/deploy/write.ts:18–99  ·  view source on GitHub ↗
(
  spec: DeploymentSpec,
  resourceSecrets: EngineSecrets,
  cfg: DeployConfig,
  req: DeployRequirements,
  componentEnv: Record<string, string> = {},
)

Source from the content-addressed store, hash-verified

16import type { ComponentSecrets } from "./spec";
17
18type DeploymentSpec = DeploymentSchemas["DeploymentSpec"];
19
20// Resolve the operator's output dir, expanding a leading `~`. A shell expands that
21// before the CLI sees it, but the interactive prompt and a --values file bypass the
22// shell — there `~/bundle` would silently become a dir literally named "~" under the
23// cwd. Only a leading segment expands, so `./~` still addresses a real "~" directory.
24export function resolveOutputDir(dir: string): string {
25 const expanded = dir === "~" || dir.startsWith("~/") ? path.join(os.homedir(), dir.slice(1)) : dir;
26 return path.resolve(process.cwd(), expanded);
27}
28
29export async function writeOutput(
30 spec: DeploymentSpec,
31 componentSecrets: Record<string, ComponentSecrets>,
32 cfg: DeployConfig,
33 req: DeployRequirements,
34 componentEnv: Record<string, string> = {},
35): Promise<string[]> {
36 const dir = resolveOutputDir(cfg.outputDir);
37
38 if (existsSync(dir)) {
39 const contents = await fs.readdir(dir);
40 if (contents.length > 0) {
41 if (!cfg.force) {
42 process.stderr.write(`output dir not empty: ${dir} (use --force to overwrite)\n`);
43 process.exit(1);
44 }
45 // force=true: wipe + recreate so stale files (an old engine.tar, a stray
46 // .env from a prior run, ...) don't end up in the new bundle.
47 await fs.rm(dir, { recursive: true, force: true });
48 await fs.mkdir(dir, { recursive: true });
49 }
50 } else {
51 await fs.mkdir(dir, { recursive: true });
52 }
53
54 const written: string[] = [];
55 // secret=true -> mode 0o600. The secret-bearing files: engine.env carries the
56 // provider keys + web-search key, a custom component's <name>.env may carry its
57 // own, and <name>-secrets.json is the resource-credential doc. The
58 // <name>-config.json files and deployment-spec.json are secret-free by
59 // construction — safe to share.
60 const emit = async (name: string, content: string, secret = false): Promise<void> => {
61 const out = path.join(dir, name);
62 const opts = secret ? { encoding: "utf-8" as const, mode: 0o600 } : { encoding: "utf-8" as const };
63 await fs.writeFile(out, content, opts);
64 written.push(out);
65 };
66 const json = (v: unknown): string => JSON.stringify(v, null, 2) + "\n";
67
68 // One <name>-config.json per component carrying a config blob (the engine's boot
69 // file today) — bind-mounted read-only by the renderer, secret-free. The full
70 // resolved spec is the deployment record.
71 for (const c of spec.components) {
72 if (c.config !== undefined) await emit(configFileName(c.name), json(c.config));
73 }
74 // One secret doc per component that needs one, keyed by resource id — the
75 // resolver already split them so each component holds only its own credentials

Callers 2

deployCommandFunction · 0.90
write.test.tsFile · 0.90

Calls 8

configFileNameFunction · 0.90
composeYamlFunction · 0.90
envFileFunction · 0.90
secretsFileNameFunction · 0.90
readmeFunction · 0.90
jsonFunction · 0.85
writeMethod · 0.80
emitFunction · 0.70

Tested by

no test coverage detected