naro-cli 0.18.0 → 0.19.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 +204 -24
- package/dist/{chunk-WLC42Y6U.js → chunk-DEEFHVDZ.js} +277 -45
- package/dist/deploy.d.ts +12 -0
- package/dist/deploy.js +2 -2
- package/dist/env-consent.d.ts +9 -1
- package/dist/env-surface.d.ts +12 -0
- package/dist/index.js +415 -142
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -33,8 +33,10 @@ import {
|
|
|
33
33
|
generateTypes,
|
|
34
34
|
indexesFromRows,
|
|
35
35
|
isBookmark,
|
|
36
|
+
isEnvTargetName,
|
|
36
37
|
isExpired,
|
|
37
38
|
isNaroError,
|
|
39
|
+
isReservedEnvTarget,
|
|
38
40
|
isStalledBackend,
|
|
39
41
|
isTerminalStatus,
|
|
40
42
|
lintSchema,
|
|
@@ -42,11 +44,13 @@ import {
|
|
|
42
44
|
loadPolicy,
|
|
43
45
|
loadStoredSession,
|
|
44
46
|
login,
|
|
47
|
+
maskEnvValue,
|
|
45
48
|
migrationNameMismatch,
|
|
46
49
|
migrationsPendingError,
|
|
47
50
|
nextMigrationFileName,
|
|
48
51
|
openBrowser,
|
|
49
52
|
parseCacheControl,
|
|
53
|
+
parseDotenv,
|
|
50
54
|
parseGitHubRemote,
|
|
51
55
|
parsePolicyNode,
|
|
52
56
|
parsePolicyStatement,
|
|
@@ -57,6 +61,7 @@ import {
|
|
|
57
61
|
policiesLostByReset,
|
|
58
62
|
projectPath,
|
|
59
63
|
promptForMissingEnv,
|
|
64
|
+
pullEnv,
|
|
60
65
|
quoteIdent,
|
|
61
66
|
quotePolicyName,
|
|
62
67
|
readProjectLink,
|
|
@@ -86,7 +91,7 @@ import {
|
|
|
86
91
|
usageMonthOf,
|
|
87
92
|
userForAccessToken,
|
|
88
93
|
writeProjectLink
|
|
89
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-DEEFHVDZ.js";
|
|
90
95
|
import "./chunk-K3NQKI34.js";
|
|
91
96
|
|
|
92
97
|
// src/args.ts
|
|
@@ -125,7 +130,8 @@ var BOOLEAN_FLAGS = {
|
|
|
125
130
|
"--empty": "empty",
|
|
126
131
|
"--recursive": "recursive",
|
|
127
132
|
"-r": "recursive",
|
|
128
|
-
"--upsert": "upsert"
|
|
133
|
+
"--upsert": "upsert",
|
|
134
|
+
"--no-branch": "noBranch"
|
|
129
135
|
};
|
|
130
136
|
var REPEATED_FLAGS = {
|
|
131
137
|
"--column": "columns"
|
|
@@ -181,7 +187,10 @@ var STRING_FLAGS = {
|
|
|
181
187
|
"--allowed-mime-types": "allowedMimeTypes",
|
|
182
188
|
"--content-type": "contentType",
|
|
183
189
|
"--cache-control": "cacheControl",
|
|
184
|
-
"--search": "search"
|
|
190
|
+
"--search": "search",
|
|
191
|
+
"--copy-env": "copyEnv",
|
|
192
|
+
"--database": "database",
|
|
193
|
+
"--protection": "protection"
|
|
185
194
|
};
|
|
186
195
|
var SECRET_FLAGS = {
|
|
187
196
|
"--api-key": "--api-key-stdin",
|
|
@@ -523,7 +532,7 @@ function createContext(flags, positionals = []) {
|
|
|
523
532
|
}
|
|
524
533
|
|
|
525
534
|
// src/router.ts
|
|
526
|
-
import { readFileSync as
|
|
535
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
527
536
|
import { dirname as dirname4, join as join6 } from "path";
|
|
528
537
|
import { fileURLToPath } from "url";
|
|
529
538
|
|
|
@@ -2536,7 +2545,7 @@ async function runDbLint(context) {
|
|
|
2536
2545
|
(message) => context.output.warn(message)
|
|
2537
2546
|
);
|
|
2538
2547
|
const input = {
|
|
2539
|
-
tables: tables.filter((
|
|
2548
|
+
tables: tables.filter((table3) => !shadow.has(table3.name)),
|
|
2540
2549
|
foreignKeyCheck,
|
|
2541
2550
|
quickCheck: quick.rows.map(firstCell),
|
|
2542
2551
|
triggers: triggers.rows.map((row) => cellText(triggers, row, "name")),
|
|
@@ -3263,7 +3272,7 @@ function conditionsFrom(flags) {
|
|
|
3263
3272
|
);
|
|
3264
3273
|
return conditions;
|
|
3265
3274
|
}
|
|
3266
|
-
function patchFromFile(context, path,
|
|
3275
|
+
function patchFromFile(context, path, table3) {
|
|
3267
3276
|
let text;
|
|
3268
3277
|
try {
|
|
3269
3278
|
text = readFileSync4(resolve3(context.cwd, path), "utf8");
|
|
@@ -3285,9 +3294,9 @@ function patchFromFile(context, path, table2) {
|
|
|
3285
3294
|
if (Object.hasOwn(body, "table")) {
|
|
3286
3295
|
if (typeof named !== "string")
|
|
3287
3296
|
refuse2(`${path}: table must be a string, the table's name.`);
|
|
3288
|
-
if (
|
|
3297
|
+
if (table3 !== void 0 && asciiLower(named) !== asciiLower(table3))
|
|
3289
3298
|
refuse2(
|
|
3290
|
-
`${path} is the policy of ${printable(named)}, not ${printable(
|
|
3299
|
+
`${path} is the policy of ${printable(named)}, not ${printable(table3)}. Remove its "table" to set it on ${printable(table3)}.`
|
|
3291
3300
|
);
|
|
3292
3301
|
}
|
|
3293
3302
|
const allowed = /* @__PURE__ */ new Set([...POLICY_ACTIONS, "ownerColumn"]);
|
|
@@ -3300,7 +3309,7 @@ function patchFromFile(context, path, table2) {
|
|
|
3300
3309
|
function asciiLower(text) {
|
|
3301
3310
|
return text.replace(/[A-Z]/g, (letter) => letter.toLowerCase());
|
|
3302
3311
|
}
|
|
3303
|
-
function policyPatch(flags, context,
|
|
3312
|
+
function policyPatch(flags, context, table3) {
|
|
3304
3313
|
if (flags.fromFile !== void 0) {
|
|
3305
3314
|
if (OTHER_FLAGS.some(
|
|
3306
3315
|
(key) => flags[key] !== void 0 && flags[key] !== false
|
|
@@ -3309,7 +3318,7 @@ function policyPatch(flags, context, table2) {
|
|
|
3309
3318
|
"--from-file takes the whole policy: leave out the other policy options."
|
|
3310
3319
|
);
|
|
3311
3320
|
if (!context) refuse2(SET_USAGE);
|
|
3312
|
-
return patchFromFile(context, flags.fromFile,
|
|
3321
|
+
return patchFromFile(context, flags.fromFile, table3);
|
|
3313
3322
|
}
|
|
3314
3323
|
const patch = {};
|
|
3315
3324
|
for (const action of POLICY_ACTIONS) {
|
|
@@ -3448,7 +3457,7 @@ function policyTable(policies) {
|
|
|
3448
3457
|
const widths = header.map(
|
|
3449
3458
|
(title, index) => Math.max(title.length, ...rows.map((row) => row[index]?.length ?? 0))
|
|
3450
3459
|
);
|
|
3451
|
-
const
|
|
3460
|
+
const table3 = [header, ...rows].map(
|
|
3452
3461
|
(cells) => cells.map(
|
|
3453
3462
|
(cell, index) => index === cells.length - 1 ? cell : cell.padEnd(widths[index] ?? 0)
|
|
3454
3463
|
).join(" ").trimEnd()
|
|
@@ -3465,10 +3474,10 @@ function policyTable(policies) {
|
|
|
3465
3474
|
}
|
|
3466
3475
|
)
|
|
3467
3476
|
);
|
|
3468
|
-
if (lines.length === 0) return
|
|
3477
|
+
if (lines.length === 0) return table3;
|
|
3469
3478
|
const width = Math.max(...lines.map((line) => line.label.length));
|
|
3470
3479
|
return [
|
|
3471
|
-
...
|
|
3480
|
+
...table3,
|
|
3472
3481
|
"",
|
|
3473
3482
|
...lines.map((line) => `${line.label.padEnd(width)} ${line.text}`)
|
|
3474
3483
|
];
|
|
@@ -3485,39 +3494,39 @@ function statementLine(policy) {
|
|
|
3485
3494
|
);
|
|
3486
3495
|
return shownStatement(policy, policy.table, "naro policy list --json");
|
|
3487
3496
|
}
|
|
3488
|
-
function policyNamed(name,
|
|
3489
|
-
return `Policy ${commentText(quotePolicyName(name))} on ${commentText(quoteIdent(
|
|
3497
|
+
function policyNamed(name, table3) {
|
|
3498
|
+
return `Policy ${commentText(quotePolicyName(name))} on ${commentText(quoteIdent(table3))}`;
|
|
3490
3499
|
}
|
|
3491
|
-
function shownStatement(statement,
|
|
3500
|
+
function shownStatement(statement, table3, exact) {
|
|
3492
3501
|
if (isPrintable(statement.sql)) return statement.sql;
|
|
3493
3502
|
return printable(
|
|
3494
|
-
`-- ${policyNamed(statement.name,
|
|
3503
|
+
`-- ${policyNamed(statement.name, table3)} ${UNSHOWN}, so it is not printed as CREATE POLICY: ${exact} has its exact text.`
|
|
3495
3504
|
);
|
|
3496
3505
|
}
|
|
3497
3506
|
function policyStatements(policies) {
|
|
3498
3507
|
const lines = [];
|
|
3499
|
-
let
|
|
3508
|
+
let table3;
|
|
3500
3509
|
for (const policy of policies) {
|
|
3501
|
-
if (
|
|
3502
|
-
|
|
3510
|
+
if (table3 !== void 0 && policy.table !== table3) lines.push("");
|
|
3511
|
+
table3 = policy.table;
|
|
3503
3512
|
lines.push(statementLine(policy));
|
|
3504
3513
|
}
|
|
3505
3514
|
return lines;
|
|
3506
3515
|
}
|
|
3507
3516
|
async function runPolicyList(context) {
|
|
3508
|
-
const
|
|
3517
|
+
const table3 = context.flags.table;
|
|
3509
3518
|
const client = await context.client();
|
|
3510
3519
|
const projectId = await context.resolveProjectId();
|
|
3511
3520
|
const policies = await client.backend.rlsPolicies(
|
|
3512
3521
|
projectId,
|
|
3513
|
-
|
|
3522
|
+
table3 === void 0 ? {} : { table: table3 }
|
|
3514
3523
|
);
|
|
3515
3524
|
let lines;
|
|
3516
3525
|
if (policies.length > 0)
|
|
3517
3526
|
lines = [...policyStatements(policies), "", DEFAULT_DENY];
|
|
3518
|
-
else if (
|
|
3527
|
+
else if (table3 !== void 0)
|
|
3519
3528
|
lines = [
|
|
3520
|
-
`No policies on ${printable(
|
|
3529
|
+
`No policies on ${printable(table3)}: anon and signed-in requests to it are denied.`
|
|
3521
3530
|
];
|
|
3522
3531
|
else
|
|
3523
3532
|
lines = [
|
|
@@ -3613,17 +3622,17 @@ async function runPolicyCreate(context) {
|
|
|
3613
3622
|
}
|
|
3614
3623
|
async function runPolicyDrop(context) {
|
|
3615
3624
|
const [name] = context.positionalOrThrow(1, DROP_USAGE);
|
|
3616
|
-
const
|
|
3617
|
-
if (
|
|
3625
|
+
const table3 = context.flags.table;
|
|
3626
|
+
if (table3 === void 0 || table3 === "") refuse2(DROP_USAGE);
|
|
3618
3627
|
await confirmOrAbort(
|
|
3619
3628
|
context,
|
|
3620
|
-
`Drop policy ${printable(name)} on ${printable(
|
|
3629
|
+
`Drop policy ${printable(name)} on ${printable(table3)}? It stops applying within about 10 seconds.`
|
|
3621
3630
|
);
|
|
3622
3631
|
const client = await context.client();
|
|
3623
3632
|
const projectId = await context.resolveProjectId();
|
|
3624
3633
|
const dropped = await client.backend.dropRlsPolicy(
|
|
3625
3634
|
projectId,
|
|
3626
|
-
|
|
3635
|
+
table3,
|
|
3627
3636
|
name
|
|
3628
3637
|
);
|
|
3629
3638
|
context.output.result({ projectId, dropped }, [
|
|
@@ -3631,13 +3640,13 @@ async function runPolicyDrop(context) {
|
|
|
3631
3640
|
]);
|
|
3632
3641
|
}
|
|
3633
3642
|
async function runPolicySet(context) {
|
|
3634
|
-
const [
|
|
3635
|
-
const patch = policyPatch(context.flags, context,
|
|
3643
|
+
const [table3] = context.positionalOrThrow(1, SET_USAGE);
|
|
3644
|
+
const patch = policyPatch(context.flags, context, table3);
|
|
3636
3645
|
const client = await context.client();
|
|
3637
3646
|
const projectId = await context.resolveProjectId();
|
|
3638
3647
|
const policy = await client.backend.setPolicy(
|
|
3639
3648
|
projectId,
|
|
3640
|
-
|
|
3649
|
+
table3,
|
|
3641
3650
|
patch
|
|
3642
3651
|
);
|
|
3643
3652
|
let named;
|
|
@@ -3658,14 +3667,14 @@ async function runPolicySet(context) {
|
|
|
3658
3667
|
);
|
|
3659
3668
|
}
|
|
3660
3669
|
async function runPolicyRemove(context) {
|
|
3661
|
-
const [
|
|
3670
|
+
const [table3] = context.positionalOrThrow(1, REMOVE_USAGE);
|
|
3662
3671
|
await confirmOrAbort(
|
|
3663
3672
|
context,
|
|
3664
|
-
`Remove every policy on ${printable(
|
|
3673
|
+
`Remove every policy on ${printable(table3)}? Anon and signed-in requests to it are denied from then on.`
|
|
3665
3674
|
);
|
|
3666
3675
|
const client = await context.client();
|
|
3667
3676
|
const projectId = await context.resolveProjectId();
|
|
3668
|
-
const policy = await client.backend.removePolicy(projectId,
|
|
3677
|
+
const policy = await client.backend.removePolicy(projectId, table3);
|
|
3669
3678
|
const names = policy.policies ?? [];
|
|
3670
3679
|
context.output.result({ projectId, removed: true, policy }, [
|
|
3671
3680
|
names.length === 0 ? `\u2705 Removed the policy for ${printable(policy.table)}. Anon and signed-in requests to it are denied within about 10 seconds.` : `\u2705 Removed ${names.length === 1 ? "policy" : "policies"} ${names.map(printable).join(", ")} on ${printable(policy.table)}. Anon and signed-in requests to it are denied within about 10 seconds.`
|
|
@@ -3752,14 +3761,14 @@ function parseColumnSpec(spec) {
|
|
|
3752
3761
|
column.default = defaultValue(value);
|
|
3753
3762
|
else if (value !== void 0 && key === "references") {
|
|
3754
3763
|
const dot = value.lastIndexOf(".");
|
|
3755
|
-
const
|
|
3764
|
+
const table3 = dot < 0 ? "" : value.slice(0, dot);
|
|
3756
3765
|
const target = dot < 0 ? "" : value.slice(dot + 1);
|
|
3757
|
-
if (!
|
|
3766
|
+
if (!table3 || !target)
|
|
3758
3767
|
throw new NaroError(
|
|
3759
3768
|
"VALIDATION_FAILED",
|
|
3760
3769
|
`${shown}: references needs <table>.<column>.`
|
|
3761
3770
|
);
|
|
3762
|
-
column.references = { table:
|
|
3771
|
+
column.references = { table: table3, column: target };
|
|
3763
3772
|
} else if (value !== void 0 && key === "ondelete") {
|
|
3764
3773
|
onDelete = ON_DELETE[value.toLowerCase()];
|
|
3765
3774
|
if (!onDelete)
|
|
@@ -3839,14 +3848,6 @@ function removedLines(policy, dryRun) {
|
|
|
3839
3848
|
}
|
|
3840
3849
|
return lines;
|
|
3841
3850
|
}
|
|
3842
|
-
function removedBeforeFailure(error) {
|
|
3843
|
-
if (!isNaroError(error)) return [];
|
|
3844
|
-
const removed = error.details?.removedPolicies;
|
|
3845
|
-
if (!Array.isArray(removed)) return [];
|
|
3846
|
-
return removed.filter(
|
|
3847
|
-
(entry) => typeof entry === "object" && entry !== null && typeof entry.table === "string" && entry.ownerColumn !== void 0
|
|
3848
|
-
);
|
|
3849
|
-
}
|
|
3850
3851
|
async function runSchemaChange(context, operations, question) {
|
|
3851
3852
|
const dryRun = context.flags.dryRun === true;
|
|
3852
3853
|
if (question !== void 0 && !dryRun)
|
|
@@ -3872,12 +3873,6 @@ async function runSchemaChange(context, operations, question) {
|
|
|
3872
3873
|
const result = await client.db.schema(projectId, sent, {
|
|
3873
3874
|
...dryRun ? { dryRun: true } : {},
|
|
3874
3875
|
name
|
|
3875
|
-
}).catch((error) => {
|
|
3876
|
-
if (!context.flags.json)
|
|
3877
|
-
for (const policy of removedBeforeFailure(error))
|
|
3878
|
-
for (const line of removedLines(policy, false))
|
|
3879
|
-
context.output.warn(line);
|
|
3880
|
-
throw error;
|
|
3881
3876
|
});
|
|
3882
3877
|
const sqlLines = result.sql.trimEnd().split("\n").map(printable);
|
|
3883
3878
|
const removed = result.removedPolicies ?? [];
|
|
@@ -3938,16 +3933,16 @@ async function runDbIndexUsage() {
|
|
|
3938
3933
|
throw new NaroError("VALIDATION_FAILED", USAGE2.index);
|
|
3939
3934
|
}
|
|
3940
3935
|
async function runDbTableCreate(context) {
|
|
3941
|
-
const [
|
|
3936
|
+
const [table3] = context.positionals;
|
|
3942
3937
|
const specs = columnsFlag(context);
|
|
3943
|
-
if (!
|
|
3938
|
+
if (!table3 || specs.length === 0)
|
|
3944
3939
|
throw new NaroError("VALIDATION_FAILED", USAGE2.create);
|
|
3945
3940
|
const columns = specs.map(parseColumnSpec);
|
|
3946
3941
|
const owner = context.flags.ownerColumn;
|
|
3947
3942
|
await runSchemaChange(context, [
|
|
3948
3943
|
{
|
|
3949
3944
|
op: "createTable",
|
|
3950
|
-
table:
|
|
3945
|
+
table: table3,
|
|
3951
3946
|
columns,
|
|
3952
3947
|
...context.flags.ifNotExists ? { ifNotExists: true } : {},
|
|
3953
3948
|
...owner === void 0 ? {} : { owner: { column: owner } }
|
|
@@ -3955,56 +3950,56 @@ async function runDbTableCreate(context) {
|
|
|
3955
3950
|
]);
|
|
3956
3951
|
}
|
|
3957
3952
|
async function runDbTableAddColumn(context) {
|
|
3958
|
-
const [
|
|
3953
|
+
const [table3] = context.positionals;
|
|
3959
3954
|
const specs = columnsFlag(context);
|
|
3960
3955
|
const [spec] = specs;
|
|
3961
|
-
if (!
|
|
3956
|
+
if (!table3 || spec === void 0 || specs.length !== 1)
|
|
3962
3957
|
throw new NaroError("VALIDATION_FAILED", USAGE2.addColumn);
|
|
3963
3958
|
await runSchemaChange(context, [
|
|
3964
|
-
{ op: "addColumn", table:
|
|
3959
|
+
{ op: "addColumn", table: table3, column: parseColumnSpec(spec) }
|
|
3965
3960
|
]);
|
|
3966
3961
|
}
|
|
3967
3962
|
async function runDbTableRenameColumn(context) {
|
|
3968
|
-
const [
|
|
3969
|
-
if (!
|
|
3963
|
+
const [table3, column, to] = context.positionals;
|
|
3964
|
+
if (!table3 || !column || !to)
|
|
3970
3965
|
throw new NaroError("VALIDATION_FAILED", USAGE2.renameColumn);
|
|
3971
|
-
await runSchemaChange(context, [{ op: "renameColumn", table:
|
|
3966
|
+
await runSchemaChange(context, [{ op: "renameColumn", table: table3, column, to }]);
|
|
3972
3967
|
}
|
|
3973
3968
|
async function runDbTableRename(context) {
|
|
3974
|
-
const [
|
|
3975
|
-
if (!
|
|
3976
|
-
await runSchemaChange(context, [{ op: "renameTable", table:
|
|
3969
|
+
const [table3, to] = context.positionals;
|
|
3970
|
+
if (!table3 || !to) throw new NaroError("VALIDATION_FAILED", USAGE2.rename);
|
|
3971
|
+
await runSchemaChange(context, [{ op: "renameTable", table: table3, to }]);
|
|
3977
3972
|
}
|
|
3978
3973
|
async function runDbTableDropColumn(context) {
|
|
3979
|
-
const [
|
|
3980
|
-
if (!
|
|
3974
|
+
const [table3, column] = context.positionals;
|
|
3975
|
+
if (!table3 || !column)
|
|
3981
3976
|
throw new NaroError("VALIDATION_FAILED", USAGE2.dropColumn);
|
|
3982
|
-
const target = `${
|
|
3977
|
+
const target = `${table3}.${column}`;
|
|
3983
3978
|
await runSchemaChange(
|
|
3984
3979
|
context,
|
|
3985
|
-
[{ op: "dropColumn", table:
|
|
3980
|
+
[{ op: "dropColumn", table: table3, column, confirm: target }],
|
|
3986
3981
|
`Drop column ${printable(target)} and its value in every row? naro db restore can bring it back (Time Travel, 30 days).`
|
|
3987
3982
|
);
|
|
3988
3983
|
}
|
|
3989
3984
|
async function runDbTableDrop(context) {
|
|
3990
|
-
const [
|
|
3991
|
-
if (!
|
|
3985
|
+
const [table3] = context.positionals;
|
|
3986
|
+
if (!table3) throw new NaroError("VALIDATION_FAILED", USAGE2.drop);
|
|
3992
3987
|
await runSchemaChange(
|
|
3993
3988
|
context,
|
|
3994
|
-
[{ op: "dropTable", table:
|
|
3995
|
-
`Drop table ${printable(
|
|
3989
|
+
[{ op: "dropTable", table: table3, confirm: table3 }],
|
|
3990
|
+
`Drop table ${printable(table3)} and every row in it? naro db restore can bring it back (Time Travel, 30 days).`
|
|
3996
3991
|
);
|
|
3997
3992
|
}
|
|
3998
3993
|
async function runDbIndexCreate(context) {
|
|
3999
|
-
const [
|
|
3994
|
+
const [table3, list] = context.positionals;
|
|
4000
3995
|
const columns = listFlag(list);
|
|
4001
|
-
if (!
|
|
3996
|
+
if (!table3 || columns.length === 0)
|
|
4002
3997
|
throw new NaroError("VALIDATION_FAILED", USAGE2.indexCreate);
|
|
4003
3998
|
const name = context.flags.name;
|
|
4004
3999
|
await runSchemaChange(context, [
|
|
4005
4000
|
{
|
|
4006
4001
|
op: "createIndex",
|
|
4007
|
-
table:
|
|
4002
|
+
table: table3,
|
|
4008
4003
|
columns,
|
|
4009
4004
|
...context.flags.unique ? { unique: true } : {},
|
|
4010
4005
|
...name === void 0 ? {} : { name }
|
|
@@ -4337,10 +4332,30 @@ async function runUnlink(context) {
|
|
|
4337
4332
|
}
|
|
4338
4333
|
|
|
4339
4334
|
// src/commands/deploy.ts
|
|
4335
|
+
function deployTarget(flags) {
|
|
4336
|
+
if (flags.env === void 0)
|
|
4337
|
+
return { production: flags.prod, environment: void 0 };
|
|
4338
|
+
if (flags.env === "")
|
|
4339
|
+
throw new NaroError(
|
|
4340
|
+
"VALIDATION_FAILED",
|
|
4341
|
+
"--env must be production, preview or an environment name."
|
|
4342
|
+
);
|
|
4343
|
+
if (flags.prod)
|
|
4344
|
+
throw new NaroError(
|
|
4345
|
+
"VALIDATION_FAILED",
|
|
4346
|
+
"--env and --prod cannot be used together."
|
|
4347
|
+
);
|
|
4348
|
+
if (flags.env === "production")
|
|
4349
|
+
return { production: true, environment: void 0 };
|
|
4350
|
+
if (flags.env === "preview")
|
|
4351
|
+
return { production: false, environment: void 0 };
|
|
4352
|
+
return { production: false, environment: flags.env };
|
|
4353
|
+
}
|
|
4340
4354
|
async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDeploy }) {
|
|
4341
4355
|
const { flags, output } = context;
|
|
4356
|
+
const target = deployTarget(flags);
|
|
4342
4357
|
const link = flags.project ? { projectId: flags.project, teamSlug: void 0 } : readProjectLink(context.cwd);
|
|
4343
|
-
if (
|
|
4358
|
+
if (target.production && !flags.yes && !flags.json && isInteractive()) {
|
|
4344
4359
|
const confirmed = await context.prompter.confirm("Deploy to production?");
|
|
4345
4360
|
if (!confirmed) throw new NaroError("CANCELED", "Canceled.");
|
|
4346
4361
|
}
|
|
@@ -4360,10 +4375,20 @@ async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDep
|
|
|
4360
4375
|
teamSlug: team.slug,
|
|
4361
4376
|
rootDirectory: flags.root ?? null,
|
|
4362
4377
|
branch: flags.branch,
|
|
4363
|
-
production:
|
|
4378
|
+
production: target.production,
|
|
4379
|
+
...target.environment !== void 0 && {
|
|
4380
|
+
environment: target.environment
|
|
4381
|
+
},
|
|
4364
4382
|
...canPrompt && {
|
|
4365
4383
|
onMissingEnv: (keys) => promptForMissingEnv(
|
|
4366
|
-
{
|
|
4384
|
+
{
|
|
4385
|
+
cwd: context.cwd,
|
|
4386
|
+
prompter: context.prompter,
|
|
4387
|
+
output,
|
|
4388
|
+
...target.environment !== void 0 && {
|
|
4389
|
+
environment: target.environment
|
|
4390
|
+
}
|
|
4391
|
+
},
|
|
4367
4392
|
keys
|
|
4368
4393
|
)
|
|
4369
4394
|
},
|
|
@@ -4390,20 +4415,22 @@ async function runDeploy(context, deps = { deploy: deployDirectory, offerAutoDep
|
|
|
4390
4415
|
teamSlug: result.teamSlug
|
|
4391
4416
|
});
|
|
4392
4417
|
const inspect = `${controlPlaneUrl()}${projectPath(result.teamSlug, result.projectSlug)}`;
|
|
4418
|
+
const surface = result.environment ?? (result.production ? "production" : "preview");
|
|
4393
4419
|
context.output.result(
|
|
4394
4420
|
{
|
|
4395
4421
|
deploymentId: result.deploymentId,
|
|
4396
4422
|
projectId: result.projectId,
|
|
4397
4423
|
status: result.status,
|
|
4398
4424
|
url: result.url,
|
|
4399
|
-
target:
|
|
4425
|
+
target: surface,
|
|
4426
|
+
environment: result.environment ?? null,
|
|
4400
4427
|
teamSlug: result.teamSlug,
|
|
4401
4428
|
inspectUrl: inspect,
|
|
4402
4429
|
missingEnvKeys: result.missingEnvKeys
|
|
4403
4430
|
},
|
|
4404
4431
|
[
|
|
4405
4432
|
"",
|
|
4406
|
-
`\u2705 Deployment ready (${
|
|
4433
|
+
`\u2705 Deployment ready (${surface})`,
|
|
4407
4434
|
"",
|
|
4408
4435
|
` ${result.url}`,
|
|
4409
4436
|
` Inspect ${inspect}`
|
|
@@ -4815,18 +4842,29 @@ async function runDomainsRemove(context) {
|
|
|
4815
4842
|
}
|
|
4816
4843
|
|
|
4817
4844
|
// src/commands/env.ts
|
|
4818
|
-
import { chmodSync, existsSync, writeFileSync as writeFileSync4 } from "fs";
|
|
4845
|
+
import { chmodSync, existsSync, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
4819
4846
|
import { resolve as resolve4 } from "path";
|
|
4820
|
-
var TARGETS = /* @__PURE__ */ new Set(["production", "preview", "all"]);
|
|
4821
4847
|
var KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
4848
|
+
var KEY_MAX_LENGTH = 256;
|
|
4849
|
+
var IMPORT_BATCH_SIZE = 100;
|
|
4850
|
+
var VALUE_MAX_BYTES = 5 * 1024;
|
|
4851
|
+
var TARGET_USAGE = "[production|preview|all|<environment>]";
|
|
4852
|
+
function validKey(key) {
|
|
4853
|
+
return KEY_RE.test(key) && key.length <= KEY_MAX_LENGTH;
|
|
4854
|
+
}
|
|
4855
|
+
function isSecretKey(key) {
|
|
4856
|
+
return !key.startsWith("NEXT_PUBLIC_");
|
|
4857
|
+
}
|
|
4858
|
+
function variables(count2) {
|
|
4859
|
+
return `${String(count2)} variable${count2 === 1 ? "" : "s"}`;
|
|
4860
|
+
}
|
|
4822
4861
|
function parseTarget(value) {
|
|
4823
4862
|
if (value === void 0) return "all";
|
|
4824
|
-
if (
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
return value;
|
|
4863
|
+
if (isEnvTargetName(value)) return value;
|
|
4864
|
+
throw new NaroError(
|
|
4865
|
+
"VALIDATION_FAILED",
|
|
4866
|
+
"Target must be production, preview, all or an environment name."
|
|
4867
|
+
);
|
|
4830
4868
|
}
|
|
4831
4869
|
function maskedValue(row) {
|
|
4832
4870
|
if (!row.isSecret) return row.value ?? "";
|
|
@@ -4857,12 +4895,9 @@ async function runEnvList(context) {
|
|
|
4857
4895
|
);
|
|
4858
4896
|
}
|
|
4859
4897
|
async function runEnvAdd(context) {
|
|
4860
|
-
context.positionalOrThrow(
|
|
4861
|
-
1,
|
|
4862
|
-
"Usage: naro env add KEY [production|preview|all]"
|
|
4863
|
-
);
|
|
4898
|
+
context.positionalOrThrow(1, `Usage: naro env add KEY ${TARGET_USAGE}`);
|
|
4864
4899
|
const [key, targetArg] = context.positionals;
|
|
4865
|
-
if (!key || !
|
|
4900
|
+
if (!key || !validKey(key))
|
|
4866
4901
|
throw new NaroError(
|
|
4867
4902
|
"VALIDATION_FAILED",
|
|
4868
4903
|
"Keys must look like MY_VARIABLE (letters, digits, underscore)."
|
|
@@ -4870,25 +4905,38 @@ async function runEnvAdd(context) {
|
|
|
4870
4905
|
const target = parseTarget(targetArg);
|
|
4871
4906
|
const projectId = await context.resolveProjectId();
|
|
4872
4907
|
const client = await context.client();
|
|
4908
|
+
const typed = process.stdin.isTTY === true;
|
|
4909
|
+
if (typed) {
|
|
4910
|
+
await client.projects.get(projectId);
|
|
4911
|
+
if (!isReservedEnvTarget(target))
|
|
4912
|
+
await client.environments.get(projectId, target);
|
|
4913
|
+
}
|
|
4873
4914
|
let value;
|
|
4874
|
-
if (!
|
|
4915
|
+
if (!typed) {
|
|
4875
4916
|
value = await readStdin();
|
|
4876
4917
|
} else {
|
|
4877
4918
|
value = await context.prompter.askHidden(`? What's the value of ${key}? `);
|
|
4878
4919
|
}
|
|
4879
4920
|
if (value.length === 0)
|
|
4880
4921
|
throw new NaroError("VALIDATION_FAILED", `No value provided for ${key}.`);
|
|
4881
|
-
const isSecret =
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4922
|
+
const isSecret = isSecretKey(key);
|
|
4923
|
+
let saved = target;
|
|
4924
|
+
try {
|
|
4925
|
+
await client.env.set(projectId, [{ key, value, target, isSecret }]);
|
|
4926
|
+
} catch (error) {
|
|
4927
|
+
if (!typed || target !== "all" || !(error instanceof NaroError) || error.code !== "FORBIDDEN" || !await context.prompter.confirm(
|
|
4928
|
+
`${error.message} Save ${key} for preview only?`
|
|
4929
|
+
))
|
|
4930
|
+
throw error;
|
|
4931
|
+
saved = "preview";
|
|
4932
|
+
await client.env.set(projectId, [{ key, value, target: saved, isSecret }]);
|
|
4933
|
+
}
|
|
4934
|
+
context.output.result({ projectId, key, target: saved, isSecret }, [
|
|
4935
|
+
`\u2705 Added ${key} (${saved}${isSecret ? ", secret" : ""})`
|
|
4885
4936
|
]);
|
|
4886
4937
|
}
|
|
4887
4938
|
async function runEnvRemove(context) {
|
|
4888
|
-
context.positionalOrThrow(
|
|
4889
|
-
1,
|
|
4890
|
-
"Usage: naro env remove KEY [production|preview|all]"
|
|
4891
|
-
);
|
|
4939
|
+
context.positionalOrThrow(1, `Usage: naro env remove KEY ${TARGET_USAGE}`);
|
|
4892
4940
|
const [key, targetArg] = context.positionals;
|
|
4893
4941
|
const target = targetArg === void 0 ? void 0 : parseTarget(targetArg);
|
|
4894
4942
|
await confirmOrAbort(
|
|
@@ -4902,23 +4950,73 @@ async function runEnvRemove(context) {
|
|
|
4902
4950
|
`\u2705 Removed ${String(key)} (${String(removed)} entr${removed === 1 ? "y" : "ies"})`
|
|
4903
4951
|
]);
|
|
4904
4952
|
}
|
|
4905
|
-
async function
|
|
4906
|
-
|
|
4907
|
-
const
|
|
4908
|
-
|
|
4953
|
+
async function runEnvImport(context) {
|
|
4954
|
+
context.positionalOrThrow(1, `Usage: naro env import <file> ${TARGET_USAGE}`);
|
|
4955
|
+
const [file, targetArg] = context.positionals;
|
|
4956
|
+
const target = parseTarget(targetArg);
|
|
4957
|
+
let text;
|
|
4958
|
+
try {
|
|
4959
|
+
text = readFileSync5(resolve4(context.cwd, file), "utf8");
|
|
4960
|
+
} catch {
|
|
4961
|
+
throw new NaroError("VALIDATION_FAILED", `Cannot read ${file}.`);
|
|
4962
|
+
}
|
|
4963
|
+
const vars = Object.entries(parseDotenv(text));
|
|
4964
|
+
if (vars.length === 0)
|
|
4965
|
+
throw new NaroError("VALIDATION_FAILED", `No variables found in ${file}.`);
|
|
4966
|
+
const invalid = vars.map(([key]) => key).filter((key) => !validKey(key));
|
|
4967
|
+
if (invalid.length > 0)
|
|
4909
4968
|
throw new NaroError(
|
|
4910
4969
|
"VALIDATION_FAILED",
|
|
4911
|
-
|
|
4970
|
+
`Invalid keys in ${file}: ${invalid.join(", ")}.`
|
|
4971
|
+
);
|
|
4972
|
+
const oversized = vars.filter(([, value]) => Buffer.byteLength(value, "utf8") > VALUE_MAX_BYTES).map(([key]) => key);
|
|
4973
|
+
if (oversized.length > 0)
|
|
4974
|
+
throw new NaroError(
|
|
4975
|
+
"VALIDATION_FAILED",
|
|
4976
|
+
`Values over 5 KB in ${file}: ${oversized.join(", ")}.`
|
|
4912
4977
|
);
|
|
4913
|
-
const client = await context.client();
|
|
4914
4978
|
const projectId = await context.resolveProjectId();
|
|
4915
|
-
|
|
4916
|
-
const
|
|
4917
|
-
(
|
|
4979
|
+
context.output.log(`Variables in ${file}:`);
|
|
4980
|
+
for (const [key, value] of vars)
|
|
4981
|
+
context.output.log(` ${key.padEnd(32)} ${maskEnvValue(key, value)}`);
|
|
4982
|
+
await confirmOrAbort(
|
|
4983
|
+
context,
|
|
4984
|
+
`Upload ${variables(vars.length)} to ${target}?`
|
|
4918
4985
|
);
|
|
4919
|
-
const
|
|
4920
|
-
|
|
4986
|
+
const client = await context.client();
|
|
4987
|
+
const entries = vars.map(([key, value]) => ({
|
|
4988
|
+
key,
|
|
4989
|
+
value,
|
|
4990
|
+
target,
|
|
4991
|
+
isSecret: isSecretKey(key)
|
|
4992
|
+
}));
|
|
4993
|
+
for (let start = 0; start < entries.length; start += IMPORT_BATCH_SIZE)
|
|
4994
|
+
await client.env.set(
|
|
4995
|
+
projectId,
|
|
4996
|
+
entries.slice(start, start + IMPORT_BATCH_SIZE)
|
|
4997
|
+
);
|
|
4998
|
+
context.output.result({ projectId, file, target, count: entries.length }, [
|
|
4999
|
+
`\u2705 Imported ${variables(entries.length)} (${target})`
|
|
5000
|
+
]);
|
|
5001
|
+
}
|
|
5002
|
+
function parsePullSurface(value) {
|
|
5003
|
+
if (value === void 0) return "preview";
|
|
5004
|
+
if (value !== "all" && isEnvTargetName(value)) return value;
|
|
5005
|
+
throw new NaroError(
|
|
5006
|
+
"VALIDATION_FAILED",
|
|
5007
|
+
"--env must be production, preview or an environment name."
|
|
4921
5008
|
);
|
|
5009
|
+
}
|
|
5010
|
+
async function runEnvPull(context) {
|
|
5011
|
+
const file = context.positionals[0] ?? ".env.local";
|
|
5012
|
+
const surface = parsePullSurface(context.flags.env);
|
|
5013
|
+
const client = await context.client();
|
|
5014
|
+
const projectId = await context.resolveProjectId();
|
|
5015
|
+
if (!isReservedEnvTarget(surface))
|
|
5016
|
+
await client.environments.get(projectId, surface);
|
|
5017
|
+
const rows = await client.env.list(projectId, { reveal: true });
|
|
5018
|
+
const vars = pullEnv(rows, surface);
|
|
5019
|
+
const count2 = Object.keys(vars).length;
|
|
4922
5020
|
const path = resolve4(context.cwd, file);
|
|
4923
5021
|
if (existsSync(path) && isInteractive() && !context.flags.yes && !context.flags.json) {
|
|
4924
5022
|
const confirmed = await context.prompter.confirm(`Overwrite ${file}?`);
|
|
@@ -4931,10 +5029,137 @@ ${serializeDotenv(vars)}`,
|
|
|
4931
5029
|
{ mode: 384 }
|
|
4932
5030
|
);
|
|
4933
5031
|
chmodSync(path, 384);
|
|
4934
|
-
context.output.result(
|
|
4935
|
-
|
|
4936
|
-
|
|
5032
|
+
context.output.result({ projectId, file, environment: surface, count: count2 }, [
|
|
5033
|
+
`\u2705 Pulled ${String(count2)} variable(s) into ${file}`
|
|
5034
|
+
]);
|
|
5035
|
+
}
|
|
5036
|
+
|
|
5037
|
+
// src/commands/environments.ts
|
|
5038
|
+
var CREATE_USAGE2 = "Usage: naro environments create <slug> [--branch <branch>] [--copy-env preview|production|none] [--database schema|empty]";
|
|
5039
|
+
var SET_USAGE2 = "Usage: naro environments set <slug> [--branch <branch> | --no-branch] [--protection on|off]";
|
|
5040
|
+
var DELETE_USAGE = "Usage: naro environments delete <slug> [--yes]";
|
|
5041
|
+
var NO_BRANCH = "\u2014";
|
|
5042
|
+
function parseCopyEnv(value) {
|
|
5043
|
+
if (value === void 0) return void 0;
|
|
5044
|
+
if (value === "preview" || value === "production") return value;
|
|
5045
|
+
if (value === "none") return null;
|
|
5046
|
+
throw new NaroError(
|
|
5047
|
+
"VALIDATION_FAILED",
|
|
5048
|
+
"--copy-env must be preview, production or none."
|
|
5049
|
+
);
|
|
5050
|
+
}
|
|
5051
|
+
function parseDatabase(value) {
|
|
5052
|
+
if (value === void 0) return void 0;
|
|
5053
|
+
if (value === "schema" || value === "empty") return value;
|
|
5054
|
+
throw new NaroError(
|
|
5055
|
+
"VALIDATION_FAILED",
|
|
5056
|
+
"--database must be schema or empty."
|
|
5057
|
+
);
|
|
5058
|
+
}
|
|
5059
|
+
function parseProtection(value) {
|
|
5060
|
+
if (value === void 0) return void 0;
|
|
5061
|
+
if (value === "on") return true;
|
|
5062
|
+
if (value === "off") return false;
|
|
5063
|
+
throw new NaroError("VALIDATION_FAILED", "--protection must be on or off.");
|
|
5064
|
+
}
|
|
5065
|
+
function table2(rows) {
|
|
5066
|
+
const widths = (rows[0] ?? []).map(
|
|
5067
|
+
(_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length))
|
|
5068
|
+
);
|
|
5069
|
+
return rows.map(
|
|
5070
|
+
(row) => row.map(
|
|
5071
|
+
(cell, column) => column === row.length - 1 ? cell : cell.padEnd(widths[column] ?? 0)
|
|
5072
|
+
).join(" ")
|
|
5073
|
+
);
|
|
5074
|
+
}
|
|
5075
|
+
function environmentRow(environment) {
|
|
5076
|
+
return [
|
|
5077
|
+
environment.slug,
|
|
5078
|
+
environment.url,
|
|
5079
|
+
environment.branch ?? NO_BRANCH,
|
|
5080
|
+
environment.protectionEnabled ? "on" : "off"
|
|
5081
|
+
];
|
|
5082
|
+
}
|
|
5083
|
+
async function runEnvironmentsList(context) {
|
|
5084
|
+
const projectId = await context.resolveProjectId();
|
|
5085
|
+
const client = await context.client();
|
|
5086
|
+
const { environments, limit } = await client.environments.list(projectId);
|
|
5087
|
+
const count2 = String(environments.length);
|
|
5088
|
+
context.output.result({ projectId, environments, limit }, [
|
|
5089
|
+
...environments.length === 0 ? ["No environments. Create one with `naro environments create qa`."] : table2([
|
|
5090
|
+
["slug", "url", "branch", "protection"],
|
|
5091
|
+
...environments.map(environmentRow)
|
|
5092
|
+
]),
|
|
5093
|
+
limit === null ? `${count2} environment${environments.length === 1 ? "" : "s"}` : `${count2} of ${String(limit)} environments`
|
|
5094
|
+
]);
|
|
5095
|
+
}
|
|
5096
|
+
async function runEnvironmentsCreate(context) {
|
|
5097
|
+
const [slug] = context.positionalOrThrow(1, CREATE_USAGE2);
|
|
5098
|
+
const { branch, copyEnv, database } = context.flags;
|
|
5099
|
+
const copyEnvFrom = parseCopyEnv(copyEnv);
|
|
5100
|
+
const seed = parseDatabase(database);
|
|
5101
|
+
const projectId = await context.resolveProjectId();
|
|
5102
|
+
const client = await context.client();
|
|
5103
|
+
const environment = await client.environments.create(projectId, {
|
|
5104
|
+
slug,
|
|
5105
|
+
...branch !== void 0 && { branch },
|
|
5106
|
+
// null is a value here (copy nothing): only undefined leaves the key out.
|
|
5107
|
+
...copyEnvFrom !== void 0 && { copyEnvFrom },
|
|
5108
|
+
...seed !== void 0 && { database: seed }
|
|
5109
|
+
});
|
|
5110
|
+
context.output.result({ projectId, environment }, [
|
|
5111
|
+
`\u2705 Created environment ${environment.slug} \u2192 ${environment.url}`
|
|
5112
|
+
]);
|
|
5113
|
+
}
|
|
5114
|
+
async function runEnvironmentsSet(context) {
|
|
5115
|
+
const [slug] = context.positionalOrThrow(1, SET_USAGE2);
|
|
5116
|
+
const { branch, noBranch, protection } = context.flags;
|
|
5117
|
+
if (branch !== void 0 && noBranch === true)
|
|
5118
|
+
throw new NaroError(
|
|
5119
|
+
"VALIDATION_FAILED",
|
|
5120
|
+
"--branch and --no-branch cannot be used together."
|
|
5121
|
+
);
|
|
5122
|
+
const protectionEnabled = parseProtection(protection);
|
|
5123
|
+
if (branch === void 0 && noBranch !== true && protectionEnabled === void 0)
|
|
5124
|
+
throw new NaroError(
|
|
5125
|
+
"VALIDATION_FAILED",
|
|
5126
|
+
"Nothing to change: pass --branch, --no-branch or --protection."
|
|
5127
|
+
);
|
|
5128
|
+
const patch = {
|
|
5129
|
+
...branch !== void 0 && { branch },
|
|
5130
|
+
...noBranch === true && { branch: null },
|
|
5131
|
+
...protectionEnabled !== void 0 && { protectionEnabled }
|
|
5132
|
+
};
|
|
5133
|
+
const projectId = await context.resolveProjectId();
|
|
5134
|
+
if (protectionEnabled === false)
|
|
5135
|
+
await confirmOrAbort(
|
|
5136
|
+
context,
|
|
5137
|
+
`Turn off protection for ${slug}? Anyone with the link can open it.`
|
|
5138
|
+
);
|
|
5139
|
+
const client = await context.client();
|
|
5140
|
+
const found = await client.environments.get(projectId, slug);
|
|
5141
|
+
const environment = await client.environments.update(
|
|
5142
|
+
projectId,
|
|
5143
|
+
found.id,
|
|
5144
|
+
patch
|
|
5145
|
+
);
|
|
5146
|
+
context.output.result({ projectId, environment }, [
|
|
5147
|
+
`\u2705 Updated environment ${slug}`
|
|
5148
|
+
]);
|
|
5149
|
+
}
|
|
5150
|
+
async function runEnvironmentsDelete(context) {
|
|
5151
|
+
const [slug] = context.positionalOrThrow(1, DELETE_USAGE);
|
|
5152
|
+
const projectId = await context.resolveProjectId();
|
|
5153
|
+
await confirmOrAbort(
|
|
5154
|
+
context,
|
|
5155
|
+
`Delete environment ${slug}? Its worker and protection key are removed.`
|
|
4937
5156
|
);
|
|
5157
|
+
const client = await context.client();
|
|
5158
|
+
const found = await client.environments.get(projectId, slug);
|
|
5159
|
+
await client.environments.delete(projectId, found.id);
|
|
5160
|
+
context.output.result({ projectId, slug, deleted: true }, [
|
|
5161
|
+
`\u2705 Deleted environment ${slug}`
|
|
5162
|
+
]);
|
|
4938
5163
|
}
|
|
4939
5164
|
|
|
4940
5165
|
// src/commands/gen.ts
|
|
@@ -4954,7 +5179,7 @@ async function runGenTypes(context) {
|
|
|
4954
5179
|
read.policies === void 0 ? {} : { policies: read.policies }
|
|
4955
5180
|
);
|
|
4956
5181
|
const undescribed = tables.flatMap(
|
|
4957
|
-
(
|
|
5182
|
+
(table3) => table3.error === void 0 ? [] : [{ name: table3.name, error: table3.error }]
|
|
4958
5183
|
);
|
|
4959
5184
|
for (const { name, error } of undescribed)
|
|
4960
5185
|
context.output.warn(
|
|
@@ -5395,7 +5620,7 @@ function count(n, noun) {
|
|
|
5395
5620
|
}
|
|
5396
5621
|
|
|
5397
5622
|
// src/commands/storage.ts
|
|
5398
|
-
var
|
|
5623
|
+
var CREATE_USAGE3 = "Usage: naro storage bucket create <name> [--public] [--file-size-limit <size>] [--allowed-mime-types <t,\u2026>]";
|
|
5399
5624
|
var UPDATE_USAGE = "Usage: naro storage bucket update <name> [--public | --private] [--file-size-limit <size> | none] [--allowed-mime-types <t,\u2026> | any]";
|
|
5400
5625
|
var EMPTY_USAGE = "Usage: naro storage bucket empty <name> [--yes]";
|
|
5401
5626
|
var REMOVE_USAGE2 = "Usage: naro storage bucket remove <name> [--empty] [--yes]";
|
|
@@ -5535,7 +5760,7 @@ async function runStorageBucketList(context) {
|
|
|
5535
5760
|
);
|
|
5536
5761
|
}
|
|
5537
5762
|
async function runStorageBucketCreate(context) {
|
|
5538
|
-
const name = bucketName(context,
|
|
5763
|
+
const name = bucketName(context, CREATE_USAGE3);
|
|
5539
5764
|
const input = { name, ...bucketFields(context.flags) };
|
|
5540
5765
|
const client = await context.client();
|
|
5541
5766
|
const projectId = await context.resolveProjectId();
|
|
@@ -7152,8 +7377,8 @@ function patternOf(pattern) {
|
|
|
7152
7377
|
}
|
|
7153
7378
|
var MAX_LIMIT = 1e3;
|
|
7154
7379
|
var CAP = `one select reads at most ${String(MAX_LIMIT)} rows, by limit() or range()`;
|
|
7155
|
-
function initialState(
|
|
7156
|
-
return { table:
|
|
7380
|
+
function initialState(table3) {
|
|
7381
|
+
return { table: table3, problem: null, filters: [], columns: "*", order: [] };
|
|
7157
7382
|
}
|
|
7158
7383
|
function copyRow(row) {
|
|
7159
7384
|
return { ...row };
|
|
@@ -7386,11 +7611,11 @@ var SelectBuilder = class extends FilterBuilder {
|
|
|
7386
7611
|
return this.next({ single: "maybe" });
|
|
7387
7612
|
}
|
|
7388
7613
|
build() {
|
|
7389
|
-
const { table:
|
|
7614
|
+
const { table: table3, columns, order, limit, offset, single } = this.state;
|
|
7390
7615
|
return {
|
|
7391
7616
|
version: 1,
|
|
7392
7617
|
action: "select",
|
|
7393
|
-
table:
|
|
7618
|
+
table: table3,
|
|
7394
7619
|
columns: columns === "*" ? "*" : [...columns],
|
|
7395
7620
|
...this.filtersPart(),
|
|
7396
7621
|
...order.length === 0 ? {} : { order: order.map((term) => ({ ...term })) },
|
|
@@ -7406,11 +7631,11 @@ var InsertBuilder = class extends Builder {
|
|
|
7406
7631
|
return this.withReturning(columns);
|
|
7407
7632
|
}
|
|
7408
7633
|
build() {
|
|
7409
|
-
const { table:
|
|
7634
|
+
const { table: table3, rows = [], returning } = this.state;
|
|
7410
7635
|
return {
|
|
7411
7636
|
version: 1,
|
|
7412
7637
|
action: "insert",
|
|
7413
|
-
table:
|
|
7638
|
+
table: table3,
|
|
7414
7639
|
rows: rows.map(copyRow),
|
|
7415
7640
|
...returning === void 0 ? {} : { returning }
|
|
7416
7641
|
};
|
|
@@ -7427,7 +7652,7 @@ var MutationBuilder = class extends FilterBuilder {
|
|
|
7427
7652
|
}
|
|
7428
7653
|
build() {
|
|
7429
7654
|
const {
|
|
7430
|
-
table:
|
|
7655
|
+
table: table3,
|
|
7431
7656
|
action,
|
|
7432
7657
|
values = {},
|
|
7433
7658
|
returning,
|
|
@@ -7438,10 +7663,10 @@ var MutationBuilder = class extends FilterBuilder {
|
|
|
7438
7663
|
...returning === void 0 ? {} : { returning },
|
|
7439
7664
|
...allowUnfiltered === void 0 ? {} : { allowUnfiltered }
|
|
7440
7665
|
};
|
|
7441
|
-
return action === "delete" ? { version: 1, action: "delete", table:
|
|
7666
|
+
return action === "delete" ? { version: 1, action: "delete", table: table3, ...tail } : {
|
|
7442
7667
|
version: 1,
|
|
7443
7668
|
action: "update",
|
|
7444
|
-
table:
|
|
7669
|
+
table: table3,
|
|
7445
7670
|
values: copyRow(values),
|
|
7446
7671
|
...tail
|
|
7447
7672
|
};
|
|
@@ -7450,9 +7675,9 @@ var MutationBuilder = class extends FilterBuilder {
|
|
|
7450
7675
|
var TableBuilder = class {
|
|
7451
7676
|
#run;
|
|
7452
7677
|
#table;
|
|
7453
|
-
constructor(run,
|
|
7678
|
+
constructor(run, table3) {
|
|
7454
7679
|
this.#run = run;
|
|
7455
|
-
this.#table =
|
|
7680
|
+
this.#table = table3;
|
|
7456
7681
|
}
|
|
7457
7682
|
/** `"*"` (the default) or `"id, title"`. */
|
|
7458
7683
|
select(columns) {
|
|
@@ -8268,8 +8493,8 @@ var NaroClient2 = class {
|
|
|
8268
8493
|
this.storage = new StorageClient(http, this.auth);
|
|
8269
8494
|
}
|
|
8270
8495
|
/** Start a query on `table`: `.select()`, `.insert()`, `.update()` or `.delete()`. */
|
|
8271
|
-
from(
|
|
8272
|
-
return new TableBuilder(this.#run,
|
|
8496
|
+
from(table3) {
|
|
8497
|
+
return new TableBuilder(this.#run, table3);
|
|
8273
8498
|
}
|
|
8274
8499
|
};
|
|
8275
8500
|
function createClient(options) {
|
|
@@ -8918,15 +9143,16 @@ function meterRow(meter, limitsKnown) {
|
|
|
8918
9143
|
if (!limitsKnown) return [label3, used, "\u2014", "\u2014"];
|
|
8919
9144
|
if (meter.limit === null) return [label3, used, "No limit", "\u2014"];
|
|
8920
9145
|
if (meter.limit === 0) return [label3, used, "Not included", "\u2014"];
|
|
8921
|
-
return [
|
|
8922
|
-
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
];
|
|
9146
|
+
return [label3, used, formatCount(meter.limit), usedPercent(meter)];
|
|
9147
|
+
}
|
|
9148
|
+
function usedPercent(meter) {
|
|
9149
|
+
const percent2 = meter.percent ?? 0;
|
|
9150
|
+
return percent2 === 0 && meter.used > 0 ? "<1%" : `${String(percent2)}%`;
|
|
8927
9151
|
}
|
|
8928
9152
|
function projectName(project) {
|
|
8929
9153
|
if (project.deleted) return `Deleted project ${printable(project.projectId)}`;
|
|
9154
|
+
if (project.moved)
|
|
9155
|
+
return `Moved to another team ${printable(project.projectId)}`;
|
|
8930
9156
|
return printable(project.name ?? project.projectId);
|
|
8931
9157
|
}
|
|
8932
9158
|
function usageLines2(report, summary) {
|
|
@@ -8995,6 +9221,7 @@ Usage:
|
|
|
8995
9221
|
|
|
8996
9222
|
naro deploy [--prod] Build locally and deploy (preview by default)
|
|
8997
9223
|
[--root <dir>] [--name <name>] --root points at the app in a monorepo
|
|
9224
|
+
[--env <name>] Deploy to a named environment instead of preview (not with --prod)
|
|
8998
9225
|
naro deployments List deployments of the linked project
|
|
8999
9226
|
naro inspect <deployment-id> Show one deployment + build logs
|
|
9000
9227
|
naro cancel <deployment-id> Stop a build that is still running
|
|
@@ -9020,10 +9247,19 @@ Usage:
|
|
|
9020
9247
|
naro protection enable | disable Protect the preview URL (members only) or open it to anyone with the link (disable asks first; --yes in CI)
|
|
9021
9248
|
naro protection bypass [remove] Create (shown once) or remove the secret automation sends as x-naro-protection-bypass (replacing or removing asks first; --yes in CI)
|
|
9022
9249
|
|
|
9023
|
-
naro env list List env vars (secrets masked)
|
|
9024
|
-
naro env add KEY [
|
|
9025
|
-
naro env remove KEY [
|
|
9250
|
+
naro env list List env vars (secrets masked); an environment's value shows its name
|
|
9251
|
+
naro env add KEY [target] Add/update (value from prompt or stdin)
|
|
9252
|
+
naro env remove KEY [target] Remove; with no target only production, preview and all, never an environment's
|
|
9253
|
+
naro env import <file> [target] Upload a dotenv file's variables (asks first; --yes in CI)
|
|
9026
9254
|
naro env pull [file] Write env vars to .env.local
|
|
9255
|
+
target is production, preview, all or an environment name; add and import use all when none is given
|
|
9256
|
+
|
|
9257
|
+
naro environments List the project's environments: address, branch, protection, and how many the plan allows
|
|
9258
|
+
naro environments create <slug> [--branch <b>] [--copy-env preview|production|none] [--database schema|empty]
|
|
9259
|
+
A QA or staging surface served at <subdomain>--<slug>; starts with preview's variables
|
|
9260
|
+
naro environments set <slug> [--branch <b> | --no-branch] [--protection on|off]
|
|
9261
|
+
Change the branch whose pushes deploy here, or who can open it (protection off asks first; --yes in CI)
|
|
9262
|
+
naro environments delete <slug> Delete it with its worker and protection key (asks first; --yes in CI)
|
|
9027
9263
|
|
|
9028
9264
|
naro domains list | add <host> [--no-www] | remove <host>
|
|
9029
9265
|
add example.com also connects www.example.com and redirects the apex to
|
|
@@ -9181,7 +9417,14 @@ Global options:
|
|
|
9181
9417
|
--device <d> Only this device: desktop, mobile or tablet (naro analytics, naro insights)
|
|
9182
9418
|
--browser <b> Only this browser: chrome, edge, firefox, opera, safari or other (naro analytics, naro insights)
|
|
9183
9419
|
--os <o> Only this operating system: android, ios, linux, macos, windows or other (naro analytics, naro insights)
|
|
9184
|
-
--env <e> production or preview (naro logs, naro insights: production by default; naro env pull: preview by default)
|
|
9420
|
+
--env <e> production or preview (naro logs, naro insights: production by default; naro env pull, naro deploy: preview by default, or an environment name)
|
|
9421
|
+
--branch <b> naro environments create, set: the branch whose pushes deploy to that environment. naro deploy: the default
|
|
9422
|
+
branch of a project this deploy creates or updates (the deploy's own branch follows where it goes)
|
|
9423
|
+
--no-branch No branch deploys to the environment any more (naro environments set; not with --branch)
|
|
9424
|
+
--copy-env <source> Where a new environment's variables come from: preview (default), production or none (naro environments create)
|
|
9425
|
+
--database <seed> What a new environment's database starts with: schema (default) or empty (naro environments create)
|
|
9426
|
+
--protection <on|off> on: only members of the project can open the environment; off: anyone with the link can
|
|
9427
|
+
(naro environments set; off asks first, --yes in CI)
|
|
9185
9428
|
--no-www Register the apex alone (naro domains add)
|
|
9186
9429
|
--file <path> SQL file to run (naro db query, naro policy create)
|
|
9187
9430
|
--dry-run Show what would change without changing it (naro db push, db restore, db reset, db table, db index)
|
|
@@ -9447,11 +9690,41 @@ function route(parsed) {
|
|
|
9447
9690
|
case "remove":
|
|
9448
9691
|
case "rm":
|
|
9449
9692
|
return { handler: runEnvRemove, positionals: positionals.slice(1) };
|
|
9693
|
+
case "import":
|
|
9694
|
+
return { handler: runEnvImport, positionals: positionals.slice(1) };
|
|
9450
9695
|
case "pull":
|
|
9451
9696
|
return { handler: runEnvPull, positionals: positionals.slice(1) };
|
|
9452
9697
|
default:
|
|
9453
9698
|
return null;
|
|
9454
9699
|
}
|
|
9700
|
+
// Spec 2026-10-18-named-environments §14.2.
|
|
9701
|
+
case "environments":
|
|
9702
|
+
switch (sub) {
|
|
9703
|
+
case void 0:
|
|
9704
|
+
case "list":
|
|
9705
|
+
case "ls":
|
|
9706
|
+
return {
|
|
9707
|
+
handler: runEnvironmentsList,
|
|
9708
|
+
positionals: positionals.slice(1)
|
|
9709
|
+
};
|
|
9710
|
+
case "create":
|
|
9711
|
+
return {
|
|
9712
|
+
handler: runEnvironmentsCreate,
|
|
9713
|
+
positionals: positionals.slice(1)
|
|
9714
|
+
};
|
|
9715
|
+
case "set":
|
|
9716
|
+
return {
|
|
9717
|
+
handler: runEnvironmentsSet,
|
|
9718
|
+
positionals: positionals.slice(1)
|
|
9719
|
+
};
|
|
9720
|
+
case "delete":
|
|
9721
|
+
return {
|
|
9722
|
+
handler: runEnvironmentsDelete,
|
|
9723
|
+
positionals: positionals.slice(1)
|
|
9724
|
+
};
|
|
9725
|
+
default:
|
|
9726
|
+
return null;
|
|
9727
|
+
}
|
|
9455
9728
|
case "domains":
|
|
9456
9729
|
switch (sub) {
|
|
9457
9730
|
case void 0:
|
|
@@ -9617,7 +9890,7 @@ function route(parsed) {
|
|
|
9617
9890
|
function version() {
|
|
9618
9891
|
try {
|
|
9619
9892
|
const pkg = JSON.parse(
|
|
9620
|
-
|
|
9893
|
+
readFileSync6(
|
|
9621
9894
|
join6(dirname4(fileURLToPath(import.meta.url)), "..", "package.json"),
|
|
9622
9895
|
"utf8"
|
|
9623
9896
|
)
|