atmn 2.0.41 → 2.0.43

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 +405 -392
  2. package/dist/index.js +20 -21
  3. package/dist/tsconfig.tsbuildinfo +1 -1
  4. package/package.json +2 -2
  5. package/src/actions/pull/webhooks/appendWebhook.ts +6 -4
  6. package/src/actions/pull/webhooks/applyWebhooksPull.ts +28 -105
  7. package/src/actions/pull/webhooks/deleteWebhook.ts +59 -0
  8. package/src/actions/pull/webhooks/locateWebhook.ts +9 -1
  9. package/src/actions/pull/webhooks/readWebhookEnvs.ts +7 -12
  10. package/src/actions/pull/webhooks/types.ts +3 -2
  11. package/src/actions/pull/webhooks/updateWebhook.ts +37 -42
  12. package/src/actions/pull/webhooks/webhookEnvSkipWarning.ts +29 -0
  13. package/src/actions/pull/webhooks/webhookPullEnvs.ts +5 -5
  14. package/src/actions/pull.ts +1 -1
  15. package/src/actions/push.ts +30 -12
  16. package/src/actions/webhooks/applyWebhooks.ts +41 -10
  17. package/src/actions/webhooks/previewWebhookEnvs.ts +213 -0
  18. package/src/actions/webhooks/resolveWebhooksForEnv.ts +5 -16
  19. package/src/actions/webhooks/types/webhookEnv.ts +1 -3
  20. package/src/actions/webhooks/webhookEnvFailures.ts +27 -0
  21. package/src/actions/webhooks/webhookSecretName.ts +10 -9
  22. package/src/actions/webhooks/writeWebhookSecrets.ts +5 -2
  23. package/src/auth/runOAuthFlow.ts +1 -1
  24. package/src/cli.ts +23 -25
  25. package/src/generated/apiRoutes.ts +43 -12
  26. package/src/generated/client.ts +10 -10
  27. package/src/generated/emit.ts +4 -4
  28. package/src/generated/emitRuntime.ts +3 -3
  29. package/src/generated/lintRules.ts +16 -20
  30. package/src/generated/lintRuntime.ts +10 -0
  31. package/src/generated/skills.ts +10 -10
  32. package/src/generated/webhooks.ts +7 -9
  33. package/src/generated/wire.ts +0 -1
  34. package/src/render/renderPreview.ts +25 -9
  35. package/src/render/renderWebhooks.ts +9 -3
  36. package/src/actions/pull/webhooks/removeWebhookEnv.ts +0 -141
  37. package/src/actions/webhooks/isDashboardWebhook.ts +0 -9
  38. package/src/actions/webhooks/newWebhookId.ts +0 -4
  39. package/src/actions/webhooks/previewWebhooks.ts +0 -40
  40. package/src/actions/webhooks/resolveWebhookEnv.ts +0 -34
@@ -955,7 +955,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
955
955
  type: "string",
956
956
  required: false,
957
957
  description:
958
- "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges.",
958
+ "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges, 'bill_difference' charges/credits the full-period price difference now without changing the billing cycle.",
959
959
  },
960
960
  {
961
961
  name: "redirect_mode",
@@ -1190,7 +1190,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
1190
1190
  type: "string",
1191
1191
  required: false,
1192
1192
  description:
1193
- "Whether to prorate the immediate phase. 'none' skips proration charges and credits.",
1193
+ "Whether to prorate the immediate phase. 'none' skips proration charges and credits, 'bill_difference' charges/credits the full-period price difference.",
1194
1194
  },
1195
1195
  {
1196
1196
  name: "no_billing_changes",
@@ -1300,7 +1300,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
1300
1300
  type: "string",
1301
1301
  required: false,
1302
1302
  description:
1303
- "How to handle billing. 'prorate_immediately' charges/credits prorated amounts now, 'none' does not charge/credit anything.",
1303
+ "How to handle billing. 'prorate_immediately' charges/credits prorated amounts now, 'none' does not charge/credit anything, 'bill_difference' charges/credits the full-period price difference now without changing the billing cycle.",
1304
1304
  },
