atmn 1.1.24 → 2.0.0
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/README.md +120 -0
- package/dist/bin.js +23102 -0
- package/dist/index.js +2906 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -74
- package/src/actions/api/callApi.ts +280 -0
- package/src/actions/api/registerApiCommands.ts +121 -0
- package/src/actions/env/fetchOrgInfo.ts +32 -0
- package/src/actions/env/types/orgInfo.ts +18 -0
- package/src/actions/env.ts +94 -0
- package/src/actions/init/runInit.ts +401 -0
- package/src/actions/login/keyless.ts +135 -0
- package/src/actions/login.ts +144 -0
- package/src/actions/pull/appendPlanVersionFixture.ts +373 -0
- package/src/actions/pull/applyPreview.ts +553 -0
- package/src/actions/pull/applySettingsPreview.ts +114 -0
- package/src/actions/pull/changedFixtureKeys.ts +88 -0
- package/src/actions/pull/listSourceFiles.ts +29 -0
- package/src/actions/pull/locateFixture.ts +78 -0
- package/src/actions/pull/resolveCollectionTarget.ts +198 -0
- package/src/actions/pull/rewriteConfig.ts +57 -0
- package/src/actions/pull/scaffoldConfig.ts +118 -0
- package/src/actions/pull.ts +399 -0
- package/src/actions/push/backfillInternalIds.ts +394 -0
- package/src/actions/push/deprecatedFields.ts +55 -0
- package/src/actions/push.ts +294 -0
- package/src/actions/reset/runReset.ts +58 -0
- package/src/actions/sandbox/createSandbox.ts +83 -0
- package/src/actions/sandbox/deleteSandbox.ts +91 -0
- package/src/actions/sandbox/listSandboxes.ts +28 -0
- package/src/actions/sandbox/types/sandboxClient.ts +16 -0
- package/src/actions/sandbox/useSandbox.ts +157 -0
- package/src/actions/sandbox/withSandboxScopeHint.ts +27 -0
- package/src/actions/skills/skills.ts +246 -0
- package/src/auth/announceAuthorizationUrl.ts +26 -0
- package/src/auth/browser/openSystemBrowser.ts +7 -0
- package/src/auth/browser/tryOpenBrowser.ts +17 -0
- package/src/auth/browser/watchLauncher.ts +46 -0
- package/src/auth/buildAuthorizationUrl.ts +36 -0
- package/src/auth/callbackPages.ts +126 -0
- package/src/auth/createOrgApiKeys.ts +46 -0
- package/src/auth/keyless.ts +119 -0
- package/src/auth/oauthConfig.ts +63 -0
- package/src/auth/runOAuthFlow.ts +230 -0
- package/src/auth/types/browserOpener.ts +5 -0
- package/src/auth/types/impersonationTokens.ts +23 -0
- package/src/auth/types/oauthTokens.ts +13 -0
- package/src/auth/types/orgApiKeys.ts +6 -0
- package/src/bin.ts +9 -0
- package/src/cli.ts +648 -0
- package/src/config/configPackageName.ts +9 -0
- package/src/config/legacyConfig.ts +25 -0
- package/src/config/loadConfig.ts +231 -0
- package/src/env/assertSandboxTarget.ts +20 -0
- package/src/env/loadEnv.ts +184 -0
- package/src/env/resolveTarget.ts +134 -0
- package/src/env/sandboxKeyName.ts +18 -0
- package/src/generated/apiRoutes.ts +3787 -0
- package/src/generated/client.ts +51564 -0
- package/src/generated/emit.ts +1163 -0
- package/src/generated/emitRuntime.ts +522 -0
- package/src/generated/features.ts +146 -0
- package/src/generated/labels.ts +29 -0
- package/src/generated/licenses.ts +287 -0
- package/src/generated/lintRules.ts +2207 -0
- package/src/generated/lintRuntime.ts +865 -0
- package/src/generated/plans.ts +1628 -0
- package/src/generated/referralPrograms.ts +22 -0
- package/src/generated/rewards.ts +58 -0
- package/src/generated/settings.ts +21 -0
- package/src/generated/skills.ts +305 -0
- package/src/generated/variants.ts +934 -0
- package/src/generated/wire.ts +334 -0
- package/src/http/autumnFetch.ts +30 -0
- package/src/index.ts +20 -0
- package/src/project/chooseConfigDir.ts +41 -0
- package/src/project/resolveProject.ts +115 -0
- package/src/project/rootMarker.ts +40 -0
- package/src/prompt/prompt.ts +186 -0
- package/src/prompt/select.ts +162 -0
- package/src/render/renderEnv.ts +77 -0
- package/src/render/renderPreview.ts +945 -0
- package/src/render/renderSandboxes.ts +92 -0
- package/src/render/stripTerminalControls.ts +19 -0
- package/src/repo/findRepoRoot.ts +79 -0
- package/src/surgery/appendPropertyEdit.ts +67 -0
- package/src/surgery/appendToArray.ts +87 -0
- package/src/surgery/appendToBinding.ts +19 -0
- package/src/surgery/appendToCollection.ts +40 -0
- package/src/surgery/appendToFixtureArray.ts +71 -0
- package/src/surgery/arrayBinding.ts +30 -0
- package/src/surgery/deleteFixtureLiteral.ts +81 -0
- package/src/surgery/deleteReference.ts +48 -0
- package/src/surgery/ensureBuilderImport.ts +65 -0
- package/src/surgery/findFixture.ts +238 -0
- package/src/surgery/fixtureEdit.ts +156 -0
- package/src/surgery/fixtureLocation.ts +32 -0
- package/src/surgery/insertCollection.ts +86 -0
- package/src/surgery/insertFirstProperty.ts +73 -0
- package/src/surgery/patchFixtureProperty.ts +152 -0
- package/src/surgery/patchSingletonProperty.ts +221 -0
- package/src/surgery/replaceFixture.ts +28 -0
- package/src/surgery/setFixtureProperty.ts +55 -0
- package/src/surgery/staticFixtureRule.ts +48 -0
- package/src/version.ts +5 -0
- package/dist/cli.js +0 -142410
- package/dist/compose/index.js +0 -122
- package/dist/src/compose/builders/builderFunctions.d.ts +0 -84
- package/dist/src/compose/builders/rewardFunctions.d.ts +0 -5
- package/dist/src/compose/builders/variantFunctions.d.ts +0 -2
- package/dist/src/compose/index.d.ts +0 -19
- package/dist/src/compose/models/featureModels.d.ts +0 -262
- package/dist/src/compose/models/index.d.ts +0 -3
- package/dist/src/compose/models/planModels.d.ts +0 -367
- package/dist/src/compose/models/rewardModels.d.ts +0 -52
- package/dist/src/compose/models/variantModels.d.ts +0 -34
- package/readme.md +0 -186
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import type { SgNode } from "@ast-grep/napi";
|
|
4
|
+
import { COLLECTIONS, NESTED_FIXTURES } from "../../generated/emit";
|
|
5
|
+
import { branchSpecs, resolveBranch } from "../../generated/emitRuntime";
|
|
6
|
+
import type { FixtureShape } from "../../surgery/findFixture";
|
|
7
|
+
import { insertFirstProperty } from "../../surgery/insertFirstProperty";
|
|
8
|
+
import {
|
|
9
|
+
fixturePropertyString,
|
|
10
|
+
fixtureStatesProperty,
|
|
11
|
+
patchFixtureProperty,
|
|
12
|
+
} from "../../surgery/patchFixtureProperty";
|
|
13
|
+
import { setFixtureProperty } from "../../surgery/setFixtureProperty";
|
|
14
|
+
import { listSourceFiles } from "../pull/listSourceFiles";
|
|
15
|
+
import {
|
|
16
|
+
type FixtureConstraint,
|
|
17
|
+
type LocatedFixture,
|
|
18
|
+
locateFixture,
|
|
19
|
+
} from "../pull/locateFixture";
|
|
20
|
+
|
|
21
|
+
export type IdentityRow = {
|
|
22
|
+
id?: string;
|
|
23
|
+
internalId?: string | null;
|
|
24
|
+
versionSlug?: string | null;
|
|
25
|
+
/** A plan's variant edges as `get` returns them: the resolved plan carries the stable id. */
|
|
26
|
+
variants?: VariantEdge[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type VariantEdge = {
|
|
30
|
+
variantPlanId?: string;
|
|
31
|
+
versionSlug?: string | null;
|
|
32
|
+
internalId?: string | null;
|
|
33
|
+
plan?: { internalId?: string | null; versionSlug?: string | null } | null;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** The edge states the identity only on a bare link; otherwise the resolved
|
|
37
|
+
* plan carries it. */
|
|
38
|
+
const variantInternalId = (edge: VariantEdge): string | null | undefined =>
|
|
39
|
+
edge.internalId ?? edge.plan?.internalId;
|
|
40
|
+
|
|
41
|
+
const variantVersionSlug = (edge: VariantEdge): string | null | undefined =>
|
|
42
|
+
edge.versionSlug ?? edge.plan?.versionSlug;
|
|
43
|
+
|
|
44
|
+
/** Push: created features from `results`, every direct plan row in full. */
|
|
45
|
+
export const identityRowsFromApplied = ({
|
|
46
|
+
applied,
|
|
47
|
+
}: {
|
|
48
|
+
applied: { results?: Record<string, unknown> } & Record<string, unknown>;
|
|
49
|
+
}): Record<string, IdentityRow[]> =>
|
|
50
|
+
Object.fromEntries(
|
|
51
|
+
Object.entries(COLLECTIONS).map(([collection, spec]) => {
|
|
52
|
+
const rows = spec.versioned
|
|
53
|
+
? applied[collection]
|
|
54
|
+
: (
|
|
55
|
+
applied.results?.[collection] as { action?: string }[] | undefined
|
|
56
|
+
)?.filter((row) => row.action === "create");
|
|
57
|
+
return [collection, Array.isArray(rows) ? (rows as IdentityRow[]) : []];
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
/** Pull: every row the catalog returned. */
|
|
62
|
+
export const identityRowsFromCatalog = ({
|
|
63
|
+
catalog,
|
|
64
|
+
}: {
|
|
65
|
+
catalog: Record<string, unknown>;
|
|
66
|
+
}): Record<string, IdentityRow[]> =>
|
|
67
|
+
Object.fromEntries(
|
|
68
|
+
Object.keys(COLLECTIONS).map((collection) => {
|
|
69
|
+
const rows = catalog[collection];
|
|
70
|
+
if (!Array.isArray(rows)) return [collection, []];
|
|
71
|
+
// A union row carries its identity inside its branch.
|
|
72
|
+
const spec = COLLECTIONS[collection];
|
|
73
|
+
return [
|
|
74
|
+
collection,
|
|
75
|
+
rows.map((row) =>
|
|
76
|
+
spec === undefined
|
|
77
|
+
? row
|
|
78
|
+
: resolveBranch({
|
|
79
|
+
spec,
|
|
80
|
+
row: row as Record<string, unknown>,
|
|
81
|
+
}).row,
|
|
82
|
+
) as IdentityRow[],
|
|
83
|
+
];
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
/** A property the fixture already states: its literal, or the fact that it is
|
|
88
|
+
* an expression this rewriter must not double up on. */
|
|
89
|
+
type StatedProperty =
|
|
90
|
+
| { kind: "absent" }
|
|
91
|
+
| { kind: "dynamic" }
|
|
92
|
+
| { kind: "literal"; value: string };
|
|
93
|
+
|
|
94
|
+
const statedProperty = ({
|
|
95
|
+
call,
|
|
96
|
+
property,
|
|
97
|
+
}: {
|
|
98
|
+
call: SgNode;
|
|
99
|
+
property: string;
|
|
100
|
+
}): StatedProperty => {
|
|
101
|
+
if (!fixtureStatesProperty({ call, property })) return { kind: "absent" };
|
|
102
|
+
const value = fixturePropertyString({ call, property });
|
|
103
|
+
return value === null ? { kind: "dynamic" } : { kind: "literal", value };
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Every row's stable id is written into its fixture when the fixture lacks one,
|
|
108
|
+
* so the next push addresses the row by identity and a changed public id is a
|
|
109
|
+
* rename rather than a delete and a create. Push feeds it the applied rows,
|
|
110
|
+
* pull the whole catalog.
|
|
111
|
+
*/
|
|
112
|
+
export const backfillInternalIds = ({
|
|
113
|
+
rows,
|
|
114
|
+
configPath,
|
|
115
|
+
}: {
|
|
116
|
+
rows: Record<string, IdentityRow[]>;
|
|
117
|
+
configPath: string;
|
|
118
|
+
}): { backfilled: string[]; slugged: string[] } => {
|
|
119
|
+
const files = new Map<string, string>();
|
|
120
|
+
files.set(configPath, readFileSync(configPath, "utf8"));
|
|
121
|
+
for (const file of listSourceFiles({ directory: dirname(configPath) })) {
|
|
122
|
+
if (!files.has(file)) files.set(file, readFileSync(file, "utf8"));
|
|
123
|
+
}
|
|
124
|
+
const originals = new Map(files);
|
|
125
|
+
const backfilled: string[] = [];
|
|
126
|
+
const slugged: string[] = [];
|
|
127
|
+
|
|
128
|
+
for (const [collection, spec] of Object.entries(COLLECTIONS)) {
|
|
129
|
+
const collectionRows = rows[collection] ?? [];
|
|
130
|
+
for (const row of collectionRows) {
|
|
131
|
+
if (typeof row.id !== "string" || typeof row.internalId !== "string")
|
|
132
|
+
continue;
|
|
133
|
+
// A union row could be written with any of the branch builders.
|
|
134
|
+
let rowSpec = spec;
|
|
135
|
+
let located: ReturnType<typeof locateFixture> = null;
|
|
136
|
+
for (const candidate of branchSpecs({ spec })) {
|
|
137
|
+
located = locateFixture({
|
|
138
|
+
configPath,
|
|
139
|
+
files,
|
|
140
|
+
builder: candidate.builder,
|
|
141
|
+
idField: candidate.idField,
|
|
142
|
+
id: row.id,
|
|
143
|
+
where: candidate.versioned
|
|
144
|
+
? [
|
|
145
|
+
{
|
|
146
|
+
field: "versionSlug",
|
|
147
|
+
equals: row.versionSlug ?? "v1",
|
|
148
|
+
absentMeans: "v1",
|
|
149
|
+
},
|
|
150
|
+
]
|
|
151
|
+
: undefined,
|
|
152
|
+
allowDynamic: true,
|
|
153
|
+
});
|
|
154
|
+
if (located !== null) {
|
|
155
|
+
rowSpec = candidate;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// No literal to write into: the row still matches by public id next push.
|
|
160
|
+
if (located === null) continue;
|
|
161
|
+
const stated = statedProperty({
|
|
162
|
+
call: located.node,
|
|
163
|
+
property: "internalId",
|
|
164
|
+
});
|
|
165
|
+
// An expression the fixture computes would override an inserted pair.
|
|
166
|
+
if (stated.kind === "dynamic") continue;
|
|
167
|
+
if (stated.kind === "literal" && stated.value === row.internalId)
|
|
168
|
+
continue;
|
|
169
|
+
// A stated id the server did not know was ignored and the row minted
|
|
170
|
+
// fresh, so the fixture takes the real id in place of the guess.
|
|
171
|
+
const updated =
|
|
172
|
+
stated.kind === "absent"
|
|
173
|
+
? insertFirstProperty({
|
|
174
|
+
source: located.source,
|
|
175
|
+
builder: rowSpec.builder,
|
|
176
|
+
idField: located.idField,
|
|
177
|
+
id: located.id,
|
|
178
|
+
where: located.where,
|
|
179
|
+
property: `internalId: ${JSON.stringify(row.internalId)}`,
|
|
180
|
+
})
|
|
181
|
+
: setFixtureProperty({
|
|
182
|
+
source: located.source,
|
|
183
|
+
builder: rowSpec.builder,
|
|
184
|
+
idField: located.idField,
|
|
185
|
+
id: located.id,
|
|
186
|
+
where: located.where,
|
|
187
|
+
property: "internalId",
|
|
188
|
+
value: row.internalId,
|
|
189
|
+
});
|
|
190
|
+
if (updated === null) continue;
|
|
191
|
+
files.set(located.file, updated);
|
|
192
|
+
backfilled.push(row.id);
|
|
193
|
+
}
|
|
194
|
+
// The slug travels with the id: a fixture that never stated one takes the
|
|
195
|
+
// server's, so a nuke-and-repush or a sandbox-to-prod push keeps its names.
|
|
196
|
+
if (spec.versioned) {
|
|
197
|
+
for (const row of collectionRows) {
|
|
198
|
+
if (
|
|
199
|
+
typeof row.internalId !== "string" ||
|
|
200
|
+
typeof row.versionSlug !== "string"
|
|
201
|
+
)
|
|
202
|
+
continue;
|
|
203
|
+
const located = locateFixture({
|
|
204
|
+
configPath,
|
|
205
|
+
files,
|
|
206
|
+
builder: spec.builder,
|
|
207
|
+
idField: spec.idField,
|
|
208
|
+
id: typeof row.id === "string" ? row.id : "",
|
|
209
|
+
internalId: row.internalId,
|
|
210
|
+
allowDynamic: true,
|
|
211
|
+
});
|
|
212
|
+
if (located === null) continue;
|
|
213
|
+
// A slug the fixture already states, literal or computed, is its own.
|
|
214
|
+
if (
|
|
215
|
+
fixtureStatesProperty({
|
|
216
|
+
call: located.node,
|
|
217
|
+
property: "versionSlug",
|
|
218
|
+
})
|
|
219
|
+
)
|
|
220
|
+
continue;
|
|
221
|
+
const updated = patchFixtureProperty({
|
|
222
|
+
source: located.source,
|
|
223
|
+
builder: spec.builder,
|
|
224
|
+
idField: located.idField,
|
|
225
|
+
id: located.id,
|
|
226
|
+
where: located.where,
|
|
227
|
+
property: "versionSlug",
|
|
228
|
+
text: JSON.stringify(row.versionSlug),
|
|
229
|
+
});
|
|
230
|
+
if (updated === null) continue;
|
|
231
|
+
files.set(located.file, updated);
|
|
232
|
+
if (typeof row.id === "string") slugged.push(row.id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// A variant is a `variant({...})` fixture of its own, or an object literal
|
|
238
|
+
// inline under its base plan's `variants`; either takes its id and slug.
|
|
239
|
+
// The inline lookup is scoped to the base row the edge came from, so two
|
|
240
|
+
// versions each holding a slug-less `{ variantPlanId }` get their own ids.
|
|
241
|
+
const variantSpec = NESTED_FIXTURES.variants;
|
|
242
|
+
const parentSpec = COLLECTIONS[variantSpec.parent];
|
|
243
|
+
const variantShapesUnder = (row: IdentityRow): FixtureShape[] => [
|
|
244
|
+
variantSpec.builder,
|
|
245
|
+
{
|
|
246
|
+
parentBuilder: parentSpec?.builder ?? "plan",
|
|
247
|
+
arrayProperty: variantSpec.path,
|
|
248
|
+
...(typeof row.id === "string"
|
|
249
|
+
? {
|
|
250
|
+
parent: {
|
|
251
|
+
...(typeof row.internalId === "string"
|
|
252
|
+
? { internalId: row.internalId }
|
|
253
|
+
: {}),
|
|
254
|
+
idField: parentSpec?.idField ?? "planId",
|
|
255
|
+
id: row.id,
|
|
256
|
+
where: [
|
|
257
|
+
{
|
|
258
|
+
field: "versionSlug",
|
|
259
|
+
equals: row.versionSlug ?? "v1",
|
|
260
|
+
absentMeans: "v1",
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
: {}),
|
|
266
|
+
},
|
|
267
|
+
];
|
|
268
|
+
const locateVariant = ({
|
|
269
|
+
row,
|
|
270
|
+
variantPlanId,
|
|
271
|
+
internalId,
|
|
272
|
+
where,
|
|
273
|
+
}: {
|
|
274
|
+
row: IdentityRow;
|
|
275
|
+
variantPlanId: string;
|
|
276
|
+
internalId?: string;
|
|
277
|
+
where?: FixtureConstraint[];
|
|
278
|
+
}): LocatedFixture | null => {
|
|
279
|
+
const [, inlineUnderBase] = variantShapesUnder(row);
|
|
280
|
+
const located = locateFixture({
|
|
281
|
+
configPath,
|
|
282
|
+
files,
|
|
283
|
+
builder: variantShapesUnder(row),
|
|
284
|
+
idField: variantSpec.idField,
|
|
285
|
+
id: variantPlanId,
|
|
286
|
+
internalId,
|
|
287
|
+
where,
|
|
288
|
+
allowDynamic: true,
|
|
289
|
+
});
|
|
290
|
+
if (located !== null || inlineUnderBase === undefined) return located;
|
|
291
|
+
// One base declares a variant once, so under it the slug is redundant:
|
|
292
|
+
// a slug-less inline entry belongs to whatever row its base links.
|
|
293
|
+
return locateFixture({
|
|
294
|
+
configPath,
|
|
295
|
+
files,
|
|
296
|
+
builder: [inlineUnderBase],
|
|
297
|
+
idField: variantSpec.idField,
|
|
298
|
+
id: variantPlanId,
|
|
299
|
+
internalId,
|
|
300
|
+
allowDynamic: true,
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
for (const row of rows.plans ?? []) {
|
|
304
|
+
for (const edge of row.variants ?? []) {
|
|
305
|
+
const internalId = variantInternalId(edge);
|
|
306
|
+
const versionSlug = variantVersionSlug(edge);
|
|
307
|
+
if (
|
|
308
|
+
typeof edge.variantPlanId !== "string" ||
|
|
309
|
+
typeof internalId !== "string"
|
|
310
|
+
)
|
|
311
|
+
continue;
|
|
312
|
+
const located = locateVariant({
|
|
313
|
+
row,
|
|
314
|
+
variantPlanId: edge.variantPlanId,
|
|
315
|
+
internalId,
|
|
316
|
+
// Versions of one variant share the id; the slug tells them apart
|
|
317
|
+
// whenever the catalog states one.
|
|
318
|
+
...(typeof versionSlug === "string"
|
|
319
|
+
? {
|
|
320
|
+
where: [
|
|
321
|
+
{
|
|
322
|
+
field: "versionSlug",
|
|
323
|
+
equals: versionSlug,
|
|
324
|
+
absentMeans: "v1",
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
}
|
|
328
|
+
: {}),
|
|
329
|
+
});
|
|
330
|
+
if (located === null) continue;
|
|
331
|
+
const stated = statedProperty({
|
|
332
|
+
call: located.node,
|
|
333
|
+
property: "internalId",
|
|
334
|
+
});
|
|
335
|
+
if (stated.kind === "dynamic") continue;
|
|
336
|
+
if (stated.kind !== "literal" || stated.value !== internalId) {
|
|
337
|
+
const updated =
|
|
338
|
+
stated.kind === "absent"
|
|
339
|
+
? insertFirstProperty({
|
|
340
|
+
source: located.source,
|
|
341
|
+
builder: located.builder,
|
|
342
|
+
idField: located.idField,
|
|
343
|
+
id: located.id,
|
|
344
|
+
where: located.where,
|
|
345
|
+
property: `internalId: ${JSON.stringify(internalId)}`,
|
|
346
|
+
})
|
|
347
|
+
: setFixtureProperty({
|
|
348
|
+
source: located.source,
|
|
349
|
+
builder: located.builder,
|
|
350
|
+
idField: located.idField,
|
|
351
|
+
id: located.id,
|
|
352
|
+
where: located.where,
|
|
353
|
+
property: "internalId",
|
|
354
|
+
value: internalId,
|
|
355
|
+
});
|
|
356
|
+
if (updated === null) continue;
|
|
357
|
+
files.set(located.file, updated);
|
|
358
|
+
backfilled.push(edge.variantPlanId);
|
|
359
|
+
}
|
|
360
|
+
if (typeof versionSlug !== "string") continue;
|
|
361
|
+
// The id is on the literal now, so the slug lookup addresses it by that.
|
|
362
|
+
const identified = locateVariant({
|
|
363
|
+
row,
|
|
364
|
+
variantPlanId: edge.variantPlanId,
|
|
365
|
+
internalId,
|
|
366
|
+
});
|
|
367
|
+
if (identified === null) continue;
|
|
368
|
+
if (
|
|
369
|
+
fixtureStatesProperty({
|
|
370
|
+
call: identified.node,
|
|
371
|
+
property: "versionSlug",
|
|
372
|
+
})
|
|
373
|
+
)
|
|
374
|
+
continue;
|
|
375
|
+
const withSlug = patchFixtureProperty({
|
|
376
|
+
source: identified.source,
|
|
377
|
+
builder: identified.builder,
|
|
378
|
+
idField: identified.idField,
|
|
379
|
+
id: identified.id,
|
|
380
|
+
where: identified.where,
|
|
381
|
+
property: "versionSlug",
|
|
382
|
+
text: JSON.stringify(versionSlug),
|
|
383
|
+
});
|
|
384
|
+
if (withSlug === null) continue;
|
|
385
|
+
files.set(identified.file, withSlug);
|
|
386
|
+
slugged.push(edge.variantPlanId);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
for (const [file, source] of files) {
|
|
391
|
+
if (source !== originals.get(file)) writeFileSync(file, source, "utf8");
|
|
392
|
+
}
|
|
393
|
+
return { backfilled, slugged };
|
|
394
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { COLLECTIONS } from "../../generated/emit";
|
|
2
|
+
|
|
3
|
+
export type DeprecatedUse = {
|
|
4
|
+
collection: string;
|
|
5
|
+
id: string;
|
|
6
|
+
path: string;
|
|
7
|
+
reason: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const holdsPath = (value: unknown, segments: string[]): boolean => {
|
|
11
|
+
if (segments.length === 0) return value !== undefined && value !== null;
|
|
12
|
+
if (Array.isArray(value))
|
|
13
|
+
return value.some((entry) => holdsPath(entry, segments));
|
|
14
|
+
if (value === null || typeof value !== "object") return false;
|
|
15
|
+
const [head, ...rest] = segments;
|
|
16
|
+
return holdsPath((value as Record<string, unknown>)[head], rest);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** Every deprecated field a wire document still states, so push can say so
|
|
20
|
+
* before the preview: the field keeps working, and nobody new should copy it. */
|
|
21
|
+
export const deprecatedUsesIn = ({
|
|
22
|
+
wire,
|
|
23
|
+
}: {
|
|
24
|
+
wire: Record<string, unknown>;
|
|
25
|
+
}): DeprecatedUse[] =>
|
|
26
|
+
Object.entries(COLLECTIONS).flatMap(([collection, spec]) => {
|
|
27
|
+
const rows = wire[collection];
|
|
28
|
+
if (!Array.isArray(rows)) return [];
|
|
29
|
+
const idKey = spec.idField.replace(
|
|
30
|
+
/[A-Z]/g,
|
|
31
|
+
(letter) => `_${letter.toLowerCase()}`,
|
|
32
|
+
);
|
|
33
|
+
return (spec.deprecated ?? []).flatMap(({ path, reason }) =>
|
|
34
|
+
rows.flatMap((row) => {
|
|
35
|
+
if (row === null || typeof row !== "object") return [];
|
|
36
|
+
if (!holdsPath(row, path.split("."))) return [];
|
|
37
|
+
const id = (row as Record<string, unknown>)[idKey];
|
|
38
|
+
return [
|
|
39
|
+
{ collection, id: typeof id === "string" ? id : "?", path, reason },
|
|
40
|
+
];
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const renderDeprecatedUses = ({
|
|
46
|
+
uses,
|
|
47
|
+
}: {
|
|
48
|
+
uses: DeprecatedUse[];
|
|
49
|
+
}): string =>
|
|
50
|
+
uses
|
|
51
|
+
.map(
|
|
52
|
+
(use) =>
|
|
53
|
+
`note: ${use.collection} "${use.id}" states deprecated \`${use.path}\`. ${use.reason}`,
|
|
54
|
+
)
|
|
55
|
+
.join("\n");
|