@primitivedotdev/cli 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/bin/run.js +5 -0
- package/dist/oclif/api-command.js +755 -0
- package/dist/oclif/auth.js +223 -0
- package/dist/oclif/commands/emails-latest.js +184 -0
- package/dist/oclif/commands/emails-poll.js +121 -0
- package/dist/oclif/commands/emails-wait.js +171 -0
- package/dist/oclif/commands/emails-watch.js +165 -0
- package/dist/oclif/commands/functions-deploy.js +123 -0
- package/dist/oclif/commands/functions-init.js +262 -0
- package/dist/oclif/commands/functions-redeploy.js +112 -0
- package/dist/oclif/commands/functions-set-secret.js +212 -0
- package/dist/oclif/commands/login.js +236 -0
- package/dist/oclif/commands/logout.js +87 -0
- package/dist/oclif/commands/send.js +221 -0
- package/dist/oclif/commands/whoami.js +94 -0
- package/dist/oclif/fish-completion.js +87 -0
- package/dist/oclif/index.js +167 -0
- package/dist/oclif/lint/raw-send-mail-fetch.js +98 -0
- package/oclif.manifest.json +4287 -0
- package/package.json +108 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { Command, Errors, Flags } from "@oclif/core";
|
|
2
|
+
import { PrimitiveApiClient } from "@primitivedotdev/sdk/api";
|
|
3
|
+
import { extractErrorPayload, removeStaleSavedCredentialOnUnauthorized, writeErrorWithHints, } from "../api-command.js";
|
|
4
|
+
import { resolveCliAuth } from "../auth.js";
|
|
5
|
+
import { formatHeader, formatRow, pickIdWidth } from "./emails-latest.js";
|
|
6
|
+
import { collectNewAcceptedEmails, DEFAULT_EMAIL_POLL_INTERVAL_SECONDS, DEFAULT_EMAIL_POLL_PAGE_SIZE, fetchEmailSearchPage, filtersFromFlags, MAX_EMAIL_POLL_PAGE_SIZE, sinceFromFlags, sleep, } from "./emails-poll.js";
|
|
7
|
+
const DEFAULT_WAIT_TIMEOUT_SECONDS = 300;
|
|
8
|
+
function cliError(message) {
|
|
9
|
+
return new Errors.CLIError(message, { exit: 1 });
|
|
10
|
+
}
|
|
11
|
+
class EmailsWaitCommand extends Command {
|
|
12
|
+
static description = "Poll until matching inbound emails arrive, printing each match as it is found.";
|
|
13
|
+
static summary = "Wait for matching inbound emails";
|
|
14
|
+
static examples = [
|
|
15
|
+
"<%= config.bin %> emails wait --to test@example.com",
|
|
16
|
+
"<%= config.bin %> emails wait --subject verify --number 5 --timeout 120",
|
|
17
|
+
"<%= config.bin %> emails wait --q 'domain:example.com' --table",
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
"api-key": Flags.string({
|
|
21
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
22
|
+
env: "PRIMITIVE_API_KEY",
|
|
23
|
+
}),
|
|
24
|
+
"api-base-url-1": Flags.string({
|
|
25
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
26
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
27
|
+
hidden: true,
|
|
28
|
+
}),
|
|
29
|
+
"api-base-url-2": Flags.string({
|
|
30
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
31
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
32
|
+
hidden: true,
|
|
33
|
+
}),
|
|
34
|
+
body: Flags.string({
|
|
35
|
+
description: "Full-text body filter",
|
|
36
|
+
}),
|
|
37
|
+
domain: Flags.string({
|
|
38
|
+
description: "Filter by inbound email domain",
|
|
39
|
+
}),
|
|
40
|
+
"domain-id": Flags.string({
|
|
41
|
+
description: "Filter by domain UUID",
|
|
42
|
+
}),
|
|
43
|
+
from: Flags.string({
|
|
44
|
+
description: "Filter by sender address or domain",
|
|
45
|
+
}),
|
|
46
|
+
"has-attachment": Flags.boolean({
|
|
47
|
+
description: "Only match emails with one or more attachments",
|
|
48
|
+
}),
|
|
49
|
+
"include-existing": Flags.boolean({
|
|
50
|
+
description: "Start from existing matching emails instead of only new arrivals",
|
|
51
|
+
}),
|
|
52
|
+
interval: Flags.integer({
|
|
53
|
+
default: DEFAULT_EMAIL_POLL_INTERVAL_SECONDS,
|
|
54
|
+
description: "Seconds to wait between empty polls",
|
|
55
|
+
min: 1,
|
|
56
|
+
}),
|
|
57
|
+
number: Flags.integer({
|
|
58
|
+
char: "n",
|
|
59
|
+
default: 1,
|
|
60
|
+
description: "Exit successfully after this many matching emails",
|
|
61
|
+
min: 1,
|
|
62
|
+
}),
|
|
63
|
+
"page-size": Flags.integer({
|
|
64
|
+
default: DEFAULT_EMAIL_POLL_PAGE_SIZE,
|
|
65
|
+
description: `Emails to fetch per poll (1-${MAX_EMAIL_POLL_PAGE_SIZE})`,
|
|
66
|
+
max: MAX_EMAIL_POLL_PAGE_SIZE,
|
|
67
|
+
min: 1,
|
|
68
|
+
}),
|
|
69
|
+
q: Flags.string({
|
|
70
|
+
description: "Full-text search DSL query",
|
|
71
|
+
}),
|
|
72
|
+
since: Flags.string({
|
|
73
|
+
description: "Only match emails received on or after this date/time",
|
|
74
|
+
}),
|
|
75
|
+
"spam-score-gte": Flags.integer({
|
|
76
|
+
description: "Only match emails with spam score greater than or equal to this value",
|
|
77
|
+
}),
|
|
78
|
+
"spam-score-lt": Flags.integer({
|
|
79
|
+
description: "Only match emails with spam score below this value",
|
|
80
|
+
}),
|
|
81
|
+
subject: Flags.string({
|
|
82
|
+
description: "Full-text subject filter",
|
|
83
|
+
}),
|
|
84
|
+
table: Flags.boolean({
|
|
85
|
+
description: "Print a human-readable table instead of JSONL",
|
|
86
|
+
}),
|
|
87
|
+
timeout: Flags.integer({
|
|
88
|
+
default: DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
89
|
+
description: "Seconds to wait before exiting nonzero; 0 waits forever",
|
|
90
|
+
min: 0,
|
|
91
|
+
}),
|
|
92
|
+
to: Flags.string({
|
|
93
|
+
description: "Filter by recipient address or domain",
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
async run() {
|
|
97
|
+
const { flags } = await this.parse(EmailsWaitCommand);
|
|
98
|
+
const baseUrlOverridden = flags["api-base-url-1"] !== undefined ||
|
|
99
|
+
flags["api-base-url-2"] !== undefined;
|
|
100
|
+
const auth = resolveCliAuth({
|
|
101
|
+
apiKey: flags["api-key"],
|
|
102
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
103
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
104
|
+
configDir: this.config.configDir,
|
|
105
|
+
});
|
|
106
|
+
const apiClient = new PrimitiveApiClient({
|
|
107
|
+
apiKey: auth.apiKey,
|
|
108
|
+
apiBaseUrl1: auth.apiBaseUrl1,
|
|
109
|
+
apiBaseUrl2: auth.apiBaseUrl2,
|
|
110
|
+
});
|
|
111
|
+
let since;
|
|
112
|
+
try {
|
|
113
|
+
since = sinceFromFlags(flags);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw cliError(error instanceof Error ? error.message : String(error));
|
|
117
|
+
}
|
|
118
|
+
const filters = filtersFromFlags(flags);
|
|
119
|
+
const deadline = flags.timeout === 0 ? null : Date.now() + flags.timeout * 1000;
|
|
120
|
+
const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
|
|
121
|
+
const seenIds = new Set();
|
|
122
|
+
let cursor = null;
|
|
123
|
+
let matched = 0;
|
|
124
|
+
let headerPrinted = false;
|
|
125
|
+
while (deadline === null || Date.now() < deadline) {
|
|
126
|
+
const page = await fetchEmailSearchPage({
|
|
127
|
+
apiClient,
|
|
128
|
+
cursor,
|
|
129
|
+
filters,
|
|
130
|
+
pageSize: flags["page-size"],
|
|
131
|
+
since,
|
|
132
|
+
});
|
|
133
|
+
if (!page.ok) {
|
|
134
|
+
const payload = extractErrorPayload(page.error);
|
|
135
|
+
writeErrorWithHints(payload);
|
|
136
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
137
|
+
auth,
|
|
138
|
+
baseUrlOverridden,
|
|
139
|
+
configDir: this.config.configDir,
|
|
140
|
+
payload,
|
|
141
|
+
});
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
cursor = page.cursor ?? cursor;
|
|
146
|
+
for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
|
|
147
|
+
if (flags.table) {
|
|
148
|
+
if (!headerPrinted) {
|
|
149
|
+
process.stderr.write(`${formatHeader(idWidth)}\n`);
|
|
150
|
+
headerPrinted = true;
|
|
151
|
+
}
|
|
152
|
+
this.log(formatRow(email, idWidth));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
this.log(JSON.stringify(email));
|
|
156
|
+
}
|
|
157
|
+
matched += 1;
|
|
158
|
+
if (matched >= flags.number)
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (page.rows.length > 0)
|
|
162
|
+
continue;
|
|
163
|
+
if (deadline !== null && Date.now() >= deadline)
|
|
164
|
+
break;
|
|
165
|
+
await sleep(flags.interval * 1000);
|
|
166
|
+
}
|
|
167
|
+
process.stderr.write(`Timed out waiting for ${flags.number} matching email${flags.number === 1 ? "" : "s"}; received ${matched}.\n`);
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export default EmailsWaitCommand;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { Command, Errors, Flags } from "@oclif/core";
|
|
2
|
+
import { PrimitiveApiClient } from "@primitivedotdev/sdk/api";
|
|
3
|
+
import { extractErrorPayload, removeStaleSavedCredentialOnUnauthorized, writeErrorWithHints, } from "../api-command.js";
|
|
4
|
+
import { resolveCliAuth } from "../auth.js";
|
|
5
|
+
import { formatHeader, formatRow, pickIdWidth } from "./emails-latest.js";
|
|
6
|
+
import { collectNewAcceptedEmails, DEFAULT_EMAIL_POLL_INTERVAL_SECONDS, DEFAULT_EMAIL_POLL_PAGE_SIZE, fetchEmailSearchPage, filtersFromFlags, MAX_EMAIL_POLL_PAGE_SIZE, sinceFromFlags, sleep, } from "./emails-poll.js";
|
|
7
|
+
function cliError(message) {
|
|
8
|
+
return new Errors.CLIError(message, { exit: 1 });
|
|
9
|
+
}
|
|
10
|
+
class EmailsWatchCommand extends Command {
|
|
11
|
+
static description = "Poll for new inbound emails and print matching messages as they arrive.";
|
|
12
|
+
static summary = "Watch inbound emails with filters";
|
|
13
|
+
static examples = [
|
|
14
|
+
"<%= config.bin %> emails watch --to support@example.com",
|
|
15
|
+
"<%= config.bin %> emails watch --subject verify --seconds 300",
|
|
16
|
+
"<%= config.bin %> emails watch --number 20 --jsonl",
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
"api-key": Flags.string({
|
|
20
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
21
|
+
env: "PRIMITIVE_API_KEY",
|
|
22
|
+
}),
|
|
23
|
+
"api-base-url-1": Flags.string({
|
|
24
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
25
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
26
|
+
hidden: true,
|
|
27
|
+
}),
|
|
28
|
+
"api-base-url-2": Flags.string({
|
|
29
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
30
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
31
|
+
hidden: true,
|
|
32
|
+
}),
|
|
33
|
+
body: Flags.string({
|
|
34
|
+
description: "Full-text body filter",
|
|
35
|
+
}),
|
|
36
|
+
domain: Flags.string({
|
|
37
|
+
description: "Filter by inbound email domain",
|
|
38
|
+
}),
|
|
39
|
+
"domain-id": Flags.string({
|
|
40
|
+
description: "Filter by domain UUID",
|
|
41
|
+
}),
|
|
42
|
+
from: Flags.string({
|
|
43
|
+
description: "Filter by sender address or domain",
|
|
44
|
+
}),
|
|
45
|
+
"has-attachment": Flags.boolean({
|
|
46
|
+
description: "Only show emails with one or more attachments",
|
|
47
|
+
}),
|
|
48
|
+
"include-existing": Flags.boolean({
|
|
49
|
+
description: "Start from existing matching emails instead of only new arrivals",
|
|
50
|
+
}),
|
|
51
|
+
interval: Flags.integer({
|
|
52
|
+
default: DEFAULT_EMAIL_POLL_INTERVAL_SECONDS,
|
|
53
|
+
description: "Seconds to wait between empty polls",
|
|
54
|
+
min: 1,
|
|
55
|
+
}),
|
|
56
|
+
jsonl: Flags.boolean({
|
|
57
|
+
description: "Print each email as one JSON object per line",
|
|
58
|
+
}),
|
|
59
|
+
number: Flags.integer({
|
|
60
|
+
description: "Exit after printing this many matching emails",
|
|
61
|
+
min: 1,
|
|
62
|
+
}),
|
|
63
|
+
"page-size": Flags.integer({
|
|
64
|
+
default: DEFAULT_EMAIL_POLL_PAGE_SIZE,
|
|
65
|
+
description: `Emails to fetch per poll (1-${MAX_EMAIL_POLL_PAGE_SIZE})`,
|
|
66
|
+
max: MAX_EMAIL_POLL_PAGE_SIZE,
|
|
67
|
+
min: 1,
|
|
68
|
+
}),
|
|
69
|
+
q: Flags.string({
|
|
70
|
+
description: "Full-text search DSL query",
|
|
71
|
+
}),
|
|
72
|
+
seconds: Flags.integer({
|
|
73
|
+
description: "Exit after this many seconds",
|
|
74
|
+
min: 1,
|
|
75
|
+
}),
|
|
76
|
+
since: Flags.string({
|
|
77
|
+
description: "Only show emails received on or after this date/time",
|
|
78
|
+
}),
|
|
79
|
+
"spam-score-gte": Flags.integer({
|
|
80
|
+
description: "Only show emails with spam score greater than or equal to this value",
|
|
81
|
+
}),
|
|
82
|
+
"spam-score-lt": Flags.integer({
|
|
83
|
+
description: "Only show emails with spam score below this value",
|
|
84
|
+
}),
|
|
85
|
+
subject: Flags.string({
|
|
86
|
+
description: "Full-text subject filter",
|
|
87
|
+
}),
|
|
88
|
+
to: Flags.string({
|
|
89
|
+
description: "Filter by recipient address or domain",
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
async run() {
|
|
93
|
+
const { flags } = await this.parse(EmailsWatchCommand);
|
|
94
|
+
const baseUrlOverridden = flags["api-base-url-1"] !== undefined ||
|
|
95
|
+
flags["api-base-url-2"] !== undefined;
|
|
96
|
+
const auth = resolveCliAuth({
|
|
97
|
+
apiKey: flags["api-key"],
|
|
98
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
99
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
100
|
+
configDir: this.config.configDir,
|
|
101
|
+
});
|
|
102
|
+
const apiClient = new PrimitiveApiClient({
|
|
103
|
+
apiKey: auth.apiKey,
|
|
104
|
+
apiBaseUrl1: auth.apiBaseUrl1,
|
|
105
|
+
apiBaseUrl2: auth.apiBaseUrl2,
|
|
106
|
+
});
|
|
107
|
+
let since;
|
|
108
|
+
try {
|
|
109
|
+
since = sinceFromFlags(flags);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw cliError(error instanceof Error ? error.message : String(error));
|
|
113
|
+
}
|
|
114
|
+
const filters = filtersFromFlags(flags);
|
|
115
|
+
const deadline = flags.seconds ? Date.now() + flags.seconds * 1000 : null;
|
|
116
|
+
const idWidth = pickIdWidth(Boolean(process.stdout.isTTY));
|
|
117
|
+
const seenIds = new Set();
|
|
118
|
+
let cursor = null;
|
|
119
|
+
let printed = 0;
|
|
120
|
+
let headerPrinted = false;
|
|
121
|
+
while (deadline === null || Date.now() < deadline) {
|
|
122
|
+
const page = await fetchEmailSearchPage({
|
|
123
|
+
apiClient,
|
|
124
|
+
cursor,
|
|
125
|
+
filters,
|
|
126
|
+
pageSize: flags["page-size"],
|
|
127
|
+
since,
|
|
128
|
+
});
|
|
129
|
+
if (!page.ok) {
|
|
130
|
+
const payload = extractErrorPayload(page.error);
|
|
131
|
+
writeErrorWithHints(payload);
|
|
132
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
133
|
+
auth,
|
|
134
|
+
baseUrlOverridden,
|
|
135
|
+
configDir: this.config.configDir,
|
|
136
|
+
payload,
|
|
137
|
+
});
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
cursor = page.cursor ?? cursor;
|
|
142
|
+
for (const email of collectNewAcceptedEmails(page.rows, seenIds)) {
|
|
143
|
+
if (flags.jsonl) {
|
|
144
|
+
this.log(JSON.stringify(email));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
if (!headerPrinted) {
|
|
148
|
+
process.stderr.write(`${formatHeader(idWidth)}\n`);
|
|
149
|
+
headerPrinted = true;
|
|
150
|
+
}
|
|
151
|
+
this.log(formatRow(email, idWidth));
|
|
152
|
+
}
|
|
153
|
+
printed += 1;
|
|
154
|
+
if (flags.number && printed >= flags.number)
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (page.rows.length > 0)
|
|
158
|
+
continue;
|
|
159
|
+
if (deadline !== null && Date.now() >= deadline)
|
|
160
|
+
break;
|
|
161
|
+
await sleep(flags.interval * 1000);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
export default EmailsWatchCommand;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Command, Flags } from "@oclif/core";
|
|
2
|
+
import { createFunction, PrimitiveApiClient } from "@primitivedotdev/sdk/api";
|
|
3
|
+
import { extractErrorPayload, readTextFileFlag, removeStaleSavedCredentialOnUnauthorized, runWithTiming, TIME_FLAG_DESCRIPTION, writeErrorWithHints, } from "../api-command.js";
|
|
4
|
+
import { resolveCliAuth } from "../auth.js";
|
|
5
|
+
import { emitRawSendMailFetchWarning } from "../lint/raw-send-mail-fetch.js";
|
|
6
|
+
// `primitive functions:deploy` is the agent-grade shortcut for
|
|
7
|
+
// `functions:create-function`. The underlying operation takes `code`
|
|
8
|
+
// as a string in the JSON body, which is awkward at the CLI for
|
|
9
|
+
// multi-line bundles: agents would otherwise have to shell-escape an
|
|
10
|
+
// entire ESM file or write a temp body.json. This command reads the
|
|
11
|
+
// bundle straight off disk via --file, so the natural workflow is:
|
|
12
|
+
//
|
|
13
|
+
// esbuild handler.ts --bundle --format=esm --outfile=bundle.js
|
|
14
|
+
// primitive functions:deploy --name myfn --file bundle.js
|
|
15
|
+
//
|
|
16
|
+
// Source maps follow the same shape via --source-map-file. They are
|
|
17
|
+
// stored only on the runtime side (not in our database) so dropping
|
|
18
|
+
// them later in the pipeline is fine; the CLI just hands them through.
|
|
19
|
+
//
|
|
20
|
+
// For full control (raw body, --raw-body JSON, etc.) the underlying
|
|
21
|
+
// `functions:create-function` operation stays available.
|
|
22
|
+
class FunctionsDeployCommand extends Command {
|
|
23
|
+
static description = `Deploy a new function from a bundled handler file. Agent-grade shortcut for functions:create-function.
|
|
24
|
+
|
|
25
|
+
Reads the bundle off disk (--file) instead of forcing the caller to
|
|
26
|
+
serialize the source into a JSON body. Use the underlying operation
|
|
27
|
+
\`functions:create-function\` if you need the full flag surface
|
|
28
|
+
(raw-body JSON, etc.).`;
|
|
29
|
+
static summary = "Deploy a new function from a bundled handler file";
|
|
30
|
+
static examples = [
|
|
31
|
+
"<%= config.bin %> functions:deploy --name forwarder --file ./bundle.js",
|
|
32
|
+
"<%= config.bin %> functions:deploy --name forwarder --file ./bundle.js --source-map-file ./bundle.js.map",
|
|
33
|
+
];
|
|
34
|
+
static flags = {
|
|
35
|
+
"api-key": Flags.string({
|
|
36
|
+
description: "Primitive API key (defaults to PRIMITIVE_API_KEY or saved `primitive login` credentials)",
|
|
37
|
+
env: "PRIMITIVE_API_KEY",
|
|
38
|
+
}),
|
|
39
|
+
"api-base-url-1": Flags.string({
|
|
40
|
+
description: "Override the primary API base URL. Internal testing only; not documented to customers.",
|
|
41
|
+
env: "PRIMITIVE_API_BASE_URL_1",
|
|
42
|
+
hidden: true,
|
|
43
|
+
}),
|
|
44
|
+
"api-base-url-2": Flags.string({
|
|
45
|
+
description: "Override the attachments-supporting send host base URL. Internal testing only; not documented to customers.",
|
|
46
|
+
env: "PRIMITIVE_API_BASE_URL_2",
|
|
47
|
+
hidden: true,
|
|
48
|
+
}),
|
|
49
|
+
name: Flags.string({
|
|
50
|
+
description: "Slug-style name. Lowercase letters, digits, hyphens, underscores. 1-64 chars. Must be unique within the org.",
|
|
51
|
+
required: true,
|
|
52
|
+
}),
|
|
53
|
+
file: Flags.string({
|
|
54
|
+
description: "Path to the bundled ESM handler file (single self-contained module). Loaded as the `code` body field.",
|
|
55
|
+
required: true,
|
|
56
|
+
}),
|
|
57
|
+
"source-map-file": Flags.string({
|
|
58
|
+
description: "Optional path to a source map for the bundle. Stored only on the runtime side and used to symbolicate stack traces.",
|
|
59
|
+
}),
|
|
60
|
+
time: Flags.boolean({
|
|
61
|
+
description: TIME_FLAG_DESCRIPTION,
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
async run() {
|
|
65
|
+
const { flags } = await this.parse(FunctionsDeployCommand);
|
|
66
|
+
await runWithTiming(flags.time, async () => {
|
|
67
|
+
// Reads are inside the timed block so --time captures disk I/O
|
|
68
|
+
// alongside the API call. A pathological filesystem (NFS, slow
|
|
69
|
+
// FUSE mount) showing up here is exactly the kind of latency
|
|
70
|
+
// surprise --time is meant to surface.
|
|
71
|
+
const code = readTextFileFlag(flags.file, "--file");
|
|
72
|
+
const sourceMap = flags["source-map-file"]
|
|
73
|
+
? readTextFileFlag(flags["source-map-file"], "--source-map-file")
|
|
74
|
+
: undefined;
|
|
75
|
+
// Non-blocking deploy-time lint: if the bundle has a raw
|
|
76
|
+
// fetch(...) call against /send-mail, nudge the author toward
|
|
77
|
+
// `createPrimitiveClient` from `@primitivedotdev/sdk/api`.
|
|
78
|
+
// The warning lands on stderr so it never contaminates the
|
|
79
|
+
// JSON stdout the caller may pipe into jq.
|
|
80
|
+
emitRawSendMailFetchWarning(code, (chunk) => process.stderr.write(chunk));
|
|
81
|
+
const baseUrlOverridden = flags["api-base-url-1"] !== undefined ||
|
|
82
|
+
flags["api-base-url-2"] !== undefined;
|
|
83
|
+
const auth = resolveCliAuth({
|
|
84
|
+
apiKey: flags["api-key"],
|
|
85
|
+
apiBaseUrl1: flags["api-base-url-1"],
|
|
86
|
+
apiBaseUrl2: flags["api-base-url-2"],
|
|
87
|
+
configDir: this.config.configDir,
|
|
88
|
+
});
|
|
89
|
+
const apiClient = new PrimitiveApiClient({
|
|
90
|
+
apiKey: auth.apiKey,
|
|
91
|
+
apiBaseUrl1: auth.apiBaseUrl1,
|
|
92
|
+
apiBaseUrl2: auth.apiBaseUrl2,
|
|
93
|
+
});
|
|
94
|
+
const authFailureContext = {
|
|
95
|
+
auth,
|
|
96
|
+
baseUrlOverridden,
|
|
97
|
+
configDir: this.config.configDir,
|
|
98
|
+
};
|
|
99
|
+
const result = await createFunction({
|
|
100
|
+
body: {
|
|
101
|
+
name: flags.name,
|
|
102
|
+
code,
|
|
103
|
+
...(sourceMap !== undefined ? { sourceMap } : {}),
|
|
104
|
+
},
|
|
105
|
+
client: apiClient.client,
|
|
106
|
+
responseStyle: "fields",
|
|
107
|
+
});
|
|
108
|
+
if (result.error) {
|
|
109
|
+
const errorPayload = extractErrorPayload(result.error);
|
|
110
|
+
writeErrorWithHints(errorPayload);
|
|
111
|
+
removeStaleSavedCredentialOnUnauthorized({
|
|
112
|
+
...authFailureContext,
|
|
113
|
+
payload: errorPayload,
|
|
114
|
+
});
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const envelope = result.data;
|
|
119
|
+
this.log(JSON.stringify(envelope?.data ?? null, null, 2));
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export default FunctionsDeployCommand;
|