atmn 2.0.42 → 2.0.44
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 +360 -368
- package/dist/index.js +21 -19
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/actions/pull/webhooks/appendWebhook.ts +4 -3
- package/src/actions/pull/webhooks/applyWebhooksPull.ts +27 -98
- 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 +32 -36
- 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/emit.ts +4 -4
- package/src/generated/emitRuntime.ts +3 -3
- package/src/generated/lintRules.ts +17 -18
- package/src/generated/lintRuntime.ts +10 -0
- package/src/generated/skills.ts +10 -10
- package/src/generated/webhooks.ts +5 -7
- package/src/generated/wire.ts +0 -1
- package/src/render/renderPreview.ts +25 -9
- package/src/render/renderWebhooks.ts +8 -2
- 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,25 +2234,38 @@ var LINT_RULES = {
|
|
|
2234
2234
|
webhooks: {
|
|
2235
2235
|
label: "webhook",
|
|
2236
2236
|
idField: "id",
|
|
2237
|
-
required: ["id", "url"],
|
|
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
|
+
},
|
|
2244
|
+
env: {
|
|
2245
|
+
pattern: "^[a-z0-9_-]+$"
|
|
2246
|
+
},
|
|
2247
|
+
url: {
|
|
2248
|
+
pattern: "^[Hh][Tt][Tt][Pp][Ss]:\\/\\/"
|
|
2243
2249
|
}
|
|
2244
2250
|
},
|
|
2245
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
|
+
},
|
|
2246
2257
|
{
|
|
2247
2258
|
kind: "unique",
|
|
2248
2259
|
field: "id",
|
|
2249
|
-
|
|
2260
|
+
alongside: "env",
|
|
2261
|
+
because: "Two webhooks claiming one id in one env race to define the same endpoint."
|
|
2250
2262
|
},
|
|
2251
2263
|
{
|
|
2252
2264
|
kind: "unique",
|
|
2253
2265
|
field: "id",
|
|
2266
|
+
alongside: "env",
|
|
2254
2267
|
asEnvName: true,
|
|
2255
|
-
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."
|
|
2256
2269
|
},
|
|
2257
2270
|
{
|
|
2258
2271
|
kind: "rejects",
|
|
@@ -2266,12 +2279,6 @@ var LINT_RULES = {
|
|
|
2266
2279
|
prefix: "vercel.",
|
|
2267
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."
|
|
2268
2281
|
},
|
|
2269
|
-
{
|
|
2270
|
-
kind: "nonEmpty",
|
|
2271
|
-
field: "url",
|
|
2272
|
-
warning: true,
|
|
2273
|
-
because: "With no environment key the webhook is registered nowhere. Add `live`, `sandbox` or a sandbox's slug."
|
|
2274
|
-
},
|
|
2275
2282
|
{
|
|
2276
2283
|
kind: "knownValues",
|
|
2277
2284
|
field: "events",
|
|
@@ -2293,14 +2300,6 @@ var LINT_RULES = {
|
|
|
2293
2300
|
because: "isn't known to this atmn version; the server will check it."
|
|
2294
2301
|
}
|
|
2295
2302
|
]
|
|
2296
|
-
},
|
|
2297
|
-
"webhooks.url": {
|
|
2298
|
-
keys: {
|
|
2299
|
-
pattern: "^[a-z0-9_-]+$"
|
|
2300
|
-
},
|
|
2301
|
-
values: {
|
|
2302
|
-
pattern: "^[Hh][Tt][Tt][Pp][Ss]:\\/\\/"
|
|
2303
|
-
}
|
|
2304
2303
|
}
|
|
2305
2304
|
};
|
|
2306
2305
|
|
|
@@ -2521,6 +2520,10 @@ var entryRuleFailures = ({
|
|
|
2521
2520
|
return [];
|
|
2522
2521
|
return value.filter((item) => typeof item === "string" && !rule.values.includes(item)).map((item) => `\`${item}\` ${rule.because}`);
|
|
2523
2522
|
}
|
|
2523
|
+
case "notMap": {
|
|
2524
|
+
const value = entry[rule.field];
|
|
2525
|
+
return isEntry(value) ? [`${rule.field} is a map. ${rule.because}`] : [];
|
|
2526
|
+
}
|
|
2524
2527
|
case "uniformPrefix": {
|
|
2525
2528
|
const value = entry[rule.field];
|
|
2526
2529
|
if (!Array.isArray(value))
|
|
@@ -3011,8 +3014,7 @@ var CATALOG_HINTS = hintsOf({
|
|
|
3011
3014
|
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.creditSchema.multipliers",
|
|
3012
3015
|
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.creditSchema.multipliers.*.match",
|
|
3013
3016
|
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.modelMarkups",
|
|
3014
|
-
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.providerMarkups"
|
|
3015
|
-
"webhooks.url"
|
|
3017
|
+
"plans.variants.customize.upsertLicenses.customize.addItems.featureOverride.markups.providerMarkups"
|
|
3016
3018
|
],
|
|
3017
3019
|
frozenPaths: [
|
|
3018
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.44",
|
|
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": "d55d450eb83ed6ab89420f720e58f06e35a2b52b"
|
|
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,14 +22,15 @@ const fixtureRowOf = ({
|
|
|
22
22
|
envKey: string;
|
|
23
23
|
}): Record<string, unknown> => ({
|
|
24
24
|
id: webhook.id,
|
|
25
|
-
|
|
25
|
+
[SPEC.envField]: envKey,
|
|
26
|
+
url: webhook.url,
|
|
26
27
|
// No list is every event; `events` is left out rather than written as `[]`.
|
|
27
28
|
...(webhook.events.length > 0 ? { events: webhook.events } : {}),
|
|
28
29
|
...(webhook.description ? { description: webhook.description } : {}),
|
|
29
30
|
...(webhook.disabled ? { disabled: true } : {}),
|
|
30
31
|
});
|
|
31
32
|
|
|
32
|
-
/**
|
|
33
|
+
/** An endpoint the config never names is appended as this env's entry. */
|
|
33
34
|
export const appendWebhook = ({
|
|
34
35
|
pull,
|
|
35
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,26 +8,17 @@ 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
|
-
|
|
20
|
+
env: envKey,
|
|
21
|
+
url: webhook.url,
|
|
33
22
|
events: webhook.events.length > 0 ? webhook.events : undefined,
|
|
34
23
|
description: webhook.description,
|
|
35
24
|
disabled: webhook.disabled,
|
|
@@ -42,112 +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
|
-
const id = newId();
|
|
114
|
-
const failure = appendWebhook({
|
|
115
|
-
pull,
|
|
116
|
-
webhook: { ...webhook, id },
|
|
117
|
-
envKey,
|
|
118
|
-
});
|
|
119
|
-
if (failure === null) {
|
|
120
|
-
next.set(
|
|
121
|
-
id,
|
|
122
|
-
statedFrom({ row: undefined, webhook: { ...webhook, id }, envKey }),
|
|
123
|
-
);
|
|
124
|
-
result.lines.push(
|
|
125
|
-
`+ webhook ${id} (made in the dashboard; push adopts it by URL)`,
|
|
126
|
-
);
|
|
127
|
-
} else result.unlocated.push({ id, action: failure });
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
present.add(webhook.id);
|
|
131
|
-
const row = statedById.get(webhook.id);
|
|
132
|
-
next.set(webhook.id, statedFrom({ row, webhook, envKey }));
|
|
58
|
+
const row = inEnv.get(webhook.id);
|
|
133
59
|
if (row !== undefined) {
|
|
134
60
|
merge(result, updateWebhook({ pull, webhook, stated: row, envKey }));
|
|
61
|
+
next.push(statedFrom({ webhook, envKey }));
|
|
135
62
|
continue;
|
|
136
63
|
}
|
|
137
64
|
const failure = appendWebhook({ pull, webhook, envKey });
|
|
138
|
-
if (failure
|
|
139
|
-
|
|
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 }));
|
|
140
71
|
}
|
|
141
72
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 });
|
|
145
77
|
merge(result, removed);
|
|
146
|
-
//
|
|
147
|
-
if (removed.lines.length === 0)
|
|
148
|
-
const { [envKey]: _removed, ...url } = row.url;
|
|
149
|
-
if (Object.keys(url).length === 0) next.delete(row.id);
|
|
150
|
-
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);
|
|
151
80
|
}
|
|
152
|
-
return { ...result, stated:
|
|
81
|
+
return { ...result, stated: next };
|
|
153
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,
|
|
@@ -47,8 +47,8 @@ const fieldAssignments = ({
|
|
|
47
47
|
return assignments;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
/**
|
|
51
|
-
|
|
50
|
+
/** What the server holds against what the code evaluates to. */
|
|
51
|
+
const nonLiteralUrlWarning = ({
|
|
52
52
|
id,
|
|
53
53
|
envKey,
|
|
54
54
|
server,
|
|
@@ -56,22 +56,21 @@ export const nonLiteralUrlWarning = ({
|
|
|
56
56
|
}: {
|
|
57
57
|
id: string;
|
|
58
58
|
envKey: string;
|
|
59
|
-
server: string
|
|
59
|
+
server: string;
|
|
60
60
|
config: unknown;
|
|
61
61
|
}): string => {
|
|
62
62
|
const pad = " ".repeat(id.length + 4);
|
|
63
63
|
return [
|
|
64
|
-
`⚠ ${id}
|
|
65
|
-
`${pad}server: ${server
|
|
64
|
+
`⚠ ${id} (${envKey}) url isn't a plain string, so pull left it untouched`,
|
|
65
|
+
`${pad}server: ${server}`,
|
|
66
66
|
`${pad}your config: ${typeof config === "string" ? config : "(no value)"}`,
|
|
67
67
|
`${pad}Update it by hand, or make it a string and pull will manage it.`,
|
|
68
68
|
].join("\n");
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* 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.
|
|
75
74
|
*/
|
|
76
75
|
export const updateWebhook = ({
|
|
77
76
|
pull,
|
|
@@ -85,53 +84,50 @@ export const updateWebhook = ({
|
|
|
85
84
|
envKey: string;
|
|
86
85
|
}): WebhookEditResult => {
|
|
87
86
|
const result: WebhookEditResult = { lines: [], warnings: [], unlocated: [] };
|
|
88
|
-
const
|
|
89
|
-
|
|
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) {
|
|
90
105
|
result.unlocated.push({
|
|
91
106
|
id: webhook.id,
|
|
92
|
-
action:
|
|
93
|
-
});
|
|
94
|
-
return result;
|
|
95
|
-
}
|
|
96
|
-
const patched = patchFixturePaths({
|
|
97
|
-
source: located.source,
|
|
98
|
-
builder: SPEC.builder,
|
|
99
|
-
idField: SPEC.idField,
|
|
100
|
-
id: webhook.id,
|
|
101
|
-
assignments: [
|
|
102
|
-
{ path: ["url", envKey], text: JSON.stringify(webhook.url) },
|
|
103
|
-
...fieldAssignments({ webhook, stated }),
|
|
104
|
-
],
|
|
105
|
-
bareKeys: true,
|
|
106
|
-
});
|
|
107
|
-
if (patched === null) {
|
|
108
|
-
result.unlocated.push({
|
|
109
|
-
id: webhook.id,
|
|
110
|
-
action: "update from the server by hand",
|
|
107
|
+
action: `update the ${envKey} webhook from the server by hand`,
|
|
111
108
|
});
|
|
112
109
|
return result;
|
|
113
110
|
}
|
|
114
111
|
for (const { path } of patched.skipped) {
|
|
115
112
|
const field = path.join(".");
|
|
116
|
-
if (field !==
|
|
113
|
+
if (field !== "url") {
|
|
117
114
|
result.warnings.push(
|
|
118
|
-
`⚠ ${
|
|
115
|
+
`⚠ ${label} ${field} isn't a plain literal, so pull left it untouched`,
|
|
119
116
|
);
|
|
120
117
|
continue;
|
|
121
118
|
}
|
|
122
|
-
|
|
123
|
-
if (config === webhook.url) continue;
|
|
119
|
+
if (stated.url === webhook.url) continue;
|
|
124
120
|
result.warnings.push(
|
|
125
121
|
nonLiteralUrlWarning({
|
|
126
122
|
id: webhook.id,
|
|
127
123
|
envKey,
|
|
128
124
|
server: webhook.url,
|
|
129
|
-
config,
|
|
125
|
+
config: stated.url,
|
|
130
126
|
}),
|
|
131
127
|
);
|
|
132
128
|
}
|
|
133
129
|
if (patched.source === located.source) return result;
|
|
134
130
|
pull.files.set(located.file, patched.source);
|
|
135
|
-
result.lines.push(`~ webhook ${
|
|
131
|
+
result.lines.push(`~ webhook ${label}`);
|
|
136
132
|
return result;
|
|
137
133
|
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AutumnApiError } from "../../../generated/client";
|
|
2
|
+
import { ForeignOrgKeyError, type WebhookPullEnv } from "./webhookPullEnvs";
|
|
3
|
+
|
|
4
|
+
/** Why a failure skips its env; null when it should fail the command. */
|
|
5
|
+
const skipReason = (error: unknown): string | null => {
|
|
6
|
+
if (error instanceof ForeignOrgKeyError) return "belongs to another org";
|
|
7
|
+
if (!(error instanceof AutumnApiError)) return null;
|
|
8
|
+
if (error.status === 401 || error.status === 403) return "was rejected";
|
|
9
|
+
if (error.status !== 404) return null;
|
|
10
|
+
const { message } = (error.body ?? {}) as { message?: unknown };
|
|
11
|
+
return `failed: ${typeof message === "string" ? message : error.status}`;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A rejected key, one for another org, or a 404 (a deleted sandbox) skips its
|
|
16
|
+
* env with this warning; anything else is a real failure.
|
|
17
|
+
*/
|
|
18
|
+
export const webhookEnvSkipWarning = ({
|
|
19
|
+
env,
|
|
20
|
+
error,
|
|
21
|
+
}: {
|
|
22
|
+
env: Pick<WebhookPullEnv, "label" | "keyName">;
|
|
23
|
+
error: unknown;
|
|
24
|
+
}): string | undefined => {
|
|
25
|
+
const reason = skipReason(error);
|
|
26
|
+
return reason === null
|
|
27
|
+
? undefined
|
|
28
|
+
: `⚠ webhooks: skipped ${env.label} (${env.keyName} ${reason})`;
|
|
29
|
+
};
|