@capxul/sandbox 1.0.0-alpha.3 → 1.1.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.
- package/README.md +9 -3
- package/dist/main.mjs +122 -294
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,10 +15,16 @@ npx @capxul/sandbox faucet send
|
|
|
15
15
|
vp run --filter @capxul/sandbox dev -- --help
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
`key create` uses the public quickstart endpoint and
|
|
18
|
+
`key create` uses the Little Sandpiper public staging quickstart endpoint and
|
|
19
|
+
needs no operator secret. `APP_ENV` does not retarget this public command; use
|
|
20
|
+
the returned key only with the staging tuple.
|
|
19
21
|
`faucet send` invokes the canonical Convex deployment and requires operator
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
access in the process environment: inside the infrastructure repository, run
|
|
23
|
+
it through the one env entry point
|
|
24
|
+
(`vp exec bash scripts/capxul-env.sh npx @capxul/sandbox faucet send`); outside
|
|
25
|
+
it, supply the deploy key directly
|
|
26
|
+
(`CONVEX_DEPLOY_KEY=… npx @capxul/sandbox faucet send`). Never print or commit
|
|
27
|
+
those values.
|
|
22
28
|
|
|
23
29
|
## Verify in the repository
|
|
24
30
|
|
package/dist/main.mjs
CHANGED
|
@@ -1,307 +1,134 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
import { cancel, confirm, intro, isCancel, note, outro, select, spinner, text } from "@clack/prompts";
|
|
4
3
|
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
4
|
import { homedir, tmpdir } from "node:os";
|
|
7
|
-
import { resolve } from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
+
import path, { resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
7
|
import { readFile, writeFile } from "node:fs/promises";
|
|
10
|
-
//#region
|
|
11
|
-
var
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
""
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
function parse(src) {
|
|
51
|
-
const obj = {};
|
|
52
|
-
let lines = src.toString();
|
|
53
|
-
lines = lines.replace(/\r\n?/gm, "\n");
|
|
54
|
-
let match;
|
|
55
|
-
while ((match = LINE.exec(lines)) != null) {
|
|
56
|
-
const key = match[1];
|
|
57
|
-
let value = match[2] || "";
|
|
58
|
-
value = value.trim();
|
|
59
|
-
const maybeQuote = value[0];
|
|
60
|
-
value = value.replace(/^(['"`])([\s\S]*)\1$/gm, "$2");
|
|
61
|
-
if (maybeQuote === "\"") {
|
|
62
|
-
value = value.replace(/\\n/g, "\n");
|
|
63
|
-
value = value.replace(/\\r/g, "\r");
|
|
64
|
-
}
|
|
65
|
-
obj[key] = value;
|
|
66
|
-
}
|
|
67
|
-
return obj;
|
|
68
|
-
}
|
|
69
|
-
function _parseVault(options) {
|
|
70
|
-
options = options || {};
|
|
71
|
-
const vaultPath = _vaultPath(options);
|
|
72
|
-
options.path = vaultPath;
|
|
73
|
-
const result = DotenvModule.configDotenv(options);
|
|
74
|
-
if (!result.parsed) {
|
|
75
|
-
const err = /* @__PURE__ */ new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
76
|
-
err.code = "MISSING_DATA";
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
79
|
-
const keys = _dotenvKey(options).split(",");
|
|
80
|
-
const length = keys.length;
|
|
81
|
-
let decrypted;
|
|
82
|
-
for (let i = 0; i < length; i++) try {
|
|
83
|
-
const attrs = _instructions(result, keys[i].trim());
|
|
84
|
-
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
85
|
-
break;
|
|
86
|
-
} catch (error) {
|
|
87
|
-
if (i + 1 >= length) throw error;
|
|
88
|
-
}
|
|
89
|
-
return DotenvModule.parse(decrypted);
|
|
90
|
-
}
|
|
91
|
-
function _warn(message) {
|
|
92
|
-
console.error(`⚠ ${message}`);
|
|
93
|
-
}
|
|
94
|
-
function _debug(message) {
|
|
95
|
-
console.log(`┆ ${message}`);
|
|
96
|
-
}
|
|
97
|
-
function _log(message) {
|
|
98
|
-
console.log(`◇ ${message}`);
|
|
99
|
-
}
|
|
100
|
-
function _dotenvKey(options) {
|
|
101
|
-
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) return options.DOTENV_KEY;
|
|
102
|
-
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) return process.env.DOTENV_KEY;
|
|
103
|
-
return "";
|
|
104
|
-
}
|
|
105
|
-
function _instructions(result, dotenvKey) {
|
|
106
|
-
let uri;
|
|
107
|
-
try {
|
|
108
|
-
uri = new URL(dotenvKey);
|
|
109
|
-
} catch (error) {
|
|
110
|
-
if (error.code === "ERR_INVALID_URL") {
|
|
111
|
-
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
|
|
112
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
113
|
-
throw err;
|
|
114
|
-
}
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
const key = uri.password;
|
|
118
|
-
if (!key) {
|
|
119
|
-
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Missing key part");
|
|
120
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
121
|
-
throw err;
|
|
122
|
-
}
|
|
123
|
-
const environment = uri.searchParams.get("environment");
|
|
124
|
-
if (!environment) {
|
|
125
|
-
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: Missing environment part");
|
|
126
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
127
|
-
throw err;
|
|
128
|
-
}
|
|
129
|
-
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
130
|
-
const ciphertext = result.parsed[environmentKey];
|
|
131
|
-
if (!ciphertext) {
|
|
132
|
-
const err = /* @__PURE__ */ new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
133
|
-
err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
|
|
134
|
-
throw err;
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
ciphertext,
|
|
138
|
-
key
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
function _vaultPath(options) {
|
|
142
|
-
let possibleVaultPath = null;
|
|
143
|
-
if (options && options.path && options.path.length > 0) if (Array.isArray(options.path)) {
|
|
144
|
-
for (const filepath of options.path) if (fs.existsSync(filepath)) possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
145
|
-
} else possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
146
|
-
else possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
|
|
147
|
-
if (fs.existsSync(possibleVaultPath)) return possibleVaultPath;
|
|
148
|
-
return null;
|
|
149
|
-
}
|
|
150
|
-
function _resolveHome(envPath) {
|
|
151
|
-
return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
152
|
-
}
|
|
153
|
-
function _configVault(options) {
|
|
154
|
-
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
155
|
-
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
156
|
-
if (debug || !quiet) _log("loading env from encrypted .env.vault");
|
|
157
|
-
const parsed = DotenvModule._parseVault(options);
|
|
158
|
-
let processEnv = process.env;
|
|
159
|
-
if (options && options.processEnv != null) processEnv = options.processEnv;
|
|
160
|
-
DotenvModule.populate(processEnv, parsed, options);
|
|
161
|
-
return { parsed };
|
|
162
|
-
}
|
|
163
|
-
function configDotenv(options) {
|
|
164
|
-
const dotenvPath = path.resolve(process.cwd(), ".env");
|
|
165
|
-
let encoding = "utf8";
|
|
166
|
-
let processEnv = process.env;
|
|
167
|
-
if (options && options.processEnv != null) processEnv = options.processEnv;
|
|
168
|
-
let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
169
|
-
let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
170
|
-
if (options && options.encoding) encoding = options.encoding;
|
|
171
|
-
else if (debug) _debug("no encoding is specified (UTF-8 is used by default)");
|
|
172
|
-
let optionPaths = [dotenvPath];
|
|
173
|
-
if (options && options.path) if (!Array.isArray(options.path)) optionPaths = [_resolveHome(options.path)];
|
|
174
|
-
else {
|
|
175
|
-
optionPaths = [];
|
|
176
|
-
for (const filepath of options.path) optionPaths.push(_resolveHome(filepath));
|
|
177
|
-
}
|
|
178
|
-
let lastError;
|
|
179
|
-
const parsedAll = {};
|
|
180
|
-
for (const path of optionPaths) try {
|
|
181
|
-
const parsed = DotenvModule.parse(fs.readFileSync(path, { encoding }));
|
|
182
|
-
DotenvModule.populate(parsedAll, parsed, options);
|
|
183
|
-
} catch (e) {
|
|
184
|
-
if (debug) _debug(`failed to load ${path} ${e.message}`);
|
|
185
|
-
lastError = e;
|
|
186
|
-
}
|
|
187
|
-
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
188
|
-
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
189
|
-
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
190
|
-
if (debug || !quiet) {
|
|
191
|
-
const keysCount = Object.keys(populated).length;
|
|
192
|
-
const shortPaths = [];
|
|
193
|
-
for (const filePath of optionPaths) try {
|
|
194
|
-
const relative = path.relative(process.cwd(), filePath);
|
|
195
|
-
shortPaths.push(relative);
|
|
196
|
-
} catch (e) {
|
|
197
|
-
if (debug) _debug(`failed to load ${filePath} ${e.message}`);
|
|
198
|
-
lastError = e;
|
|
199
|
-
}
|
|
200
|
-
_log(`injected env (${keysCount}) from ${shortPaths.join(",")} ${dim(`// tip: ${_getRandomTip()}`)}`);
|
|
201
|
-
}
|
|
202
|
-
if (lastError) return {
|
|
203
|
-
parsed: parsedAll,
|
|
204
|
-
error: lastError
|
|
205
|
-
};
|
|
206
|
-
else return { parsed: parsedAll };
|
|
207
|
-
}
|
|
208
|
-
function config(options) {
|
|
209
|
-
if (_dotenvKey(options).length === 0) return DotenvModule.configDotenv(options);
|
|
210
|
-
const vaultPath = _vaultPath(options);
|
|
211
|
-
if (!vaultPath) {
|
|
212
|
-
_warn(`you set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}`);
|
|
213
|
-
return DotenvModule.configDotenv(options);
|
|
8
|
+
//#region ../../config/deployment-topology.json
|
|
9
|
+
var deployment_topology_default = {
|
|
10
|
+
schemaVersion: 1,
|
|
11
|
+
product: "capxul-platform",
|
|
12
|
+
environments: {
|
|
13
|
+
"development": {
|
|
14
|
+
"appEnv": "development",
|
|
15
|
+
"productRole": "development",
|
|
16
|
+
"convexProject": "capxul-platform-dev",
|
|
17
|
+
"deployment": "dev:incredible-possum-990",
|
|
18
|
+
"deploymentReference": "dev/aaron-griffith",
|
|
19
|
+
"name": "incredible-possum-990",
|
|
20
|
+
"convexType": "dev",
|
|
21
|
+
"convexUrl": "https://incredible-possum-990.convex.cloud",
|
|
22
|
+
"convexSiteUrl": "https://incredible-possum-990.convex.site",
|
|
23
|
+
"chainId": 84532,
|
|
24
|
+
"expectedFingerprints": {
|
|
25
|
+
"applicationId": "a4b0c7e61ec45aec",
|
|
26
|
+
"openfortPublishableKey": "aab42edbcdc683e7",
|
|
27
|
+
"shieldPublishableKey": "a323489c9a8d8da2"
|
|
28
|
+
},
|
|
29
|
+
"proofCommand": "APP_ENV=development vp exec bash scripts/capxul-env.sh vp exec node scripts/prove-deployment-bootstrap.mjs"
|
|
30
|
+
},
|
|
31
|
+
"staging": {
|
|
32
|
+
"appEnv": "staging",
|
|
33
|
+
"productRole": "staging",
|
|
34
|
+
"convexProject": "capxul-platform-effect",
|
|
35
|
+
"deployment": "prod:little-sandpiper-974",
|
|
36
|
+
"deploymentReference": "production",
|
|
37
|
+
"name": "little-sandpiper-974",
|
|
38
|
+
"convexType": "prod",
|
|
39
|
+
"convexUrl": "https://little-sandpiper-974.convex.cloud",
|
|
40
|
+
"convexSiteUrl": "https://little-sandpiper-974.convex.site",
|
|
41
|
+
"chainId": 84532,
|
|
42
|
+
"expectedFingerprints": {
|
|
43
|
+
"applicationId": "4e2989ea295fc269",
|
|
44
|
+
"openfortPublishableKey": "aab42edbcdc683e7",
|
|
45
|
+
"shieldPublishableKey": "a323489c9a8d8da2"
|
|
46
|
+
},
|
|
47
|
+
"proofCommand": "APP_ENV=staging vp exec bash scripts/capxul-env.sh vp exec node scripts/prove-deployment-bootstrap.mjs"
|
|
214
48
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
49
|
+
},
|
|
50
|
+
production: null
|
|
51
|
+
};
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region ../../scripts/deployment-topology.mjs
|
|
54
|
+
const SUPPORTED_APP_ENVS = ["development", "staging"];
|
|
55
|
+
function loadDeploymentTopology() {
|
|
56
|
+
const topology = structuredClone(deployment_topology_default);
|
|
57
|
+
const environmentNames = Object.keys(topology.environments ?? {}).sort();
|
|
58
|
+
if (topology.schemaVersion !== 1 || topology.product !== "capxul-platform" || topology.production !== null || JSON.stringify(environmentNames) !== JSON.stringify(SUPPORTED_APP_ENVS)) throw new Error("Invalid Capxul deployment topology: expected development + staging and no production tier");
|
|
59
|
+
return topology;
|
|
60
|
+
}
|
|
61
|
+
function resolveDeploymentTier(appEnv) {
|
|
62
|
+
const tier = loadDeploymentTopology().environments[appEnv];
|
|
63
|
+
if (tier === void 0) throw new Error(`Unsupported APP_ENV '${String(appEnv)}'; expected development or staging (production is not provisioned)`);
|
|
64
|
+
return tier;
|
|
65
|
+
}
|
|
66
|
+
function assertDeploymentTuple(appEnv, values) {
|
|
67
|
+
const tier = resolveDeploymentTier(appEnv);
|
|
68
|
+
const expected = {
|
|
69
|
+
CONVEX_DEPLOYMENT: tier.deployment,
|
|
70
|
+
CONVEX_URL: tier.convexUrl,
|
|
71
|
+
CONVEX_SITE_URL: tier.convexSiteUrl
|
|
72
|
+
};
|
|
73
|
+
const mismatches = Object.entries(expected).flatMap(([name, expectedValue]) => {
|
|
74
|
+
const actual = values[name]?.trim();
|
|
75
|
+
return actual === expectedValue ? [] : [`${name}: expected '${expectedValue}', received '${actual ?? "<missing>"}'`];
|
|
76
|
+
});
|
|
77
|
+
const deployKey = values.CONVEX_DEPLOY_KEY?.trim();
|
|
78
|
+
if (deployKey !== void 0 && deployKey.length > 0) {
|
|
79
|
+
const separatorIndex = deployKey.indexOf("|");
|
|
80
|
+
const candidateDeployment = separatorIndex > 0 && deployKey.slice(separatorIndex + 1).trim().length > 0 ? deployKey.slice(0, separatorIndex) : "";
|
|
81
|
+
const keyDeployment = /^(?:dev|prod):[a-z0-9-]+$/u.test(candidateDeployment) ? candidateDeployment : void 0;
|
|
82
|
+
if (keyDeployment !== tier.deployment) mismatches.push(`CONVEX_DEPLOY_KEY: expected a key bound to '${tier.deployment}', received a key for '${keyDeployment ?? "<invalid>"}'`);
|
|
83
|
+
}
|
|
84
|
+
for (const optionalUrlName of ["CAPXUL_E2E_BOOTSTRAP_URL", "CAPXUL_E2E_BOOTSTRAP_BASE_URL"]) {
|
|
85
|
+
const raw = values[optionalUrlName]?.trim();
|
|
86
|
+
if (raw === void 0 || raw.length === 0) continue;
|
|
87
|
+
let actualHost;
|
|
223
88
|
try {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const isRange = error instanceof RangeError;
|
|
229
|
-
const invalidKeyLength = error.message === "Invalid key length";
|
|
230
|
-
const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
|
|
231
|
-
if (isRange || invalidKeyLength) {
|
|
232
|
-
const err = /* @__PURE__ */ new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
|
|
233
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
234
|
-
throw err;
|
|
235
|
-
} else if (decryptionFailed) {
|
|
236
|
-
const err = /* @__PURE__ */ new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
|
|
237
|
-
err.code = "DECRYPTION_FAILED";
|
|
238
|
-
throw err;
|
|
239
|
-
} else throw error;
|
|
89
|
+
actualHost = new URL(raw).host;
|
|
90
|
+
} catch {
|
|
91
|
+
mismatches.push(`${optionalUrlName}: expected a URL for ${tier.name}, received an invalid URL`);
|
|
92
|
+
continue;
|
|
240
93
|
}
|
|
94
|
+
if (actualHost !== new URL(tier.convexSiteUrl).host) mismatches.push(`${optionalUrlName}: expected host '${new URL(tier.convexSiteUrl).host}', received '${actualHost}'`);
|
|
241
95
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
populate
|
|
272
|
-
};
|
|
273
|
-
module.exports.configDotenv = DotenvModule.configDotenv;
|
|
274
|
-
module.exports._configVault = DotenvModule._configVault;
|
|
275
|
-
module.exports._parseVault = DotenvModule._parseVault;
|
|
276
|
-
module.exports.config = DotenvModule.config;
|
|
277
|
-
module.exports.decrypt = DotenvModule.decrypt;
|
|
278
|
-
module.exports.parse = DotenvModule.parse;
|
|
279
|
-
module.exports.populate = DotenvModule.populate;
|
|
280
|
-
module.exports = DotenvModule;
|
|
281
|
-
})))();
|
|
96
|
+
if (mismatches.length > 0) throw new Error(`Deployment tuple mismatch: expected ${appEnv} '${tier.name}'. Refusing to continue.\n${mismatches.join("\n")}`);
|
|
97
|
+
return tier;
|
|
98
|
+
}
|
|
99
|
+
function runCli() {
|
|
100
|
+
const command = process.argv[2] ?? "check";
|
|
101
|
+
const appEnvIndex = process.argv.indexOf("--app-env");
|
|
102
|
+
const appEnv = appEnvIndex === -1 ? process.env.APP_ENV ?? "development" : process.argv[appEnvIndex + 1];
|
|
103
|
+
if (command === "print") {
|
|
104
|
+
process.stdout.write(`${JSON.stringify(resolveDeploymentTier(appEnv), null, 2)}\n`);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (command === "field") {
|
|
108
|
+
const fieldIndex = process.argv.indexOf("--field");
|
|
109
|
+
const field = fieldIndex === -1 ? void 0 : process.argv[fieldIndex + 1];
|
|
110
|
+
const tier = resolveDeploymentTier(appEnv);
|
|
111
|
+
if (field === void 0 || !Object.hasOwn(tier, field)) throw new Error(`Unknown deployment topology field '${String(field)}'`);
|
|
112
|
+
process.stdout.write(`${String(tier[field])}\n`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (command !== "check") throw new Error(`Unknown deployment-topology command '${command}'; expected check, print, or field`);
|
|
116
|
+
const tier = assertDeploymentTuple(appEnv, process.env);
|
|
117
|
+
process.stdout.write(`Capxul ${tier.productRole} tuple verified: ${tier.deployment} (${tier.convexProject})\n`);
|
|
118
|
+
}
|
|
119
|
+
if (process.argv[1] !== void 0 && path.basename(process.argv[1]) === "deployment-topology.mjs" && import.meta.url === pathToFileURL(process.argv[1]).href) try {
|
|
120
|
+
runCli();
|
|
121
|
+
} catch (error) {
|
|
122
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
}
|
|
282
125
|
resolve(resolve(fileURLToPath(new URL("..", import.meta.url))), "..", "..");
|
|
283
|
-
const
|
|
126
|
+
const standingStaging = resolveDeploymentTier("staging");
|
|
284
127
|
const CANONICAL_QUICKSTART_DEPLOYMENT = {
|
|
285
|
-
deployment:
|
|
286
|
-
convexUrl:
|
|
287
|
-
convexSiteUrl:
|
|
128
|
+
deployment: standingStaging.deployment,
|
|
129
|
+
convexUrl: standingStaging.convexUrl,
|
|
130
|
+
convexSiteUrl: standingStaging.convexSiteUrl
|
|
288
131
|
};
|
|
289
|
-
function loadOperatorEnv(sourceEnv = process.env) {
|
|
290
|
-
const env = { ...sourceEnv };
|
|
291
|
-
const envFiles = [defaultOperatorSecretsPath, env.CAPXUL_SECRETS_FILE ? resolve(env.CAPXUL_SECRETS_FILE) : void 0].filter((path) => path !== void 0);
|
|
292
|
-
for (const path of envFiles) if (existsSync(path)) {
|
|
293
|
-
const parsed = (0, import_main.config)({
|
|
294
|
-
path,
|
|
295
|
-
processEnv: env,
|
|
296
|
-
override: true
|
|
297
|
-
});
|
|
298
|
-
if (parsed.error !== void 0) throw parsed.error;
|
|
299
|
-
}
|
|
300
|
-
env.CONVEX_URL ||= env.CAPXUL_E2E_CONVEX_URL;
|
|
301
|
-
env.CONVEX_SITE_URL ||= env.CAPXUL_E2E_CONVEX_SITE_URL;
|
|
302
|
-
env.CAPXUL_E2E_ORIGIN ||= env.CAPXUL_E2E_BOOTSTRAP_URL || env.SITE_URL;
|
|
303
|
-
return env;
|
|
304
|
-
}
|
|
305
132
|
function normalizeHttpOrigin(raw) {
|
|
306
133
|
const value = raw.trim();
|
|
307
134
|
if (value.length === 0) throw new Error("Origin is required.");
|
|
@@ -513,7 +340,7 @@ async function runFaucetSend() {
|
|
|
513
340
|
const s = spinner();
|
|
514
341
|
s.start("Minting testnet funds");
|
|
515
342
|
try {
|
|
516
|
-
const runConvex = canonicalConvexRunner(
|
|
343
|
+
const runConvex = canonicalConvexRunner();
|
|
517
344
|
const result = recipient.kind === "address" ? runConvex("", [
|
|
518
345
|
"run",
|
|
519
346
|
FAUCET_ADDRESS_FUNCTION,
|
|
@@ -545,7 +372,8 @@ async function runFaucetSend() {
|
|
|
545
372
|
return 0;
|
|
546
373
|
} catch (error) {
|
|
547
374
|
s.stop("Faucet send failed");
|
|
548
|
-
|
|
375
|
+
const hint = process.env.CONVEX_DEPLOY_KEY === void 0 ? "\nHint: CONVEX_DEPLOY_KEY is not set — the faucet needs operator access to the canonical deployment. Supply it (CONVEX_DEPLOY_KEY=… npx @capxul/sandbox faucet send) or, inside the infrastructure repo, run through scripts/capxul-env.sh." : "";
|
|
376
|
+
cancel(errorMessage(error) + hint);
|
|
549
377
|
return 1;
|
|
550
378
|
}
|
|
551
379
|
}
|