neon 4.17.4 → 4.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/analytics.js +25 -1
- package/dist/commands/bootstrap.js +5 -1
- package/dist/commands/config.js +25 -6
- package/dist/commands/init.js +10 -0
- package/dist/commands/mcp.js +2 -0
- package/dist/commands/plugins.js +2 -0
- package/dist/commands/skills.js +2 -0
- package/dist/commands/triggers.js +108 -33
- package/dist/index.js +3 -9
- package/dist/utils/config_diff.js +37 -12
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -736,6 +736,8 @@ neon function domains delete docs.example.com
|
|
|
736
736
|
|
|
737
737
|
Register prints the record, then an INFO line `CNAME <domain> to <cname_target>`. The domain goes live after that CNAME resolves and a certificate is issued on the first request.
|
|
738
738
|
|
|
739
|
+
A `neon.ts` `customDomains` list is applied by `neon config plan`, `neon config apply`, and `neon checkout` only on the project's default branch. Other branches still deploy the function. Set `customDomains` in the `branch` closure to register a hostname on a child. See [`@neon/config` Default-branch-only fields](../config/README.md#default-branch-only-fields).
|
|
740
|
+
|
|
739
741
|
When a package cannot be bundled — a native addon with no esbuild loader, or an optional peer dependency a library references on an untaken code path — list it in that function's `externalPackages` and the bundler leaves the import alone. `neon dev` honours the same list. It does not make the package resolvable in the deployed archive (there is no `node_modules` next to the bundle), so it only unblocks an import that is never evaluated — a dependency the handler actually calls has to be bundled, and a natively-backed one cannot be. See [`@neon/config`](../config/README.md#unbundleable-dependencies-externalpackages).
|
|
740
742
|
|
|
741
743
|
## Scaffold a project (`bootstrap`)
|
package/dist/analytics.js
CHANGED
|
@@ -195,6 +195,30 @@ const trackEvent = (event, properties) => {
|
|
|
195
195
|
});
|
|
196
196
|
log.debug("Sent CLI event: %s", event);
|
|
197
197
|
};
|
|
198
|
+
let commandSuccessExtras = {};
|
|
199
|
+
const recordCommandSuccessExtras = (patch) => {
|
|
200
|
+
commandSuccessExtras = {
|
|
201
|
+
...commandSuccessExtras,
|
|
202
|
+
...patch
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
const recordScaffoldedTemplate = (templateId) => {
|
|
206
|
+
recordCommandSuccessExtras({ template: templateId });
|
|
207
|
+
};
|
|
208
|
+
const takeCommandSuccessExtras = () => {
|
|
209
|
+
const extras = commandSuccessExtras;
|
|
210
|
+
commandSuccessExtras = {};
|
|
211
|
+
return extras;
|
|
212
|
+
};
|
|
213
|
+
const commandSuccessProperties = (args) => ({
|
|
214
|
+
...getAnalyticsEventProperties(args),
|
|
215
|
+
projectId: args.projectId,
|
|
216
|
+
branchId: args.branchId,
|
|
217
|
+
accountId: args.accountId,
|
|
218
|
+
authMethod: args.authMethod,
|
|
219
|
+
authData: args.authData,
|
|
220
|
+
...takeCommandSuccessExtras()
|
|
221
|
+
});
|
|
198
222
|
const analyticsCommand = (args) => {
|
|
199
223
|
const command = args._.join(" ");
|
|
200
224
|
const raw = args._[0] !== "ask" || typeof args.prompt !== "string" ? command : `${command} ${args.prompt}`;
|
|
@@ -209,4 +233,4 @@ const getAnalyticsEventProperties = (args) => ({
|
|
|
209
233
|
githubEnvVars: getGithubEnvVars(process.env)
|
|
210
234
|
});
|
|
211
235
|
//#endregion
|
|
212
|
-
export { analyticsMiddleware, analyticsUserId, closeAnalytics, getAnalyticsEventProperties, getErrorAnalyticsEventProperties, initAnalyticsClientMiddleware, sendError, storedCredentialAttribution, telemetryCredential, trackEvent };
|
|
236
|
+
export { analyticsMiddleware, analyticsUserId, closeAnalytics, commandSuccessProperties, getAnalyticsEventProperties, getErrorAnalyticsEventProperties, initAnalyticsClientMiddleware, recordCommandSuccessExtras, recordScaffoldedTemplate, sendError, storedCredentialAttribution, takeCommandSuccessExtras, telemetryCredential, trackEvent };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { isCi } from "../env.js";
|
|
3
3
|
import { log } from "../log.js";
|
|
4
|
+
import { recordCommandSuccessExtras, recordScaffoldedTemplate } from "../analytics.js";
|
|
4
5
|
import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
|
|
5
6
|
import { getCliName } from "../utils/cli_name.js";
|
|
6
7
|
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
@@ -114,8 +115,11 @@ const handler = async (props) => {
|
|
|
114
115
|
const targetDir = await resolveTargetDir(props, interactive, template);
|
|
115
116
|
ensureTargetUsable(targetDir, props.force);
|
|
116
117
|
await scaffold(template, targetDir);
|
|
118
|
+
recordScaffoldedTemplate(template.id);
|
|
117
119
|
printScaffolded(template, targetDir);
|
|
118
|
-
|
|
120
|
+
const result = await runPostScaffoldSteps(props, targetDir, interactive, template, named);
|
|
121
|
+
recordCommandSuccessExtras({ agent_setup: result.agentSetup });
|
|
122
|
+
return result;
|
|
119
123
|
};
|
|
120
124
|
/**
|
|
121
125
|
* The template list to choose from. When --template is given we try the
|
package/dist/commands/config.js
CHANGED
|
@@ -12,7 +12,7 @@ import { loadEnvFileIntoProcess } from "../env_file.js";
|
|
|
12
12
|
import { writer } from "../writer.js";
|
|
13
13
|
import { assertAiGatewayProvisionable, warnAiGateway } from "../utils/ai_gateway_notice.js";
|
|
14
14
|
import { announceTargetBranch } from "../utils/branch_notice.js";
|
|
15
|
-
import { renderAppliedChanges, renderBranchSettingConflicts } from "../utils/config_diff.js";
|
|
15
|
+
import { renderAppliedChanges, renderBranchSettingConflicts, renderCustomDomainFollowup } from "../utils/config_diff.js";
|
|
16
16
|
import { fillSingleProject, resolveBranchRef } from "../utils/enrichers.js";
|
|
17
17
|
import { formatInstallCommand, installArgs, resolvePackageManager, runCommand } from "../utils/package_manager.js";
|
|
18
18
|
import { bundleEntry } from "../utils/esbuild.js";
|
|
@@ -70,6 +70,7 @@ const neonctlBundler = async (fn) => {
|
|
|
70
70
|
assertZipWithinLimits(fn.slug, zip, entries);
|
|
71
71
|
return zip;
|
|
72
72
|
};
|
|
73
|
+
const reasonAllowsUpdateExisting = (reason) => reason.includes("Pass `updateExisting: true`");
|
|
73
74
|
const INSPECT_FIELDS = [
|
|
74
75
|
"project",
|
|
75
76
|
"branch",
|
|
@@ -381,6 +382,7 @@ const planCmd = async (props) => {
|
|
|
381
382
|
});
|
|
382
383
|
const services = utilizedServices(config);
|
|
383
384
|
reportPushResult(props, result, "plan", services);
|
|
385
|
+
if (result.conflicts.some((conflict) => conflict.field === "customDomain" && !reasonAllowsUpdateExisting(conflict.reason))) process.exitCode = 1;
|
|
384
386
|
if (services.includes("AI Gateway")) await warnAiGateway({
|
|
385
387
|
apiClient: props.apiClient,
|
|
386
388
|
projectId: props.projectId,
|
|
@@ -411,7 +413,8 @@ const applyCmd = async (props) => {
|
|
|
411
413
|
} catch (err) {
|
|
412
414
|
if (err instanceof PushConflictError) {
|
|
413
415
|
reportConflicts(props, err.conflicts);
|
|
414
|
-
|
|
416
|
+
const overrideable = err.conflicts.every((c) => reasonAllowsUpdateExisting(c.reason));
|
|
417
|
+
throw new Error(overrideable ? "Branch settings conflict with the policy. Re-run with --update-existing to apply the changes shown above." : "The policy conflicts with remote state. --update-existing will not resolve every conflict shown above.");
|
|
415
418
|
}
|
|
416
419
|
throw err;
|
|
417
420
|
}
|
|
@@ -472,9 +475,16 @@ const reportPushResult = (props, result, mode, services) => {
|
|
|
472
475
|
}
|
|
473
476
|
const color = props.color !== false;
|
|
474
477
|
const out = writer(props);
|
|
475
|
-
const noChanges = appliedChanges.length === 0;
|
|
478
|
+
const noChanges = appliedChanges.length === 0 && result.conflicts.length === 0;
|
|
476
479
|
const appliedText = renderAppliedChanges(appliedChanges, mode === "plan" ? "Planned changes" : "Applied changes", { color });
|
|
477
480
|
if (appliedText) out.text(`${appliedText}\n`);
|
|
481
|
+
if (result.conflicts.length > 0) {
|
|
482
|
+
const conflictText = renderBranchSettingConflicts(result.conflicts, { color });
|
|
483
|
+
if (conflictText) out.text(`\n${conflictText}\n`);
|
|
484
|
+
for (const conflict of result.conflicts) if (!reasonAllowsUpdateExisting(conflict.reason)) out.text(` ! ${conflict.field}: ${conflict.reason}\n`);
|
|
485
|
+
}
|
|
486
|
+
const followup = renderCustomDomainFollowup(result, { color });
|
|
487
|
+
if (followup) out.text(`\n${followup}\n`);
|
|
478
488
|
if (functionUrlBySlug.size > 0) {
|
|
479
489
|
const heading = mode === "plan" ? "Function URLs (after apply)" : "Function URLs";
|
|
480
490
|
out.text(`\n${isCi() ? heading : chalk.bold(heading)}\n`);
|
|
@@ -498,7 +508,7 @@ const reportConflicts = (props, conflicts) => {
|
|
|
498
508
|
const out = writer(props);
|
|
499
509
|
const text = renderBranchSettingConflicts([...conflicts], { color: props.color !== false });
|
|
500
510
|
if (text) out.text(`${text}\n`);
|
|
501
|
-
for (const conflict of conflicts) if (
|
|
511
|
+
for (const conflict of conflicts) if (!reasonAllowsUpdateExisting(conflict.reason)) out.text(` ! ${conflict.field}: ${conflict.reason}\n`);
|
|
502
512
|
};
|
|
503
513
|
/**
|
|
504
514
|
* Block provisioning the AI Gateway on a Free plan from the `checkout` policy paths, which
|
|
@@ -565,11 +575,20 @@ const applyPolicyOnCreate = async (props) => {
|
|
|
565
575
|
*/
|
|
566
576
|
const logPolicyResult = (result, opts) => {
|
|
567
577
|
const changes = result.applied.filter((c) => c.action !== "noop");
|
|
568
|
-
if (changes.length === 0) {
|
|
578
|
+
if (changes.length === 0 && result.conflicts.length === 0) {
|
|
569
579
|
log.info("neon.ts applied — no changes were needed.");
|
|
580
|
+
const followup = renderCustomDomainFollowup(result, opts);
|
|
581
|
+
if (followup) log.info("%s", followup);
|
|
570
582
|
return;
|
|
571
583
|
}
|
|
572
|
-
log.info("%s", renderAppliedChanges(changes, `neon.ts applied — ${changes.length} change${changes.length === 1 ? "" : "s"}:`, opts));
|
|
584
|
+
if (changes.length > 0) log.info("%s", renderAppliedChanges(changes, `neon.ts applied — ${changes.length} change${changes.length === 1 ? "" : "s"}:`, opts));
|
|
585
|
+
if (result.conflicts.length > 0) {
|
|
586
|
+
const conflictText = renderBranchSettingConflicts(result.conflicts, opts);
|
|
587
|
+
if (conflictText) log.info("%s", conflictText);
|
|
588
|
+
for (const conflict of result.conflicts) if (!reasonAllowsUpdateExisting(conflict.reason)) log.info(" ! %s: %s", conflict.field, conflict.reason);
|
|
589
|
+
}
|
|
590
|
+
const followup = renderCustomDomainFollowup(result, opts);
|
|
591
|
+
if (followup) log.info("%s", followup);
|
|
573
592
|
};
|
|
574
593
|
/**
|
|
575
594
|
* Create a branch **from** the local `neon.ts` policy. Returns `null` when there is no
|
package/dist/commands/init.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
3
|
import { contextBranch, readContextFile } from "../context.js";
|
|
4
|
+
import { recordCommandSuccessExtras } from "../analytics.js";
|
|
4
5
|
import { t as credentialInputs } from "../_chunks/auth_selection-pitMFzDe.js";
|
|
5
6
|
import { getCliName } from "../utils/cli_name.js";
|
|
6
7
|
import { deprecatedServiceMessage, parseServices, servicesFlagValue, servicesOption } from "../neon_services.js";
|
|
@@ -184,6 +185,12 @@ const noteTemplateKeepsShippedConfig = (config, services) => {
|
|
|
184
185
|
if (config === void 0 && services === void 0) return;
|
|
185
186
|
log.warning(INIT_TEMPLATE_KEEPS_CONFIG);
|
|
186
187
|
};
|
|
188
|
+
const recordInitSuccess = (kind, agentSetup) => {
|
|
189
|
+
recordCommandSuccessExtras({
|
|
190
|
+
init_kind: kind,
|
|
191
|
+
...agentSetup !== void 0 ? { agent_setup: agentSetup } : {}
|
|
192
|
+
});
|
|
193
|
+
};
|
|
187
194
|
const handler = async (props) => {
|
|
188
195
|
if (props.output === "json" || props.output === "yaml") throw new Error(`\`${getCliName()} init\` does not support --output. The commands it runs print their own output.`);
|
|
189
196
|
const cwd = props.cwd ?? process.cwd();
|
|
@@ -228,6 +235,7 @@ const handler = async (props) => {
|
|
|
228
235
|
...templateChoice.kind === "template" ? { id: templateChoice.id } : {}
|
|
229
236
|
}, linkInputs));
|
|
230
237
|
printNestedBootstrapDone(result, cwd, templateChoice.kind === "template" ? templateChoice.id : "default");
|
|
238
|
+
recordInitSuccess("empty-template", result?.agentSetup);
|
|
231
239
|
return;
|
|
232
240
|
}
|
|
233
241
|
if (templateChoice.kind === "ask") {
|
|
@@ -242,6 +250,7 @@ const handler = async (props) => {
|
|
|
242
250
|
useDefault: false
|
|
243
251
|
}, linkInputs));
|
|
244
252
|
printNestedBootstrapDone(result, cwd, picked.template.id);
|
|
253
|
+
recordInitSuccess("empty-template", result?.agentSetup);
|
|
245
254
|
return;
|
|
246
255
|
}
|
|
247
256
|
}
|
|
@@ -351,6 +360,7 @@ Run \`${getCliName()} env pull\` once resolved.`, message);
|
|
|
351
360
|
],
|
|
352
361
|
next: []
|
|
353
362
|
}));
|
|
363
|
+
recordInitSuccess(templateChoice.kind === "existing" ? "existing" : "empty-skip", agentSetup);
|
|
354
364
|
};
|
|
355
365
|
//#endregion
|
|
356
366
|
export { builder, command, describe, handler, initChildEnv, init_exports as t };
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
3
|
import { readContextFile } from "../context.js";
|
|
4
|
+
import { recordCommandSuccessExtras } from "../analytics.js";
|
|
4
5
|
import { getCliName } from "../utils/cli_name.js";
|
|
5
6
|
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
6
7
|
import { writer } from "../writer.js";
|
|
@@ -217,6 +218,7 @@ const handler = async (props) => {
|
|
|
217
218
|
if (minted) log.info("Minted API key %s (id %d, %s). Revoke with: %s", minted.name, minted.id, minted.projectId ? "project" : "account", mintedKeyRevokeCommand(minted));
|
|
218
219
|
if (plan.auth === "oauth") log.info("The agent will prompt for Neon sign-in on first use.");
|
|
219
220
|
if (failedAgents.length > 0) throw new Error(`Failed to write Neon MCP config for: ${failedAgents.join(", ")}.`);
|
|
221
|
+
recordCommandSuccessExtras({ scope: plan.scope });
|
|
220
222
|
};
|
|
221
223
|
//#endregion
|
|
222
224
|
export { builder, command, describe, handler, mcp_exports as t };
|
package/dist/commands/plugins.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
|
+
import { recordCommandSuccessExtras } from "../analytics.js";
|
|
3
4
|
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
4
5
|
import { writer } from "../writer.js";
|
|
5
6
|
import { noPassthrough } from "../utils/flags.js";
|
|
@@ -124,6 +125,7 @@ const handler = async (props) => {
|
|
|
124
125
|
});
|
|
125
126
|
out.end();
|
|
126
127
|
if (failed.length === 0) {
|
|
128
|
+
recordCommandSuccessExtras({ scope: plan.scope });
|
|
127
129
|
log.info(plan.scope === "project" ? "Installed the Neon plugin (project)." : "Installed the Neon plugin (user).");
|
|
128
130
|
return;
|
|
129
131
|
}
|
package/dist/commands/skills.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "../_chunks/rolldown-runtime-8H4AJuhK.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
|
+
import { recordCommandSuccessExtras } from "../analytics.js";
|
|
3
4
|
import { helpCsv, helpEpilogue } from "../utils/help_text.js";
|
|
4
5
|
import { writer } from "../writer.js";
|
|
5
6
|
import { noPassthrough } from "../utils/flags.js";
|
|
@@ -160,6 +161,7 @@ const handler = async (props) => {
|
|
|
160
161
|
});
|
|
161
162
|
out.end();
|
|
162
163
|
if (failed.length === 0) {
|
|
164
|
+
recordCommandSuccessExtras({ scope: plan.scope });
|
|
163
165
|
log.info(plan.scope === "project" ? "Wrote skills in this directory." : "Wrote user-level skills.");
|
|
164
166
|
return;
|
|
165
167
|
}
|
|
@@ -27,17 +27,112 @@ async function withTriggerNotFound(triggerId, branchId, run) {
|
|
|
27
27
|
throw err;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
const TRIGGER_COMMON_FIELDS = [
|
|
31
|
+
"trigger_id",
|
|
32
|
+
"name",
|
|
33
|
+
"function_slug",
|
|
34
|
+
"function_path",
|
|
35
|
+
"enabled",
|
|
36
|
+
"inherited"
|
|
37
|
+
];
|
|
30
38
|
const TRIGGER_FIELDS = [
|
|
31
39
|
"trigger_id",
|
|
32
40
|
"name",
|
|
41
|
+
"type",
|
|
33
42
|
"function_slug",
|
|
34
43
|
"function_path",
|
|
35
44
|
"schedule",
|
|
45
|
+
"storage",
|
|
36
46
|
"enabled",
|
|
37
47
|
"inherited",
|
|
38
48
|
"next_run_at"
|
|
39
49
|
];
|
|
40
|
-
|
|
50
|
+
function storageMatch(config) {
|
|
51
|
+
return config.prefix === void 0 ? config.bucket_name : `${config.bucket_name} ${config.prefix}`;
|
|
52
|
+
}
|
|
53
|
+
function triggerTableRow(t) {
|
|
54
|
+
if (t.type === "schedule") return {
|
|
55
|
+
trigger_id: t.trigger_id,
|
|
56
|
+
name: t.name,
|
|
57
|
+
type: t.type,
|
|
58
|
+
function_slug: t.function_slug,
|
|
59
|
+
function_path: t.function_path,
|
|
60
|
+
schedule: t.schedule.cron,
|
|
61
|
+
storage: "",
|
|
62
|
+
enabled: t.enabled,
|
|
63
|
+
inherited: t.inherited,
|
|
64
|
+
next_run_at: t.next_run_at ?? ""
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
trigger_id: t.trigger_id,
|
|
68
|
+
name: t.name,
|
|
69
|
+
type: t.type,
|
|
70
|
+
function_slug: t.function_slug,
|
|
71
|
+
function_path: t.function_path,
|
|
72
|
+
schedule: "",
|
|
73
|
+
storage: storageMatch(t.storage_object_created),
|
|
74
|
+
enabled: t.enabled,
|
|
75
|
+
inherited: t.inherited,
|
|
76
|
+
next_run_at: ""
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function writeTrigger(props, data, extra) {
|
|
80
|
+
if (props.output !== "table") {
|
|
81
|
+
writer(props).end(data, {
|
|
82
|
+
fields: TRIGGER_COMMON_FIELDS,
|
|
83
|
+
...extra
|
|
84
|
+
});
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const rows = Array.isArray(data) ? data.map(triggerTableRow) : triggerTableRow(data);
|
|
88
|
+
writer(props).end(rows, {
|
|
89
|
+
fields: TRIGGER_FIELDS,
|
|
90
|
+
...extra
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
function enabledPatch(type, enabled) {
|
|
94
|
+
if (type === "schedule") return {
|
|
95
|
+
type: "schedule",
|
|
96
|
+
enabled
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
type: "storage_object_created",
|
|
100
|
+
enabled
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function updatePatch(type, props) {
|
|
104
|
+
if (type === "schedule") {
|
|
105
|
+
const body = { type: "schedule" };
|
|
106
|
+
if (props["function-slug"] !== void 0) body.function_slug = props["function-slug"];
|
|
107
|
+
if (props.name !== void 0) body.name = props.name;
|
|
108
|
+
if (props.cron !== void 0) body.schedule = { cron: props.cron };
|
|
109
|
+
if (props["function-path"] !== void 0) body.function_path = props["function-path"];
|
|
110
|
+
if (props.enabled !== void 0) body.enabled = props.enabled;
|
|
111
|
+
return body;
|
|
112
|
+
}
|
|
113
|
+
const body = { type: "storage_object_created" };
|
|
114
|
+
if (props["function-slug"] !== void 0) body.function_slug = props["function-slug"];
|
|
115
|
+
if (props.name !== void 0) body.name = props.name;
|
|
116
|
+
if (props["function-path"] !== void 0) body.function_path = props["function-path"];
|
|
117
|
+
if (props.enabled !== void 0) body.enabled = props.enabled;
|
|
118
|
+
return body;
|
|
119
|
+
}
|
|
120
|
+
function noFieldsToUpdateMessage(type) {
|
|
121
|
+
const flags = type === "schedule" ? [
|
|
122
|
+
"--function-slug",
|
|
123
|
+
"--name",
|
|
124
|
+
"--cron",
|
|
125
|
+
"--function-path",
|
|
126
|
+
"--enabled"
|
|
127
|
+
] : [
|
|
128
|
+
"--function-slug",
|
|
129
|
+
"--name",
|
|
130
|
+
"--function-path",
|
|
131
|
+
"--enabled"
|
|
132
|
+
];
|
|
133
|
+
const last = flags[flags.length - 1];
|
|
134
|
+
return `No fields to update. Pass at least one of ${flags.slice(0, -1).join(", ")}, or ${last}.`;
|
|
135
|
+
}
|
|
41
136
|
const command = "triggers";
|
|
42
137
|
const describe = "Manage function triggers";
|
|
43
138
|
const aliases = ["trigger"];
|
|
@@ -117,19 +212,12 @@ const handler = (args) => {
|
|
|
117
212
|
const list = async (props) => {
|
|
118
213
|
const branchId = await branchIdFromProps(props);
|
|
119
214
|
const { data } = await props.apiClient.listProjectBranchTriggers(props.projectId, branchId);
|
|
120
|
-
|
|
121
|
-
fields: TRIGGER_FIELDS,
|
|
122
|
-
renderColumns,
|
|
123
|
-
emptyMessage: "No triggers found on this branch."
|
|
124
|
-
});
|
|
215
|
+
writeTrigger(props, data.triggers, { emptyMessage: "No triggers found on this branch." });
|
|
125
216
|
};
|
|
126
217
|
const get = async (props) => {
|
|
127
218
|
const branchId = await branchIdFromProps(props);
|
|
128
219
|
const { data } = await withTriggerNotFound(props.id, branchId, () => props.apiClient.getProjectBranchTrigger(props.projectId, branchId, props.id));
|
|
129
|
-
|
|
130
|
-
fields: TRIGGER_FIELDS,
|
|
131
|
-
renderColumns
|
|
132
|
-
});
|
|
220
|
+
writeTrigger(props, data.trigger);
|
|
133
221
|
};
|
|
134
222
|
const create = async (props) => {
|
|
135
223
|
const branchId = await branchIdFromProps(props);
|
|
@@ -142,36 +230,23 @@ const create = async (props) => {
|
|
|
142
230
|
if (props["function-path"] !== void 0) body.function_path = props["function-path"];
|
|
143
231
|
if (props.enabled !== void 0) body.enabled = props.enabled;
|
|
144
232
|
const { data } = await retryOnLock(() => props.apiClient.createProjectBranchTrigger(props.projectId, branchId, body));
|
|
145
|
-
|
|
146
|
-
fields: TRIGGER_FIELDS,
|
|
147
|
-
renderColumns
|
|
148
|
-
});
|
|
233
|
+
writeTrigger(props, data.trigger);
|
|
149
234
|
};
|
|
150
235
|
const update = async (props) => {
|
|
151
236
|
const branchId = await branchIdFromProps(props);
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
if (props.
|
|
155
|
-
|
|
156
|
-
if (
|
|
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.");
|
|
237
|
+
const { data: existing } = await withTriggerNotFound(props.id, branchId, () => props.apiClient.getProjectBranchTrigger(props.projectId, branchId, props.id));
|
|
238
|
+
const trigger = existing.trigger;
|
|
239
|
+
if (props.cron !== void 0 && trigger.type !== "schedule") throw new Error(`Trigger ${props.id} is type ${trigger.type}; --cron applies to schedule triggers.`);
|
|
240
|
+
const body = updatePatch(trigger.type, props);
|
|
241
|
+
if (Object.keys(body).filter((k) => k !== "type").length === 0) throw new Error(noFieldsToUpdateMessage(trigger.type));
|
|
159
242
|
const { data } = await withTriggerNotFound(props.id, branchId, () => retryOnLock(() => props.apiClient.updateProjectBranchTrigger(props.projectId, branchId, props.id, body)));
|
|
160
|
-
|
|
161
|
-
fields: TRIGGER_FIELDS,
|
|
162
|
-
renderColumns
|
|
163
|
-
});
|
|
243
|
+
writeTrigger(props, data.trigger);
|
|
164
244
|
};
|
|
165
245
|
const setEnabled = async (props, enabled) => {
|
|
166
246
|
const branchId = await branchIdFromProps(props);
|
|
167
|
-
const { data } = await withTriggerNotFound(props.id, branchId, () =>
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
})));
|
|
171
|
-
writer(props).end(data.trigger, {
|
|
172
|
-
fields: TRIGGER_FIELDS,
|
|
173
|
-
renderColumns
|
|
174
|
-
});
|
|
247
|
+
const { data: existing } = await withTriggerNotFound(props.id, branchId, () => props.apiClient.getProjectBranchTrigger(props.projectId, branchId, props.id));
|
|
248
|
+
const { data } = await withTriggerNotFound(props.id, branchId, () => retryOnLock(() => props.apiClient.updateProjectBranchTrigger(props.projectId, branchId, props.id, enabledPatch(existing.trigger.type, enabled))));
|
|
249
|
+
writeTrigger(props, data.trigger);
|
|
175
250
|
};
|
|
176
251
|
const deleteTrigger = async (props) => {
|
|
177
252
|
const branchId = await branchIdFromProps(props);
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getAuthContext } from "./auth_context.js";
|
|
|
5
5
|
import { r as defaultDir } from "./_chunks/paths-DMq0Lt7a.js";
|
|
6
6
|
import { ensureConfigDir } from "./config.js";
|
|
7
7
|
import { currentContextFile, enrichFromContext } from "./context.js";
|
|
8
|
-
import { analyticsMiddleware, closeAnalytics,
|
|
8
|
+
import { analyticsMiddleware, closeAnalytics, commandSuccessProperties, initAnalyticsClientMiddleware, sendError, takeCommandSuccessExtras, trackEvent } from "./analytics.js";
|
|
9
9
|
import { NETWORK_ERROR_MESSAGE, isNetworkError, matchErrorCode } from "./errors.js";
|
|
10
10
|
import { AuthRefreshError, defaultClientID } from "./auth.js";
|
|
11
11
|
import { ensureAuth } from "./commands/auth.js";
|
|
@@ -187,14 +187,7 @@ async function handleError(msg, err, canRetry) {
|
|
|
187
187
|
const MAX_ATTEMPTS = 2;
|
|
188
188
|
while (attempts < MAX_ATTEMPTS) try {
|
|
189
189
|
const args = await builder.argv;
|
|
190
|
-
trackEvent("cli_command_success",
|
|
191
|
-
...getAnalyticsEventProperties(args),
|
|
192
|
-
projectId: args.projectId,
|
|
193
|
-
branchId: args.branchId,
|
|
194
|
-
accountId: args.accountId,
|
|
195
|
-
authMethod: args.authMethod,
|
|
196
|
-
authData: args.authData
|
|
197
|
-
});
|
|
190
|
+
trackEvent("cli_command_success", commandSuccessProperties(args));
|
|
198
191
|
if (args._.length === 0 || args.help) {
|
|
199
192
|
await showHelp(builder);
|
|
200
193
|
process.exit(0);
|
|
@@ -202,6 +195,7 @@ async function handleError(msg, err, canRetry) {
|
|
|
202
195
|
await closeAnalytics();
|
|
203
196
|
break;
|
|
204
197
|
} catch (err) {
|
|
198
|
+
takeCommandSuccessExtras();
|
|
205
199
|
attempts++;
|
|
206
200
|
if (!await handleError("", err, attempts < MAX_ATTEMPTS) || attempts >= MAX_ATTEMPTS) {
|
|
207
201
|
await closeAnalytics();
|
|
@@ -75,11 +75,19 @@ const renderBranchGroups = (byBranch, paint) => {
|
|
|
75
75
|
return lines;
|
|
76
76
|
};
|
|
77
77
|
/** Friendly label for a service change identifier (`bucket:x` → `bucket x`). */
|
|
78
|
-
const serviceLabel = (identifier) => {
|
|
78
|
+
const serviceLabel = (identifier, details) => {
|
|
79
79
|
if (identifier === "auth") return "Neon Auth";
|
|
80
80
|
if (identifier === "dataApi") return "Data API";
|
|
81
81
|
if (identifier.startsWith("bucket:")) return `bucket ${identifier.slice(7)}`;
|
|
82
82
|
if (identifier.startsWith("function:")) return `function ${identifier.slice(9)}`;
|
|
83
|
+
if (identifier.startsWith("domain:")) {
|
|
84
|
+
const domain = identifier.slice(7);
|
|
85
|
+
const previous = typeof details?.previousSlug === "string" ? details.previousSlug : void 0;
|
|
86
|
+
const slug = typeof details?.slug === "string" ? details.slug : void 0;
|
|
87
|
+
if (previous !== void 0 && slug !== void 0) return `domain ${domain}: ${previous} → ${slug}`;
|
|
88
|
+
if (slug !== void 0) return `domain ${domain} -> ${slug}`;
|
|
89
|
+
return `domain ${domain}`;
|
|
90
|
+
}
|
|
83
91
|
return identifier;
|
|
84
92
|
};
|
|
85
93
|
/**
|
|
@@ -110,7 +118,7 @@ const renderAppliedChanges = (changes, title, opts) => {
|
|
|
110
118
|
const lines = [paint.title(title)];
|
|
111
119
|
const services = changes.filter((c) => c.kind === "service").sort((a, b) => a.identifier.localeCompare(b.identifier));
|
|
112
120
|
for (const service of services) {
|
|
113
|
-
const label = serviceLabel(service.identifier);
|
|
121
|
+
const label = serviceLabel(service.identifier, service.details);
|
|
114
122
|
const line = service.action === "create" ? `+ ${label}` : service.action === "delete" ? `- ${label}` : `~ ${label}`;
|
|
115
123
|
lines.push(` ${service.action === "create" ? paint.added(line) : service.action === "delete" ? paint.removed(line) : paint.arrow(line)}`);
|
|
116
124
|
}
|
|
@@ -122,22 +130,39 @@ const renderAppliedChanges = (changes, title, opts) => {
|
|
|
122
130
|
lines.push(...renderBranchGroups(byBranch, paint));
|
|
123
131
|
return lines.join("\n");
|
|
124
132
|
};
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
const isOverrideableConflict = (conflict) => conflict.reason.includes("Pass `updateExisting: true`");
|
|
134
|
+
const CUSTOM_DOMAIN_REASON = /^custom domain "([^"]+)"/;
|
|
135
|
+
const labeledConflictField = (conflict) => {
|
|
136
|
+
if (conflict.field !== "customDomain") return conflict.field;
|
|
137
|
+
const match = CUSTOM_DOMAIN_REASON.exec(conflict.reason);
|
|
138
|
+
return match?.[1] !== void 0 ? `customDomain ${match[1]}` : conflict.field;
|
|
139
|
+
};
|
|
132
140
|
const renderBranchSettingConflicts = (conflicts, opts) => {
|
|
133
141
|
if (conflicts.length === 0) return "";
|
|
134
142
|
const paint = palette(opts.color);
|
|
135
143
|
const byBranch = /* @__PURE__ */ new Map();
|
|
136
144
|
for (const conflict of conflicts) {
|
|
137
145
|
const existing = byBranch.get(conflict.identifier) ?? [];
|
|
138
|
-
byBranch.set(conflict.identifier, [...existing, ...expandField(conflict
|
|
146
|
+
byBranch.set(conflict.identifier, [...existing, ...expandField(labeledConflictField(conflict), conflict.current, conflict.desired)]);
|
|
147
|
+
}
|
|
148
|
+
const heading = conflicts.every(isOverrideableConflict) ? "Branch settings differ (re-run with --update-existing to apply)" : "Branch settings differ";
|
|
149
|
+
return [paint.title(heading), ...renderBranchGroups(byBranch, paint)].join("\n");
|
|
150
|
+
};
|
|
151
|
+
const renderCustomDomainFollowup = (result, opts) => {
|
|
152
|
+
const paint = palette(opts.color);
|
|
153
|
+
const lines = [];
|
|
154
|
+
const cnames = (result.customDomains ?? []).filter((d) => d.cnameTarget !== void 0 && d.cnameTarget !== "");
|
|
155
|
+
if (cnames.length > 0) {
|
|
156
|
+
lines.push(paint.title("Custom domains"));
|
|
157
|
+
for (const d of cnames) lines.push(` CNAME ${d.domain} -> ${d.cnameTarget}`);
|
|
158
|
+
lines.push(" Point each hostname at the CNAME target. Neon does not create DNS records.");
|
|
139
159
|
}
|
|
140
|
-
|
|
160
|
+
if (result.warnings.length > 0) {
|
|
161
|
+
if (lines.length > 0) lines.push("");
|
|
162
|
+
lines.push(paint.title("Warnings"));
|
|
163
|
+
for (const warning of result.warnings) lines.push(` ${warning}`);
|
|
164
|
+
}
|
|
165
|
+
return lines.join("\n");
|
|
141
166
|
};
|
|
142
167
|
//#endregion
|
|
143
|
-
export { renderAppliedChanges, renderBranchSettingConflicts };
|
|
168
|
+
export { renderAppliedChanges, renderBranchSettingConflicts, renderCustomDomainFollowup };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.18.1",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"yaml": "^2.9.0",
|
|
64
64
|
"yargs": "17.7.2",
|
|
65
65
|
"yoctocolors": "^2.1.2",
|
|
66
|
-
"@neon/config": "1.4.
|
|
67
|
-
"@neon/sdk": "
|
|
68
|
-
"@neon/config
|
|
66
|
+
"@neon/config-runtime": "1.4.1",
|
|
67
|
+
"@neon/sdk": "6.0.0",
|
|
68
|
+
"@neon/config": "1.5.1"
|
|
69
69
|
},
|
|
70
70
|
"optionalDependencies": {
|
|
71
71
|
"@napi-rs/keyring": "1.3.0",
|
|
@@ -97,9 +97,9 @@
|
|
|
97
97
|
"tsx": "4.22.3",
|
|
98
98
|
"typescript": "^5.9.0",
|
|
99
99
|
"vitest": "^3.0.9",
|
|
100
|
-
"@neon-internals/env-core": "0.0.15",
|
|
101
100
|
"@neon-internals/cli-core": "0.0.0",
|
|
102
101
|
"@neon/e2e-harness": "0.0.0",
|
|
102
|
+
"@neon-internals/env-core": "0.0.17",
|
|
103
103
|
"@neon/functions": "0.10.0"
|
|
104
104
|
},
|
|
105
105
|
"publishConfig": {
|