1305
1305
  {
1306
1306
  name: "billing_cycle_anchor",
@@ -1420,7 +1420,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
1420
1420
  type: "string",
1421
1421
  required: false,
1422
1422
  description:
1423
- "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges.",
1423
+ "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges, 'bill_difference' charges/credits the full-period price difference now without changing the billing cycle.",
1424
1424
  },
1425
1425
  {
1426
1426
  name: "redirect_mode",
@@ -1648,7 +1648,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
1648
1648
  type: "string",
1649
1649
  required: false,
1650
1650
  description:
1651
- "How to handle billing. 'prorate_immediately' charges/credits prorated amounts now, 'none' does not charge/credit anything.",
1651
+ "How to handle billing. 'prorate_immediately' charges/credits prorated amounts now, 'none' does not charge/credit anything, 'bill_difference' charges/credits the full-period price difference now without changing the billing cycle.",
1652
1652
  },
1653
1653
  {
1654
1654
  name: "billing_cycle_anchor",
@@ -1769,7 +1769,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
1769
1769
  type: "string",
1770
1770
  required: false,
1771
1771
  description:
1772
- "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges.",
1772
+ "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges, 'bill_difference' charges/credits the full-period price difference now without changing the billing cycle.",
1773
1773
  },
1774
1774
  {
1775
1775
  name: "redirect_mode",
@@ -1929,7 +1929,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
1929
1929
  type: "string",
1930
1930
  required: false,
1931
1931
  description:
1932
- "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges.",
1932
+ "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges, 'bill_difference' charges/credits the full-period price difference now without changing the billing cycle.",
1933
1933
  },
1934
1934
  {
1935
1935
  name: "redirect_mode",
@@ -2198,7 +2198,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
2198
2198
  type: "string",
2199
2199
  required: false,
2200
2200
  description:
2201
- "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges.",
2201
+ "How to handle proration when updating an existing subscription. 'prorate_immediately' charges/credits prorated amounts now, 'none' skips creating any charges, 'bill_difference' charges/credits the full-period price difference now without changing the billing cycle.",
2202
2202
  },
2203
2203
  {
2204
2204
  name: "subscription_id",
@@ -2332,6 +2332,37 @@ export const API_ROUTES: readonly ApiRoute[] = [
2332
2332
  },
2333
2333
  ],
2334
2334
  },
2335
+ {
2336
+ group: "billing",
2337
+ method: "verify",
2338
+ path: "/v1/billing.verify",
2339
+ description:
2340
+ "Checks a customer's Stripe subscriptions against Autumn's record of their plans and reports any drift. Read-only: it never changes Autumn or Stripe.",
2341
+ body: "object",
2342
+ fields: [
2343
+ {
2344
+ name: "customer_id",
2345
+ type: "string",
2346
+ required: true,
2347
+ description:
2348
+ "Autumn customer whose Stripe subscriptions should be checked against Autumn's customer_products.",
2349
+ },
2350
+ {
2351
+ name: "subscription_ids",
2352
+ type: "json",
2353
+ required: false,
2354
+ description:
2355
+ "Optional whitelist of Stripe subscription IDs to verify. Defaults to every subscription linked to the customer's plans, plus any other active Stripe subscription on the customer, which is reported as not linked to Autumn.",
2356
+ },
2357
+ {
2358
+ name: "strict",
2359
+ type: "boolean",
2360
+ required: false,
2361
+ description:
2362
+ "When true, report missing usage-based items and unexpected metered Stripe items. Defaults to false.",
2363
+ },
2364
+ ],
2365
+ },
2335
2366
  {
2336
2367
  group: "billing",
2337
2368
  method: "import",
@@ -2873,7 +2904,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
2873
2904
  method: "batch_track",
2874
2905
  path: "/v1/balances.batch_track",
2875
2906
  description:
2876
- "Enqueue up to 1000 usage events for asynchronous processing. Items are validated synchronously up front; validated items are then enqueued via SQS for background deduction by workers. The response returns 202 immediately and does not include balance information. On partial enqueue failure (some items fail to enqueue, others succeed), the endpoint still returns 202 and logs the failures server-side; clients should NOT retry, because retrying re-enqueues the already-succeeded items. A 503 is returned only when zero items were successfully enqueued (queue entirely unavailable) — that case is safe to retry.",
2907
+ "Enqueue up to 1000 usage events for asynchronous processing. Items are validated synchronously up front; validated items are then enqueued via SQS for background deduction by workers. The response returns 200 (or 202) immediately and does not include balance information. On partial enqueue failure (some items fail to enqueue, others succeed), the endpoint still returns 200 (or 202) and logs the failures server-side; clients should NOT retry, because retrying re-enqueues the already-succeeded items. A 503 is returned only when zero items were successfully enqueued (queue entirely unavailable) — that case is safe to retry.",
2877
2908
  body: "array",
2878
2909
  fields: [],
2879
2910
  },
