@domesystems/templates 0.1.0 → 0.1.1

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.
@@ -106,6 +106,7 @@ app:
106
106
  runtime:
107
107
  pattern: chat
108
108
  language: python
109
+ image_tag: dev
109
110
  agent:
110
111
  prompt: app/instructions.md
111
112
  help: app/help.md
@@ -115,6 +115,7 @@ app:
115
115
  runtime:
116
116
  pattern: chat
117
117
  language: python
118
+ image_tag: dev
118
119
  agent:
119
120
  prompt: app/instructions.md
120
121
  help: app/help.md
@@ -122,6 +122,7 @@ app:
122
122
  runtime:
123
123
  pattern: run
124
124
  language: python
125
+ image_tag: dev
125
126
  agent:
126
127
  prompt: app/instructions.md
127
128
  help: app/help.md
@@ -2,4 +2,4 @@
2
2
 
3
3
  import { main } from "../src/cli.mjs";
4
4
 
5
- main(process.argv.slice(2));
5
+ await main(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@domesystems/templates",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Initialize and run Dome governed-agent templates.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { spawnSync } from "node:child_process";
4
+ import { spawn, spawnSync } from "node:child_process";
5
+ import readline from "node:readline/promises";
5
6
  import { readManifest } from "./manifest.mjs";
6
7
  import { imageForRuntime, renderCompose } from "./runtime.mjs";
7
8
  import { copyTemplate, initializeEnvironment } from "./template-files.mjs";
@@ -67,7 +68,11 @@ function init(slug, options, output) {
67
68
  copyTemplate(source, target);
68
69
  const environmentCreated = initializeEnvironment(target);
69
70
  output(`Initialized ${slug} in ${target}.`);
70
- if (environmentCreated) output("Created .env from .env.example. Add the required values before a live run.");
71
+ if (environmentCreated) {
72
+ output("Created .env from .env.example.");
73
+ output("When you run `up`, Dome will prompt for import credentials; they are never written to .env.");
74
+ output("After import, `up` writes DOME_TOKEN and DOME_GATEWAY_URL into .env. Add any other template-specific runtime values before starting.");
75
+ }
71
76
  return target;
72
77
  }
73
78
 
@@ -83,7 +88,117 @@ function run(command, args, cwd) {
83
88
  if (result.status !== 0) throw new Error(`${command} ${args.join(" ")} exited with status ${result.status}.`);
84
89
  }
85
90
 
86
- export function main(argv, { output = console.log, error = console.error } = {}) {
91
+ function parseFinalJSON(output, command) {
92
+ const starts = [...output.matchAll(/(?:^|\n)\{/g)].map((match) => match.index + (match[0][0] === "\n" ? 1 : 0));
93
+ for (const start of starts.reverse()) {
94
+ try {
95
+ return JSON.parse(output.slice(start));
96
+ } catch {
97
+ // The CLI may have printed an interactive prompt before its final JSON result.
98
+ }
99
+ }
100
+ throw new Error(`${command} completed without a readable JSON result.`);
101
+ }
102
+
103
+ function runJSON(command, args, cwd, { displayOutput = true } = {}) {
104
+ return new Promise((resolve, reject) => {
105
+ const child = spawn(command, args, { cwd, stdio: ["inherit", "pipe", "inherit"] });
106
+ let output = "";
107
+ child.stdout.on("data", (chunk) => {
108
+ const text = chunk.toString();
109
+ output += text;
110
+ if (displayOutput) process.stdout.write(text);
111
+ });
112
+ child.on("error", (error) => reject(new Error(`Could not run ${command}: ${error.message}`)));
113
+ child.on("close", (status) => {
114
+ if (status !== 0) {
115
+ reject(new Error(`${command} ${args.join(" ")} exited with status ${status}.`));
116
+ return;
117
+ }
118
+ try {
119
+ resolve(parseFinalJSON(output, command));
120
+ } catch (error) {
121
+ reject(error);
122
+ }
123
+ });
124
+ });
125
+ }
126
+
127
+ function replaceEnvironmentValue(source, name, value) {
128
+ const expression = new RegExp(`^${name}=.*$`, "m");
129
+ if (!expression.test(source)) throw new Error(`The runtime .env is missing ${name}.`);
130
+ return source.replace(expression, `${name}=${JSON.stringify(value)}`);
131
+ }
132
+
133
+ function undefinedEnvironmentValues(source) {
134
+ return source
135
+ .split(/\r?\n/)
136
+ .map((line) => line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(?:\s*|""|''|undefined)$/)?.[1])
137
+ .filter(Boolean);
138
+ }
139
+
140
+ async function fillRuntimeEnvironment(target, output) {
141
+ const environmentPath = path.join(target, ".env");
142
+ let environment = fs.readFileSync(environmentPath, "utf8");
143
+ const missing = undefinedEnvironmentValues(environment);
144
+ if (missing.length === 0) return;
145
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
146
+ throw new Error(`Missing runtime .env values: ${missing.join(", ")}. Set them before starting Docker.`);
147
+ }
148
+
149
+ output(`Complete ${environmentPath} before starting the app.`);
150
+ const terminal = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
151
+ const write = terminal._writeToOutput.bind(terminal);
152
+ terminal._writeToOutput = (text) => {
153
+ if (text === "\n" || text === "\r\n") write(text);
154
+ };
155
+ try {
156
+ for (const name of missing) {
157
+ const value = (await terminal.question(`Enter ${name}: `)).trim();
158
+ if (!value) throw new Error(`${name} is required to start the app.`);
159
+ environment = replaceEnvironmentValue(environment, name, value);
160
+ }
161
+ } finally {
162
+ terminal.close();
163
+ }
164
+ fs.writeFileSync(environmentPath, environment);
165
+ output(`Saved runtime values to ${environmentPath}.`);
166
+ }
167
+
168
+ function gatewayURL(response) {
169
+ return response.data?.gateway?.endpoints?.gatewayUrl ?? response.data?.gateway?.endpoints?.gateway_url;
170
+ }
171
+
172
+ async function writeRuntimeEnvironment(target, manifest, importResponse, output) {
173
+ const jobID = importResponse.data?.job?.id;
174
+ if (!jobID) throw new Error("Dome import succeeded but did not return an import job ID.");
175
+
176
+ const gatewayName = manifest.catalog?.gateway?.name;
177
+ if (!gatewayName) throw new Error("template.yaml must declare catalog.gateway.name to configure the runtime.");
178
+
179
+ const environmentPath = path.join(target, ".env");
180
+ let environment = fs.readFileSync(environmentPath, "utf8");
181
+ // Validate the local contract before consuming a one-time secret.
182
+ for (const name of ["DOME_TOKEN", "DOME_GATEWAY_URL"]) {
183
+ if (!new RegExp(`^${name}=.*$`, "m").test(environment)) throw new Error(`The runtime .env is missing ${name}.`);
184
+ }
185
+
186
+ const gateway = await runJSON("dome", ["gateways", "get", gatewayName, "--format", "json"], target, { displayOutput: false });
187
+ const url = gatewayURL(gateway);
188
+ if (!url) throw new Error(`Gateway ${gatewayName} has no runtime endpoint.`);
189
+
190
+ const revealed = await runJSON("dome", ["import", "outputs", jobID, "--format", "json"], target, { displayOutput: false });
191
+ const outputs = revealed.data?.outputs ?? {};
192
+ const tokens = Object.entries(outputs).filter(([name, value]) => name.endsWith("_token") && value);
193
+ if (tokens.length !== 1) throw new Error("The import must produce exactly one *_token output to configure DOME_TOKEN.");
194
+
195
+ environment = replaceEnvironmentValue(environment, "DOME_TOKEN", tokens[0][1]);
196
+ environment = replaceEnvironmentValue(environment, "DOME_GATEWAY_URL", url);
197
+ fs.writeFileSync(environmentPath, environment);
198
+ output(`Configured ${environmentPath} with the one-time agent token and Gateway URL.`);
199
+ }
200
+
201
+ export async function main(argv, { output = console.log, error = console.error } = {}) {
87
202
  try {
88
203
  const parsed = parseArguments(argv);
89
204
  if (parsed.help) {
@@ -102,8 +217,12 @@ export function main(argv, { output = console.log, error = console.error } = {})
102
217
  fs.writeFileSync(composePath, renderCompose(manifest.app.runtime));
103
218
  output(`Generated ${path.basename(composePath)} using ${imageForRuntime(manifest.app.runtime)}.`);
104
219
 
105
- if (options.provision) run("dome", ["import", "dome.tf"], target);
220
+ if (options.provision) {
221
+ const imported = await runJSON("dome", ["import", "dome.tf", "--format", "json"], target);
222
+ await writeRuntimeEnvironment(target, manifest, imported, output);
223
+ }
106
224
  if (options.start) {
225
+ await fillRuntimeEnvironment(target, output);
107
226
  const args = ["compose", "-f", ".dome-compose.yaml", "up"];
108
227
  if (options.detach) args.push("--detach");
109
228
  run("docker", args, target);