neon 6.1.0 → 6.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/checkout.js +76 -6
- package/dist/commands/config.js +41 -0
- package/dist/commands/dev.js +2 -2
- package/dist/commands/env.js +3 -2
- package/dist/commands/git.js +356 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/link.js +2 -1
- package/dist/context.js +92 -3
- package/dist/dev/env.js +1 -1
- package/dist/env_services.js +120 -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 +6 -6
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { log } from "../log.js";
|
|
2
|
+
import { neonSafeBranchName } from "./branch_name.js";
|
|
3
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
//#region src/utils/git.ts
|
|
7
|
+
/**
|
|
8
|
+
* Env var the installed `post-checkout` hook sets before invoking `neon git sync`, so the
|
|
9
|
+
* CLI can tell a hook-triggered run from a manual `neon checkout` / `deploy`. Read by
|
|
10
|
+
* {@link buildCheckoutEvent} to construct the hook contexts' `event` field (see
|
|
11
|
+
* `CheckoutEvent` in `@neon/config`) — not carried on {@link GitContext} itself.
|
|
12
|
+
*/
|
|
13
|
+
const GIT_HOOK_ENV_FLAG = "NEON_GIT_HOOK";
|
|
14
|
+
/** Sentinel marking the `post-checkout` hook as neon-managed (safe to update/remove). */
|
|
15
|
+
const HOOK_SENTINEL = "# neon:git-sync (managed — edit `neon git` instead)";
|
|
16
|
+
const POST_CHECKOUT_HOOK = `#!/usr/bin/env sh
|
|
17
|
+
${HOOK_SENTINEL}
|
|
18
|
+
# Syncs the Neon branch to the checked-out git branch. Runs only on branch switches
|
|
19
|
+
# ($3 == 1), never blocks the checkout, and no-ops when neon isn't on PATH.
|
|
20
|
+
[ "$3" = "1" ] || exit 0
|
|
21
|
+
command -v neon >/dev/null 2>&1 || exit 0
|
|
22
|
+
${GIT_HOOK_ENV_FLAG}=1 neon git sync --quiet || true
|
|
23
|
+
`;
|
|
24
|
+
/** Run a git command, returning trimmed stdout, or `undefined` on any failure. */
|
|
25
|
+
const git = (args, cwd) => {
|
|
26
|
+
try {
|
|
27
|
+
return execFileSync("git", args, {
|
|
28
|
+
cwd,
|
|
29
|
+
encoding: "utf-8",
|
|
30
|
+
stdio: [
|
|
31
|
+
"ignore",
|
|
32
|
+
"pipe",
|
|
33
|
+
"ignore"
|
|
34
|
+
]
|
|
35
|
+
}).trim();
|
|
36
|
+
} catch {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/** Whether `cwd` is inside a git work tree. */
|
|
41
|
+
const isGitRepo = (cwd) => git(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
|
|
42
|
+
/** Absolute path to the repository root (`git rev-parse --show-toplevel`), if any. */
|
|
43
|
+
const gitRepoRoot = (cwd) => git(["rev-parse", "--show-toplevel"], cwd);
|
|
44
|
+
/** The current branch name, or `undefined` in detached-HEAD state / outside a repo. */
|
|
45
|
+
const currentGitBranch = (cwd) => {
|
|
46
|
+
const branch = git([
|
|
47
|
+
"symbolic-ref",
|
|
48
|
+
"--quiet",
|
|
49
|
+
"--short",
|
|
50
|
+
"HEAD"
|
|
51
|
+
], cwd);
|
|
52
|
+
return branch && branch.length > 0 ? branch : void 0;
|
|
53
|
+
};
|
|
54
|
+
/** All local branch names (`refs/heads`). Empty outside a repo or with no branches. */
|
|
55
|
+
const localGitBranches = (cwd) => {
|
|
56
|
+
const out = git([
|
|
57
|
+
"for-each-ref",
|
|
58
|
+
"--format=%(refname:short)",
|
|
59
|
+
"refs/heads"
|
|
60
|
+
], cwd);
|
|
61
|
+
if (!out) return [];
|
|
62
|
+
return out.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
63
|
+
};
|
|
64
|
+
/** Whether the current branch has a configured upstream (`@{u}`) to pull from. */
|
|
65
|
+
const hasUpstream = (cwd) => git([
|
|
66
|
+
"rev-parse",
|
|
67
|
+
"--abbrev-ref",
|
|
68
|
+
"--symbolic-full-name",
|
|
69
|
+
"@{u}"
|
|
70
|
+
], cwd) !== void 0;
|
|
71
|
+
/**
|
|
72
|
+
* Fast-forward the current branch from its upstream (`git pull --ff-only`). Used by
|
|
73
|
+
* `git sync` to bring local files — crucially, committed migration files — up to date with a
|
|
74
|
+
* shared branch *before* a `checkout.after` migration runs, so a branch that is ahead of your
|
|
75
|
+
* local tree doesn't leave code↔schema skewed.
|
|
76
|
+
*
|
|
77
|
+
* `--ff-only` is deliberate: it never creates a merge commit or leaves conflict markers in
|
|
78
|
+
* your tree. If the branch has diverged it fails cleanly (reported, non-fatal) instead of
|
|
79
|
+
* mutating the working tree mid-sync. No-ops (returns `no-upstream`) when there's nothing to
|
|
80
|
+
* pull from.
|
|
81
|
+
*/
|
|
82
|
+
const gitPull = (cwd) => {
|
|
83
|
+
if (!hasUpstream(cwd)) return { status: "no-upstream" };
|
|
84
|
+
try {
|
|
85
|
+
execFileSync("git", ["pull", "--ff-only"], {
|
|
86
|
+
cwd,
|
|
87
|
+
encoding: "utf-8",
|
|
88
|
+
stdio: [
|
|
89
|
+
"ignore",
|
|
90
|
+
"pipe",
|
|
91
|
+
"pipe"
|
|
92
|
+
]
|
|
93
|
+
});
|
|
94
|
+
return { status: "pulled" };
|
|
95
|
+
} catch (err) {
|
|
96
|
+
let detail;
|
|
97
|
+
if (err !== null && typeof err === "object" && "stderr" in err && typeof err.stderr === "string" && err.stderr.trim().length > 0) detail = err.stderr.trim();
|
|
98
|
+
else detail = err instanceof Error ? err.message : "unknown error";
|
|
99
|
+
return {
|
|
100
|
+
status: "failed",
|
|
101
|
+
detail
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Read the read-only git facts the hooks receive. Never throws: outside a repo (or with git
|
|
107
|
+
* uninstalled) it returns `{ available: false, … }` with the optional fields absent.
|
|
108
|
+
*
|
|
109
|
+
* @param cwd directory to inspect.
|
|
110
|
+
*/
|
|
111
|
+
const readGitContext = (cwd) => {
|
|
112
|
+
if (!isGitRepo(cwd)) return {
|
|
113
|
+
available: false,
|
|
114
|
+
isDetached: false,
|
|
115
|
+
isDirty: false
|
|
116
|
+
};
|
|
117
|
+
const branch = currentGitBranch(cwd);
|
|
118
|
+
const sha = git(["rev-parse", "HEAD"], cwd);
|
|
119
|
+
const shortSha = git([
|
|
120
|
+
"rev-parse",
|
|
121
|
+
"--short",
|
|
122
|
+
"HEAD"
|
|
123
|
+
], cwd);
|
|
124
|
+
const status = git(["status", "--porcelain"], cwd);
|
|
125
|
+
const remoteHead = git([
|
|
126
|
+
"rev-parse",
|
|
127
|
+
"--abbrev-ref",
|
|
128
|
+
"origin/HEAD"
|
|
129
|
+
], cwd);
|
|
130
|
+
const defaultBranch = remoteHead?.startsWith("origin/") ? remoteHead.slice(7) : void 0;
|
|
131
|
+
const remoteUrl = git([
|
|
132
|
+
"remote",
|
|
133
|
+
"get-url",
|
|
134
|
+
"origin"
|
|
135
|
+
], cwd);
|
|
136
|
+
const repoRoot = gitRepoRoot(cwd);
|
|
137
|
+
return {
|
|
138
|
+
available: true,
|
|
139
|
+
isDetached: branch === void 0,
|
|
140
|
+
isDirty: status !== void 0 && status.length > 0,
|
|
141
|
+
...branch ? {
|
|
142
|
+
branch,
|
|
143
|
+
neonSafeBranchName: neonSafeBranchName(branch)
|
|
144
|
+
} : {},
|
|
145
|
+
...sha ? { sha } : {},
|
|
146
|
+
...shortSha ? { shortSha } : {},
|
|
147
|
+
...defaultBranch ? { defaultBranch } : {},
|
|
148
|
+
...remoteUrl ? { remoteUrl } : {},
|
|
149
|
+
...repoRoot ? { repoRoot } : {}
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Construct the `CheckoutEvent` a checkout/create hook context receives: `"git-checkout"`
|
|
154
|
+
* when the installed `post-checkout` hook triggered this run (env flag {@link
|
|
155
|
+
* GIT_HOOK_ENV_FLAG}), carrying the git branch that triggered it — `inputName` is never set
|
|
156
|
+
* on this variant, since nothing was typed at a prompt. `"neon-checkout"` for an explicit
|
|
157
|
+
* `neon checkout` invocation, carrying whatever name/id the user passed (`undefined` for
|
|
158
|
+
* the interactive picker). `git sync` bails out before ever reaching a checkout when `git`
|
|
159
|
+
* is detached/unavailable (see `sync` in `commands/git.ts`), so a defined `git.branch` is
|
|
160
|
+
* expected whenever the env flag is set; falling back to `""` is a defensive guard against
|
|
161
|
+
* that invariant breaking, not an expected runtime path.
|
|
162
|
+
*/
|
|
163
|
+
const buildCheckoutEvent = (git, inputName) => process.env["NEON_GIT_HOOK"] === "1" ? {
|
|
164
|
+
type: "git-checkout",
|
|
165
|
+
gitBranch: git.branch ?? ""
|
|
166
|
+
} : {
|
|
167
|
+
type: "neon-checkout",
|
|
168
|
+
inputName
|
|
169
|
+
};
|
|
170
|
+
/**
|
|
171
|
+
* Path to the `post-checkout` hook for a repo. Uses `git rev-parse --git-path hooks` so it
|
|
172
|
+
* honors whatever git itself would use — `.git/hooks`, a `core.hooksPath` override (relative
|
|
173
|
+
* or absolute), worktrees, etc. — instead of re-deriving the rules by hand.
|
|
174
|
+
*/
|
|
175
|
+
const postCheckoutHookPath = (repoRoot) => {
|
|
176
|
+
const hooksDir = git([
|
|
177
|
+
"rev-parse",
|
|
178
|
+
"--git-path",
|
|
179
|
+
"hooks"
|
|
180
|
+
], repoRoot);
|
|
181
|
+
const dir = hooksDir ? isAbsolute(hooksDir) ? hooksDir : resolve(repoRoot, hooksDir) : resolve(repoRoot, ".git", "hooks");
|
|
182
|
+
return resolve(dir, "post-checkout");
|
|
183
|
+
};
|
|
184
|
+
/** Whether a `post-checkout` hook exists and is neon-managed (carries our sentinel). */
|
|
185
|
+
const isManagedHook = (hookPath) => {
|
|
186
|
+
if (!existsSync(hookPath)) return false;
|
|
187
|
+
try {
|
|
188
|
+
return readFileSync(hookPath, "utf-8").includes(HOOK_SENTINEL);
|
|
189
|
+
} catch {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
/**
|
|
194
|
+
* Install (or refresh) the neon-managed `post-checkout` hook. Refuses to clobber a
|
|
195
|
+
* pre-existing hook we don't own, returning `{ status: 'conflict' }` so the caller can warn
|
|
196
|
+
* instead of silently overwriting the user's script.
|
|
197
|
+
*/
|
|
198
|
+
const installPostCheckoutHook = (repoRoot) => {
|
|
199
|
+
const hookPath = postCheckoutHookPath(repoRoot);
|
|
200
|
+
const existed = existsSync(hookPath);
|
|
201
|
+
if (existed && !isManagedHook(hookPath)) return {
|
|
202
|
+
status: "conflict",
|
|
203
|
+
hookPath
|
|
204
|
+
};
|
|
205
|
+
mkdirSync(dirname(hookPath), { recursive: true });
|
|
206
|
+
writeFileSync(hookPath, POST_CHECKOUT_HOOK);
|
|
207
|
+
chmodSync(hookPath, 493);
|
|
208
|
+
return { status: existed ? "updated" : "installed" };
|
|
209
|
+
};
|
|
210
|
+
/** Remove the managed `post-checkout` hook. Leaves a foreign (user-authored) hook untouched. */
|
|
211
|
+
const removePostCheckoutHook = (repoRoot) => {
|
|
212
|
+
const hookPath = postCheckoutHookPath(repoRoot);
|
|
213
|
+
if (!existsSync(hookPath)) return "absent";
|
|
214
|
+
if (!isManagedHook(hookPath)) return "foreign";
|
|
215
|
+
rmSync(hookPath);
|
|
216
|
+
log.debug("Removed managed post-checkout hook at %s", hookPath);
|
|
217
|
+
return "removed";
|
|
218
|
+
};
|
|
219
|
+
//#endregion
|
|
220
|
+
export { GIT_HOOK_ENV_FLAG, buildCheckoutEvent, currentGitBranch, gitPull, gitRepoRoot, hasUpstream, installPostCheckoutHook, isGitRepo, isManagedHook, localGitBranches, postCheckoutHookPath, readGitContext, removePostCheckoutHook };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { log } from "../log.js";
|
|
2
|
+
import { s as fetchEnvKeys } from "../_chunks/env-N19A7qND.js";
|
|
3
|
+
import { loadConfigFromFile, runHook } from "@neon/config-runtime";
|
|
4
|
+
//#region src/utils/hooks.ts
|
|
5
|
+
/**
|
|
6
|
+
* Load the nearest `neon.ts` for hook discovery, or `undefined` when there is none — or when
|
|
7
|
+
* it fails to load. Deliberately best-effort: `checkout` on an *existing* branch must keep
|
|
8
|
+
* working even with a broken `neon.ts` sitting in the repo, exactly as it did before hooks
|
|
9
|
+
* existed. A load failure other than "missing" degrades to a warning rather than aborting
|
|
10
|
+
* the checkout; a genuinely broken policy still surfaces loudly the moment something that
|
|
11
|
+
* actually depends on it runs — `checkout --create`'s own `createBranchFromPolicyOnCheckout`,
|
|
12
|
+
* or `deploy`, both load the config again through their own (throwing) path. Callers read
|
|
13
|
+
* `.experimental?.hooks` off the result for the hooks block, and pass the whole thing to
|
|
14
|
+
* {@link resolveHookEnv} so `after` hooks resolve exactly the namespaces the policy declares.
|
|
15
|
+
*/
|
|
16
|
+
const loadHookConfig = async (cwd) => {
|
|
17
|
+
try {
|
|
18
|
+
const { config } = await loadConfigFromFile({ cwd });
|
|
19
|
+
return config;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
22
|
+
if (!/Could not find a Neon config file/i.test(message)) log.warning("Could not load neon.ts for lifecycle hooks (continuing without them): %s", message);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/** Stream hook (shell) output through the CLI logger. */
|
|
27
|
+
const onOutput = (chunk) => {
|
|
28
|
+
const text = chunk.replace(/\n$/, "");
|
|
29
|
+
if (text.length > 0) log.info("%s", text);
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Run the `checkout.before` hook (if any). Returns the rewritten branch name when the hook
|
|
33
|
+
* (function form) returns `{ name }`, else `undefined`. Throws propagate to abort the
|
|
34
|
+
* checkout — that's the documented `before`-hook contract.
|
|
35
|
+
*/
|
|
36
|
+
const runCheckoutBeforeHook = async (args) => {
|
|
37
|
+
const hook = args.hooks?.checkout?.before;
|
|
38
|
+
if (!hook) return void 0;
|
|
39
|
+
return (await runHook(hook, {
|
|
40
|
+
event: args.event,
|
|
41
|
+
git: args.git
|
|
42
|
+
}, {
|
|
43
|
+
cwd: args.cwd,
|
|
44
|
+
onOutput
|
|
45
|
+
}))?.name;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Run the `create.before` hook (if any). Fires only when a new branch is actually about to
|
|
49
|
+
* be created (never for `checkout` selecting an existing one). Throws propagate to abort
|
|
50
|
+
* the create — the checkout that triggered it aborts too.
|
|
51
|
+
*/
|
|
52
|
+
const runCreateBeforeHook = async (args) => {
|
|
53
|
+
const hook = args.hooks?.create?.before;
|
|
54
|
+
if (!hook) return;
|
|
55
|
+
await runHook(hook, {
|
|
56
|
+
branchName: args.branchName,
|
|
57
|
+
git: args.git,
|
|
58
|
+
event: args.event
|
|
59
|
+
}, {
|
|
60
|
+
cwd: args.cwd,
|
|
61
|
+
onOutput
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
/** Run the `deploy.before` hook (if any). Throws propagate to abort the deploy. */
|
|
65
|
+
const runDeployBeforeHook = async (args) => {
|
|
66
|
+
const hook = args.hooks?.deploy?.before;
|
|
67
|
+
if (!hook) return;
|
|
68
|
+
await runHook(hook, {
|
|
69
|
+
branch: args.branch,
|
|
70
|
+
git: args.git,
|
|
71
|
+
event: args.event
|
|
72
|
+
}, {
|
|
73
|
+
cwd: args.cwd,
|
|
74
|
+
onOutput
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Run the `checkout.after` hook (if any). Failures degrade to a warning — the branch is
|
|
79
|
+
* already checked out, so an `after` failure must not unwind the pin.
|
|
80
|
+
*/
|
|
81
|
+
const runCheckoutAfterHook = async (args) => {
|
|
82
|
+
const hook = args.hooks?.checkout?.after;
|
|
83
|
+
if (!hook) return;
|
|
84
|
+
await runAfter("checkout.after", () => runHook(hook, {
|
|
85
|
+
branch: args.branch,
|
|
86
|
+
env: args.env,
|
|
87
|
+
git: args.git,
|
|
88
|
+
event: args.event
|
|
89
|
+
}, {
|
|
90
|
+
cwd: args.cwd,
|
|
91
|
+
env: hookEnvToProcessEnv(args.env),
|
|
92
|
+
onOutput
|
|
93
|
+
}));
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Run the `create.after` hook (if any). Failures degrade to a warning — the branch already
|
|
97
|
+
* exists, so an `after` failure must not unwind the create.
|
|
98
|
+
*/
|
|
99
|
+
const runCreateAfterHook = async (args) => {
|
|
100
|
+
const hook = args.hooks?.create?.after;
|
|
101
|
+
if (!hook) return;
|
|
102
|
+
await runAfter("create.after", () => runHook(hook, {
|
|
103
|
+
branch: args.branch,
|
|
104
|
+
env: args.env,
|
|
105
|
+
git: args.git,
|
|
106
|
+
event: args.event
|
|
107
|
+
}, {
|
|
108
|
+
cwd: args.cwd,
|
|
109
|
+
env: hookEnvToProcessEnv(args.env),
|
|
110
|
+
onOutput
|
|
111
|
+
}));
|
|
112
|
+
};
|
|
113
|
+
/** Run the `deploy.after` hook (if any). Failures degrade to a warning (apply already ran). */
|
|
114
|
+
const runDeployAfterHook = async (args) => {
|
|
115
|
+
const hook = args.hooks?.deploy?.after;
|
|
116
|
+
if (!hook) return;
|
|
117
|
+
await runAfter("deploy.after", () => runHook(hook, {
|
|
118
|
+
branch: args.branch,
|
|
119
|
+
env: args.env,
|
|
120
|
+
result: args.result,
|
|
121
|
+
git: args.git,
|
|
122
|
+
event: args.event
|
|
123
|
+
}, {
|
|
124
|
+
cwd: args.cwd,
|
|
125
|
+
env: hookEnvToProcessEnv(args.env),
|
|
126
|
+
onOutput
|
|
127
|
+
}));
|
|
128
|
+
};
|
|
129
|
+
const runAfter = async (label, run) => {
|
|
130
|
+
try {
|
|
131
|
+
await run();
|
|
132
|
+
} catch (err) {
|
|
133
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
134
|
+
log.warning("The `%s` hook failed: %s\nThe branch change already succeeded; re-run the hook manually if needed.", label, message);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/**
|
|
138
|
+
* Fetch the branch's resolved Neon env (DATABASE_URL, …) for an `after` hook, shaped into
|
|
139
|
+
* {@link HookEnv}. Resolved in-memory regardless of `--env-pull`, so a migration hook always
|
|
140
|
+
* has a connection string even when no `.env` is written. Returns `undefined` on failure so
|
|
141
|
+
* the caller can skip the hook with a warning rather than crash.
|
|
142
|
+
*
|
|
143
|
+
* `config` is the same policy the checkout/deploy is already working from — it decides
|
|
144
|
+
* which namespaces `fetchEnvKeys` resolves (an empty/default `Config` would resolve
|
|
145
|
+
* postgres only, since nothing else is declared enabled).
|
|
146
|
+
*/
|
|
147
|
+
const resolveHookEnv = async (config, options) => {
|
|
148
|
+
try {
|
|
149
|
+
const resolved = await fetchEnvKeys(config, options, null);
|
|
150
|
+
return shapeHookEnv(resolved);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
+
log.warning("Could not resolve env for the after hook: %s", message);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Map `@neon-internals/env-core`'s flat, all-optional {@link ResolvedNeonEnv} into
|
|
159
|
+
* {@link HookEnv}. Pure — split out from {@link resolveHookEnv} so the shaping is testable
|
|
160
|
+
* without a live Neon API call. `postgres` always defaults to empty strings rather than
|
|
161
|
+
* being dropped: {@link HookEnv.postgres} is the one namespace every hook can rely on
|
|
162
|
+
* existing, even when this particular fetch didn't select either connection string.
|
|
163
|
+
*/
|
|
164
|
+
const shapeHookEnv = (resolved) => ({
|
|
165
|
+
postgres: {
|
|
166
|
+
databaseUrl: resolved.postgres?.databaseUrl ?? "",
|
|
167
|
+
databaseUrlUnpooled: resolved.postgres?.databaseUrlUnpooled ?? ""
|
|
168
|
+
},
|
|
169
|
+
...resolved.branch?.name ? { branch: { name: resolved.branch.name } } : {},
|
|
170
|
+
...resolved.auth?.baseUrl !== void 0 && resolved.auth.jwksUrl !== void 0 ? { auth: {
|
|
171
|
+
baseUrl: resolved.auth.baseUrl,
|
|
172
|
+
jwksUrl: resolved.auth.jwksUrl
|
|
173
|
+
} } : {},
|
|
174
|
+
...resolved.dataApi?.url !== void 0 ? { dataApi: { url: resolved.dataApi.url } } : {},
|
|
175
|
+
...resolved.storage?.accessKeyId !== void 0 && resolved.storage.secretAccessKey !== void 0 && resolved.storage.endpoint !== void 0 && resolved.storage.region !== void 0 ? { storage: {
|
|
176
|
+
accessKeyId: resolved.storage.accessKeyId,
|
|
177
|
+
secretAccessKey: resolved.storage.secretAccessKey,
|
|
178
|
+
endpoint: resolved.storage.endpoint,
|
|
179
|
+
region: resolved.storage.region
|
|
180
|
+
} } : {},
|
|
181
|
+
...resolved.aiGateway?.apiKey !== void 0 && resolved.aiGateway.baseUrl !== void 0 ? { aiGateway: {
|
|
182
|
+
apiKey: resolved.aiGateway.apiKey,
|
|
183
|
+
baseUrl: resolved.aiGateway.baseUrl
|
|
184
|
+
} } : {},
|
|
185
|
+
...resolved.functions ? { functions: Object.fromEntries(Object.entries(resolved.functions).map(([slug, fn]) => [slug, { baseUrl: fn.baseUrl }])) } : {}
|
|
186
|
+
});
|
|
187
|
+
/** Re-derive the OS-level env vars from a {@link HookEnv} for shell-hook injection. */
|
|
188
|
+
const hookEnvToProcessEnv = (env) => {
|
|
189
|
+
const out = {
|
|
190
|
+
DATABASE_URL: env.postgres.databaseUrl,
|
|
191
|
+
DATABASE_URL_UNPOOLED: env.postgres.databaseUrlUnpooled
|
|
192
|
+
};
|
|
193
|
+
if (env.branch?.name) out.NEON_BRANCH = env.branch.name;
|
|
194
|
+
if (env.auth) {
|
|
195
|
+
out.NEON_AUTH_BASE_URL = env.auth.baseUrl;
|
|
196
|
+
out.NEON_AUTH_JWKS_URL = env.auth.jwksUrl;
|
|
197
|
+
}
|
|
198
|
+
if (env.dataApi) out.NEON_DATA_API_URL = env.dataApi.url;
|
|
199
|
+
if (env.storage) {
|
|
200
|
+
out.AWS_ACCESS_KEY_ID = env.storage.accessKeyId;
|
|
201
|
+
out.AWS_SECRET_ACCESS_KEY = env.storage.secretAccessKey;
|
|
202
|
+
out.AWS_ENDPOINT_URL_S3 = env.storage.endpoint;
|
|
203
|
+
out.AWS_REGION = env.storage.region;
|
|
204
|
+
}
|
|
205
|
+
if (env.aiGateway) {
|
|
206
|
+
out.NEON_AI_GATEWAY_TOKEN = env.aiGateway.apiKey;
|
|
207
|
+
out.NEON_AI_GATEWAY_BASE_URL = env.aiGateway.baseUrl;
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Build a {@link HookBranch} from the live branch metadata. `created` is supplied by the
|
|
213
|
+
* caller (only the checkout/deploy flow knows whether this op created the branch).
|
|
214
|
+
*/
|
|
215
|
+
const buildHookBranch = async (args) => {
|
|
216
|
+
const { data } = await args.apiClient.getProjectBranch(args.projectId, args.branchId);
|
|
217
|
+
const branch = data.branch;
|
|
218
|
+
return {
|
|
219
|
+
projectId: args.projectId,
|
|
220
|
+
id: branch.id,
|
|
221
|
+
name: branch.name,
|
|
222
|
+
created: args.created,
|
|
223
|
+
isDefault: branch.default ?? false,
|
|
224
|
+
isProtected: branch.protected ?? false,
|
|
225
|
+
...branch.parent_id ? { parentId: branch.parent_id } : {},
|
|
226
|
+
...branch.expires_at ? { expiresAt: branch.expires_at } : {}
|
|
227
|
+
};
|
|
228
|
+
};
|
|
229
|
+
//#endregion
|
|
230
|
+
export { buildHookBranch, loadHookConfig, resolveHookEnv, runCheckoutAfterHook, runCheckoutBeforeHook, runCreateAfterHook, runCreateBeforeHook, runDeployAfterHook, runDeployBeforeHook, shapeHookEnv };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "6.1
|
|
3
|
+
"version": "6.2.1",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"yaml": "^2.9.0",
|
|
65
65
|
"yargs": "17.7.2",
|
|
66
66
|
"yoctocolors": "^2.1.2",
|
|
67
|
-
"@neon/config
|
|
68
|
-
"@neon/
|
|
69
|
-
"@neon/
|
|
67
|
+
"@neon/config": "1.8.1",
|
|
68
|
+
"@neon/sdk": "6.1.1",
|
|
69
|
+
"@neon/config-runtime": "1.7.1"
|
|
70
70
|
},
|
|
71
71
|
"optionalDependencies": {
|
|
72
72
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -99,9 +99,9 @@
|
|
|
99
99
|
"tsx": "4.22.3",
|
|
100
100
|
"typescript": "^5.9.0",
|
|
101
101
|
"vitest": "^3.0.9",
|
|
102
|
-
"@neon-internals/
|
|
103
|
-
"@neon-internals/env-core": "0.0.22",
|
|
102
|
+
"@neon-internals/env-core": "0.0.24",
|
|
104
103
|
"@neon/e2e-harness": "0.0.0",
|
|
104
|
+
"@neon-internals/cli-core": "0.0.0",
|
|
105
105
|
"@neon/functions": "0.11.0"
|
|
106
106
|
},
|
|
107
107
|
"publishConfig": {
|