@@ -4140,9 +4171,9 @@ export const API_ROUTES: readonly ApiRoute[] = [
4140
4171
  {
4141
4172
  name: "events",
4142
4173
  type: "json",
4143
- required: true,
4174
+ required: false,
4144
4175
  description:
4145
- "The events sent to this webhook. At least one. `vercel.*` events can't be mixed with other events.",
4176
+ "The events sent to this webhook. Leave it out to send every event. `vercel.*` events can't be mixed with other events.",
4146
4177
  },
4147
4178
  {
4148
4179
  name: "description",
@@ -4211,7 +4242,7 @@ export const API_ROUTES: readonly ApiRoute[] = [
4211
4242
  type: "json",
4212
4243
  required: false,
4213
4244
  description:
4214
- "The events sent to this webhook. At least one. `vercel.*` events can't be mixed with other events.",
4245
+ "The events sent to this webhook. Pass `[]` to send every event; omit it to keep the current list. `vercel.*` events can't be mixed with other events.",
4215
4246
  },
4216
4247
  {
4217
4248
  name: "description",
@@ -97,8 +97,8 @@ webhooks: Array<{
97
97
  id: string;
98
98
  /** The https URL Autumn sends events to. Localhost and private-network addresses are rejected; tunnels such as ngrok work. */
99
99
  url: string;
100
- /** The events sent to this webhook. At least one. `vercel.*` events can't be mixed with other events. */
101
- events: Array<"customer.products.updated" | "customer.threshold_reached" | "balances.usage_alert_triggered" | "balances.limit_reached" | "billing.auto_topup_failed" | "billing.auto_topup_succeeded" | "billing.updated" | "invoice.finalized" | "vercel.resources.deleted" | "vercel.resources.provisioned" | "vercel.resources.rotate_secrets" | "vercel.webhooks.event">;
100
+ /** The events sent to this webhook. Leave it out to send every event. `vercel.*` events can't be mixed with other events. Defaults to []. */
101
+ events?: Array<"customer.products.updated" | "customer.threshold_reached" | "balances.usage_alert_triggered" | "balances.limit_reached" | "billing.auto_topup_failed" | "billing.auto_topup_succeeded" | "billing.updated" | "invoice.finalized" | "vercel.resources.deleted" | "vercel.resources.provisioned" | "vercel.resources.rotate_secrets" | "vercel.webhooks.event">;
102
102
  /** A note for your own reference. */
103
103
  description?: string;
104
104
  /** When true, no events are sent to the webhook. */
@@ -52515,7 +52515,7 @@ id: string;
52515
52515
  url: string;
52516
52516
  /** A note for your own reference. */
52517
52517
  description: string | null;
52518
- /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty only for a webhook made in the dashboard that receives every event. */
52518
+ /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty means the webhook receives every event. */
52519
52519
  events: Array<string>;
52520
52520
  /** When true, no events are sent to the webhook. */
52521
52521
  disabled: boolean;
@@ -52537,7 +52537,7 @@ id: string;
52537
52537
  url: string;
52538
52538
  /** A note for your own reference. */
52539
52539
  description: string | null;
52540
- /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty only for a webhook made in the dashboard that receives every event. */
52540
+ /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty means the webhook receives every event. */
52541
52541
  events: Array<string>;
52542
52542
  /** When true, no events are sent to the webhook. */
52543
52543
  disabled: boolean;
@@ -52556,7 +52556,7 @@ id: string;
52556
52556
  url: string;
52557
52557
  /** A note for your own reference. */
52558
52558
  description: string | null;
52559
- /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty only for a webhook made in the dashboard that receives every event. */
52559
+ /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty means the webhook receives every event. */
52560
52560
  events: Array<string>;
52561
52561
  /** When true, no events are sent to the webhook. */
52562
52562
  disabled: boolean;
@@ -52572,7 +52572,7 @@ id: string;
52572
52572
  url: string;
52573
52573
  /** A note for your own reference. */
52574
52574
  description: string | null;
52575
- /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty only for a webhook made in the dashboard that receives every event. */
52575
+ /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty means the webhook receives every event. */
52576
52576
  events: Array<string>;
52577
52577
  /** When true, no events are sent to the webhook. */
52578
52578
  disabled: boolean;
@@ -52591,7 +52591,7 @@ id: string;
52591
52591
  url: string;
52592
52592
  /** A note for your own reference. */
52593
52593
  description: string | null;
52594
- /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty only for a webhook made in the dashboard that receives every event. */
52594
+ /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty means the webhook receives every event. */
52595
52595
  events: Array<string>;
52596
52596
  /** When true, no events are sent to the webhook. */
52597
52597
  disabled: boolean;
@@ -52607,7 +52607,7 @@ id: string;
52607
52607
  url: string;
52608
52608
  /** A note for your own reference. */
52609
52609
  description: string | null;
52610
- /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty only for a webhook made in the dashboard that receives every event. */
52610
+ /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty means the webhook receives every event. */
52611
52611
  events: Array<string>;
52612
52612
  /** When true, no events are sent to the webhook. */
52613
52613
  disabled: boolean;
@@ -52626,7 +52626,7 @@ id: string;
52626
52626
  url: string;
52627
52627
  /** A note for your own reference. */
52628
52628
  description: string | null;
52629
- /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty only for a webhook made in the dashboard that receives every event. */
52629
+ /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty means the webhook receives every event. */
52630
52630
  events: Array<string>;
52631
52631
  /** When true, no events are sent to the webhook. */
52632
52632
  disabled: boolean;
@@ -52651,7 +52651,7 @@ id: string;
52651
52651
  url: string;
52652
52652
  /** A note for your own reference. */
52653
52653
  description: string | null;
52654
- /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty only for a webhook made in the dashboard that receives every event. */
52654
+ /** The events sent to this webhook, as `WebhookEventType` names; a type newer than your client is returned as-is. Empty means the webhook receives every event. */
52655
52655
  events: Array<string>;
52656
52656
  /** When true, no events are sent to the webhook. */
52657
52657
  disabled: boolean;
@@ -1216,11 +1216,11 @@ export const SYNCED_LISTS: Readonly<Record<string, SyncedListSpec>> = {
1216
1216
  builder: "webhook",
1217
1217
  idField: "id",
1218
1218
  responseIdField: "id",
1219
- keys: ["id", "url", "events", "description", "disabled"],
1220
- required: ["events", "id", "url"],
1221
- paths: ["description", "disabled", "events", "id", "url"],
1219
+ keys: ["id", "env", "url", "events", "description", "disabled"],
1220
+ required: ["env", "id", "url"],
1221
+ paths: ["description", "disabled", "env", "events", "id", "url"],
1222
1222
  pull: true,
1223
- envKeyed: ["url"],
1223
+ envField: "env",
1224
1224
  },
1225
1225
  };
1226
1226
 
@@ -94,11 +94,11 @@ export const branchSpecs = ({
94
94
  spec.branches?.map((branch) => branchSpecOf({ spec, branch })) ?? [spec];
95
95
 
96
96
  /** A list keyed by id outside the catalog: emitted like a collection item,
97
- * with fields a config states once per environment. */
97
+ * one entry per environment. */
98
98
  export type SyncedListSpec = CollectionSpec & {
99
99
  readonly wireKey: string;
100
- /** Fixture fields holding `{ live?, sandbox?, [sandboxSlug]? }` maps. */
101
- readonly envKeyed: readonly string[];
100
+ /** The fixture field naming the entry's environment. */
101
+ readonly envField: string;
102
102
  };
103
103
 
104
104
  export type SingletonSpec = {
@@ -2206,30 +2206,41 @@ export const LINT_RULES: LintRules = {
2206
2206
  webhooks: {
2207
2207
  label: "webhook",
2208
2208
  idField: "id",
2209
- required: ["events", "id", "url"],
2209
+ required: ["env", "id", "url"],
2210
2210
  fields: {
2211
2211
  id: {
2212
2212
  minLength: 1,
2213
2213
  maxLength: 256,
2214
2214
  pattern: "^[a-zA-Z0-9_-]+$",
2215
2215
  },
2216
- events: {
2217
- minItems: 1,
2216
+ env: {
2217
+ pattern: "^[a-z0-9_-]+$",
2218
+ },
2219
+ url: {
2220
+ pattern: "^[Hh][Tt][Tt][Pp][Ss]:\\/\\/",
2218
2221
  },
2219
2222
  },
2220
2223
  rules: [
2224
+ {
2225
+ kind: "notMap",
2226
+ field: "url",
2227
+ because:
2228
+ 'Each webhook() is one endpoint in one env: split into one webhook() per env, e.g. webhook({ id, env: "live", url }).',
2229
+ },
2221
2230
  {
2222
2231
  kind: "unique",
2223
2232
  field: "id",
2233
+ alongside: "env",
2224
2234
  because:
2225
- "Two webhooks claiming one id race to define the same endpoint.",
2235
+ "Two webhooks claiming one id in one env race to define the same endpoint.",
2226
2236
  },
2227
2237
  {
2228
2238
  kind: "unique",
2229
2239
  field: "id",
2240
+ alongside: "env",
2230
2241
  asEnvName: true,
2231
2242
  because:
2232
- "Each webhook's signing secret is saved as AUTUMN_WEBHOOK_<ID>_SECRET, so these two would overwrite each other's secret. Rename one.",
2243
+ "Each webhook's signing secret is saved under its id in an env var name, so these two would overwrite each other's secret. Rename one.",
2233
2244
  },
2234
2245
  {
2235
2246
  kind: "rejects",
@@ -2245,13 +2256,6 @@ export const LINT_RULES: LintRules = {
2245
2256
  because:
2246
2257
  "vercel.* events can't be mixed with other events in one webhook: they're delivered from a separate app. Make one webhook for each.",
2247
2258
  },
2248
- {
2249
- kind: "nonEmpty",
2250
- field: "url",
2251
- warning: true,
2252
- because:
2253
- "With no environment key the webhook is registered nowhere. Add `live`, `sandbox` or a sandbox's slug.",
2254
- },
2255
2259
  {
2256
2260
  kind: "knownValues",
2257
2261
  field: "events",
@@ -2274,12 +2278,4 @@ export const LINT_RULES: LintRules = {
2274
2278
  },
2275
2279
  ],
2276
2280
  },
2277
- "webhooks.url": {
2278
- keys: {
2279
- pattern: "^[a-z0-9_-]+$",
2280
- },
2281
- values: {
2282
- pattern: "^[Hh][Tt][Tt][Pp][Ss]:\\/\\/",
2283
- },
2284
- },
2285
2281
  };
@@ -131,6 +131,12 @@ export type LintRule =
131
131
  readonly warning?: true;
132
132
  readonly because: string;
133
133
  }
134
+ | {
135
+ /** A stated `field` must be a plain value, never an object map. */
136
+ readonly kind: "notMap";
137
+ readonly field: string;
138
+ readonly because: string;
139
+ }
134
140
  | {
135
141
  /** List `field` holds names starting with `prefix` only, or none of them. */
136
142
  readonly kind: "uniformPrefix";
@@ -538,6 +544,10 @@ const entryRuleFailures = ({
538
544
  )
539
545
  .map((item) => `\`${item}\` ${rule.because}`);
540
546
  }
547
+ case "notMap": {
548
+ const value = entry[rule.field];
549
+ return isEntry(value) ? [`${rule.field} is a map. ${rule.because}`] : [];
550
+ }
541
551
  case "uniformPrefix": {
542
552
  const value = entry[rule.field];
543
553
  if (!Array.isArray(value)) return [];