@primitivedotdev/cli 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/oclif/index.js +572 -25
- package/package.json +1 -1
package/dist/oclif/index.js
CHANGED
|
@@ -7375,8 +7375,11 @@ const operationManifest = [
|
|
|
7375
7375
|
"type": "string"
|
|
7376
7376
|
},
|
|
7377
7377
|
{
|
|
7378
|
+
"default": 50,
|
|
7378
7379
|
"description": "Number of results per page",
|
|
7379
7380
|
"enum": null,
|
|
7381
|
+
"maximum": 100,
|
|
7382
|
+
"minimum": 1,
|
|
7380
7383
|
"name": "limit",
|
|
7381
7384
|
"required": false,
|
|
7382
7385
|
"type": "integer"
|
|
@@ -7645,13 +7648,17 @@ const operationManifest = [
|
|
|
7645
7648
|
"type": "string"
|
|
7646
7649
|
},
|
|
7647
7650
|
{
|
|
7651
|
+
"default": 50,
|
|
7648
7652
|
"description": "Number of results per page",
|
|
7649
7653
|
"enum": null,
|
|
7654
|
+
"maximum": 100,
|
|
7655
|
+
"minimum": 1,
|
|
7650
7656
|
"name": "limit",
|
|
7651
7657
|
"required": false,
|
|
7652
7658
|
"type": "integer"
|
|
7653
7659
|
},
|
|
7654
7660
|
{
|
|
7661
|
+
"default": "true",
|
|
7655
7662
|
"description": "Include subject/body highlight snippets when text search is active.",
|
|
7656
7663
|
"enum": ["true", "false"],
|
|
7657
7664
|
"name": "snippet",
|
|
@@ -7659,6 +7666,7 @@ const operationManifest = [
|
|
|
7659
7666
|
"type": "string"
|
|
7660
7667
|
},
|
|
7661
7668
|
{
|
|
7669
|
+
"default": "true",
|
|
7662
7670
|
"description": "Include facet counts for sender, domain, status, and attachment presence.",
|
|
7663
7671
|
"enum": ["true", "false"],
|
|
7664
7672
|
"name": "include_facets",
|
|
@@ -9114,8 +9122,11 @@ const operationManifest = [
|
|
|
9114
9122
|
"type": "string"
|
|
9115
9123
|
}],
|
|
9116
9124
|
"queryParams": [{
|
|
9125
|
+
"default": 50,
|
|
9117
9126
|
"description": "Maximum number of rows to return. Clamped to 1..200; default\n50.\n",
|
|
9118
9127
|
"enum": null,
|
|
9128
|
+
"maximum": 200,
|
|
9129
|
+
"minimum": 1,
|
|
9119
9130
|
"name": "limit",
|
|
9120
9131
|
"required": false,
|
|
9121
9132
|
"type": "integer"
|
|
@@ -9950,8 +9961,11 @@ const operationManifest = [
|
|
|
9950
9961
|
"type": "string"
|
|
9951
9962
|
},
|
|
9952
9963
|
{
|
|
9964
|
+
"default": 50,
|
|
9953
9965
|
"description": "Number of results per page",
|
|
9954
9966
|
"enum": null,
|
|
9967
|
+
"maximum": 100,
|
|
9968
|
+
"minimum": 1,
|
|
9955
9969
|
"name": "limit",
|
|
9956
9970
|
"required": false,
|
|
9957
9971
|
"type": "integer"
|
|
@@ -10523,8 +10537,11 @@ const operationManifest = [
|
|
|
10523
10537
|
"type": "string"
|
|
10524
10538
|
},
|
|
10525
10539
|
{
|
|
10540
|
+
"default": 50,
|
|
10526
10541
|
"description": "Number of results per page",
|
|
10527
10542
|
"enum": null,
|
|
10543
|
+
"maximum": 100,
|
|
10544
|
+
"minimum": 1,
|
|
10528
10545
|
"name": "limit",
|
|
10529
10546
|
"required": false,
|
|
10530
10547
|
"type": "integer"
|
|
@@ -10988,6 +11005,22 @@ function flagName(parameterName) {
|
|
|
10988
11005
|
function flagDescription(parameter) {
|
|
10989
11006
|
return parameter.description ?? parameter.name;
|
|
10990
11007
|
}
|
|
11008
|
+
const numberFlag = Flags.custom({ async parse(input, _context, options) {
|
|
11009
|
+
const trimmed = input.trim();
|
|
11010
|
+
if (trimmed === "") throw new Errors.CLIError(`Expected a number but received: ${input}`);
|
|
11011
|
+
const value = Number(trimmed);
|
|
11012
|
+
if (!Number.isFinite(value)) throw new Errors.CLIError(`Expected a number but received: ${input}`);
|
|
11013
|
+
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}`);
|
|
11014
|
+
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}`);
|
|
11015
|
+
return value;
|
|
11016
|
+
} });
|
|
11017
|
+
function numericFlagOptions(parameter) {
|
|
11018
|
+
return {
|
|
11019
|
+
...typeof parameter.default === "number" ? { default: parameter.default } : {},
|
|
11020
|
+
...typeof parameter.maximum === "number" ? { max: parameter.maximum } : {},
|
|
11021
|
+
...typeof parameter.minimum === "number" ? { min: parameter.minimum } : {}
|
|
11022
|
+
};
|
|
11023
|
+
}
|
|
10991
11024
|
function extractBodyFields(schema) {
|
|
10992
11025
|
if (!schema || typeof schema !== "object") return [];
|
|
10993
11026
|
const properties = schema.properties;
|
|
@@ -11003,7 +11036,8 @@ function extractBodyFields(schema) {
|
|
|
11003
11036
|
if (typeof t === "string") {
|
|
11004
11037
|
displayType = t;
|
|
11005
11038
|
if (t === "string") kind = "string";
|
|
11006
|
-
else if (t === "integer"
|
|
11039
|
+
else if (t === "integer") kind = "integer";
|
|
11040
|
+
else if (t === "number") kind = "number";
|
|
11007
11041
|
else if (t === "boolean") kind = "boolean";
|
|
11008
11042
|
else if (t === "array") {
|
|
11009
11043
|
const items = propSchema.items;
|
|
@@ -11019,7 +11053,8 @@ function extractBodyFields(schema) {
|
|
|
11019
11053
|
const single = nonNull[0];
|
|
11020
11054
|
displayType = `${single}?`;
|
|
11021
11055
|
if (single === "string") kind = "string";
|
|
11022
|
-
else if (single === "integer"
|
|
11056
|
+
else if (single === "integer") kind = "integer";
|
|
11057
|
+
else if (single === "number") kind = "number";
|
|
11023
11058
|
else if (single === "boolean") kind = "boolean";
|
|
11024
11059
|
else kind = "complex";
|
|
11025
11060
|
} else {
|
|
@@ -11036,7 +11071,9 @@ function extractBodyFields(schema) {
|
|
|
11036
11071
|
required: required.has(name),
|
|
11037
11072
|
displayType,
|
|
11038
11073
|
kind,
|
|
11039
|
-
...enumValues && enumValues.length > 0 ? { enumValues } : {}
|
|
11074
|
+
...enumValues && enumValues.length > 0 ? { enumValues } : {},
|
|
11075
|
+
...typeof propSchema.maximum === "number" ? { maximum: propSchema.maximum } : {},
|
|
11076
|
+
...typeof propSchema.minimum === "number" ? { minimum: propSchema.minimum } : {}
|
|
11040
11077
|
});
|
|
11041
11078
|
}
|
|
11042
11079
|
return fields.sort((a, b) => {
|
|
@@ -11084,7 +11121,14 @@ function flagForParameter(parameter) {
|
|
|
11084
11121
|
required: parameter.required
|
|
11085
11122
|
};
|
|
11086
11123
|
if (parameter.type === "boolean") return Flags.boolean(common);
|
|
11087
|
-
if (parameter.type === "integer") return Flags.integer(
|
|
11124
|
+
if (parameter.type === "integer") return Flags.integer({
|
|
11125
|
+
...common,
|
|
11126
|
+
...numericFlagOptions(parameter)
|
|
11127
|
+
});
|
|
11128
|
+
if (parameter.type === "number") return numberFlag({
|
|
11129
|
+
...common,
|
|
11130
|
+
...numericFlagOptions(parameter)
|
|
11131
|
+
});
|
|
11088
11132
|
if (parameter.enum && parameter.enum.length > 0) return Flags.string({
|
|
11089
11133
|
...common,
|
|
11090
11134
|
options: parameter.enum
|
|
@@ -11234,12 +11278,20 @@ const RESERVED_FLAG_NAMES = new Set([
|
|
|
11234
11278
|
"api-base-url-2",
|
|
11235
11279
|
"raw-body",
|
|
11236
11280
|
"body-file",
|
|
11281
|
+
"envelope",
|
|
11237
11282
|
"output"
|
|
11238
11283
|
]);
|
|
11239
11284
|
function bodyFieldFlag(field) {
|
|
11240
11285
|
const common = { description: field.description || field.name };
|
|
11241
11286
|
if (field.kind === "boolean") return Flags.boolean(common);
|
|
11242
|
-
if (field.kind === "integer") return Flags.integer(
|
|
11287
|
+
if (field.kind === "integer") return Flags.integer({
|
|
11288
|
+
...common,
|
|
11289
|
+
...numericFlagOptions(field)
|
|
11290
|
+
});
|
|
11291
|
+
if (field.kind === "number") return numberFlag({
|
|
11292
|
+
...common,
|
|
11293
|
+
...numericFlagOptions(field)
|
|
11294
|
+
});
|
|
11243
11295
|
if (field.enumValues) return Flags.string({
|
|
11244
11296
|
...common,
|
|
11245
11297
|
options: field.enumValues
|
|
@@ -11264,6 +11316,7 @@ function buildFlags(operation) {
|
|
|
11264
11316
|
}),
|
|
11265
11317
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
11266
11318
|
};
|
|
11319
|
+
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
11320
|
for (const parameter of [...operation.pathParams, ...operation.queryParams]) flags[flagName(parameter.name)] = flagForParameter(parameter);
|
|
11268
11321
|
const bodyFieldFlagToProperty = /* @__PURE__ */ new Map();
|
|
11269
11322
|
if (operation.hasJsonBody) {
|
|
@@ -11302,6 +11355,9 @@ function collectValues(parameters, flags) {
|
|
|
11302
11355
|
}
|
|
11303
11356
|
return values;
|
|
11304
11357
|
}
|
|
11358
|
+
function operationOutputPayload(envelope, includeEnvelope) {
|
|
11359
|
+
return includeEnvelope ? envelope ?? null : envelope?.data ?? null;
|
|
11360
|
+
}
|
|
11305
11361
|
const OPERATION_HINTS = {
|
|
11306
11362
|
createFunction: "Tip: prefer `primitive functions deploy --name <name> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
11307
11363
|
updateFunction: "Tip: prefer `primitive functions redeploy --id <id> --file <bundle>` for file-input ergonomics. This raw command exists for callers passing JSON.",
|
|
@@ -11409,7 +11465,7 @@ function createOperationCommand(operation) {
|
|
|
11409
11465
|
writeIdempotentReplayBannerIfReplay(envelope?.data, { write: (chunk) => {
|
|
11410
11466
|
process.stderr.write(chunk);
|
|
11411
11467
|
} });
|
|
11412
|
-
this.log(JSON.stringify(envelope
|
|
11468
|
+
this.log(JSON.stringify(operationOutputPayload(envelope, parsedFlags.envelope === true), null, 2));
|
|
11413
11469
|
});
|
|
11414
11470
|
}
|
|
11415
11471
|
}
|
|
@@ -12167,6 +12223,92 @@ var EmailsWatchCommand = class EmailsWatchCommand extends Command {
|
|
|
12167
12223
|
}
|
|
12168
12224
|
};
|
|
12169
12225
|
//#endregion
|
|
12226
|
+
//#region src/oclif/function-deploy-wait.ts
|
|
12227
|
+
function validateDeployWaitFlags(params) {
|
|
12228
|
+
if (params.timeoutSeconds < 0) return "--timeout must be greater than or equal to 0.";
|
|
12229
|
+
if (params.pollIntervalSeconds <= 0) return "--poll-interval must be greater than 0.";
|
|
12230
|
+
return null;
|
|
12231
|
+
}
|
|
12232
|
+
function isTerminal(status) {
|
|
12233
|
+
return status === "deployed" || status === "failed";
|
|
12234
|
+
}
|
|
12235
|
+
function resultForTerminal(snapshot) {
|
|
12236
|
+
if (snapshot.deploy_status === "failed") return {
|
|
12237
|
+
function: snapshot,
|
|
12238
|
+
kind: "failed"
|
|
12239
|
+
};
|
|
12240
|
+
return {
|
|
12241
|
+
function: snapshot,
|
|
12242
|
+
kind: "ok"
|
|
12243
|
+
};
|
|
12244
|
+
}
|
|
12245
|
+
function toDeployWaitSnapshot(value) {
|
|
12246
|
+
return {
|
|
12247
|
+
...value.created_at !== void 0 ? { created_at: value.created_at } : {},
|
|
12248
|
+
...value.deploy_error !== void 0 ? { deploy_error: value.deploy_error } : {},
|
|
12249
|
+
deploy_status: value.deploy_status,
|
|
12250
|
+
...value.deployed_at !== void 0 ? { deployed_at: value.deployed_at } : {},
|
|
12251
|
+
gateway_url: value.gateway_url,
|
|
12252
|
+
id: value.id,
|
|
12253
|
+
name: value.name,
|
|
12254
|
+
...value.updated_at !== void 0 ? { updated_at: value.updated_at } : {}
|
|
12255
|
+
};
|
|
12256
|
+
}
|
|
12257
|
+
function elapsedSeconds(startedAt, now) {
|
|
12258
|
+
return Math.max(0, Math.round((now() - startedAt) / 1e3));
|
|
12259
|
+
}
|
|
12260
|
+
async function defaultSleep(ms) {
|
|
12261
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
12262
|
+
}
|
|
12263
|
+
async function waitForFunctionDeploy(params) {
|
|
12264
|
+
const now = params.now ?? Date.now;
|
|
12265
|
+
const sleep = params.sleep ?? defaultSleep;
|
|
12266
|
+
const writeStderr = params.writeStderr ?? ((chunk) => {
|
|
12267
|
+
process.stderr.write(chunk);
|
|
12268
|
+
});
|
|
12269
|
+
const startedAt = now();
|
|
12270
|
+
const timeoutMs = params.timeoutSeconds * 1e3;
|
|
12271
|
+
const pollIntervalMs = params.pollIntervalSeconds * 1e3;
|
|
12272
|
+
const hasTimeout = params.timeoutSeconds > 0;
|
|
12273
|
+
let last = params.initial ? toDeployWaitSnapshot(params.initial) : null;
|
|
12274
|
+
let lastStatus = last?.deploy_status ?? "unknown";
|
|
12275
|
+
if (last && isTerminal(last.deploy_status)) return resultForTerminal(last);
|
|
12276
|
+
writeStderr(`Waiting for function ${params.id} deploy to finish (current status: ${lastStatus})...\n`);
|
|
12277
|
+
while (true) {
|
|
12278
|
+
const elapsedMs = now() - startedAt;
|
|
12279
|
+
if (hasTimeout && elapsedMs >= timeoutMs) return {
|
|
12280
|
+
elapsedSeconds: elapsedSeconds(startedAt, now),
|
|
12281
|
+
kind: "timeout",
|
|
12282
|
+
lastFunction: last
|
|
12283
|
+
};
|
|
12284
|
+
await sleep(hasTimeout ? Math.min(pollIntervalMs, Math.max(0, timeoutMs - elapsedMs)) : pollIntervalMs);
|
|
12285
|
+
if (hasTimeout && now() - startedAt >= timeoutMs) return {
|
|
12286
|
+
elapsedSeconds: elapsedSeconds(startedAt, now),
|
|
12287
|
+
kind: "timeout",
|
|
12288
|
+
lastFunction: last
|
|
12289
|
+
};
|
|
12290
|
+
const result = await params.getFunction({ id: params.id });
|
|
12291
|
+
if (result.error) return {
|
|
12292
|
+
kind: "error",
|
|
12293
|
+
payload: extractErrorPayload(result.error)
|
|
12294
|
+
};
|
|
12295
|
+
const fetched = result.data?.data;
|
|
12296
|
+
if (!fetched) return {
|
|
12297
|
+
kind: "error",
|
|
12298
|
+
payload: {
|
|
12299
|
+
code: "client_error",
|
|
12300
|
+
message: "Get function returned no data while waiting for deploy"
|
|
12301
|
+
}
|
|
12302
|
+
};
|
|
12303
|
+
last = toDeployWaitSnapshot(fetched);
|
|
12304
|
+
if (last.deploy_status !== lastStatus) {
|
|
12305
|
+
lastStatus = last.deploy_status;
|
|
12306
|
+
writeStderr(`Function ${params.id} deploy status: ${last.deploy_status}\n`);
|
|
12307
|
+
}
|
|
12308
|
+
if (isTerminal(last.deploy_status)) return resultForTerminal(last);
|
|
12309
|
+
}
|
|
12310
|
+
}
|
|
12311
|
+
//#endregion
|
|
12170
12312
|
//#region src/oclif/lint/raw-send-mail-fetch.ts
|
|
12171
12313
|
const RAW_SEND_MAIL_FETCH_REGEX = /fetch\s*\(\s*[`'"][^`'"]*primitive\.dev[^`'"]*\/send-mail(?![A-Za-z0-9_-])/g;
|
|
12172
12314
|
const SNIPPET_PADDING = 60;
|
|
@@ -12208,8 +12350,8 @@ function resolveSecretFlags(input) {
|
|
|
12208
12350
|
const secrets = [];
|
|
12209
12351
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
12210
12352
|
const env = input.env ?? process.env;
|
|
12211
|
-
const readFile = input.readFile ?? defaultReadFile;
|
|
12212
|
-
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
12353
|
+
const readFile = input.readFile ?? defaultReadFile$1;
|
|
12354
|
+
const readStdin = input.readStdin ?? defaultReadStdin$1;
|
|
12213
12355
|
const envFileCache = /* @__PURE__ */ new Map();
|
|
12214
12356
|
const reserveSecretKey = (key, sourceLabel) => {
|
|
12215
12357
|
const keyError = validateKey(key, sourceLabel);
|
|
@@ -12291,6 +12433,8 @@ function resolveSecretFlags(input) {
|
|
|
12291
12433
|
};
|
|
12292
12434
|
}
|
|
12293
12435
|
function resolveSingleSecretValue(input) {
|
|
12436
|
+
const keyError = validateKey(input.key, "--key");
|
|
12437
|
+
if (keyError) return keyError;
|
|
12294
12438
|
if ([
|
|
12295
12439
|
input.value !== void 0 ? "--value" : null,
|
|
12296
12440
|
input.valueFromEnv !== void 0 ? "--value-from-env" : null,
|
|
@@ -12302,8 +12446,8 @@ function resolveSingleSecretValue(input) {
|
|
|
12302
12446
|
message: "Pass exactly one of --value, --value-from-env, --value-file, --value-from-env-file, or --stdin."
|
|
12303
12447
|
};
|
|
12304
12448
|
const env = input.env ?? process.env;
|
|
12305
|
-
const readFile = input.readFile ?? defaultReadFile;
|
|
12306
|
-
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
12449
|
+
const readFile = input.readFile ?? defaultReadFile$1;
|
|
12450
|
+
const readStdin = input.readStdin ?? defaultReadStdin$1;
|
|
12307
12451
|
if (input.value !== void 0) return {
|
|
12308
12452
|
kind: "ok",
|
|
12309
12453
|
value: input.value
|
|
@@ -12341,10 +12485,10 @@ function resolveSingleSecretValue(input) {
|
|
|
12341
12485
|
message: "Pass exactly one of --value, --value-from-env, --value-file, --value-from-env-file, or --stdin."
|
|
12342
12486
|
};
|
|
12343
12487
|
}
|
|
12344
|
-
function defaultReadFile(path) {
|
|
12488
|
+
function defaultReadFile$1(path) {
|
|
12345
12489
|
return readFileSync(path, "utf8");
|
|
12346
12490
|
}
|
|
12347
|
-
function defaultReadStdin() {
|
|
12491
|
+
function defaultReadStdin$1() {
|
|
12348
12492
|
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
12493
|
return readFileSync(0, "utf8");
|
|
12350
12494
|
}
|
|
@@ -12631,6 +12775,7 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
12631
12775
|
static summary = "Deploy a new function from a bundled handler file";
|
|
12632
12776
|
static examples = [
|
|
12633
12777
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js",
|
|
12778
|
+
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --wait",
|
|
12634
12779
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
12635
12780
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
12636
12781
|
"<%= config.bin %> functions deploy --name forwarder --file ./bundle.js --secret-from-env OPENAI_KEY --secret-from-env-file .env.local:OWNER_EMAIL",
|
|
@@ -12677,11 +12822,29 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
12677
12822
|
multiple: true
|
|
12678
12823
|
}),
|
|
12679
12824
|
"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." }),
|
|
12825
|
+
wait: Flags.boolean({ description: "Wait until the function deploy reaches deployed or failed. Progress is written to stderr; stdout remains the final JSON payload." }),
|
|
12826
|
+
timeout: Flags.integer({
|
|
12827
|
+
default: 120,
|
|
12828
|
+
description: "Seconds to wait when --wait is set before exiting non-zero. Use 0 to wait forever."
|
|
12829
|
+
}),
|
|
12830
|
+
"poll-interval": Flags.integer({
|
|
12831
|
+
default: 2,
|
|
12832
|
+
description: "Seconds between deploy-status polls when --wait is set."
|
|
12833
|
+
}),
|
|
12680
12834
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
12681
12835
|
};
|
|
12682
12836
|
async run() {
|
|
12683
12837
|
const { flags } = await this.parse(FunctionsDeployCommand);
|
|
12684
12838
|
await runWithTiming(flags.time, async () => {
|
|
12839
|
+
const waitFlagError = validateDeployWaitFlags({
|
|
12840
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
12841
|
+
timeoutSeconds: flags.timeout
|
|
12842
|
+
});
|
|
12843
|
+
if (waitFlagError) {
|
|
12844
|
+
process.stderr.write(`${waitFlagError}\n`);
|
|
12845
|
+
process.exitCode = 1;
|
|
12846
|
+
return;
|
|
12847
|
+
}
|
|
12685
12848
|
const parsedSecrets = resolveSecretFlags({
|
|
12686
12849
|
fromEnv: flags["secret-from-env"] ?? [],
|
|
12687
12850
|
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
@@ -12767,6 +12930,42 @@ var FunctionsDeployCommand = class FunctionsDeployCommand extends Command {
|
|
|
12767
12930
|
return;
|
|
12768
12931
|
}
|
|
12769
12932
|
const payload = outcome.result.redeploy ?? outcome.result.created;
|
|
12933
|
+
if (flags.wait) {
|
|
12934
|
+
const waitResult = await waitForFunctionDeploy({
|
|
12935
|
+
getFunction: (p) => getFunction({
|
|
12936
|
+
client: apiClient.client,
|
|
12937
|
+
path: { id: p.id },
|
|
12938
|
+
responseStyle: "fields"
|
|
12939
|
+
}),
|
|
12940
|
+
id: payload.id,
|
|
12941
|
+
initial: payload,
|
|
12942
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
12943
|
+
timeoutSeconds: flags.timeout,
|
|
12944
|
+
writeStderr: (chunk) => process.stderr.write(chunk)
|
|
12945
|
+
});
|
|
12946
|
+
if (waitResult.kind === "error") {
|
|
12947
|
+
writeErrorWithHints(waitResult.payload);
|
|
12948
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
12949
|
+
...authFailureContext,
|
|
12950
|
+
payload: waitResult.payload
|
|
12951
|
+
});
|
|
12952
|
+
process.exitCode = 1;
|
|
12953
|
+
return;
|
|
12954
|
+
}
|
|
12955
|
+
if (waitResult.kind === "timeout") {
|
|
12956
|
+
const status = waitResult.lastFunction?.deploy_status ?? "unknown";
|
|
12957
|
+
process.stderr.write(`Timed out after ${flags.timeout}s waiting for function ${payload.id} deploy to finish (last status: ${status}).\n`);
|
|
12958
|
+
process.exitCode = 2;
|
|
12959
|
+
return;
|
|
12960
|
+
}
|
|
12961
|
+
this.log(JSON.stringify(waitResult.function, null, 2));
|
|
12962
|
+
if (waitResult.kind === "failed") {
|
|
12963
|
+
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
12964
|
+
process.stderr.write(`Function ${payload.id} deploy failed${detail}\n`);
|
|
12965
|
+
process.exitCode = 1;
|
|
12966
|
+
}
|
|
12967
|
+
return;
|
|
12968
|
+
}
|
|
12770
12969
|
this.log(JSON.stringify(payload, null, 2));
|
|
12771
12970
|
});
|
|
12772
12971
|
}
|
|
@@ -12779,8 +12978,8 @@ const PRIMITIVE_TEAM_AUTHOR = {
|
|
|
12779
12978
|
name: "Primitive Team",
|
|
12780
12979
|
url: "https://primitive.dev"
|
|
12781
12980
|
};
|
|
12782
|
-
const SDK_VERSION_RANGE = "^0.
|
|
12783
|
-
const CLI_VERSION_RANGE = "^0.
|
|
12981
|
+
const SDK_VERSION_RANGE = "^0.30.0";
|
|
12982
|
+
const CLI_VERSION_RANGE = "^0.30.0";
|
|
12784
12983
|
const ESBUILD_VERSION_RANGE = "^0.27.0";
|
|
12785
12984
|
function renderHandler() {
|
|
12786
12985
|
return `// env.PRIMITIVE_API_KEY is auto-injected by the Primitive Functions runtime.
|
|
@@ -13179,6 +13378,166 @@ var FunctionsInitCommand = class FunctionsInitCommand extends Command {
|
|
|
13179
13378
|
}
|
|
13180
13379
|
};
|
|
13181
13380
|
//#endregion
|
|
13381
|
+
//#region src/oclif/commands/functions-logs.ts
|
|
13382
|
+
const DEFAULT_LOG_LIMIT = 50;
|
|
13383
|
+
const DEFAULT_LOG_POLL_INTERVAL_SECONDS = 2;
|
|
13384
|
+
function levelLabel(level) {
|
|
13385
|
+
return level.toUpperCase().padEnd(5);
|
|
13386
|
+
}
|
|
13387
|
+
function orderFunctionLogsForDisplay(rows) {
|
|
13388
|
+
return [...rows].reverse();
|
|
13389
|
+
}
|
|
13390
|
+
function formatFunctionLogLine(row) {
|
|
13391
|
+
const metadata = row.metadata && Object.keys(row.metadata).length > 0 ? ` ${JSON.stringify(row.metadata)}` : "";
|
|
13392
|
+
return `${row.ts} ${levelLabel(row.level)} ${row.message}${metadata}`;
|
|
13393
|
+
}
|
|
13394
|
+
function collectFreshFunctionLogsFromPage(rows, seenIds) {
|
|
13395
|
+
const freshNewestFirst = [];
|
|
13396
|
+
let reachedSeen = false;
|
|
13397
|
+
for (const row of rows) {
|
|
13398
|
+
if (seenIds.has(row.id)) {
|
|
13399
|
+
reachedSeen = true;
|
|
13400
|
+
continue;
|
|
13401
|
+
}
|
|
13402
|
+
freshNewestFirst.push(row);
|
|
13403
|
+
seenIds.add(row.id);
|
|
13404
|
+
}
|
|
13405
|
+
return {
|
|
13406
|
+
freshNewestFirst,
|
|
13407
|
+
reachedSeen
|
|
13408
|
+
};
|
|
13409
|
+
}
|
|
13410
|
+
function emitLogRows(rows, jsonl) {
|
|
13411
|
+
for (const row of rows) {
|
|
13412
|
+
const line = jsonl ? JSON.stringify(row) : formatFunctionLogLine(row);
|
|
13413
|
+
process.stdout.write(`${line}\n`);
|
|
13414
|
+
}
|
|
13415
|
+
}
|
|
13416
|
+
var FunctionsLogsCommand = class FunctionsLogsCommand extends Command {
|
|
13417
|
+
static description = "List or follow function execution logs. Defaults to compact text output; use --jsonl for one JSON object per log row.";
|
|
13418
|
+
static summary = "List or follow a function's execution logs";
|
|
13419
|
+
static examples = [
|
|
13420
|
+
"<%= config.bin %> functions logs --id <fn-id>",
|
|
13421
|
+
"<%= config.bin %> functions logs --id <fn-id> --jsonl",
|
|
13422
|
+
"<%= config.bin %> functions logs --id <fn-id> --follow"
|
|
13423
|
+
];
|
|
13424
|
+
static flags = {
|
|
13425
|
+
"api-key": Flags.string({
|
|
13426
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
13427
|
+
env: "PRIMITIVE_API_KEY"
|
|
13428
|
+
}),
|
|
13429
|
+
"api-base-url-1": Flags.string({
|
|
13430
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
13431
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
13432
|
+
hidden: true
|
|
13433
|
+
}),
|
|
13434
|
+
"api-base-url-2": Flags.string({
|
|
13435
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
13436
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
13437
|
+
hidden: true
|
|
13438
|
+
}),
|
|
13439
|
+
id: Flags.string({
|
|
13440
|
+
description: "Function id (UUID).",
|
|
13441
|
+
required: true
|
|
13442
|
+
}),
|
|
13443
|
+
limit: Flags.integer({
|
|
13444
|
+
default: DEFAULT_LOG_LIMIT,
|
|
13445
|
+
description: "Maximum rows to fetch per poll. Server clamps to 1..200."
|
|
13446
|
+
}),
|
|
13447
|
+
cursor: Flags.string({ description: "Opaque pagination cursor from a previous logs response. Not supported with --follow." }),
|
|
13448
|
+
follow: Flags.boolean({
|
|
13449
|
+
char: "f",
|
|
13450
|
+
description: "Keep polling the newest logs and print rows not seen yet."
|
|
13451
|
+
}),
|
|
13452
|
+
jsonl: Flags.boolean({ description: "Print one compact JSON object per log row." }),
|
|
13453
|
+
"poll-interval": Flags.integer({
|
|
13454
|
+
default: DEFAULT_LOG_POLL_INTERVAL_SECONDS,
|
|
13455
|
+
description: "Seconds between polls when --follow is set."
|
|
13456
|
+
}),
|
|
13457
|
+
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
13458
|
+
};
|
|
13459
|
+
async run() {
|
|
13460
|
+
const { flags } = await this.parse(FunctionsLogsCommand);
|
|
13461
|
+
if (flags.limit <= 0) this.error("--limit must be greater than 0.", { exit: 2 });
|
|
13462
|
+
if (flags["poll-interval"] <= 0) this.error("--poll-interval must be greater than 0.", { exit: 2 });
|
|
13463
|
+
if (flags.follow && flags.cursor) this.error("--cursor cannot be combined with --follow.", { exit: 2 });
|
|
13464
|
+
await runWithTiming(flags.time, async () => {
|
|
13465
|
+
const baseUrlOverridden = flags["api-base-url-1"] !== void 0 || flags["api-base-url-2"] !== void 0;
|
|
13466
|
+
const auth = resolveCliAuth({
|
|
13467
|
+
apiKey: flags["api-key"],
|
|
13468
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
13469
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
13470
|
+
configDir: this.config.configDir
|
|
13471
|
+
});
|
|
13472
|
+
const apiClient = new PrimitiveApiClient({
|
|
13473
|
+
apiKey: auth.apiKey,
|
|
13474
|
+
apiBaseUrl1: auth.apiBaseUrl1,
|
|
13475
|
+
apiBaseUrl2: auth.apiBaseUrl2
|
|
13476
|
+
});
|
|
13477
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
13478
|
+
let completedInitialFollowPoll = false;
|
|
13479
|
+
let hasObservedLogs = false;
|
|
13480
|
+
let wroteEmptyHint = false;
|
|
13481
|
+
while (true) {
|
|
13482
|
+
let cursor = flags.cursor;
|
|
13483
|
+
let nextCursor = null;
|
|
13484
|
+
let rows = [];
|
|
13485
|
+
while (true) {
|
|
13486
|
+
const result = await listFunctionLogs({
|
|
13487
|
+
client: apiClient.client,
|
|
13488
|
+
path: { id: flags.id },
|
|
13489
|
+
query: {
|
|
13490
|
+
...cursor ? { cursor } : {},
|
|
13491
|
+
limit: flags.limit
|
|
13492
|
+
},
|
|
13493
|
+
responseStyle: "fields"
|
|
13494
|
+
});
|
|
13495
|
+
if (result.error) {
|
|
13496
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
13497
|
+
writeErrorWithHints(errorPayload);
|
|
13498
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
13499
|
+
auth,
|
|
13500
|
+
baseUrlOverridden,
|
|
13501
|
+
configDir: this.config.configDir,
|
|
13502
|
+
payload: errorPayload
|
|
13503
|
+
});
|
|
13504
|
+
process.exitCode = 1;
|
|
13505
|
+
return;
|
|
13506
|
+
}
|
|
13507
|
+
const page = result.data?.data ?? {
|
|
13508
|
+
items: [],
|
|
13509
|
+
next_cursor: null
|
|
13510
|
+
};
|
|
13511
|
+
nextCursor = page.next_cursor;
|
|
13512
|
+
if (!flags.follow) {
|
|
13513
|
+
rows = orderFunctionLogsForDisplay(page.items);
|
|
13514
|
+
break;
|
|
13515
|
+
}
|
|
13516
|
+
if (page.items.length > 0) hasObservedLogs = true;
|
|
13517
|
+
const collected = collectFreshFunctionLogsFromPage(page.items, seenIds);
|
|
13518
|
+
rows.push(...collected.freshNewestFirst);
|
|
13519
|
+
if (!completedInitialFollowPoll || collected.reachedSeen || !page.next_cursor) {
|
|
13520
|
+
rows = orderFunctionLogsForDisplay(rows);
|
|
13521
|
+
break;
|
|
13522
|
+
}
|
|
13523
|
+
cursor = page.next_cursor;
|
|
13524
|
+
}
|
|
13525
|
+
if (rows.length === 0 && !wroteEmptyHint) {
|
|
13526
|
+
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");
|
|
13527
|
+
wroteEmptyHint = true;
|
|
13528
|
+
}
|
|
13529
|
+
emitLogRows(rows, flags.jsonl);
|
|
13530
|
+
if (!flags.follow) {
|
|
13531
|
+
if (nextCursor) process.stderr.write(`next cursor: ${nextCursor}\n`);
|
|
13532
|
+
return;
|
|
13533
|
+
}
|
|
13534
|
+
completedInitialFollowPoll = true;
|
|
13535
|
+
await sleep$1(flags["poll-interval"] * 1e3);
|
|
13536
|
+
}
|
|
13537
|
+
});
|
|
13538
|
+
}
|
|
13539
|
+
};
|
|
13540
|
+
//#endregion
|
|
13182
13541
|
//#region src/oclif/commands/functions-redeploy.ts
|
|
13183
13542
|
async function runRedeployWithSecrets(api, params) {
|
|
13184
13543
|
const writtenSecrets = [];
|
|
@@ -13260,6 +13619,7 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
13260
13619
|
static summary = "Redeploy a function from a bundled handler file";
|
|
13261
13620
|
static examples = [
|
|
13262
13621
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js",
|
|
13622
|
+
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --wait",
|
|
13263
13623
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
13264
13624
|
"<%= config.bin %> functions redeploy --id <fn-id> --file ./bundle.js --secret OPENAI_KEY=sk-... --secret OWNER_EMAIL=me@example.com",
|
|
13265
13625
|
"<%= 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 +13666,29 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
13306
13666
|
multiple: true
|
|
13307
13667
|
}),
|
|
13308
13668
|
"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." }),
|
|
13669
|
+
wait: Flags.boolean({ description: "Wait until the function deploy reaches deployed or failed. Progress is written to stderr; stdout remains the final JSON payload." }),
|
|
13670
|
+
timeout: Flags.integer({
|
|
13671
|
+
default: 120,
|
|
13672
|
+
description: "Seconds to wait when --wait is set before exiting non-zero. Use 0 to wait forever."
|
|
13673
|
+
}),
|
|
13674
|
+
"poll-interval": Flags.integer({
|
|
13675
|
+
default: 2,
|
|
13676
|
+
description: "Seconds between deploy-status polls when --wait is set."
|
|
13677
|
+
}),
|
|
13309
13678
|
time: Flags.boolean({ description: TIME_FLAG_DESCRIPTION })
|
|
13310
13679
|
};
|
|
13311
13680
|
async run() {
|
|
13312
13681
|
const { flags } = await this.parse(FunctionsRedeployCommand);
|
|
13313
13682
|
await runWithTiming(flags.time, async () => {
|
|
13683
|
+
const waitFlagError = validateDeployWaitFlags({
|
|
13684
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
13685
|
+
timeoutSeconds: flags.timeout
|
|
13686
|
+
});
|
|
13687
|
+
if (waitFlagError) {
|
|
13688
|
+
process.stderr.write(`${waitFlagError}\n`);
|
|
13689
|
+
process.exitCode = 1;
|
|
13690
|
+
return;
|
|
13691
|
+
}
|
|
13314
13692
|
const parsedSecrets = resolveSecretFlags({
|
|
13315
13693
|
fromEnv: flags["secret-from-env"] ?? [],
|
|
13316
13694
|
fromEnvFile: flags["secret-from-env-file"] ?? [],
|
|
@@ -13386,6 +13764,42 @@ var FunctionsRedeployCommand = class FunctionsRedeployCommand extends Command {
|
|
|
13386
13764
|
process.exitCode = 1;
|
|
13387
13765
|
return;
|
|
13388
13766
|
}
|
|
13767
|
+
if (flags.wait) {
|
|
13768
|
+
const waitResult = await waitForFunctionDeploy({
|
|
13769
|
+
getFunction: (p) => getFunction({
|
|
13770
|
+
client: apiClient.client,
|
|
13771
|
+
path: { id: p.id },
|
|
13772
|
+
responseStyle: "fields"
|
|
13773
|
+
}),
|
|
13774
|
+
id: outcome.result.redeploy.id,
|
|
13775
|
+
initial: outcome.result.redeploy,
|
|
13776
|
+
pollIntervalSeconds: flags["poll-interval"],
|
|
13777
|
+
timeoutSeconds: flags.timeout,
|
|
13778
|
+
writeStderr: (chunk) => process.stderr.write(chunk)
|
|
13779
|
+
});
|
|
13780
|
+
if (waitResult.kind === "error") {
|
|
13781
|
+
writeErrorWithHints(waitResult.payload);
|
|
13782
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
13783
|
+
...authFailureContext,
|
|
13784
|
+
payload: waitResult.payload
|
|
13785
|
+
});
|
|
13786
|
+
process.exitCode = 1;
|
|
13787
|
+
return;
|
|
13788
|
+
}
|
|
13789
|
+
if (waitResult.kind === "timeout") {
|
|
13790
|
+
const status = waitResult.lastFunction?.deploy_status ?? "unknown";
|
|
13791
|
+
process.stderr.write(`Timed out after ${flags.timeout}s waiting for function ${outcome.result.redeploy.id} deploy to finish (last status: ${status}).\n`);
|
|
13792
|
+
process.exitCode = 2;
|
|
13793
|
+
return;
|
|
13794
|
+
}
|
|
13795
|
+
this.log(JSON.stringify(waitResult.function, null, 2));
|
|
13796
|
+
if (waitResult.kind === "failed") {
|
|
13797
|
+
const detail = waitResult.function.deploy_error ? `: ${waitResult.function.deploy_error}` : ".";
|
|
13798
|
+
process.stderr.write(`Function ${outcome.result.redeploy.id} deploy failed${detail}\n`);
|
|
13799
|
+
process.exitCode = 1;
|
|
13800
|
+
}
|
|
13801
|
+
return;
|
|
13802
|
+
}
|
|
13389
13803
|
this.log(JSON.stringify(outcome.result.redeploy, null, 2));
|
|
13390
13804
|
});
|
|
13391
13805
|
}
|
|
@@ -13630,6 +14044,11 @@ function buildFunctionTestOutcome(params) {
|
|
|
13630
14044
|
if (params.showSends) outcome.sent_emails = params.detail.replies;
|
|
13631
14045
|
return outcome;
|
|
13632
14046
|
}
|
|
14047
|
+
function writeFunctionTestProgress(message, writeStderr = (chunk) => {
|
|
14048
|
+
process.stderr.write(chunk);
|
|
14049
|
+
}) {
|
|
14050
|
+
writeStderr(`${message}\n`);
|
|
14051
|
+
}
|
|
13633
14052
|
function stringOrNull(value) {
|
|
13634
14053
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
13635
14054
|
}
|
|
@@ -13784,7 +14203,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
13784
14203
|
const timeoutMs = flags.timeout * 1e3;
|
|
13785
14204
|
const pollIntervalMs = flags["poll-interval"] * 1e3;
|
|
13786
14205
|
const isExpired = () => flags.timeout > 0 && Date.now() - startedAt > timeoutMs;
|
|
13787
|
-
|
|
14206
|
+
writeFunctionTestProgress(`Waiting for test inbound to arrive at ${invocation.to}...`);
|
|
13788
14207
|
let inboundId;
|
|
13789
14208
|
while (!isExpired()) {
|
|
13790
14209
|
const page = await fetchEmailSearchPage({
|
|
@@ -13813,7 +14232,7 @@ var FunctionsTestFunctionCommand = class FunctionsTestFunctionCommand extends Co
|
|
|
13813
14232
|
await sleep$1(pollIntervalMs);
|
|
13814
14233
|
}
|
|
13815
14234
|
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
|
-
|
|
14235
|
+
writeFunctionTestProgress(`Inbound landed (${inboundId}). Waiting for function to run...`);
|
|
13817
14236
|
let detail;
|
|
13818
14237
|
while (!isExpired()) {
|
|
13819
14238
|
const result = await getEmail({
|
|
@@ -14116,6 +14535,106 @@ var LogoutCommand = class LogoutCommand extends Command {
|
|
|
14116
14535
|
}
|
|
14117
14536
|
};
|
|
14118
14537
|
//#endregion
|
|
14538
|
+
//#region src/oclif/message-body-sources.ts
|
|
14539
|
+
function defaultReadFile(path) {
|
|
14540
|
+
return readFileSync(path, "utf8");
|
|
14541
|
+
}
|
|
14542
|
+
function defaultReadStdin() {
|
|
14543
|
+
if (process.stdin.isTTY) throw new Error("stdin is a TTY; pipe a value into this command or pass a file/string source instead.");
|
|
14544
|
+
return readFileSync(0, "utf8");
|
|
14545
|
+
}
|
|
14546
|
+
function selectedSources(sources) {
|
|
14547
|
+
return sources.filter(([, selected]) => selected).map(([label]) => label);
|
|
14548
|
+
}
|
|
14549
|
+
function readTextFile(path, label, readFile) {
|
|
14550
|
+
try {
|
|
14551
|
+
return {
|
|
14552
|
+
content: readFile(path),
|
|
14553
|
+
kind: "ok"
|
|
14554
|
+
};
|
|
14555
|
+
} catch (error) {
|
|
14556
|
+
return {
|
|
14557
|
+
kind: "error",
|
|
14558
|
+
message: `Could not read ${label} ${path}: ${error instanceof Error ? error.message : String(error)}`
|
|
14559
|
+
};
|
|
14560
|
+
}
|
|
14561
|
+
}
|
|
14562
|
+
function readTextStdin(label, readStdin) {
|
|
14563
|
+
try {
|
|
14564
|
+
return {
|
|
14565
|
+
content: readStdin(),
|
|
14566
|
+
kind: "ok"
|
|
14567
|
+
};
|
|
14568
|
+
} catch (error) {
|
|
14569
|
+
return {
|
|
14570
|
+
kind: "error",
|
|
14571
|
+
message: `Could not read ${label}: ${error instanceof Error ? error.message : String(error)}`
|
|
14572
|
+
};
|
|
14573
|
+
}
|
|
14574
|
+
}
|
|
14575
|
+
function resolveMessageBodies(input) {
|
|
14576
|
+
const bodySources = selectedSources([
|
|
14577
|
+
["--body", input.body !== void 0],
|
|
14578
|
+
["--body-file", input.bodyFile !== void 0],
|
|
14579
|
+
["--body-stdin", input.bodyStdin === true]
|
|
14580
|
+
]);
|
|
14581
|
+
if (bodySources.length > 1) return {
|
|
14582
|
+
kind: "error",
|
|
14583
|
+
message: `Pass only one plain-text body source (got ${bodySources.join(", ")}).`
|
|
14584
|
+
};
|
|
14585
|
+
const htmlSources = selectedSources([
|
|
14586
|
+
["--html", input.html !== void 0],
|
|
14587
|
+
["--html-file", input.htmlFile !== void 0],
|
|
14588
|
+
["--html-stdin", input.htmlStdin === true]
|
|
14589
|
+
]);
|
|
14590
|
+
if (htmlSources.length > 1) return {
|
|
14591
|
+
kind: "error",
|
|
14592
|
+
message: `Pass only one HTML body source (got ${htmlSources.join(", ")}).`
|
|
14593
|
+
};
|
|
14594
|
+
const stdinSources = selectedSources([["--body-stdin", input.bodyStdin === true], ["--html-stdin", input.htmlStdin === true]]);
|
|
14595
|
+
if (stdinSources.length > 1) return {
|
|
14596
|
+
kind: "error",
|
|
14597
|
+
message: `Stdin can only be consumed once (got ${stdinSources.join(", ")}).`
|
|
14598
|
+
};
|
|
14599
|
+
if (bodySources.length === 0 && htmlSources.length === 0) return {
|
|
14600
|
+
kind: "error",
|
|
14601
|
+
message: "Either a plain-text body source or an HTML body source is required."
|
|
14602
|
+
};
|
|
14603
|
+
const readFile = input.readFile ?? defaultReadFile;
|
|
14604
|
+
const readStdin = input.readStdin ?? defaultReadStdin;
|
|
14605
|
+
let body = input.body;
|
|
14606
|
+
let html = input.html;
|
|
14607
|
+
if (input.bodyFile !== void 0) {
|
|
14608
|
+
const result = readTextFile(input.bodyFile, "--body-file", readFile);
|
|
14609
|
+
if (result.kind === "error") return result;
|
|
14610
|
+
body = result.content;
|
|
14611
|
+
}
|
|
14612
|
+
if (input.bodyStdin === true) {
|
|
14613
|
+
const result = readTextStdin("--body-stdin", readStdin);
|
|
14614
|
+
if (result.kind === "error") return result;
|
|
14615
|
+
body = result.content;
|
|
14616
|
+
}
|
|
14617
|
+
if (input.htmlFile !== void 0) {
|
|
14618
|
+
const result = readTextFile(input.htmlFile, "--html-file", readFile);
|
|
14619
|
+
if (result.kind === "error") return result;
|
|
14620
|
+
html = result.content;
|
|
14621
|
+
}
|
|
14622
|
+
if (input.htmlStdin === true) {
|
|
14623
|
+
const result = readTextStdin("--html-stdin", readStdin);
|
|
14624
|
+
if (result.kind === "error") return result;
|
|
14625
|
+
html = result.content;
|
|
14626
|
+
}
|
|
14627
|
+
if (!body && !html) return {
|
|
14628
|
+
kind: "error",
|
|
14629
|
+
message: "Either a non-empty plain-text body or a non-empty HTML body is required."
|
|
14630
|
+
};
|
|
14631
|
+
return {
|
|
14632
|
+
...body !== void 0 ? { body } : {},
|
|
14633
|
+
...html !== void 0 ? { html } : {},
|
|
14634
|
+
kind: "ok"
|
|
14635
|
+
};
|
|
14636
|
+
}
|
|
14637
|
+
//#endregion
|
|
14119
14638
|
//#region src/oclif/commands/reply.ts
|
|
14120
14639
|
var ReplyCommand = class ReplyCommand extends Command {
|
|
14121
14640
|
static description = `Reply to an inbound email.
|
|
@@ -14124,6 +14643,7 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
14124
14643
|
static summary = "Reply to an inbound email";
|
|
14125
14644
|
static examples = [
|
|
14126
14645
|
"<%= config.bin %> reply --id <inbound-email-id> --body 'Thanks, got it.'",
|
|
14646
|
+
"<%= config.bin %> reply --id <inbound-email-id> --body-file ./reply.txt",
|
|
14127
14647
|
"<%= config.bin %> reply --id <inbound-email-id> --html '<p>Thanks, got it.</p>' --wait",
|
|
14128
14648
|
"<%= config.bin %> reply --id <inbound-email-id> --from 'Support <support@example.com>' --body 'Thanks!'"
|
|
14129
14649
|
];
|
|
@@ -14147,7 +14667,11 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
14147
14667
|
required: true
|
|
14148
14668
|
}),
|
|
14149
14669
|
body: Flags.string({ description: "Plain-text reply body. Either --body or --html (or both) is required." }),
|
|
14670
|
+
"body-file": Flags.string({ description: "Read the plain-text reply body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
14671
|
+
"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
14672
|
html: Flags.string({ description: "HTML reply body. Either --body or --html (or both) is required." }),
|
|
14673
|
+
"html-file": Flags.string({ description: "Read the HTML reply body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
14674
|
+
"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
14675
|
from: Flags.string({ description: "Optional From header override. Defaults to the inbound recipient." }),
|
|
14152
14676
|
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
14677
|
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
@@ -14155,7 +14679,15 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
14155
14679
|
};
|
|
14156
14680
|
async run() {
|
|
14157
14681
|
const { flags } = await this.parse(ReplyCommand);
|
|
14158
|
-
|
|
14682
|
+
const bodies = resolveMessageBodies({
|
|
14683
|
+
body: flags.body,
|
|
14684
|
+
bodyFile: flags["body-file"],
|
|
14685
|
+
bodyStdin: flags["body-stdin"],
|
|
14686
|
+
html: flags.html,
|
|
14687
|
+
htmlFile: flags["html-file"],
|
|
14688
|
+
htmlStdin: flags["html-stdin"]
|
|
14689
|
+
});
|
|
14690
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
14159
14691
|
await runWithTiming(flags.time, async () => {
|
|
14160
14692
|
const baseUrlOverridden = flags["api-base-url-1"] !== void 0 || flags["api-base-url-2"] !== void 0;
|
|
14161
14693
|
const auth = resolveCliAuth({
|
|
@@ -14171,8 +14703,8 @@ var ReplyCommand = class ReplyCommand extends Command {
|
|
|
14171
14703
|
});
|
|
14172
14704
|
const result = await replyToEmail({
|
|
14173
14705
|
body: {
|
|
14174
|
-
...
|
|
14175
|
-
...
|
|
14706
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
14707
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
14176
14708
|
...flags.from !== void 0 ? { from: flags.from } : {},
|
|
14177
14709
|
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
14178
14710
|
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
@@ -14247,6 +14779,7 @@ var SendCommand = class SendCommand extends Command {
|
|
|
14247
14779
|
static summary = "Send an email (simplified, agent-friendly)";
|
|
14248
14780
|
static examples = [
|
|
14249
14781
|
"<%= config.bin %> send --to alice@example.com --body 'Hi Alice!'",
|
|
14782
|
+
"<%= config.bin %> send --to alice@example.com --body-file ./message.txt",
|
|
14250
14783
|
"<%= config.bin %> send --to alice@example.com --from support@yourcompany.com --subject 'Quick question' --body 'Are you free Thursday?'",
|
|
14251
14784
|
"<%= config.bin %> send --to alice@example.com --html '<p>Hello!</p>'",
|
|
14252
14785
|
"<%= config.bin %> send --to alice@example.com --body 'Confirmed' --wait",
|
|
@@ -14274,7 +14807,11 @@ var SendCommand = class SendCommand extends Command {
|
|
|
14274
14807
|
from: Flags.string({ description: "Sender address. Defaults to agent@<your-first-verified-outbound-domain>." }),
|
|
14275
14808
|
subject: Flags.string({ description: "Subject line. Defaults to the first line of --body / --html when omitted." }),
|
|
14276
14809
|
body: Flags.string({ description: "Plain-text message body. Either --body or --html (or both) is required." }),
|
|
14810
|
+
"body-file": Flags.string({ description: "Read the plain-text message body from a UTF-8 file. Mutually exclusive with --body and --body-stdin." }),
|
|
14811
|
+
"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
14812
|
html: Flags.string({ description: "HTML message body. Either --body or --html (or both) is required." }),
|
|
14813
|
+
"html-file": Flags.string({ description: "Read the HTML message body from a UTF-8 file. Mutually exclusive with --html and --html-stdin." }),
|
|
14814
|
+
"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
14815
|
"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
14816
|
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
14817
|
"wait-timeout-ms": Flags.integer({ description: "Maximum time to wait when --wait is set. Defaults to 30000ms." }),
|
|
@@ -14282,7 +14819,15 @@ var SendCommand = class SendCommand extends Command {
|
|
|
14282
14819
|
};
|
|
14283
14820
|
async run() {
|
|
14284
14821
|
const { flags } = await this.parse(SendCommand);
|
|
14285
|
-
|
|
14822
|
+
const bodies = resolveMessageBodies({
|
|
14823
|
+
body: flags.body,
|
|
14824
|
+
bodyFile: flags["body-file"],
|
|
14825
|
+
bodyStdin: flags["body-stdin"],
|
|
14826
|
+
html: flags.html,
|
|
14827
|
+
htmlFile: flags["html-file"],
|
|
14828
|
+
htmlStdin: flags["html-stdin"]
|
|
14829
|
+
});
|
|
14830
|
+
if (bodies.kind === "error") throw new Errors.CLIError(bodies.message);
|
|
14286
14831
|
await runWithTiming(flags.time, async () => {
|
|
14287
14832
|
const baseUrlOverridden = flags["api-base-url-1"] !== void 0 || flags["api-base-url-2"] !== void 0;
|
|
14288
14833
|
const auth = resolveCliAuth({
|
|
@@ -14302,14 +14847,14 @@ var SendCommand = class SendCommand extends Command {
|
|
|
14302
14847
|
configDir: this.config.configDir
|
|
14303
14848
|
};
|
|
14304
14849
|
const from = flags.from ?? await pickDefaultFromAddress(apiClient, authFailureContext);
|
|
14305
|
-
const subject = flags.subject ?? (
|
|
14850
|
+
const subject = flags.subject ?? (bodies.body ? deriveSubject(bodies.body) : "Message");
|
|
14306
14851
|
const result = await sendEmail({
|
|
14307
14852
|
body: {
|
|
14308
14853
|
from,
|
|
14309
14854
|
to: flags.to,
|
|
14310
14855
|
subject,
|
|
14311
|
-
...
|
|
14312
|
-
...
|
|
14856
|
+
...bodies.body !== void 0 ? { body_text: bodies.body } : {},
|
|
14857
|
+
...bodies.html !== void 0 ? { body_html: bodies.html } : {},
|
|
14313
14858
|
...flags["in-reply-to"] !== void 0 ? { in_reply_to: flags["in-reply-to"] } : {},
|
|
14314
14859
|
...flags.wait !== void 0 ? { wait: flags.wait } : {},
|
|
14315
14860
|
...flags["wait-timeout-ms"] !== void 0 ? { wait_timeout_ms: flags["wait-timeout-ms"] } : {}
|
|
@@ -14849,6 +15394,7 @@ function renderFishCompletion(binName) {
|
|
|
14849
15394
|
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
15395
|
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
15396
|
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)'`);
|
|
15397
|
+
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
15398
|
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
15399
|
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
15400
|
}
|
|
@@ -14966,7 +15512,6 @@ const CANONICAL_OPERATION_ALIASES = {
|
|
|
14966
15512
|
"functions:get": "functions:get-function",
|
|
14967
15513
|
"functions:list": "functions:list-functions",
|
|
14968
15514
|
"functions:list-secrets": "functions:list-function-secrets",
|
|
14969
|
-
"functions:logs": "functions:list-function-logs",
|
|
14970
15515
|
"sending:get": "sending:get-sent-email",
|
|
14971
15516
|
"sending:list": "sending:list-sent-emails",
|
|
14972
15517
|
"sending:permissions": "sending:get-send-permissions",
|
|
@@ -14979,6 +15524,7 @@ const CANONICAL_OPERATION_ALIASES = {
|
|
|
14979
15524
|
};
|
|
14980
15525
|
const DESCRIBE_OPERATION_ALIASES = {
|
|
14981
15526
|
...CANONICAL_OPERATION_ALIASES,
|
|
15527
|
+
"functions:logs": "functions:list-function-logs",
|
|
14982
15528
|
reply: "sending:reply-to-email"
|
|
14983
15529
|
};
|
|
14984
15530
|
function resolveOperationAlias(id) {
|
|
@@ -15012,7 +15558,8 @@ const COMMANDS = {
|
|
|
15012
15558
|
if (!command) throw new Error(`Missing generated command target for alias ${alias}`);
|
|
15013
15559
|
return [alias, command];
|
|
15014
15560
|
})),
|
|
15015
|
-
...generatedCommands
|
|
15561
|
+
...generatedCommands,
|
|
15562
|
+
"functions:logs": FunctionsLogsCommand
|
|
15016
15563
|
};
|
|
15017
15564
|
//#endregion
|
|
15018
15565
|
export { CANONICAL_OPERATION_ALIASES, COMMANDS, lookupOperation };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitivedotdev/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "Official Primitive CLI: deploy Primitive Functions, send and inspect mail, manage endpoints, all from the terminal. Wraps the @primitivedotdev/sdk runtime client with one-shot commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|