neon 8.3.6 → 8.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/{env-L2DPzDAK.js → env-4WGRW2nw.js} +25 -8
- package/dist/_chunks/{env-XsUZj_Ay.js → env-BpP3Dqpe.js} +71 -1
- package/dist/api.js +15 -0
- package/dist/commands/claim.js +5 -4
- package/dist/commands/config.js +49 -10
- package/dist/commands/dev.js +2 -2
- package/dist/commands/env.js +8 -6
- package/dist/commands/env_output.js +1 -1
- package/dist/commands/index.js +4 -0
- package/dist/commands/init.js +2 -1
- package/dist/commands/manifest.js +25 -2
- package/dist/commands/realtime.js +50 -0
- package/dist/commands/subscribe.js +247 -0
- package/dist/config_edit.js +49 -0
- package/dist/config_services.js +4 -1
- package/dist/config_template.js +2 -1
- package/dist/dev/env.js +1 -1
- package/dist/env_services.js +5 -1
- package/dist/index.js +10 -2
- package/dist/init/run.js +2 -1
- package/dist/neon_services.js +5 -3
- package/dist/parameters.gen.js +10 -0
- package/dist/test_utils/fixtures.js +22 -3
- package/dist/utils/config_diff.js +1 -0
- package/dist/utils/hooks.js +3 -1
- package/dist/utils/repeated_flags.js +43 -0
- package/dist/utils/staging.js +49 -0
- package/package.json +8 -5
|
@@ -2,10 +2,11 @@ import { log } from "../log.js";
|
|
|
2
2
|
import { getCliName } from "../utils/cli_name.js";
|
|
3
3
|
import { declaredNeonServices } from "../config_services.js";
|
|
4
4
|
import { loadProjectConfig } from "../project.js";
|
|
5
|
-
import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-
|
|
5
|
+
import { a as defaultAiGatewayCredential, c as fetchEnvKeysState, d as isLiveCredential, h as toEntries, i as credentialName, l as functionBaseUrlKey, m as resolveBranchPolicy, n as createApiFromOptions, o as defaultStorageCredential, p as policyEnvKeys, r as credentialEnvKeys, t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "./env-BpP3Dqpe.js";
|
|
6
6
|
import { ENV_PULL_SERVICES, envKeysForSelection, serviceForEnvKey, servicesForEnvKeys } from "../env_services.js";
|
|
7
|
+
import { StagingOnlyError, assertPolicyAvailable, assertRealtimeAvailable, stagingFeaturesEnabled } from "../utils/staging.js";
|
|
7
8
|
import { formatInstallCommand, resolvePackageManager } from "../utils/package_manager.js";
|
|
8
|
-
import { ErrorCode, createNeonApiFromOptions, isPlatformError } from "@neon/config";
|
|
9
|
+
import { ErrorCode, PlatformError, createNeonApiFromOptions, isPlatformError } from "@neon/config";
|
|
9
10
|
import { credentialScopesSatisfied } from "@neon/config/v1";
|
|
10
11
|
import { plan, pullConfig } from "@neon/config-runtime";
|
|
11
12
|
//#region ../../internals/env-core/dist/reuse-secrets.js
|
|
@@ -238,7 +239,8 @@ var ServiceNotOnBranchError = class extends Error {
|
|
|
238
239
|
const CLAIMABLE_UNSUPPORTED_SERVICES = /* @__PURE__ */ new Set([
|
|
239
240
|
"ai-gateway",
|
|
240
241
|
"functions",
|
|
241
|
-
"object-storage"
|
|
242
|
+
"object-storage",
|
|
243
|
+
"realtime"
|
|
242
244
|
]);
|
|
243
245
|
const CLAIMABLE_RESOLVE_ORDER = [
|
|
244
246
|
"postgres",
|
|
@@ -366,7 +368,7 @@ const resolveClaimableNeonEnvVars = async (ctx) => {
|
|
|
366
368
|
};
|
|
367
369
|
/**
|
|
368
370
|
* Resolve the branch's Neon env vars (pooled / direct `DATABASE_URL`, plus Auth /
|
|
369
|
-
* Data API when enabled) into a `{ KEY: value }` map. Shared by `neon dev` (which
|
|
371
|
+
* Data API / Realtime when enabled) into a `{ KEY: value }` map. Shared by `neon dev` (which
|
|
370
372
|
* injects them) and `neon env pull` (which writes them to a `.env` file).
|
|
371
373
|
*
|
|
372
374
|
* Tiered:
|
|
@@ -382,7 +384,7 @@ const resolveClaimableNeonEnvVars = async (ctx) => {
|
|
|
382
384
|
* function still gets `NEON_FUNCTION_*_BASE_URL`. Otherwise `fetchEnv` evaluates
|
|
383
385
|
* the policy.
|
|
384
386
|
* 2. no `neon.ts`, but a project + branch are known -> `pullConfig` reads the
|
|
385
|
-
* branch's live state (Auth / Data API enablement plus any object-storage
|
|
387
|
+
* branch's live state (Auth / Data API / Realtime enablement plus any object-storage
|
|
386
388
|
* buckets) into a config, then `fetchEnv` resolves what is actually enabled —
|
|
387
389
|
* so a branch with a bucket gets its `AWS_*` storage vars pulled with no policy.
|
|
388
390
|
* Function invocation URLs are listed live (`functionUrls: "all-live"`) because
|
|
@@ -412,7 +414,8 @@ const resolveNeonEnvVars = async (ctx) => {
|
|
|
412
414
|
const pulled = await pullConfig({
|
|
413
415
|
projectId: ctx.projectId,
|
|
414
416
|
branchId: ctx.branchId,
|
|
415
|
-
...apiOptions(ctx)
|
|
417
|
+
...apiOptions(ctx),
|
|
418
|
+
realtime: stagingFeaturesEnabled(ctx.apiHost)
|
|
416
419
|
});
|
|
417
420
|
const resolvedBranch = {
|
|
418
421
|
id: pulled.branch.id,
|
|
@@ -510,12 +513,14 @@ const resolveSelectedServices = async (ctx, directServices, envKeys) => {
|
|
|
510
513
|
const impliedServices = servicesForEnvKeys(envKeys);
|
|
511
514
|
const services = ENV_PULL_SERVICES.filter((service) => directServices.includes(service) || impliedServices.includes(service));
|
|
512
515
|
const selectedKeys = envKeysForSelection(directServices, envKeys);
|
|
516
|
+
if (services.includes("realtime")) assertRealtimeAvailable(ctx.apiHost);
|
|
513
517
|
const api = apiFor(ctx);
|
|
514
518
|
const has = (service) => services.includes(service);
|
|
515
519
|
const checkAiGateway = has("ai-gateway") && !selectedKeys.includes(NEON_ENV_VAR_KEYS.aiGateway.apiKey);
|
|
516
|
-
const [auth, dataApiEnabled, buckets, aiGatewayAvailable, functions] = await Promise.all([
|
|
520
|
+
const [auth, dataApiEnabled, realtime, buckets, aiGatewayAvailable, functions] = await Promise.all([
|
|
517
521
|
has("auth") ? api.getNeonAuth(projectId, branchId) : null,
|
|
518
522
|
has("data-api") ? readDataApiEnabled(api, projectId, branchId) : null,
|
|
523
|
+
has("realtime") ? readRealtime(api, projectId, branchId) : null,
|
|
519
524
|
has("object-storage") ? api.listBranchBuckets(projectId, branchId) : null,
|
|
520
525
|
checkAiGateway ? readAiGatewayAvailable(api, projectId, branchId) : null,
|
|
521
526
|
directServices.includes("functions") ? api.listBranchFunctions(projectId, branchId) : null
|
|
@@ -527,6 +532,7 @@ const resolveSelectedServices = async (ctx, directServices, envKeys) => {
|
|
|
527
532
|
const config = configForServices(services, branchId, {
|
|
528
533
|
authEnabled: auth !== null,
|
|
529
534
|
dataApiEnabled,
|
|
535
|
+
realtimeEnabled: realtime === null ? false : realtime.enabled || realtime.pending,
|
|
530
536
|
buckets: buckets ?? [],
|
|
531
537
|
aiGatewayAvailable: aiGatewayAvailable ?? true
|
|
532
538
|
}, {
|
|
@@ -567,6 +573,11 @@ const readAiGatewayAvailable = async (api, projectId, branchId) => {
|
|
|
567
573
|
throw error;
|
|
568
574
|
}
|
|
569
575
|
};
|
|
576
|
+
const readRealtime = async (api, projectId, branchId) => {
|
|
577
|
+
const getRealtime = api.getProjectBranchRealtime;
|
|
578
|
+
if (!getRealtime) throw new PlatformError(ErrorCode.FeatureUnavailable, "This NeonApi adapter does not implement getProjectBranchRealtime.");
|
|
579
|
+
return await getRealtime.call(api, projectId, branchId);
|
|
580
|
+
};
|
|
570
581
|
/** Neon's default database, and the one `fetchEnv` prefers when a branch has several. */
|
|
571
582
|
const NEON_DEFAULT_DATABASE = "neondb";
|
|
572
583
|
/**
|
|
@@ -580,6 +591,7 @@ const configForServices = (services, branchId, branch, selection) => {
|
|
|
580
591
|
const provisionWith = {
|
|
581
592
|
auth: `${getCliName()} neon-auth enable`,
|
|
582
593
|
"data-api": `${getCliName()} data-api create`,
|
|
594
|
+
realtime: `${getCliName()} realtime enable`,
|
|
583
595
|
"object-storage": `${getCliName()} buckets create <name>`
|
|
584
596
|
};
|
|
585
597
|
const notOnBranch = (service, what) => {
|
|
@@ -604,6 +616,10 @@ const configForServices = (services, branchId, branch, selection) => {
|
|
|
604
616
|
if (branch.dataApiEnabled === false) notOnBranch("data-api", "Data API integration");
|
|
605
617
|
config.dataApi = true;
|
|
606
618
|
}
|
|
619
|
+
if (services.includes("realtime")) {
|
|
620
|
+
if (!branch.realtimeEnabled) notOnBranch("realtime", "Realtime integration");
|
|
621
|
+
config.preview = { realtime: true };
|
|
622
|
+
}
|
|
607
623
|
if (services.includes("object-storage")) {
|
|
608
624
|
if (branch.buckets.length === 0) notOnBranch("object-storage", "object-storage buckets");
|
|
609
625
|
config.buckets = Object.fromEntries(branch.buckets.map((bucket) => [bucket.name, { access: bucket.accessLevel }]));
|
|
@@ -633,7 +649,7 @@ const resolveDevEnv = async (ctx) => {
|
|
|
633
649
|
credential
|
|
634
650
|
};
|
|
635
651
|
} catch (err) {
|
|
636
|
-
if (err instanceof DevEnvMismatchError) throw err;
|
|
652
|
+
if (err instanceof DevEnvMismatchError || err instanceof StagingOnlyError) throw err;
|
|
637
653
|
if (err instanceof MissingBranchContextError) {
|
|
638
654
|
log.debug("dev: %s; skipping env injection", err.message);
|
|
639
655
|
return {
|
|
@@ -742,6 +758,7 @@ const loadNeonConfig = async (ctx) => {
|
|
|
742
758
|
...ctx.config ? { path: ctx.config } : {},
|
|
743
759
|
...ctx.omitUnsetFunctionEnv ? { unsetFunctionEnv: "omit" } : {}
|
|
744
760
|
});
|
|
761
|
+
assertPolicyAvailable(config, ctx.apiHost);
|
|
745
762
|
return config;
|
|
746
763
|
} catch (err) {
|
|
747
764
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -20,7 +20,7 @@ import { ErrorCode, PlatformError, createNeonApiFromOptions, deriveCredentialSco
|
|
|
20
20
|
* Each top-level key here is a {@link NeonEnv} namespace; the inner record maps the
|
|
21
21
|
* camelCase property names exposed to TypeScript to the UPPER_SNAKE env-var names used
|
|
22
22
|
* by the OS. Keep this in sync with {@link postgresEnvSchema} / {@link authEnvSchema} /
|
|
23
|
-
* {@link dataApiEnvSchema}.
|
|
23
|
+
* {@link dataApiEnvSchema} / {@link realtimeEnvSchema}.
|
|
24
24
|
*/
|
|
25
25
|
/**
|
|
26
26
|
* Neon's default database, created with every project. When a branch has several databases
|
|
@@ -44,6 +44,10 @@ const NEON_ENV_VAR_KEYS = {
|
|
|
44
44
|
jwksUrl: "NEON_AUTH_JWKS_URL"
|
|
45
45
|
},
|
|
46
46
|
dataApi: { url: "NEON_DATA_API_URL" },
|
|
47
|
+
realtime: {
|
|
48
|
+
url: "NEON_REALTIME_URL",
|
|
49
|
+
secret: "NEON_REALTIME_SECRET"
|
|
50
|
+
},
|
|
47
51
|
/**
|
|
48
52
|
* Object storage (Preview). The S3 SDKs read `AWS_*` from their standard config chain, so
|
|
49
53
|
* a branch credential + `neon dev` / `env pull` makes object storage work from env alone.
|
|
@@ -101,6 +105,55 @@ function requiredValue(value, description) {
|
|
|
101
105
|
if (value === null) throw new Error(`fetchEnv: missing ${description}.`);
|
|
102
106
|
return value;
|
|
103
107
|
}
|
|
108
|
+
const REALTIME_POLL_INTERVAL_MS = 1e3;
|
|
109
|
+
/**
|
|
110
|
+
* Wait for asynchronous Realtime provisioning/rotation before reading its secret. The
|
|
111
|
+
* secret endpoint deliberately returns 404 until provisioning finishes, and during a
|
|
112
|
+
* rotation it can still expose the previous key while `pending` is true.
|
|
113
|
+
*/
|
|
114
|
+
async function resolveRealtimeEnv(args) {
|
|
115
|
+
const getRealtime = args.api.getProjectBranchRealtime;
|
|
116
|
+
if (!getRealtime) throw new PlatformError(ErrorCode.FeatureUnavailable, "fetchEnv cannot resolve Realtime: this NeonApi adapter does not implement getProjectBranchRealtime.");
|
|
117
|
+
const getSecret = args.api.getProjectBranchRealtimeSecret;
|
|
118
|
+
let resolvedEnv;
|
|
119
|
+
while (resolvedEnv === void 0) {
|
|
120
|
+
const snapshot = await getRealtime.call(args.api, args.projectId, args.branch.id);
|
|
121
|
+
if (!snapshot.pending) {
|
|
122
|
+
if (!snapshot.enabled) throw new PlatformError(ErrorCode.NotFound, [`fetchEnv: branch policy enables realtime but Realtime is disabled on branch ${args.branch.name} (${args.branch.id}).`, "Enable it via `apply(config, { projectId, branchId })` (or `npx neon deploy`) and re-run fetchEnv. Or set realtime to false or omit it."].join(" "), { details: {
|
|
123
|
+
projectId: args.projectId,
|
|
124
|
+
branchId: args.branch.id
|
|
125
|
+
} });
|
|
126
|
+
let secret;
|
|
127
|
+
if (args.wantsSecret) {
|
|
128
|
+
if (!getSecret) throw new PlatformError(ErrorCode.FeatureUnavailable, "fetchEnv cannot resolve NEON_REALTIME_SECRET: this NeonApi adapter does not implement getProjectBranchRealtimeSecret.");
|
|
129
|
+
secret = await getSecret.call(args.api, args.projectId, args.branch.id);
|
|
130
|
+
}
|
|
131
|
+
if (!secret?.pending) resolvedEnv = {
|
|
132
|
+
...args.wantsUrl ? { url: realtimeWebSocketUrl(requiredRealtimeInvocationUrl(snapshot, args.branch)) } : {},
|
|
133
|
+
...secret ? { secret: secret.secret } : {}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (resolvedEnv === void 0) await new Promise((resolve) => setTimeout(resolve, REALTIME_POLL_INTERVAL_MS));
|
|
137
|
+
}
|
|
138
|
+
return resolvedEnv;
|
|
139
|
+
}
|
|
140
|
+
function requiredRealtimeInvocationUrl(snapshot, branch) {
|
|
141
|
+
if (snapshot.invocationUrl !== void 0 && snapshot.invocationUrl !== "") return snapshot.invocationUrl;
|
|
142
|
+
throw new PlatformError(ErrorCode.ServerError, `Realtime is enabled on branch ${branch.name} (${branch.id}), but the Neon API returned no invocation URL.`, { details: { branchId: branch.id } });
|
|
143
|
+
}
|
|
144
|
+
/** Normalize the control-plane invocation URL to the WebSocket URL the client SDK needs. */
|
|
145
|
+
function realtimeWebSocketUrl(value) {
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = new URL(value);
|
|
149
|
+
} catch {
|
|
150
|
+
throw new PlatformError(ErrorCode.ServerError, `Realtime invocation URL is invalid: ${JSON.stringify(value)}.`);
|
|
151
|
+
}
|
|
152
|
+
if (parsed.protocol === "https:") parsed.protocol = "wss:";
|
|
153
|
+
else if (parsed.protocol === "http:") parsed.protocol = "ws:";
|
|
154
|
+
else if (parsed.protocol !== "wss:" && parsed.protocol !== "ws:") throw new PlatformError(ErrorCode.ServerError, `Realtime invocation URL must use http(s) or ws(s): ${JSON.stringify(value)}.`);
|
|
155
|
+
return parsed.toString();
|
|
156
|
+
}
|
|
104
157
|
/**
|
|
105
158
|
* The {@link fetchEnv} body, with the key selection as a plain argument and no generic
|
|
106
159
|
* narrowing. Exists for callers that compute the selection at runtime — notably
|
|
@@ -130,6 +183,7 @@ async function fetchEnvKeysState(config, options, keys, policy) {
|
|
|
130
183
|
const wantsUnpooled = wants(K.postgres.databaseUrlUnpooled);
|
|
131
184
|
const wantsAuth = desired.authEnabled && (wants(K.auth.baseUrl) || wants(K.auth.jwksUrl));
|
|
132
185
|
const wantsDataApi = desired.dataApiEnabled && wants(K.dataApi.url);
|
|
186
|
+
const wantsRealtime = desired.realtimePolicy === "enabled" && (wants(K.realtime.url) || wants(K.realtime.secret));
|
|
133
187
|
const gatewayEnabled = desired.preview?.aiGatewayEnabled ?? false;
|
|
134
188
|
const functionUrlMode = options.functionUrls ?? "policy";
|
|
135
189
|
const declaredSlugs = (desired.preview?.functions ?? []).map((fn) => fn.slug);
|
|
@@ -196,6 +250,19 @@ async function fetchEnvKeysState(config, options, keys, policy) {
|
|
|
196
250
|
}
|
|
197
251
|
result.dataApi = { url: dataApiSnapshot.url };
|
|
198
252
|
}
|
|
253
|
+
if (wantsRealtime) {
|
|
254
|
+
const realtime = await resolveRealtimeEnv({
|
|
255
|
+
api,
|
|
256
|
+
projectId,
|
|
257
|
+
branch,
|
|
258
|
+
wantsUrl: wants(K.realtime.url),
|
|
259
|
+
wantsSecret: wants(K.realtime.secret)
|
|
260
|
+
});
|
|
261
|
+
const realtimeEnv = {};
|
|
262
|
+
if (wants(K.realtime.url)) realtimeEnv.url = requiredValue(realtime.url, "WebSocket URL for enabled Realtime");
|
|
263
|
+
if (wants(K.realtime.secret)) realtimeEnv.secret = requiredValue(realtime.secret, "shared secret for enabled Realtime");
|
|
264
|
+
result.realtime = realtimeEnv;
|
|
265
|
+
}
|
|
199
266
|
const wantsGateway = gatewayEnabled && (wants(K.aiGateway.apiKey) || wants(K.aiGateway.baseUrl));
|
|
200
267
|
const wantsStorageCredential = storageEnabled && (wants(K.storage.accessKeyId) || wants(K.storage.secretAccessKey));
|
|
201
268
|
const wantsGatewayCredential = gatewayEnabled && wants(K.aiGateway.apiKey);
|
|
@@ -360,6 +427,7 @@ function policyEnvKeys(desired) {
|
|
|
360
427
|
K.branch.name,
|
|
361
428
|
...desired.authEnabled ? [K.auth.baseUrl, K.auth.jwksUrl] : [],
|
|
362
429
|
...desired.dataApiEnabled ? [K.dataApi.url] : [],
|
|
430
|
+
...desired.realtimePolicy === "enabled" ? [K.realtime.url, K.realtime.secret] : [],
|
|
363
431
|
...(desired.preview?.buckets.length ?? 0) > 0 ? [
|
|
364
432
|
K.storage.accessKeyId,
|
|
365
433
|
K.storage.secretAccessKey,
|
|
@@ -572,6 +640,8 @@ function toEntries(env) {
|
|
|
572
640
|
put(K.auth.baseUrl, env.auth?.baseUrl);
|
|
573
641
|
put(K.auth.jwksUrl, env.auth?.jwksUrl);
|
|
574
642
|
put(K.dataApi.url, env.dataApi?.url);
|
|
643
|
+
put(K.realtime.url, env.realtime?.url);
|
|
644
|
+
put(K.realtime.secret, env.realtime?.secret);
|
|
575
645
|
put(K.storage.accessKeyId, env.storage?.accessKeyId);
|
|
576
646
|
put(K.storage.secretAccessKey, env.storage?.secretAccessKey);
|
|
577
647
|
put(K.storage.endpoint, env.storage?.endpoint);
|
package/dist/api.js
CHANGED
|
@@ -644,6 +644,21 @@ const getApiClient = ({ apiKey, apiHost, requestTimeoutMs = REQUEST_TIMEOUT_MS }
|
|
|
644
644
|
database_name: databaseName
|
|
645
645
|
}
|
|
646
646
|
})),
|
|
647
|
+
enableProjectBranchRealtime: (projectId, branchId, data) => call(() => raw.enableProjectBranchRealtime({
|
|
648
|
+
client,
|
|
649
|
+
path: {
|
|
650
|
+
project_id: projectId,
|
|
651
|
+
branch_id: branchId
|
|
652
|
+
},
|
|
653
|
+
body: data
|
|
654
|
+
})),
|
|
655
|
+
disableProjectBranchRealtime: (projectId, branchId) => call(() => raw.disableProjectBranchRealtime({
|
|
656
|
+
client,
|
|
657
|
+
path: {
|
|
658
|
+
project_id: projectId,
|
|
659
|
+
branch_id: branchId
|
|
660
|
+
}
|
|
661
|
+
})),
|
|
647
662
|
listProjectVpcEndpoints: (projectId) => call(() => raw.listProjectVpcEndpoints({
|
|
648
663
|
client,
|
|
649
664
|
path: { project_id: projectId }
|
package/dist/commands/claim.js
CHANGED
|
@@ -7,7 +7,7 @@ import { t as credentialInputs } from "../_chunks/auth_selection-YX5_KZgD.js";
|
|
|
7
7
|
import { ClaimableClient, ClaimableServiceError } from "../claimable/api.js";
|
|
8
8
|
import { assertionHasExpired, listClaimableCredentials, readClaimableCredentials, readLinkedClaimableCredentials, removeClaimableCredentials, writeClaimableCredentials } from "../claimable/state.js";
|
|
9
9
|
import { NEON_SERVICES, deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
|
|
10
|
-
import { claimableDataApiCreateBody,
|
|
10
|
+
import { claimableDataApiCreateBody, declaredClaimableNeonServices, isClaimableService } from "../config_services.js";
|
|
11
11
|
import { projectConfigPath } from "../project.js";
|
|
12
12
|
import { resolveEnvFilePath } from "../env_file.js";
|
|
13
13
|
import { writer } from "../writer.js";
|
|
@@ -30,6 +30,7 @@ var claim_exports = /* @__PURE__ */ __exportAll({
|
|
|
30
30
|
describe: () => describe,
|
|
31
31
|
handler: () => handler
|
|
32
32
|
});
|
|
33
|
+
const CLAIMABLE_SERVICES = NEON_SERVICES.filter(isClaimableService);
|
|
33
34
|
const CAPABILITY_FOR_SERVICE = {
|
|
34
35
|
postgres: "postgres",
|
|
35
36
|
auth: "auth",
|
|
@@ -106,7 +107,7 @@ const builder = (argv) => argv.usage("$0 claim <sub-command> [options]").option(
|
|
|
106
107
|
hidden: true
|
|
107
108
|
}).command("create", "Create a temporary Neon project without an account", (y) => y.option("service", servicesOption({
|
|
108
109
|
key: "service",
|
|
109
|
-
allowed:
|
|
110
|
+
allowed: CLAIMABLE_SERVICES,
|
|
110
111
|
describe: "Services to request for the claimable project",
|
|
111
112
|
also: "Postgres is always included. Services unavailable before claim are recorded and reported."
|
|
112
113
|
})).option("file", {
|
|
@@ -122,12 +123,12 @@ const builder = (argv) => argv.usage("$0 claim <sub-command> [options]").option(
|
|
|
122
123
|
}).strict().check(noPassthrough("claim create")), async (args) => {
|
|
123
124
|
const rawServices = servicesFlagValue(args.service);
|
|
124
125
|
const services = rawServices ? parseServices(rawServices, {
|
|
125
|
-
allowed:
|
|
126
|
+
allowed: CLAIMABLE_SERVICES,
|
|
126
127
|
flag: "--service",
|
|
127
128
|
onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical))
|
|
128
129
|
}) : [];
|
|
129
130
|
const policy = await loadCreatePolicy(typeof args.config === "string" ? args.config : void 0, typeof args.contextFile === "string" ? args.contextFile : void 0);
|
|
130
|
-
const configuredServices = policy ?
|
|
131
|
+
const configuredServices = policy ? declaredClaimableNeonServices(policy.config) : [];
|
|
131
132
|
const dataApi = policy ? claimableDataApiCreateBody(policy.config) : void 0;
|
|
132
133
|
await create({
|
|
133
134
|
...args,
|
package/dist/commands/config.js
CHANGED
|
@@ -11,6 +11,7 @@ import { toNeonConfigView } from "../config_format.js";
|
|
|
11
11
|
import { declaredNeonServices } from "../config_services.js";
|
|
12
12
|
import { loadProjectConfig, neonConfigFilename, projectConfigPath, projectDir } from "../project.js";
|
|
13
13
|
import { loadEnvFileIntoProcess } from "../env_file.js";
|
|
14
|
+
import { assertPolicyAvailable, assertRealtimeAvailable, listedServices, stagingFeaturesEnabled, stagingFeaturesEnabledForArgv } from "../utils/staging.js";
|
|
14
15
|
import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
15
16
|
import { writer } from "../writer.js";
|
|
16
17
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
@@ -26,7 +27,7 @@ import { autoPullEnvAfterPin } from "./env.js";
|
|
|
26
27
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
27
28
|
import { dirname, join, relative, resolve } from "node:path";
|
|
28
29
|
import chalk from "chalk";
|
|
29
|
-
import { packagesToStage, previewGaWarningForConfig, resolveConfig } from "@neon/config";
|
|
30
|
+
import { defineConfig, packagesToStage, previewGaWarningForConfig, resolveConfig } from "@neon/config";
|
|
30
31
|
import { PushAbortedError, PushConflictError, apply, assertZipWithinLimits, bundleAsIs, createBranch, describeNativeFinding, enforceLimits, findUndeclaredNativePackages, inspect, isPartialBranchCreateError, plan, resolveEsbuildEntry, traceNativePackages, zipFunctionBundle } from "@neon/config-runtime";
|
|
31
32
|
//#region src/commands/config.ts
|
|
32
33
|
var config_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -158,7 +159,11 @@ const missingDependencies = (cwd) => {
|
|
|
158
159
|
* starter policy, which is what `config init` has always written.
|
|
159
160
|
*/
|
|
160
161
|
const resolveServices = async (props) => {
|
|
161
|
-
if (props.services !== void 0)
|
|
162
|
+
if (props.services !== void 0) {
|
|
163
|
+
const services = parseConfigInitServices(props.services, (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical)));
|
|
164
|
+
if (services.includes("realtime")) assertRealtimeAvailable(props.apiHost);
|
|
165
|
+
return services;
|
|
166
|
+
}
|
|
162
167
|
if (props.pickServices) return props.pickServices();
|
|
163
168
|
if (isCi() || !process.stdout.isTTY) return [];
|
|
164
169
|
return pickServicesInteractively();
|
|
@@ -264,7 +269,10 @@ const initCmd = async (props) => {
|
|
|
264
269
|
writeFileSync(join(cwd, "neon.ts"), renderNeonConfig(services));
|
|
265
270
|
if (!props.silent) {
|
|
266
271
|
if (services.length === 0) log.info("Created neon.ts with a starter policy.");
|
|
267
|
-
else
|
|
272
|
+
else {
|
|
273
|
+
const declared = services.map((service) => service === "realtime" ? "Realtime" : service);
|
|
274
|
+
log.info("Created neon.ts declaring %s.", declared.join(", "));
|
|
275
|
+
}
|
|
268
276
|
}
|
|
269
277
|
if (services.includes("functions")) scaffoldFunction(cwd);
|
|
270
278
|
}
|
|
@@ -287,6 +295,18 @@ const planAdd = (target, configPath, explicitConfig) => {
|
|
|
287
295
|
const flag = explicitConfig ? ` --config ${shellQuote(explicitConfig)}` : "";
|
|
288
296
|
const provision = `Next: \`${cli} config plan${flag}\` to preview, \`${cli} config apply${flag}\` to provision.`;
|
|
289
297
|
switch (target.kind) {
|
|
298
|
+
case "realtime": {
|
|
299
|
+
const realtime = target.allowedOrigins === void 0 ? true : { allowedOrigins: target.allowedOrigins };
|
|
300
|
+
defineConfig({ preview: { realtime } });
|
|
301
|
+
return {
|
|
302
|
+
edit: {
|
|
303
|
+
kind: "realtime",
|
|
304
|
+
enabled: true,
|
|
305
|
+
...target.allowedOrigins !== void 0 ? { allowedOrigins: target.allowedOrigins } : {}
|
|
306
|
+
},
|
|
307
|
+
next: provision
|
|
308
|
+
};
|
|
309
|
+
}
|
|
290
310
|
case "service": return {
|
|
291
311
|
edit: {
|
|
292
312
|
kind: "service",
|
|
@@ -326,7 +346,7 @@ const planAdd = (target, configPath, explicitConfig) => {
|
|
|
326
346
|
}
|
|
327
347
|
};
|
|
328
348
|
/**
|
|
329
|
-
* Declare a service, function, or bucket in the project's `neon.ts` with a text edit that
|
|
349
|
+
* Declare a service, feature, function, or bucket in the project's `neon.ts` with a text edit that
|
|
330
350
|
* leaves the rest of the file alone, creating the file (and installing the config packages)
|
|
331
351
|
* when the project has none. Local only, like `config init`.
|
|
332
352
|
*/
|
|
@@ -346,8 +366,8 @@ const addCmd = async (props) => {
|
|
|
346
366
|
const plan = planAdd(props.target, configPath, props.config);
|
|
347
367
|
const { source, changes } = editNeonConfig(existing ? readFileSync(existing, "utf8") : renderNeonConfig([]), plan.edit);
|
|
348
368
|
if (changes.length === 0) {
|
|
349
|
-
if (props.target.kind === "service") {
|
|
350
|
-
log.info("%s already
|
|
369
|
+
if (props.target.kind === "service" || props.target.kind === "realtime") {
|
|
370
|
+
log.info("%s already has the requested configuration; nothing to change.", shown(configPath));
|
|
351
371
|
return;
|
|
352
372
|
}
|
|
353
373
|
throw new Error(`${shown(configPath)} already declares ${props.target.kind} "${props.target.kind === "function" ? props.target.slug : props.target.name}". Edit it there, or pick another name.`);
|
|
@@ -436,7 +456,22 @@ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").option
|
|
|
436
456
|
}))).command("ai-gateway", "Enable the AI Gateway (aiGateway: true)", addSharedOptions, (args) => addCmd(addProps(args, {
|
|
437
457
|
kind: "service",
|
|
438
458
|
service: "ai-gateway"
|
|
439
|
-
}))).command(
|
|
459
|
+
}))).command({
|
|
460
|
+
command: "realtime",
|
|
461
|
+
describe: stagingFeaturesEnabledForArgv() ? "Enable Realtime (preview.realtime: true)" : false,
|
|
462
|
+
builder: (yargs) => addSharedOptions(yargs).option("allowed-origin", {
|
|
463
|
+
describe: "Allowed browser origin (repeatable). \"*\" allows every origin",
|
|
464
|
+
type: "array",
|
|
465
|
+
string: true
|
|
466
|
+
}),
|
|
467
|
+
handler: (args) => {
|
|
468
|
+
assertRealtimeAvailable();
|
|
469
|
+
return addCmd(addProps(args, {
|
|
470
|
+
kind: "realtime",
|
|
471
|
+
...args.allowedOrigin !== void 0 ? { allowedOrigins: args.allowedOrigin } : {}
|
|
472
|
+
}));
|
|
473
|
+
}
|
|
474
|
+
}).command("function <slug>", "Declare a Neon Function and create its handler file", (yargs) => addSharedOptions(yargs).example("$0 config add function sendemail", "Creates functions/sendemail.ts and declares it").example("$0 config add function sendemail --source src/send.ts", "Declares an existing handler").positional("slug", {
|
|
440
475
|
describe: "Function slug: 1-20 lowercase letters and digits",
|
|
441
476
|
type: "string",
|
|
442
477
|
demandOption: true
|
|
@@ -467,7 +502,7 @@ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").option
|
|
|
467
502
|
kind: "bucket",
|
|
468
503
|
name: args.name,
|
|
469
504
|
access: args.access
|
|
470
|
-
})))).command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.options({
|
|
505
|
+
})))).command("init", "Scaffold a neon.ts policy and install the Neon config packages", (yargs) => yargs.strict().options({
|
|
471
506
|
install: {
|
|
472
507
|
describe: "Install @neon/config and @neon/env if they're missing. On by default; use --no-install to just print the command.",
|
|
473
508
|
type: "boolean",
|
|
@@ -475,7 +510,7 @@ const builder = (argv) => argv.usage("$0 config <sub-command> [options]").option
|
|
|
475
510
|
},
|
|
476
511
|
services: servicesOption({
|
|
477
512
|
key: "services",
|
|
478
|
-
allowed: NEON_SERVICES,
|
|
513
|
+
allowed: listedServices(NEON_SERVICES),
|
|
479
514
|
describe: "Services the scaffolded neon.ts declares",
|
|
480
515
|
also: "postgres alone writes the starter policy; every branch has Postgres. Omitted: pick interactively on a terminal, starter policy in CI or without a TTY."
|
|
481
516
|
}),
|
|
@@ -506,7 +541,8 @@ const liveConfigView = async (opts) => {
|
|
|
506
541
|
branchId: opts.branchId,
|
|
507
542
|
...opts.apiKey ? { apiKey: opts.apiKey } : {},
|
|
508
543
|
...opts.apiHost ? { apiHost: opts.apiHost } : {},
|
|
509
|
-
...opts.runtimeApi ? { api: opts.runtimeApi } : {}
|
|
544
|
+
...opts.runtimeApi ? { api: opts.runtimeApi } : {},
|
|
545
|
+
realtime: stagingFeaturesEnabled(opts.apiHost)
|
|
510
546
|
});
|
|
511
547
|
const resolved = resolveConfig(live.config, {
|
|
512
548
|
name: live.branch.name,
|
|
@@ -533,6 +569,7 @@ const loadConfig = async (props) => {
|
|
|
533
569
|
contextFile: props.contextFile,
|
|
534
570
|
...props.config ? { path: props.config } : {}
|
|
535
571
|
});
|
|
572
|
+
assertPolicyAvailable(config, props.apiHost);
|
|
536
573
|
return config;
|
|
537
574
|
};
|
|
538
575
|
const status = async (props) => {
|
|
@@ -957,6 +994,7 @@ const applyPolicyOnCreate = async (props) => {
|
|
|
957
994
|
if (/Could not find a Neon config file/i.test(message)) return;
|
|
958
995
|
throw err;
|
|
959
996
|
}
|
|
997
|
+
assertPolicyAvailable(config, props.apiHost);
|
|
960
998
|
try {
|
|
961
999
|
warnDeprecatedPreview(config, await warningTargetForExistingBranch({
|
|
962
1000
|
projectId: props.projectId,
|
|
@@ -1041,6 +1079,7 @@ const createBranchFromPolicyOnCheckout = async (props) => {
|
|
|
1041
1079
|
if (/Could not find a Neon config file/i.test(message)) return null;
|
|
1042
1080
|
throw err;
|
|
1043
1081
|
}
|
|
1082
|
+
assertPolicyAvailable(config, props.apiHost);
|
|
1044
1083
|
await assertAiGatewayProvisionableFromCreds({
|
|
1045
1084
|
projectId: props.projectId,
|
|
1046
1085
|
...props.apiKey ? { apiKey: props.apiKey } : {},
|
package/dist/commands/dev.js
CHANGED
|
@@ -3,10 +3,10 @@ import { log } from "../log.js";
|
|
|
3
3
|
import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
4
4
|
import { getCliName } from "../utils/cli_name.js";
|
|
5
5
|
import { readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
6
|
-
import { l as functionBaseUrlKey } from "../_chunks/env-
|
|
6
|
+
import { l as functionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
|
|
7
7
|
import { branchIdResolve } from "../utils/enrichers.js";
|
|
8
8
|
import { bundleEntry } from "../utils/esbuild.js";
|
|
9
|
-
import { a as resolveDevEnv } from "../_chunks/env-
|
|
9
|
+
import { a as resolveDevEnv } from "../_chunks/env-4WGRW2nw.js";
|
|
10
10
|
import { findConfigFunctionBySource, resolveFunctionsFromConfig } from "../dev/functions.js";
|
|
11
11
|
import { resolveWatchInputs } from "../dev/inputs.js";
|
|
12
12
|
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
package/dist/commands/env.js
CHANGED
|
@@ -5,11 +5,12 @@ import { isClaimableEnvTarget } from "../claimable/state.js";
|
|
|
5
5
|
import { getCliName } from "../utils/cli_name.js";
|
|
6
6
|
import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
|
|
7
7
|
import { mergeEnvFile, readEnvFile, resolveEnvFilePath } from "../env_file.js";
|
|
8
|
-
import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-
|
|
8
|
+
import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
|
|
9
9
|
import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
|
|
10
|
+
import { listedServices } from "../utils/staging.js";
|
|
10
11
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
11
12
|
import { warnAiGateway } from "../utils/ai_gateway_notice.js";
|
|
12
|
-
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-
|
|
13
|
+
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-4WGRW2nw.js";
|
|
13
14
|
import { formatPulledEnv, pulledBranch } from "./env_output.js";
|
|
14
15
|
import { existsSync } from "node:fs";
|
|
15
16
|
import chalk from "chalk";
|
|
@@ -47,7 +48,7 @@ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
|
|
|
47
48
|
},
|
|
48
49
|
service: servicesOption({
|
|
49
50
|
key: "service",
|
|
50
|
-
allowed: ENV_PULL_SERVICES,
|
|
51
|
+
allowed: listedServices(ENV_PULL_SERVICES),
|
|
51
52
|
describe: "Pull only these services' variables",
|
|
52
53
|
also: "Overrides neon.ts, and prunes only within the services you name. Combines with --env as a union."
|
|
53
54
|
}),
|
|
@@ -73,7 +74,7 @@ const builder = (argv) => argv.usage("$0 env <sub-command> [options]").options({
|
|
|
73
74
|
"",
|
|
74
75
|
"On an unclaimed Claimable Neon project, neon.ts is still the source",
|
|
75
76
|
"of truth when it only declares Postgres, Auth, and the Data API.",
|
|
76
|
-
"A neon.ts that declares AI Gateway, Functions, or Object Storage",
|
|
77
|
+
"A neon.ts that declares Realtime, AI Gateway, Functions, or Object Storage",
|
|
77
78
|
"fails: those cannot be used until the project is claimed. Without",
|
|
78
79
|
"a neon.ts, a bare pull writes provisioned Postgres, Auth, and Data",
|
|
79
80
|
"API. Naming unsupported services with --service / --env warns",
|
|
@@ -107,8 +108,8 @@ const NEON_VAR_NAMES = Object.values(NEON_ENV_VAR_KEYS).flatMap((group) => Objec
|
|
|
107
108
|
* The Neon env vars `env pull` *owns*, so it removes any that the branch no longer has when
|
|
108
109
|
* it reconciles the local `.env` (see {@link pull}). Scoped to the unambiguously Neon-named
|
|
109
110
|
* vars — the `NEON_*` aliases plus `DATABASE_URL[_UNPOOLED]` — so switching a working
|
|
110
|
-
* directory to a project/branch without Auth / the Data API drops the now-stale
|
|
111
|
-
* `NEON_AUTH_*` / `NEON_DATA_API_*` lines instead of leaving credentials for features that
|
|
111
|
+
* directory to a project/branch without Auth / the Data API / Realtime drops the now-stale
|
|
112
|
+
* `NEON_AUTH_*` / `NEON_DATA_API_*` / `NEON_REALTIME_*` lines instead of leaving credentials for features that
|
|
112
113
|
* aren't enabled.
|
|
113
114
|
*
|
|
114
115
|
* Deliberately **excludes** the storage vars Neon projects onto third-party SDK names
|
|
@@ -120,6 +121,7 @@ const NEON_OWNED_ENV_KEYS = [
|
|
|
120
121
|
...Object.values(NEON_ENV_VAR_KEYS.postgres),
|
|
121
122
|
...Object.values(NEON_ENV_VAR_KEYS.auth),
|
|
122
123
|
...Object.values(NEON_ENV_VAR_KEYS.dataApi),
|
|
124
|
+
...Object.values(NEON_ENV_VAR_KEYS.realtime),
|
|
123
125
|
...Object.values(NEON_ENV_VAR_KEYS.aiGateway)
|
|
124
126
|
];
|
|
125
127
|
const pull = async (props, opts = {}) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NEON_SERVICES, NEON_SERVICE_LABELS } from "../neon_services.js";
|
|
2
|
-
import { u as isFunctionBaseUrlKey } from "../_chunks/env-
|
|
2
|
+
import { u as isFunctionBaseUrlKey } from "../_chunks/env-BpP3Dqpe.js";
|
|
3
3
|
import { BRANCH_ENV_KEY, ENV_PULL_KEYS, serviceForEnvKey } from "../env_services.js";
|
|
4
4
|
import { isAbsolute, relative } from "node:path";
|
|
5
5
|
import { homedir } from "node:os";
|
package/dist/commands/index.js
CHANGED
|
@@ -34,10 +34,12 @@ import { t as operations_exports } from "./operations.js";
|
|
|
34
34
|
import { t as orgs_exports } from "./orgs.js";
|
|
35
35
|
import { t as profile_exports } from "./profile.js";
|
|
36
36
|
import { t as psql_exports } from "./psql.js";
|
|
37
|
+
import { t as realtime_exports } from "./realtime.js";
|
|
37
38
|
import { t as roles_exports } from "./roles.js";
|
|
38
39
|
import { t as set_context_exports } from "./set_context.js";
|
|
39
40
|
import { t as snapshots_exports } from "./snapshots.js";
|
|
40
41
|
import { t as status_exports } from "./status.js";
|
|
42
|
+
import { t as subscribe_exports } from "./subscribe.js";
|
|
41
43
|
import { t as triggers_exports } from "./triggers.js";
|
|
42
44
|
import { t as user_exports } from "./user.js";
|
|
43
45
|
import { t as vpc_endpoints_exports } from "./vpc_endpoints.js";
|
|
@@ -75,6 +77,8 @@ var commands_default = [
|
|
|
75
77
|
ask_exports,
|
|
76
78
|
feedback_exports,
|
|
77
79
|
data_api_exports,
|
|
80
|
+
realtime_exports,
|
|
81
|
+
subscribe_exports,
|
|
78
82
|
functions_exports,
|
|
79
83
|
triggers_exports,
|
|
80
84
|
credentials_exports,
|
package/dist/commands/init.js
CHANGED
|
@@ -2,6 +2,7 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
|
2
2
|
import { getCliName } from "../utils/cli_name.js";
|
|
3
3
|
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
4
4
|
import { NEON_SERVICES, servicesOption } from "../neon_services.js";
|
|
5
|
+
import { listedServices } from "../utils/staging.js";
|
|
5
6
|
import { coerceAgentFlag } from "../utils/agent_flag.js";
|
|
6
7
|
import { initPluginAgents, initSkillsMcpAgents } from "../init/plan.js";
|
|
7
8
|
import { listSkillIds } from "../skills/catalog.js";
|
|
@@ -51,7 +52,7 @@ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file
|
|
|
51
52
|
describe: "Create neon.ts after linking. Use --no-config to skip. Omitted in Custom: you will be asked"
|
|
52
53
|
}).option("services", servicesOption({
|
|
53
54
|
key: "services",
|
|
54
|
-
allowed: NEON_SERVICES,
|
|
55
|
+
allowed: listedServices(NEON_SERVICES),
|
|
55
56
|
describe: "Services to declare in neon.ts",
|
|
56
57
|
also: "postgres alone writes the starter policy; every branch has Postgres. Implies creating neon.ts. Cannot be combined with --no-config."
|
|
57
58
|
})).option("org-id", {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getCliName } from "../utils/cli_name.js";
|
|
2
|
+
import { REALTIME_COMING_SOON } from "../utils/staging.js";
|
|
2
3
|
import { Parser } from "yargs/helpers";
|
|
3
4
|
//#region src/commands/manifest.ts
|
|
4
5
|
/**
|
|
@@ -186,6 +187,18 @@ const commandManifest = [
|
|
|
186
187
|
describe: "Manage the Neon Data API for a database",
|
|
187
188
|
load: () => import("./data_api.js")
|
|
188
189
|
},
|
|
190
|
+
{
|
|
191
|
+
command: "realtime",
|
|
192
|
+
describe: "Manage Neon Realtime for a branch",
|
|
193
|
+
load: () => import("./realtime.js"),
|
|
194
|
+
stagingOnly: REALTIME_COMING_SOON
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
command: "subscribe <query>",
|
|
198
|
+
describe: "Subscribe to a live SQL query with Neon Realtime",
|
|
199
|
+
load: () => import("./subscribe.js"),
|
|
200
|
+
stagingOnly: REALTIME_COMING_SOON
|
|
201
|
+
},
|
|
189
202
|
{
|
|
190
203
|
command: "functions",
|
|
191
204
|
aliases: ["function"],
|
|
@@ -302,7 +315,8 @@ const preloadCommand = async (cli, args) => {
|
|
|
302
315
|
* command still runs, because yargs 17 awaits a builder's promise when it runs a command; shell
|
|
303
316
|
* completion does not await it, so a mispredicted completion lists no subcommands.
|
|
304
317
|
*/
|
|
305
|
-
const lazyCommands = commandManifest.map((entry) => (
|
|
318
|
+
const lazyCommands = ({ staging }) => commandManifest.map((entry) => entry.stagingOnly && !staging ? comingSoonCommand(entry, entry.stagingOnly) : lazyCommand(entry));
|
|
319
|
+
const lazyCommand = (entry) => ({
|
|
306
320
|
command: entry.command,
|
|
307
321
|
aliases: entry.aliases,
|
|
308
322
|
describe: entry.describe,
|
|
@@ -316,6 +330,15 @@ const lazyCommands = commandManifest.map((entry) => ({
|
|
|
316
330
|
handler: async (args) => {
|
|
317
331
|
return (loaded.get(entry) ?? await entry.load()).handler?.(args);
|
|
318
332
|
}
|
|
319
|
-
})
|
|
333
|
+
});
|
|
334
|
+
const comingSoonCommand = (entry, message) => ({
|
|
335
|
+
command: `${verbOf(entry)} [args..]`,
|
|
336
|
+
aliases: entry.aliases,
|
|
337
|
+
describe: false,
|
|
338
|
+
builder: () => {
|
|
339
|
+
throw new Error(message);
|
|
340
|
+
},
|
|
341
|
+
handler: () => {}
|
|
342
|
+
});
|
|
320
343
|
//#endregion
|
|
321
344
|
export { commandManifest, lazyCommands, preloadCommand, selectCommand, selectCompletionCommand };
|