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.
- package/README.md +48 -5
- package/dist/_chunks/{env-DsakkOVZ.js → env-B75xufe_.js} +57 -64
- package/dist/_chunks/{env_services-CAWrZTWa.js → env_services-CbARRnq9.js} +66 -11
- package/dist/analytics.js +11 -9
- package/dist/api.js +79 -0
- package/dist/commands/bootstrap.js +38 -34
- package/dist/commands/credentials.js +146 -0
- package/dist/commands/dev.js +8 -10
- package/dist/commands/env.js +6 -6
- package/dist/commands/index.js +4 -0
- package/dist/commands/init.js +233 -23
- package/dist/commands/link.js +27 -16
- package/dist/commands/triggers.js +182 -0
- package/dist/context.js +1 -0
- package/dist/dev/env.js +1 -1
- package/dist/env_services.js +1 -1
- package/dist/init/child.js +2 -1
- package/dist/init/choices.js +46 -0
- package/dist/init/chrome.js +25 -2
- package/dist/init/plan.js +42 -7
- package/dist/init/template_title.js +15 -0
- package/dist/init/tooling.js +5 -1
- package/dist/init/wizard.js +58 -6
- package/package.json +5 -5
package/dist/commands/init.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
|
-
import {
|
|
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,
|
|
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,
|
|
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:
|
|
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", "
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 (
|
|
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:
|
|
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: "
|
|
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:
|
|
318
|
+
value: projectRow({
|
|
319
|
+
alreadyLinked,
|
|
320
|
+
linkedNow: shouldLink
|
|
321
|
+
})
|
|
115
322
|
},
|
|
116
323
|
{
|
|
117
324
|
label: "Config",
|
|
118
|
-
value:
|
|
325
|
+
value: configSummaryLabel(configSummary({
|
|
326
|
+
plan: configPlan,
|
|
327
|
+
existingConfig
|
|
328
|
+
}))
|
|
119
329
|
}
|
|
120
330
|
],
|
|
121
331
|
next: []
|
package/dist/commands/link.js
CHANGED
|
@@ -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
|
-
...
|
|
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
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
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 (
|
|
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)
|
|
769
|
-
...props
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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-
|
|
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 };
|
package/dist/env_services.js
CHANGED
|
@@ -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-
|
|
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 };
|
package/dist/init/child.js
CHANGED