neon 4.15.0 → 4.17.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.
@@ -1,14 +1,22 @@
1
1
  import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
- import { readContextFile } from "../context.js";
2
+ import { log } from "../log.js";
3
+ import { contextBranch, readContextFile } from "../context.js";
3
4
  import { getCliName } from "../utils/cli_name.js";
5
+ import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
6
+ import { CONFIG_INIT_NONE_MEANS, CONFIG_INIT_SERVICES, CONFIG_INIT_UNAVAILABLE } from "../config_template.js";
4
7
  import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
8
+ import { fetchTemplates } from "../init/bootstrap.js";
5
9
  import { initChildEnv, spawnCliChild } from "../init/child.js";
6
- import { agentSetupLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
10
+ import { agentSetupLabel, configSummaryLabel, formatInitDone, printInitBanner, printInitDone, shouldPrintInitBanner } from "../init/chrome.js";
7
11
  import { coerceAgentFlag } from "../utils/agent_flag.js";
8
- import { INIT_NEEDS_YES_OR_TERMINAL, assertNamedAgentTooling, bootstrapInitStep, directoryIsEmpty, initPluginAgents, initSkillsMcpAgents, planExistingInit, resolveNamedAgents } from "../init/plan.js";
12
+ import { INIT_NEEDS_YES_OR_TERMINAL, assertNamedAgentTooling, directoryIsEmpty, initPluginAgents, initSkillsMcpAgents, linkInputArgv, planExistingInit, resolveNamedAgents } from "../init/plan.js";
9
13
  import { canPickAgentsInteractively } from "../utils/agent_picker.js";
14
+ import { pickInitConfigInteractively, pickInitTemplateInteractively } from "../init/wizard.js";
10
15
  import { runAgentTooling, runInitSteps } from "../init/tooling.js";
11
16
  import { helpCsv, helpEpilogue } from "../utils/help_text.js";
17
+ import { handler as handler$1 } from "./bootstrap.js";
18
+ import { hasNeonConfigFile } from "./config.js";
19
+ import { INIT_CONFIG_SERVICES_CONFLICT, INIT_TEMPLATE_KEEPS_CONFIG, configPlanFromResolution, resolveInitConfigChoice, resolveInitTemplateChoice, shouldRefreshEnvAfterNewConfig } from "../init/choices.js";
12
20
  import { existsSync, readdirSync } from "node:fs";
13
21
  import { resolve } from "node:path";
14
22
  //#region src/commands/init.ts
@@ -20,13 +28,45 @@ var init_exports = /* @__PURE__ */ __exportAll({
20
28
  initChildEnv: () => initChildEnv
21
29
  });
22
30
  const command = "init";
23
- const describe = "Set up this directory for Neon: agent tooling, a linked project, and neon.ts. In an empty directory, scaffolds a template first. Skills needs Node.js 22.20 or newer.";
31
+ const describe = "Set up this directory for Neon: agent tooling, a linked project, and optionally neon.ts. In an empty directory, pick a template or skip scaffolding.";
24
32
  const removedProtocol = () => `\`${getCliName()} init --data\` was removed. Run \`${getCliName()} init\` or \`${getCliName()} init -y\`.`;
25
33
  const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file", { hidden: true }).option("yes", {
26
34
  alias: "y",
27
35
  type: "boolean",
28
36
  default: false,
29
- describe: "Empty dir: bootstrap --default. Otherwise plugin, or skills and MCP, for project folders, else the host CLI agent. Exits if none. Then link --yes and config init --services none. link --yes still asks for a project unless one is already linked"
37
+ describe: "Empty dir: scaffold the default template. --skip-template: plugin, or skills and MCP, for project folders, else the host CLI agent. Exits if none. Then link --yes and config init --services none. link --yes still asks for a project unless one is already linked"
38
+ }).option("skip-template", {
39
+ type: "boolean",
40
+ default: false,
41
+ describe: "Do not scaffold a template. Set up agents, link a project, and optionally neon.ts in this directory"
42
+ }).option("template", {
43
+ type: "string",
44
+ describe: "Template to scaffold into an empty directory. Conflicts with --skip-template"
45
+ }).option("config", {
46
+ type: "boolean",
47
+ describe: "Existing app or --skip-template: create neon.ts after linking. Use --no-config to skip. Omitted in a terminal: you will be asked. Scaffolding a template keeps that template's neon.ts"
48
+ }).option("services", servicesOption({
49
+ key: "services",
50
+ allowed: CONFIG_INIT_SERVICES,
51
+ describe: "Existing app or --skip-template: services to declare in neon.ts",
52
+ noneMeans: CONFIG_INIT_NONE_MEANS,
53
+ also: "Implies creating neon.ts. Cannot be combined with --no-config. Ignored when scaffolding a template."
54
+ })).option("org-id", {
55
+ describe: "Forwarded to link: organization ID to link to",
56
+ type: "string"
57
+ }).option("project-id", {
58
+ describe: "Forwarded to link: existing project ID to link to",
59
+ type: "string"
60
+ }).option("project-name", {
61
+ describe: "Forwarded to link: name for a new project",
62
+ type: "string"
63
+ }).option("region-id", {
64
+ describe: "Forwarded to link: region for a new project",
65
+ type: "string"
66
+ }).option("branch", {
67
+ alias: "branch-id",
68
+ describe: "Forwarded to link: branch name or ID to pin",
69
+ type: "string"
30
70
  }).option("agent", {
31
71
  alias: "a",
32
72
  type: "array",
@@ -40,7 +80,7 @@ const builder = (yargs) => yargs.usage("$0 init [options]").option("context-file
40
80
  alias: "o",
41
81
  hidden: true,
42
82
  describe: "Not supported; the commands init runs print their own output"
43
- }).example("$0 init", "Empty dir: bootstrap (scaffold, agent tooling, link). Existing app: agent tooling, link, config init").example("$0 init -y", "Same steps, using each child's defaults").example("$0 init --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "-y installs the plugin when Cursor, Claude Code, or Codex is in project folders, else the host CLI agent. Otherwise skills and MCP. If none are found, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick. Then link unless already linked. link --yes may still ask for a project.", "--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with -y.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).check((argv) => {
83
+ }).example("$0 init", "Set up this directory (template picker if empty)").example("$0 init --skip-template", "Empty dir: agents, link, and neon.ts — no template files").example("$0 init -y", "Default template, or existing-app defaults").example("$0 init --agent cursor --agent claude-code", "Skip agent selection; install the plugin for those agents").epilogue(helpEpilogue("Empty directory: pick a starter template, or skip scaffolding and only set up agents, a Neon project, and neon.ts. That skip is not on `neon bootstrap`.", "Interactive agent setup: plugin (recommended), skills and MCP separately, or skip agent setup. Never both plugin and skills+MCP.", "neon.ts is optional when you skip the template or set up an existing app. Saying no skips the services picker and does not write the file. Scaffolding a template keeps that template's neon.ts.", "-y installs the plugin when Cursor, Claude Code, or Codex is in project folders, else the host CLI agent. Otherwise skills and MCP. If none are found, it exits: pass --agent <name>, run from a supported agent, or omit -y in a terminal to pick. Then link unless already linked. link --yes may still ask for a project.", "--agent / -a is forwarded to plugins, or to skills and mcp, not both. It skips agent selection, including with -y.", helpCsv("Plugin agents", initPluginAgents()), helpCsv("Skills and MCP agents", initSkillsMcpAgents()))).check((argv) => {
44
84
  if (argv.data !== void 0) throw new Error(removedProtocol());
45
85
  if (argv.help !== true && (argv.output === "json" || argv.output === "yaml")) throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
46
86
  return true;
@@ -49,6 +89,90 @@ const isLinked = (contextFile) => {
49
89
  const projectId = readContextFile(contextFile).projectId;
50
90
  return typeof projectId === "string" && projectId.length > 0;
51
91
  };
92
+ const nestedBootstrapDirectory = (cwd) => resolve(cwd) === resolve(process.cwd()) ? "." : cwd;
93
+ const parsedInitServices = (raw) => {
94
+ const values = servicesFlagValue(raw);
95
+ if (values === void 0) return;
96
+ return parseServices(values, {
97
+ allowed: CONFIG_INIT_SERVICES,
98
+ whyUnavailable: CONFIG_INIT_UNAVAILABLE,
99
+ flag: "--services",
100
+ noneMeans: CONFIG_INIT_NONE_MEANS,
101
+ onDeprecated: (used, canonical) => log.warning(deprecatedServiceMessage(used, canonical))
102
+ });
103
+ };
104
+ const nestedBootstrapProps = (props, cwd, contextFile, template, linkExtra) => ({
105
+ apiClient: props.apiClient,
106
+ apiKey: props.apiKey,
107
+ apiHost: props.apiHost,
108
+ output: props.output,
109
+ contextFile,
110
+ directory: nestedBootstrapDirectory(cwd),
111
+ force: false,
112
+ listTemplates: false,
113
+ default: template.useDefault,
114
+ install: true,
115
+ git: true,
116
+ link: true,
117
+ printBanner: false,
118
+ skipDoneSummary: true,
119
+ linkNoConfig: true,
120
+ narrate: "human",
121
+ ...template.selected ? { selectedTemplate: template.selected } : {},
122
+ ...!template.selected && template.id !== void 0 ? { template: template.id } : {},
123
+ ...linkExtra.length > 0 ? { linkExtra } : {},
124
+ ...props.agent !== void 0 ? { agent: props.agent } : {},
125
+ ...props.configDir ? { configDir: props.configDir } : {},
126
+ ...props.profile ? { profile: props.profile } : {},
127
+ ...props.analytics === false ? { analytics: false } : {},
128
+ ...props.run ? { run: props.run } : {},
129
+ ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
130
+ ...props.detectProjectAgents ? { detectProjectAgents: props.detectProjectAgents } : {},
131
+ ...props.detectAgent ? { detectAgent: props.detectAgent } : {},
132
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
133
+ });
134
+ const projectRow = (input) => {
135
+ if (input.alreadyLinked) return "already linked";
136
+ if (input.linkedNow) return "linked";
137
+ return "not linked";
138
+ };
139
+ const configSummary = (input) => {
140
+ if (input.plan.kind === "skip") return "skipped";
141
+ if (input.existingConfig) return "existing";
142
+ return "created";
143
+ };
144
+ const printNestedBootstrapDone = (result, cwd, fallbackTitle) => {
145
+ const hasConfig = result?.hasNeonConfig === true || hasNeonConfigFile(cwd);
146
+ printInitDone(formatInitDone({
147
+ heading: "Neon setup complete.",
148
+ rows: [
149
+ {
150
+ label: "Template",
151
+ value: result?.templateTitle ?? fallbackTitle
152
+ },
153
+ {
154
+ label: "Agents",
155
+ value: agentSetupLabel(result?.agentSetup ?? "skip")
156
+ },
157
+ {
158
+ label: "Project",
159
+ value: projectRow({
160
+ alreadyLinked: false,
161
+ linkedNow: result?.linked === true
162
+ })
163
+ },
164
+ {
165
+ label: "Config",
166
+ value: configSummaryLabel(hasConfig ? "template" : "skipped")
167
+ }
168
+ ],
169
+ next: []
170
+ }));
171
+ };
172
+ const noteTemplateKeepsShippedConfig = (config, services) => {
173
+ if (config === void 0 && services === void 0) return;
174
+ log.warning(INIT_TEMPLATE_KEEPS_CONFIG);
175
+ };
52
176
  const handler = async (props) => {
53
177
  if (props.output === "json" || props.output === "yaml") throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
54
178
  const cwd = props.cwd ?? process.cwd();
@@ -67,43 +191,123 @@ const handler = async (props) => {
67
191
  contextFile,
68
192
  ...props.analytics === false ? { analytics: false } : {}
69
193
  };
70
- if (directoryIsEmpty(names)) {
71
- if (!yes && !canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
72
- await runInitSteps([bootstrapInitStep(yes, named)], {
73
- cwd,
74
- run,
75
- forward,
76
- authEnv
77
- });
194
+ const services = parsedInitServices(props.services);
195
+ if (props.config === false && services !== void 0) throw new Error(INIT_CONFIG_SERVICES_CONFLICT);
196
+ const linkExtra = linkInputArgv({
197
+ ...props.orgId ? { orgId: props.orgId } : {},
198
+ ...props.projectId ? { projectId: props.projectId } : {},
199
+ ...props.projectName ? { projectName: props.projectName } : {},
200
+ ...props.regionId ? { regionId: props.regionId } : {},
201
+ ...props.branch ? { branch: props.branch } : {}
202
+ });
203
+ const templateChoice = resolveInitTemplateChoice({
204
+ empty: directoryIsEmpty(names),
205
+ yes,
206
+ skipTemplate: props.skipTemplate === true,
207
+ template: props.template
208
+ });
209
+ if (shouldPrintInitBanner(yes)) printInitBanner();
210
+ if (templateChoice.kind === "default" || templateChoice.kind === "template") {
211
+ if (templateChoice.kind === "template" && !yes && props.runBootstrap === void 0 && !canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
212
+ const runBootstrap = props.runBootstrap ?? handler$1;
213
+ noteTemplateKeepsShippedConfig(props.config, services);
214
+ const result = await runBootstrap(nestedBootstrapProps(props, cwd, contextFile, {
215
+ useDefault: yes,
216
+ ...templateChoice.kind === "template" ? { id: templateChoice.id } : {}
217
+ }, linkExtra));
218
+ printNestedBootstrapDone(result, cwd, templateChoice.kind === "template" ? templateChoice.id : "default");
78
219
  return;
79
220
  }
80
- if (shouldPrintInitBanner(yes)) printInitBanner();
221
+ if (templateChoice.kind === "ask") {
222
+ if (props.pickTemplate === void 0 && !canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
223
+ const templates = await (props.fetchTemplates ?? fetchTemplates)();
224
+ const picked = await (props.pickTemplate ?? pickInitTemplateInteractively)(templates);
225
+ if (picked.kind === "template") {
226
+ const runBootstrap = props.runBootstrap ?? handler$1;
227
+ noteTemplateKeepsShippedConfig(props.config, services);
228
+ const result = await runBootstrap(nestedBootstrapProps(props, cwd, contextFile, {
229
+ selected: picked.template,
230
+ useDefault: false
231
+ }, linkExtra));
232
+ printNestedBootstrapDone(result, cwd, picked.template.id);
233
+ return;
234
+ }
235
+ }
81
236
  const alreadyLinked = isLinked(contextFile);
237
+ const shouldLink = !alreadyLinked || linkExtra.length > 0;
238
+ const existingConfig = hasNeonConfigFile(cwd);
239
+ const canAskConfig = props.pickConfig !== void 0 || canPickAgentsInteractively();
82
240
  const agentSetup = await runAgentTooling({
83
241
  cwd,
84
242
  yes,
85
243
  run,
86
244
  forward,
87
245
  authEnv,
246
+ narrate: "human",
247
+ command: "init",
88
248
  ...named.length > 0 ? { agents: named } : {},
89
249
  ...props.pickAgentSetup ? { pickAgentSetup: props.pickAgentSetup } : {},
90
250
  ...props.detectProjectAgents ? { detectProjectAgents: props.detectProjectAgents } : {},
91
251
  ...props.detectAgent ? { detectAgent: props.detectAgent } : {},
92
- ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {},
93
- command: "init"
252
+ ...props.hasProjectPlugins ? { hasProjectPlugins: props.hasProjectPlugins } : {}
94
253
  });
95
254
  await runInitSteps(planExistingInit({
96
- linked: alreadyLinked,
255
+ linked: !shouldLink,
97
256
  yes,
98
- agentSetup: "skip"
257
+ agentSetup: "skip",
258
+ config: { kind: "skip" },
259
+ ...linkExtra.length > 0 ? { linkExtra } : {}
99
260
  }), {
100
261
  cwd,
101
262
  run,
102
263
  forward,
103
- authEnv
264
+ authEnv,
265
+ narrate: "human"
266
+ });
267
+ const configResolution = resolveInitConfigChoice({
268
+ flag: props.config,
269
+ yes,
270
+ canAsk: canAskConfig,
271
+ existingConfig,
272
+ ...services !== void 0 ? { services } : {}
104
273
  });
274
+ const accepted = configResolution.kind === "ask" ? await (props.pickConfig ?? pickInitConfigInteractively)() : void 0;
275
+ const configPlan = configPlanFromResolution(configResolution, accepted);
276
+ if (configPlan.kind === "write") {
277
+ await runInitSteps(planExistingInit({
278
+ linked: true,
279
+ yes,
280
+ agentSetup: "skip",
281
+ config: configPlan
282
+ }), {
283
+ cwd,
284
+ run,
285
+ forward,
286
+ authEnv,
287
+ narrate: "human"
288
+ });
289
+ const context = readContextFile(contextFile);
290
+ const branch = contextBranch(context);
291
+ if (shouldRefreshEnvAfterNewConfig({
292
+ wroteNewFile: !existingConfig,
293
+ projectId: context.projectId,
294
+ branch
295
+ })) try {
296
+ await runInitSteps([["env", "pull"]], {
297
+ cwd,
298
+ run,
299
+ forward,
300
+ authEnv,
301
+ narrate: "human"
302
+ });
303
+ } catch (err) {
304
+ const message = err instanceof Error ? err.message : String(err);
305
+ log.warning(`Created neon.ts, but pulling its Neon env vars failed: %s
306
+ Run \`${getCliName()} env pull\` once resolved.`, message);
307
+ }
308
+ }
105
309
  printInitDone(formatInitDone({
106
- heading: "Configured this directory for Neon.",
310
+ heading: "Neon setup complete.",
107
311
  rows: [
108
312
  {
109
313
  label: "Agents",
@@ -111,11 +315,17 @@ const handler = async (props) => {
111
315
  },
112
316
  {
113
317
  label: "Project",
114
- value: alreadyLinked ? "already linked" : "linked"
318
+ value: projectRow({
319
+ alreadyLinked,
320
+ linkedNow: shouldLink
321
+ })
115
322
  },
116
323
  {
117
324
  label: "Config",
118
- value: "neon.ts"
325
+ value: configSummaryLabel(configSummary({
326
+ plan: configPlan,
327
+ existingConfig
328
+ }))
119
329
  }
120
330
  ],
121
331
  next: []
@@ -15,7 +15,8 @@ var link_exports = /* @__PURE__ */ __exportAll({
15
15
  builder: () => builder,
16
16
  command: () => command,
17
17
  describe: () => describe,
18
- handler: () => handler
18
+ handler: () => handler,
19
+ shouldOfferConfigInit: () => shouldOfferConfigInit
19
20
  });
20
21
  const PROJECTS_LIST_LIMIT = 100;
21
22
  const CREATE_NEW_SENTINEL = "__create_new__";
@@ -79,6 +80,11 @@ const builder = (argv) => argv.usage("$0 link [options]").options({
79
80
  describe: "Pull the linked branch's Neon env vars (DATABASE_URL, …) into a local .env after linking. On by default; use --no-env-pull to skip (e.g. when injecting env at runtime with `neon-env run` / `neon dev`). Only runs when a branch is pinned.",
80
81
  type: "boolean",
81
82
  default: true
83
+ },
84
+ config: {
85
+ describe: "Offer to create neon.ts after interactive linking. Use --no-config to skip the offer",
86
+ type: "boolean",
87
+ default: true
82
88
  }
83
89
  }).example([
84
90
  ["$0 link --project-id polished-snowflake-12345678", "Link an existing project (org is inferred). Pins the only branch; several prompt in a TTY"],
@@ -725,8 +731,9 @@ const printSummary = (_props, summary) => {
725
731
  const finalizeLink = async (props, summary) => {
726
732
  printSummary(props, summary);
727
733
  if (!summary.branch || !summary.projectId) return;
734
+ const { config: _offerConfig, ...rest } = props;
728
735
  await autoPullEnvAfterPin({
729
- ...props,
736
+ ...rest,
730
737
  projectId: summary.projectId,
731
738
  branch: summary.branch,
732
739
  envPull: props.envPull
@@ -743,16 +750,17 @@ const finalizeInteractiveLink = async (props, summary) => {
743
750
  await maybeOfferConfigInit(props, summary);
744
751
  };
745
752
  /**
746
- * Offer to set up infrastructure-as-code at the end of an interactive `link` —
747
- * the natural moment, since the project is now linked. Skipped when the project
748
- * already has a `neon.ts` (nothing to scaffold). On yes, `config init` writes the
749
- * starter `neon.ts` and installs the config packages, then env is pulled again so
750
- * the local `.env` reflects the policy — the same pull `link` runs when a project
751
- * already ships a `neon.ts`.
753
+ * Interactive `link` offers neon.ts only when the directory has none and the
754
+ * caller did not pass --no-config. Init always passes --no-config so it can ask
755
+ * once after linking.
752
756
  */
757
+ const shouldOfferConfigInit = (input) => !input.hasConfig && input.offer;
753
758
  const maybeOfferConfigInit = async (props, summary) => {
754
759
  const cwd = process.cwd();
755
- if (hasNeonConfigFile(cwd)) return;
760
+ if (!shouldOfferConfigInit({
761
+ hasConfig: hasNeonConfigFile(cwd),
762
+ offer: props.config !== false
763
+ })) return;
756
764
  const { value } = await prompts({
757
765
  onState: onPromptState,
758
766
  type: "confirm",
@@ -765,12 +773,15 @@ const maybeOfferConfigInit = async (props, summary) => {
765
773
  cwd,
766
774
  install: true
767
775
  });
768
- if (summary.branch && summary.projectId) await autoPullEnvAfterPin({
769
- ...props,
770
- projectId: summary.projectId,
771
- branch: summary.branch,
772
- envPull: props.envPull
773
- });
776
+ if (summary.branch && summary.projectId) {
777
+ const { config: _offerConfig, ...rest } = props;
778
+ await autoPullEnvAfterPin({
779
+ ...rest,
780
+ projectId: summary.projectId,
781
+ branch: summary.branch,
782
+ envPull: props.envPull
783
+ });
784
+ }
774
785
  };
775
786
  const onPromptState = (state) => {
776
787
  if (state.aborted) {
@@ -784,4 +795,4 @@ const mustString = (value, name) => {
784
795
  return value;
785
796
  };
786
797
  //#endregion
787
- export { builder, command, describe, handler, link_exports as t };
798
+ export { builder, command, describe, handler, shouldOfferConfigInit, link_exports as t };
@@ -0,0 +1,182 @@
1
+ import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
2
+ import { log } from "../log.js";
3
+ import { isNeonApiError, messageFromBody, retryOnLock } from "../api.js";
4
+ import { writer } from "../writer.js";
5
+ import { branchIdFromProps, fillSingleProject } from "../utils/enrichers.js";
6
+ //#region src/commands/triggers.ts
7
+ var triggers_exports = /* @__PURE__ */ __exportAll({
8
+ TRIGGER_FIELDS: () => TRIGGER_FIELDS,
9
+ aliases: () => aliases,
10
+ builder: () => builder,
11
+ command: () => command,
12
+ create: () => create,
13
+ deleteTrigger: () => deleteTrigger,
14
+ describe: () => describe,
15
+ get: () => get,
16
+ handler: () => handler,
17
+ list: () => list,
18
+ update: () => update
19
+ });
20
+ const TRIGGER_NOT_FOUND_MESSAGE = "function trigger not visible on branch";
21
+ /** Translate the trigger-not-found 404 into a resource-specific message; re-throw everything else. */
22
+ async function withTriggerNotFound(triggerId, branchId, run) {
23
+ try {
24
+ return await run();
25
+ } catch (err) {
26
+ if (isNeonApiError(err) && err.status === 404 && messageFromBody(err.data) === TRIGGER_NOT_FOUND_MESSAGE) throw new Error(`Trigger ${triggerId} not found on branch ${branchId}.`);
27
+ throw err;
28
+ }
29
+ }
30
+ const TRIGGER_FIELDS = [
31
+ "trigger_id",
32
+ "name",
33
+ "function_slug",
34
+ "function_path",
35
+ "schedule",
36
+ "enabled",
37
+ "inherited",
38
+ "next_run_at"
39
+ ];
40
+ const renderColumns = { schedule: (t) => t.schedule?.cron ?? "" };
41
+ const command = "triggers";
42
+ const describe = "Manage function triggers";
43
+ const aliases = ["trigger"];
44
+ const builder = (argv) => argv.usage("$0 triggers <sub-command> [options]").options({
45
+ "project-id": {
46
+ describe: "Project ID",
47
+ type: "string"
48
+ },
49
+ branch: {
50
+ describe: "Branch ID or name",
51
+ type: "string"
52
+ }
53
+ }).middleware(fillSingleProject).command("list", "List triggers on the branch", (yargs) => yargs, (args) => list(args)).command("get <id>", "Show a trigger", (yargs) => yargs.positional("id", {
54
+ describe: "Trigger ID",
55
+ type: "string"
56
+ }), (args) => get(args)).command("create", "Create a trigger that invokes a function on a cron schedule", (yargs) => yargs.options({
57
+ "function-slug": {
58
+ describe: "Slug of the function to invoke",
59
+ type: "string",
60
+ demandOption: true
61
+ },
62
+ name: {
63
+ describe: "Trigger name (unique per branch)",
64
+ type: "string",
65
+ demandOption: true
66
+ },
67
+ cron: {
68
+ describe: "Five-field UTC cron expression, e.g. '*/15 * * * *'",
69
+ type: "string",
70
+ demandOption: true
71
+ },
72
+ "function-path": {
73
+ describe: "Path the invocation is sent to (default '/')",
74
+ type: "string"
75
+ },
76
+ enabled: {
77
+ describe: "Whether the trigger runs (default true)",
78
+ type: "boolean"
79
+ }
80
+ }), (args) => create(args)).command("update <id>", "Update a trigger", (yargs) => yargs.positional("id", {
81
+ describe: "Trigger ID",
82
+ type: "string"
83
+ }).options({
84
+ "function-slug": {
85
+ describe: "Slug of the function to invoke",
86
+ type: "string"
87
+ },
88
+ name: {
89
+ describe: "Trigger name (unique per branch)",
90
+ type: "string"
91
+ },
92
+ cron: {
93
+ describe: "Five-field UTC cron expression",
94
+ type: "string"
95
+ },
96
+ "function-path": {
97
+ describe: "Path the invocation is sent to",
98
+ type: "string"
99
+ },
100
+ enabled: {
101
+ describe: "Whether the trigger runs",
102
+ type: "boolean"
103
+ }
104
+ }), (args) => update(args)).command("enable <id>", "Enable a trigger", (yargs) => yargs.positional("id", {
105
+ describe: "Trigger ID",
106
+ type: "string"
107
+ }), (args) => setEnabled(args, true)).command("disable <id>", "Disable a trigger without deleting it", (yargs) => yargs.positional("id", {
108
+ describe: "Trigger ID",
109
+ type: "string"
110
+ }), (args) => setEnabled(args, false)).command("delete <id>", "Delete a trigger", (yargs) => yargs.positional("id", {
111
+ describe: "Trigger ID",
112
+ type: "string"
113
+ }), (args) => deleteTrigger(args));
114
+ const handler = (args) => {
115
+ return args;
116
+ };
117
+ const list = async (props) => {
118
+ const branchId = await branchIdFromProps(props);
119
+ const { data } = await props.apiClient.listProjectBranchTriggers(props.projectId, branchId);
120
+ writer(props).end(data.triggers, {
121
+ fields: TRIGGER_FIELDS,
122
+ renderColumns,
123
+ emptyMessage: "No triggers found on this branch."
124
+ });
125
+ };
126
+ const get = async (props) => {
127
+ const branchId = await branchIdFromProps(props);
128
+ const { data } = await withTriggerNotFound(props.id, branchId, () => props.apiClient.getProjectBranchTrigger(props.projectId, branchId, props.id));
129
+ writer(props).end(data.trigger, {
130
+ fields: TRIGGER_FIELDS,
131
+ renderColumns
132
+ });
133
+ };
134
+ const create = async (props) => {
135
+ const branchId = await branchIdFromProps(props);
136
+ const body = {
137
+ type: "schedule",
138
+ function_slug: props["function-slug"],
139
+ name: props.name,
140
+ schedule: { cron: props.cron }
141
+ };
142
+ if (props["function-path"] !== void 0) body.function_path = props["function-path"];
143
+ if (props.enabled !== void 0) body.enabled = props.enabled;
144
+ const { data } = await retryOnLock(() => props.apiClient.createProjectBranchTrigger(props.projectId, branchId, body));
145
+ writer(props).end(data.trigger, {
146
+ fields: TRIGGER_FIELDS,
147
+ renderColumns
148
+ });
149
+ };
150
+ const update = async (props) => {
151
+ const branchId = await branchIdFromProps(props);
152
+ const body = { type: "schedule" };
153
+ if (props["function-slug"] !== void 0) body.function_slug = props["function-slug"];
154
+ if (props.name !== void 0) body.name = props.name;
155
+ if (props.cron !== void 0) body.schedule = { cron: props.cron };
156
+ if (props["function-path"] !== void 0) body.function_path = props["function-path"];
157
+ if (props.enabled !== void 0) body.enabled = props.enabled;
158
+ if (Object.keys(body).filter((k) => k !== "type").length === 0) throw new Error("No fields to update. Pass at least one of --function-slug, --name, --cron, --function-path, or --enabled.");
159
+ const { data } = await withTriggerNotFound(props.id, branchId, () => retryOnLock(() => props.apiClient.updateProjectBranchTrigger(props.projectId, branchId, props.id, body)));
160
+ writer(props).end(data.trigger, {
161
+ fields: TRIGGER_FIELDS,
162
+ renderColumns
163
+ });
164
+ };
165
+ const setEnabled = async (props, enabled) => {
166
+ const branchId = await branchIdFromProps(props);
167
+ const { data } = await withTriggerNotFound(props.id, branchId, () => retryOnLock(() => props.apiClient.updateProjectBranchTrigger(props.projectId, branchId, props.id, {
168
+ type: "schedule",
169
+ enabled
170
+ })));
171
+ writer(props).end(data.trigger, {
172
+ fields: TRIGGER_FIELDS,
173
+ renderColumns
174
+ });
175
+ };
176
+ const deleteTrigger = async (props) => {
177
+ const branchId = await branchIdFromProps(props);
178
+ await withTriggerNotFound(props.id, branchId, () => retryOnLock(() => props.apiClient.deleteProjectBranchTrigger(props.projectId, branchId, props.id)));
179
+ log.info(`Trigger ${props.id} deleted`);
180
+ };
181
+ //#endregion
182
+ export { TRIGGER_FIELDS, aliases, builder, command, create, deleteTrigger, describe, get, handler, list, triggers_exports as t, update };
package/dist/context.js CHANGED
@@ -137,6 +137,7 @@ const readContextFile = (file) => {
137
137
  };
138
138
  const enrichFromContext = (args) => {
139
139
  if (args._[0] === "link" || args._[0] === "set-context") return;
140
+ if (args._[0] === "init") return;
140
141
  if (isApiKeysCommand(args)) return;
141
142
  if (isProfileCommand(args)) return;
142
143
  if (isMcpCommand(args)) return;
package/dist/dev/env.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-DsakkOVZ.js";
1
+ import { a as resolveDevEnv, i as dropClaimableUnsupported, n as MissingBranchContextError, o as resolveNeonEnvVars, r as ServiceNotOnBranchError, t as DevEnvMismatchError } from "../_chunks/env-B75xufe_.js";
2
2
  export { DevEnvMismatchError, MissingBranchContextError, ServiceNotOnBranchError, dropClaimableUnsupported, resolveDevEnv, resolveNeonEnvVars };
@@ -1,2 +1,2 @@
1
- import { a as envKeysForSelection, c as ownedEnvServiceKeys, d as servicesForEnvKeys, i as ENV_PULL_SERVICES, l as parseEnvPullKeys, n as ENV_PULL_KEYS, o as envPullFlagValue, r as ENV_PULL_KEY_HELP, s as envServiceKeys, t as BRANCH_ENV_KEY, u as serviceForEnvKey } from "./_chunks/env_services-CAWrZTWa.js";
1
+ import { a as envKeysForSelection, c as ownedEnvServiceKeys, d as servicesForEnvKeys, i as ENV_PULL_SERVICES, l as parseEnvPullKeys, n as ENV_PULL_KEYS, o as envPullFlagValue, r as ENV_PULL_KEY_HELP, s as envServiceKeys, t as BRANCH_ENV_KEY, u as serviceForEnvKey } from "./_chunks/env_services-CbARRnq9.js";
2
2
  export { BRANCH_ENV_KEY, ENV_PULL_KEYS, ENV_PULL_KEY_HELP, ENV_PULL_SERVICES, envKeysForSelection, envPullFlagValue, envServiceKeys, ownedEnvServiceKeys, parseEnvPullKeys, serviceForEnvKey, servicesForEnvKeys };
@@ -3,7 +3,8 @@ import { spawn } from "node:child_process";
3
3
  const AUTH_CHILD = /* @__PURE__ */ new Set([
4
4
  "bootstrap",
5
5
  "link",
6
- "mcp"
6
+ "mcp",
7
+ "env"
7
8
  ]);
8
9
  const initChildEnv = (command, overlay, base = process.env) => {
9
10
  const child = { ...base };