@primitivedotdev/cli 0.29.0 → 0.30.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.
- package/bin/run.js +6 -6
- package/dist/oclif/index.js +1036 -169
- package/dist/oclif/proxy-auto-detect.js +35 -9
- package/package.json +1 -1
package/dist/oclif/index.js
CHANGED
|
@@ -371,7 +371,7 @@ const mergeConfigs = (a, b) => {
|
|
|
371
371
|
...b
|
|
372
372
|
};
|
|
373
373
|
if (config.baseUrl?.endsWith("/")) config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);
|
|
374
|
-
config.headers = mergeHeaders(a.headers, b.headers);
|
|
374
|
+
config.headers = mergeHeaders$1(a.headers, b.headers);
|
|
375
375
|
return config;
|
|
376
376
|
};
|
|
377
377
|
const headersEntries = (headers) => {
|
|
@@ -381,7 +381,7 @@ const headersEntries = (headers) => {
|
|
|
381
381
|
});
|
|
382
382
|
return entries;
|
|
383
383
|
};
|
|
384
|
-
const mergeHeaders = (...headers) => {
|
|
384
|
+
const mergeHeaders$1 = (...headers) => {
|
|
385
385
|
const mergedHeaders = new Headers();
|
|
386
386
|
for (const header of headers) {
|
|
387
387
|
if (!header) continue;
|
|
@@ -461,7 +461,7 @@ const createClient = (config = {}) => {
|
|
|
461
461
|
..._config,
|
|
462
462
|
...options,
|
|
463
463
|
fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,
|
|
464
|
-
headers: mergeHeaders(_config.headers, options.headers),
|
|
464
|
+
headers: mergeHeaders$1(_config.headers, options.headers),
|
|
465
465
|
serializedBody: void 0
|
|
466
466
|
};
|
|
467
467
|
if (opts.security) await setAuthParams({
|
|
@@ -1604,17 +1604,18 @@ const updateFunction = (options) => (options.client ?? client).put({
|
|
|
1604
1604
|
* Sends a real test email from a Primitive-controlled sender to a
|
|
1605
1605
|
* local-part on one of the org's verified inbound domains. By
|
|
1606
1606
|
* default the recipient is a synthetic
|
|
1607
|
-
* `__primitive_function_test+<random>@<domain>` address
|
|
1608
|
-
*
|
|
1609
|
-
*
|
|
1610
|
-
*
|
|
1611
|
-
*
|
|
1612
|
-
*
|
|
1613
|
-
*
|
|
1614
|
-
*
|
|
1615
|
-
*
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1607
|
+
* `__primitive_function_test+<random>@<domain>` address on a
|
|
1608
|
+
* domain selected to route to the function. Scoped functions use
|
|
1609
|
+
* their scoped domain; fallback functions use a domain that has
|
|
1610
|
+
* no enabled domain-scoped endpoint. Pass `local_part` to
|
|
1611
|
+
* override and exercise routing logic that branches on a specific
|
|
1612
|
+
* recipient (the common pattern when one function handles multiple
|
|
1613
|
+
* inboxes like `summarize@` and `action@`). The function fires
|
|
1614
|
+
* through the normal MX delivery path, so reply / send-mail calls
|
|
1615
|
+
* from inside the handler against the inbound's `email.id` work
|
|
1616
|
+
* the same as in production. Returns immediately after the send is
|
|
1617
|
+
* queued; the invocation appears on the function's invocations
|
|
1618
|
+
* list within a few seconds.
|
|
1618
1619
|
*
|
|
1619
1620
|
* Requires that the function is currently `deployed`. Returns 422
|
|
1620
1621
|
* if the function is in `pending` or `failed` state, or if the
|
|
@@ -3274,7 +3275,7 @@ const openapiDocument = {
|
|
|
3274
3275
|
"post": {
|
|
3275
3276
|
"operationId": "testFunction",
|
|
3276
3277
|
"summary": "Send a test invocation",
|
|
3277
|
-
"description": "Sends a real test email from a Primitive-controlled sender to a\nlocal-part on one of the org's verified inbound domains. By\ndefault the recipient is a synthetic\n`__primitive_function_test+<random>@<domain>` address
|
|
3278
|
+
"description": "Sends a real test email from a Primitive-controlled sender to a\nlocal-part on one of the org's verified inbound domains. By\ndefault the recipient is a synthetic\n`__primitive_function_test+<random>@<domain>` address on a\ndomain selected to route to the function. Scoped functions use\ntheir scoped domain; fallback functions use a domain that has\nno enabled domain-scoped endpoint. Pass `local_part` to\noverride and exercise routing logic that branches on a specific\nrecipient (the common pattern when one function handles multiple\ninboxes like `summarize@` and `action@`). The function fires\nthrough the normal MX delivery path, so reply / send-mail calls\nfrom inside the handler against the inbound's `email.id` work\nthe same as in production. Returns immediately after the send is\nqueued; the invocation appears on the function's invocations\nlist within a few seconds.\n\nRequires that the function is currently `deployed`. Returns 422\nif the function is in `pending` or `failed` state, or if the\norg has no verified inbound domain to receive the test mail.\nReturns 400 if `local_part` is set to a value that does not\nmatch the local-part character set.\n",
|
|
3278
3279
|
"tags": ["Functions"],
|
|
3279
3280
|
"requestBody": {
|
|
3280
3281
|
"required": false,
|
|
@@ -7375,8 +7376,11 @@ const operationManifest = [
|
|
|
7375
7376
|
"type": "string"
|
|
7376
7377
|
},
|
|
7377
7378
|
{
|
|
7379
|
+
"default": 50,
|
|
7378
7380
|
"description": "Number of results per page",
|
|
7379
7381
|
"enum": null,
|
|
7382
|
+
"maximum": 100,
|
|
7383
|
+
"minimum": 1,
|
|
7380
7384
|
"name": "limit",
|
|
7381
7385
|
"required": false,
|
|
7382
7386
|
"type": "integer"
|
|
@@ -7645,13 +7649,17 @@ const operationManifest = [
|
|
|
7645
7649
|
"type": "string"
|
|
7646
7650
|
},
|
|
7647
7651
|
{
|
|
7652
|
+
"default": 50,
|
|
7648
7653
|
"description": "Number of results per page",
|
|
7649
7654
|
"enum": null,
|
|
7655
|
+
"maximum": 100,
|
|
7656
|
+
"minimum": 1,
|
|
7650
7657
|
"name": "limit",
|
|
7651
7658
|
"required": false,
|
|
7652
7659
|
"type": "integer"
|
|
7653
7660
|
},
|
|
7654
7661
|
{
|
|
7662
|
+
"default": "true",
|
|
7655
7663
|
"description": "Include subject/body highlight snippets when text search is active.",
|
|
7656
7664
|
"enum": ["true", "false"],
|
|
7657
7665
|
"name": "snippet",
|
|
@@ -7659,6 +7667,7 @@ const operationManifest = [
|
|
|
7659
7667
|
"type": "string"
|
|
7660
7668
|
},
|
|
7661
7669
|
{
|
|
7670
|
+
"default": "true",
|
|
7662
7671
|
"description": "Include facet counts for sender, domain, status, and attachment presence.",
|
|
7663
7672
|
"enum": ["true", "false"],
|
|
7664
7673
|
"name": "include_facets",
|
|
@@ -9114,8 +9123,11 @@ const operationManifest = [
|
|
|
9114
9123
|
"type": "string"
|
|
9115
9124
|
}],
|
|
9116
9125
|
"queryParams": [{
|
|
9126
|
+
"default": 50,
|
|
9117
9127
|
"description": "Maximum number of rows to return. Clamped to 1..200; default\n50.\n",
|
|
9118
9128
|
"enum": null,
|
|
9129
|
+
"maximum": 200,
|
|
9130
|
+
"minimum": 1,
|
|
9119
9131
|
"name": "limit",
|
|
9120
9132
|
"required": false,
|
|
9121
9133
|
"type": "integer"
|
|
@@ -9387,7 +9399,7 @@ const operationManifest = [
|
|
|
9387
9399
|
"binaryResponse": false,
|
|
9388
9400
|
"bodyRequired": false,
|
|
9389
9401
|
"command": "test-function",
|
|
9390
|
-
"description": "Sends a real test email from a Primitive-controlled sender to a\nlocal-part on one of the org's verified inbound domains. By\ndefault the recipient is a synthetic\n`__primitive_function_test+<random>@<domain>` address
|
|
9402
|
+
"description": "Sends a real test email from a Primitive-controlled sender to a\nlocal-part on one of the org's verified inbound domains. By\ndefault the recipient is a synthetic\n`__primitive_function_test+<random>@<domain>` address on a\ndomain selected to route to the function. Scoped functions use\ntheir scoped domain; fallback functions use a domain that has\nno enabled domain-scoped endpoint. Pass `local_part` to\noverride and exercise routing logic that branches on a specific\nrecipient (the common pattern when one function handles multiple\ninboxes like `summarize@` and `action@`). The function fires\nthrough the normal MX delivery path, so reply / send-mail calls\nfrom inside the handler against the inbound's `email.id` work\nthe same as in production. Returns immediately after the send is\nqueued; the invocation appears on the function's invocations\nlist within a few seconds.\n\nRequires that the function is currently `deployed`. Returns 422\nif the function is in `pending` or `failed` state, or if the\norg has no verified inbound domain to receive the test mail.\nReturns 400 if `local_part` is set to a value that does not\nmatch the local-part character set.\n",
|
|
9391
9403
|
"hasJsonBody": true,
|
|
9392
9404
|
"method": "POST",
|
|
9393
9405
|
"operationId": "testFunction",
|
|
@@ -9950,8 +9962,11 @@ const operationManifest = [
|
|
|
9950
9962
|
"type": "string"
|
|
9951
9963
|
},
|
|
9952
9964
|
{
|
|
9965
|
+
"default": 50,
|
|
9953
9966
|
"description": "Number of results per page",
|
|
9954
9967
|
"enum": null,
|
|
9968
|
+
"maximum": 100,
|
|
9969
|
+
"minimum": 1,
|
|
9955
9970
|
"name": "limit",
|
|
9956
9971
|
"required": false,
|
|
9957
9972
|
"type": "integer"
|
|
@@ -10523,8 +10538,11 @@ const operationManifest = [
|
|
|
10523
10538
|
"type": "string"
|
|
10524
10539
|
},
|
|
10525
10540
|
{
|
|
10541
|
+
"default": 50,
|
|
10526
10542
|
"description": "Number of results per page",
|
|
10527
10543
|
"enum": null,
|
|
10544
|
+
"maximum": 100,
|
|
10545
|
+
"minimum": 1,
|
|
10528
10546
|
"name": "limit",
|
|
10529
10547
|
"required": false,
|
|
10530
10548
|
"type": "integer"
|
|
@@ -10735,7 +10753,7 @@ const CREDENTIALS_FILE = "credentials.json";
|
|
|
10735
10753
|
const CREDENTIALS_LOCK_DIR = "credentials.lock";
|
|
10736
10754
|
const CREDENTIALS_LOCK_STALE_MS = 1800 * 1e3;
|
|
10737
10755
|
const MALFORMED_CREDENTIALS_HINT = "Run `primitive logout` and then `primitive login`.";
|
|
10738
|
-
function isRecord$
|
|
10756
|
+
function isRecord$2(value) {
|
|
10739
10757
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10740
10758
|
}
|
|
10741
10759
|
function requireString(value, key) {
|
|
@@ -10758,7 +10776,7 @@ var StaleCredentialFormatError = class extends Error {
|
|
|
10758
10776
|
}
|
|
10759
10777
|
};
|
|
10760
10778
|
function parseCredentials(raw) {
|
|
10761
|
-
if (!isRecord$
|
|
10779
|
+
if (!isRecord$2(raw)) throw new Error(`Stored Primitive CLI credentials are malformed: expected a JSON object. ${MALFORMED_CREDENTIALS_HINT}`);
|
|
10762
10780
|
if (typeof raw.api_base_url_1 !== "string" && typeof raw.base_url === "string") throw new StaleCredentialFormatError();
|
|
10763
10781
|
const orgName = raw.org_name;
|
|
10764
10782
|
if (orgName !== null && typeof orgName !== "string") throw new Error(`Stored Primitive CLI credentials are malformed: org_name must be a string or null. ${MALFORMED_CREDENTIALS_HINT}`);
|
|
@@ -10903,6 +10921,269 @@ function resolveCliAuth(params) {
|
|
|
10903
10921
|
};
|
|
10904
10922
|
}
|
|
10905
10923
|
//#endregion
|
|
10924
|
+
//#region src/oclif/cli-config.ts
|
|
10925
|
+
const CONFIG_FILE = "config.json";
|
|
10926
|
+
const CONFIG_VERSION = 1;
|
|
10927
|
+
const DEFAULT_ENVIRONMENT = "default";
|
|
10928
|
+
function cliConfigPath(configDir) {
|
|
10929
|
+
return join(configDir, CONFIG_FILE);
|
|
10930
|
+
}
|
|
10931
|
+
function cliConfigError(message) {
|
|
10932
|
+
return new Errors.CLIError(`${message} Run \`primitive config reset\` to clear the local CLI config.`, { exit: 1 });
|
|
10933
|
+
}
|
|
10934
|
+
function isRecord$1(value) {
|
|
10935
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
10936
|
+
}
|
|
10937
|
+
function normalizeCliEnvironmentName(name) {
|
|
10938
|
+
const trimmed = name?.trim();
|
|
10939
|
+
if (!trimmed) throw new Errors.CLIError("Environment name must be a non-empty string.", { exit: 1 });
|
|
10940
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/.test(trimmed)) throw new Errors.CLIError("Environment name must start with a letter or number and may only contain letters, numbers, '.', '_', or '-'.", { exit: 1 });
|
|
10941
|
+
return trimmed;
|
|
10942
|
+
}
|
|
10943
|
+
function validateCliHeaderName(name) {
|
|
10944
|
+
const trimmed = name.trim();
|
|
10945
|
+
if (!trimmed) throw new Errors.CLIError("Header name must be a non-empty string.", { exit: 1 });
|
|
10946
|
+
if (!/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(trimmed)) throw new Errors.CLIError(`Invalid header name: ${name}`, { exit: 1 });
|
|
10947
|
+
if (trimmed.toLowerCase() === "authorization") throw new Errors.CLIError("The Authorization header is managed by PRIMITIVE_API_KEY or saved CLI credentials.", { exit: 1 });
|
|
10948
|
+
return trimmed;
|
|
10949
|
+
}
|
|
10950
|
+
function validateCliHeaderValue(value, name) {
|
|
10951
|
+
if (value.length === 0) throw new Errors.CLIError(`Header ${name} value must not be empty.`, { exit: 1 });
|
|
10952
|
+
if (/[\r\n\0]/.test(value)) throw new Errors.CLIError(`Header ${name} value must not contain CR, LF, or NUL characters.`, { exit: 1 });
|
|
10953
|
+
return value;
|
|
10954
|
+
}
|
|
10955
|
+
function parseHeaderAssignment(assignment) {
|
|
10956
|
+
const separator = assignment.indexOf("=");
|
|
10957
|
+
if (separator <= 0) throw new Errors.CLIError("Header values must use name=value syntax, for example `x-custom=secret`.", { exit: 1 });
|
|
10958
|
+
const name = validateCliHeaderName(assignment.slice(0, separator));
|
|
10959
|
+
return [name, validateCliHeaderValue(assignment.slice(separator + 1), name)];
|
|
10960
|
+
}
|
|
10961
|
+
function parseHeaders(raw, context) {
|
|
10962
|
+
if (raw === void 0) return {};
|
|
10963
|
+
if (!isRecord$1(raw)) throw cliConfigError(`${context} headers must be a JSON object.`);
|
|
10964
|
+
const headers = {};
|
|
10965
|
+
for (const [rawName, rawValue] of Object.entries(raw)) {
|
|
10966
|
+
const name = validateCliHeaderName(rawName);
|
|
10967
|
+
if (typeof rawValue !== "string") throw cliConfigError(`${context} header ${name} must be a string.`);
|
|
10968
|
+
headers[name] = validateCliHeaderValue(rawValue, name);
|
|
10969
|
+
}
|
|
10970
|
+
return headers;
|
|
10971
|
+
}
|
|
10972
|
+
function parseEnvironmentConfig(raw, context) {
|
|
10973
|
+
if (!isRecord$1(raw)) throw cliConfigError(`${context} must be a JSON object.`);
|
|
10974
|
+
const env = {};
|
|
10975
|
+
if (raw.api_base_url_1 !== void 0) {
|
|
10976
|
+
if (typeof raw.api_base_url_1 !== "string") throw cliConfigError(`${context}.api_base_url_1 must be a string.`);
|
|
10977
|
+
env.api_base_url_1 = normalizeApiBaseUrl1(raw.api_base_url_1);
|
|
10978
|
+
}
|
|
10979
|
+
if (raw.api_base_url_2 !== void 0) {
|
|
10980
|
+
if (typeof raw.api_base_url_2 !== "string") throw cliConfigError(`${context}.api_base_url_2 must be a string.`);
|
|
10981
|
+
env.api_base_url_2 = normalizeApiBaseUrl2(raw.api_base_url_2);
|
|
10982
|
+
}
|
|
10983
|
+
const headers = parseHeaders(raw.headers, context);
|
|
10984
|
+
if (Object.keys(headers).length > 0) env.headers = headers;
|
|
10985
|
+
return env;
|
|
10986
|
+
}
|
|
10987
|
+
function parseStoredCliConfig(raw) {
|
|
10988
|
+
if (!isRecord$1(raw)) throw cliConfigError("Primitive CLI config must be a JSON object.");
|
|
10989
|
+
if (raw.version !== CONFIG_VERSION) throw cliConfigError(`Primitive CLI config version must be ${CONFIG_VERSION}.`);
|
|
10990
|
+
const currentRaw = raw.current_environment;
|
|
10991
|
+
const current_environment = currentRaw === null || currentRaw === void 0 ? null : typeof currentRaw === "string" ? normalizeCliEnvironmentName(currentRaw) : (() => {
|
|
10992
|
+
throw cliConfigError("Primitive CLI config current_environment must be a string or null.");
|
|
10993
|
+
})();
|
|
10994
|
+
if (!isRecord$1(raw.environments)) throw cliConfigError("Primitive CLI config environments must be an object.");
|
|
10995
|
+
const environments = {};
|
|
10996
|
+
for (const [rawName, rawEnv] of Object.entries(raw.environments)) {
|
|
10997
|
+
const name = normalizeCliEnvironmentName(rawName);
|
|
10998
|
+
environments[name] = parseEnvironmentConfig(rawEnv, `Primitive CLI config environment ${name}`);
|
|
10999
|
+
}
|
|
11000
|
+
if (current_environment && !environments[current_environment]) throw cliConfigError(`Primitive CLI config current environment ${current_environment} does not exist.`);
|
|
11001
|
+
return {
|
|
11002
|
+
version: CONFIG_VERSION,
|
|
11003
|
+
current_environment,
|
|
11004
|
+
environments
|
|
11005
|
+
};
|
|
11006
|
+
}
|
|
11007
|
+
function emptyCliConfig() {
|
|
11008
|
+
return {
|
|
11009
|
+
version: CONFIG_VERSION,
|
|
11010
|
+
current_environment: null,
|
|
11011
|
+
environments: {}
|
|
11012
|
+
};
|
|
11013
|
+
}
|
|
11014
|
+
function loadCliConfig(configDir) {
|
|
11015
|
+
const path = cliConfigPath(configDir);
|
|
11016
|
+
let contents;
|
|
11017
|
+
try {
|
|
11018
|
+
contents = readFileSync(path, "utf8");
|
|
11019
|
+
} catch (error) {
|
|
11020
|
+
if (error && typeof error === "object" && error.code === "ENOENT") return null;
|
|
11021
|
+
throw cliConfigError(`Could not read Primitive CLI config: ${error instanceof Error ? error.message : String(error)}.`);
|
|
11022
|
+
}
|
|
11023
|
+
try {
|
|
11024
|
+
return parseStoredCliConfig(JSON.parse(contents));
|
|
11025
|
+
} catch (error) {
|
|
11026
|
+
if (error instanceof SyntaxError) throw cliConfigError("Primitive CLI config is not valid JSON.");
|
|
11027
|
+
throw error;
|
|
11028
|
+
}
|
|
11029
|
+
}
|
|
11030
|
+
function saveCliConfig(configDir, config) {
|
|
11031
|
+
mkdirSync(configDir, {
|
|
11032
|
+
mode: 448,
|
|
11033
|
+
recursive: true
|
|
11034
|
+
});
|
|
11035
|
+
const path = cliConfigPath(configDir);
|
|
11036
|
+
const tempPath = join(configDir, `${CONFIG_FILE}.${process.pid}.${randomUUID()}.tmp`);
|
|
11037
|
+
try {
|
|
11038
|
+
writeFileSync(tempPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
|
|
11039
|
+
renameSync(tempPath, path);
|
|
11040
|
+
} catch (error) {
|
|
11041
|
+
rmSync(tempPath, { force: true });
|
|
11042
|
+
throw error;
|
|
11043
|
+
}
|
|
11044
|
+
}
|
|
11045
|
+
function deleteCliConfig(configDir) {
|
|
11046
|
+
rmSync(cliConfigPath(configDir), { force: true });
|
|
11047
|
+
}
|
|
11048
|
+
function resolveConfigEnvironment(config) {
|
|
11049
|
+
if (!config) return null;
|
|
11050
|
+
const current = config.current_environment;
|
|
11051
|
+
if (current) {
|
|
11052
|
+
const environment = config.environments[current];
|
|
11053
|
+
return environment ? {
|
|
11054
|
+
name: current,
|
|
11055
|
+
config: environment
|
|
11056
|
+
} : null;
|
|
11057
|
+
}
|
|
11058
|
+
const defaultEnvironment = config.environments[DEFAULT_ENVIRONMENT];
|
|
11059
|
+
return defaultEnvironment ? {
|
|
11060
|
+
name: DEFAULT_ENVIRONMENT,
|
|
11061
|
+
config: defaultEnvironment
|
|
11062
|
+
} : null;
|
|
11063
|
+
}
|
|
11064
|
+
function upsertCliEnvironment(params) {
|
|
11065
|
+
const name = normalizeCliEnvironmentName(params.environmentName ?? DEFAULT_ENVIRONMENT);
|
|
11066
|
+
const existing = params.config.environments[name] ?? {};
|
|
11067
|
+
const nextHeaders = { ...existing.headers ?? {} };
|
|
11068
|
+
for (const assignment of params.headers ?? []) {
|
|
11069
|
+
const [headerName, value] = parseHeaderAssignment(assignment);
|
|
11070
|
+
nextHeaders[headerName] = value;
|
|
11071
|
+
}
|
|
11072
|
+
for (const rawName of params.unsetHeaders ?? []) delete nextHeaders[validateCliHeaderName(rawName)];
|
|
11073
|
+
const nextEnvironment = {
|
|
11074
|
+
...existing,
|
|
11075
|
+
...params.apiBaseUrl1 !== void 0 ? { api_base_url_1: normalizeApiBaseUrl1(params.apiBaseUrl1) } : {},
|
|
11076
|
+
...params.apiBaseUrl2 !== void 0 ? { api_base_url_2: normalizeApiBaseUrl2(params.apiBaseUrl2) } : {},
|
|
11077
|
+
...Object.keys(nextHeaders).length > 0 ? { headers: nextHeaders } : {}
|
|
11078
|
+
};
|
|
11079
|
+
if (Object.keys(nextHeaders).length === 0) delete nextEnvironment.headers;
|
|
11080
|
+
return {
|
|
11081
|
+
...params.config,
|
|
11082
|
+
current_environment: params.use === false ? params.config.current_environment : name,
|
|
11083
|
+
environments: {
|
|
11084
|
+
...params.config.environments,
|
|
11085
|
+
[name]: nextEnvironment
|
|
11086
|
+
}
|
|
11087
|
+
};
|
|
11088
|
+
}
|
|
11089
|
+
function removeCliEnvironment(config, environmentName) {
|
|
11090
|
+
const name = normalizeCliEnvironmentName(environmentName);
|
|
11091
|
+
const environments = { ...config.environments };
|
|
11092
|
+
delete environments[name];
|
|
11093
|
+
return {
|
|
11094
|
+
...config,
|
|
11095
|
+
current_environment: config.current_environment === name ? null : config.current_environment,
|
|
11096
|
+
environments
|
|
11097
|
+
};
|
|
11098
|
+
}
|
|
11099
|
+
function redactCliEnvironment(environment) {
|
|
11100
|
+
const headers = environment.headers && Object.keys(environment.headers).length > 0 ? Object.fromEntries(Object.keys(environment.headers).map((name) => [name, "***"])) : void 0;
|
|
11101
|
+
return {
|
|
11102
|
+
...environment,
|
|
11103
|
+
...headers ? { headers } : {}
|
|
11104
|
+
};
|
|
11105
|
+
}
|
|
11106
|
+
//#endregion
|
|
11107
|
+
//#region src/oclif/api-client.ts
|
|
11108
|
+
const API_HEADERS_ENV = "PRIMITIVE_API_HEADERS";
|
|
11109
|
+
function mergeHeaders(...headers) {
|
|
11110
|
+
const merged = {};
|
|
11111
|
+
for (const headerSet of headers) {
|
|
11112
|
+
if (!headerSet) continue;
|
|
11113
|
+
for (const [name, value] of Object.entries(headerSet)) merged[name] = value;
|
|
11114
|
+
}
|
|
11115
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
11116
|
+
}
|
|
11117
|
+
function parseHeadersJson(raw) {
|
|
11118
|
+
let parsed;
|
|
11119
|
+
try {
|
|
11120
|
+
parsed = JSON.parse(raw);
|
|
11121
|
+
} catch (error) {
|
|
11122
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
11123
|
+
throw new Errors.CLIError(`${API_HEADERS_ENV} must be valid JSON object syntax: ${detail}`, { exit: 1 });
|
|
11124
|
+
}
|
|
11125
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Errors.CLIError(`${API_HEADERS_ENV} must be a JSON object.`, { exit: 1 });
|
|
11126
|
+
const headers = {};
|
|
11127
|
+
for (const [rawName, rawValue] of Object.entries(parsed)) {
|
|
11128
|
+
const name = validateCliHeaderName(rawName);
|
|
11129
|
+
if (typeof rawValue !== "string") throw new Errors.CLIError(`${API_HEADERS_ENV}.${name} must be a string.`, { exit: 1 });
|
|
11130
|
+
headers[name] = validateCliHeaderValue(rawValue, name);
|
|
11131
|
+
}
|
|
11132
|
+
return headers;
|
|
11133
|
+
}
|
|
11134
|
+
function cliApiHeadersFromEnv(env = process.env) {
|
|
11135
|
+
const rawGenericHeaders = env[API_HEADERS_ENV]?.trim();
|
|
11136
|
+
return rawGenericHeaders ? parseHeadersJson(rawGenericHeaders) : void 0;
|
|
11137
|
+
}
|
|
11138
|
+
function resolveCliApiRequestConfig(params) {
|
|
11139
|
+
const currentEnvironment = resolveConfigEnvironment(loadCliConfig(params.configDir));
|
|
11140
|
+
const configuredApiBaseUrl1 = currentEnvironment?.config.api_base_url_1;
|
|
11141
|
+
const configuredApiBaseUrl2 = currentEnvironment?.config.api_base_url_2;
|
|
11142
|
+
const apiBaseUrl1 = params.apiBaseUrl1 !== void 0 ? normalizeApiBaseUrl1(params.apiBaseUrl1) : configuredApiBaseUrl1;
|
|
11143
|
+
const apiBaseUrl2 = params.apiBaseUrl2 !== void 0 ? normalizeApiBaseUrl2(params.apiBaseUrl2) : configuredApiBaseUrl2;
|
|
11144
|
+
return {
|
|
11145
|
+
apiBaseUrl1,
|
|
11146
|
+
apiBaseUrl2,
|
|
11147
|
+
baseUrlOverridden: apiBaseUrl1 !== void 0 || apiBaseUrl2 !== void 0,
|
|
11148
|
+
environmentName: currentEnvironment?.name ?? null,
|
|
11149
|
+
headers: mergeHeaders(currentEnvironment?.config.headers, cliApiHeadersFromEnv(params.env)),
|
|
11150
|
+
resolvedApiBaseUrl1: normalizeApiBaseUrl1(apiBaseUrl1),
|
|
11151
|
+
resolvedApiBaseUrl2: normalizeApiBaseUrl2(apiBaseUrl2)
|
|
11152
|
+
};
|
|
11153
|
+
}
|
|
11154
|
+
function createCliApiClient(params) {
|
|
11155
|
+
const requestConfig = resolveCliApiRequestConfig(params);
|
|
11156
|
+
return {
|
|
11157
|
+
apiClient: new PrimitiveApiClient({
|
|
11158
|
+
apiKey: params.apiKey,
|
|
11159
|
+
apiBaseUrl1: requestConfig.resolvedApiBaseUrl1,
|
|
11160
|
+
apiBaseUrl2: requestConfig.resolvedApiBaseUrl2,
|
|
11161
|
+
headers: requestConfig.headers
|
|
11162
|
+
}),
|
|
11163
|
+
requestConfig
|
|
11164
|
+
};
|
|
11165
|
+
}
|
|
11166
|
+
function createAuthenticatedCliApiClient(params) {
|
|
11167
|
+
const requestConfig = resolveCliApiRequestConfig(params);
|
|
11168
|
+
const auth = resolveCliAuth({
|
|
11169
|
+
apiKey: params.apiKey,
|
|
11170
|
+
apiBaseUrl1: requestConfig.apiBaseUrl1,
|
|
11171
|
+
apiBaseUrl2: requestConfig.apiBaseUrl2,
|
|
11172
|
+
configDir: params.configDir
|
|
11173
|
+
});
|
|
11174
|
+
return {
|
|
11175
|
+
apiClient: new PrimitiveApiClient({
|
|
11176
|
+
apiKey: auth.apiKey,
|
|
11177
|
+
apiBaseUrl1: auth.apiBaseUrl1,
|
|
11178
|
+
apiBaseUrl2: auth.apiBaseUrl2,
|
|
11179
|
+
headers: requestConfig.headers
|
|
11180
|
+
}),
|
|
11181
|
+
auth,
|
|
11182
|
+
baseUrlOverridden: requestConfig.baseUrlOverridden,
|
|
11183
|
+
requestConfig
|
|
11184
|
+
};
|
|
11185
|
+
}
|
|
11186
|
+
//#endregion
|
|
10906
11187
|
//#region src/oclif/endpoints-test-redirect.ts
|
|
10907
11188
|
async function detectFunctionEndpoint(endpointId, listEndpoints) {
|
|
10908
11189
|
let response;
|
|
@@ -10988,6 +11269,22 @@ function flagName(parameterName) {
|
|
|
10988
11269
|
function flagDescription(parameter) {
|
|
10989
11270
|
return parameter.description ?? parameter.name;
|
|
10990
11271
|
}
|
|
11272
|
+
const numberFlag = Flags.custom({ async parse(input, _context, options) {
|
|
11273
|
+
const trimmed = input.trim();
|
|
11274
|
+
if (trimmed === "") throw new Errors.CLIError(`Expected a number but received: ${input}`);
|
|
11275
|
+
const value = Number(trimmed);
|
|
11276
|
+
if (!Number.isFinite(value)) throw new Errors.CLIError(`Expected a number but received: ${input}`);
|
|
11277
|
+
if (options.min !== void 0 && value < options.min) throw new Errors.CLIError(`Expected a number greater than or equal to ${options.min} but received: ${input}`);
|
|
11278
|
+
if (options.max !== void 0 && value > options.max) throw new Errors.CLIError(`Expected a number less than or equal to ${options.max} but received: ${input}`);
|
|
11279
|
+
return value;
|
|
11280
|
+
} });
|
|
11281
|
+
function numericFlagOptions(parameter) {
|
|
11282
|
+
return {
|
|
11283
|
+
...typeof parameter.default === "number" ? { default: parameter.default } : {},
|
|
11284
|
+
...typeof parameter.maximum === "number" ? { max: parameter.maximum } : {},
|
|
11285
|
+
...typeof parameter.minimum === "number" ? { min: parameter.minimum } : {}
|
|
11286
|
+
};
|
|
11287
|
+
}
|
|
10991
11288
|
function extractBodyFields(schema) {
|
|
10992
11289
|
if (!schema || typeof schema !== "object") return [];
|
|
10993
11290
|
const properties = schema.properties;
|
|
@@ -11003,7 +11300,8 @@ function extractBodyFields(schema) {
|
|
|
11003
11300
|
if (typeof t === "string") {
|
|
11004
11301
|
displayType = t;
|
|
11005
11302
|
if (t === "string") kind = "string";
|
|
11006
|
-
else if (t === "integer"
|
|
11303
|
+
else if (t === "integer") kind = "integer";
|
|
11304
|
+
else if (t === "number") kind = "number";
|
|
11007
11305
|
else if (t === "boolean") kind = "boolean";
|
|
11008
11306
|
else if (t === "array") {
|
|
11009
11307
|
const items = propSchema.items;
|
|
@@ -11019,7 +11317,8 @@ function extractBodyFields(schema) {
|
|
|
11019
11317
|
const single = nonNull[0];
|
|
11020
11318
|
displayType = `${single}?`;
|
|
11021
11319
|
if (single === "string") kind = "string";
|
|
11022
|
-
else if (single === "integer"
|
|
11320
|
+
else if (single === "integer") kind = "integer";
|
|
11321
|
+
else if (single === "number") kind = "number";
|
|
11023
11322
|
else if (single === "boolean") kind = "boolean";
|
|
11024
11323
|
else kind = "complex";
|
|
11025
11324
|
} else {
|
|
@@ -11036,7 +11335,9 @@ function extractBodyFields(schema) {
|
|
|
11036
11335
|
required: required.has(name),
|
|
11037
11336
|
displayType,
|
|
11038
11337
|
kind,
|
|
11039
|
-
...enumValues && enumValues.length > 0 ? { enumValues } : {}
|
|
11338
|
+
...enumValues && enumValues.length > 0 ? { enumValues } : {},
|
|
11339
|
+
...typeof propSchema.maximum === "number" ? { maximum: propSchema.maximum } : {},
|
|
11340
|
+
...typeof propSchema.minimum === "number" ? { minimum: propSchema.minimum } : {}
|
|
11040
11341
|
});
|
|
11041
11342
|
}
|
|
11042
11343
|
return fields.sort((a, b) => {
|
|
@@ -11084,7 +11385,14 @@ function flagForParameter(parameter) {
|
|
|
11084
11385
|
required: parameter.required
|
|
11085
11386
|
};
|
|
11086
11387
|
if (parameter.type === "boolean") return Flags.boolean(common);
|
|
11087
|
-
if (parameter.type === "integer") return Flags.integer(
|
|
11388
|
+
if (parameter.type === "integer") return Flags.integer({
|
|
11389
|
+
...common,
|
|
11390
|
+
...numericFlagOptions(parameter)
|
|
11391
|
+
});
|
|
11392
|
+
if (parameter.type === "number") return numberFlag({
|
|
11393
|
+
...common,
|
|
11394
|
+
...numericFlagOptions(parameter)
|
|
11395
|
+
});
|
|
11088
11396
|
if (parameter.enum && parameter.enum.length > 0) return Flags.string({
|
|
11089
11397
|
...common,
|
|
11090
11398
|
options: parameter.enum
|
|
@@ -11199,7 +11507,7 @@ function writeErrorWithHints(payload) {
|
|
|
11199
11507
|
function removeStaleSavedCredentialOnUnauthorized(params) {
|
|
11200
11508
|
if (extractErrorCode(params.payload) !== API_ERROR_CODES.unauthorized || params.auth.source !== "stored") return false;
|
|
11201
11509
|
if (params.baseUrlOverridden && params.auth.credentials !== null && params.auth.apiBaseUrl1 !== params.auth.credentials.api_base_url_1) {
|
|
11202
|
-
process.stderr.write("Saved Primitive CLI credentials were rejected by the overridden API base URL. The local credential was not removed; unset PRIMITIVE_API_BASE_URL_1, or run `primitive logout` to remove the stored credential.\n");
|
|
11510
|
+
process.stderr.write("Saved Primitive CLI credentials were rejected by the overridden API base URL. The local credential was not removed; unset PRIMITIVE_API_BASE_URL_1, run `primitive config reset` to clear configured URL overrides, or run `primitive logout` to remove the stored credential.\n");
|
|
11203
11511
|
return false;
|
|
11204
11512
|
}
|
|
11205
11513
|
deleteCliCredentials(params.configDir);
|
|
@@ -11224,9 +11532,6 @@ async function runWithTiming(enabled, fn) {
|
|
|
11224
11532
|
const TIME_FLAG_DESCRIPTION = "Print the wall-clock duration of this command to stderr after it completes (e.g. `[time: 1.34s]`). Useful for measuring `--wait` send latency, comparing CLI overhead, or capturing timing in scripts.";
|
|
11225
11533
|
const API_BASE_URL_1_FLAG_DESCRIPTION = "Override the primary API base URL. Internal testing only; not documented to customers.";
|
|
11226
11534
|
const API_BASE_URL_2_FLAG_DESCRIPTION = "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.";
|
|
11227
|
-
function baseUrlOverriddenFromFlags(flags) {
|
|
11228
|
-
return typeof flags["api-base-url-1"] === "string" || typeof flags["api-base-url-2"] === "string";
|
|
11229
|
-
}
|
|
11230
11535
|
const HOST_2_OPERATIONS = new Set(["sendEmail"]);
|
|
11231
11536
|
const RESERVED_FLAG_NAMES = new Set([
|
|
11232
11537
|
"api-key",
|
|
@@ -11234,12 +11539,20 @@ const RESERVED_FLAG_NAMES = new Set([
|
|
|
11234
11539
|
"api-base-url-2",
|
|
11235
11540
|
"raw-body",
|
|
11236
11541
|
"body-file",
|
|
11542
|
+
"envelope",
|
|
11237
11543
|
"output"
|
|
11238
11544
|
]);
|
|
11239
11545
|
function bodyFieldFlag(field) {
|
|
11240
11546
|
const common = { description: field.description || field.name };
|
|
11241
11547
|
if (field.kind === "boolean") return Flags.boolean(common);
|
|
11242
|
-
if (field.kind === "integer") return Flags.integer(
|
|
11548
|
+
if (field.kind === "integer") return Flags.integer({
|
|
11549
|
+
...common,
|
|
11550
|
+
...numericFlagOptions(field)
|
|
11551
|
+
});
|
|
11552
|
+
if (field.kind === "number") return numberFlag({
|
|
11553
|
+
...common,
|
|
11554
|
+
...numericFlagOptions(field)
|
|
11555
|
+
});
|
|
11243
11556
|
if (field.enumValues) return Flags.string({
|
|
11244
11557
|
...common,
|
|
11245
11558
|
options: field.enumValues
|
|
@@ -11264,6 +11577,7 @@ function buildFlags(operation) {
|
|
|
11264
11577
|
}),
|
|
11265
11578
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
11266
11579
|
};
|
|
11580
|
+
if (!operation.binaryResponse) flags.envelope = Flags.boolean({ description: "Print the full response envelope, including pagination metadata such as meta.cursor. Defaults to printing only the data payload for backward compatibility." });
|
|
11267
11581
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) flags[flagName(parameter.name)] = flagForParameter(parameter);
|
|
11268
11582
|
const bodyFieldFlagToProperty = /* @__PURE__ */ new Map();
|
|
11269
11583
|
if (operation.hasJsonBody) {
|
|
@@ -11302,6 +11616,9 @@ function collectValues(parameters, flags) {
|
|
|
11302
11616
|
}
|
|
11303
11617
|
return values;
|
|
11304
11618
|
}
|
|
11619
|
+
function operationOutputPayload(envelope, includeEnvelope) {
|
|
11620
|
+
return includeEnvelope ? envelope ?? null : envelope?.data ?? null;
|
|
11621
|
+
}
|
|
11305
11622
|
const OPERATION_HINTS = {
|
|
11306
11623
|
createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
11307
11624
|
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
@@ -11323,18 +11640,12 @@ function createOperationCommand(operation) {
|
|
|
11323
11640
|
const { flags } = await this.parse(OperationCommand);
|
|
11324
11641
|
const parsedFlags = flags;
|
|
11325
11642
|
await runWithTiming(parsedFlags.time === true, async () => {
|
|
11326
|
-
const
|
|
11327
|
-
const auth = resolveCliAuth({
|
|
11643
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
11328
11644
|
apiKey: typeof parsedFlags["api-key"] === "string" ? parsedFlags["api-key"] : void 0,
|
|
11329
11645
|
apiBaseUrl1: typeof parsedFlags["api-base-url-1"] === "string" ? parsedFlags["api-base-url-1"] : void 0,
|
|
11330
11646
|
apiBaseUrl2: typeof parsedFlags["api-base-url-2"] === "string" ? parsedFlags["api-base-url-2"] : void 0,
|
|
11331
11647
|
configDir: this.config.configDir
|
|
11332
11648
|
});
|
|
11333
|
-
const apiClient = new PrimitiveApiClient({
|
|
11334
|
-
apiKey: auth.apiKey,
|
|
11335
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
11336
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
11337
|
-
});
|
|
11338
11649
|
let body;
|
|
11339
11650
|
if (operation.hasJsonBody) {
|
|
11340
11651
|
const explicit = readJsonBody(parsedFlags);
|
|
@@ -11409,7 +11720,7 @@ function createOperationCommand(operation) {
|
|
|
11409
11720
|
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
11410
11721
|
process.stderr.write(chunk);
|
|
11411
11722
|
} });
|
|
11412
|
-
this.log(JSON.stringify(envelope
|
|
11723
|
+
this.log(JSON.stringify(operationOutputPayload(envelope, parsedFlags.envelope === true), null, 2));
|
|
11413
11724
|
});
|
|
11414
11725
|
}
|
|
11415
11726
|
}
|
|
@@ -11426,6 +11737,128 @@ function canonicalizeCliReferences(description) {
|
|
|
11426
11737
|
return description.replaceAll("`primitive emails:latest`", "`primitive emails latest`").replaceAll("`primitive describe emails:get-email | jq '.responseSchema.properties'`", "`primitive describe emails:get | jq '.responseSchema.properties'`");
|
|
11427
11738
|
}
|
|
11428
11739
|
//#endregion
|
|
11740
|
+
//#region src/oclif/commands/config.ts
|
|
11741
|
+
function loadOrCreateConfig(configDir) {
|
|
11742
|
+
return loadCliConfig(configDir) ?? emptyCliConfig();
|
|
11743
|
+
}
|
|
11744
|
+
function redactConfig(config) {
|
|
11745
|
+
return {
|
|
11746
|
+
...config,
|
|
11747
|
+
environments: Object.fromEntries(Object.entries(config.environments).map(([name, environment]) => [name, redactCliEnvironment(environment)]))
|
|
11748
|
+
};
|
|
11749
|
+
}
|
|
11750
|
+
var ConfigSetCommand = class ConfigSetCommand extends Command {
|
|
11751
|
+
static hidden = true;
|
|
11752
|
+
static summary = "Set hidden Primitive CLI request config";
|
|
11753
|
+
static flags = {
|
|
11754
|
+
environment: Flags.string({
|
|
11755
|
+
char: "e",
|
|
11756
|
+
description: "Environment name to create or update"
|
|
11757
|
+
}),
|
|
11758
|
+
"api-base-url-1": Flags.string({ description: "Primary API base URL" }),
|
|
11759
|
+
"api-base-url-2": Flags.string({ description: "Attachments-supporting API base URL" }),
|
|
11760
|
+
header: Flags.string({
|
|
11761
|
+
description: "Request header in name=value form. Repeatable.",
|
|
11762
|
+
multiple: true
|
|
11763
|
+
}),
|
|
11764
|
+
"unset-header": Flags.string({
|
|
11765
|
+
description: "Request header name to remove. Repeatable.",
|
|
11766
|
+
multiple: true
|
|
11767
|
+
})
|
|
11768
|
+
};
|
|
11769
|
+
async run() {
|
|
11770
|
+
const { flags } = await this.parse(ConfigSetCommand);
|
|
11771
|
+
const headers = flags.header ?? [];
|
|
11772
|
+
if (flags["api-base-url-1"] === void 0 && flags["api-base-url-2"] === void 0 && headers.length === 0 && (flags["unset-header"] ?? []).length === 0) throw new Errors.CLIError("Nothing to set. Pass an API base URL, --header, or --unset-header.", { exit: 1 });
|
|
11773
|
+
const config = upsertCliEnvironment({
|
|
11774
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
11775
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
11776
|
+
config: loadOrCreateConfig(this.config.configDir),
|
|
11777
|
+
environmentName: flags.environment,
|
|
11778
|
+
headers,
|
|
11779
|
+
unsetHeaders: flags["unset-header"]
|
|
11780
|
+
});
|
|
11781
|
+
saveCliConfig(this.config.configDir, config);
|
|
11782
|
+
process.stderr.write(`Primitive CLI environment ${config.current_environment} is active.\n`);
|
|
11783
|
+
}
|
|
11784
|
+
};
|
|
11785
|
+
var ConfigUseCommand = class ConfigUseCommand extends Command {
|
|
11786
|
+
static hidden = true;
|
|
11787
|
+
static summary = "Switch active Primitive CLI request config";
|
|
11788
|
+
static args = { environment: Args.string({
|
|
11789
|
+
description: "Environment name to use",
|
|
11790
|
+
required: true
|
|
11791
|
+
}) };
|
|
11792
|
+
async run() {
|
|
11793
|
+
const { args } = await this.parse(ConfigUseCommand);
|
|
11794
|
+
const environment = normalizeCliEnvironmentName(args.environment);
|
|
11795
|
+
const config = loadOrCreateConfig(this.config.configDir);
|
|
11796
|
+
if (!config.environments[environment]) throw new Errors.CLIError(`Primitive CLI environment ${environment} is not configured.`, { exit: 1 });
|
|
11797
|
+
saveCliConfig(this.config.configDir, {
|
|
11798
|
+
...config,
|
|
11799
|
+
current_environment: environment
|
|
11800
|
+
});
|
|
11801
|
+
process.stderr.write(`Primitive CLI environment ${environment} is active.\n`);
|
|
11802
|
+
}
|
|
11803
|
+
};
|
|
11804
|
+
var ConfigListCommand = class ConfigListCommand extends Command {
|
|
11805
|
+
static hidden = true;
|
|
11806
|
+
static summary = "List hidden Primitive CLI request configs";
|
|
11807
|
+
static flags = {
|
|
11808
|
+
json: Flags.boolean({ description: "Print JSON" }),
|
|
11809
|
+
"show-secrets": Flags.boolean({ description: "Show header values instead of redacting them" })
|
|
11810
|
+
};
|
|
11811
|
+
async run() {
|
|
11812
|
+
const { flags } = await this.parse(ConfigListCommand);
|
|
11813
|
+
const config = loadOrCreateConfig(this.config.configDir);
|
|
11814
|
+
const output = flags["show-secrets"] ? config : redactConfig(config);
|
|
11815
|
+
if (flags.json) {
|
|
11816
|
+
this.log(JSON.stringify(output, null, 2));
|
|
11817
|
+
return;
|
|
11818
|
+
}
|
|
11819
|
+
const entries = Object.entries(config.environments);
|
|
11820
|
+
if (entries.length === 0) {
|
|
11821
|
+
this.log("No Primitive CLI environments configured.");
|
|
11822
|
+
return;
|
|
11823
|
+
}
|
|
11824
|
+
const activeEnvironment = resolveConfigEnvironment(config)?.name ?? null;
|
|
11825
|
+
for (const [name, environment] of entries) {
|
|
11826
|
+
const active = activeEnvironment === name ? "*" : " ";
|
|
11827
|
+
const headerNames = Object.keys(environment.headers ?? {});
|
|
11828
|
+
this.log(`${active} ${name}`);
|
|
11829
|
+
if (environment.api_base_url_1) this.log(` api_base_url_1: ${environment.api_base_url_1}`);
|
|
11830
|
+
if (environment.api_base_url_2) this.log(` api_base_url_2: ${environment.api_base_url_2}`);
|
|
11831
|
+
this.log(` headers: ${headerNames.length > 0 ? headerNames.join(", ") : "(none)"}`);
|
|
11832
|
+
}
|
|
11833
|
+
}
|
|
11834
|
+
};
|
|
11835
|
+
var ConfigResetCommand = class ConfigResetCommand extends Command {
|
|
11836
|
+
static hidden = true;
|
|
11837
|
+
static summary = "Reset hidden Primitive CLI request config";
|
|
11838
|
+
static flags = { environment: Flags.string({
|
|
11839
|
+
char: "e",
|
|
11840
|
+
description: "Only remove one environment"
|
|
11841
|
+
}) };
|
|
11842
|
+
async run() {
|
|
11843
|
+
const { flags } = await this.parse(ConfigResetCommand);
|
|
11844
|
+
if (flags.environment === void 0) {
|
|
11845
|
+
deleteCliConfig(this.config.configDir);
|
|
11846
|
+
process.stderr.write("Primitive CLI request config reset.\n");
|
|
11847
|
+
return;
|
|
11848
|
+
}
|
|
11849
|
+
const environment = normalizeCliEnvironmentName(flags.environment);
|
|
11850
|
+
const config = loadCliConfig(this.config.configDir);
|
|
11851
|
+
if (!config?.environments[environment]) {
|
|
11852
|
+
process.stderr.write(`Primitive CLI environment ${environment} was not configured.\n`);
|
|
11853
|
+
return;
|
|
11854
|
+
}
|
|
11855
|
+
const nextConfig = removeCliEnvironment(config, environment);
|
|
11856
|
+
if (Object.keys(nextConfig.environments).length === 0) deleteCliConfig(this.config.configDir);
|
|
11857
|
+
else saveCliConfig(this.config.configDir, nextConfig);
|
|
11858
|
+
process.stderr.write(`Primitive CLI environment ${environment} removed.\n`);
|
|
11859
|
+
}
|
|
11860
|
+
};
|
|
11861
|
+
//#endregion
|
|
11429
11862
|
//#region src/oclif/commands/doctor.ts
|
|
11430
11863
|
const MIN_NODE_MAJOR = 22;
|
|
11431
11864
|
function renderRow({ label, outcome }) {
|
|
@@ -11524,14 +11957,10 @@ function checkApiKey(opts) {
|
|
|
11524
11957
|
hint: "Run `primitive login`, pass --api-key explicitly, or export PRIMITIVE_API_KEY=prim_..."
|
|
11525
11958
|
};
|
|
11526
11959
|
}
|
|
11527
|
-
async function checkAccount(
|
|
11960
|
+
async function checkAccount(client) {
|
|
11528
11961
|
try {
|
|
11529
11962
|
const result = await getAccount({
|
|
11530
|
-
client:
|
|
11531
|
-
apiKey: opts.apiKey,
|
|
11532
|
-
apiBaseUrl1: opts.apiBaseUrl1,
|
|
11533
|
-
apiBaseUrl2: opts.apiBaseUrl2
|
|
11534
|
-
}).client,
|
|
11963
|
+
client: client.client,
|
|
11535
11964
|
responseStyle: "fields"
|
|
11536
11965
|
});
|
|
11537
11966
|
const apiError = result.error;
|
|
@@ -11572,14 +12001,10 @@ async function checkAccount(opts) {
|
|
|
11572
12001
|
};
|
|
11573
12002
|
}
|
|
11574
12003
|
}
|
|
11575
|
-
async function checkDomains(
|
|
12004
|
+
async function checkDomains(client) {
|
|
11576
12005
|
try {
|
|
11577
12006
|
const result = await listDomains({
|
|
11578
|
-
client:
|
|
11579
|
-
apiKey: opts.apiKey,
|
|
11580
|
-
apiBaseUrl1: opts.apiBaseUrl1,
|
|
11581
|
-
apiBaseUrl2: opts.apiBaseUrl2
|
|
11582
|
-
}).client,
|
|
12007
|
+
client: client.client,
|
|
11583
12008
|
responseStyle: "fields"
|
|
11584
12009
|
});
|
|
11585
12010
|
if (result.error) return {
|
|
@@ -11650,28 +12075,20 @@ var DoctorCommand = class DoctorCommand extends Command {
|
|
|
11650
12075
|
outcome: apiKeyCheck
|
|
11651
12076
|
});
|
|
11652
12077
|
if (apiKeyCheck.status !== "fail") {
|
|
11653
|
-
const auth =
|
|
12078
|
+
const { apiClient, auth } = createAuthenticatedCliApiClient({
|
|
11654
12079
|
apiKey: flags["api-key"],
|
|
11655
12080
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
11656
12081
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
11657
12082
|
configDir: this.config.configDir
|
|
11658
12083
|
});
|
|
11659
12084
|
if (auth.apiKey !== void 0) {
|
|
11660
|
-
const accountCheck = await checkAccount(
|
|
11661
|
-
apiKey: auth.apiKey,
|
|
11662
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
11663
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
11664
|
-
});
|
|
12085
|
+
const accountCheck = await checkAccount(apiClient);
|
|
11665
12086
|
rows.push({
|
|
11666
12087
|
label: "API auth",
|
|
11667
12088
|
outcome: accountCheck.outcome
|
|
11668
12089
|
});
|
|
11669
12090
|
if (accountCheck.outcome.status === "ok") {
|
|
11670
|
-
const domainsOutcome = await checkDomains(
|
|
11671
|
-
apiKey: auth.apiKey,
|
|
11672
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
11673
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
11674
|
-
});
|
|
12091
|
+
const domainsOutcome = await checkDomains(apiClient);
|
|
11675
12092
|
rows.push({
|
|
11676
12093
|
label: "Domains",
|
|
11677
12094
|
outcome: domainsOutcome
|
|
@@ -11765,19 +12182,14 @@ var EmailsLatestCommand = class EmailsLatestCommand extends Command {
|
|
|
11765
12182
|
async run() {
|
|
11766
12183
|
const { flags } = await this.parse(EmailsLatestCommand);
|
|
11767
12184
|
await runWithTiming(flags.time, async () => {
|
|
11768
|
-
const
|
|
11769
|
-
const auth = resolveCliAuth({
|
|
12185
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
11770
12186
|
apiKey: flags["api-key"],
|
|
11771
12187
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
11772
12188
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
11773
12189
|
configDir: this.config.configDir
|
|
11774
12190
|
});
|
|
11775
12191
|
const result = await listEmails({
|
|
11776
|
-
client:
|
|
11777
|
-
apiKey: auth.apiKey,
|
|
11778
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
11779
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
11780
|
-
}).client,
|
|
12192
|
+
client: apiClient.client,
|
|
11781
12193
|
query: { limit: flags.limit },
|
|
11782
12194
|
responseStyle: "fields"
|
|
11783
12195
|
});
|
|
@@ -11974,18 +12386,12 @@ var EmailsWaitCommand = class EmailsWaitCommand extends Command {
|
|
|
11974
12386
|
};
|
|
11975
12387
|
async run() {
|
|
11976
12388
|
const { flags } = await this.parse(EmailsWaitCommand);
|
|
11977
|
-
const
|
|
11978
|
-
const auth = resolveCliAuth({
|
|
12389
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
11979
12390
|
apiKey: flags["api-key"],
|
|
11980
12391
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
11981
12392
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
11982
12393
|
configDir: this.config.configDir
|
|
11983
12394
|
});
|
|
11984
|
-
const apiClient = new PrimitiveApiClient({
|
|
11985
|
-
apiKey: auth.apiKey,
|
|
11986
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
11987
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
11988
|
-
});
|
|
11989
12395
|
let since;
|
|
11990
12396
|
try {
|
|
11991
12397
|
since = sinceFromFlags(flags);
|
|
@@ -12102,18 +12508,12 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
12102
12508
|
};
|
|
12103
12509
|
async run() {
|
|
12104
12510
|
const { flags } = await this.parse(EmailsWatchCommand);
|
|
12105
|
-
const
|
|
12106
|
-
const auth = resolveCliAuth({
|
|
12511
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
12107
12512
|
apiKey: flags["api-key"],
|
|
12108
12513
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
12109
12514
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
12110
12515
|
configDir: this.config.configDir
|
|
12111
12516
|
});
|
|
12112
|
-
const apiClient = new PrimitiveApiClient({
|
|
12113
|
-
apiKey: auth.apiKey,
|
|
12114
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
12115
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
12116
|
-
});
|
|
12117
12517
|
let since;
|
|
12118
12518
|
try {
|
|
12119
12519
|
since = sinceFromFlags(flags);
|
|
@@ -12167,6 +12567,92 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
12167
12567
|
}
|
|
12168
12568
|
};
|
|
12169
12569
|
//#endregion
|
|
12570
|
+
//#region src/oclif/function-deploy-wait.ts
|
|
12571
|
+
function validateDeployWaitFlags(params) {
|
|
12572
|
+
if (params.timeoutSeconds < 0) return "--timeout must be greater than or equal to 0.";
|
|
12573
|
+
if (params.pollIntervalSeconds <= 0) return "--poll-interval must be greater than 0.";
|
|
12574
|
+
return null;
|
|
12575
|
+
}
|
|
12576
|
+
function isTerminal(status) {
|
|
12577
|
+
return status === "deployed" || status === "failed";
|
|
12578
|
+
}
|
|
12579
|
+
function resultForTerminal(snapshot) {
|
|
12580
|
+
if (snapshot.deploy_status === "failed") return {
|
|
12581
|
+
function: snapshot,
|
|
12582
|
+
kind: "failed"
|
|
12583
|
+
};
|
|
12584
|
+
return {
|
|
12585
|
+
function: snapshot,
|
|
12586
|
+
kind: "ok"
|
|
12587
|
+
};
|
|
12588
|
+
}
|
|
12589
|
+
function toDeployWaitSnapshot(value) {
|
|
12590
|
+
return {
|
|
12591
|
+
...value.created_at !== void 0 ? { created_at: value.created_at } : {},
|
|
12592
|
+
...value.deploy_error !== void 0 ? { deploy_error: value.deploy_error } : {},
|
|
12593
|
+
deploy_status: value.deploy_status,
|
|
12594
|
+
...value.deployed_at !== void 0 ? { deployed_at: value.deployed_at } : {},
|
|
12595
|
+
gateway_url: value.gateway_url,
|
|
12596
|
+
id: value.id,
|
|
12597
|
+
name: value.name,
|
|
12598
|
+
...value.updated_at !== void 0 ? { updated_at: value.updated_at } : {}
|
|
12599
|
+
};
|
|
12600
|
+
}
|
|
12601
|
+
function elapsedSeconds(startedAt, now) {
|
|
12602
|
+
return Math.max(0, Math.round((now() - startedAt) / 1e3));
|
|
12603
|
+
}
|
|
12604
|
+
async function defaultSleep(ms) {
|
|
12605
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
12606
|
+
}
|
|
12607
|
+
async function waitForFunctionDeploy(params) {
|
|
12608
|
+
const now = params.now ?? Date.now;
|
|
12609
|
+
const sleep = params.sleep ?? defaultSleep;
|
|
12610
|
+
const writeStderr = params.writeStderr ?? ((chunk) => {
|
|
12611
|
+
process.stderr.write(chunk);
|
|
12612
|
+
});
|
|
12613
|
+
const startedAt = now();
|
|
12614
|
+
const timeoutMs = params.timeoutSeconds * 1e3;
|
|
12615
|
+
const pollIntervalMs = params.pollIntervalSeconds * 1e3;
|
|
12616
|
+
const hasTimeout = params.timeoutSeconds > 0;
|
|
12617
|
+
let last = params.initial ? toDeployWaitSnapshot(params.initial) : null;
|
|
12618
|
+
let lastStatus = last?.deploy_status ?? "unknown";
|
|
12619
|
+
if (last && isTerminal(last.deploy_status)) return resultForTerminal(last);
|
|
12620
|
+
writeStderr(`Waiting for function ${params.id} deploy to finish (current status: ${lastStatus})...\n`);
|
|
12621
|
+
while (true) {
|
|
12622
|
+
const elapsedMs = now() - startedAt;
|
|
12623
|
+
if (hasTimeout && elapsedMs >= timeoutMs) return {
|
|
12624
|
+
elapsedSeconds: elapsedSeconds(startedAt, now),
|
|
12625
|
+
kind: "timeout",
|
|
12626
|
+
lastFunction: last
|
|
12627
|
+
};
|
|
12628
|
+
await sleep(hasTimeout ? Math.min(pollIntervalMs, Math.max(0, timeoutMs - elapsedMs)) : pollIntervalMs);
|
|
12629
|
+
if (hasTimeout && now() - startedAt >= timeoutMs) return {
|
|
12630
|
+
elapsedSeconds: elapsedSeconds(startedAt, now),
|
|
12631
|
+
kind: "timeout",
|
|
12632
|
+
lastFunction: last
|
|
12633
|
+
};
|
|
12634
|
+
const result = await params.getFunction({ id: params.id });
|
|
12635
|
+
if (result.error) return {
|
|
12636
|
+
kind: "error",
|
|
12637
|
+
payload: extractErrorPayload(result.error)
|
|
12638
|
+
};
|
|
12639
|
+
const fetched = result.data?.data;
|
|
12640
|
+
if (!fetched) return {
|
|
12641
|
+
kind: "error",
|
|
12642
|
+
payload: {
|
|
12643
|
+
code: "client_error",
|
|
12644
|
+
message: "Get function returned no data while waiting for deploy"
|
|
12645
|
+
}
|
|
12646
|
+
};
|
|
12647
|
+
last = toDeployWaitSnapshot(fetched);
|
|
12648
|
+
if (last.deploy_status !== lastStatus) {
|
|
12649
|
+
lastStatus = last.deploy_status;
|
|
12650
|
+
writeStderr(`Function ${params.id} deploy status: ${last.deploy_status}\n`);
|
|
12651
|
+
}
|
|
12652
|
+
if (isTerminal(last.deploy_status)) return resultForTerminal(last);
|
|
12653
|
+
}
|
|
12654
|
+
}
|
|
12655
|
+
//#endregion
|
|
12170
12656
|
//#region src/oclif/lint/raw-send-mail-fetch.ts
|
|
12171
12657
|
const RAW_SEND_MAIL_FETCH_REGEX = /fetch\s*\(\s*[`'"][^`'"]*primitive\.dev[^`'"]*\/send-mail(?![A-Za-z0-9_-])/g;
|
|
12172
12658
|
const SNIPPET_PADDING = 60;
|
|
@@ -12208,8 +12694,8 @@ function resolveSecretFlags(input) {
|
|
|
12208
12694
|
const secrets = [];
|
|
12209
12695
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
12210
12696
|
const env = input.env ?? process.env;
|
|
12211
|
-
const readFile = input.readFile ?? defaultReadFile;
|
|
12212
|
-
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
12697
|
+
const readFile = input.readFile ?? defaultReadFile$1;
|
|
12698
|
+
const readStdin = input.readStdin ?? defaultReadStdin$1;
|
|
12213
12699
|
const envFileCache = /* @__PURE__ */ new Map();
|
|
12214
12700
|
const reserveSecretKey = (key, sourceLabel) => {
|
|
12215
12701
|
const keyError = validateKey(key, sourceLabel);
|
|
@@ -12291,6 +12777,8 @@ function resolveSecretFlags(input) {
|
|
|
12291
12777
|
};
|
|
12292
12778
|
}
|
|
12293
12779
|
function resolveSingleSecretValue(input) {
|
|
12780
|
+
const keyError = validateKey(input.key, "--key");
|
|
12781
|
+
if (keyError) return keyError;
|
|
12294
12782
|
if ([
|
|
12295
12783
|
input.value !== void 0 ? "--value" : null,
|
|
12296
12784
|
input.valueFromEnv !== void 0 ? "--value-from-env" : null,
|
|
@@ -12302,8 +12790,8 @@ function resolveSingleSecretValue(input) {
|
|
|
12302
12790
|
message: "Pass exactly one of --value, --value-from-env, --value-file, --value-from-env-file, or --stdin."
|
|
12303
12791
|
};
|
|
12304
12792
|
const env = input.env ?? process.env;
|
|
12305
|
-
const readFile = input.readFile ?? defaultReadFile;
|
|
12306
|
-
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
12793
|
+
const readFile = input.readFile ?? defaultReadFile$1;
|
|
12794
|
+
const readStdin = input.readStdin ?? defaultReadStdin$1;
|
|
12307
12795
|
if (input.value !== void 0) return {
|
|
12308
12796
|
kind: "ok",
|
|
12309
12797
|
value: input.value
|
|
@@ -12341,10 +12829,10 @@ function resolveSingleSecretValue(input) {
|
|
|
12341
12829
|
message: "Pass exactly one of --value, --value-from-env, --value-file, --value-from-env-file, or --stdin."
|
|
12342
12830
|
};
|
|
12343
12831
|
}
|
|
12344
|
-
function defaultReadFile(path) {
|
|
12832
|
+
function defaultReadFile$1(path) {
|
|
12345
12833
|
return readFileSync(path, "utf8");
|
|
12346
12834
|
}
|
|
12347
|
-
function defaultReadStdin() {
|
|
12835
|
+
function defaultReadStdin$1() {
|
|
12348
12836
|
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/env source instead.");
|
|
12349
12837
|
return readFileSync(0, "utf8");
|
|
12350
12838
|
}
|
|
@@ -12631,6 +13119,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
12631
13119
|
static summary = "Deploy a new function from a bundled handler file";
|
|
12632
13120
|
static examples = [
|
|
12633
13121
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js",
|
|
13122
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --wait",
|
|
12634
13123
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
12635
13124
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
12636
13125
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-env-file .env.local:OWNER_EMAIL",
|
|
@@ -12677,11 +13166,29 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
12677
13166
|
multiple: true
|
|
12678
13167
|
}),
|
|
12679
13168
|
"secret-from-stdin": Flags.string({ description: "Secret KEY to read from stdin and seed on the deployed function. A single trailing line ending is stripped. Stdin is consumed once, so this flag is not repeatable." }),
|
|
13169
|
+
wait: Flags.boolean({ description: "Wait until the function deploy reaches deployed or failed. Progress is written to stderr; stdout remains the final JSON payload." }),
|
|
13170
|
+
timeout: Flags.integer({
|
|
13171
|
+
default: 120,
|
|
13172
|
+
description: "Seconds to wait when --wait is set before exiting non-zero. Use 0 to wait forever."
|
|
13173
|
+
}),
|
|
13174
|
+
"poll-interval": Flags.integer({
|
|
13175
|
+
default: 2,
|
|
13176
|
+
description: "Seconds between deploy-status polls when --wait is set."
|
|
13177
|
+
}),
|
|
12680
13178
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
12681
13179
|
};
|
|
12682
13180
|
async run() {
|
|
12683
13181
|
const { flags } = await this.parse(FunctionsDeployCommand);
|
|
12684
13182
|
await runWithTiming(flags.time, async () => {
|
|
13183
|
+
const waitFlagError = validateDeployWaitFlags({
|
|
13184
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
13185
|
+
timeoutSeconds: flags.timeout
|
|
13186
|
+
});
|
|
13187
|
+
if (waitFlagError) {
|
|
13188
|
+
process.stderr.write(`${waitFlagError}\n`);
|
|
13189
|
+
process.exitCode = 1;
|
|
13190
|
+
return;
|
|
13191
|
+
}
|
|
12685
13192
|
const parsedSecrets = resolveSecretFlags({
|
|
12686
13193
|
fromEnv: flags["secret-from-env"] ?? [],
|
|
12687
13194
|
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
@@ -12697,18 +13204,12 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
12697
13204
|
const code = readTextFileFlag(flags.file, "--file");
|
|
12698
13205
|
const sourceMap = flags["source-map-file"] ? readTextFileFlag(flags["source-map-file"], "--source-map-file") : void 0;
|
|
12699
13206
|
emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
|
|
12700
|
-
const
|
|
12701
|
-
const auth = resolveCliAuth({
|
|
13207
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
12702
13208
|
apiKey: flags["api-key"],
|
|
12703
13209
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
12704
13210
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
12705
13211
|
configDir: this.config.configDir
|
|
12706
13212
|
});
|
|
12707
|
-
const apiClient = new PrimitiveApiClient({
|
|
12708
|
-
apiKey: auth.apiKey,
|
|
12709
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
12710
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
12711
|
-
});
|
|
12712
13213
|
const authFailureContext = {
|
|
12713
13214
|
auth,
|
|
12714
13215
|
baseUrlOverridden,
|
|
@@ -12767,6 +13268,42 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
12767
13268
|
return;
|
|
12768
13269
|
}
|
|
12769
13270
|
const payload = outcome.result.redeploy ?? outcome.result.created;
|
|
13271
|
+
if (flags.wait) {
|
|
13272
|
+
const waitResult = await waitForFunctionDeploy({
|
|
13273
|
+
getFunction: (p) => getFunction({
|
|
13274
|
+
client: apiClient.client,
|
|
13275
|
+
path: { id: p.id },
|
|
13276
|
+
responseStyle: "fields"
|
|
13277
|
+
}),
|
|
13278
|
+
id: payload.id,
|
|
13279
|
+
initial: payload,
|
|
13280
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
13281
|
+
timeoutSeconds: flags.timeout,
|
|
13282
|
+
writeStderr: (chunk) => process.stderr.write(chunk)
|
|
13283
|
+
});
|
|
13284
|
+
if (waitResult.kind === "error") {
|
|
13285
|
+
writeErrorWithHints(waitResult.payload);
|
|
13286
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
13287
|
+
...authFailureContext,
|
|
13288
|
+
payload: waitResult.payload
|
|
13289
|
+
});
|
|
13290
|
+
process.exitCode = 1;
|
|
13291
|
+
return;
|
|
13292
|
+
}
|
|
13293
|
+
if (waitResult.kind === "timeout") {
|
|
13294
|
+
const status = waitResult.lastFunction?.deploy_status ?? "unknown";
|
|
13295
|
+
process.stderr.write(`Timed out after ${flags.timeout}s waiting for function ${payload.id} deploy to finish (last status: ${status}).\n`);
|
|
13296
|
+
process.exitCode = 2;
|
|
13297
|
+
return;
|
|
13298
|
+
}
|
|
13299
|
+
this.log(JSON.stringify(waitResult.function, null, 2));
|
|
13300
|
+
if (waitResult.kind === "failed") {
|
|
13301
|
+
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
13302
|
+
process.stderr.write(`Function ${payload.id} deploy failed${detail}\n`);
|
|
13303
|
+
process.exitCode = 1;
|
|
13304
|
+
}
|
|
13305
|
+
return;
|
|
13306
|
+
}
|
|
12770
13307
|
this.log(JSON.stringify(payload, null, 2));
|
|
12771
13308
|
});
|
|
12772
13309
|
}
|
|
@@ -12779,8 +13316,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
12779
13316
|
name: "Primitive Team",
|
|
12780
13317
|
url: "https://primitive.dev"
|
|
12781
13318
|
};
|
|
12782
|
-
const SDK_VERSION_RANGE = "^0.
|
|
12783
|
-
const CLI_VERSION_RANGE = "^0.
|
|
13319
|
+
const SDK_VERSION_RANGE = "^0.30.1";
|
|
13320
|
+
const CLI_VERSION_RANGE = "^0.30.1";
|
|
12784
13321
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
12785
13322
|
function renderHandler() {
|
|
12786
13323
|
return `// env.PRIMITIVE_API_KEY is auto-injected by the Primitive Functions runtime.
|
|
@@ -12802,14 +13339,14 @@ import {
|
|
|
12802
13339
|
// the loop guard recognizes mail returning to it as self-traffic.
|
|
12803
13340
|
const REPLY_FROM = "you@your-domain.primitive.email";
|
|
12804
13341
|
|
|
12805
|
-
// Loop protection. A deployed Function
|
|
12806
|
-
//
|
|
12807
|
-
//
|
|
12808
|
-
//
|
|
12809
|
-
// fan-out loop.
|
|
13342
|
+
// Loop protection. A newly deployed Function starts as a fallback
|
|
13343
|
+
// endpoint for managed *.primitive.email domains that do not have a
|
|
13344
|
+
// domain-scoped endpoint. That can include bounces and auto-replies
|
|
13345
|
+
// generated by the handler's own outbound traffic. Without this guard
|
|
13346
|
+
// the handler can respond to its own bounces and create a fan-out loop.
|
|
12810
13347
|
//
|
|
12811
13348
|
// The default check returns true when From is on any *.primitive.email
|
|
12812
|
-
// address (covers
|
|
13349
|
+
// address (covers managed-domain fallback mail, the simple
|
|
12813
13350
|
// self-reply case, and bounces from mailer-daemon@*.primitive.email)
|
|
12814
13351
|
// or when From contains REPLY_FROM as a case-insensitive substring.
|
|
12815
13352
|
// Substring matching is deliberate so display-name forms like
|
|
@@ -13179,6 +13716,160 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
13179
13716
|
}
|
|
13180
13717
|
};
|
|
13181
13718
|
//#endregion
|
|
13719
|
+
//#region src/oclif/commands/functions-logs.ts
|
|
13720
|
+
const DEFAULT_LOG_LIMIT = 50;
|
|
13721
|
+
const DEFAULT_LOG_POLL_INTERVAL_SECONDS = 2;
|
|
13722
|
+
function levelLabel(level) {
|
|
13723
|
+
return level.toUpperCase().padEnd(5);
|
|
13724
|
+
}
|
|
13725
|
+
function orderFunctionLogsForDisplay(rows) {
|
|
13726
|
+
return [...rows].reverse();
|
|
13727
|
+
}
|
|
13728
|
+
function formatFunctionLogLine(row) {
|
|
13729
|
+
const metadata = row.metadata && Object.keys(row.metadata).length > 0 ? ` ${JSON.stringify(row.metadata)}` : "";
|
|
13730
|
+
return `${row.ts} ${levelLabel(row.level)} ${row.message}${metadata}`;
|
|
13731
|
+
}
|
|
13732
|
+
function collectFreshFunctionLogsFromPage(rows, seenIds) {
|
|
13733
|
+
const freshNewestFirst = [];
|
|
13734
|
+
let reachedSeen = false;
|
|
13735
|
+
for (const row of rows) {
|
|
13736
|
+
if (seenIds.has(row.id)) {
|
|
13737
|
+
reachedSeen = true;
|
|
13738
|
+
continue;
|
|
13739
|
+
}
|
|
13740
|
+
freshNewestFirst.push(row);
|
|
13741
|
+
seenIds.add(row.id);
|
|
13742
|
+
}
|
|
13743
|
+
return {
|
|
13744
|
+
freshNewestFirst,
|
|
13745
|
+
reachedSeen
|
|
13746
|
+
};
|
|
13747
|
+
}
|
|
13748
|
+
function emitLogRows(rows, jsonl) {
|
|
13749
|
+
for (const row of rows) {
|
|
13750
|
+
const line = jsonl ? JSON.stringify(row) : formatFunctionLogLine(row);
|
|
13751
|
+
process.stdout.write(`${line}\n`);
|
|
13752
|
+
}
|
|
13753
|
+
}
|
|
13754
|
+
var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
13755
|
+
static description = "List or follow function execution logs. Defaults to compact text output; use --jsonl for one JSON object per log row.";
|
|
13756
|
+
static summary = "List or follow a function's execution logs";
|
|
13757
|
+
static examples = [
|
|
13758
|
+
"<%= config.bin %> functions logs --id <fn-id>",
|
|
13759
|
+
"<%= config.bin %> functions logs --id <fn-id> --jsonl",
|
|
13760
|
+
"<%= config.bin %> functions logs --id <fn-id> --follow"
|
|
13761
|
+
];
|
|
13762
|
+
static flags = {
|
|
13763
|
+
"api-key": Flags.string({
|
|
13764
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
13765
|
+
env: "PRIMITIVE_API_KEY"
|
|
13766
|
+
}),
|
|
13767
|
+
"api-base-url-1": Flags.string({
|
|
13768
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
13769
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
13770
|
+
hidden: true
|
|
13771
|
+
}),
|
|
13772
|
+
"api-base-url-2": Flags.string({
|
|
13773
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
13774
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
13775
|
+
hidden: true
|
|
13776
|
+
}),
|
|
13777
|
+
id: Flags.string({
|
|
13778
|
+
description: "Function id (UUID).",
|
|
13779
|
+
required: true
|
|
13780
|
+
}),
|
|
13781
|
+
limit: Flags.integer({
|
|
13782
|
+
default: DEFAULT_LOG_LIMIT,
|
|
13783
|
+
description: "Maximum rows to fetch per poll. Server clamps to 1..200."
|
|
13784
|
+
}),
|
|
13785
|
+
cursor: Flags.string({ description: "Opaque pagination cursor from a previous logs response. Not supported with --follow." }),
|
|
13786
|
+
follow: Flags.boolean({
|
|
13787
|
+
char: "f",
|
|
13788
|
+
description: "Keep polling the newest logs and print rows not seen yet."
|
|
13789
|
+
}),
|
|
13790
|
+
jsonl: Flags.boolean({ description: "Print one compact JSON object per log row." }),
|
|
13791
|
+
"poll-interval": Flags.integer({
|
|
13792
|
+
default: DEFAULT_LOG_POLL_INTERVAL_SECONDS,
|
|
13793
|
+
description: "Seconds between polls when --follow is set."
|
|
13794
|
+
}),
|
|
13795
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
13796
|
+
};
|
|
13797
|
+
async run() {
|
|
13798
|
+
const { flags } = await this.parse(FunctionsLogsCommand);
|
|
13799
|
+
if (flags.limit <= 0) this.error("--limit must be greater than 0.", { exit: 2 });
|
|
13800
|
+
if (flags["poll-interval"] <= 0) this.error("--poll-interval must be greater than 0.", { exit: 2 });
|
|
13801
|
+
if (flags.follow && flags.cursor) this.error("--cursor cannot be combined with --follow.", { exit: 2 });
|
|
13802
|
+
await runWithTiming(flags.time, async () => {
|
|
13803
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
13804
|
+
apiKey: flags["api-key"],
|
|
13805
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
13806
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
13807
|
+
configDir: this.config.configDir
|
|
13808
|
+
});
|
|
13809
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
13810
|
+
let completedInitialFollowPoll = false;
|
|
13811
|
+
let hasObservedLogs = false;
|
|
13812
|
+
let wroteEmptyHint = false;
|
|
13813
|
+
while (true) {
|
|
13814
|
+
let cursor = flags.cursor;
|
|
13815
|
+
let nextCursor = null;
|
|
13816
|
+
let rows = [];
|
|
13817
|
+
while (true) {
|
|
13818
|
+
const result = await listFunctionLogs({
|
|
13819
|
+
client: apiClient.client,
|
|
13820
|
+
path: { id: flags.id },
|
|
13821
|
+
query: {
|
|
13822
|
+
...cursor ? { cursor } : {},
|
|
13823
|
+
limit: flags.limit
|
|
13824
|
+
},
|
|
13825
|
+
responseStyle: "fields"
|
|
13826
|
+
});
|
|
13827
|
+
if (result.error) {
|
|
13828
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
13829
|
+
writeErrorWithHints(errorPayload);
|
|
13830
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
13831
|
+
auth,
|
|
13832
|
+
baseUrlOverridden,
|
|
13833
|
+
configDir: this.config.configDir,
|
|
13834
|
+
payload: errorPayload
|
|
13835
|
+
});
|
|
13836
|
+
process.exitCode = 1;
|
|
13837
|
+
return;
|
|
13838
|
+
}
|
|
13839
|
+
const page = result.data?.data ?? {
|
|
13840
|
+
items: [],
|
|
13841
|
+
next_cursor: null
|
|
13842
|
+
};
|
|
13843
|
+
nextCursor = page.next_cursor;
|
|
13844
|
+
if (!flags.follow) {
|
|
13845
|
+
rows = orderFunctionLogsForDisplay(page.items);
|
|
13846
|
+
break;
|
|
13847
|
+
}
|
|
13848
|
+
if (page.items.length > 0) hasObservedLogs = true;
|
|
13849
|
+
const collected = collectFreshFunctionLogsFromPage(page.items, seenIds);
|
|
13850
|
+
rows.push(...collected.freshNewestFirst);
|
|
13851
|
+
if (!completedInitialFollowPoll || collected.reachedSeen || !page.next_cursor) {
|
|
13852
|
+
rows = orderFunctionLogsForDisplay(rows);
|
|
13853
|
+
break;
|
|
13854
|
+
}
|
|
13855
|
+
cursor = page.next_cursor;
|
|
13856
|
+
}
|
|
13857
|
+
if (rows.length === 0 && !wroteEmptyHint) {
|
|
13858
|
+
process.stderr.write(flags.follow ? hasObservedLogs ? "Waiting for new function logs...\n" : "No function logs yet. Waiting for new rows...\n" : "No function logs yet. Trigger the function, then run this command again.\n");
|
|
13859
|
+
wroteEmptyHint = true;
|
|
13860
|
+
}
|
|
13861
|
+
emitLogRows(rows, flags.jsonl);
|
|
13862
|
+
if (!flags.follow) {
|
|
13863
|
+
if (nextCursor) process.stderr.write(`next cursor: ${nextCursor}\n`);
|
|
13864
|
+
return;
|
|
13865
|
+
}
|
|
13866
|
+
completedInitialFollowPoll = true;
|
|
13867
|
+
await sleep$1(flags["poll-interval"] * 1e3);
|
|
13868
|
+
}
|
|
13869
|
+
});
|
|
13870
|
+
}
|
|
13871
|
+
};
|
|
13872
|
+
//#endregion
|
|
13182
13873
|
//#region src/oclif/commands/functions-redeploy.ts
|
|
13183
13874
|
async function runRedeployWithSecrets(api, params) {
|
|
13184
13875
|
const writtenSecrets = [];
|
|
@@ -13260,6 +13951,7 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
13260
13951
|
static summary = "Redeploy a function from a bundled handler file";
|
|
13261
13952
|
static examples = [
|
|
13262
13953
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js",
|
|
13954
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --wait",
|
|
13263
13955
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
13264
13956
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
13265
13957
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-file PRIVATE_KEY=./private-key.pem",
|
|
@@ -13306,11 +13998,29 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
13306
13998
|
multiple: true
|
|
13307
13999
|
}),
|
|
13308
14000
|
"secret-from-stdin": Flags.string({ description: "Secret KEY to read from stdin and write before the redeploy. A single trailing line ending is stripped. Stdin is consumed once, so this flag is not repeatable." }),
|
|
14001
|
+
wait: Flags.boolean({ description: "Wait until the function deploy reaches deployed or failed. Progress is written to stderr; stdout remains the final JSON payload." }),
|
|
14002
|
+
timeout: Flags.integer({
|
|
14003
|
+
default: 120,
|
|
14004
|
+
description: "Seconds to wait when --wait is set before exiting non-zero. Use 0 to wait forever."
|
|
14005
|
+
}),
|
|
14006
|
+
"poll-interval": Flags.integer({
|
|
14007
|
+
default: 2,
|
|
14008
|
+
description: "Seconds between deploy-status polls when --wait is set."
|
|
14009
|
+
}),
|
|
13309
14010
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
13310
14011
|
};
|
|
13311
14012
|
async run() {
|
|
13312
14013
|
const { flags } = await this.parse(FunctionsRedeployCommand);
|
|
13313
14014
|
await runWithTiming(flags.time, async () => {
|
|
14015
|
+
const waitFlagError = validateDeployWaitFlags({
|
|
14016
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
14017
|
+
timeoutSeconds: flags.timeout
|
|
14018
|
+
});
|
|
14019
|
+
if (waitFlagError) {
|
|
14020
|
+
process.stderr.write(`${waitFlagError}\n`);
|
|
14021
|
+
process.exitCode = 1;
|
|
14022
|
+
return;
|
|
14023
|
+
}
|
|
13314
14024
|
const parsedSecrets = resolveSecretFlags({
|
|
13315
14025
|
fromEnv: flags["secret-from-env"] ?? [],
|
|
13316
14026
|
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
@@ -13326,18 +14036,12 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
13326
14036
|
const code = readTextFileFlag(flags.file, "--file");
|
|
13327
14037
|
const sourceMap = flags["source-map-file"] ? readTextFileFlag(flags["source-map-file"], "--source-map-file") : void 0;
|
|
13328
14038
|
emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
|
|
13329
|
-
const
|
|
13330
|
-
const auth = resolveCliAuth({
|
|
14039
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
13331
14040
|
apiKey: flags["api-key"],
|
|
13332
14041
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
13333
14042
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
13334
14043
|
configDir: this.config.configDir
|
|
13335
14044
|
});
|
|
13336
|
-
const apiClient = new PrimitiveApiClient({
|
|
13337
|
-
apiKey: auth.apiKey,
|
|
13338
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
13339
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
13340
|
-
});
|
|
13341
14045
|
const authFailureContext = {
|
|
13342
14046
|
auth,
|
|
13343
14047
|
baseUrlOverridden,
|
|
@@ -13386,6 +14090,42 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
13386
14090
|
process.exitCode = 1;
|
|
13387
14091
|
return;
|
|
13388
14092
|
}
|
|
14093
|
+
if (flags.wait) {
|
|
14094
|
+
const waitResult = await waitForFunctionDeploy({
|
|
14095
|
+
getFunction: (p) => getFunction({
|
|
14096
|
+
client: apiClient.client,
|
|
14097
|
+
path: { id: p.id },
|
|
14098
|
+
responseStyle: "fields"
|
|
14099
|
+
}),
|
|
14100
|
+
id: outcome.result.redeploy.id,
|
|
14101
|
+
initial: outcome.result.redeploy,
|
|
14102
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
14103
|
+
timeoutSeconds: flags.timeout,
|
|
14104
|
+
writeStderr: (chunk) => process.stderr.write(chunk)
|
|
14105
|
+
});
|
|
14106
|
+
if (waitResult.kind === "error") {
|
|
14107
|
+
writeErrorWithHints(waitResult.payload);
|
|
14108
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
14109
|
+
...authFailureContext,
|
|
14110
|
+
payload: waitResult.payload
|
|
14111
|
+
});
|
|
14112
|
+
process.exitCode = 1;
|
|
14113
|
+
return;
|
|
14114
|
+
}
|
|
14115
|
+
if (waitResult.kind === "timeout") {
|
|
14116
|
+
const status = waitResult.lastFunction?.deploy_status ?? "unknown";
|
|
14117
|
+
process.stderr.write(`Timed out after ${flags.timeout}s waiting for function ${outcome.result.redeploy.id} deploy to finish (last status: ${status}).\n`);
|
|
14118
|
+
process.exitCode = 2;
|
|
14119
|
+
return;
|
|
14120
|
+
}
|
|
14121
|
+
this.log(JSON.stringify(waitResult.function, null, 2));
|
|
14122
|
+
if (waitResult.kind === "failed") {
|
|
14123
|
+
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
14124
|
+
process.stderr.write(`Function ${outcome.result.redeploy.id} deploy failed${detail}\n`);
|
|
14125
|
+
process.exitCode = 1;
|
|
14126
|
+
}
|
|
14127
|
+
return;
|
|
14128
|
+
}
|
|
13389
14129
|
this.log(JSON.stringify(outcome.result.redeploy, null, 2));
|
|
13390
14130
|
});
|
|
13391
14131
|
}
|
|
@@ -13511,18 +14251,12 @@ var FunctionsSetSecretCommand = class FunctionsSetSecretCommand extends Command
|
|
|
13511
14251
|
async run() {
|
|
13512
14252
|
const { flags } = await this.parse(FunctionsSetSecretCommand);
|
|
13513
14253
|
await runWithTiming(flags.time, async () => {
|
|
13514
|
-
const
|
|
13515
|
-
const auth = resolveCliAuth({
|
|
14254
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
13516
14255
|
apiKey: flags["api-key"],
|
|
13517
14256
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
13518
14257
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
13519
14258
|
configDir: this.config.configDir
|
|
13520
14259
|
});
|
|
13521
|
-
const apiClient = new PrimitiveApiClient({
|
|
13522
|
-
apiKey: auth.apiKey,
|
|
13523
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
13524
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
13525
|
-
});
|
|
13526
14260
|
const authFailureContext = {
|
|
13527
14261
|
auth,
|
|
13528
14262
|
baseUrlOverridden,
|
|
@@ -13630,11 +14364,17 @@ function buildFunctionTestOutcome(params) {
|
|
|
13630
14364
|
if (params.showSends) outcome.sent_emails = params.detail.replies;
|
|
13631
14365
|
return outcome;
|
|
13632
14366
|
}
|
|
14367
|
+
function writeFunctionTestProgress(message, writeStderr = (chunk) => {
|
|
14368
|
+
process.stderr.write(chunk);
|
|
14369
|
+
}) {
|
|
14370
|
+
writeStderr(`${message}\n`);
|
|
14371
|
+
}
|
|
13633
14372
|
function stringOrNull(value) {
|
|
13634
14373
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
13635
14374
|
}
|
|
13636
14375
|
function findMatchingFunctionEndpoints(params) {
|
|
13637
|
-
const
|
|
14376
|
+
const domainMatches = [];
|
|
14377
|
+
const fallbackMatches = [];
|
|
13638
14378
|
for (const endpoint of params.endpoints) {
|
|
13639
14379
|
if (endpoint.kind !== "function") continue;
|
|
13640
14380
|
if (endpoint.enabled === false) continue;
|
|
@@ -13644,20 +14384,22 @@ function findMatchingFunctionEndpoints(params) {
|
|
|
13644
14384
|
const domainId = stringOrNull(endpoint.domain_id);
|
|
13645
14385
|
if (domainId !== null && (params.inboundDomainId === null || domainId !== params.inboundDomainId)) continue;
|
|
13646
14386
|
const functionId = stringOrNull(endpoint.function_id);
|
|
13647
|
-
|
|
14387
|
+
const match = {
|
|
13648
14388
|
function_id: functionId,
|
|
13649
14389
|
id,
|
|
13650
14390
|
is_current_function: functionId === params.currentFunctionId,
|
|
13651
|
-
scope: domainId === null ? "
|
|
13652
|
-
}
|
|
14391
|
+
scope: domainId === null ? "fallback" : "domain"
|
|
14392
|
+
};
|
|
14393
|
+
if (domainId === null) fallbackMatches.push(match);
|
|
14394
|
+
else domainMatches.push(match);
|
|
13653
14395
|
}
|
|
13654
|
-
return
|
|
14396
|
+
return domainMatches.length > 0 ? domainMatches : fallbackMatches;
|
|
13655
14397
|
}
|
|
13656
14398
|
function formatFunctionEndpointNoiseWarning(params) {
|
|
13657
14399
|
if (params.endpoints.filter((endpoint) => !endpoint.is_current_function).length === 0) return null;
|
|
13658
14400
|
const lines = [`Warning: ${params.endpoints.length} function endpoints may receive mail for ${params.toAddress}:`];
|
|
13659
14401
|
for (const endpoint of params.endpoints) {
|
|
13660
|
-
const scope = endpoint.scope === "
|
|
14402
|
+
const scope = endpoint.scope === "fallback" ? "fallback" : `scoped to ${params.inboundDomain}`;
|
|
13661
14403
|
const current = endpoint.is_current_function ? " (this function)" : "";
|
|
13662
14404
|
const target = endpoint.function_id ? ` -> function ${endpoint.function_id}` : "";
|
|
13663
14405
|
lines.push(`- endpoint ${endpoint.id}${target}, ${scope}${current}`);
|
|
@@ -13736,18 +14478,12 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
13736
14478
|
const { flags } = await this.parse(FunctionsTestFunctionCommand);
|
|
13737
14479
|
const shouldWait = flags.wait || flags["show-sends"];
|
|
13738
14480
|
const shouldShowSends = flags["show-sends"];
|
|
13739
|
-
const baseUrlOverridden =
|
|
13740
|
-
const auth = resolveCliAuth({
|
|
14481
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
13741
14482
|
apiKey: flags["api-key"],
|
|
13742
14483
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
13743
14484
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
13744
14485
|
configDir: this.config.configDir
|
|
13745
14486
|
});
|
|
13746
|
-
const apiClient = new PrimitiveApiClient({
|
|
13747
|
-
apiKey: auth.apiKey,
|
|
13748
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
13749
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
13750
|
-
});
|
|
13751
14487
|
await runWithTiming(flags.time, async () => {
|
|
13752
14488
|
const triggerResult = await testFunction({
|
|
13753
14489
|
client: apiClient.client,
|
|
@@ -13784,7 +14520,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
13784
14520
|
const timeoutMs = flags.timeout * 1e3;
|
|
13785
14521
|
const pollIntervalMs = flags["poll-interval"] * 1e3;
|
|
13786
14522
|
const isExpired = () => flags.timeout > 0 && Date.now() - startedAt > timeoutMs;
|
|
13787
|
-
|
|
14523
|
+
writeFunctionTestProgress(`Waiting for test inbound to arrive at ${invocation.to}...`);
|
|
13788
14524
|
let inboundId;
|
|
13789
14525
|
while (!isExpired()) {
|
|
13790
14526
|
const page = await fetchEmailSearchPage({
|
|
@@ -13813,7 +14549,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
13813
14549
|
await sleep$1(pollIntervalMs);
|
|
13814
14550
|
}
|
|
13815
14551
|
if (!inboundId) this.error(`Timed out after ${flags.timeout}s waiting for test inbound ${invocation.to} to land. Browse ${invocation.watch_url} for the live view.`, { exit: 2 });
|
|
13816
|
-
|
|
14552
|
+
writeFunctionTestProgress(`Inbound landed (${inboundId}). Waiting for function to run...`);
|
|
13817
14553
|
let detail;
|
|
13818
14554
|
while (!isExpired()) {
|
|
13819
14555
|
const result = await getEmail({
|
|
@@ -13887,11 +14623,16 @@ function retryAfterSeconds$1(result) {
|
|
|
13887
14623
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
13888
14624
|
}
|
|
13889
14625
|
async function checkExistingLogin(params) {
|
|
13890
|
-
const
|
|
13891
|
-
|
|
14626
|
+
const requestConfig = resolveCliApiRequestConfig({
|
|
14627
|
+
apiBaseUrl1: params.apiBaseUrl1,
|
|
14628
|
+
configDir: params.configDir
|
|
14629
|
+
});
|
|
14630
|
+
const probeApiBaseUrl1 = requestConfig.apiBaseUrl1 ?? params.credentials.api_base_url_1;
|
|
13892
14631
|
const apiClient = new PrimitiveApiClient({
|
|
13893
14632
|
apiKey: params.credentials.api_key,
|
|
13894
|
-
apiBaseUrl1: probeApiBaseUrl1
|
|
14633
|
+
apiBaseUrl1: probeApiBaseUrl1,
|
|
14634
|
+
apiBaseUrl2: requestConfig.resolvedApiBaseUrl2,
|
|
14635
|
+
headers: requestConfig.headers
|
|
13895
14636
|
});
|
|
13896
14637
|
const result = await (params.checkAccount ?? ((client) => getAccount({
|
|
13897
14638
|
client: client.client,
|
|
@@ -13907,7 +14648,7 @@ async function checkExistingLogin(params) {
|
|
|
13907
14648
|
credentials: params.credentials,
|
|
13908
14649
|
source: "stored"
|
|
13909
14650
|
},
|
|
13910
|
-
baseUrlOverridden,
|
|
14651
|
+
baseUrlOverridden: requestConfig.baseUrlOverridden,
|
|
13911
14652
|
configDir: params.configDir,
|
|
13912
14653
|
payload
|
|
13913
14654
|
})) return { status: "removed_stale" };
|
|
@@ -13953,7 +14694,11 @@ var LoginCommand = class LoginCommand extends Command {
|
|
|
13953
14694
|
}
|
|
13954
14695
|
}
|
|
13955
14696
|
async runWithCredentialLock(flags) {
|
|
13956
|
-
const
|
|
14697
|
+
const { apiClient, requestConfig } = createCliApiClient({
|
|
14698
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
14699
|
+
configDir: this.config.configDir
|
|
14700
|
+
});
|
|
14701
|
+
const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
|
|
13957
14702
|
let existing;
|
|
13958
14703
|
try {
|
|
13959
14704
|
existing = loadCliCredentials(this.config.configDir);
|
|
@@ -13976,7 +14721,6 @@ var LoginCommand = class LoginCommand extends Command {
|
|
|
13976
14721
|
throw cliError$2(existingStatus.message);
|
|
13977
14722
|
} else throw cliError$2(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before logging in again.`);
|
|
13978
14723
|
}
|
|
13979
|
-
const apiClient = new PrimitiveApiClient({ apiBaseUrl1 });
|
|
13980
14724
|
const started = await startCliLogin({
|
|
13981
14725
|
body: { device_name: flags["device-name"] ?? hostname() },
|
|
13982
14726
|
client: apiClient.client,
|
|
@@ -14086,10 +14830,15 @@ var LogoutCommand = class LogoutCommand extends Command {
|
|
|
14086
14830
|
return;
|
|
14087
14831
|
}
|
|
14088
14832
|
if (!credentials) throw cliError$1("Not logged in. Run `primitive login` to create saved CLI credentials.");
|
|
14089
|
-
const
|
|
14833
|
+
const requestConfig = resolveCliApiRequestConfig({
|
|
14834
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
14835
|
+
configDir: this.config.configDir
|
|
14836
|
+
});
|
|
14837
|
+
const apiBaseUrl1 = requestConfig.apiBaseUrl1 ? normalizeApiBaseUrl1(requestConfig.apiBaseUrl1) : credentials.api_base_url_1;
|
|
14090
14838
|
const apiClient = new PrimitiveApiClient({
|
|
14091
14839
|
apiKey: credentials.api_key,
|
|
14092
|
-
apiBaseUrl1
|
|
14840
|
+
apiBaseUrl1,
|
|
14841
|
+
headers: requestConfig.headers
|
|
14093
14842
|
});
|
|
14094
14843
|
const result = await cliLogout({
|
|
14095
14844
|
body: { key_id: credentials.key_id },
|
|
@@ -14116,6 +14865,106 @@ var LogoutCommand = class LogoutCommand extends Command {
|
|
|
14116
14865
|
}
|
|
14117
14866
|
};
|
|
14118
14867
|
//#endregion
|
|
14868
|
+
//#region src/oclif/message-body-sources.ts
|
|
14869
|
+
function defaultReadFile(path) {
|
|
14870
|
+
return readFileSync(path, "utf8");
|
|
14871
|
+
}
|
|
14872
|
+
function defaultReadStdin() {
|
|
14873
|
+
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/string source instead.");
|
|
14874
|
+
return readFileSync(0, "utf8");
|
|
14875
|
+
}
|
|
14876
|
+
function selectedSources(sources) {
|
|
14877
|
+
return sources.filter(([, selected]) => selected).map(([label]) => label);
|
|
14878
|
+
}
|
|
14879
|
+
function readTextFile(path, label, readFile) {
|
|
14880
|
+
try {
|
|
14881
|
+
return {
|
|
14882
|
+
content: readFile(path),
|
|
14883
|
+
kind: "ok"
|
|
14884
|
+
};
|
|
14885
|
+
} catch (error) {
|
|
14886
|
+
return {
|
|
14887
|
+
kind: "error",
|
|
14888
|
+
message: `Could not read ${label} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
14889
|
+
};
|
|
14890
|
+
}
|
|
14891
|
+
}
|
|
14892
|
+
function readTextStdin(label, readStdin) {
|
|
14893
|
+
try {
|
|
14894
|
+
return {
|
|
14895
|
+
content: readStdin(),
|
|
14896
|
+
kind: "ok"
|
|
14897
|
+
};
|
|
14898
|
+
} catch (error) {
|
|
14899
|
+
return {
|
|
14900
|
+
kind: "error",
|
|
14901
|
+
message: `Could not read ${label}: ${error instanceof Error ? error.message : String(error)}`
|
|
14902
|
+
};
|
|
14903
|
+
}
|
|
14904
|
+
}
|
|
14905
|
+
function resolveMessageBodies(input) {
|
|
14906
|
+
const bodySources = selectedSources([
|
|
14907
|
+
["--body", input.body !== void 0],
|
|
14908
|
+
["--body-file", input.bodyFile !== void 0],
|
|
14909
|
+
["--body-stdin", input.bodyStdin === true]
|
|
14910
|
+
]);
|
|
14911
|
+
if (bodySources.length > 1) return {
|
|
14912
|
+
kind: "error",
|
|
14913
|
+
message: `Pass only one plain-text body source (got ${bodySources.join(", ")}).`
|
|
14914
|
+
};
|
|
14915
|
+
const htmlSources = selectedSources([
|
|
14916
|
+
["--html", input.html !== void 0],
|
|
14917
|
+
["--html-file", input.htmlFile !== void 0],
|
|
14918
|
+
["--html-stdin", input.htmlStdin === true]
|
|
14919
|
+
]);
|
|
14920
|
+
if (htmlSources.length > 1) return {
|
|
14921
|
+
kind: "error",
|
|
14922
|
+
message: `Pass only one HTML body source (got ${htmlSources.join(", ")}).`
|
|
14923
|
+
};
|
|
14924
|
+
const stdinSources = selectedSources([["--body-stdin", input.bodyStdin === true], ["--html-stdin", input.htmlStdin === true]]);
|
|
14925
|
+
if (stdinSources.length > 1) return {
|
|
14926
|
+
kind: "error",
|
|
14927
|
+
message: `Stdin can only be consumed once (got ${stdinSources.join(", ")}).`
|
|
14928
|
+
};
|
|
14929
|
+
if (bodySources.length === 0 && htmlSources.length === 0) return {
|
|
14930
|
+
kind: "error",
|
|
14931
|
+
message: "Either a plain-text body source or an HTML body source is required."
|
|
14932
|
+
};
|
|
14933
|
+
const readFile = input.readFile ?? defaultReadFile;
|
|
14934
|
+
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
14935
|
+
let body = input.body;
|
|
14936
|
+
let html = input.html;
|
|
14937
|
+
if (input.bodyFile !== void 0) {
|
|
14938
|
+
const result = readTextFile(input.bodyFile, "--body-file", readFile);
|
|
14939
|
+
if (result.kind === "error") return result;
|
|
14940
|
+
body = result.content;
|
|
14941
|
+
}
|
|
14942
|
+
if (input.bodyStdin === true) {
|
|
14943
|
+
const result = readTextStdin("--body-stdin", readStdin);
|
|
14944
|
+
if (result.kind === "error") return result;
|
|
14945
|
+
body = result.content;
|
|
14946
|
+
}
|
|
14947
|
+
if (input.htmlFile !== void 0) {
|
|
14948
|
+
const result = readTextFile(input.htmlFile, "--html-file", readFile);
|
|
14949
|
+
if (result.kind === "error") return result;
|
|
14950
|
+
html = result.content;
|
|
14951
|
+
}
|
|
14952
|
+
if (input.htmlStdin === true) {
|
|
14953
|
+
const result = readTextStdin("--html-stdin", readStdin);
|
|
14954
|
+
if (result.kind === "error") return result;
|
|
14955
|
+
html = result.content;
|
|
14956
|
+
}
|
|
14957
|
+
if (!body && !html) return {
|
|
14958
|
+
kind: "error",
|
|
14959
|
+
message: "Either a non-empty plain-text body or a non-empty HTML body is required."
|
|
14960
|
+
};
|
|
14961
|
+
return {
|
|
14962
|
+
...body !== void 0 ? { body } : {},
|
|
14963
|
+
...html !== void 0 ? { html } : {},
|
|
14964
|
+
kind: "ok"
|
|
14965
|
+
};
|
|
14966
|
+
}
|
|
14967
|
+
//#endregion
|
|
14119
14968
|
//#region src/oclif/commands/reply.ts
|
|
14120
14969
|
var ReplyCommand = class ReplyCommand extends Command {
|
|
14121
14970
|
static description = `Reply to an inbound email.
|
|
@@ -14124,6 +14973,7 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
14124
14973
|
static summary = "Reply to an inbound email";
|
|
14125
14974
|
static examples = [
|
|
14126
14975
|
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
14976
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
|
|
14127
14977
|
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
14128
14978
|
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
14129
14979
|
];
|
|
@@ -14147,7 +14997,11 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
14147
14997
|
required: true
|
|
14148
14998
|
}),
|
|
14149
14999
|
body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
|
|
15000
|
+
"body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
15001
|
+
"body-stdin": Flags.boolean({ description: "Read the plain-text reply body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
|
|
14150
15002
|
html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
|
|
15003
|
+
"html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
15004
|
+
"html-stdin": Flags.boolean({ description: "Read the HTML reply body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
|
|
14151
15005
|
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
14152
15006
|
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the reply for delivery." }),
|
|
14153
15007
|
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
@@ -14155,24 +15009,26 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
14155
15009
|
};
|
|
14156
15010
|
async run() {
|
|
14157
15011
|
const { flags } = await this.parse(ReplyCommand);
|
|
14158
|
-
|
|
15012
|
+
const bodies = resolveMessageBodies({
|
|
15013
|
+
body: flags.body,
|
|
15014
|
+
bodyFile: flags["body-file"],
|
|
15015
|
+
bodyStdin: flags["body-stdin"],
|
|
15016
|
+
html: flags.html,
|
|
15017
|
+
htmlFile: flags["html-file"],
|
|
15018
|
+
htmlStdin: flags["html-stdin"]
|
|
15019
|
+
});
|
|
15020
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
14159
15021
|
await runWithTiming(flags.time, async () => {
|
|
14160
|
-
const
|
|
14161
|
-
const auth = resolveCliAuth({
|
|
15022
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
14162
15023
|
apiKey: flags["api-key"],
|
|
14163
15024
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
14164
15025
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
14165
15026
|
configDir: this.config.configDir
|
|
14166
15027
|
});
|
|
14167
|
-
const apiClient = new PrimitiveApiClient({
|
|
14168
|
-
apiKey: auth.apiKey,
|
|
14169
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
14170
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
14171
|
-
});
|
|
14172
15028
|
const result = await replyToEmail({
|
|
14173
15029
|
body: {
|
|
14174
|
-
...
|
|
14175
|
-
...
|
|
15030
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
15031
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
14176
15032
|
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
14177
15033
|
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
14178
15034
|
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
@@ -14247,6 +15103,7 @@ var SendCommand = class SendCommand extends Command {
|
|
|
14247
15103
|
static summary = "Send an email (simplified, agent-friendly)";
|
|
14248
15104
|
static examples = [
|
|
14249
15105
|
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
15106
|
+
"<%= config.bin %> send --to alice@example.com --body-file ./message.txt",
|
|
14250
15107
|
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
14251
15108
|
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
14252
15109
|
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
|
|
@@ -14274,7 +15131,11 @@ var SendCommand = class SendCommand extends Command {
|
|
|
14274
15131
|
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
14275
15132
|
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
14276
15133
|
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
15134
|
+
"body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
15135
|
+
"body-stdin": Flags.boolean({ description: "Read the plain-text message body from stdin. Mutually exclusive with --body and --body-file. Stdin can only be consumed once." }),
|
|
14277
15136
|
html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
|
|
15137
|
+
"html-file": Flags.string({ description: "Read the HTML message body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
15138
|
+
"html-stdin": Flags.boolean({ description: "Read the HTML message body from stdin. Mutually exclusive with --html and --html-file. Stdin can only be consumed once." }),
|
|
14278
15139
|
"in-reply-to": Flags.string({ description: "Message-Id of the parent email when threading a reply on the wire. For replying to an inbound message you received, prefer `primitive reply --id <inbound-id>`." }),
|
|
14279
15140
|
wait: Flags.boolean({ description: "Block until the receiving MTA returns an outcome. Without --wait, the call returns once Primitive has accepted the message for delivery." }),
|
|
14280
15141
|
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
@@ -14282,34 +15143,36 @@ var SendCommand = class SendCommand extends Command {
|
|
|
14282
15143
|
};
|
|
14283
15144
|
async run() {
|
|
14284
15145
|
const { flags } = await this.parse(SendCommand);
|
|
14285
|
-
|
|
15146
|
+
const bodies = resolveMessageBodies({
|
|
15147
|
+
body: flags.body,
|
|
15148
|
+
bodyFile: flags["body-file"],
|
|
15149
|
+
bodyStdin: flags["body-stdin"],
|
|
15150
|
+
html: flags.html,
|
|
15151
|
+
htmlFile: flags["html-file"],
|
|
15152
|
+
htmlStdin: flags["html-stdin"]
|
|
15153
|
+
});
|
|
15154
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
14286
15155
|
await runWithTiming(flags.time, async () => {
|
|
14287
|
-
const
|
|
14288
|
-
const auth = resolveCliAuth({
|
|
15156
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
14289
15157
|
apiKey: flags["api-key"],
|
|
14290
15158
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
14291
15159
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
14292
15160
|
configDir: this.config.configDir
|
|
14293
15161
|
});
|
|
14294
|
-
const apiClient = new PrimitiveApiClient({
|
|
14295
|
-
apiKey: auth.apiKey,
|
|
14296
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
14297
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
14298
|
-
});
|
|
14299
15162
|
const authFailureContext = {
|
|
14300
15163
|
auth,
|
|
14301
15164
|
baseUrlOverridden,
|
|
14302
15165
|
configDir: this.config.configDir
|
|
14303
15166
|
};
|
|
14304
15167
|
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
14305
|
-
const subject = flags.subject ?? (
|
|
15168
|
+
const subject = flags.subject ?? (bodies.body ? deriveSubject(bodies.body) : "Message");
|
|
14306
15169
|
const result = await sendEmail({
|
|
14307
15170
|
body: {
|
|
14308
15171
|
from,
|
|
14309
15172
|
to: flags.to,
|
|
14310
15173
|
subject,
|
|
14311
|
-
...
|
|
14312
|
-
...
|
|
15174
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
15175
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
14313
15176
|
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
14314
15177
|
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
14315
15178
|
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
@@ -14568,7 +15431,11 @@ async function runSignupWithCredentialLock(params) {
|
|
|
14568
15431
|
const startFn = deps.startCliSignup ?? startCliSignup;
|
|
14569
15432
|
const verifyFn = deps.verifyCliSignup ?? verifyCliSignup;
|
|
14570
15433
|
const checkExistingLoginFn = deps.checkExistingLogin ?? checkExistingLogin;
|
|
14571
|
-
const
|
|
15434
|
+
const { apiClient, requestConfig } = createCliApiClient({
|
|
15435
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
15436
|
+
configDir
|
|
15437
|
+
});
|
|
15438
|
+
const apiBaseUrl1 = requestConfig.resolvedApiBaseUrl1;
|
|
14572
15439
|
let existing;
|
|
14573
15440
|
try {
|
|
14574
15441
|
existing = loadCliCredentials(configDir);
|
|
@@ -14592,7 +15459,6 @@ async function runSignupWithCredentialLock(params) {
|
|
|
14592
15459
|
} else throw cliError(`Already logged in${existing.org_name ? ` for ${existing.org_name}` : ""}. Run \`primitive logout\` before creating a new account.`);
|
|
14593
15460
|
}
|
|
14594
15461
|
if (flags.force) deletePendingCliSignup(configDir);
|
|
14595
|
-
const apiClient = new PrimitiveApiClient({ apiBaseUrl1 });
|
|
14596
15462
|
let start = flags.force ? null : loadPendingCliSignup(configDir, apiBaseUrl1);
|
|
14597
15463
|
const resumed = Boolean(start);
|
|
14598
15464
|
if (start) process$1.stderr.write(`Continuing pending Primitive CLI signup for ${start.email}.\n`);
|
|
@@ -14749,19 +15615,14 @@ var WhoamiCommand = class WhoamiCommand extends Command {
|
|
|
14749
15615
|
async run() {
|
|
14750
15616
|
const { flags } = await this.parse(WhoamiCommand);
|
|
14751
15617
|
await runWithTiming(flags.time, async () => {
|
|
14752
|
-
const
|
|
14753
|
-
const auth = resolveCliAuth({
|
|
15618
|
+
const { apiClient, auth, baseUrlOverridden } = createAuthenticatedCliApiClient({
|
|
14754
15619
|
apiKey: flags["api-key"],
|
|
14755
15620
|
apiBaseUrl1: flags["api-base-url-1"],
|
|
14756
15621
|
apiBaseUrl2: flags["api-base-url-2"],
|
|
14757
15622
|
configDir: this.config.configDir
|
|
14758
15623
|
});
|
|
14759
15624
|
const result = await getAccount({
|
|
14760
|
-
client:
|
|
14761
|
-
apiKey: auth.apiKey,
|
|
14762
|
-
apiBaseUrl1: auth.apiBaseUrl1,
|
|
14763
|
-
apiBaseUrl2: auth.apiBaseUrl2
|
|
14764
|
-
}).client,
|
|
15625
|
+
client: apiClient.client,
|
|
14765
15626
|
responseStyle: "fields"
|
|
14766
15627
|
});
|
|
14767
15628
|
if (result.error) {
|
|
@@ -14849,6 +15710,7 @@ function renderFishCompletion(binName) {
|
|
|
14849
15710
|
lines.push(`complete -c ${binName} -f -n '__fish_${binName}_topic_needs_subcommand ${fishEscape(topic)}' -a '${fishEscape(operation.command)}' -d '${fishEscape(operation.summary ?? `${operation.method} ${operation.path}`)}'`);
|
|
14850
15711
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l '${fishEscape(parameter.name.replace(/_/g, "-"))}' -r -d '${fishEscape(parameter.description ?? parameter.name)}'`);
|
|
14851
15712
|
lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'api-key' -r -d 'Primitive API key (defaults to PRIMITIVE_API_KEY or saved primitive login credentials)'`);
|
|
15713
|
+
if (!operation.binaryResponse) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'envelope' -d 'Print the full response envelope, including pagination metadata'`);
|
|
14852
15714
|
if (operation.hasJsonBody) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'body' -r -d 'JSON request body'`, `complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'body-file' -r -d 'Path to a JSON file used as the request body'`);
|
|
14853
15715
|
if (operation.binaryResponse) lines.push(`complete -c ${binName} -n '${operationCondition(operation).replace(BIN_PLACEHOLDER, binName)}' -l 'output' -r -d 'Write binary response bytes to a file'`);
|
|
14854
15716
|
}
|
|
@@ -14966,7 +15828,6 @@ const CANONICAL_OPERATION_ALIASES = {
|
|
|
14966
15828
|
"functions:get": "functions:get-function",
|
|
14967
15829
|
"functions:list": "functions:list-functions",
|
|
14968
15830
|
"functions:list-secrets": "functions:list-function-secrets",
|
|
14969
|
-
"functions:logs": "functions:list-function-logs",
|
|
14970
15831
|
"sending:get": "sending:get-sent-email",
|
|
14971
15832
|
"sending:list": "sending:list-sent-emails",
|
|
14972
15833
|
"sending:permissions": "sending:get-send-permissions",
|
|
@@ -14979,6 +15840,7 @@ const CANONICAL_OPERATION_ALIASES = {
|
|
|
14979
15840
|
};
|
|
14980
15841
|
const DESCRIBE_OPERATION_ALIASES = {
|
|
14981
15842
|
...CANONICAL_OPERATION_ALIASES,
|
|
15843
|
+
"functions:logs": "functions:list-function-logs",
|
|
14982
15844
|
reply: "sending:reply-to-email"
|
|
14983
15845
|
};
|
|
14984
15846
|
function resolveOperationAlias(id) {
|
|
@@ -14989,6 +15851,10 @@ const generatedCommands = Object.fromEntries(operationManifest.filter((operation
|
|
|
14989
15851
|
const COMMANDS = {
|
|
14990
15852
|
completion: CompletionCommand,
|
|
14991
15853
|
"list-operations": ListOperationsCommand,
|
|
15854
|
+
"config:list": ConfigListCommand,
|
|
15855
|
+
"config:reset": ConfigResetCommand,
|
|
15856
|
+
"config:set": ConfigSetCommand,
|
|
15857
|
+
"config:use": ConfigUseCommand,
|
|
14992
15858
|
describe: DescribeCommand,
|
|
14993
15859
|
send: SendCommand,
|
|
14994
15860
|
reply: ReplyCommand,
|
|
@@ -15012,7 +15878,8 @@ const COMMANDS = {
|
|
|
15012
15878
|
if (!command) throw new Error(`Missing generated command target for alias ${alias}`);
|
|
15013
15879
|
return [alias, command];
|
|
15014
15880
|
})),
|
|
15015
|
-
...generatedCommands
|
|
15881
|
+
...generatedCommands,
|
|
15882
|
+
"functions:logs": FunctionsLogsCommand
|
|
15016
15883
|
};
|
|
15017
15884
|
//#endregion
|
|
15018
15885
|
export { CANONICAL_OPERATION_ALIASES, COMMANDS, lookupOperation };
|