atmn 2.0.32 → 2.0.34

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.
Files changed (40) hide show
  1. package/dist/bin.js +1408 -188
  2. package/dist/index.js +220 -19
  3. package/dist/tsconfig.tsbuildinfo +1 -1
  4. package/package.json +2 -2
  5. package/src/actions/pull/webhooks/appendWebhook.ts +84 -0
  6. package/src/actions/pull/webhooks/applyWebhooksPull.ts +100 -0
  7. package/src/actions/pull/webhooks/locateWebhook.ts +22 -0
  8. package/src/actions/pull/webhooks/removeWebhookEnv.ts +141 -0
  9. package/src/actions/pull/webhooks/types.ts +20 -0
  10. package/src/actions/pull/webhooks/updateWebhook.ts +138 -0
  11. package/src/actions/pull.ts +32 -2
  12. package/src/actions/push/pushLanesError.ts +47 -0
  13. package/src/actions/push.ts +220 -60
  14. package/src/actions/webhooks/applyWebhooks.ts +52 -0
  15. package/src/actions/webhooks/isDashboardWebhook.ts +9 -0
  16. package/src/actions/webhooks/newWebhookId.ts +4 -0
  17. package/src/actions/webhooks/previewWebhooks.ts +40 -0
  18. package/src/actions/webhooks/resolveWebhookEnv.ts +34 -0
  19. package/src/actions/webhooks/resolveWebhooksForEnv.ts +29 -0
  20. package/src/actions/webhooks/types/webhookEnv.ts +9 -0
  21. package/src/actions/webhooks/webhookSecretName.ts +22 -0
  22. package/src/actions/webhooks/writeWebhookSecrets.ts +55 -0
  23. package/src/cli.ts +20 -1
  24. package/src/config/loadConfig.ts +3 -0
  25. package/src/generated/apiRoutes.ts +165 -1
  26. package/src/generated/client.ts +239 -1
  27. package/src/generated/emit.ts +20 -1
  28. package/src/generated/emitRuntime.ts +8 -0
  29. package/src/generated/isLocalWebhookUrl.ts +64 -0
  30. package/src/generated/lintRules.ts +95 -0
  31. package/src/generated/lintRuntime.ts +113 -10
  32. package/src/generated/sandboxName.ts +52 -0
  33. package/src/generated/skills.ts +10 -10
  34. package/src/generated/webhooks.ts +36 -0
  35. package/src/generated/wire.ts +32 -5
  36. package/src/index.ts +1 -0
  37. package/src/render/renderPreview.ts +17 -2
  38. package/src/render/renderWebhooks.ts +92 -0
  39. package/src/surgery/appendPropertyEdit.ts +9 -3
  40. package/src/surgery/patchFixturePaths.ts +57 -5
package/dist/index.js CHANGED
@@ -16,6 +16,59 @@ var featureGrant = (input) => ({
16
16
  });
17
17
  // src/generated/variants.ts
18
18
  var variant = (input) => input;
19
+ // src/generated/webhooks.ts
20
+ var webhook = (input) => input;
21
+ // src/generated/isLocalWebhookUrl.ts
22
+ var PRIVATE_IPV4_RANGES = [
23
+ [0, 32],
24
+ [2130706432, 8],
25
+ [167772160, 8],
26
+ [2886729728, 12],
27
+ [3232235520, 16],
28
+ [2851995648, 16]
29
+ ];
30
+ var parseIpv4 = (host) => {
31
+ const parts = host.split(".");
32
+ if (parts.length !== 4)
33
+ return null;
34
+ if (parts.some((part) => !/^\d+$/.test(part) || Number(part) > 255)) {
35
+ return null;
36
+ }
37
+ return parts.reduce((acc, part) => acc * 256 + Number(part), 0);
38
+ };
39
+ var isPrivateIpv4 = (address) => PRIVATE_IPV4_RANGES.some(([base, prefixBits]) => {
40
+ const mask = prefixBits === 0 ? 0 : 4294967295 << 32 - prefixBits >>> 0;
41
+ return (address & mask) >>> 0 === base;
42
+ });
43
+ var isLocalIpv6 = (host) => {
44
+ if (host === "::1" || host === "::")
45
+ return true;
46
+ if (/^fe[89ab][0-9a-f]:/.test(host))
47
+ return true;
48
+ if (/^f[cd][0-9a-f]{0,2}:/.test(host))
49
+ return true;
50
+ const [, high, low] = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/) ?? [];
51
+ if (!high || !low)
52
+ return false;
53
+ return isPrivateIpv4(Number.parseInt(high, 16) * 65536 + Number.parseInt(low, 16));
54
+ };
55
+ var isLocalWebhookUrl = (url) => {
56
+ let hostname;
57
+ try {
58
+ hostname = new URL(url).hostname.toLowerCase().replace(/\.$/, "");
59
+ } catch {
60
+ return false;
61
+ }
62
+ if (hostname.startsWith("[") && hostname.endsWith("]")) {
63
+ return isLocalIpv6(hostname.slice(1, -1));
64
+ }
65
+ if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local")) {
66
+ return true;
67
+ }
68
+ const ipv4 = parseIpv4(hostname);
69
+ return ipv4 !== null && isPrivateIpv4(ipv4);
70
+ };
71
+
19
72
  // src/generated/lintRules.ts
