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.
- package/dist/bin.js +405 -392
- package/dist/index.js +20 -21
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/actions/pull/webhooks/appendWebhook.ts +6 -4
- package/src/actions/pull/webhooks/applyWebhooksPull.ts +28 -105
- package/src/actions/pull/webhooks/deleteWebhook.ts +59 -0
- package/src/actions/pull/webhooks/locateWebhook.ts +9 -1
- package/src/actions/pull/webhooks/readWebhookEnvs.ts +7 -12
- package/src/actions/pull/webhooks/types.ts +3 -2
- package/src/actions/pull/webhooks/updateWebhook.ts +37 -42
- package/src/actions/pull/webhooks/webhookEnvSkipWarning.ts +29 -0
- package/src/actions/pull/webhooks/webhookPullEnvs.ts +5 -5
- package/src/actions/pull.ts +1 -1
- package/src/actions/push.ts +30 -12
- package/src/actions/webhooks/applyWebhooks.ts +41 -10
- package/src/actions/webhooks/previewWebhookEnvs.ts +213 -0
- package/src/actions/webhooks/resolveWebhooksForEnv.ts +5 -16
- package/src/actions/webhooks/types/webhookEnv.ts +1 -3
- package/src/actions/webhooks/webhookEnvFailures.ts +27 -0
- package/src/actions/webhooks/webhookSecretName.ts +10 -9
- package/src/actions/webhooks/writeWebhookSecrets.ts +5 -2
- package/src/auth/runOAuthFlow.ts +1 -1
- package/src/cli.ts +23 -25
- package/src/generated/apiRoutes.ts +43 -12
- package/src/generated/client.ts +10 -10
- package/src/generated/emit.ts +4 -4
- package/src/generated/emitRuntime.ts +3 -3
- package/src/generated/lintRules.ts +16 -20
- package/src/generated/lintRuntime.ts +10 -0
- package/src/generated/skills.ts +10 -10
- package/src/generated/webhooks.ts +7 -9
- package/src/generated/wire.ts +0 -1
- package/src/render/renderPreview.ts +25 -9
- package/src/render/renderWebhooks.ts +9 -3
- package/src/actions/pull/webhooks/removeWebhookEnv.ts +0 -141
- package/src/actions/webhooks/isDashboardWebhook.ts +0 -9
- package/src/actions/webhooks/newWebhookId.ts +0 -4
- package/src/actions/webhooks/previewWebhooks.ts +0 -40
- package/src/actions/webhooks/resolveWebhookEnv.ts +0 -34
package/dist/index.js
CHANGED
|
@@ -2234,28 +2234,38 @@ var LINT_RULES = {
|
|
|
2234
2234
|
webhooks: {
|
|
2235
2235
|
label: "webhook",
|
|
2236
2236
|
idField: "id",
|
|
2237
|
-
required: ["
|
|
2237
|
+
required: ["env", "id", "url"],
|
|
2238
2238
|
fields: {
|
|
2239
2239
|
id: {
|
|
2240
2240
|
minLength: 1,
|
|
2241
2241
|
maxLength: 256,
|
|
2242
2242
|
pattern: "^[a-zA-Z0-9_-]+$"
|
|
2243
2243
|
},
|
|
2244
|
-
|
|
2245
|
-
|
|
2244
|
+
env: {
|
|
2245
|
+
pattern: "^[a-z0-9_-]+$"
|
|
2246
|
+
},
|
|
2247
|
+
url: {
|
|
2248
|
+
pattern: "^[Hh][Tt][Tt][Pp][Ss]:\\/\\/"
|
|
2246
2249
|
}
|
|
2247
2250
|
},
|
|
2248
2251
|
rules: [
|
|
2252
|
+
{
|
|
2253
|
+
kind: "notMap",
|
|
2254
|
+
field: "url",
|
|
2255
|
+
because: 'Each webhook() is one endpoint in one env: split into one webhook() per env, e.g. webhook({ id, env: "live", url }).'
|
|
2256
|
+
},
|
|
2249
2257
|
{
|
|
2250
2258
|
kind: "unique",
|
|
2251
2259
|
field: "id",
|
|
2252
|
-
|
|
2260
|
+
alongside: "env",
|
|
2261
|
+
because: "Two webhooks claiming one id in one env race to define the same endpoint."
|
|
2253
2262
|
},
|
|
2254
2263
|
{
|
|
2255
2264
|
kind: "unique",
|
|
2256
2265
|
field: "id",
|
|
2266
|
+
alongside: "env",
|
|
2257
2267
|
asEnvName: true,
|
|
2258
|
-
because: "Each webhook's signing secret is saved
|
|
2268
|
+
because: "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."
|
|
2259
2269
|
},
|
|
2260
2270
|
{
|
|
2261
2271
|
kind: "rejects",
|
|
@@ -2269,12 +2279,6 @@ var LINT_RULES = {
|
|
|
2269
2279
|
prefix: "vercel.",
|
|
2270
2280
|
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
2281
|
},
|
|
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
2282
|
{
|
|
2279
2283
|
kind: "knownValues",
|
|
2280
2284
|
field: "events",
|
|
@@ -2296,14 +2300,6 @@ var LINT_RULES = {
|
|
|
2296
2300
|
because: "isn't known to this atmn version; the server will check it."
|
|
2297
2301
|
}
|
|
2298
2302
|
]
|
|
2299
|
-
},
|
|
2300
|
-
"webhooks.url": {
|
|
2301
|
-
keys: {
|
|
2302
|
-
pattern: "^[a-z0-9_-]+$"
|
|
2303
|
-
},
|
|
2304
|
-
values: {
|
|
2305
|
-
pattern: "^[Hh][Tt][Tt][Pp][Ss]:\\/\\/"
|
|
2306
|
-
}
|
|
2307
2303
|
}
|
|
2308
2304
|
};
|
|
2309
2305
|
|
|
@@ -2524,6 +2520,10 @@ var entryRuleFailures = ({
|
|
|
2524
2520
|
return [];
|
|
2525
2521
|
return value.filter((item) => typeof item === "string" && !rule.values.includes(item)).map((item) => `\`${item}\` ${rule.because}`);
|
|
2526
2522
|
}
|
|
2523
|
+
case "notMap": {
|
|
2524
|
+
const value = entry[rule.field];
|
|
2525
|
+
return isEntry(value) ? [`${rule.field} is a map. ${rule.because}`] : [];
|
|
2526
|
+
}
|
|
2527
2527
|
case "uniformPrefix": {
|
|
2528
2528
|
const value = entry[rule.field];
|
|
2529
2529
|
if (!Array.isArray(value))
|
|
@@ -3014,8 +3014,7 @@ var CATALOG_HINTS = hintsOf({
|
|
|
3014
3014
|
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.creditSchema.multipliers",
|
|
3015
3015
|
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.creditSchema.multipliers.*.match",
|
|
3016
3016
|
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.modelMarkups",
|
|
3017
|
-
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.providerMarkups"
|
|
3018
|
-
"webhooks.url"
|
|
3017
|
+
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.providerMarkups"
|
|
3019
3018
|
],
|
|
3020
3019
|
frozenPaths: [
|
|
3021
3020
|
"plans.licenses.metadata",
|
|
@@ -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/pull/webhooks/appendWebhook.ts","../src/actions/pull/webhooks/applyWebhooksPull.ts","../src/actions/pull/webhooks/
|
|
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/deleteWebhook.ts","../src/actions/pull/webhooks/locateWebhook.ts","../src/actions/pull/webhooks/readWebhookEnvs.ts","../src/actions/pull/webhooks/types.ts","../src/actions/pull/webhooks/updateWebhook.ts","../src/actions/pull/webhooks/webhookEnvSkipWarning.ts","../src/actions/pull/webhooks/webhookPullEnvs.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/previewWebhookEnvs.ts","../src/actions/webhooks/resolveWebhooksForEnv.ts","../src/actions/webhooks/webhookEnvFailures.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/webhooksPullEnvs.test.ts","../test/webhooks/webhooksPush.test.ts","../test/webhooks/webhooksRender.test.ts"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atmn",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.43",
|
|
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": "
|
|
52
|
+
"gitHead": "f52f8ea6a3ce297f99e4ec2cde6218fdcb73f80b"
|
|
53
53
|
}
|
|
@@ -13,7 +13,7 @@ import type { PullFiles, RemoteWebhook } from "./types";
|
|
|
13
13
|
const SPEC = SYNCED_LISTS.webhooks;
|
|
14
14
|
const COLLECTION = "webhooks";
|
|
15
15
|
|
|
16
|
-
/** The fixture
|
|
16
|
+
/** The fixture one env's remote endpoint reads as. */
|
|
17
17
|
const fixtureRowOf = ({
|
|
18
18
|
webhook,
|
|
19
19
|
envKey,
|
|
@@ -22,13 +22,15 @@ const fixtureRowOf = ({
|
|
|
22
22
|
envKey: string;
|
|
23
23
|
}): Record<string, unknown> => ({
|
|
24
24
|
id: webhook.id,
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
[SPEC.envField]: envKey,
|
|
26
|
+
url: webhook.url,
|
|
27
|
+
// No list is every event; `events` is left out rather than written as `[]`.
|
|
28
|
+
...(webhook.events.length > 0 ? { events: webhook.events } : {}),
|
|
27
29
|
...(webhook.description ? { description: webhook.description } : {}),
|
|
28
30
|
...(webhook.disabled ? { disabled: true } : {}),
|
|
29
31
|
});
|
|
30
32
|
|
|
31
|
-
/**
|
|
33
|
+
/** An endpoint the config never names is appended as this env's entry. */
|
|
32
34
|
export const appendWebhook = ({
|
|
33
35
|
pull,
|
|
34
36
|
webhook,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { isDashboardWebhook } from "../../webhooks/isDashboardWebhook";
|
|
2
|
-
import { newWebhookId } from "../../webhooks/newWebhookId";
|
|
3
1
|
import { appendWebhook } from "./appendWebhook";
|
|
4
|
-
import {
|
|
2
|
+
import { deleteWebhook } from "./deleteWebhook";
|
|
5
3
|
import type {
|
|
6
4
|
PullFiles,
|
|
7
5
|
RemoteWebhook,
|
|
@@ -10,27 +8,18 @@ import type {
|
|
|
10
8
|
} from "./types";
|
|
11
9
|
import { updateWebhook } from "./updateWebhook";
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
Array.isArray(events) &&
|
|
15
|
-
events.length > 0 &&
|
|
16
|
-
events.every(
|
|
17
|
-
(event) => typeof event === "string" && event.startsWith("vercel."),
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
/** The config as it reads once this env's remote webhook is written into it. */
|
|
11
|
+
/** The config entry as it reads once this env's endpoint is written into it. */
|
|
21
12
|
const statedFrom = ({
|
|
22
|
-
row,
|
|
23
13
|
webhook,
|
|
24
14
|
envKey,
|
|
25
15
|
}: {
|
|
26
|
-
row: StatedWebhook | undefined;
|
|
27
16
|
webhook: RemoteWebhook;
|
|
28
17
|
envKey: string;
|
|
29
18
|
}): StatedWebhook => ({
|
|
30
|
-
...row,
|
|
31
19
|
id: webhook.id,
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
env: envKey,
|
|
21
|
+
url: webhook.url,
|
|
22
|
+
events: webhook.events.length > 0 ? webhook.events : undefined,
|
|
34
23
|
description: webhook.description,
|
|
35
24
|
disabled: webhook.disabled,
|
|
36
25
|
});
|
|
@@ -42,118 +31,52 @@ const merge = (target: WebhookEditResult, source: WebhookEditResult): void => {
|
|
|
42
31
|
};
|
|
43
32
|
|
|
44
33
|
/**
|
|
45
|
-
* One env's
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* next push adopts it by that URL, keeping its signing secret.
|
|
34
|
+
* One env's endpoints written back into the config, one entry each, found by
|
|
35
|
+
* `(env, id)`. A dashboard endpoint is its own `ep_` id; other envs' entries,
|
|
36
|
+
* comments and ordering stay.
|
|
49
37
|
*/
|
|
50
38
|
export const applyWebhooksPull = ({
|
|
51
39
|
pull,
|
|
52
40
|
remote,
|
|
53
41
|
stated,
|
|
54
42
|
envKey,
|
|
55
|
-
newId = newWebhookId,
|
|
56
43
|
}: {
|
|
57
44
|
pull: PullFiles;
|
|
58
45
|
remote: RemoteWebhook[];
|
|
59
46
|
/** The config's webhooks as evaluated; absent when it states none. */
|
|
60
47
|
stated: StatedWebhook[] | undefined;
|
|
61
48
|
envKey: string;
|
|
62
|
-
/** Mints the id a dashboard webhook is written under. */
|
|
63
|
-
newId?: () => string;
|
|
64
49
|
}): WebhookEditResult & { stated: StatedWebhook[] } => {
|
|
65
50
|
const result: WebhookEditResult = { lines: [], warnings: [], unlocated: [] };
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
!statedById.has(webhook.id) && isDashboardWebhook(webhook);
|
|
72
|
-
const present = new Set<string>();
|
|
51
|
+
const rows = stated ?? [];
|
|
52
|
+
const inEnv = new Map(
|
|
53
|
+
rows.filter((row) => row.env === envKey).map((row) => [row.id, row]),
|
|
54
|
+
);
|
|
55
|
+
const next = rows.filter((row) => row.env !== envKey);
|
|
73
56
|
|
|
74
57
|
for (const webhook of remote) {
|
|
75
|
-
|
|
76
|
-
// Vercel and other webhooks live in separate apps: a URL only
|
|
77
|
-
// matches within one.
|
|
78
|
-
const sameApp = (row: StatedWebhook) =>
|
|
79
|
-
isVercelWebhook(row.events) === isVercelWebhook(webhook.events);
|
|
80
|
-
// Another env's copy of this URL fills in this env's key rather than
|
|
81
|
-
// becoming a second webhook.
|
|
82
|
-
const represented =
|
|
83
|
-
(stated ?? []).find(
|
|
84
|
-
(row) => row.url?.[envKey] === webhook.url && sameApp(row),
|
|
85
|
-
) ??
|
|
86
|
-
(stated ?? []).find(
|
|
87
|
-
(row) =>
|
|
88
|
-
row.url?.[envKey] === undefined &&
|
|
89
|
-
Object.values(row.url ?? {}).includes(webhook.url) &&
|
|
90
|
-
sameApp(row),
|
|
91
|
-
);
|
|
92
|
-
if (represented !== undefined) {
|
|
93
|
-
present.add(represented.id);
|
|
94
|
-
next.set(
|
|
95
|
-
represented.id,
|
|
96
|
-
statedFrom({
|
|
97
|
-
row: represented,
|
|
98
|
-
webhook: { ...webhook, id: represented.id },
|
|
99
|
-
envKey,
|
|
100
|
-
}),
|
|
101
|
-
);
|
|
102
|
-
merge(
|
|
103
|
-
result,
|
|
104
|
-
updateWebhook({
|
|
105
|
-
pull,
|
|
106
|
-
webhook: { ...webhook, id: represented.id },
|
|
107
|
-
stated: represented,
|
|
108
|
-
envKey,
|
|
109
|
-
}),
|
|
110
|
-
);
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (webhook.events.length === 0) {
|
|
114
|
-
result.lines.push(
|
|
115
|
-
`· webhook ${webhook.id} receives every event; give it an event list in the dashboard, or add it to your config, to manage it here`,
|
|
116
|
-
);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
const id = newId();
|
|
120
|
-
const failure = appendWebhook({
|
|
121
|
-
pull,
|
|
122
|
-
webhook: { ...webhook, id },
|
|
123
|
-
envKey,
|
|
124
|
-
});
|
|
125
|
-
if (failure === null) {
|
|
126
|
-
next.set(
|
|
127
|
-
id,
|
|
128
|
-
statedFrom({ row: undefined, webhook: { ...webhook, id }, envKey }),
|
|
129
|
-
);
|
|
130
|
-
result.lines.push(
|
|
131
|
-
`+ webhook ${id} (made in the dashboard; push adopts it by URL)`,
|
|
132
|
-
);
|
|
133
|
-
} else result.unlocated.push({ id, action: failure });
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
present.add(webhook.id);
|
|
137
|
-
const row = statedById.get(webhook.id);
|
|
138
|
-
next.set(webhook.id, statedFrom({ row, webhook, envKey }));
|
|
58
|
+
const row = inEnv.get(webhook.id);
|
|
139
59
|
if (row !== undefined) {
|
|
140
60
|
merge(result, updateWebhook({ pull, webhook, stated: row, envKey }));
|
|
61
|
+
next.push(statedFrom({ webhook, envKey }));
|
|
141
62
|
continue;
|
|
142
63
|
}
|
|
143
64
|
const failure = appendWebhook({ pull, webhook, envKey });
|
|
144
|
-
if (failure
|
|
145
|
-
|
|
65
|
+
if (failure !== null) {
|
|
66
|
+
result.unlocated.push({ id: webhook.id, action: failure });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
result.lines.push(`+ webhook ${webhook.id} (${envKey})`);
|
|
70
|
+
next.push(statedFrom({ webhook, envKey }));
|
|
146
71
|
}
|
|
147
72
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
73
|
+
const remoteIds = new Set(remote.map(({ id }) => id));
|
|
74
|
+
for (const row of inEnv.values()) {
|
|
75
|
+
if (remoteIds.has(row.id)) continue;
|
|
76
|
+
const removed = deleteWebhook({ pull, stated: row, envKey });
|
|
151
77
|
merge(result, removed);
|
|
152
|
-
//
|
|
153
|
-
if (removed.lines.length === 0)
|
|
154
|
-
const { [envKey]: _removed, ...url } = row.url;
|
|
155
|
-
if (Object.keys(url).length === 0) next.delete(row.id);
|
|
156
|
-
else next.set(row.id, { ...row, url });
|
|
78
|
+
// An entry left in the source (unlocated) is still the config's.
|
|
79
|
+
if (removed.lines.length === 0) next.push(row);
|
|
157
80
|
}
|
|
158
|
-
return { ...result, stated:
|
|
81
|
+
return { ...result, stated: next };
|
|
159
82
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { SYNCED_LISTS } from "../../../generated/emit";
|
|
2
|
+
import { deleteFixtureLiteral } from "../../../surgery/deleteFixtureLiteral";
|
|
3
|
+
import { deleteReference } from "../../../surgery/deleteReference";
|
|
4
|
+
import { resolveCollectionTarget } from "../resolveCollectionTarget";
|
|
5
|
+
import { locateWebhook, webhookWhere } from "./locateWebhook";
|
|
6
|
+
import type { PullFiles, StatedWebhook, WebhookEditResult } from "./types";
|
|
7
|
+
|
|
8
|
+
const SPEC = SYNCED_LISTS.webhooks;
|
|
9
|
+
|
|
10
|
+
/** The server has no such endpoint in this env: its entry goes, with any export reference left pointing at it. */
|
|
11
|
+
export const deleteWebhook = ({
|
|
12
|
+
pull,
|
|
13
|
+
stated,
|
|
14
|
+
envKey,
|
|
15
|
+
}: {
|
|
16
|
+
pull: PullFiles;
|
|
17
|
+
stated: StatedWebhook;
|
|
18
|
+
envKey: string;
|
|
19
|
+
}): WebhookEditResult => {
|
|
20
|
+
const result: WebhookEditResult = { lines: [], warnings: [], unlocated: [] };
|
|
21
|
+
const located = locateWebhook({ pull, id: stated.id, envKey });
|
|
22
|
+
const removed =
|
|
23
|
+
located === null
|
|
24
|
+
? null
|
|
25
|
+
: deleteFixtureLiteral({
|
|
26
|
+
source: located.source,
|
|
27
|
+
builder: SPEC.builder,
|
|
28
|
+
idField: SPEC.idField,
|
|
29
|
+
id: stated.id,
|
|
30
|
+
where: webhookWhere({ envKey }),
|
|
31
|
+
});
|
|
32
|
+
if (located === null || removed === null) {
|
|
33
|
+
result.unlocated.push({
|
|
34
|
+
id: stated.id,
|
|
35
|
+
action: `delete the ${envKey} webhook by hand`,
|
|
36
|
+
});
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
pull.files.set(located.file, removed.source);
|
|
40
|
+
if (removed.exportedName !== undefined) {
|
|
41
|
+
const referenceFiles = new Set([pull.configPath]);
|
|
42
|
+
const target = resolveCollectionTarget({
|
|
43
|
+
configPath: pull.configPath,
|
|
44
|
+
files: pull.files,
|
|
45
|
+
collection: "webhooks",
|
|
46
|
+
});
|
|
47
|
+
if (target !== null) referenceFiles.add(target.file);
|
|
48
|
+
for (const file of referenceFiles)
|
|
49
|
+
pull.files.set(
|
|
50
|
+
file,
|
|
51
|
+
deleteReference({
|
|
52
|
+
source: pull.files.get(file) ?? "",
|
|
53
|
+
name: removed.exportedName,
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
result.lines.push(`- webhook ${stated.id} (${envKey})`);
|
|
58
|
+
return result;
|
|
59
|
+
};
|
|
@@ -4,13 +4,15 @@ import type { PullFiles } from "./types";
|
|
|
4
4
|
|
|
5
5
|
const SPEC = SYNCED_LISTS.webhooks;
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/** The `webhook({...})` with this id in this env, even when some values are code. */
|
|
8
8
|
export const locateWebhook = ({
|
|
9
9
|
pull,
|
|
10
10
|
id,
|
|
11
|
+
envKey,
|
|
11
12
|
}: {
|
|
12
13
|
pull: PullFiles;
|
|
13
14
|
id: string;
|
|
15
|
+
envKey: string;
|
|
14
16
|
}): LocatedFixture | null =>
|
|
15
17
|
locateFixture({
|
|
16
18
|
configPath: pull.configPath,
|
|
@@ -18,5 +20,11 @@ export const locateWebhook = ({
|
|
|
18
20
|
builder: SPEC.builder,
|
|
19
21
|
idField: SPEC.idField,
|
|
20
22
|
id,
|
|
23
|
+
where: webhookWhere({ envKey }),
|
|
21
24
|
allowDynamic: true,
|
|
22
25
|
});
|
|
26
|
+
|
|
27
|
+
/** Narrows a lookup of one id to the entry in this env. */
|
|
28
|
+
export const webhookWhere = ({ envKey }: { envKey: string }) => [
|
|
29
|
+
{ field: SPEC.envField, equals: envKey },
|
|
30
|
+
];
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { AutumnApiError } from "../../../generated/client";
|
|
2
1
|
import type { RemoteWebhook } from "./types";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const isRejectedKey = (error: unknown): boolean =>
|
|
6
|
-
error instanceof AutumnApiError &&
|
|
7
|
-
(error.status === 401 || error.status === 403);
|
|
2
|
+
import { webhookEnvSkipWarning } from "./webhookEnvSkipWarning";
|
|
3
|
+
import type { WebhookPullEnv } from "./webhookPullEnvs";
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
|
-
* Lists every env in parallel. A rejected key,
|
|
11
|
-
* its env with one warning;
|
|
6
|
+
* Lists every env in parallel. A rejected key, one for another org, or a 404
|
|
7
|
+
* (a deleted sandbox) skips its env with one warning; anything else fails the pull.
|
|
12
8
|
*/
|
|
13
9
|
export const readWebhookEnvs = async ({
|
|
14
10
|
envs,
|
|
@@ -27,10 +23,9 @@ export const readWebhookEnvs = async ({
|
|
|
27
23
|
]);
|
|
28
24
|
return { envKey, list };
|
|
29
25
|
} catch (error) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return `⚠ webhooks: skipped ${env.label} (${env.keyName} was rejected)`;
|
|
26
|
+
const warning = webhookEnvSkipWarning({ env, error });
|
|
27
|
+
if (warning === undefined) throw error;
|
|
28
|
+
return warning;
|
|
34
29
|
}
|
|
35
30
|
}),
|
|
36
31
|
);
|
|
@@ -2,10 +2,11 @@ import type { ListWebhooksResponse } from "../../../generated/client";
|
|
|
2
2
|
|
|
3
3
|
export type RemoteWebhook = ListWebhooksResponse["list"][number];
|
|
4
4
|
|
|
5
|
-
/** A config webhook as evaluated:
|
|
5
|
+
/** A config webhook as evaluated: one endpoint in one env. */
|
|
6
6
|
export type StatedWebhook = Record<string, unknown> & {
|
|
7
7
|
id: string;
|
|
8
|
-
|
|
8
|
+
env?: unknown;
|
|
9
|
+
url?: unknown;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
/** The in-memory sources a pull edits, plus where the config lives. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { SYNCED_LISTS } from "../../../generated/emit";
|
|
3
3
|
import { patchFixturePaths } from "../../../surgery/patchFixturePaths";
|
|
4
|
-
import { locateWebhook } from "./locateWebhook";
|
|
4
|
+
import { locateWebhook, webhookWhere } from "./locateWebhook";
|
|
5
5
|
import type {
|
|
6
6
|
PullFiles,
|
|
7
7
|
RemoteWebhook,
|
|
@@ -25,8 +25,11 @@ const fieldAssignments = ({
|
|
|
25
25
|
stated: StatedWebhook;
|
|
26
26
|
}): Assignment[] => {
|
|
27
27
|
const assignments: Assignment[] = [];
|
|
28
|
-
//
|
|
29
|
-
if (webhook.events.length
|
|
28
|
+
// No list is every event, which a config states by leaving `events` out.
|
|
29
|
+
if (webhook.events.length === 0) {
|
|
30
|
+
if (stated.events !== undefined)
|
|
31
|
+
assignments.push({ path: ["events"], text: null });
|
|
32
|
+
} else if (!sameEvents(stated.events, webhook.events))
|
|
30
33
|
assignments.push({
|
|
31
34
|
path: ["events"],
|
|
32
35
|
text: `[${webhook.events.map((event) => JSON.stringify(event)).join(", ")}]`,
|
|
@@ -44,8 +47,8 @@ const fieldAssignments = ({
|
|
|
44
47
|
return assignments;
|
|
45
48
|
};
|
|
46
49
|
|
|
47
|
-
/**
|
|
48
|
-
|
|
50
|
+
/** What the server holds against what the code evaluates to. */
|
|
51
|
+
const nonLiteralUrlWarning = ({
|
|
49
52
|
id,
|
|
50
53
|
envKey,
|
|
51
54
|
server,
|
|
@@ -53,22 +56,21 @@ export const nonLiteralUrlWarning = ({
|
|
|
53
56
|
}: {
|
|
54
57
|
id: string;
|
|
55
58
|
envKey: string;
|
|
56
|
-
server: string
|
|
59
|
+
server: string;
|
|
57
60
|
config: unknown;
|
|
58
61
|
}): string => {
|
|
59
62
|
const pad = " ".repeat(id.length + 4);
|
|
60
63
|
return [
|
|
61
|
-
`⚠ ${id}
|
|
62
|
-
`${pad}server: ${server
|
|
64
|
+
`⚠ ${id} (${envKey}) url isn't a plain string, so pull left it untouched`,
|
|
65
|
+
`${pad}server: ${server}`,
|
|
63
66
|
`${pad}your config: ${typeof config === "string" ? config : "(no value)"}`,
|
|
64
67
|
`${pad}Update it by hand, or make it a string and pull will manage it.`,
|
|
65
68
|
].join("\n");
|
|
66
69
|
};
|
|
67
70
|
|
|
68
71
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* value that evaluates differently from the server earns a warning instead.
|
|
72
|
+
* Set a string-literal url and bring the other fields in line with the server.
|
|
73
|
+
* Code is never rewritten; a value that evaluates differently earns a warning.
|
|
72
74
|
*/
|
|
73
75
|
export const updateWebhook = ({
|
|
74
76
|
pull,
|
|
@@ -82,57 +84,50 @@ export const updateWebhook = ({
|
|
|
82
84
|
envKey: string;
|
|
83
85
|
}): WebhookEditResult => {
|
|
84
86
|
const result: WebhookEditResult = { lines: [], warnings: [], unlocated: [] };
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
{ path: ["url", envKey], text: JSON.stringify(webhook.url) },
|
|
104
|
-
...fieldAssignments({ webhook, stated }),
|
|
105
|
-
],
|
|
106
|
-
bareKeys: true,
|
|
107
|
-
});
|
|
108
|
-
if (patched === null) {
|
|
87
|
+
const label = `${webhook.id} (${envKey})`;
|
|
88
|
+
const located = locateWebhook({ pull, id: webhook.id, envKey });
|
|
89
|
+
const patched =
|
|
90
|
+
located === null
|
|
91
|
+
? null
|
|
92
|
+
: patchFixturePaths({
|
|
93
|
+
source: located.source,
|
|
94
|
+
builder: SPEC.builder,
|
|
95
|
+
idField: SPEC.idField,
|
|
96
|
+
id: webhook.id,
|
|
97
|
+
where: webhookWhere({ envKey }),
|
|
98
|
+
assignments: [
|
|
99
|
+
{ path: ["url"], text: JSON.stringify(webhook.url) },
|
|
100
|
+
...fieldAssignments({ webhook, stated }),
|
|
101
|
+
],
|
|
102
|
+
bareKeys: true,
|
|
103
|
+
});
|
|
104
|
+
if (located === null || patched === null) {
|
|
109
105
|
result.unlocated.push({
|
|
110
106
|
id: webhook.id,
|
|
111
|
-
action:
|
|
107
|
+
action: `update the ${envKey} webhook from the server by hand`,
|
|
112
108
|
});
|
|
113
109
|
return result;
|
|
114
110
|
}
|
|
115
111
|
for (const { path } of patched.skipped) {
|
|
116
112
|
const field = path.join(".");
|
|
117
|
-
if (field !==
|
|
113
|
+
if (field !== "url") {
|
|
118
114
|
result.warnings.push(
|
|
119
|
-
`⚠ ${
|
|
115
|
+
`⚠ ${label} ${field} isn't a plain literal, so pull left it untouched`,
|
|
120
116
|
);
|
|
121
117
|
continue;
|
|
122
118
|
}
|
|
123
|
-
|
|
124
|
-
if (config === webhook.url) continue;
|
|
119
|
+
if (stated.url === webhook.url) continue;
|
|
125
120
|
result.warnings.push(
|
|
126
121
|
nonLiteralUrlWarning({
|
|
127
122
|
id: webhook.id,
|
|
128
123
|
envKey,
|
|
129
124
|
server: webhook.url,
|
|
130
|
-
config,
|
|
125
|
+
config: stated.url,
|
|
131
126
|
}),
|
|
132
127
|
);
|
|
133
128
|
}
|
|
134
129
|
if (patched.source === located.source) return result;
|
|
135
130
|
pull.files.set(located.file, patched.source);
|
|
136
|
-
result.lines.push(`~ webhook ${
|
|
131
|
+
result.lines.push(`~ webhook ${label}`);
|
|
137
132
|
return result;
|
|
138
133
|
};
|