neon 5.0.0 → 6.0.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 +29 -26
- package/dist/commands/auth.js +9 -2
- package/dist/commands/config.js +39 -18
- package/dist/commands/init.js +44 -299
- package/dist/commands/mcp.js +1 -16
- package/dist/commands/plugins.js +2 -12
- package/dist/commands/skills.js +2 -12
- package/dist/index.js +9 -1
- package/dist/init/cancelled.js +16 -0
- package/dist/init/child.js +16 -4
- package/dist/init/choices.js +10 -21
- package/dist/init/chrome.js +71 -19
- package/dist/init/copy.js +133 -0
- package/dist/init/detect.js +58 -0
- package/dist/init/funnel.js +115 -0
- package/dist/init/mode.js +30 -0
- package/dist/init/plan.js +84 -3
- package/dist/init/run.js +641 -0
- package/dist/init/tooling.js +1 -1
- package/dist/init/wizard.js +207 -48
- package/dist/mcp/wizard.js +1 -27
- package/dist/skills/wizard.js +1 -25
- package/dist/test_utils/npm_env.js +26 -0
- package/dist/update_check_worker.js +47 -0
- package/dist/update_notifier.js +145 -0
- package/dist/utils/package_manager.js +8 -1
- package/dist/utils/service_picker.js +34 -8
- package/package.json +7 -5
- package/dist/plugins/wizard.js +0 -36
package/dist/init/wizard.js
CHANGED
|
@@ -1,96 +1,254 @@
|
|
|
1
|
+
import { CONFIG_INIT_SERVICES } from "../config_template.js";
|
|
2
|
+
import { InitCancelled, restoreCursorVisibilityOnAbort } from "./cancelled.js";
|
|
3
|
+
import { AGENT_DETECTED_DESCRIPTION, AGENT_OTHER_DESCRIPTION, AGENT_PICKER_MESSAGE, AGENT_SETUP_MESSAGE, AGENT_SETUP_PLUGIN_DESCRIPTION, AGENT_SETUP_PLUGIN_TITLE, AGENT_SETUP_SKILLS_DESCRIPTION, AGENT_SETUP_SKILLS_TITLE, AGENT_SETUP_SKIP_DESCRIPTION, AGENT_SETUP_SKIP_TITLE, CONFIG_CONFIRM_HINT, CONFIG_CONFIRM_MESSAGE, MCP_AUTH_API_KEY_DESCRIPTION, MCP_AUTH_API_KEY_TITLE, MCP_AUTH_MESSAGE, MCP_AUTH_OAUTH_DESCRIPTION, MCP_AUTH_OAUTH_TITLE, MCP_SCOPE_GLOBAL_DESCRIPTION, MCP_SCOPE_GLOBAL_TITLE, MCP_SCOPE_MESSAGE, MCP_SCOPE_PROJECT_DESCRIPTION, MCP_SCOPE_PROJECT_TITLE, MODE_CUSTOM_DESCRIPTION, MODE_CUSTOM_TITLE, MODE_MESSAGE, MODE_RECOMMENDED_DESCRIPTION, MODE_RECOMMENDED_TITLE, PACKAGE_MANAGER_MESSAGE, PROJECT_SETUP_CLAIMABLE_DESCRIPTION, PROJECT_SETUP_CLAIMABLE_TITLE, PROJECT_SETUP_LINK_DESCRIPTION, PROJECT_SETUP_LINK_TITLE, PROJECT_SETUP_MESSAGE, SERVICES_HINT, SERVICES_MESSAGE, SKILLS_PICKER_MESSAGE } from "./copy.js";
|
|
4
|
+
import { installedPackageManagers } from "../utils/package_manager.js";
|
|
5
|
+
import { POSTGRES_SERVICE_CHOICE, keepPostgresSelected } from "../utils/service_picker.js";
|
|
6
|
+
import { getAgentDisplayName, listMcpAgentIds } from "../mcp/agents.js";
|
|
1
7
|
import { INIT_NEEDS_YES_OR_TERMINAL } from "./plan.js";
|
|
2
|
-
import { SKIP_TEMPLATE_VALUE, formatTemplateTitle } from "./template_title.js";
|
|
3
8
|
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
4
|
-
import {
|
|
9
|
+
import { NEON_SKILL_CATALOG, listSkillIds } from "../skills/catalog.js";
|
|
5
10
|
import chalk from "chalk";
|
|
6
11
|
import prompts from "prompts";
|
|
7
12
|
//#region src/init/wizard.ts
|
|
8
13
|
const restoreCursorOnAbort = (state) => {
|
|
9
|
-
|
|
10
|
-
process.stdout.write("\x1B[?25h");
|
|
11
|
-
process.stdout.write("\n");
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
+
restoreCursorVisibilityOnAbort(state);
|
|
14
15
|
};
|
|
15
16
|
const requireInteractive = () => {
|
|
16
17
|
if (!canPickAgentsInteractively()) throw new Error(INIT_NEEDS_YES_OR_TERMINAL);
|
|
17
18
|
};
|
|
19
|
+
const aborted = () => {
|
|
20
|
+
throw new InitCancelled();
|
|
21
|
+
};
|
|
22
|
+
const pickInitModeInteractively = async () => {
|
|
23
|
+
requireInteractive();
|
|
24
|
+
const { mode } = await prompts({
|
|
25
|
+
onState: restoreCursorOnAbort,
|
|
26
|
+
type: "select",
|
|
27
|
+
name: "mode",
|
|
28
|
+
message: MODE_MESSAGE,
|
|
29
|
+
initial: 0,
|
|
30
|
+
choices: [{
|
|
31
|
+
title: MODE_RECOMMENDED_TITLE,
|
|
32
|
+
value: "recommended",
|
|
33
|
+
description: MODE_RECOMMENDED_DESCRIPTION
|
|
34
|
+
}, {
|
|
35
|
+
title: MODE_CUSTOM_TITLE,
|
|
36
|
+
value: "custom",
|
|
37
|
+
description: MODE_CUSTOM_DESCRIPTION
|
|
38
|
+
}]
|
|
39
|
+
});
|
|
40
|
+
if (mode !== "recommended" && mode !== "custom") return aborted();
|
|
41
|
+
return mode;
|
|
42
|
+
};
|
|
18
43
|
const pickAgentSetupInteractively = async () => {
|
|
19
44
|
requireInteractive();
|
|
20
45
|
const { setup } = await prompts({
|
|
21
46
|
onState: restoreCursorOnAbort,
|
|
22
47
|
type: "select",
|
|
23
48
|
name: "setup",
|
|
24
|
-
message:
|
|
49
|
+
message: AGENT_SETUP_MESSAGE,
|
|
25
50
|
initial: 0,
|
|
26
51
|
choices: [
|
|
27
52
|
{
|
|
28
|
-
title:
|
|
53
|
+
title: AGENT_SETUP_PLUGIN_TITLE,
|
|
29
54
|
value: "plugin",
|
|
30
|
-
description:
|
|
55
|
+
description: AGENT_SETUP_PLUGIN_DESCRIPTION
|
|
31
56
|
},
|
|
32
57
|
{
|
|
33
|
-
title:
|
|
58
|
+
title: AGENT_SETUP_SKILLS_TITLE,
|
|
34
59
|
value: "skills-mcp",
|
|
35
|
-
description:
|
|
60
|
+
description: AGENT_SETUP_SKILLS_DESCRIPTION
|
|
36
61
|
},
|
|
37
62
|
{
|
|
38
|
-
title:
|
|
63
|
+
title: AGENT_SETUP_SKIP_TITLE,
|
|
39
64
|
value: "skip",
|
|
40
|
-
description:
|
|
65
|
+
description: AGENT_SETUP_SKIP_DESCRIPTION
|
|
41
66
|
}
|
|
42
67
|
]
|
|
43
68
|
});
|
|
44
|
-
if (setup !== "plugin" && setup !== "skills-mcp" && setup !== "skip")
|
|
69
|
+
if (setup !== "plugin" && setup !== "skills-mcp" && setup !== "skip") return aborted();
|
|
45
70
|
return setup;
|
|
46
71
|
};
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
const pickInitAgentsInteractively = async (input) => {
|
|
73
|
+
requireInteractive();
|
|
74
|
+
if (input.available.length === 0) throw new Error("No coding agents are available to pick.");
|
|
75
|
+
const detected = new Set(input.detected);
|
|
76
|
+
const { agents } = await prompts({
|
|
77
|
+
onState: restoreCursorOnAbort,
|
|
78
|
+
type: "multiselect",
|
|
79
|
+
name: "agents",
|
|
80
|
+
message: AGENT_PICKER_MESSAGE,
|
|
81
|
+
instructions: false,
|
|
82
|
+
min: 1,
|
|
83
|
+
choices: input.available.map((id) => ({
|
|
84
|
+
value: id,
|
|
85
|
+
title: `${getAgentDisplayName(id)} (${id})`,
|
|
86
|
+
description: detected.has(id) ? AGENT_DETECTED_DESCRIPTION : AGENT_OTHER_DESCRIPTION,
|
|
87
|
+
selected: detected.has(id)
|
|
88
|
+
}))
|
|
89
|
+
});
|
|
90
|
+
if (!Array.isArray(agents)) return aborted();
|
|
91
|
+
return agents.filter((id) => typeof id === "string");
|
|
92
|
+
};
|
|
93
|
+
const pickInitSkillsInteractively = async () => {
|
|
94
|
+
requireInteractive();
|
|
95
|
+
const { skills } = await prompts({
|
|
96
|
+
onState: restoreCursorOnAbort,
|
|
97
|
+
type: "multiselect",
|
|
98
|
+
name: "skills",
|
|
99
|
+
message: SKILLS_PICKER_MESSAGE,
|
|
100
|
+
instructions: false,
|
|
101
|
+
min: 1,
|
|
102
|
+
choices: NEON_SKILL_CATALOG.map((entry) => ({
|
|
103
|
+
value: entry.skill,
|
|
104
|
+
title: entry.skill,
|
|
105
|
+
selected: entry.defaultSelected
|
|
106
|
+
}))
|
|
107
|
+
});
|
|
108
|
+
if (!Array.isArray(skills)) return aborted();
|
|
109
|
+
const allowed = new Set(listSkillIds());
|
|
110
|
+
return skills.filter((skill) => typeof skill === "string" && allowed.has(skill));
|
|
111
|
+
};
|
|
112
|
+
const pickInitMcpConfigLocationInteractively = async () => {
|
|
113
|
+
requireInteractive();
|
|
114
|
+
const { scope } = await prompts({
|
|
115
|
+
onState: restoreCursorOnAbort,
|
|
116
|
+
type: "select",
|
|
117
|
+
name: "scope",
|
|
118
|
+
message: MCP_SCOPE_MESSAGE,
|
|
119
|
+
initial: 0,
|
|
120
|
+
choices: [{
|
|
121
|
+
title: MCP_SCOPE_GLOBAL_TITLE,
|
|
122
|
+
value: "global",
|
|
123
|
+
description: MCP_SCOPE_GLOBAL_DESCRIPTION
|
|
124
|
+
}, {
|
|
125
|
+
title: MCP_SCOPE_PROJECT_TITLE,
|
|
126
|
+
value: "project",
|
|
127
|
+
description: MCP_SCOPE_PROJECT_DESCRIPTION
|
|
128
|
+
}]
|
|
129
|
+
});
|
|
130
|
+
if (scope !== "global" && scope !== "project") return aborted();
|
|
131
|
+
return scope;
|
|
132
|
+
};
|
|
133
|
+
const pickInitMcpAuthInteractively = async (input) => {
|
|
134
|
+
requireInteractive();
|
|
135
|
+
const { auth } = await prompts({
|
|
136
|
+
onState: restoreCursorOnAbort,
|
|
137
|
+
type: "select",
|
|
138
|
+
name: "auth",
|
|
139
|
+
message: MCP_AUTH_MESSAGE,
|
|
140
|
+
initial: input.authenticated ? 1 : 0,
|
|
141
|
+
choices: [{
|
|
142
|
+
title: MCP_AUTH_OAUTH_TITLE,
|
|
143
|
+
value: "oauth",
|
|
144
|
+
description: MCP_AUTH_OAUTH_DESCRIPTION
|
|
145
|
+
}, {
|
|
146
|
+
title: MCP_AUTH_API_KEY_TITLE,
|
|
147
|
+
value: "api-key",
|
|
148
|
+
description: MCP_AUTH_API_KEY_DESCRIPTION
|
|
149
|
+
}]
|
|
150
|
+
});
|
|
151
|
+
if (auth !== "oauth" && auth !== "api-key") return aborted();
|
|
152
|
+
return auth;
|
|
60
153
|
};
|
|
61
|
-
const
|
|
154
|
+
const pickInitProjectSetupInteractively = async () => {
|
|
62
155
|
requireInteractive();
|
|
63
|
-
const
|
|
64
|
-
const { id } = await prompts({
|
|
156
|
+
const { setup } = await prompts({
|
|
65
157
|
onState: restoreCursorOnAbort,
|
|
66
158
|
type: "select",
|
|
67
|
-
name: "
|
|
68
|
-
message:
|
|
69
|
-
initial:
|
|
70
|
-
choices
|
|
159
|
+
name: "setup",
|
|
160
|
+
message: PROJECT_SETUP_MESSAGE,
|
|
161
|
+
initial: 0,
|
|
162
|
+
choices: [{
|
|
163
|
+
title: PROJECT_SETUP_LINK_TITLE,
|
|
164
|
+
value: "link",
|
|
165
|
+
description: PROJECT_SETUP_LINK_DESCRIPTION
|
|
166
|
+
}, {
|
|
167
|
+
title: PROJECT_SETUP_CLAIMABLE_TITLE,
|
|
168
|
+
value: "claimable",
|
|
169
|
+
description: PROJECT_SETUP_CLAIMABLE_DESCRIPTION
|
|
170
|
+
}]
|
|
71
171
|
});
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
const selected = templates.find((template) => template.id === id);
|
|
75
|
-
if (selected === void 0) throw new Error("Aborted.");
|
|
76
|
-
return {
|
|
77
|
-
kind: "template",
|
|
78
|
-
template: selected
|
|
79
|
-
};
|
|
172
|
+
if (setup !== "link" && setup !== "claimable") return aborted();
|
|
173
|
+
return setup;
|
|
80
174
|
};
|
|
81
175
|
const pickInitConfigInteractively = async () => {
|
|
82
176
|
requireInteractive();
|
|
83
|
-
process.stdout.write(`${chalk.dim(
|
|
177
|
+
process.stdout.write(`${chalk.dim(CONFIG_CONFIRM_HINT)}\n`);
|
|
84
178
|
const { value } = await prompts({
|
|
85
179
|
onState: restoreCursorOnAbort,
|
|
86
180
|
type: "confirm",
|
|
87
181
|
name: "value",
|
|
88
|
-
message:
|
|
182
|
+
message: CONFIG_CONFIRM_MESSAGE,
|
|
89
183
|
initial: true
|
|
90
184
|
});
|
|
91
|
-
if (value === void 0)
|
|
185
|
+
if (value === void 0) return aborted();
|
|
92
186
|
return value === true;
|
|
93
187
|
};
|
|
188
|
+
const pickInitServicesInteractively = async () => {
|
|
189
|
+
requireInteractive();
|
|
190
|
+
process.stdout.write(`${chalk.dim(SERVICES_HINT)}\n`);
|
|
191
|
+
const { services } = await prompts({
|
|
192
|
+
onState: restoreCursorOnAbort,
|
|
193
|
+
onRender() {
|
|
194
|
+
keepPostgresSelected(this);
|
|
195
|
+
},
|
|
196
|
+
type: "multiselect",
|
|
197
|
+
name: "services",
|
|
198
|
+
message: SERVICES_MESSAGE,
|
|
199
|
+
instructions: false,
|
|
200
|
+
cursor: 1,
|
|
201
|
+
choices: [
|
|
202
|
+
POSTGRES_SERVICE_CHOICE,
|
|
203
|
+
{
|
|
204
|
+
value: "auth",
|
|
205
|
+
title: "Managed Better Auth",
|
|
206
|
+
description: "Declare managed authentication for users and sessions."
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
value: "data-api",
|
|
210
|
+
title: "Data API",
|
|
211
|
+
description: "Declare an HTTP API for Postgres. Also declares Managed Better Auth."
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
value: "functions",
|
|
215
|
+
title: "Functions",
|
|
216
|
+
description: "Declare a hello function and add its source file."
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
value: "object-storage",
|
|
220
|
+
title: "Object Storage",
|
|
221
|
+
description: "Declare a private S3-compatible bucket named assets."
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
value: "ai-gateway",
|
|
225
|
+
title: "AI Gateway",
|
|
226
|
+
description: "Declare AI Gateway access. Requires an eligible Neon plan."
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
});
|
|
230
|
+
if (!Array.isArray(services)) return aborted();
|
|
231
|
+
return CONFIG_INIT_SERVICES.filter((service) => services.includes(service));
|
|
232
|
+
};
|
|
233
|
+
const pickInitPackageManagerInteractively = async () => {
|
|
234
|
+
requireInteractive();
|
|
235
|
+
const available = installedPackageManagers();
|
|
236
|
+
if (available.length <= 1) return available[0];
|
|
237
|
+
const initial = Math.max(0, available.indexOf("npm"));
|
|
238
|
+
const { pm } = await prompts({
|
|
239
|
+
onState: restoreCursorOnAbort,
|
|
240
|
+
type: "select",
|
|
241
|
+
name: "pm",
|
|
242
|
+
message: PACKAGE_MANAGER_MESSAGE,
|
|
243
|
+
initial,
|
|
244
|
+
choices: available.map((id) => ({
|
|
245
|
+
title: id,
|
|
246
|
+
value: id
|
|
247
|
+
}))
|
|
248
|
+
});
|
|
249
|
+
if (pm !== "npm" && pm !== "pnpm" && pm !== "yarn" && pm !== "bun") return aborted();
|
|
250
|
+
return pm;
|
|
251
|
+
};
|
|
94
252
|
const pickInitLinkInteractively = async () => {
|
|
95
253
|
requireInteractive();
|
|
96
254
|
const { value } = await prompts({
|
|
@@ -100,8 +258,9 @@ const pickInitLinkInteractively = async () => {
|
|
|
100
258
|
message: "Link this project to a Neon project now?",
|
|
101
259
|
initial: true
|
|
102
260
|
});
|
|
103
|
-
if (value === void 0)
|
|
261
|
+
if (value === void 0) return aborted();
|
|
104
262
|
return value === true;
|
|
105
263
|
};
|
|
264
|
+
const initAgentIds = () => listMcpAgentIds();
|
|
106
265
|
//#endregion
|
|
107
|
-
export {
|
|
266
|
+
export { initAgentIds, pickAgentSetupInteractively, pickInitAgentsInteractively, pickInitConfigInteractively, pickInitLinkInteractively, pickInitMcpAuthInteractively, pickInitMcpConfigLocationInteractively, pickInitModeInteractively, pickInitPackageManagerInteractively, pickInitProjectSetupInteractively, pickInitServicesInteractively, pickInitSkillsInteractively };
|
package/dist/mcp/wizard.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { getAgentDisplayName } from "./agents.js";
|
|
2
1
|
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
3
2
|
import prompts from "prompts";
|
|
4
3
|
//#region src/mcp/wizard.ts
|
|
@@ -62,30 +61,5 @@ const pickMcpProjectPin = async (linkedProjectId, willMintKey = false) => {
|
|
|
62
61
|
});
|
|
63
62
|
return pin === true;
|
|
64
63
|
};
|
|
65
|
-
const mcpInstallSummary = (options) => {
|
|
66
|
-
const agents = options.install.map(getAgentDisplayName).join(", ");
|
|
67
|
-
const auth = options.auth === "oauth" ? "OAuth, agent signs in on first use" : options.reuse ? "reuse the API key already in agent config" : options.mintProjectId ? `mint an API key limited to ${options.mintProjectId}` : "mint an account-wide API key that reaches every organization";
|
|
68
|
-
const rows = [
|
|
69
|
-
["Config", options.scope === "project" ? "this directory" : "user-level"],
|
|
70
|
-
["Agents", agents],
|
|
71
|
-
["Auth", auth],
|
|
72
|
-
["URL", options.url]
|
|
73
|
-
];
|
|
74
|
-
if (options.skipped.length > 0) rows.push(["Skipped", options.skipped.map((row) => `${getAgentDisplayName(row.agent)} (${row.error})`).join("; ")]);
|
|
75
|
-
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
76
|
-
return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join("\n");
|
|
77
|
-
};
|
|
78
|
-
const confirmMcpInstall = async (options) => {
|
|
79
|
-
if (!canPickAgentsInteractively()) return true;
|
|
80
|
-
process.stdout.write(`\n${mcpInstallSummary(options)}\n\n`);
|
|
81
|
-
const { ok } = await prompts({
|
|
82
|
-
onState: restoreCursorOnAbort,
|
|
83
|
-
type: "confirm",
|
|
84
|
-
name: "ok",
|
|
85
|
-
message: "Write the Neon MCP server into these agents?",
|
|
86
|
-
initial: true
|
|
87
|
-
});
|
|
88
|
-
return ok === true;
|
|
89
|
-
};
|
|
90
64
|
//#endregion
|
|
91
|
-
export {
|
|
65
|
+
export { pickMcpAuth, pickMcpProjectPin, pickMcpScope };
|
package/dist/skills/wizard.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { getAgentDisplayName } from "../mcp/agents.js";
|
|
2
|
-
import "../init/agents.js";
|
|
3
1
|
import { canPickAgentsInteractively } from "../utils/agent_picker.js";
|
|
4
2
|
import { NEON_SKILL_CATALOG } from "./catalog.js";
|
|
5
3
|
import prompts from "prompts";
|
|
@@ -34,28 +32,6 @@ const isSkillEntry = (value) => {
|
|
|
34
32
|
if (!("source" in value) || !("skill" in value)) return false;
|
|
35
33
|
return typeof value.source === "string" && typeof value.skill === "string" && value.source.length > 0 && value.skill.length > 0;
|
|
36
34
|
};
|
|
37
|
-
const skillsInstallSummary = (options) => {
|
|
38
|
-
const skills = options.invocations.flatMap((invocation) => invocation.skills).join(", ");
|
|
39
|
-
const rows = [
|
|
40
|
-
["Config", options.scope === "project" ? "this directory" : "user-level"],
|
|
41
|
-
["Agents", options.agents.map(getAgentDisplayName).join(", ")],
|
|
42
|
-
["Skills", skills]
|
|
43
|
-
];
|
|
44
|
-
const labelWidth = Math.max(...rows.map(([label]) => label.length));
|
|
45
|
-
return rows.map(([label, value]) => `${label.padEnd(labelWidth)} ${value}`).join("\n");
|
|
46
|
-
};
|
|
47
|
-
const confirmSkillsInstall = async (options) => {
|
|
48
|
-
if (!canPickAgentsInteractively()) return true;
|
|
49
|
-
process.stdout.write(`\n${skillsInstallSummary(options)}\n\n`);
|
|
50
|
-
const { ok } = await prompts({
|
|
51
|
-
onState: restoreCursorOnAbort,
|
|
52
|
-
type: "confirm",
|
|
53
|
-
name: "ok",
|
|
54
|
-
message: "Write Neon agent skills into these agents?",
|
|
55
|
-
initial: true
|
|
56
|
-
});
|
|
57
|
-
return ok === true;
|
|
58
|
-
};
|
|
59
35
|
const confirmSkillsUpdate = async (options) => {
|
|
60
36
|
if (!canPickAgentsInteractively()) return true;
|
|
61
37
|
const config = options.scope === "project" ? "this directory" : "user-level";
|
|
@@ -70,4 +46,4 @@ const confirmSkillsUpdate = async (options) => {
|
|
|
70
46
|
return ok === true;
|
|
71
47
|
};
|
|
72
48
|
//#endregion
|
|
73
|
-
export {
|
|
49
|
+
export { confirmSkillsUpdate, pickSkillsInteractively };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
//#region src/test_utils/npm_env.ts
|
|
5
|
+
const isNpmEnvKey = (key) => {
|
|
6
|
+
const lower = key.toLowerCase();
|
|
7
|
+
return lower.startsWith("npm_config_") || lower === "node_auth_token" || lower === "npm_token";
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Keep registry auth when a test replaces HOME. Isolated agent detection
|
|
11
|
+
* otherwise drops ~/.npmrc and `npx`/skills fail with E401 or hang.
|
|
12
|
+
*/
|
|
13
|
+
const npmEnvForIsolatedHome = () => {
|
|
14
|
+
const env = {};
|
|
15
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
16
|
+
if (value === void 0 || !isNpmEnvKey(key)) continue;
|
|
17
|
+
env[key] = value;
|
|
18
|
+
}
|
|
19
|
+
if (env.npm_config_userconfig === void 0 && env.NPM_CONFIG_USERCONFIG === void 0) {
|
|
20
|
+
const src = join(homedir(), ".npmrc");
|
|
21
|
+
if (existsSync(src)) env.npm_config_userconfig = src;
|
|
22
|
+
}
|
|
23
|
+
return env;
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { npmEnvForIsolatedHome };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { recordLatestVersion } from "./update_notifier.js";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
//#region src/update_check_worker.ts
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
8
|
+
const npmViewInvocation = (platform) => ({
|
|
9
|
+
args: [
|
|
10
|
+
"view",
|
|
11
|
+
"neon",
|
|
12
|
+
"version",
|
|
13
|
+
"--json"
|
|
14
|
+
],
|
|
15
|
+
command: platform === "win32" ? "npm.cmd" : "npm",
|
|
16
|
+
shell: platform === "win32"
|
|
17
|
+
});
|
|
18
|
+
const npmLatestVersion = async () => {
|
|
19
|
+
const invocation = npmViewInvocation(process.platform);
|
|
20
|
+
const { stdout } = await execFileAsync(invocation.command, invocation.args, {
|
|
21
|
+
encoding: "utf8",
|
|
22
|
+
shell: invocation.shell,
|
|
23
|
+
timeout: LOOKUP_TIMEOUT_MS,
|
|
24
|
+
windowsHide: true
|
|
25
|
+
});
|
|
26
|
+
const value = JSON.parse(stdout);
|
|
27
|
+
return typeof value === "string" ? value : void 0;
|
|
28
|
+
};
|
|
29
|
+
const homebrewLatestVersion = async () => {
|
|
30
|
+
const response = await fetch("https://formulae.brew.sh/api/formula/neonctl.json", { signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) });
|
|
31
|
+
if (!response.ok) return void 0;
|
|
32
|
+
const value = await response.json();
|
|
33
|
+
if (typeof value !== "object" || value === null || !("versions" in value) || typeof value.versions !== "object" || value.versions === null || !("stable" in value.versions) || typeof value.versions.stable !== "string") return;
|
|
34
|
+
return value.versions.stable;
|
|
35
|
+
};
|
|
36
|
+
const run = async () => {
|
|
37
|
+
const [cachePath, source] = process.argv.slice(2);
|
|
38
|
+
if (cachePath === void 0 || source !== "homebrew" && source !== "npm") return;
|
|
39
|
+
try {
|
|
40
|
+
const latestVersion = source === "homebrew" ? await homebrewLatestVersion() : await npmLatestVersion();
|
|
41
|
+
if (latestVersion !== void 0) recordLatestVersion(cachePath, source, latestVersion);
|
|
42
|
+
} catch {}
|
|
43
|
+
};
|
|
44
|
+
const entry = process.argv[1];
|
|
45
|
+
if (entry !== void 0 && import.meta.url === pathToFileURL(entry).href) run();
|
|
46
|
+
//#endregion
|
|
47
|
+
export { npmViewInvocation };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { isCi } from "./env.js";
|
|
2
|
+
import { log } from "./log.js";
|
|
3
|
+
import { recommendedCliUpgradeCommand } from "./utils/package_manager.js";
|
|
4
|
+
import { existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
import semver from "semver";
|
|
9
|
+
//#region src/update_notifier.ts
|
|
10
|
+
const CHECK_INTERVAL_MS = 864e5;
|
|
11
|
+
const NOTIFY_INTERVAL_MS = 3 * CHECK_INTERVAL_MS;
|
|
12
|
+
const CACHE_FILE = "update-check.json";
|
|
13
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
const parseUpdateCheckCache = (raw) => {
|
|
15
|
+
try {
|
|
16
|
+
const value = JSON.parse(raw);
|
|
17
|
+
if (!isRecord(value) || typeof value.checkedAt !== "number" || !Number.isFinite(value.checkedAt) || value.source !== "homebrew" && value.source !== "npm" || value.latestVersion !== void 0 && typeof value.latestVersion !== "string" || value.notifiedAt !== void 0 && (typeof value.notifiedAt !== "number" || !Number.isFinite(value.notifiedAt))) return;
|
|
18
|
+
return {
|
|
19
|
+
checkedAt: value.checkedAt,
|
|
20
|
+
source: value.source,
|
|
21
|
+
...value.latestVersion === void 0 ? {} : { latestVersion: value.latestVersion },
|
|
22
|
+
...value.notifiedAt === void 0 ? {} : { notifiedAt: value.notifiedAt }
|
|
23
|
+
};
|
|
24
|
+
} catch {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const readUpdateCheckCache = (cachePath) => {
|
|
29
|
+
try {
|
|
30
|
+
return parseUpdateCheckCache(readFileSync(cachePath, "utf8"));
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const writeUpdateCheckCache = (cachePath, cache) => {
|
|
36
|
+
const temporaryPath = `${cachePath}.${process.pid}.tmp`;
|
|
37
|
+
try {
|
|
38
|
+
writeFileSync(temporaryPath, `${JSON.stringify(cache)}\n`, { mode: 384 });
|
|
39
|
+
renameSync(temporaryPath, cachePath);
|
|
40
|
+
return true;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
try {
|
|
43
|
+
rmSync(temporaryPath, { force: true });
|
|
44
|
+
} catch {}
|
|
45
|
+
log.debug("Could not write the CLI update cache: %s", error instanceof Error ? error.message : String(error));
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const formatUpdateNotice = ({ currentVersion, latestVersion, upgradeCommand }) => {
|
|
50
|
+
if (semver.valid(currentVersion) === null || semver.valid(latestVersion) === null || !semver.gt(latestVersion, currentVersion)) return;
|
|
51
|
+
return `Neon CLI update available: ${currentVersion} → ${latestVersion}\n${upgradeCommand}`;
|
|
52
|
+
};
|
|
53
|
+
const shouldRefreshUpdateCheck = (cache, now) => cache === void 0 || now - cache.checkedAt >= CHECK_INTERVAL_MS;
|
|
54
|
+
const cacheForUpdateSource = (cache, source) => cache?.source === source ? cache : void 0;
|
|
55
|
+
const isCurrentBranchProbe = ({ commandPath, currentBranch }) => currentBranch && (commandPath[0] === "status" || commandPath[0] === "config" && commandPath[1] === "status");
|
|
56
|
+
const updateSource = (command) => command.startsWith("brew ") ? "homebrew" : "npm";
|
|
57
|
+
const spawnUpdateWorkerProcess = ({ cachePath, executablePath, source, workerPath }) => {
|
|
58
|
+
const child = spawn(executablePath, [
|
|
59
|
+
workerPath,
|
|
60
|
+
cachePath,
|
|
61
|
+
source
|
|
62
|
+
], {
|
|
63
|
+
detached: true,
|
|
64
|
+
stdio: "ignore",
|
|
65
|
+
windowsHide: true
|
|
66
|
+
});
|
|
67
|
+
child.on("error", (error) => {
|
|
68
|
+
log.debug("CLI update check failed to start: %s", error.message);
|
|
69
|
+
});
|
|
70
|
+
child.unref();
|
|
71
|
+
return child;
|
|
72
|
+
};
|
|
73
|
+
const spawnUpdateWorker = (cachePath, source) => {
|
|
74
|
+
const workerPath = fileURLToPath(new URL("./update_check_worker.js", import.meta.url));
|
|
75
|
+
if (!existsSync(workerPath)) return;
|
|
76
|
+
try {
|
|
77
|
+
spawnUpdateWorkerProcess({
|
|
78
|
+
cachePath,
|
|
79
|
+
executablePath: process.execPath,
|
|
80
|
+
source,
|
|
81
|
+
workerPath
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
log.debug("Could not start the CLI update check: %s", error instanceof Error ? error.message : String(error));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const isPackagedExecutable = () => "pkg" in process;
|
|
88
|
+
const shouldRunUpdateNotifier = ({ commandPath, currentBranch, disabled, isCi: runningInCi, isPackaged, output, stderrIsTty, stdoutIsTty }) => !runningInCi && !disabled && !isPackaged && output === "table" && stdoutIsTty && stderrIsTty && commandPath.length > 0 && commandPath[0] !== "completion" && !isCurrentBranchProbe({
|
|
89
|
+
commandPath,
|
|
90
|
+
currentBranch
|
|
91
|
+
});
|
|
92
|
+
const notifyIfUpdateAvailable = ({ commandPath, configDir, currentBranch, currentVersion, output }) => {
|
|
93
|
+
if (!shouldRunUpdateNotifier({
|
|
94
|
+
commandPath,
|
|
95
|
+
currentBranch,
|
|
96
|
+
disabled: "NO_UPDATE_NOTIFIER" in process.env,
|
|
97
|
+
isCi: isCi(),
|
|
98
|
+
isPackaged: isPackagedExecutable(),
|
|
99
|
+
output,
|
|
100
|
+
stderrIsTty: process.stderr.isTTY === true,
|
|
101
|
+
stdoutIsTty: process.stdout.isTTY === true
|
|
102
|
+
})) return;
|
|
103
|
+
const command = recommendedCliUpgradeCommand(fileURLToPath(import.meta.url));
|
|
104
|
+
if (command === void 0) return;
|
|
105
|
+
const cachePath = join(configDir, CACHE_FILE);
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const source = updateSource(command);
|
|
108
|
+
const cache = cacheForUpdateSource(readUpdateCheckCache(cachePath), source);
|
|
109
|
+
let nextCache = cache;
|
|
110
|
+
if (cache?.latestVersion !== void 0 && (cache.notifiedAt === void 0 || now - cache.notifiedAt >= NOTIFY_INTERVAL_MS)) {
|
|
111
|
+
const notice = formatUpdateNotice({
|
|
112
|
+
currentVersion,
|
|
113
|
+
latestVersion: cache.latestVersion,
|
|
114
|
+
upgradeCommand: command
|
|
115
|
+
});
|
|
116
|
+
if (notice !== void 0) {
|
|
117
|
+
log.warning("%s", notice);
|
|
118
|
+
nextCache = {
|
|
119
|
+
...cache,
|
|
120
|
+
notifiedAt: now
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (!shouldRefreshUpdateCheck(cache, now)) {
|
|
125
|
+
if (nextCache !== cache && nextCache !== void 0) writeUpdateCheckCache(cachePath, nextCache);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
nextCache = {
|
|
129
|
+
...nextCache,
|
|
130
|
+
checkedAt: now,
|
|
131
|
+
source
|
|
132
|
+
};
|
|
133
|
+
if (writeUpdateCheckCache(cachePath, nextCache)) spawnUpdateWorker(cachePath, source);
|
|
134
|
+
};
|
|
135
|
+
const recordLatestVersion = (cachePath, source, latestVersion) => {
|
|
136
|
+
if (semver.valid(latestVersion) === null) return;
|
|
137
|
+
const cache = cacheForUpdateSource(readUpdateCheckCache(cachePath), source);
|
|
138
|
+
if (cache === void 0) return;
|
|
139
|
+
writeUpdateCheckCache(cachePath, {
|
|
140
|
+
...cache,
|
|
141
|
+
latestVersion
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
//#endregion
|
|
145
|
+
export { cacheForUpdateSource, formatUpdateNotice, notifyIfUpdateAvailable, parseUpdateCheckCache, recordLatestVersion, shouldRefreshUpdateCheck, shouldRunUpdateNotifier, spawnUpdateWorkerProcess, writeUpdateCheckCache };
|
|
@@ -159,6 +159,13 @@ const resolvePackageManager = (cwd) => inferPackageManager(cwd) ?? installedPack
|
|
|
159
159
|
* as {@link resolvePackageManager} minus the lockfile walk.
|
|
160
160
|
*/
|
|
161
161
|
const resolveInvokingPackageManager = () => detectInvokingPackageManager() ?? installedPackageManagers()[0] ?? "npm";
|
|
162
|
+
const recommendedCliUpgradeCommand = (modulePath) => {
|
|
163
|
+
const path = modulePath.replace(/\\/g, "/").toLowerCase();
|
|
164
|
+
if (path.includes("/cellar/neonctl/")) return "brew upgrade neonctl";
|
|
165
|
+
if (path.includes("/.bun/install/global/node_modules/neon/")) return "bun i -g neon@latest";
|
|
166
|
+
if (path.includes("/pnpm/global/") && path.includes("/.pnpm/neon@") && path.includes("/node_modules/neon/")) return "pnpm i -g neon@latest";
|
|
167
|
+
if (path.includes("/lib/node_modules/neon/") || path.includes("/npm/node_modules/neon/")) return "npm i -g neon@latest";
|
|
168
|
+
};
|
|
162
169
|
/**
|
|
163
170
|
* The argv for an install with `pm`: every dependency in the manifest when
|
|
164
171
|
* `packages` is empty, otherwise those packages added to it. npm spells the add
|
|
@@ -326,4 +333,4 @@ const runCommand = (cmd, args, cwd, env) => new Promise((resolvePromise) => {
|
|
|
326
333
|
});
|
|
327
334
|
});
|
|
328
335
|
//#endregion
|
|
329
|
-
export { DO_NOT_SUBSTITUTE_HINT, MISSING_BINARY_HINT, commandEnv, detectInvokingPackageManager, detectProjectPackageManager, formatExecCommand, formatInstallCommand, globalInstallCommand, inferPackageManager, installArgs, installedPackageManagers, resolveInvokingPackageManager, resolvePackageManager, runCommand };
|
|
336
|
+
export { DO_NOT_SUBSTITUTE_HINT, MISSING_BINARY_HINT, commandEnv, detectInvokingPackageManager, detectProjectPackageManager, formatExecCommand, formatInstallCommand, globalInstallCommand, inferPackageManager, installArgs, installedPackageManagers, recommendedCliUpgradeCommand, resolveInvokingPackageManager, resolvePackageManager, runCommand };
|