20
73
  var LINT_RULES = {
21
74
  features: {
@@ -319,6 +372,10 @@ var LINT_RULES = {
319
372
  keys: {
320
373
  minLength: 1,
321
374
  maxLength: 64
375
+ },
376
+ values: {
377
+ minLength: 1,
378
+ maxLength: 128
322
379
  }
323
380
  },
324
381
  "plans.billingControls.usageLimits": {
@@ -342,6 +399,10 @@ var LINT_RULES = {
342
399
  keys: {
343
400
  minLength: 1,
344
401
  maxLength: 64
402
+ },
403
+ values: {
404
+ minLength: 1,
405
+ maxLength: 128
345
406
  }
346
407
  },
347
408
  "plans.freeTrial": {
@@ -1312,6 +1373,10 @@ var LINT_RULES = {
1312
1373
  keys: {
1313
1374
  minLength: 1,
1314
1375
  maxLength: 64
1376
+ },
1377
+ values: {
1378
+ minLength: 1,
1379
+ maxLength: 128
1315
1380
  }
1316
1381
  },
1317
1382
  "plans.variants.customize.billingControls.usageLimits": {
@@ -1335,6 +1400,10 @@ var LINT_RULES = {
1335
1400
  keys: {
1336
1401
  minLength: 1,
1337
1402
  maxLength: 64
1403
+ },
1404
+ values: {
1405
+ minLength: 1,
1406
+ maxLength: 128
1338
1407
  }
1339
1408
  },
1340
1409
  "plans.variants.customize.freeTrial": {
@@ -2161,6 +2230,80 @@ var LINT_RULES = {
2161
2230
  exclusiveMinimum: 0
2162
2231
  }
2163
2232
  }
2233
+ },
2234
+ webhooks: {
2235
+ label: "webhook",
2236
+ idField: "id",
2237
+ required: ["events", "id", "url"],
2238
+ fields: {
2239
+ id: {
2240
+ minLength: 1,
2241
+ maxLength: 256,
2242
+ pattern: "^[a-zA-Z0-9_-]+$"
2243
+ },
2244
+ events: {
2245
+ minItems: 1
2246
+ }
2247
+ },
2248
+ rules: [
2249
+ {
2250
+ kind: "unique",
2251
+ field: "id",
2252
+ because: "Two webhooks claiming one id race to define the same endpoint."
2253
+ },
2254
+ {
2255
+ kind: "unique",
2256
+ field: "id",
2257
+ asEnvName: true,
2258
+ because: "Each webhook's signing secret is saved as AUTUMN_WEBHOOK_<ID>_SECRET, so these two would overwrite each other's secret. Rename one."
2259
+ },
2260
+ {
2261
+ kind: "rejects",
2262
+ field: "url",
2263
+ check: "isLocalWebhookUrl",
2264
+ because: "Autumn delivers from the internet, so localhost and private-network addresses can never be reached. Use a public URL or a tunnel (e.g. ngrok)."
2265
+ },
2266
+ {
2267
+ kind: "uniformPrefix",
2268
+ field: "events",
2269
+ prefix: "vercel.",
2270
+ because: "vercel.* events can't be mixed with other events in one webhook: they're delivered from a separate app. Make one webhook for each."
2271
+ },
2272
+ {
2273
+ kind: "nonEmpty",
2274
+ field: "url",
2275
+ warning: true,
2276
+ because: "With no environment key the webhook is registered nowhere. Add `live`, `sandbox` or a sandbox's slug."
2277
+ },
2278
+ {
2279
+ kind: "knownValues",
2280
+ field: "events",
2281
+ values: [
2282
+ "customer.products.updated",
2283
+ "customer.threshold_reached",
2284
+ "balances.usage_alert_triggered",
2285
+ "balances.limit_reached",
2286
+ "billing.auto_topup_failed",
2287
+ "billing.auto_topup_succeeded",
2288
+ "billing.updated",
2289
+ "invoice.finalized",
2290
+ "vercel.resources.deleted",
2291
+ "vercel.resources.provisioned",
2292
+ "vercel.resources.rotate_secrets",
2293
+ "vercel.webhooks.event"
2294
+ ],
2295
+ warning: true,
2296
+ because: "isn't known to this atmn version; the server will check it."
2297
+ }
2298
+ ]
2299
+ },
2300
+ "webhooks.url": {
2301
+ keys: {
2302
+ pattern: "^[a-z0-9_-]+$"
2303
+ },
2304
+ values: {
2305
+ pattern: "^[Hh][Tt][Tt][Pp][Ss]:\\/\\/"
2306
+ }
2164
2307
  }
2165
2308
  };
2166
2309
 
@@ -2331,7 +2474,8 @@ var entryRuleFailures = ({
2331
2474
  entry,
2332
2475
  rule,
2333
2476
  document,
2334
- parent
2477
+ parent,
2478
+ checks
2335
2479
  }) => {
2336
2480
  switch (rule.kind) {
2337
2481
  case "requiredWhen": {
@@ -2371,9 +2515,29 @@ var entryRuleFailures = ({
2371
2515
  }
2372
2516
  case "nonEmpty": {
2373
2517
  const value = entry[rule.field];
2374
- if (!Array.isArray(value) || value.length > 0)
2518
+ const empty = Array.isArray(value) ? value.length === 0 : isEntry(value) && Object.keys(value).length === 0;
2519
+ return empty ? [`${rule.field} is empty. ${rule.because}`] : [];
2520
+ }
2521
+ case "knownValues": {
2522
+ const value = entry[rule.field];
2523
+ if (!Array.isArray(value))
2524
+ return [];
2525
+ return value.filter((item) => typeof item === "string" && !rule.values.includes(item)).map((item) => `\`${item}\` ${rule.because}`);
2526
+ }
2527
+ case "uniformPrefix": {
2528
+ const value = entry[rule.field];
2529
+ if (!Array.isArray(value))
2530
+ return [];
2531
+ const prefixed = value.filter((item) => typeof item === "string" && item.startsWith(rule.prefix)).length;
2532
+ return prefixed === 0 || prefixed === value.length ? [] : [rule.because];
2533
+ }
2534
+ case "rejects": {
2535
+ const value = entry[rule.field];
2536
+ const check = checks[rule.check];
2537
+ if (check === undefined)
2375
2538
  return [];
2376
- return [`${rule.field} is empty. ${rule.because}`];
2539
+ const stated = typeof value === "string" ? [value] : isEntry(value) ? Object.values(value).filter((item) => typeof item === "string") : [];
2540
+ return stated.filter((item) => check(item)).map((item) => `${rule.field} ${show(item)} is refused. ${rule.because}`);
2377
2541
  }
2378
2542
  case "compare": {
2379
2543
  const a = entry[rule.field];
@@ -2435,7 +2599,7 @@ var checkUnique = ({
2435
2599
  trail,
2436
2600
  issues
2437
2601
  }) => {
2438
- const seen = new Set;
2602
+ const seen = new Map;
2439
2603
  for (const [index, entry] of entries.entries()) {
2440
2604
  if (!isEntry(entry))
2441
2605
  continue;
@@ -2446,15 +2610,25 @@ var checkUnique = ({
2446
2610
  const pair = rule.alongside === undefined ? undefined : entry[rule.alongside];
2447
2611
  if (rule.alongside !== undefined && typeof pair !== "string")
2448
2612
  continue;
2449
- const composite = rule.alongside === undefined ? value : `${value}@${pair}`;
2450
- if (seen.has(composite)) {
2613
+ const normalized = rule.asEnvName ? value.toUpperCase().replace(/-/g, "_") : value;
2614
+ const composite = rule.alongside === undefined ? normalized : `${normalized}@${pair}`;
2615
+ const first = seen.get(composite);
2616
+ if (rule.asEnvName && first !== undefined) {
2617
+ if (first !== value)
2618
+ issues.push({
2619
+ path: render([...trail, crumbFor({ node, key, entry, index })]),
2620
+ message: `${fieldName} ${show(value)} and ${show(first)} both read as ${normalized} in an env var name. ${rule.because}`
2621
+ });
2622
+ continue;
2623
+ }
2624
+ if (first !== undefined) {
2451
2625
  const label = rule.alongside === undefined ? `${fieldName} ${show(value)}` : `${fieldName} ${show(value)} with ${rule.alongside} ${show(pair)}`;
2452
2626
  issues.push({
2453
2627
  path: render([...trail, crumbFor({ node, key, entry, index })]),
2454
2628
  message: `${label} is used more than once. ${rule.because}`
2455
2629
  });
2456
2630
  }
2457
- seen.add(composite);
2631
+ seen.set(composite, value);
2458
2632
  }
2459
2633
  };
2460
2634
  var pinOf = ({
@@ -2595,13 +2769,15 @@ var checkEntry = ({
2595
2769
  checkVariants({ entry, variants: node.variants, at, issues: walk.issues });
2596
2770
  }
2597
2771
  for (const rule of node.rules ?? []) {
2772
+ const warning = "warning" in rule && rule.warning === true;
2598
2773
  for (const message of entryRuleFailures({
2599
2774
  entry,
2600
2775
  rule,
2601
2776
  document: walk.document,
2602
- parent
2777
+ parent,
2778
+ checks: walk.checks
2603
2779
  })) {
2604
- walk.issues.push({ path: at, message });
2780
+ walk.issues.push({ path: at, message, ...warning ? { warning } : {} });
2605
2781
  }
2606
2782
  }
2607
2783
  };
@@ -2664,7 +2840,17 @@ var walkValue = ({
2664
2840
  return;
2665
2841
  if (walk.hints.recordPaths.has(path)) {
2666
2842
  const keys = walk.rules[path]?.keys;
2843
+ const values = walk.rules[path]?.values;
2667
2844
  for (const [recordKey, child] of Object.entries(value)) {
2845
+ if (values && !isEntry(child) && !Array.isArray(child)) {
2846
+ for (const message of constraintFailures({
2847
+ name: `${key}.${recordKey}`,
2848
+ value: child,
2849
+ constraints: values
2850
+ })) {
2851
+ walk.issues.push({ path: render(trail), message });
2852
+ }
2853
+ }
2668
2854
  if (keys) {
2669
2855
  for (const message of constraintFailures({
2670
2856
  name: `${key} key ${show(recordKey)}`,
@@ -2690,9 +2876,10 @@ var walkValue = ({
2690
2876
  var lintDocument = ({
2691
2877
  document,
2692
2878
  rules,
2693
- hints
2879
+ hints,
2880
+ checks = {}
2694
2881
  }) => {
2695
- const walk = { document, rules, hints, issues: [] };
2882
+ const walk = { document, rules, hints, checks, issues: [] };
2696
2883
  walkEntry({ entry: document, path: "", trail: [], walk });
2697
2884
  return walk.issues;
2698
2885
  };
@@ -2827,7 +3014,8 @@ var CATALOG_HINTS = hintsOf({
2827
3014
  "plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.creditSchema.multipliers",
2828
3015
  "plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.creditSchema.multipliers.*.match",
2829
3016
  "plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.modelMarkups",
2830
- "plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.providerMarkups"
3017
+ "plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.providerMarkups",
3018
+ "webhooks.url"
2831
3019
  ],
2832
3020
  frozenPaths: [
2833
3021
  "plans.licenses.metadata",
@@ -2844,15 +3032,19 @@ var stated = (config) => ({
2844
3032
  } : {},
2845
3033
  ...config.rewards !== undefined ? { rewards: config.rewards } : {},
2846
3034
  ...config.referralPrograms !== undefined ? { referralPrograms: config.referralPrograms } : {},
2847
- ...config.settings !== undefined ? { settings: config.settings } : {}
3035
+ ...config.settings !== undefined ? { settings: config.settings } : {},
3036
+ ...config.webhooks !== undefined ? { webhooks: config.webhooks } : {}
2848
3037
  });
2849
3038
  var SINGLETON_KEYS = ["settings"];
3039
+ var LIST_KEYS = ["webhooks"];
3040
+ var WARNINGS_KEY = "lint_warnings";
2850
3041
  var CONFIG_KEYS = [
2851
3042
  "features",
2852
3043
  "plans",
2853
3044
  "rewards",
2854
3045
  "referralPrograms",
2855
- "settings"
3046
+ "settings",
3047
+ "webhooks"
2856
3048
  ];
2857
3049
  var unknownKeyIssue = (key) => key === "planVersions" ? {
2858
3050
  path: key,
@@ -2862,10 +3054,14 @@ var unknownKeyIssue = (key) => key === "planVersions" ? {
2862
3054
  message: `${key} is not a config field. The fields are ${CONFIG_KEYS.join(", ")}.`
2863
3055
  };
2864
3056
  var splitWire = (document) => ({
2865
- catalog: Object.fromEntries(Object.entries(document).filter(([key]) => !SINGLETON_KEYS.includes(key))),
3057
+ catalog: Object.fromEntries(Object.entries(document).filter(([key]) => !SINGLETON_KEYS.includes(key) && !LIST_KEYS.includes(key) && key !== WARNINGS_KEY)),
2866
3058
  singletons: {
2867
3059
  settings: document.settings === undefined ? undefined : { config: document.settings }
2868
- }
3060
+ },
3061
+ lists: {
3062
+ webhooks: document.webhooks
3063
+ },
3064
+ warnings: document[WARNINGS_KEY] ?? []
2869
3065
  });
2870
3066
  var atmn = (config) => {
2871
3067
  const unknownKeys = Object.keys(config).filter((key) => !CONFIG_KEYS.includes(key));
@@ -2875,21 +3071,26 @@ var atmn = (config) => {
2875
3071
  const issues = lintDocument({
2876
3072
  document,
2877
3073
  rules: LINT_RULES,
2878
- hints: CATALOG_HINTS
3074
+ hints: CATALOG_HINTS,
3075
+ checks: { isLocalWebhookUrl }
2879
3076
  });
2880
- if (issues.length > 0)
2881
- throw new ConfigError(issues);
3077
+ const errors = issues.filter((issue) => issue.warning !== true);
3078
+ if (errors.length > 0)
3079
+ throw new ConfigError(errors);
3080
+ const warnings = issues.filter((issue) => issue.warning === true);
2882
3081
  return {
2883
3082
  ...toWire({
2884
3083
  value: document,
2885
3084
  path: "",
2886
3085
  hints: CATALOG_HINTS
2887
3086
  }),
3087
+ ...warnings.length > 0 ? { [WARNINGS_KEY]: warnings } : {},
2888
3088
  skip_deletions: false,
2889
3089
  migration: { draft: true }
2890
3090
  };
2891
3091
  };
2892
3092
  export {
3093
+ webhook,
2893
3094
  variant,
2894
3095
  referralProgram,
2895
3096
  plan,
@@ -1 +1 @@
1
- {"version":"7.0.0-dev.20260511.1","root":["../src/bin.ts","../src/cli.ts","../src/index.ts","../src/version.ts","../src/actions/env.ts","../src/actions/login.ts","../src/actions/pull.ts","../src/actions/push.ts","../src/actions/api/callApi.ts","../src/actions/api/registerApiCommands.ts","../src/actions/env/fetchOrgInfo.ts","../src/actions/env/types/orgInfo.ts","../src/actions/init/runInit.ts","../src/actions/login/keyless.ts","../src/actions/pull/appendPlanVersionFixture.ts","../src/actions/pull/applyMappings.ts","../src/actions/pull/applyPreview.ts","../src/actions/pull/applySettingsPreview.ts","../src/actions/pull/changedFixtureKeys.ts","../src/actions/pull/elementStatesPlanId.ts","../src/actions/pull/listSourceFiles.ts","../src/actions/pull/locateFixture.ts","../src/actions/pull/pruneUnpulledPlanIds.ts","../src/actions/pull/resolveCollectionTarget.ts","../src/actions/pull/rewriteConfig.ts","../src/actions/pull/scaffoldConfig.ts","../src/actions/push/backfillInternalIds.ts","../src/actions/push/deprecatedFields.ts","../src/actions/reset/runReset.ts","../src/actions/sandbox/createSandbox.ts","../src/actions/sandbox/deleteSandbox.ts","../src/actions/sandbox/listSandboxes.ts","../src/actions/sandbox/useSandbox.ts","../src/actions/sandbox/withSandboxScopeHint.ts","../src/actions/sandbox/types/sandboxClient.ts","../src/actions/skills/ignoreBundledSkills.ts","../src/actions/skills/skills.ts","../src/auth/announceAuthorizationUrl.ts","../src/auth/buildAuthorizationUrl.ts","../src/auth/callbackPages.ts","../src/auth/createOrgApiKeys.ts","../src/auth/keyless.ts","../src/auth/oauthConfig.ts","../src/auth/runOAuthFlow.ts","../src/auth/browser/openSystemBrowser.ts","../src/auth/browser/tryOpenBrowser.ts","../src/auth/browser/watchLauncher.ts","../src/auth/types/browserOpener.ts","../src/auth/types/impersonationTokens.ts","../src/auth/types/oauthTokens.ts","../src/auth/types/orgApiKeys.ts","../src/config/configPackageName.ts","../src/config/legacyConfig.ts","../src/config/loadConfig.ts","../src/env/assertSandboxTarget.ts","../src/env/loadEnv.ts","../src/env/resolveTarget.ts","../src/env/sandboxKeyName.ts","../src/generated/apiRoutes.ts","../src/generated/client.ts","../src/generated/emit.ts","../src/generated/emitRuntime.ts","../src/generated/features.ts","../src/generated/labels.ts","../src/generated/licenses.ts","../src/generated/lintRules.ts","../src/generated/lintRuntime.ts","../src/generated/mappingAssignments.ts","../src/generated/plans.ts","../src/generated/referralPrograms.ts","../src/generated/rewards.ts","../src/generated/settings.ts","../src/generated/skills.ts","../src/generated/variants.ts","../src/generated/wire.ts","../src/http/autumnFetch.ts","../src/project/chooseConfigDir.ts","../src/project/resolveProject.ts","../src/project/rootMarker.ts","../src/prompt/prompt.ts","../src/prompt/select.ts","../src/render/renderEnv.ts","../src/render/renderPreview.ts","../src/render/renderSandboxes.ts","../src/render/stripTerminalControls.ts","../src/repo/findRepoRoot.ts","../src/surgery/appendPropertyEdit.ts","../src/surgery/appendToArray.ts","../src/surgery/appendToBinding.ts","../src/surgery/appendToCollection.ts","../src/surgery/appendToFixtureArray.ts","../src/surgery/arrayBinding.ts","../src/surgery/deleteFixtureLiteral.ts","../src/surgery/deleteReference.ts","../src/surgery/ensureBuilderImport.ts","../src/surgery/findFixture.ts","../src/surgery/fixtureEdit.ts","../src/surgery/fixtureLocation.ts","../src/surgery/insertCollection.ts","../src/surgery/insertFirstProperty.ts","../src/surgery/patchFixturePaths.ts","../src/surgery/patchFixtureProperty.ts","../src/surgery/patchSingletonProperty.ts","../src/surgery/readFixtureValue.ts","../src/surgery/replaceFixture.ts","../src/surgery/setFixtureProperty.ts","../src/surgery/staticFixtureRule.ts","../test/apiCommand.test.ts","../test/authorizationUrl.test.ts","../test/autumnFetch.test.ts","../test/backfill.test.ts","../test/backfillInlineVariants.test.ts","../test/backfillSlug.test.ts","../test/backfillVariants.test.ts","../test/chooseConfigDir.test.ts","../test/cliTargetFlags.test.ts","../test/client.test.ts","../test/configPackageName.test.ts","../test/emitFixture.test.ts","../test/env.test.ts","../test/envWriter.test.ts","../test/ignoreBundledSkills.test.ts","../test/init.test.ts","../test/keyless.test.ts","../test/legacyConfig.test.ts","../test/lintDocument.test.ts","../test/loadConfigNode.test.ts","../test/loadConfigV2.test.ts","../test/locateFixtureUnique.test.ts","../test/login.test.ts","../test/nodeNextTypes.test.ts","../test/patchFixturePaths.test.ts","../test/plans.test.ts","../test/previewIsEmpty.test.ts","../test/prompt.test.ts","../test/pull.test.ts","../test/pullByReference.test.ts","../test/pullFieldLevel.test.ts","../test/pullMappings.test.ts","../test/pullOmitDefaults.test.ts","../test/pullPlans.test.ts","../test/pullStatesSlugs.test.ts","../test/pullTwoCollections.test.ts","../test/readFixtureValue.test.ts","../test/renameHint.test.ts","../test/renderPreview.test.ts","../test/renderSandboxes.test.ts","../test/renderSettings.test.ts","../test/reset.test.ts","../test/resolveCollectionTarget.test.ts","../test/resolveProject.test.ts","../test/resolveTarget.test.ts","../test/sandboxActions.test.ts","../test/sandboxKeyName.test.ts","../test/sandboxUse.test.ts","../test/settingsActions.test.ts","../test/settingsWire.test.ts","../test/skills.test.ts","../test/watchLauncher.test.ts","../test/wireDocument.test.ts","../test/helpers/targetFor.ts","../test/surgery/appendToCollection.test.ts","../test/surgery/deleteFixtureLiteral.test.ts","../test/surgery/deleteReference.test.ts","../test/surgery/findFixture.test.ts","../test/surgery/fixtureLocation.test.ts","../test/surgery/insertCollection.test.ts","../test/surgery/insertFirstProperty.test.ts","../test/surgery/patchFixtureProperty.test.ts","../test/surgery/patchSingletonProperty.test.ts","../test/surgery/replaceFixture.test.ts","../test/surgery/staticFixtureRule.test.ts"]}
1
+ {"version":"7.0.0-dev.20260511.1","root":["../src/bin.ts","../src/cli.ts","../src/index.ts","../src/version.ts","../src/actions/env.ts","../src/actions/login.ts","../src/actions/pull.ts","../src/actions/push.ts","../src/actions/api/callApi.ts","../src/actions/api/registerApiCommands.ts","../src/actions/env/fetchOrgInfo.ts","../src/actions/env/types/orgInfo.ts","../src/actions/init/runInit.ts","../src/actions/login/keyless.ts","../src/actions/pull/appendPlanVersionFixture.ts","../src/actions/pull/applyMappings.ts","../src/actions/pull/applyPreview.ts","../src/actions/pull/applySettingsPreview.ts","../src/actions/pull/changedFixtureKeys.ts","../src/actions/pull/elementStatesPlanId.ts","../src/actions/pull/listSourceFiles.ts","../src/actions/pull/locateFixture.ts","../src/actions/pull/pruneUnpulledPlanIds.ts","../src/actions/pull/resolveCollectionTarget.ts","../src/actions/pull/rewriteConfig.ts","../src/actions/pull/scaffoldConfig.ts","../src/actions/pull/webhooks/appendWebhook.ts","../src/actions/pull/webhooks/applyWebhooksPull.ts","../src/actions/pull/webhooks/locateWebhook.ts","../src/actions/pull/webhooks/removeWebhookEnv.ts","../src/actions/pull/webhooks/types.ts","../src/actions/pull/webhooks/updateWebhook.ts","../src/actions/push/backfillInternalIds.ts","../src/actions/push/deprecatedFields.ts","../src/actions/push/pushLanesError.ts","../src/actions/reset/runReset.ts","../src/actions/sandbox/createSandbox.ts","../src/actions/sandbox/deleteSandbox.ts","../src/actions/sandbox/listSandboxes.ts","../src/actions/sandbox/useSandbox.ts","../src/actions/sandbox/withSandboxScopeHint.ts","../src/actions/sandbox/types/sandboxClient.ts","../src/actions/skills/ignoreBundledSkills.ts","../src/actions/skills/skills.ts","../src/actions/webhooks/applyWebhooks.ts","../src/actions/webhooks/isDashboardWebhook.ts","../src/actions/webhooks/newWebhookId.ts","../src/actions/webhooks/previewWebhooks.ts","../src/actions/webhooks/resolveWebhookEnv.ts","../src/actions/webhooks/resolveWebhooksForEnv.ts","../src/actions/webhooks/webhookSecretName.ts","../src/actions/webhooks/writeWebhookSecrets.ts","../src/actions/webhooks/types/webhookEnv.ts","../src/auth/announceAuthorizationUrl.ts","../src/auth/buildAuthorizationUrl.ts","../src/auth/callbackPages.ts","../src/auth/createOrgApiKeys.ts","../src/auth/keyless.ts","../src/auth/oauthConfig.ts","../src/auth/runOAuthFlow.ts","../src/auth/browser/openSystemBrowser.ts","../src/auth/browser/tryOpenBrowser.ts","../src/auth/browser/watchLauncher.ts","../src/auth/types/browserOpener.ts","../src/auth/types/impersonationTokens.ts","../src/auth/types/oauthTokens.ts","../src/auth/types/orgApiKeys.ts","../src/config/configPackageName.ts","../src/config/legacyConfig.ts","../src/config/loadConfig.ts","../src/env/assertSandboxTarget.ts","../src/env/loadEnv.ts","../src/env/resolveTarget.ts","../src/env/sandboxKeyName.ts","../src/generated/apiRoutes.ts","../src/generated/client.ts","../src/generated/emit.ts","../src/generated/emitRuntime.ts","../src/generated/features.ts","../src/generated/isLocalWebhookUrl.ts","../src/generated/labels.ts","../src/generated/licenses.ts","../src/generated/lintRules.ts","../src/generated/lintRuntime.ts","../src/generated/mappingAssignments.ts","../src/generated/plans.ts","../src/generated/referralPrograms.ts","../src/generated/rewards.ts","../src/generated/sandboxName.ts","../src/generated/settings.ts","../src/generated/skills.ts","../src/generated/variants.ts","../src/generated/webhooks.ts","../src/generated/wire.ts","../src/http/autumnFetch.ts","../src/project/chooseConfigDir.ts","../src/project/resolveProject.ts","../src/project/rootMarker.ts","../src/prompt/prompt.ts","../src/prompt/select.ts","../src/render/renderEnv.ts","../src/render/renderPreview.ts","../src/render/renderSandboxes.ts","../src/render/renderWebhooks.ts","../src/render/stripTerminalControls.ts","../src/repo/findRepoRoot.ts","../src/surgery/appendPropertyEdit.ts","../src/surgery/appendToArray.ts","../src/surgery/appendToBinding.ts","../src/surgery/appendToCollection.ts","../src/surgery/appendToFixtureArray.ts","../src/surgery/arrayBinding.ts","../src/surgery/deleteFixtureLiteral.ts","../src/surgery/deleteReference.ts","../src/surgery/ensureBuilderImport.ts","../src/surgery/findFixture.ts","../src/surgery/fixtureEdit.ts","../src/surgery/fixtureLocation.ts","../src/surgery/insertCollection.ts","../src/surgery/insertFirstProperty.ts","../src/surgery/patchFixturePaths.ts","../src/surgery/patchFixtureProperty.ts","../src/surgery/patchSingletonProperty.ts","../src/surgery/readFixtureValue.ts","../src/surgery/replaceFixture.ts","../src/surgery/setFixtureProperty.ts","../src/surgery/staticFixtureRule.ts","../test/apiCommand.test.ts","../test/authorizationUrl.test.ts","../test/autumnFetch.test.ts","../test/backfill.test.ts","../test/backfillInlineVariants.test.ts","../test/backfillSlug.test.ts","../test/backfillVariants.test.ts","../test/chooseConfigDir.test.ts","../test/cliTargetFlags.test.ts","../test/client.test.ts","../test/configPackageName.test.ts","../test/emitFixture.test.ts","../test/env.test.ts","../test/envWriter.test.ts","../test/ignoreBundledSkills.test.ts","../test/init.test.ts","../test/keyless.test.ts","../test/legacyConfig.test.ts","../test/lintDocument.test.ts","../test/loadConfigNode.test.ts","../test/loadConfigV2.test.ts","../test/locateFixtureUnique.test.ts","../test/login.test.ts","../test/nodeNextTypes.test.ts","../test/patchFixturePaths.test.ts","../test/plans.test.ts","../test/previewIsEmpty.test.ts","../test/prompt.test.ts","../test/pull.test.ts","../test/pullByReference.test.ts","../test/pullFieldLevel.test.ts","../test/pullMappings.test.ts","../test/pullOmitDefaults.test.ts","../test/pullPlans.test.ts","../test/pullStatesSlugs.test.ts","../test/pullTwoCollections.test.ts","../test/readFixtureValue.test.ts","../test/renameHint.test.ts","../test/renderPreview.test.ts","../test/renderSandboxes.test.ts","../test/renderSettings.test.ts","../test/reset.test.ts","../test/resolveCollectionTarget.test.ts","../test/resolveProject.test.ts","../test/resolveTarget.test.ts","../test/sandboxActions.test.ts","../test/sandboxKeyName.test.ts","../test/sandboxUse.test.ts","../test/settingsActions.test.ts","../test/settingsWire.test.ts","../test/skills.test.ts","../test/watchLauncher.test.ts","../test/wireDocument.test.ts","../test/helpers/targetFor.ts","../test/surgery/appendToCollection.test.ts","../test/surgery/deleteFixtureLiteral.test.ts","../test/surgery/deleteReference.test.ts","../test/surgery/findFixture.test.ts","../test/surgery/fixtureLocation.test.ts","../test/surgery/insertCollection.test.ts","../test/surgery/insertFirstProperty.test.ts","../test/surgery/patchFixtureProperty.test.ts","../test/surgery/patchSingletonProperty.test.ts","../test/surgery/replaceFixture.test.ts","../test/surgery/staticFixtureRule.test.ts","../test/webhooks/webhooksEnv.test.ts","../test/webhooks/webhooksLint.test.ts","../test/webhooks/webhooksPull.test.ts","../test/webhooks/webhooksPush.test.ts"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atmn",
3
- "version": "2.0.32",
3
+ "version": "2.0.34",
4
4
  "license": "MIT",
5
5
  "description": "The CLI for Autumn — define your pricing in code, sync it to Autumn, and keep everything in version control.",
6
6
  "bin": {
@@ -49,5 +49,5 @@
49
49
  "open": "10.2.0",
50
50
  "yaml": "2.9.0"
51
51
  },
52
- "gitHead": "5fcd03a1c30d352578bb087458d1548951c6fdb0"
52
+ "gitHead": "c29d787ec5b5f0c7414a3dca13b36fdcb40b6579"
53
53
  }
@@ -0,0 +1,84 @@
1
+ import { SYNCED_LISTS } from "../../../generated/emit";
2
+ import { emitFixture } from "../../../generated/emitRuntime";
3
+ import { appendToBinding } from "../../../surgery/appendToBinding";
4
+ import { appendToCollection } from "../../../surgery/appendToCollection";
5
+ import { ensureBuilderImport } from "../../../surgery/ensureBuilderImport";
6
+ import {
7
+ insertCollection,
8
+ rootSpreadNames,
9
+ } from "../../../surgery/insertCollection";
10
+ import { resolveCollectionTarget } from "../resolveCollectionTarget";
11
+ import type { PullFiles, RemoteWebhook } from "./types";
12
+
13
+ const SPEC = SYNCED_LISTS.webhooks;
14
+ const COLLECTION = "webhooks";
15
+
16
+ /** The fixture a remote webhook reads as from one env: its url under that env alone. */
17
+ const fixtureRowOf = ({
18
+ webhook,
19
+ envKey,
20
+ }: {
21
+ webhook: RemoteWebhook;
22
+ envKey: string;
23
+ }): Record<string, unknown> => ({
24
+ id: webhook.id,
25
+ url: { [envKey]: webhook.url },
26
+ events: webhook.events,
27
+ ...(webhook.description ? { description: webhook.description } : {}),
28
+ ...(webhook.disabled ? { disabled: true } : {}),
29
+ });
30
+
31
+ /** Rule 1: a remote webhook the config never names is appended for this env. */
32
+ export const appendWebhook = ({
33
+ pull,
34
+ webhook,
35
+ envKey,
36
+ }: {
37
+ pull: PullFiles;
38
+ webhook: RemoteWebhook;
39
+ envKey: string;
40
+ }): string | null => {
41
+ const { configPath, files } = pull;
42
+ const configSource = files.get(configPath) ?? "";
43
+ const withKey = insertCollection({
44
+ source: configSource,
45
+ collection: COLLECTION,
46
+ });
47
+ if (withKey === null)
48
+ return "append to `webhooks` by hand: no atmn({...}) call";
49
+ // A spread may already hold `webhooks`; a second key after it would override it.
50
+ const spreads = rootSpreadNames({ source: configSource });
51
+ if (withKey !== configSource && spreads.length > 0)
52
+ return `append to \`webhooks\` by hand: atmn() spreads \`${spreads.join("`, `")}\`, which may already hold it`;
53
+ files.set(configPath, withKey);
54
+ const target = resolveCollectionTarget({
55
+ configPath,
56
+ files,
57
+ collection: COLLECTION,
58
+ });
59
+ if (target === null)
60
+ return "append to `webhooks` by hand: it is not an array literal";
61
+ const text = (indent: string) =>
62
+ emitFixture({
63
+ spec: SPEC,
64
+ row: fixtureRowOf({ webhook, envKey }),
65
+ includeMappings: false,
66
+ indent,
67
+ });
68
+ const source = files.get(target.file) ?? "";
69
+ const updated =
70
+ target.kind === "inline"
71
+ ? appendToCollection({ source, collection: COLLECTION, text })
72
+ : appendToBinding({ source, name: target.name, text });
73
+ if (updated === null)
74
+ return "append to `webhooks` by hand: it is not an array literal";
75
+ files.set(
76
+ target.file,
77
+ ensureBuilderImport({
78
+ source: updated,
79
+ builder: SPEC.builder,
80
+ collection: COLLECTION,
81
+ }),
82
+ );
83
+ return null;
84
+ };
@@ -0,0 +1,100 @@
1
+ import { isDashboardWebhook } from "../../webhooks/isDashboardWebhook";
2
+ import { newWebhookId } from "../../webhooks/newWebhookId";
3
+ import { appendWebhook } from "./appendWebhook";
4
+ import { removeWebhookEnv } from "./removeWebhookEnv";
5
+ import type {
6
+ PullFiles,
7
+ RemoteWebhook,
8
+ StatedWebhook,
9
+ WebhookEditResult,
10
+ } from "./types";
11
+ import { updateWebhook } from "./updateWebhook";
12
+
13
+ const merge = (target: WebhookEditResult, source: WebhookEditResult): void => {
14
+ target.lines.push(...source.lines);
15
+ target.warnings.push(...source.warnings);
16
+ target.unlocated.push(...source.unlocated);
17
+ };
18
+
19
+ /**
20
+ * One env's webhooks written back into the config. Only `url[envKey]` is
21
+ * touched on the url map; every other env's key, comment and ordering stays.
22
+ * A dashboard webhook is matched to the config by URL, never duplicated: the
23
+ * next push adopts it by that URL, keeping its signing secret.
24
+ */
25
+ export const applyWebhooksPull = ({
26
+ pull,
27
+ remote,
28
+ stated,
29
+ envKey,
30
+ newId = newWebhookId,
31
+ }: {
32
+ pull: PullFiles;
33
+ remote: RemoteWebhook[];
34
+ /** The config's webhooks as evaluated; absent when it states none. */
35
+ stated: StatedWebhook[] | undefined;
36
+ envKey: string;
37
+ /** Mints the id a dashboard webhook is written under. */
38
+ newId?: () => string;
39
+ }): WebhookEditResult => {
40
+ const result: WebhookEditResult = { lines: [], warnings: [], unlocated: [] };
41
+ const statedById = new Map((stated ?? []).map((row) => [row.id, row]));
42
+ // An id the config states is the config's, whatever its shape.
43
+ const fromDashboard = (webhook: RemoteWebhook): boolean =>
44
+ !statedById.has(webhook.id) && isDashboardWebhook(webhook);
45
+ const present = new Set<string>();
46
+
47
+ for (const webhook of remote) {
48
+ if (fromDashboard(webhook)) {
49
+ const represented = (stated ?? []).find(
50
+ (row) => row.url?.[envKey] === webhook.url,
51
+ );
52
+ if (represented !== undefined) {
53
+ present.add(represented.id);
54
+ merge(
55
+ result,
56
+ updateWebhook({
57
+ pull,
58
+ webhook: { ...webhook, id: represented.id },
59
+ stated: represented,
60
+ envKey,
61
+ }),
62
+ );
63
+ continue;
64
+ }
65
+ if (webhook.events.length === 0) {
66
+ result.lines.push(
67
+ `· webhook ${webhook.id} receives every event; give it an event list in the dashboard, or add it to your config, to manage it here`,
68
+ );
69
+ continue;
70
+ }
71
+ const id = newId();
72
+ const failure = appendWebhook({
73
+ pull,
74
+ webhook: { ...webhook, id },
75
+ envKey,
76
+ });
77
+ if (failure === null)
78
+ result.lines.push(
79
+ `+ webhook ${id} (made in the dashboard; push adopts it by URL)`,
80
+ );
81
+ else result.unlocated.push({ id, action: failure });
82
+ continue;
83
+ }
84
+ present.add(webhook.id);
85
+ const row = statedById.get(webhook.id);
86
+ if (row !== undefined) {
87
+ merge(result, updateWebhook({ pull, webhook, stated: row, envKey }));
88
+ continue;
89
+ }
90
+ const failure = appendWebhook({ pull, webhook, envKey });
91
+ if (failure === null) result.lines.push(`+ webhook ${webhook.id}`);
92
+ else result.unlocated.push({ id: webhook.id, action: failure });
93
+ }
94
+
95
+ for (const row of stated ?? []) {
96
+ if (present.has(row.id) || row.url?.[envKey] === undefined) continue;
97
+ merge(result, removeWebhookEnv({ pull, stated: row, envKey }));
98
+ }
99
+ return result;
100
+ };
@@ -0,0 +1,22 @@
1
+ import { SYNCED_LISTS } from "../../../generated/emit";
2
+ import { type LocatedFixture, locateFixture } from "../locateFixture";
3
+ import type { PullFiles } from "./types";
4
+
5
+ const SPEC = SYNCED_LISTS.webhooks;
6
+
7
+ /** A `webhook({...})` call with this id, even when some values are code. */
8
+ export const locateWebhook = ({
9
+ pull,
10
+ id,
11
+ }: {
12
+ pull: PullFiles;
13
+ id: string;
14
+ }): LocatedFixture | null =>
15
+ locateFixture({
16
+ configPath: pull.configPath,
17
+ files: pull.files,
18
+ builder: SPEC.builder,
19
+ idField: SPEC.idField,
20
+ id,
21
+ allowDynamic: true,
22
+ });