neon 6.0.0 → 6.2.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 +5 -1
- package/dist/_chunks/{env-DRpFX-_d.js → env-CtOH9xsl.js} +2 -1
- package/dist/_chunks/{env_services-DlXECBPi.js → env-N19A7qND.js} +13 -120
- package/dist/commands/auth.js +3 -1
- package/dist/commands/bootstrap.js +19 -17
- package/dist/commands/checkout.js +76 -6
- package/dist/commands/config.js +41 -0
- package/dist/commands/dev.js +3 -3
- package/dist/commands/env.js +3 -2
- package/dist/commands/git.js +356 -0
- package/dist/commands/index.js +5 -3
- package/dist/commands/init.js +2 -4
- package/dist/commands/link.js +2 -1
- package/dist/commands/mcp.js +67 -25
- package/dist/commands/plugins.js +54 -23
- package/dist/commands/skills.js +54 -23
- package/dist/commands/snapshots.js +23 -7
- package/dist/context.js +92 -3
- package/dist/dev/env.js +1 -1
- package/dist/env_services.js +120 -1
- package/dist/init/auth.js +84 -0
- package/dist/init/cancelled.js +26 -1
- package/dist/init/chrome.js +2 -19
- package/dist/init/detect.js +1 -1
- package/dist/init/plan.js +85 -63
- package/dist/init/run.js +30 -26
- package/dist/init/tooling.js +103 -21
- package/dist/init/wizard.js +1 -1
- package/dist/mcp/plan.js +1 -1
- package/dist/plugins/plan.js +1 -1
- package/dist/skills/plan.js +1 -1
- package/dist/utils/branch_name.js +29 -0
- package/dist/utils/git.js +220 -0
- package/dist/utils/hooks.js +230 -0
- package/package.json +5 -5
- package/dist/init/child.js +0 -49
package/README.md
CHANGED
|
@@ -936,12 +936,15 @@ The plugins CLI installs every plugin it finds in the Neon plugin package. Today
|
|
|
936
936
|
|
|
937
937
|
`neon snapshots` (alias `neon snapshot`) manages **snapshots** — point-in-time backups of a branch that you can list, rename, expire, restore into a branch, or schedule automatically. Snapshots are a Beta Neon feature and were previously only available in the Console and REST API; this command group brings them to the CLI.
|
|
938
938
|
|
|
939
|
-
Every sub-command resolves the project through the standard chain (`--project-id`, then the `.neon` context file, then a single-project auto-detect). Branch-scoped sub-commands (`create`, `schedule`) default to the branch pinned in `.neon`, falling back to the project's default branch, and accept `--branch <id|name>`. The `get`, `update`, `delete`, and `restore` sub-commands take a snapshot **id or
|
|
939
|
+
Every sub-command resolves the project through the standard chain (`--project-id`, then the `.neon` context file, then a single-project auto-detect). Branch-scoped sub-commands (`create`, `schedule`) default to the branch pinned in `.neon`, falling back to the project's default branch, and accept `--branch <id|name>`. The `get`, `update`, `delete`, and `restore` sub-commands take a snapshot **id, unique name, or slug** as their positional argument. An id wins. A unique name still wins over another snapshot's matching slug, so a name you already use keeps targeting that snapshot. Slug is used when no name matches. An ambiguous name errors and asks you to use the id.
|
|
940
940
|
|
|
941
941
|
```bash
|
|
942
942
|
# Snapshot the head of the current/default branch
|
|
943
943
|
neon snapshots create --name pre-migration
|
|
944
944
|
|
|
945
|
+
# Give the snapshot a slug (unique in the project; omit to let the API generate one)
|
|
946
|
+
neon snapshots create --branch main --name "Before migration" --slug before-migration
|
|
947
|
+
|
|
945
948
|
# Snapshot a specific branch at a point in time (RFC 3339 timestamp OR LSN — mutually exclusive)
|
|
946
949
|
neon snapshots create --branch main --timestamp 2025-01-01T00:00:00Z
|
|
947
950
|
neon snapshots create --branch main --lsn 0/1F3C8A0 --expires-at 2025-12-31T23:59:59Z
|
|
@@ -949,6 +952,7 @@ neon snapshots create --branch main --lsn 0/1F3C8A0 --expires-at 2025-12-31T23:5
|
|
|
949
952
|
# List / inspect
|
|
950
953
|
neon snapshots list
|
|
951
954
|
neon snapshots get pre-migration
|
|
955
|
+
neon snapshots get before-migration
|
|
952
956
|
|
|
953
957
|
# Rename or change expiration (omit both to error; --expires-at and --clear-expiration conflict)
|
|
954
958
|
neon snapshots update snap-1234 --name nightly
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { log } from "../log.js";
|
|
2
2
|
import { getCliName } from "../utils/cli_name.js";
|
|
3
3
|
import { declaredNeonServices } from "../config_services.js";
|
|
4
|
-
import {
|
|
4
|
+
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-N19A7qND.js";
|
|
5
|
+
import { ENV_PULL_SERVICES, envKeysForSelection, serviceForEnvKey, servicesForEnvKeys } from "../env_services.js";
|
|
5
6
|
import { formatInstallCommand, resolvePackageManager } from "../utils/package_manager.js";
|
|
6
7
|
import { ErrorCode, createNeonApiFromOptions, isPlatformError, loadConfigFromFile } from "@neon/config";
|
|
7
8
|
import { credentialScopesSatisfied } from "@neon/config/v1";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { NEON_SERVICES } from "../neon_services.js";
|
|
2
1
|
import { ErrorCode, PlatformError, createNeonApiFromOptions, deriveCredentialScopes, isPlatformError, resolveConfig } from "@neon/config/v1";
|
|
3
2
|
//#region ../../internals/env-core/dist/env.js
|
|
4
3
|
/**
|
|
@@ -102,6 +101,18 @@ function requiredValue(value, description) {
|
|
|
102
101
|
if (value === null) throw new Error(`fetchEnv: missing ${description}.`);
|
|
103
102
|
return value;
|
|
104
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The {@link fetchEnv} body, with the key selection as a plain argument and no generic
|
|
106
|
+
* narrowing. Exists for callers that compute the selection at runtime — notably
|
|
107
|
+
* {@link fetchEnvReusingSecrets}, which decides which keys it still needs by checking the
|
|
108
|
+
* branch — since the public overload's `keys` is bound to a literal union those callers cannot
|
|
109
|
+
* produce without asserting.
|
|
110
|
+
*
|
|
111
|
+
* `keys === null` selects everything the policy enables.
|
|
112
|
+
*/
|
|
113
|
+
async function fetchEnvKeys(config, options, keys) {
|
|
114
|
+
return (await fetchEnvKeysState(config, options, keys)).env;
|
|
115
|
+
}
|
|
105
116
|
async function fetchEnvKeysState(config, options, keys) {
|
|
106
117
|
const api = options.api ?? createApiFromOptions(options);
|
|
107
118
|
const projectId = options.projectId;
|
|
@@ -562,122 +573,4 @@ function toEntries(env) {
|
|
|
562
573
|
return out;
|
|
563
574
|
}
|
|
564
575
|
//#endregion
|
|
565
|
-
|
|
566
|
-
const ENV_PULL_SERVICES = NEON_SERVICES;
|
|
567
|
-
const ENV_PULL_KEYS = [
|
|
568
|
-
...Object.values(NEON_ENV_VAR_KEYS.postgres),
|
|
569
|
-
NEON_ENV_VAR_KEYS.branch.name,
|
|
570
|
-
...Object.values(NEON_ENV_VAR_KEYS.auth),
|
|
571
|
-
...Object.values(NEON_ENV_VAR_KEYS.dataApi),
|
|
572
|
-
...Object.values(NEON_ENV_VAR_KEYS.storage),
|
|
573
|
-
...Object.values(NEON_ENV_VAR_KEYS.aiGateway)
|
|
574
|
-
];
|
|
575
|
-
const ENV_PULL_KEY_HELP = `${ENV_PULL_KEYS.join(", ")}, or NEON_FUNCTION_<SLUG>_BASE_URL`;
|
|
576
|
-
/** The OS-level env vars each service contributes to a pulled `.env`. */
|
|
577
|
-
const SERVICE_ENV_KEYS = {
|
|
578
|
-
postgres: Object.values(NEON_ENV_VAR_KEYS.postgres),
|
|
579
|
-
auth: Object.values(NEON_ENV_VAR_KEYS.auth),
|
|
580
|
-
"data-api": Object.values(NEON_ENV_VAR_KEYS.dataApi),
|
|
581
|
-
"object-storage": Object.values(NEON_ENV_VAR_KEYS.storage),
|
|
582
|
-
"ai-gateway": Object.values(NEON_ENV_VAR_KEYS.aiGateway),
|
|
583
|
-
functions: []
|
|
584
|
-
};
|
|
585
|
-
/**
|
|
586
|
-
* The subset of {@link SERVICE_ENV_KEYS} a pull *owns*, and so may prune from the target file
|
|
587
|
-
* when the branch no longer has it. Object storage is deliberately absent: it is emitted under
|
|
588
|
-
* the third-party `AWS_*` names, which collide with credentials a user may set by hand, so
|
|
589
|
-
* `env pull` only ever writes them. Function URL keys cannot live here because slugs are
|
|
590
|
-
* discovered at runtime.
|
|
591
|
-
*/
|
|
592
|
-
const SERVICE_OWNED_ENV_KEYS = {
|
|
593
|
-
...SERVICE_ENV_KEYS,
|
|
594
|
-
"object-storage": [],
|
|
595
|
-
functions: []
|
|
596
|
-
};
|
|
597
|
-
/**
|
|
598
|
-
* Branch identity. Not a service — every branch has a name — so a scoped pull refreshes it
|
|
599
|
-
* alongside whatever services were selected.
|
|
600
|
-
*/
|
|
601
|
-
const BRANCH_ENV_KEY = NEON_ENV_VAR_KEYS.branch.name;
|
|
602
|
-
const ENV_KEY_SERVICE = {
|
|
603
|
-
DATABASE_URL: "postgres",
|
|
604
|
-
DATABASE_URL_UNPOOLED: "postgres",
|
|
605
|
-
NEON_BRANCH: null,
|
|
606
|
-
NEON_AUTH_BASE_URL: "auth",
|
|
607
|
-
NEON_AUTH_JWKS_URL: "auth",
|
|
608
|
-
NEON_DATA_API_URL: "data-api",
|
|
609
|
-
AWS_ACCESS_KEY_ID: "object-storage",
|
|
610
|
-
AWS_SECRET_ACCESS_KEY: "object-storage",
|
|
611
|
-
AWS_ENDPOINT_URL_S3: "object-storage",
|
|
612
|
-
AWS_REGION: "object-storage",
|
|
613
|
-
NEON_AI_GATEWAY_TOKEN: "ai-gateway",
|
|
614
|
-
NEON_AI_GATEWAY_BASE_URL: "ai-gateway"
|
|
615
|
-
};
|
|
616
|
-
const serviceForEnvKey = (key) => isFunctionBaseUrlKey(key) ? "functions" : ENV_KEY_SERVICE[key];
|
|
617
|
-
/** Services that must be resolved to produce the selected env keys. */
|
|
618
|
-
const servicesForEnvKeys = (keys) => ENV_PULL_SERVICES.filter((service) => keys.some((key) => serviceForEnvKey(key) === service));
|
|
619
|
-
/** Every env var the selected services contribute, plus branch identity. */
|
|
620
|
-
const envServiceKeys = (services) => {
|
|
621
|
-
const keys = /* @__PURE__ */ new Set([BRANCH_ENV_KEY]);
|
|
622
|
-
for (const service of services) for (const key of SERVICE_ENV_KEYS[service]) keys.add(key);
|
|
623
|
-
return keys;
|
|
624
|
-
};
|
|
625
|
-
/**
|
|
626
|
-
* The env vars a pull scoped to `services` may prune. Narrower than the unscoped set on
|
|
627
|
-
* purpose: `env pull -s ai-gateway` says nothing about `DATABASE_URL`, so it must leave it
|
|
628
|
-
* alone rather than treat its absence from this pull as "the branch no longer has it".
|
|
629
|
-
* Function URL keys cannot live here because slugs are discovered at runtime.
|
|
630
|
-
*/
|
|
631
|
-
const ownedEnvServiceKeys = (services) => services.flatMap((service) => SERVICE_OWNED_ENV_KEYS[service]);
|
|
632
|
-
const envKeysForSelection = (services, envKeys) => {
|
|
633
|
-
const selected = services.length > 0 ? envServiceKeys(services) : /* @__PURE__ */ new Set();
|
|
634
|
-
for (const key of envKeys) selected.add(key);
|
|
635
|
-
if (selected.has(NEON_ENV_VAR_KEYS.storage.accessKeyId) !== selected.has(NEON_ENV_VAR_KEYS.storage.secretAccessKey)) throw new Error("AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY must be selected together: they are two halves of one newly issued object-storage credential. Add the missing key to --env, or use --service object-storage.");
|
|
636
|
-
const staticKeys = ENV_PULL_KEYS.filter((key) => selected.has(key));
|
|
637
|
-
const functionKeys = [...selected].filter(isFunctionBaseUrlKey).sort((left, right) => left.localeCompare(right));
|
|
638
|
-
return [...staticKeys, ...functionKeys];
|
|
639
|
-
};
|
|
640
|
-
/** Parse repeated or comma-separated `--env` values into canonical env-key order. */
|
|
641
|
-
const parseEnvPullKeys = (raw, flag) => {
|
|
642
|
-
const names = raw.flatMap((value) => value.split(",")).map((name) => name.trim()).filter((name) => name !== "");
|
|
643
|
-
const supported = `Supported values: ${ENV_PULL_KEY_HELP}.`;
|
|
644
|
-
if (names.length === 0) throw new Error(`${flag} needs at least one env variable. ${supported}`);
|
|
645
|
-
const unknown = names.filter((name) => !ENV_PULL_KEYS.some((key) => key === name) && !isFunctionBaseUrlKey(name));
|
|
646
|
-
if (unknown.length > 0) {
|
|
647
|
-
const displayNames = unknown.map(redactUnknownEnvValue);
|
|
648
|
-
const suggestions = [...new Set(unknown.map(suggestEnvPullKey).filter((key) => key !== null))];
|
|
649
|
-
const suggestion = suggestions.length > 0 ? ` Did you mean ${suggestions.join(" or ")}?` : "";
|
|
650
|
-
throw new Error(`Unknown env variable${unknown.length === 1 ? "" : "s"} ${displayNames.join(", ")}.${suggestion} ${supported}`);
|
|
651
|
-
}
|
|
652
|
-
const staticKeys = ENV_PULL_KEYS.filter((key) => names.includes(key));
|
|
653
|
-
const functionKeys = [...new Set(names.filter(isFunctionBaseUrlKey))].sort((left, right) => left.localeCompare(right));
|
|
654
|
-
return [...staticKeys, ...functionKeys];
|
|
655
|
-
};
|
|
656
|
-
const isKnownEnvPullKey = (key) => ENV_PULL_KEYS.some((supportedKey) => supportedKey === key) || isFunctionBaseUrlKey(key);
|
|
657
|
-
const redactUnknownEnvValue = (value) => {
|
|
658
|
-
const separator = value.indexOf("=");
|
|
659
|
-
if (separator !== -1) {
|
|
660
|
-
const key = value.slice(0, separator);
|
|
661
|
-
return isKnownEnvPullKey(key) ? `${key}=<redacted>` : "<redacted invalid value>";
|
|
662
|
-
}
|
|
663
|
-
return "<redacted invalid value>";
|
|
664
|
-
};
|
|
665
|
-
const suggestEnvPullKey = (value) => {
|
|
666
|
-
if (value.includes("=")) return null;
|
|
667
|
-
if (parseFunctionBaseUrlKey(value) !== null) return null;
|
|
668
|
-
const closest = ENV_PULL_KEYS.map((key) => [key, editDistance(value, key)]).sort((a, b) => a[1] - b[1])[0];
|
|
669
|
-
return closest && closest[1] <= 2 ? closest[0] : null;
|
|
670
|
-
};
|
|
671
|
-
const editDistance = (left, right) => {
|
|
672
|
-
let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
673
|
-
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
674
|
-
const current = [leftIndex];
|
|
675
|
-
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) current[rightIndex] = Math.min((previous[rightIndex] ?? 0) + 1, (current[rightIndex - 1] ?? 0) + 1, (previous[rightIndex - 1] ?? 0) + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1));
|
|
676
|
-
previous = current;
|
|
677
|
-
}
|
|
678
|
-
return previous[right.length] ?? right.length;
|
|
679
|
-
};
|
|
680
|
-
/** Narrow yargs' array option value without accepting any other runtime shape. */
|
|
681
|
-
const envPullFlagValue = (value) => Array.isArray(value) ? value.map(String) : void 0;
|
|
682
|
-
//#endregion
|
|
683
|
-
export { resolveBranchPolicy as C, policyEnvKeys as S, defaultStorageCredential as _, envKeysForSelection as a, isFunctionBaseUrlKey as b, ownedEnvServiceKeys as c, servicesForEnvKeys as d, NEON_ENV_VAR_KEYS as f, defaultAiGatewayCredential as g, credentialName as h, ENV_PULL_SERVICES as i, parseEnvPullKeys as l, credentialEnvKeys as m, ENV_PULL_KEYS as n, envPullFlagValue as o, createApiFromOptions as p, ENV_PULL_KEY_HELP as r, envServiceKeys as s, BRANCH_ENV_KEY as t, serviceForEnvKey as u, fetchEnvKeysState as v, toEntries as w, isLiveCredential as x, functionBaseUrlKey as y };
|
|
576
|
+
export { defaultAiGatewayCredential as a, fetchEnvKeysState as c, isLiveCredential as d, parseFunctionBaseUrlKey as f, toEntries as h, credentialName as i, functionBaseUrlKey as l, resolveBranchPolicy as m, createApiFromOptions as n, defaultStorageCredential as o, policyEnvKeys as p, credentialEnvKeys as r, fetchEnvKeys as s, NEON_ENV_VAR_KEYS as t, isFunctionBaseUrlKey as u };
|
package/dist/commands/auth.js
CHANGED
|
@@ -5,7 +5,7 @@ import { log } from "../log.js";
|
|
|
5
5
|
import { getApiClient, isNeonApiError } from "../api.js";
|
|
6
6
|
import { setAuthContext } from "../auth_context.js";
|
|
7
7
|
import { a as isOwnedCredentialPath } from "../_chunks/paths-DMq0Lt7a.js";
|
|
8
|
-
import { currentContextFile, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, readContextFile } from "../context.js";
|
|
8
|
+
import { currentContextFile, isAskCommand, isClaimCommand, isConfigInit, isCurrentBranchProbe, isGitLocalCommand, isInspectDbUrl, isMcpCommand, isMcpOauth, isPluginsCommand, isProfileCommand, isSkillsCommand, isUnfollowedGitHookSync, readContextFile } from "../context.js";
|
|
9
9
|
import { _ as upsertProfile, d as newProfileLocation, g as selectProfileName, h as resolveProfile, i as assertValidProfileName, l as locationForName, m as readProfiles, n as KEYRING_CREDENTIALS, p as profilesUsingPath, r as assertProfilesUsable, s as isKeyringPointer, t as DEFAULT_PROFILE, u as locationOf } from "../_chunks/profiles-CvnFEQyd.js";
|
|
10
10
|
import { extendTokenSet } from "../utils/auth.js";
|
|
11
11
|
import { AuthRefreshError, auth, classifyRefreshFailure, refreshToken } from "../auth.js";
|
|
@@ -297,6 +297,8 @@ const ensureAuth = async (props) => {
|
|
|
297
297
|
if (isCurrentBranchProbe(props)) return;
|
|
298
298
|
if (isConfigInit(props)) return;
|
|
299
299
|
if (isProfileCommand(props)) return;
|
|
300
|
+
if (isGitLocalCommand(props)) return;
|
|
301
|
+
if (isUnfollowedGitHookSync(props)) return;
|
|
300
302
|
if (isMcpOauth(props)) return;
|
|
301
303
|
if (isInspectDbUrl(props)) return;
|
|
302
304
|
if (props._[0] === "open") return;
|
|
@@ -2,12 +2,10 @@ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
|
2
2
|
import { isCi } from "../env.js";
|
|
3
3
|
import { log } from "../log.js";
|
|
4
4
|
import { recordCommandSuccessExtras, recordScaffoldedTemplate } from "../analytics.js";
|
|
5
|
-
import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
|
|
6
5
|
import { getCliName } from "../utils/cli_name.js";
|
|
7
6
|
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
8
7
|
import { writer } from "../writer.js";
|
|
9
8
|
import { FALLBACK_TEMPLATES, ensureTargetUsable, fetchTemplates, findTemplate, scaffoldTemplate, templateIds } from "../init/bootstrap.js";
|
|
10
|
-
import { spawnCliChild } from "../init/child.js";
|
|
11
9
|
import { agentSetupDoneLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
|
|
12
10
|
import { formatInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
13
11
|
import { runAuthenticatedLink } from "../init/link.js";
|
|
@@ -271,7 +269,9 @@ const runDefaultSteps = async (props, targetDir, pm, neonConfig, template, named
|
|
|
271
269
|
};
|
|
272
270
|
const executePostScaffold = async (props, targetDir, choices) => {
|
|
273
271
|
const kids = {
|
|
274
|
-
|
|
272
|
+
output: "table",
|
|
273
|
+
auth: bootstrapAuth(props, targetDir),
|
|
274
|
+
operations: props.operations,
|
|
275
275
|
...props.narrate ? { narrate: props.narrate } : {}
|
|
276
276
|
};
|
|
277
277
|
let installed = false;
|
|
@@ -370,20 +370,22 @@ const hasNeonConfig = (dir) => NEON_CONFIG_FILENAMES.some((name) => existsSync(j
|
|
|
370
370
|
const logSkippedLink = (pm) => {
|
|
371
371
|
log.info(`Skipping the Neon link step: \`${getCliName()} link\` reads this project's neon.ts to pull env vars, which needs its dependencies. Run \`${formatInstallCommand(pm)}\`, then \`${getCliName()} link\`.`);
|
|
372
372
|
};
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
}
|
|
373
|
+
/**
|
|
374
|
+
* plugins/skills need no Neon auth at all; mcp resolves it itself, in-process, exactly as
|
|
375
|
+
* the standalone `neon mcp` does (see `init/auth.ts`) — this is that shared context.
|
|
376
|
+
*/
|
|
377
|
+
const bootstrapAuth = (props, targetDir) => ({
|
|
378
|
+
apiClient: props.apiClient,
|
|
379
|
+
apiKey: props.apiKey,
|
|
380
|
+
apiHost: props.apiHost,
|
|
381
|
+
contextFile: projectContextFile(targetDir, props.contextFile),
|
|
382
|
+
...props.configDir ? { configDir: props.configDir } : {},
|
|
383
|
+
...props.profile ? { profile: props.profile } : {},
|
|
384
|
+
...props.oauthHost ? { oauthHost: props.oauthHost } : {},
|
|
385
|
+
...props.clientId ? { clientId: props.clientId } : {},
|
|
386
|
+
...props.forceAuth !== void 0 ? { forceAuth: props.forceAuth } : {},
|
|
387
|
+
...props.allowUnsafeTls !== void 0 ? { allowUnsafeTls: props.allowUnsafeTls } : {}
|
|
388
|
+
});
|
|
387
389
|
/**
|
|
388
390
|
* Initialize a git repository in the scaffolded directory. Just `git init` — we
|
|
389
391
|
* deliberately don't auto-commit, both to avoid failing on a machine with no
|
|
@@ -4,9 +4,12 @@ import { log } from "../log.js";
|
|
|
4
4
|
import { isNeonApiError } from "../api.js";
|
|
5
5
|
import { applyContext, contextBranch, readContextFile } from "../context.js";
|
|
6
6
|
import { getCliName } from "../utils/cli_name.js";
|
|
7
|
+
import { loadEnvFileIntoProcess } from "../env_file.js";
|
|
7
8
|
import { createBranch, pickBranchInteractively } from "../utils/branch_picker.js";
|
|
8
9
|
import { looksLikeBranchId } from "../utils/formats.js";
|
|
9
10
|
import { fillSingleProject, listAllProjectBranches } from "../utils/enrichers.js";
|
|
11
|
+
import { buildCheckoutEvent, readGitContext } from "../utils/git.js";
|
|
12
|
+
import { buildHookBranch, loadHookConfig, resolveHookEnv, runCheckoutAfterHook, runCheckoutBeforeHook, runCreateAfterHook, runCreateBeforeHook } from "../utils/hooks.js";
|
|
10
13
|
import { autoPullEnvAfterPin } from "./env.js";
|
|
11
14
|
import { applyPolicyOnCreate, createBranchFromPolicyOnCheckout, envFlag } from "./config.js";
|
|
12
15
|
import { handler as handler$1 } from "./link.js";
|
|
@@ -66,7 +69,34 @@ const handler = async (props) => {
|
|
|
66
69
|
if (previousBranch) log.info("%s Currently on branch %s", chalk.dim("→"), chalk.cyan.bold(previousBranch));
|
|
67
70
|
if (props.create && !props.id?.trim()) throw new Error(`No branch specified. Pass a branch name with --create (e.g. \`${getCliName()} checkout dev --create\`).`);
|
|
68
71
|
const projectId = await resolveProjectId(props);
|
|
69
|
-
|
|
72
|
+
if (props.create && props.env) try {
|
|
73
|
+
loadEnvFileIntoProcess(props.env);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
log.debug("checkout: could not preload --env for hook discovery (continuing): %s", err instanceof Error ? err.message : String(err));
|
|
76
|
+
}
|
|
77
|
+
const cwd = process.cwd();
|
|
78
|
+
const git = readGitContext(cwd);
|
|
79
|
+
const event = buildCheckoutEvent(git, props.id);
|
|
80
|
+
const config = await loadHookConfig(cwd);
|
|
81
|
+
const hooks = config?.experimental?.hooks;
|
|
82
|
+
if (props.id) {
|
|
83
|
+
const renamed = await runCheckoutBeforeHook({
|
|
84
|
+
hooks,
|
|
85
|
+
event,
|
|
86
|
+
git,
|
|
87
|
+
cwd
|
|
88
|
+
});
|
|
89
|
+
if (renamed && renamed !== props.id) {
|
|
90
|
+
log.info("%s checkout.before hook mapped %s → %s", chalk.dim("→"), chalk.cyan(props.id), chalk.cyan.bold(renamed));
|
|
91
|
+
props.id = renamed;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const { branchId, branchName, created, policyApplied, policyFailure } = await resolveBranchId(props, projectId, {
|
|
95
|
+
hooks,
|
|
96
|
+
git,
|
|
97
|
+
event,
|
|
98
|
+
cwd
|
|
99
|
+
});
|
|
70
100
|
const orgId = await resolveOrgId(props, projectId);
|
|
71
101
|
applyContext(props.contextFile, {
|
|
72
102
|
projectId,
|
|
@@ -89,6 +119,39 @@ const handler = async (props) => {
|
|
|
89
119
|
branch: branchId,
|
|
90
120
|
envPull: props.envPull
|
|
91
121
|
});
|
|
122
|
+
const needsCreateAfter = created && hooks?.create?.after;
|
|
123
|
+
if (config && (hooks?.checkout?.after || needsCreateAfter)) {
|
|
124
|
+
const env = await resolveHookEnv(config, {
|
|
125
|
+
projectId,
|
|
126
|
+
branch: branchId,
|
|
127
|
+
...props.apiKey ? { apiKey: props.apiKey } : {},
|
|
128
|
+
...props.apiHost ? { apiHost: props.apiHost } : {}
|
|
129
|
+
});
|
|
130
|
+
if (env) {
|
|
131
|
+
const branch = await buildHookBranch({
|
|
132
|
+
apiClient: props.apiClient,
|
|
133
|
+
projectId,
|
|
134
|
+
branchId,
|
|
135
|
+
created
|
|
136
|
+
});
|
|
137
|
+
if (hooks?.checkout?.after) await runCheckoutAfterHook({
|
|
138
|
+
hooks,
|
|
139
|
+
branch,
|
|
140
|
+
env,
|
|
141
|
+
git,
|
|
142
|
+
event,
|
|
143
|
+
cwd
|
|
144
|
+
});
|
|
145
|
+
if (needsCreateAfter) await runCreateAfterHook({
|
|
146
|
+
hooks,
|
|
147
|
+
branch,
|
|
148
|
+
env,
|
|
149
|
+
git,
|
|
150
|
+
event,
|
|
151
|
+
cwd
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
92
155
|
if (failure) throw new Error(formatCheckoutPolicyFailure({
|
|
93
156
|
branchName,
|
|
94
157
|
branchId,
|
|
@@ -109,7 +172,7 @@ const applyPolicyOrDescribeFailure = async (props) => {
|
|
|
109
172
|
return err instanceof Error ? err.message : String(err);
|
|
110
173
|
}
|
|
111
174
|
};
|
|
112
|
-
const resolveBranchId = async (props, projectId) => {
|
|
175
|
+
const resolveBranchId = async (props, projectId, hookCtx) => {
|
|
113
176
|
const branches = await listAllProjectBranches(props.apiClient, projectId);
|
|
114
177
|
if (!props.id) {
|
|
115
178
|
const picked = await pickBranchInteractively(branches, {
|
|
@@ -125,7 +188,7 @@ const resolveBranchId = async (props, projectId) => {
|
|
|
125
188
|
policyApplied: false
|
|
126
189
|
};
|
|
127
190
|
}
|
|
128
|
-
return createCheckoutBranch(props, projectId, picked.name, branches);
|
|
191
|
+
return createCheckoutBranch(props, projectId, picked.name, branches, hookCtx);
|
|
129
192
|
}
|
|
130
193
|
const ref = props.id;
|
|
131
194
|
if (looksLikeBranchId(ref)) {
|
|
@@ -145,7 +208,7 @@ const resolveBranchId = async (props, projectId) => {
|
|
|
145
208
|
created: false,
|
|
146
209
|
policyApplied: false
|
|
147
210
|
};
|
|
148
|
-
if (props.create) return createCheckoutBranch(props, projectId, ref, branches);
|
|
211
|
+
if (props.create) return createCheckoutBranch(props, projectId, ref, branches, hookCtx);
|
|
149
212
|
const missingName = notFoundMessage(ref, branches, { suggestCreate: true });
|
|
150
213
|
if (isCi() || !process.stdout.isTTY) throw new Error(missingName);
|
|
151
214
|
log.error(missingName);
|
|
@@ -156,7 +219,7 @@ const resolveBranchId = async (props, projectId) => {
|
|
|
156
219
|
initial: true
|
|
157
220
|
});
|
|
158
221
|
if (!create) throw new Error(`Aborted: branch "${ref}" was not found and not created.`);
|
|
159
|
-
return createCheckoutBranch(props, projectId, ref, branches);
|
|
222
|
+
return createCheckoutBranch(props, projectId, ref, branches, hookCtx);
|
|
160
223
|
};
|
|
161
224
|
/**
|
|
162
225
|
* Create the branch to check out. When a `neon.ts` exists, route through the policy-driven
|
|
@@ -165,7 +228,14 @@ const resolveBranchId = async (props, projectId) => {
|
|
|
165
228
|
* a bare branch off the default — the handler then applies the policy (a no-op with no
|
|
166
229
|
* `neon.ts`).
|
|
167
230
|
*/
|
|
168
|
-
const createCheckoutBranch = async (props, projectId, name, branches) => {
|
|
231
|
+
const createCheckoutBranch = async (props, projectId, name, branches, hookCtx) => {
|
|
232
|
+
await runCreateBeforeHook({
|
|
233
|
+
hooks: hookCtx.hooks,
|
|
234
|
+
branchName: name,
|
|
235
|
+
git: hookCtx.git,
|
|
236
|
+
event: hookCtx.event,
|
|
237
|
+
cwd: hookCtx.cwd
|
|
238
|
+
});
|
|
169
239
|
const fromPolicy = await createBranchFromPolicyOnCheckout({
|
|
170
240
|
projectId,
|
|
171
241
|
branchName: name,
|
package/dist/commands/config.js
CHANGED
|
@@ -16,6 +16,8 @@ import { announceTargetBranch } from "../utils/branch_notice.js";
|
|
|
16
16
|
import { renderAppliedChanges, renderBranchSettingConflicts, renderCustomDomainFollowup } from "../utils/config_diff.js";
|
|
17
17
|
import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
18
18
|
import { bundleEntry } from "../utils/esbuild.js";
|
|
19
|
+
import { readGitContext } from "../utils/git.js";
|
|
20
|
+
import { resolveHookEnv, runDeployAfterHook, runDeployBeforeHook } from "../utils/hooks.js";
|
|
19
21
|
import { pickServicesInteractively } from "../utils/service_picker.js";
|
|
20
22
|
import { zipBundle } from "../utils/zip.js";
|
|
21
23
|
import { autoPullEnvAfterPin } from "./env.js";
|
|
@@ -485,6 +487,27 @@ const applyCmd = async (props) => {
|
|
|
485
487
|
apiClient: props.apiClient,
|
|
486
488
|
projectId: props.projectId
|
|
487
489
|
});
|
|
490
|
+
const cwd = process.cwd();
|
|
491
|
+
const git = readGitContext(cwd);
|
|
492
|
+
const event = { type: "neon-deploy" };
|
|
493
|
+
const hooks = config.experimental?.hooks;
|
|
494
|
+
const hookBranch = {
|
|
495
|
+
projectId: props.projectId,
|
|
496
|
+
id: branch.branchId,
|
|
497
|
+
name: branch.branchName,
|
|
498
|
+
created: false,
|
|
499
|
+
isDefault: branch.isDefault ?? false,
|
|
500
|
+
isProtected: branch.isProtected ?? false,
|
|
501
|
+
...branch.parentId ? { parentId: branch.parentId } : {},
|
|
502
|
+
...branch.expiresAt ? { expiresAt: branch.expiresAt } : {}
|
|
503
|
+
};
|
|
504
|
+
await runDeployBeforeHook({
|
|
505
|
+
hooks,
|
|
506
|
+
branch: hookBranch,
|
|
507
|
+
git,
|
|
508
|
+
event,
|
|
509
|
+
cwd
|
|
510
|
+
});
|
|
488
511
|
let result;
|
|
489
512
|
try {
|
|
490
513
|
result = await apply(config, {
|
|
@@ -510,6 +533,24 @@ const applyCmd = async (props) => {
|
|
|
510
533
|
...props,
|
|
511
534
|
envPull: props.envPull !== false
|
|
512
535
|
});
|
|
536
|
+
if (hooks?.deploy?.after) {
|
|
537
|
+
const env = await resolveHookEnv(config, {
|
|
538
|
+
projectId: props.projectId,
|
|
539
|
+
branch: branchId,
|
|
540
|
+
...props.apiKey ? { apiKey: props.apiKey } : {},
|
|
541
|
+
...props.apiHost ? { apiHost: props.apiHost } : {},
|
|
542
|
+
...props.runtimeApi ? { api: props.runtimeApi } : {}
|
|
543
|
+
});
|
|
544
|
+
if (env) await runDeployAfterHook({
|
|
545
|
+
hooks,
|
|
546
|
+
branch: hookBranch,
|
|
547
|
+
env,
|
|
548
|
+
result,
|
|
549
|
+
git,
|
|
550
|
+
event,
|
|
551
|
+
cwd
|
|
552
|
+
});
|
|
553
|
+
}
|
|
513
554
|
};
|
|
514
555
|
/**
|
|
515
556
|
* Human-readable list of the services a `neon.ts` policy utilizes on the branch, shown under
|
package/dist/commands/dev.js
CHANGED
|
@@ -3,18 +3,18 @@ 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 {
|
|
6
|
+
import { l as functionBaseUrlKey } from "../_chunks/env-N19A7qND.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-CtOH9xsl.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";
|
|
13
13
|
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
14
14
|
import { fileURLToPath } from "node:url";
|
|
15
15
|
import chalk from "chalk";
|
|
16
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
17
16
|
import { bundleAsIs, describeNativeFinding, findUndeclaredNativePackages, resolveEsbuildEntry } from "@neon/config-runtime";
|
|
17
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
18
18
|
import { once } from "node:events";
|
|
19
19
|
import { createServer } from "node:net";
|
|
20
20
|
//#region src/commands/dev.ts
|
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 {
|
|
8
|
+
import { t as NEON_ENV_VAR_KEYS, u as isFunctionBaseUrlKey } from "../_chunks/env-N19A7qND.js";
|
|
9
|
+
import { ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, ownedEnvServiceKeys, parseEnvPullKeys } from "../env_services.js";
|
|
9
10
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
10
11
|
import { warnAiGateway } from "../utils/ai_gateway_notice.js";
|
|
11
12
|
import { announceTargetBranch } from "../utils/branch_notice.js";
|
|
12
|
-
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-
|
|
13
|
+
import { i as dropClaimableUnsupported, o as resolveNeonEnvVars } from "../_chunks/env-CtOH9xsl.js";
|
|
13
14
|
import { existsSync } from "node:fs";
|
|
14
15
|
import chalk from "chalk";
|
|
15
16
|
//#region src/commands/env.ts
|