run402 1.64.0 → 1.65.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/README.md +2 -1
- package/lib/deploy-v2.mjs +8 -0
- package/lib/functions.mjs +65 -17
- package/lib/projects.mjs +120 -0
- package/package.json +1 -1
- package/sdk/dist/namespaces/deploy.d.ts.map +1 -1
- package/sdk/dist/namespaces/deploy.js +55 -2
- package/sdk/dist/namespaces/deploy.js.map +1 -1
- package/sdk/dist/namespaces/functions.d.ts +2 -1
- package/sdk/dist/namespaces/functions.d.ts.map +1 -1
- package/sdk/dist/namespaces/functions.js +19 -7
- package/sdk/dist/namespaces/functions.js.map +1 -1
- package/sdk/dist/namespaces/functions.types.d.ts +12 -2
- package/sdk/dist/namespaces/functions.types.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.d.ts +10 -1
- package/sdk/dist/namespaces/projects.d.ts.map +1 -1
- package/sdk/dist/namespaces/projects.js +64 -0
- package/sdk/dist/namespaces/projects.js.map +1 -1
- package/sdk/dist/namespaces/projects.types.d.ts +23 -0
- package/sdk/dist/namespaces/projects.types.d.ts.map +1 -1
- package/sdk/dist/scoped.d.ts +2 -1
- package/sdk/dist/scoped.d.ts.map +1 -1
- package/sdk/dist/scoped.js +6 -0
- package/sdk/dist/scoped.js.map +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ run402 allowance export # print address (for funding)
|
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
49
|
run402 projects sql <id> "CREATE TABLE items (id serial PRIMARY KEY, …)"
|
|
50
|
+
run402 projects validate-expose <id> --file manifest.json # check auth manifest, no mutation
|
|
50
51
|
run402 projects apply-expose <id> --file manifest.json # declare what's reachable
|
|
51
52
|
run402 projects rest <id> items "select=*&order=id.desc&limit=10"
|
|
52
53
|
run402 projects schema <id> # introspect tables + RLS
|
|
@@ -113,7 +114,7 @@ run402 functions deploy <id> my-fn --file fn.ts \
|
|
|
113
114
|
--timeout 30 --memory 256 \
|
|
114
115
|
--schedule "*/15 * * * *" \
|
|
115
116
|
--deps "stripe,zod@^3"
|
|
116
|
-
run402 functions logs <id> my-fn --tail 100 --follow
|
|
117
|
+
run402 functions logs <id> my-fn --tail 100 --request-id req_abc123 --follow
|
|
117
118
|
run402 functions invoke <id> my-fn --body '{"hello":"world"}'
|
|
118
119
|
```
|
|
119
120
|
|
package/lib/deploy-v2.mjs
CHANGED
|
@@ -526,6 +526,14 @@ const ROUTE_WARNING_GUIDANCE = {
|
|
|
526
526
|
"Add method coverage or static files deliberately.",
|
|
527
527
|
],
|
|
528
528
|
},
|
|
529
|
+
WILDCARD_ROUTE_EXCLUDES_MUTATION_METHODS: {
|
|
530
|
+
hint: "A wildcard function route only allows GET/HEAD, so POST/PUT/PATCH/DELETE paths under that prefix will be rejected before the function runs.",
|
|
531
|
+
next_actions: [
|
|
532
|
+
"Add the mutation methods the routed function supports, such as POST.",
|
|
533
|
+
"Omit methods to allow every supported method when the route is an API surface.",
|
|
534
|
+
"Use --allow-warnings only if the wildcard prefix is intentionally read-only.",
|
|
535
|
+
],
|
|
536
|
+
},
|
|
529
537
|
ROUTE_TABLE_NEAR_LIMIT: {
|
|
530
538
|
hint: "The route table is near the gateway/project limit.",
|
|
531
539
|
next_actions: [
|
package/lib/functions.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import { getSdk } from "./sdk.mjs";
|
|
|
4
4
|
import { reportSdkError, fail } from "./sdk-errors.mjs";
|
|
5
5
|
import { assertKnownFlags, hasHelp, normalizeArgv, parseIntegerFlag, validateRegularFile } from "./argparse.mjs";
|
|
6
6
|
|
|
7
|
+
const FUNCTION_LOG_REQUEST_ID_RE = /^req_[A-Za-z0-9_-]{4,128}$/;
|
|
8
|
+
|
|
7
9
|
const HELP = `run402 functions — Manage serverless functions
|
|
8
10
|
|
|
9
11
|
Usage:
|
|
@@ -14,7 +16,7 @@ Subcommands:
|
|
|
14
16
|
Deploy a function to a project
|
|
15
17
|
invoke <id> <name> [--method <M>] [--body <json>]
|
|
16
18
|
Invoke a deployed function
|
|
17
|
-
logs <id> <name> [--tail <n>] [--since <ts>] [--follow]
|
|
19
|
+
logs <id> <name> [--tail <n>] [--since <ts>] [--request-id <req_...>] [--follow]
|
|
18
20
|
Get function logs
|
|
19
21
|
update <id> <name> [--schedule <cron>] [--schedule-remove] [--timeout <s>] [--memory <mb>]
|
|
20
22
|
Update function schedule or config without re-deploying
|
|
@@ -28,6 +30,7 @@ Examples:
|
|
|
28
30
|
run402 functions invoke prj_abc123 stripe-webhook --body '{"event":"test"}'
|
|
29
31
|
run402 functions logs prj_abc123 stripe-webhook --tail 100
|
|
30
32
|
run402 functions logs prj_abc123 stripe-webhook --since 2026-03-29T14:00:00Z
|
|
33
|
+
run402 functions logs prj_abc123 stripe-webhook --request-id req_abc123
|
|
31
34
|
run402 functions logs prj_abc123 stripe-webhook --follow
|
|
32
35
|
run402 functions update prj_abc123 send-reminders --schedule '0 */4 * * *'
|
|
33
36
|
run402 functions update prj_abc123 send-reminders --schedule-remove
|
|
@@ -110,13 +113,15 @@ Arguments:
|
|
|
110
113
|
<name> Function name
|
|
111
114
|
|
|
112
115
|
Options:
|
|
113
|
-
--tail <n> Number of most-recent entries (default 50)
|
|
116
|
+
--tail <n> Number of most-recent entries (default 50, max 1000)
|
|
114
117
|
--since <ts> ISO timestamp or epoch ms; only entries after this
|
|
118
|
+
--request-id <id> Only entries correlated to this req_... request id
|
|
115
119
|
--follow Poll every 3s and stream new entries (Ctrl-C to stop)
|
|
116
120
|
|
|
117
121
|
Examples:
|
|
118
122
|
run402 functions logs prj_abc123 stripe-webhook --tail 100
|
|
119
123
|
run402 functions logs prj_abc123 stripe-webhook --since 2026-03-29T14:00:00Z
|
|
124
|
+
run402 functions logs prj_abc123 stripe-webhook --request-id req_abc123
|
|
120
125
|
run402 functions logs prj_abc123 stripe-webhook --follow
|
|
121
126
|
`,
|
|
122
127
|
update: `run402 functions update — Update function config without re-deploying
|
|
@@ -227,14 +232,16 @@ async function invoke(projectId, name, args) {
|
|
|
227
232
|
}
|
|
228
233
|
|
|
229
234
|
async function logs(projectId, name, args) {
|
|
230
|
-
assertRequiredProjectAndName(projectId, name, "run402 functions logs <project_id> <name> [--tail <n>]");
|
|
231
|
-
assertKnownFlags(args, ["--tail", "--since", "--follow", "--help", "-h"], ["--tail", "--since"]);
|
|
235
|
+
assertRequiredProjectAndName(projectId, name, "run402 functions logs <project_id> <name> [--tail <n>] [--request-id <req_...>]");
|
|
236
|
+
assertKnownFlags(args, ["--tail", "--since", "--request-id", "--follow", "--help", "-h"], ["--tail", "--since", "--request-id"]);
|
|
232
237
|
let tail = 50;
|
|
233
238
|
let since = undefined;
|
|
239
|
+
let requestId = undefined;
|
|
234
240
|
let follow = false;
|
|
235
241
|
for (let i = 0; i < args.length; i++) {
|
|
236
242
|
if (args[i] === "--tail") tail = parseIntegerFlag("--tail", args[++i], { min: 1 });
|
|
237
243
|
if (args[i] === "--since" && args[i + 1]) since = args[++i];
|
|
244
|
+
if (args[i] === "--request-id" && args[i + 1]) requestId = args[++i];
|
|
238
245
|
if (args[i] === "--follow") follow = true;
|
|
239
246
|
}
|
|
240
247
|
|
|
@@ -254,12 +261,20 @@ async function logs(projectId, name, args) {
|
|
|
254
261
|
}
|
|
255
262
|
sinceIso = new Date(ms).toISOString();
|
|
256
263
|
}
|
|
264
|
+
if (requestId !== undefined && !FUNCTION_LOG_REQUEST_ID_RE.test(requestId)) {
|
|
265
|
+
fail({
|
|
266
|
+
code: "BAD_USAGE",
|
|
267
|
+
message: `Invalid --request-id value: ${requestId}`,
|
|
268
|
+
details: { flag: "--request-id", value: requestId, expected: "req_<4-128 url-safe chars>" },
|
|
269
|
+
});
|
|
270
|
+
}
|
|
257
271
|
|
|
258
272
|
const fetchLogs = async () => {
|
|
259
273
|
try {
|
|
260
274
|
const data = await getSdk().functions.logs(projectId, name, {
|
|
261
275
|
tail,
|
|
262
276
|
since: sinceIso,
|
|
277
|
+
requestId,
|
|
263
278
|
});
|
|
264
279
|
return data.logs || [];
|
|
265
280
|
} catch (err) {
|
|
@@ -278,27 +293,60 @@ async function logs(projectId, name, args) {
|
|
|
278
293
|
let running = true;
|
|
279
294
|
process.on("SIGINT", () => { running = false; });
|
|
280
295
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
console.log(`[${entry.timestamp}] ${entry.message}`);
|
|
284
|
-
}
|
|
285
|
-
if (initial.length > 0) {
|
|
286
|
-
sinceIso = new Date(new Date(initial[initial.length - 1].timestamp).getTime() + 1).toISOString();
|
|
287
|
-
}
|
|
296
|
+
let highWaterMs = sinceIso === undefined ? Number.NEGATIVE_INFINITY : new Date(sinceIso).getTime();
|
|
297
|
+
let seenAtHighWater = new Set();
|
|
288
298
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const entries = await fetchLogs();
|
|
299
|
+
const printFreshEntries = (entries) => {
|
|
300
|
+
let nextHighWaterMs = highWaterMs;
|
|
301
|
+
const fresh = [];
|
|
293
302
|
for (const entry of entries) {
|
|
303
|
+
const entryMs = logTimestampMs(entry);
|
|
304
|
+
const identity = logEntryIdentity(entry);
|
|
305
|
+
if (entryMs < highWaterMs) continue;
|
|
306
|
+
if (entryMs === highWaterMs && seenAtHighWater.has(identity)) continue;
|
|
307
|
+
fresh.push({ entry, entryMs, identity });
|
|
308
|
+
if (entryMs > nextHighWaterMs) nextHighWaterMs = entryMs;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
for (const { entry } of fresh) {
|
|
294
312
|
console.log(`[${entry.timestamp}] ${entry.message}`);
|
|
295
313
|
}
|
|
296
|
-
if (
|
|
297
|
-
|
|
314
|
+
if (fresh.length === 0 || !Number.isFinite(nextHighWaterMs)) return;
|
|
315
|
+
|
|
316
|
+
const nextSeenAtHighWater = new Set();
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
if (logTimestampMs(entry) === nextHighWaterMs) {
|
|
319
|
+
nextSeenAtHighWater.add(logEntryIdentity(entry));
|
|
320
|
+
}
|
|
298
321
|
}
|
|
322
|
+
for (const { entry, entryMs, identity } of fresh) {
|
|
323
|
+
if (entryMs === nextHighWaterMs) {
|
|
324
|
+
nextSeenAtHighWater.add(identity);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
highWaterMs = nextHighWaterMs;
|
|
328
|
+
seenAtHighWater = nextSeenAtHighWater;
|
|
329
|
+
sinceIso = new Date(highWaterMs).toISOString();
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
printFreshEntries(await fetchLogs());
|
|
333
|
+
|
|
334
|
+
while (running) {
|
|
335
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
336
|
+
if (!running) break;
|
|
337
|
+
printFreshEntries(await fetchLogs());
|
|
299
338
|
}
|
|
300
339
|
}
|
|
301
340
|
|
|
341
|
+
function logTimestampMs(entry) {
|
|
342
|
+
const ms = new Date(entry.timestamp).getTime();
|
|
343
|
+
return Number.isNaN(ms) ? 0 : ms;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function logEntryIdentity(entry) {
|
|
347
|
+
return entry.event_id || `${entry.log_stream_name || ""}:${entry.timestamp || ""}:${entry.message || ""}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
302
350
|
async function update(projectId, name, args) {
|
|
303
351
|
assertRequiredProjectAndName(projectId, name, "run402 functions update <project_id> <name> [options]");
|
|
304
352
|
assertKnownFlags(args, ["--schedule", "--schedule-remove", "--timeout", "--memory", "--help", "-h"], ["--schedule", "--timeout", "--memory"]);
|
package/lib/projects.mjs
CHANGED
|
@@ -23,6 +23,8 @@ Subcommands:
|
|
|
23
23
|
schema [id] Inspect the database schema
|
|
24
24
|
apply-expose [id] <manifest_json> Apply a declarative authorization manifest
|
|
25
25
|
apply-expose [id] --file <path> Apply a manifest from a JSON file
|
|
26
|
+
validate-expose [id] <manifest_json> Validate an authorization manifest without applying it
|
|
27
|
+
validate-expose [id] --file <path> Validate a manifest file without mutating the project
|
|
26
28
|
get-expose [id] Get the current authorization manifest
|
|
27
29
|
delete [id] --confirm Immediately and irreversibly delete a project (cascade purge) and remove from local state. Requires --confirm.
|
|
28
30
|
pin [id] Pin a project (admin only; uses admin allowance wallet)
|
|
@@ -43,6 +45,7 @@ Examples:
|
|
|
43
45
|
run402 projects usage prj_abc123
|
|
44
46
|
run402 projects costs prj_abc123 --window 30d
|
|
45
47
|
run402 projects schema prj_abc123
|
|
48
|
+
run402 projects validate-expose prj_abc123 --file manifest.json
|
|
46
49
|
run402 projects apply-expose prj_abc123 --file manifest.json
|
|
47
50
|
run402 projects get-expose prj_abc123
|
|
48
51
|
run402 projects keys prj_abc123
|
|
@@ -68,6 +71,9 @@ Notes:
|
|
|
68
71
|
row), public_read_write_UNRESTRICTED (fully open; requires
|
|
69
72
|
"i_understand_this_is_unrestricted": true on the entry), custom (provide
|
|
70
73
|
custom_sql with CREATE POLICY statements).
|
|
74
|
+
- 'validate-expose' checks the same auth/expose manifest shape without
|
|
75
|
+
applying it. Optional migration SQL is used only for reference checks; it is
|
|
76
|
+
not executed as a PostgreSQL dry run.
|
|
71
77
|
`;
|
|
72
78
|
|
|
73
79
|
const SUB_HELP = {
|
|
@@ -135,6 +141,35 @@ Examples:
|
|
|
135
141
|
run402 projects costs prj_abc123
|
|
136
142
|
RUN402_ADMIN_COOKIE='run402_admin=...' run402 projects costs prj_abc123
|
|
137
143
|
RUN402_ADMIN_COOKIE='run402_admin=...' run402 projects costs --window 7d
|
|
144
|
+
`,
|
|
145
|
+
"validate-expose": `run402 projects validate-expose — Validate an authorization manifest without applying it
|
|
146
|
+
|
|
147
|
+
Usage:
|
|
148
|
+
run402 projects validate-expose [id] <manifest_json> [options]
|
|
149
|
+
run402 projects validate-expose [id] --file <path> [options]
|
|
150
|
+
cat manifest.json | run402 projects validate-expose [id] [options]
|
|
151
|
+
|
|
152
|
+
Arguments:
|
|
153
|
+
[id] Optional project ID. When omitted, the active project is
|
|
154
|
+
used if one is set; otherwise validation is projectless.
|
|
155
|
+
<manifest_json> Inline auth/expose manifest JSON.
|
|
156
|
+
|
|
157
|
+
Options:
|
|
158
|
+
--file <path> Read the auth/expose manifest from a JSON file
|
|
159
|
+
--migration-file <path> Read migration SQL for reference checks only
|
|
160
|
+
--migration-sql <sql> Inline migration SQL for reference checks only
|
|
161
|
+
|
|
162
|
+
Notes:
|
|
163
|
+
- This validates the auth/expose manifest used by manifest.json,
|
|
164
|
+
database.expose, and apply-expose. It does not validate deploy manifests.
|
|
165
|
+
- Migration SQL is parsed as context for references; it is not executed.
|
|
166
|
+
- Validation findings are returned in JSON with hasErrors and do not make the
|
|
167
|
+
command fail. Usage, file, auth, and network errors still exit non-zero.
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
run402 projects validate-expose --file manifest.json
|
|
171
|
+
run402 projects validate-expose prj_abc123 --file manifest.json --migration-file setup.sql
|
|
172
|
+
run402 projects validate-expose '{"version":"1","tables":[]}'
|
|
138
173
|
`,
|
|
139
174
|
};
|
|
140
175
|
|
|
@@ -237,6 +272,86 @@ async function applyExpose(projectId, args = []) {
|
|
|
237
272
|
}
|
|
238
273
|
}
|
|
239
274
|
|
|
275
|
+
async function validateExpose(args = []) {
|
|
276
|
+
let projectId = null;
|
|
277
|
+
let file = null;
|
|
278
|
+
let inline = null;
|
|
279
|
+
let migrationFile = null;
|
|
280
|
+
let migrationSql = null;
|
|
281
|
+
for (let i = 0; i < args.length; i++) {
|
|
282
|
+
const arg = args[i];
|
|
283
|
+
if (arg === "--file") {
|
|
284
|
+
if (args[i + 1] === undefined) {
|
|
285
|
+
fail({ code: "BAD_FLAG", message: "--file requires a value", details: { flag: "--file" } });
|
|
286
|
+
}
|
|
287
|
+
file = args[++i];
|
|
288
|
+
}
|
|
289
|
+
else if (arg === "--migration-file") {
|
|
290
|
+
if (args[i + 1] === undefined) {
|
|
291
|
+
fail({ code: "BAD_FLAG", message: "--migration-file requires a value", details: { flag: "--migration-file" } });
|
|
292
|
+
}
|
|
293
|
+
migrationFile = args[++i];
|
|
294
|
+
}
|
|
295
|
+
else if (arg === "--migration-sql") {
|
|
296
|
+
if (args[i + 1] === undefined) {
|
|
297
|
+
fail({ code: "BAD_FLAG", message: "--migration-sql requires a value", details: { flag: "--migration-sql" } });
|
|
298
|
+
}
|
|
299
|
+
migrationSql = args[++i];
|
|
300
|
+
}
|
|
301
|
+
else if (!projectId && typeof arg === "string" && arg.startsWith("prj_")) { projectId = arg; }
|
|
302
|
+
else if (!inline && typeof arg === "string" && !arg.startsWith("--")) { inline = arg; }
|
|
303
|
+
else if (typeof arg === "string" && !arg.startsWith("--")) {
|
|
304
|
+
fail({
|
|
305
|
+
code: "BAD_USAGE",
|
|
306
|
+
message: `Unexpected extra argument: ${arg}`,
|
|
307
|
+
hint: "run402 projects validate-expose [id] <manifest_json>",
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (file && inline) {
|
|
312
|
+
fail({
|
|
313
|
+
code: "BAD_USAGE",
|
|
314
|
+
message: "Provide either inline manifest JSON or --file <path>, not both.",
|
|
315
|
+
hint: "run402 projects validate-expose [id] --file manifest.json",
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
if (migrationFile && migrationSql !== null) {
|
|
319
|
+
fail({
|
|
320
|
+
code: "BAD_USAGE",
|
|
321
|
+
message: "Provide either --migration-file or --migration-sql, not both.",
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
if (file) validateRegularFile(file, "--file");
|
|
325
|
+
if (migrationFile) validateRegularFile(migrationFile, "--migration-file");
|
|
326
|
+
|
|
327
|
+
let raw = file ? readFileSync(file, "utf-8") : inline;
|
|
328
|
+
if (!raw && process.stdin && process.stdin.isTTY === false) {
|
|
329
|
+
raw = readFileSync(0, "utf-8");
|
|
330
|
+
}
|
|
331
|
+
if (!raw) {
|
|
332
|
+
fail({
|
|
333
|
+
code: "BAD_USAGE",
|
|
334
|
+
message: "Missing manifest.",
|
|
335
|
+
hint: "Provide inline JSON, pipe JSON to stdin, or use --file <path>",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const activeProjectId = getActiveProjectId();
|
|
340
|
+
const project = projectId || activeProjectId || undefined;
|
|
341
|
+
if (!project) allowanceAuthHeaders("/projects/v1/expose/validate");
|
|
342
|
+
const migration = migrationFile ? readFileSync(migrationFile, "utf-8") : migrationSql;
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const data = await getSdk().projects.validateExpose(raw, {
|
|
346
|
+
...(project ? { project } : {}),
|
|
347
|
+
...(migration !== null && migration !== undefined ? { migrationSql: migration } : {}),
|
|
348
|
+
});
|
|
349
|
+
console.log(JSON.stringify({ status: "ok", ...data }, null, 2));
|
|
350
|
+
} catch (err) {
|
|
351
|
+
reportSdkError(err);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
240
355
|
async function getExpose(projectId) {
|
|
241
356
|
try {
|
|
242
357
|
const data = await getSdk().projects.getExpose(projectId);
|
|
@@ -463,6 +578,10 @@ const FLAGS_BY_SUB = {
|
|
|
463
578
|
sql: { known: ["--file", "--params"], values: ["--file", "--params"] },
|
|
464
579
|
costs: { known: ["--window"], values: ["--window"] },
|
|
465
580
|
"apply-expose": { known: ["--file"], values: ["--file"] },
|
|
581
|
+
"validate-expose": {
|
|
582
|
+
known: ["--file", "--migration-file", "--migration-sql"],
|
|
583
|
+
values: ["--file", "--migration-file", "--migration-sql"],
|
|
584
|
+
},
|
|
466
585
|
delete: { known: ["--confirm"], values: [] },
|
|
467
586
|
};
|
|
468
587
|
|
|
@@ -495,6 +614,7 @@ export async function run(sub, args) {
|
|
|
495
614
|
case "costs": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true, valueFlags: FLAGS_BY_SUB.costs.values }); await costs(projectId, rest); break; }
|
|
496
615
|
case "schema": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await schema(projectId); break; }
|
|
497
616
|
case "apply-expose": { const { projectId, rest } = resolvePositionalProject(args, { maxBarePositionals: 1, valueFlags: FLAGS_BY_SUB["apply-expose"].values, rejectBareFirstWhenFlagPresent: ["--file"] }); await applyExpose(projectId, rest); break; }
|
|
617
|
+
case "validate-expose": await validateExpose(args); break;
|
|
498
618
|
case "get-expose": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await getExpose(projectId); break; }
|
|
499
619
|
case "delete": { const { projectId, rest } = resolvePositionalProject(args, { rejectBareFirst: true }); await deleteProject(projectId, rest); break; }
|
|
500
620
|
case "pin": { const { projectId } = resolvePositionalProject(args, { rejectBareFirst: true }); await pin(projectId); break; }
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAc3C,OAAO,KAAK,EACV,YAAY,EACZ,sBAAsB,EAKtB,WAAW,EACX,oBAAoB,EAEpB,kBAAkB,EAClB,eAAe,EACf,YAAY,EAWZ,iBAAiB,EAGjB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,2BAA2B,EAC3B,uBAAuB,EACvB,WAAW,EACX,oBAAoB,EACpB,YAAY,
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/namespaces/deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -406,11 +406,11 @@ async function planInternal(client, spec, idempotencyKey, dryRun = false) {
|
|
|
406
406
|
}
|
|
407
407
|
let plan;
|
|
408
408
|
try {
|
|
409
|
-
plan = normalizePlanResponse(await client.request(dryRun ? "/deploy/v2/plans?dry_run=true" : "/deploy/v2/plans", {
|
|
409
|
+
plan = withClientPlanWarnings(normalized, normalizePlanResponse(await client.request(dryRun ? "/deploy/v2/plans?dry_run=true" : "/deploy/v2/plans", {
|
|
410
410
|
method: "POST",
|
|
411
411
|
body,
|
|
412
412
|
context: "planning deploy",
|
|
413
|
-
}));
|
|
413
|
+
})));
|
|
414
414
|
}
|
|
415
415
|
catch (err) {
|
|
416
416
|
throw translateDeployError(err, "plan", null, null);
|
|
@@ -1297,6 +1297,59 @@ function emitPlanWarnings(plan, emit) {
|
|
|
1297
1297
|
emit({ type: "plan.warnings", warnings: plan.warnings });
|
|
1298
1298
|
}
|
|
1299
1299
|
}
|
|
1300
|
+
function withClientPlanWarnings(spec, plan) {
|
|
1301
|
+
const warnings = clientRoutePlanWarnings(spec);
|
|
1302
|
+
if (warnings.length === 0)
|
|
1303
|
+
return plan;
|
|
1304
|
+
const seen = new Set(plan.warnings.map((warning) => warningKey(warning)));
|
|
1305
|
+
const nextWarnings = [...plan.warnings];
|
|
1306
|
+
for (const warning of warnings) {
|
|
1307
|
+
const key = warningKey(warning);
|
|
1308
|
+
if (seen.has(key))
|
|
1309
|
+
continue;
|
|
1310
|
+
seen.add(key);
|
|
1311
|
+
nextWarnings.push(warning);
|
|
1312
|
+
}
|
|
1313
|
+
return { ...plan, warnings: nextWarnings };
|
|
1314
|
+
}
|
|
1315
|
+
function warningKey(warning) {
|
|
1316
|
+
return `${warning.code}:${(warning.affected ?? []).join(",")}`;
|
|
1317
|
+
}
|
|
1318
|
+
function clientRoutePlanWarnings(spec) {
|
|
1319
|
+
const routes = spec.routes;
|
|
1320
|
+
if (!routes || !("replace" in routes))
|
|
1321
|
+
return [];
|
|
1322
|
+
const affected = routes.replace
|
|
1323
|
+
.filter((route) => {
|
|
1324
|
+
if (route.target.type !== "function")
|
|
1325
|
+
return false;
|
|
1326
|
+
if (!route.pattern.endsWith("/*"))
|
|
1327
|
+
return false;
|
|
1328
|
+
if (!route.methods)
|
|
1329
|
+
return false;
|
|
1330
|
+
const methods = new Set(route.methods);
|
|
1331
|
+
return (methods.size > 0 &&
|
|
1332
|
+
[...methods].every((method) => method === "GET" || method === "HEAD"));
|
|
1333
|
+
})
|
|
1334
|
+
.map((route) => route.pattern)
|
|
1335
|
+
.sort();
|
|
1336
|
+
if (affected.length === 0)
|
|
1337
|
+
return [];
|
|
1338
|
+
return [
|
|
1339
|
+
{
|
|
1340
|
+
code: "WILDCARD_ROUTE_EXCLUDES_MUTATION_METHODS",
|
|
1341
|
+
severity: "warn",
|
|
1342
|
+
requires_confirmation: true,
|
|
1343
|
+
message: "A wildcard function route only allows GET/HEAD. Mutation endpoints under that prefix will be rejected by the gateway before the function runs.",
|
|
1344
|
+
affected,
|
|
1345
|
+
confidence: "heuristic",
|
|
1346
|
+
details: {
|
|
1347
|
+
missing_common_methods: ["POST", "PUT", "PATCH", "DELETE"],
|
|
1348
|
+
fix: "Add the mutation methods your routed function supports, or omit methods to allow every supported method.",
|
|
1349
|
+
},
|
|
1350
|
+
},
|
|
1351
|
+
];
|
|
1352
|
+
}
|
|
1300
1353
|
function abortOnConfirmationWarnings(plan, opts) {
|
|
1301
1354
|
if (opts.allowWarnings)
|
|
1302
1355
|
return;
|