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
|
@@ -15091,7 +15091,13 @@ var createProjectSchema = external_exports.object({
|
|
|
15091
15091
|
rootDirectory: optionalRootDirectory
|
|
15092
15092
|
});
|
|
15093
15093
|
var createDeploymentSchema = external_exports.object({
|
|
15094
|
-
branch: external_exports.string().trim().max(240).optional().transform((value) => value || null)
|
|
15094
|
+
branch: external_exports.string().trim().max(240).optional().transform((value) => value || null),
|
|
15095
|
+
/**
|
|
15096
|
+
* The surface to deploy to: `production`, `preview`, or the slug of one of
|
|
15097
|
+
* the project's environments. Whether the name exists is the route's to say;
|
|
15098
|
+
* 63 is the DNS label a worker name has to fit in.
|
|
15099
|
+
*/
|
|
15100
|
+
environment: external_exports.string().trim().min(1).max(63).optional()
|
|
15095
15101
|
});
|
|
15096
15102
|
var connectRepositorySchema = external_exports.object({
|
|
15097
15103
|
// Web-only route context: the only consumer is apps/web's
|
|
@@ -15354,6 +15360,40 @@ function splitStatements(sql) {
|
|
|
15354
15360
|
};
|
|
15355
15361
|
}
|
|
15356
15362
|
|
|
15363
|
+
// ../../packages/core/src/surface.ts
|
|
15364
|
+
var PRODUCTION_SURFACE = { kind: "production" };
|
|
15365
|
+
var PREVIEW_SURFACE = { kind: "preview" };
|
|
15366
|
+
var ENVIRONMENT_SLUG_MAX_LENGTH = 20;
|
|
15367
|
+
var RESERVED_ENVIRONMENT_SLUGS = [
|
|
15368
|
+
"production",
|
|
15369
|
+
"preview",
|
|
15370
|
+
"all"
|
|
15371
|
+
];
|
|
15372
|
+
var SLUG_PATTERN = /^[a-z0-9](-?[a-z0-9])*$/;
|
|
15373
|
+
var ENVIRONMENT_SEPARATOR = "--";
|
|
15374
|
+
var PREVIEW_SUFFIX = "-preview";
|
|
15375
|
+
function environmentSlugProblem(value) {
|
|
15376
|
+
if (value.length < 1 || value.length > ENVIRONMENT_SLUG_MAX_LENGTH)
|
|
15377
|
+
return "invalid";
|
|
15378
|
+
if (!SLUG_PATTERN.test(value)) return "invalid";
|
|
15379
|
+
if (RESERVED_ENVIRONMENT_SLUGS.includes(value))
|
|
15380
|
+
return "reserved";
|
|
15381
|
+
return null;
|
|
15382
|
+
}
|
|
15383
|
+
function environmentWorkerName(subdomain, slug) {
|
|
15384
|
+
return `${subdomain}${ENVIRONMENT_SEPARATOR}${slug}`;
|
|
15385
|
+
}
|
|
15386
|
+
function workerNameForSurface(subdomain, surface) {
|
|
15387
|
+
switch (surface.kind) {
|
|
15388
|
+
case "production":
|
|
15389
|
+
return subdomain;
|
|
15390
|
+
case "preview":
|
|
15391
|
+
return `${subdomain}${PREVIEW_SUFFIX}`;
|
|
15392
|
+
case "environment":
|
|
15393
|
+
return environmentWorkerName(subdomain, surface.slug);
|
|
15394
|
+
}
|
|
15395
|
+
}
|
|
15396
|
+
|
|
15357
15397
|
// ../../packages/build/src/next-version.ts
|
|
15358
15398
|
var NEXT_RELEASE_DATES = {
|
|
15359
15399
|
16: "2025-10-21",
|
|
@@ -17104,6 +17144,12 @@ async function collectProjectFacts(appRoot) {
|
|
|
17104
17144
|
function label(strategy) {
|
|
17105
17145
|
return `P${String(strategy.priority)} ${strategy.id}`;
|
|
17106
17146
|
}
|
|
17147
|
+
function clause(text2) {
|
|
17148
|
+
return text2.endsWith(".") && !text2.endsWith("..") ? text2.slice(0, -1) : text2;
|
|
17149
|
+
}
|
|
17150
|
+
function sentence(text2) {
|
|
17151
|
+
return text2.endsWith(".") ? text2 : `${text2}.`;
|
|
17152
|
+
}
|
|
17107
17153
|
function describeOutcome(outcome) {
|
|
17108
17154
|
switch (outcome.status) {
|
|
17109
17155
|
case "success":
|
|
@@ -17128,7 +17174,7 @@ async function evaluateOne(strategy, context, observer) {
|
|
|
17128
17174
|
await observer.onLog({
|
|
17129
17175
|
level: outcome.status === "success" ? "INFO" : "WARN",
|
|
17130
17176
|
stage: "BUILDING",
|
|
17131
|
-
message: `${label(strategy)} ${describeOutcome(outcome)}
|
|
17177
|
+
message: sentence(`${label(strategy)} ${describeOutcome(outcome)}`)
|
|
17132
17178
|
});
|
|
17133
17179
|
return { id: strategy.id, priority: strategy.priority, outcome };
|
|
17134
17180
|
}
|
|
@@ -17226,10 +17272,13 @@ async function resolveDeploymentStrategy(strategies, context, observer, options)
|
|
|
17226
17272
|
attempts
|
|
17227
17273
|
};
|
|
17228
17274
|
}
|
|
17275
|
+
const othersLeft = candidates.some(
|
|
17276
|
+
(candidate) => candidate.id !== pinnedStrategy.id
|
|
17277
|
+
);
|
|
17229
17278
|
await observer.onLog({
|
|
17230
17279
|
level: "WARN",
|
|
17231
17280
|
stage: "BUILDING",
|
|
17232
|
-
message: `Stored strategy ${label(pinnedStrategy)} no longer works; resolving again with the remaining candidates.`
|
|
17281
|
+
message: othersLeft ? `Stored strategy ${label(pinnedStrategy)} no longer works; resolving again with the remaining candidates.` : `Stored strategy ${label(pinnedStrategy)} no longer works, and no other deployment strategy is left to try for Next.js ${context.nextVersion}.`
|
|
17233
17282
|
});
|
|
17234
17283
|
}
|
|
17235
17284
|
} else if (options.pinned) {
|
|
@@ -17242,13 +17291,14 @@ async function resolveDeploymentStrategy(strategies, context, observer, options)
|
|
|
17242
17291
|
const remaining = candidates.filter(
|
|
17243
17292
|
(candidate) => !attempts.some((attempt) => attempt.id === candidate.id)
|
|
17244
17293
|
);
|
|
17245
|
-
|
|
17246
|
-
|
|
17247
|
-
|
|
17248
|
-
|
|
17249
|
-
|
|
17250
|
-
|
|
17251
|
-
|
|
17294
|
+
if (remaining.length > 0)
|
|
17295
|
+
await observer.onLog({
|
|
17296
|
+
level: "INFO",
|
|
17297
|
+
stage: "BUILDING",
|
|
17298
|
+
message: `Resolving deployment strategy for Next.js ${context.nextVersion}: ${remaining.map(label).join(
|
|
17299
|
+
", "
|
|
17300
|
+
)}${options.concurrency > 1 && remaining.length > 1 ? " (in parallel)" : ""}.`
|
|
17301
|
+
});
|
|
17252
17302
|
if (options.concurrency > 1) {
|
|
17253
17303
|
const results = await runPool(
|
|
17254
17304
|
remaining.map(
|
|
@@ -17266,11 +17316,25 @@ async function resolveDeploymentStrategy(strategies, context, observer, options)
|
|
|
17266
17316
|
}
|
|
17267
17317
|
const best = pickBest(candidates, attempts);
|
|
17268
17318
|
if (!best) {
|
|
17319
|
+
const summary = attempts.map(
|
|
17320
|
+
(attempt) => clause(
|
|
17321
|
+
`P${String(attempt.priority)} ${attempt.id} ${describeOutcome(attempt.outcome)}`
|
|
17322
|
+
)
|
|
17323
|
+
).join("; ");
|
|
17324
|
+
if (attempts.some(
|
|
17325
|
+
(attempt) => attempt.outcome.status === "failed" && attempt.outcome.appBuild
|
|
17326
|
+
))
|
|
17327
|
+
throw new DeploymentError(
|
|
17328
|
+
"BUILD_FAILED",
|
|
17329
|
+
sentence(
|
|
17330
|
+
`The app's own build failed for Next.js ${context.nextVersion}: ${summary}`
|
|
17331
|
+
)
|
|
17332
|
+
);
|
|
17269
17333
|
throw new DeploymentError(
|
|
17270
17334
|
"NO_DEPLOYMENT_STRATEGY",
|
|
17271
|
-
|
|
17272
|
-
|
|
17273
|
-
)
|
|
17335
|
+
sentence(
|
|
17336
|
+
`No deployment strategy succeeded for Next.js ${context.nextVersion}: ${summary}`
|
|
17337
|
+
)
|
|
17274
17338
|
);
|
|
17275
17339
|
}
|
|
17276
17340
|
await observer.onLog({
|
|
@@ -17499,6 +17563,9 @@ function describeFailure(step) {
|
|
|
17499
17563
|
function failed(stage, reason) {
|
|
17500
17564
|
return { status: "failed", stage, reason };
|
|
17501
17565
|
}
|
|
17566
|
+
function appBuildFailed(reason) {
|
|
17567
|
+
return { status: "failed", stage: "build", reason, appBuild: true };
|
|
17568
|
+
}
|
|
17502
17569
|
async function info(observer, message) {
|
|
17503
17570
|
await observer.onLog({ level: "INFO", stage: "BUILDING", message });
|
|
17504
17571
|
}
|
|
@@ -17797,7 +17864,7 @@ async function buildDeclaresNodeMiddleware(appRoot) {
|
|
|
17797
17864
|
}
|
|
17798
17865
|
}
|
|
17799
17866
|
function createOpenNextCloudflareStrategy(options) {
|
|
17800
|
-
async function attempt(context, roots, observer) {
|
|
17867
|
+
async function attempt(context, roots, observer, ownBuild) {
|
|
17801
17868
|
let platform;
|
|
17802
17869
|
try {
|
|
17803
17870
|
platform = await options.createPlatformBuildConfig(
|
|
@@ -17832,7 +17899,7 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17832
17899
|
return {
|
|
17833
17900
|
ok: false,
|
|
17834
17901
|
nodeMiddleware: null,
|
|
17835
|
-
outcome: failed("build", describeFailure(nextBuild))
|
|
17902
|
+
outcome: ownBuild ? appBuildFailed(describeFailure(nextBuild)) : failed("build", describeFailure(nextBuild))
|
|
17836
17903
|
};
|
|
17837
17904
|
}
|
|
17838
17905
|
if (await buildDeclaresNodeMiddleware(roots.appRoot)) {
|
|
@@ -17941,7 +18008,7 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17941
18008
|
observer,
|
|
17942
18009
|
"P2 opennext-cloudflare: building the project as written."
|
|
17943
18010
|
);
|
|
17944
|
-
const first = await attempt(context, context.workspace, observer);
|
|
18011
|
+
const first = await attempt(context, context.workspace, observer, true);
|
|
17945
18012
|
if (first.ok) {
|
|
17946
18013
|
return {
|
|
17947
18014
|
status: "success",
|
|
@@ -17973,8 +18040,47 @@ function createOpenNextCloudflareStrategy(options) {
|
|
|
17973
18040
|
observer,
|
|
17974
18041
|
`Converted ${conversion.file ?? context.facts.proxyFile} to edge middleware for this build only. Next.js 16 proxy runs on the Node.js runtime, which Cloudflare does not support; middleware runs on the Edge runtime, so Node.js-only APIs are unavailable. Your repository is unchanged.`
|
|
17975
18042
|
);
|
|
17976
|
-
const second = await attempt(context, fork, observer);
|
|
17977
|
-
if (!second.ok)
|
|
18043
|
+
const second = await attempt(context, fork, observer, rememberedRewrite);
|
|
18044
|
+
if (!second.ok) {
|
|
18045
|
+
if (!rememberedRewrite || second.outcome.status !== "failed" || !second.outcome.appBuild)
|
|
18046
|
+
return second.outcome;
|
|
18047
|
+
await info(
|
|
18048
|
+
observer,
|
|
18049
|
+
`P2 opennext-cloudflare: the rewritten copy did not build; building the project as written once to tell the app's own error from the rewrite's.`
|
|
18050
|
+
);
|
|
18051
|
+
const asWritten = await attempt(
|
|
18052
|
+
context,
|
|
18053
|
+
context.workspace,
|
|
18054
|
+
observer,
|
|
18055
|
+
true
|
|
18056
|
+
);
|
|
18057
|
+
if (asWritten.ok) {
|
|
18058
|
+
await info(
|
|
18059
|
+
observer,
|
|
18060
|
+
"P2 opennext-cloudflare: the project as written builds and ships as it is; the rewrite is no longer needed."
|
|
18061
|
+
);
|
|
18062
|
+
return {
|
|
18063
|
+
status: "success",
|
|
18064
|
+
artifact: {
|
|
18065
|
+
kind: "cloudflare-worker",
|
|
18066
|
+
artifact: asWritten.artifact
|
|
18067
|
+
},
|
|
18068
|
+
notes: []
|
|
18069
|
+
};
|
|
18070
|
+
}
|
|
18071
|
+
if (asWritten.outcome.status === "failed" && asWritten.outcome.appBuild) {
|
|
18072
|
+
await warn(
|
|
18073
|
+
observer,
|
|
18074
|
+
"P2 opennext-cloudflare: the project as written does not build either, so the error is the app's own."
|
|
18075
|
+
);
|
|
18076
|
+
return asWritten.outcome;
|
|
18077
|
+
}
|
|
18078
|
+
await warn(
|
|
18079
|
+
observer,
|
|
18080
|
+
"P2 opennext-cloudflare: the project as written builds, so the rewritten copy's error is the Edge rewrite's (a Node-only API reached from proxy.ts), not the app's code."
|
|
18081
|
+
);
|
|
18082
|
+
return failed("build", second.outcome.reason);
|
|
18083
|
+
}
|
|
17978
18084
|
return {
|
|
17979
18085
|
status: "success",
|
|
17980
18086
|
artifact: { kind: "cloudflare-worker", artifact: second.artifact },
|
|
@@ -19351,6 +19457,14 @@ function openBrowser(url2) {
|
|
|
19351
19457
|
child.unref();
|
|
19352
19458
|
}
|
|
19353
19459
|
|
|
19460
|
+
// ../../packages/sdk/src/environments.ts
|
|
19461
|
+
function isReservedEnvTarget(value) {
|
|
19462
|
+
return RESERVED_ENVIRONMENT_SLUGS.includes(value);
|
|
19463
|
+
}
|
|
19464
|
+
function isEnvTargetName(value) {
|
|
19465
|
+
return environmentSlugProblem(value) !== "invalid";
|
|
19466
|
+
}
|
|
19467
|
+
|
|
19354
19468
|
// ../../packages/sdk/src/insights.ts
|
|
19355
19469
|
var SPEED_INSIGHTS_METRICS = [
|
|
19356
19470
|
"lcp",
|
|
@@ -19463,6 +19577,7 @@ var NaroClient = class {
|
|
|
19463
19577
|
insights;
|
|
19464
19578
|
analytics;
|
|
19465
19579
|
protection;
|
|
19580
|
+
environments;
|
|
19466
19581
|
constructor(options) {
|
|
19467
19582
|
this.baseUrl = (options.baseUrl ?? controlPlaneUrl()).replace(/\/$/, "");
|
|
19468
19583
|
this.getAccessToken = options.getAccessToken;
|
|
@@ -19481,6 +19596,7 @@ var NaroClient = class {
|
|
|
19481
19596
|
this.insights = new InsightsApi(this);
|
|
19482
19597
|
this.analytics = new AnalyticsApi(this);
|
|
19483
19598
|
this.protection = new ProtectionApi(this);
|
|
19599
|
+
this.environments = new EnvironmentsApi(this);
|
|
19484
19600
|
}
|
|
19485
19601
|
async request(path, options) {
|
|
19486
19602
|
const token = await this.getAccessToken();
|
|
@@ -19645,7 +19761,10 @@ var DeploymentsApi = class {
|
|
|
19645
19761
|
{
|
|
19646
19762
|
method: "POST",
|
|
19647
19763
|
// createDeploymentSchema accepts string | undefined — never null.
|
|
19648
|
-
body:
|
|
19764
|
+
body: {
|
|
19765
|
+
...options.branch ? { branch: options.branch } : {},
|
|
19766
|
+
...options.environment ? { environment: options.environment } : {}
|
|
19767
|
+
},
|
|
19649
19768
|
context: "Create deployment"
|
|
19650
19769
|
}
|
|
19651
19770
|
);
|
|
@@ -19764,11 +19883,16 @@ var EnvApi = class {
|
|
|
19764
19883
|
{ method: "DELETE", context: "Remove environment variable" }
|
|
19765
19884
|
);
|
|
19766
19885
|
}
|
|
19767
|
-
/**
|
|
19886
|
+
/**
|
|
19887
|
+
* Removes a key's rows: the one `target` names, or, with no target, the
|
|
19888
|
+
* `production`, `preview` and `all` rows. An environment's value goes only
|
|
19889
|
+
* when its slug is the target, so removing a key everywhere else does not
|
|
19890
|
+
* take a QA or staging value with it.
|
|
19891
|
+
*/
|
|
19768
19892
|
async remove(projectId, key, target2) {
|
|
19769
19893
|
const rows = await this.list(projectId);
|
|
19770
19894
|
const matches = rows.filter(
|
|
19771
|
-
(row) => row.key === key && (target2 === void 0
|
|
19895
|
+
(row) => row.key === key && (target2 === void 0 ? isReservedEnvTarget(row.target) : row.target === target2)
|
|
19772
19896
|
);
|
|
19773
19897
|
if (matches.length === 0) {
|
|
19774
19898
|
throw new NaroError(
|
|
@@ -20061,6 +20185,55 @@ var ProtectionApi = class {
|
|
|
20061
20185
|
);
|
|
20062
20186
|
}
|
|
20063
20187
|
};
|
|
20188
|
+
var EnvironmentsApi = class {
|
|
20189
|
+
constructor(client) {
|
|
20190
|
+
this.client = client;
|
|
20191
|
+
}
|
|
20192
|
+
base(projectId) {
|
|
20193
|
+
return `/api/projects/${encodeURIComponent(projectId)}/environments`;
|
|
20194
|
+
}
|
|
20195
|
+
/** The project's environments and how many its plan allows (null: no limit). */
|
|
20196
|
+
async list(projectId) {
|
|
20197
|
+
return this.client.request(this.base(projectId), {
|
|
20198
|
+
context: "List environments"
|
|
20199
|
+
});
|
|
20200
|
+
}
|
|
20201
|
+
/** One environment by its slug, from the list: the routes address it by id. */
|
|
20202
|
+
async get(projectId, slug) {
|
|
20203
|
+
const { environments } = await this.list(projectId);
|
|
20204
|
+
const environment = environments.find((entry) => entry.slug === slug);
|
|
20205
|
+
if (!environment)
|
|
20206
|
+
throw new NaroError("ENVIRONMENT_NOT_FOUND", "Environment not found.");
|
|
20207
|
+
return environment;
|
|
20208
|
+
}
|
|
20209
|
+
async create(projectId, input) {
|
|
20210
|
+
const body = await this.client.request(
|
|
20211
|
+
this.base(projectId),
|
|
20212
|
+
{
|
|
20213
|
+
method: "POST",
|
|
20214
|
+
// JSON drops a key that is undefined and keeps a null, which is what
|
|
20215
|
+
// the route reads: no copyEnvFrom is "preview", null copies nothing.
|
|
20216
|
+
body: input,
|
|
20217
|
+
context: "Create environment"
|
|
20218
|
+
}
|
|
20219
|
+
);
|
|
20220
|
+
return body.environment;
|
|
20221
|
+
}
|
|
20222
|
+
async update(projectId, environmentId, patch) {
|
|
20223
|
+
const body = await this.client.request(
|
|
20224
|
+
`${this.base(projectId)}/${encodeURIComponent(environmentId)}`,
|
|
20225
|
+
{ method: "PATCH", body: patch, context: "Update environment" }
|
|
20226
|
+
);
|
|
20227
|
+
return body.environment;
|
|
20228
|
+
}
|
|
20229
|
+
/** Removes the environment, its worker and its protection. */
|
|
20230
|
+
async delete(projectId, environmentId) {
|
|
20231
|
+
await this.client.request(
|
|
20232
|
+
`${this.base(projectId)}/${encodeURIComponent(environmentId)}`,
|
|
20233
|
+
{ method: "DELETE", context: "Delete environment" }
|
|
20234
|
+
);
|
|
20235
|
+
}
|
|
20236
|
+
};
|
|
20064
20237
|
var EXPORT_FIRST_POLL_MS = 1e3;
|
|
20065
20238
|
var EXPORT_MAX_POLL_MS = 5e3;
|
|
20066
20239
|
var EXPORT_TIMEOUT_MS = 15 * 6e4;
|
|
@@ -20245,10 +20418,12 @@ var DbApi = class {
|
|
|
20245
20418
|
* `d1_migrations` — all operations or none. `dryRun` returns the SQL and
|
|
20246
20419
|
* the name without applying anything. Save `sql` as `migrations/<name>` so
|
|
20247
20420
|
* `naro db reset` re-applies it; a refused operation's index is in
|
|
20248
|
-
* `details.operation
|
|
20249
|
-
*
|
|
20250
|
-
*
|
|
20251
|
-
*
|
|
20421
|
+
* `details.operation`, or — refused by the policy translation, or failed
|
|
20422
|
+
* in SQLite — in `statementIndex` (one statement per operation). The Data API policies go in the same migration: a
|
|
20423
|
+
* rename carries the table's and rewrites the others that read it, a
|
|
20424
|
+
* dropped table's go with it, and what an earlier table left under a name
|
|
20425
|
+
* the change gives is removed — `removedPolicies` lists what went. A change
|
|
20426
|
+
* that fails changes none of them.
|
|
20252
20427
|
*/
|
|
20253
20428
|
async schema(projectId, operations, options = {}) {
|
|
20254
20429
|
return this.client.request(
|
|
@@ -20996,7 +21171,7 @@ import { join as join19 } from "path";
|
|
|
20996
21171
|
function parseDotenv(text2) {
|
|
20997
21172
|
const out = {};
|
|
20998
21173
|
for (const line of text2.split(/\r?\n/)) {
|
|
20999
|
-
const trimmed = line.trim();
|
|
21174
|
+
const trimmed = line.trim().replace(/^export\s+(?=[^=\s])/, "");
|
|
21000
21175
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
21001
21176
|
const eq = trimmed.indexOf("=");
|
|
21002
21177
|
if (eq <= 0) continue;
|
|
@@ -27501,16 +27676,16 @@ function readCreate(stream, start) {
|
|
|
27501
27676
|
let check2 = null;
|
|
27502
27677
|
let position = start.position;
|
|
27503
27678
|
if (stream.acceptKeyword("using")) {
|
|
27504
|
-
const
|
|
27505
|
-
using =
|
|
27506
|
-
position =
|
|
27679
|
+
const clause2 = readClause(stream);
|
|
27680
|
+
using = clause2.text;
|
|
27681
|
+
position = clause2.position;
|
|
27507
27682
|
}
|
|
27508
27683
|
if (isKeyword(stream.peek(), "with")) {
|
|
27509
27684
|
stream.next();
|
|
27510
27685
|
stream.expectKeyword("check");
|
|
27511
|
-
const
|
|
27512
|
-
check2 =
|
|
27513
|
-
position =
|
|
27686
|
+
const clause2 = readClause(stream);
|
|
27687
|
+
check2 = clause2.text;
|
|
27688
|
+
position = clause2.position;
|
|
27514
27689
|
}
|
|
27515
27690
|
if (!roles.ok) throw new Error("unreachable");
|
|
27516
27691
|
readEnd(stream);
|
|
@@ -28024,6 +28199,7 @@ COMMENT ON POLICY ${quotePolicyName(policy.name)} ON ${policyTableSql(policy.tab
|
|
|
28024
28199
|
function unreadablePolicyComment(policy) {
|
|
28025
28200
|
return `-- Policy ${commentText(quotePolicyName(policy.name))} on ${commentText(policyTableSql(policy.table))} cannot be read (${commentText(policy.invalid)}); the requests it applies to are refused until it is dropped and created again.`;
|
|
28026
28201
|
}
|
|
28202
|
+
var STORED_POLICIES_SQL = `SELECT table_name, name, kind, command, roles, using_expr, check_expr, owner_column, updated_at FROM ${NAMED_POLICY_TABLE} ORDER BY table_name, name`;
|
|
28027
28203
|
var SYSTEM_PREDICATE = systemTablePredicate("name");
|
|
28028
28204
|
var STORAGE_EXISTS = `EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ${sqlStringLiteral(STORAGE_OBJECTS_TABLE)})`;
|
|
28029
28205
|
|
|
@@ -28968,9 +29144,20 @@ function policiesFromFiles(files) {
|
|
|
28968
29144
|
tables,
|
|
28969
29145
|
refused: { file: file2, reason: planned.message }
|
|
28970
29146
|
};
|
|
28971
|
-
for (const statement2 of planned.plan.statements) {
|
|
29147
|
+
for (const [index, statement2] of planned.plan.statements.entries()) {
|
|
28972
29148
|
if (statement2.kind === "create-policy") {
|
|
28973
29149
|
const { table, name } = statement2.policy;
|
|
29150
|
+
if (policies.some(
|
|
29151
|
+
(policy) => sameTable(policy.table, table) && policy.name === name
|
|
29152
|
+
))
|
|
29153
|
+
return {
|
|
29154
|
+
policies,
|
|
29155
|
+
tables,
|
|
29156
|
+
refused: {
|
|
29157
|
+
file: file2,
|
|
29158
|
+
reason: `statement ${String(index + 1)} of ${String(planned.plan.statements.length)} failed and the migration was rolled back: policy "${name}" for table "${table}" already exists`
|
|
29159
|
+
}
|
|
29160
|
+
};
|
|
28974
29161
|
policies.push({ table, name });
|
|
28975
29162
|
} else if (statement2.kind === "drop-policy") {
|
|
28976
29163
|
policies = policies.filter(
|
|
@@ -28988,6 +29175,10 @@ function policiesFromFiles(files) {
|
|
|
28988
29175
|
if (!tables.has(asciiLower(head.to))) forget(head.to);
|
|
28989
29176
|
tables.delete(asciiLower(head.table));
|
|
28990
29177
|
tables.add(asciiLower(head.to));
|
|
29178
|
+
if (!sameTable(head.table, STORAGE_OBJECTS) && !sameTable(head.table, REALTIME_MESSAGES))
|
|
29179
|
+
policies = policies.map(
|
|
29180
|
+
(policy) => sameTable(policy.table, head.table) ? { ...policy, table: head.to } : policy
|
|
29181
|
+
);
|
|
28991
29182
|
}
|
|
28992
29183
|
}
|
|
28993
29184
|
}
|
|
@@ -29093,26 +29284,31 @@ function usageStatus(report, currentMonth, requests) {
|
|
|
29093
29284
|
import { existsSync as existsSync6 } from "fs";
|
|
29094
29285
|
import { join as join23 } from "path";
|
|
29095
29286
|
var DOTENV_FILES = [".env.local", ".env.production", ".env"];
|
|
29096
|
-
var
|
|
29287
|
+
var PUBLIC_PREFIX = "NEXT_PUBLIC_";
|
|
29288
|
+
function maskSecretValue(value) {
|
|
29289
|
+
return value.length <= 8 ? "****" : `${value.slice(0, 4)}****`;
|
|
29290
|
+
}
|
|
29097
29291
|
function maskEnvValue(key, value) {
|
|
29098
|
-
if (
|
|
29292
|
+
if (key.startsWith(PUBLIC_PREFIX))
|
|
29099
29293
|
return value.length > 60 ? `${value.slice(0, 60)}\u2026` : value;
|
|
29100
|
-
return value
|
|
29294
|
+
return maskSecretValue(value);
|
|
29101
29295
|
}
|
|
29102
29296
|
function findDotenvFile(dir) {
|
|
29103
29297
|
for (const name of DOTENV_FILES) if (existsSync6(join23(dir, name))) return name;
|
|
29104
29298
|
return null;
|
|
29105
29299
|
}
|
|
29106
|
-
function envAddGuidance(keys) {
|
|
29300
|
+
function envAddGuidance(keys, environment) {
|
|
29301
|
+
const suffix = environment === void 0 ? "" : ` ${environment}`;
|
|
29107
29302
|
return [
|
|
29108
29303
|
" Set them yourself:",
|
|
29109
|
-
...keys.map((key) => ` npx naro-cli env add ${key}`),
|
|
29304
|
+
...keys.map((key) => ` npx naro-cli env add ${key}${suffix}`),
|
|
29110
29305
|
" (values come from a hidden prompt or stdin, never from argv)"
|
|
29111
29306
|
];
|
|
29112
29307
|
}
|
|
29113
29308
|
async function promptForMissingEnv(ctx, keys) {
|
|
29114
29309
|
const guide = (remaining2) => {
|
|
29115
|
-
for (const line of envAddGuidance(remaining2
|
|
29310
|
+
for (const line of envAddGuidance(remaining2, ctx.environment))
|
|
29311
|
+
ctx.output.log(line);
|
|
29116
29312
|
};
|
|
29117
29313
|
const file2 = findDotenvFile(ctx.cwd);
|
|
29118
29314
|
if (!file2) {
|
|
@@ -29150,6 +29346,20 @@ async function promptForMissingEnv(ctx, keys) {
|
|
|
29150
29346
|
return collected;
|
|
29151
29347
|
}
|
|
29152
29348
|
|
|
29349
|
+
// src/env-surface.ts
|
|
29350
|
+
function pullEnv(rows, surface) {
|
|
29351
|
+
const chosen = /* @__PURE__ */ new Map();
|
|
29352
|
+
for (const row of rows) {
|
|
29353
|
+
if (row.target !== "all" && row.target !== surface) continue;
|
|
29354
|
+
const held = chosen.get(row.key);
|
|
29355
|
+
if (!held || held.target === "all" && row.target !== "all")
|
|
29356
|
+
chosen.set(row.key, row);
|
|
29357
|
+
}
|
|
29358
|
+
return Object.fromEntries(
|
|
29359
|
+
[...chosen.values()].map((row) => [row.key, row.value ?? ""])
|
|
29360
|
+
);
|
|
29361
|
+
}
|
|
29362
|
+
|
|
29153
29363
|
// src/git-info.ts
|
|
29154
29364
|
import { execFile as execFile2 } from "child_process";
|
|
29155
29365
|
var SUPPORTED_REMOTE = [
|
|
@@ -29740,6 +29950,13 @@ function localDependencies(sourceDir, say = () => void 0) {
|
|
|
29740
29950
|
async function deployDirectory(sourceDir, options, log2) {
|
|
29741
29951
|
const cpUrl = controlPlaneUrl();
|
|
29742
29952
|
const production = options.production ?? false;
|
|
29953
|
+
const environment = production ? void 0 : options.environment;
|
|
29954
|
+
if (environment === "production" || environment === "preview")
|
|
29955
|
+
throw new NaroError(
|
|
29956
|
+
"VALIDATION_FAILED",
|
|
29957
|
+
"Pass production: true for production; preview is the default."
|
|
29958
|
+
);
|
|
29959
|
+
const surface = production ? "production" : environment ?? "preview";
|
|
29743
29960
|
const appRoot = options.rootDirectory ? resolve11(sourceDir, options.rootDirectory) : sourceDir;
|
|
29744
29961
|
const framework = detectFramework2(appRoot);
|
|
29745
29962
|
if (!framework.deployable && !options.force) {
|
|
@@ -29762,6 +29979,8 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29762
29979
|
getAccessToken: () => auth.getAccessToken(),
|
|
29763
29980
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
29764
29981
|
});
|
|
29982
|
+
if (environment !== void 0 && options.projectId)
|
|
29983
|
+
await client.environments.get(options.projectId, environment);
|
|
29765
29984
|
const name = slugify2(options.name ?? await projectName(appRoot));
|
|
29766
29985
|
const git2 = await (options.gitInfo ?? detectGitInfo)(sourceDir);
|
|
29767
29986
|
const branch = options.branch ?? git2.branch;
|
|
@@ -29779,7 +29998,7 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29779
29998
|
if (options.projectId) {
|
|
29780
29999
|
try {
|
|
29781
30000
|
const rows = await client.env.list(options.projectId);
|
|
29782
|
-
existing = new Set(
|
|
30001
|
+
existing = new Set(Object.keys(pullEnv(rows, surface)));
|
|
29783
30002
|
} catch {
|
|
29784
30003
|
}
|
|
29785
30004
|
}
|
|
@@ -29791,8 +30010,10 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29791
30010
|
log2(
|
|
29792
30011
|
`\u25B8 ${String(missing.length)} env var(s) needed, not set on this project: ${missing.join(", ")}`
|
|
29793
30012
|
);
|
|
29794
|
-
|
|
29795
|
-
|
|
30013
|
+
const canCollect = environment === void 0 || options.projectId;
|
|
30014
|
+
if (options.onMissingEnv && canCollect)
|
|
30015
|
+
collected = await options.onMissingEnv(missing);
|
|
30016
|
+
else for (const line of envAddGuidance(missing, environment)) log2(line);
|
|
29796
30017
|
}
|
|
29797
30018
|
const missingEnvKeys = missing.filter((key) => !(key in collected));
|
|
29798
30019
|
const envToSend = { ...options.env, ...collected };
|
|
@@ -29801,6 +30022,9 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29801
30022
|
...options.projectId ? { projectId: options.projectId } : { name },
|
|
29802
30023
|
...options.teamSlug ? { teamSlug: options.teamSlug } : {},
|
|
29803
30024
|
production,
|
|
30025
|
+
...options.environment !== void 0 && {
|
|
30026
|
+
environment: options.environment
|
|
30027
|
+
},
|
|
29804
30028
|
...git2.repositoryUrl != null && { repositoryUrl: git2.repositoryUrl },
|
|
29805
30029
|
...branch != null && { branch },
|
|
29806
30030
|
...repositoryRoot != null && { rootDirectory: repositoryRoot },
|
|
@@ -29891,6 +30115,8 @@ async function deployDirectory(sourceDir, options, log2) {
|
|
|
29891
30115
|
status: detail.deployment.status,
|
|
29892
30116
|
url: url2,
|
|
29893
30117
|
production,
|
|
30118
|
+
// A control plane from before named environments sends no environment.
|
|
30119
|
+
environment: ctx.environment ?? null,
|
|
29894
30120
|
missingEnvKeys,
|
|
29895
30121
|
teamSlug: ctx.teamSlug,
|
|
29896
30122
|
projectSlug: ctx.projectSlug,
|
|
@@ -29908,9 +30134,10 @@ async function waitForFinish(client, ctx, log2, intervalMs) {
|
|
|
29908
30134
|
}
|
|
29909
30135
|
function deploymentUrl(detail, ctx, production) {
|
|
29910
30136
|
if (detail.deployment.url) return detail.deployment.url;
|
|
29911
|
-
const surface = ctx.
|
|
30137
|
+
const surface = ctx.environment ? { kind: "environment", slug: ctx.environment, environmentId: null } : production ? PRODUCTION_SURFACE : PREVIEW_SURFACE;
|
|
30138
|
+
const worker = workerNameForSurface(ctx.subdomain ?? ctx.projectId, surface);
|
|
29912
30139
|
const host = new URL(ctx.controlPlaneUrl || controlPlaneUrl()).hostname;
|
|
29913
|
-
return `https://${
|
|
30140
|
+
return `https://${worker}.${host}`;
|
|
29914
30141
|
}
|
|
29915
30142
|
|
|
29916
30143
|
export {
|
|
@@ -29934,6 +30161,8 @@ export {
|
|
|
29934
30161
|
suppliedToken,
|
|
29935
30162
|
resolveAuth,
|
|
29936
30163
|
openBrowser,
|
|
30164
|
+
isReservedEnvTarget,
|
|
30165
|
+
isEnvTargetName,
|
|
29937
30166
|
SPEED_INSIGHTS_METRICS,
|
|
29938
30167
|
SPEED_INSIGHTS_FILTER_KEYS,
|
|
29939
30168
|
SPEED_INSIGHTS_MIN_DEPLOYMENT_SAMPLES,
|
|
@@ -29949,6 +30178,7 @@ export {
|
|
|
29949
30178
|
cellText,
|
|
29950
30179
|
renderRows,
|
|
29951
30180
|
summarizeStatement,
|
|
30181
|
+
parseDotenv,
|
|
29952
30182
|
serializeDotenv,
|
|
29953
30183
|
MAX_SINGLE_UPLOAD_BYTES,
|
|
29954
30184
|
parseCacheControl,
|
|
@@ -29996,9 +30226,11 @@ export {
|
|
|
29996
30226
|
storedPolicySql,
|
|
29997
30227
|
usageMonthOf,
|
|
29998
30228
|
summarizeTeamUsage,
|
|
30229
|
+
maskEnvValue,
|
|
29999
30230
|
promptForMissingEnv,
|
|
30231
|
+
pullEnv,
|
|
30000
30232
|
detectGitInfo,
|
|
30001
|
-
parseDotenv2
|
|
30233
|
+
parseDotenv2,
|
|
30002
30234
|
readProjectEnv2 as readProjectEnv,
|
|
30003
30235
|
eventsObserver,
|
|
30004
30236
|
slugify2 as slugify,
|
package/dist/deploy.d.ts
CHANGED
|
@@ -53,6 +53,13 @@ export interface DeployOptions {
|
|
|
53
53
|
teamSlug?: string | undefined;
|
|
54
54
|
rootDirectory?: string | null;
|
|
55
55
|
production?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Deploy to this named environment (its slug) instead of preview. Not with
|
|
58
|
+
* `production` (the control plane refuses both), and not `production` or
|
|
59
|
+
* `preview` themselves: those are `production: true` and the default, and
|
|
60
|
+
* are refused with VALIDATION_FAILED before anything runs.
|
|
61
|
+
*/
|
|
62
|
+
environment?: string | undefined;
|
|
56
63
|
env?: Record<string, string> | undefined;
|
|
57
64
|
branch?: string | undefined;
|
|
58
65
|
/** Explicit token (--token / MCP config); wins over the stored session. */
|
|
@@ -84,6 +91,11 @@ export interface DeployResult {
|
|
|
84
91
|
status: DeploymentStatus;
|
|
85
92
|
url: string;
|
|
86
93
|
production: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* The named environment it went to, as the control plane confirmed; null
|
|
96
|
+
* for production and preview.
|
|
97
|
+
*/
|
|
98
|
+
environment?: string | null;
|
|
87
99
|
/** 끝까지 채워지지 않은 키. */
|
|
88
100
|
missingEnvKeys: string[];
|
|
89
101
|
/** 서버가 확정한 목적지 — 출력과 링크 파일이 이 값을 쓴다. */
|
package/dist/deploy.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
deployDirectory,
|
|
4
4
|
eventsObserver,
|
|
5
5
|
localDependencies,
|
|
6
|
-
parseDotenv,
|
|
6
|
+
parseDotenv2 as parseDotenv,
|
|
7
7
|
readProjectEnv,
|
|
8
8
|
slugify,
|
|
9
9
|
workingTreeNotices
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DEEFHVDZ.js";
|
|
11
11
|
import "./chunk-K3NQKI34.js";
|
|
12
12
|
export {
|
|
13
13
|
deployDirectory,
|
package/dist/env-consent.d.ts
CHANGED
|
@@ -4,13 +4,21 @@ export interface EnvConsentContext {
|
|
|
4
4
|
cwd: string;
|
|
5
5
|
prompter: Prompter;
|
|
6
6
|
output: Output;
|
|
7
|
+
/** 배포가 가는 이름 있는 환경의 slug. 없으면 production·preview 배포다. */
|
|
8
|
+
environment?: string;
|
|
7
9
|
/** 테스트 주입용. 기본값은 SDK의 readProjectEnv. */
|
|
8
10
|
readEnv?: (dir: string) => Record<string, string>;
|
|
9
11
|
}
|
|
12
|
+
/** 비밀값은 앞 네 글자까지만 — 8자 이하면 하나도 보이지 않는다. */
|
|
13
|
+
export declare function maskSecretValue(value: string): string;
|
|
10
14
|
export declare function maskEnvValue(key: string, value: string): string;
|
|
11
15
|
/** 동의를 구하기 전에는 존재만 확인한다. 내용은 열지 않는다. */
|
|
12
16
|
export declare function findDotenvFile(dir: string): string | null;
|
|
13
|
-
|
|
17
|
+
/**
|
|
18
|
+
* 값을 채우는 명령 안내. `env add KEY`만 쓰면 target이 `all`이라 production에도
|
|
19
|
+
* 닿으므로, 환경에 배포할 때는 그 환경의 slug를 붙여 그 환경에만 저장하게 한다.
|
|
20
|
+
*/
|
|
21
|
+
export declare function envAddGuidance(keys: string[], environment?: string): string[];
|
|
14
22
|
/**
|
|
15
23
|
* 누락된 env 키를 채운다. dotenv 파일은 사용자가 명시적으로 동의한 뒤에만
|
|
16
24
|
* 열리고, 그때도 요청된 키의 값만 꺼낸다 — 파일의 나머지 내용은 전송되지 않는다.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EnvVarInfo } from "@next-deploy/sdk";
|
|
2
|
+
/**
|
|
3
|
+
* The values one surface gets, one per key, the way the server's
|
|
4
|
+
* `envForSurface` picks them for a deployment: the surface's own row beats
|
|
5
|
+
* `all` whichever the list shows first, and no other surface's row applies
|
|
6
|
+
* (preview's never reaches an environment). A row's target is its surface's
|
|
7
|
+
* name: `production`, `preview` or an environment's slug.
|
|
8
|
+
*
|
|
9
|
+
* A leaf module on purpose: `naro env pull` and the library deploy (which MCP
|
|
10
|
+
* imports) both read it, and neither should pull in the other's commands.
|
|
11
|
+
*/
|
|
12
|
+
export declare function pullEnv(rows: EnvVarInfo[], surface: string): Record<string, string>;
|