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.
- package/dist/bin.js +1408 -188
- package/dist/index.js +220 -19
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/actions/pull/webhooks/appendWebhook.ts +84 -0
- package/src/actions/pull/webhooks/applyWebhooksPull.ts +100 -0
- package/src/actions/pull/webhooks/locateWebhook.ts +22 -0
- package/src/actions/pull/webhooks/removeWebhookEnv.ts +141 -0
- package/src/actions/pull/webhooks/types.ts +20 -0
- package/src/actions/pull/webhooks/updateWebhook.ts +138 -0
- package/src/actions/pull.ts +32 -2
- package/src/actions/push/pushLanesError.ts +47 -0
- package/src/actions/push.ts +220 -60
- package/src/actions/webhooks/applyWebhooks.ts +52 -0
- package/src/actions/webhooks/isDashboardWebhook.ts +9 -0
- package/src/actions/webhooks/newWebhookId.ts +4 -0
- package/src/actions/webhooks/previewWebhooks.ts +40 -0
- package/src/actions/webhooks/resolveWebhookEnv.ts +34 -0
- package/src/actions/webhooks/resolveWebhooksForEnv.ts +29 -0
- package/src/actions/webhooks/types/webhookEnv.ts +9 -0
- package/src/actions/webhooks/webhookSecretName.ts +22 -0
- package/src/actions/webhooks/writeWebhookSecrets.ts +55 -0
- package/src/cli.ts +20 -1
- package/src/config/loadConfig.ts +3 -0
- package/src/generated/apiRoutes.ts +165 -1
- package/src/generated/client.ts +239 -1
- package/src/generated/emit.ts +20 -1
- package/src/generated/emitRuntime.ts +8 -0
- package/src/generated/isLocalWebhookUrl.ts +64 -0
- package/src/generated/lintRules.ts +95 -0
- package/src/generated/lintRuntime.ts +113 -10
- package/src/generated/sandboxName.ts +52 -0
- package/src/generated/skills.ts +10 -10
- package/src/generated/webhooks.ts +36 -0
- package/src/generated/wire.ts +32 -5
- package/src/index.ts +1 -0
- package/src/render/renderPreview.ts +17 -2
- package/src/render/renderWebhooks.ts +92 -0
- package/src/surgery/appendPropertyEdit.ts +9 -3
- package/src/surgery/patchFixturePaths.ts +57 -5
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { PreviewSyncWebhooksResponse } from "../generated/client";
|
|
3
|
+
|
|
4
|
+
type WebhookChange = PreviewSyncWebhooksResponse["changes"][number];
|
|
5
|
+
type WebhookState = Extract<WebhookChange, { action: "create" }>["webhook"];
|
|
6
|
+
|
|
7
|
+
/** One push's webhook lane: the server's diff for the target env. */
|
|
8
|
+
export type WebhooksPreview = {
|
|
9
|
+
changes: WebhookChange[];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const webhooksHaveWork = ({
|
|
13
|
+
webhooks,
|
|
14
|
+
}: {
|
|
15
|
+
webhooks: WebhooksPreview | undefined;
|
|
16
|
+
}): boolean =>
|
|
17
|
+
(webhooks?.changes ?? []).some((change) => change.action !== "unmanaged");
|
|
18
|
+
|
|
19
|
+
const DETAIL_INDENT = " ";
|
|
20
|
+
|
|
21
|
+
const list = (values: readonly string[]): string =>
|
|
22
|
+
values.length === 0 ? "(none)" : values.join(", ");
|
|
23
|
+
|
|
24
|
+
/** Every field that moved, one line each; the URL first, since it is the one that bites. */
|
|
25
|
+
const updateDetails = ({
|
|
26
|
+
id,
|
|
27
|
+
before,
|
|
28
|
+
after,
|
|
29
|
+
}: {
|
|
30
|
+
id: string;
|
|
31
|
+
before: WebhookState;
|
|
32
|
+
after: WebhookState;
|
|
33
|
+
}): string[] => {
|
|
34
|
+
const lines: string[] = [];
|
|
35
|
+
if (before.url !== after.url)
|
|
36
|
+
lines.push(`${id} url: ${before.url} → ${after.url}`);
|
|
37
|
+
const events = (state: WebhookState) => [...state.events].sort();
|
|
38
|
+
if (events(before).join() !== events(after).join())
|
|
39
|
+
lines.push(`events: ${list(events(before))} → ${list(events(after))}`);
|
|
40
|
+
if ((before.description ?? "") !== (after.description ?? ""))
|
|
41
|
+
lines.push(
|
|
42
|
+
`description: ${JSON.stringify(before.description ?? "")} → ${JSON.stringify(after.description ?? "")}`,
|
|
43
|
+
);
|
|
44
|
+
if (before.disabled !== after.disabled)
|
|
45
|
+
lines.push(`disabled: ${before.disabled} → ${after.disabled}`);
|
|
46
|
+
return lines.map((line) => `${DETAIL_INDENT}${chalk.yellow(line)}`);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const renderChange = ({ change }: { change: WebhookChange }): string[] => {
|
|
50
|
+
if (change.action === "create")
|
|
51
|
+
return [
|
|
52
|
+
` ${chalk.green(`+ ${change.id}`)}${chalk.dim(` ${change.webhook.url}`)}`,
|
|
53
|
+
`${DETAIL_INDENT}${chalk.dim(`events: ${list(change.webhook.events)}`)}`,
|
|
54
|
+
];
|
|
55
|
+
if (change.action === "adopt")
|
|
56
|
+
return [
|
|
57
|
+
` ${chalk.yellow(`~ ${change.id}`)} ${chalk.bold("adopt")} ${chalk.dim("existing dashboard webhook · signing secret unchanged")}`,
|
|
58
|
+
...updateDetails({
|
|
59
|
+
id: change.id,
|
|
60
|
+
before: change.before,
|
|
61
|
+
after: change.after,
|
|
62
|
+
}),
|
|
63
|
+
];
|
|
64
|
+
if (change.action === "update")
|
|
65
|
+
return [
|
|
66
|
+
` ${chalk.yellow(`~ ${change.id}`)}`,
|
|
67
|
+
...updateDetails({
|
|
68
|
+
id: change.id,
|
|
69
|
+
before: change.before,
|
|
70
|
+
after: change.after,
|
|
71
|
+
}),
|
|
72
|
+
];
|
|
73
|
+
return [
|
|
74
|
+
chalk.dim(
|
|
75
|
+
` · ${change.id} unmanaged (not in your config; atmn leaves it alone)`,
|
|
76
|
+
),
|
|
77
|
+
];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Null when the lane has nothing to say, so push prints no empty heading. */
|
|
81
|
+
export const renderWebhooks = ({
|
|
82
|
+
webhooks,
|
|
83
|
+
}: {
|
|
84
|
+
webhooks: WebhooksPreview;
|
|
85
|
+
}): string | null => {
|
|
86
|
+
const rows = webhooks.changes.flatMap((change) => renderChange({ change }));
|
|
87
|
+
if (rows.length === 0) return null;
|
|
88
|
+
const count = webhooks.changes.filter(
|
|
89
|
+
(change) => change.action !== "unmanaged",
|
|
90
|
+
).length;
|
|
91
|
+
return [chalk.bold(`Webhooks (${count})`), ...rows].join("\n");
|
|
92
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Edit, SgNode } from "@ast-grep/napi";
|
|
2
|
-
import { leadingIndentOfLine, lineStartOf } from "./fixtureEdit";
|
|
2
|
+
import { leadingIndentOfLine, lineEndOf, lineStartOf } from "./fixtureEdit";
|
|
3
3
|
|
|
4
4
|
const isMember = (child: SgNode): boolean =>
|
|
5
5
|
child.kind() === "pair" ||
|
|
@@ -49,9 +49,15 @@ export const appendPropertyEdit = ({
|
|
|
49
49
|
// Anchored to the last member itself, so a value whose closing brace shares
|
|
50
50
|
// the object's closing line can never push the insert outside the literal.
|
|
51
51
|
const indent = leadingIndentOfLine(source, last.range().start.index);
|
|
52
|
+
// A trailing `// comment` belongs to the member it follows: insert after it.
|
|
53
|
+
const restOfLine = source.slice(afterComma, lineEndOf(source, afterComma));
|
|
54
|
+
const at =
|
|
55
|
+
trailingComma && /^\s*\/\/.*$/.test(restOfLine)
|
|
56
|
+
? afterComma + restOfLine.length
|
|
57
|
+
: afterComma;
|
|
52
58
|
return {
|
|
53
|
-
startPos:
|
|
54
|
-
endPos:
|
|
59
|
+
startPos: at,
|
|
60
|
+
endPos: at,
|
|
55
61
|
insertedText: trailingComma
|
|
56
62
|
? `\n${indent}${pair},`
|
|
57
63
|
: `,\n${indent}${pair},`,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
findFixtures,
|
|
8
8
|
fixtureObjectOf,
|
|
9
9
|
} from "./findFixture";
|
|
10
|
+
import { lineEndOf, lineStartOf } from "./fixtureEdit";
|
|
10
11
|
import {
|
|
11
12
|
DYNAMIC_VALUE,
|
|
12
13
|
fixtureArrayElements,
|
|
@@ -47,7 +48,48 @@ const matchesLiteral = ({
|
|
|
47
48
|
return !isDynamic(proposed) && isDeepStrictEqual(value, proposed);
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
const
|
|
51
|
+
const PLAIN_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
52
|
+
|
|
53
|
+
/** Inserted keys are quoted unless the caller asks for identifiers where they are valid. */
|
|
54
|
+
const keyText = ({
|
|
55
|
+
key,
|
|
56
|
+
bareKeys,
|
|
57
|
+
}: {
|
|
58
|
+
key: string;
|
|
59
|
+
bareKeys: boolean;
|
|
60
|
+
}): string =>
|
|
61
|
+
bareKeys && PLAIN_IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
62
|
+
|
|
63
|
+
/** A member alone on its line goes with the whole line, its own trailing comment included. */
|
|
64
|
+
const wholeLineEdit = ({
|
|
65
|
+
source,
|
|
66
|
+
member,
|
|
67
|
+
comma,
|
|
68
|
+
}: {
|
|
69
|
+
source: string;
|
|
70
|
+
member: SgNode;
|
|
71
|
+
comma: SgNode | null;
|
|
72
|
+
}): Edit | null => {
|
|
73
|
+
const start = member.range().start.index;
|
|
74
|
+
const lineStart = lineStartOf(source, start);
|
|
75
|
+
if (source.slice(lineStart, start).trim() !== "") return null;
|
|
76
|
+
const end = Math.max(member.range().end.index, comma?.range().end.index ?? 0);
|
|
77
|
+
const lineEnd = lineEndOf(source, end);
|
|
78
|
+
if (!/^\s*(\/\/.*)?$/.test(source.slice(end, lineEnd))) return null;
|
|
79
|
+
return {
|
|
80
|
+
startPos: lineStart,
|
|
81
|
+
endPos: Math.min(lineEnd + 1, source.length),
|
|
82
|
+
insertedText: "",
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const removeMemberEdits = ({
|
|
87
|
+
source,
|
|
88
|
+
member,
|
|
89
|
+
}: {
|
|
90
|
+
source: string;
|
|
91
|
+
member: SgNode;
|
|
92
|
+
}): Edit[] => {
|
|
51
93
|
const siblings = member.parent()?.children() ?? [];
|
|
52
94
|
const index = siblings.findIndex(
|
|
53
95
|
(sibling) => sibling.range().start.index === member.range().start.index,
|
|
@@ -61,6 +103,11 @@ const removeMemberEdits = ({ member }: { member: SgNode }): Edit[] => {
|
|
|
61
103
|
.find((sibling) => sibling.kind() !== "comment");
|
|
62
104
|
const comma =
|
|
63
105
|
next?.kind() === "," ? next : previous?.kind() === "," ? previous : null;
|
|
106
|
+
const line =
|
|
107
|
+
comma === next || comma === null
|
|
108
|
+
? wholeLineEdit({ source, member, comma })
|
|
109
|
+
: null;
|
|
110
|
+
if (line !== null) return [line];
|
|
64
111
|
return [member, ...(comma === null ? [] : [comma])].map((node) => ({
|
|
65
112
|
startPos: node.range().start.index,
|
|
66
113
|
endPos: node.range().end.index,
|
|
@@ -74,10 +121,12 @@ const pathEdits = ({
|
|
|
74
121
|
path,
|
|
75
122
|
text,
|
|
76
123
|
builders,
|
|
124
|
+
bareKeys,
|
|
77
125
|
}: {
|
|
78
126
|
source: string;
|
|
79
127
|
object: SgNode;
|
|
80
128
|
builders: readonly string[];
|
|
129
|
+
bareKeys: boolean;
|
|
81
130
|
} & Assignment): Edit[] | string => {
|
|
82
131
|
let current = object;
|
|
83
132
|
for (let index = 0; index < path.length; index += 1) {
|
|
@@ -124,8 +173,8 @@ const pathEdits = ({
|
|
|
124
173
|
return "missing-array-element";
|
|
125
174
|
let inserted = text;
|
|
126
175
|
for (const key of remaining.reverse())
|
|
127
|
-
inserted = `{ ${
|
|
128
|
-
const pair = `${
|
|
176
|
+
inserted = `{ ${keyText({ key: String(key), bareKeys })}: ${inserted} }`;
|
|
177
|
+
const pair = `${keyText({ key: segment, bareKeys })}: ${inserted}`;
|
|
129
178
|
const members = current
|
|
130
179
|
.namedChildren()
|
|
131
180
|
.filter((child) => child.kind() !== "comment");
|
|
@@ -149,7 +198,7 @@ const pathEdits = ({
|
|
|
149
198
|
if (text === null)
|
|
150
199
|
return member === undefined
|
|
151
200
|
? "array-element-removal"
|
|
152
|
-
: removeMemberEdits({ member });
|
|
201
|
+
: removeMemberEdits({ source, member });
|
|
153
202
|
if (matchesLiteral({ value: currentValue, text })) return [];
|
|
154
203
|
return [
|
|
155
204
|
{
|
|
@@ -170,6 +219,7 @@ export const patchFixturePaths = ({
|
|
|
170
219
|
where,
|
|
171
220
|
assignments,
|
|
172
221
|
builders = [],
|
|
222
|
+
bareKeys = false,
|
|
173
223
|
}: {
|
|
174
224
|
source: string;
|
|
175
225
|
builder: FixtureShape;
|
|
@@ -178,6 +228,8 @@ export const patchFixturePaths = ({
|
|
|
178
228
|
where?: FixtureConstraint[];
|
|
179
229
|
assignments: Assignment[];
|
|
180
230
|
builders?: readonly string[];
|
|
231
|
+
/** Write an inserted key bare when it is a valid identifier. */
|
|
232
|
+
bareKeys?: boolean;
|
|
181
233
|
}): PatchResult | null => {
|
|
182
234
|
const selector = { builder, idField, id, where, allowDynamic: true };
|
|
183
235
|
const initial = findFixtures({ source, ...selector });
|
|
@@ -193,7 +245,7 @@ export const patchFixturePaths = ({
|
|
|
193
245
|
const edits =
|
|
194
246
|
object === null
|
|
195
247
|
? "ambiguous-fixture"
|
|
196
|
-
: pathEdits({ source, object, builders, ...assignment });
|
|
248
|
+
: pathEdits({ source, object, builders, bareKeys, ...assignment });
|
|
197
249
|
if (typeof edits === "string") {
|
|
198
250
|
skipped.push({ path: assignment.path, reason: edits });
|
|
199
251
|
continue;
|