atmn 1.1.25 → 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.
Files changed (117) hide show
  1. package/README.md +120 -0
  2. package/dist/bin.js +23102 -0
  3. package/dist/index.js +2906 -0
  4. package/dist/tsconfig.tsbuildinfo +1 -1
  5. package/package.json +27 -74
  6. package/src/actions/api/callApi.ts +280 -0
  7. package/src/actions/api/registerApiCommands.ts +121 -0
  8. package/src/actions/env/fetchOrgInfo.ts +32 -0
  9. package/src/actions/env/types/orgInfo.ts +18 -0
  10. package/src/actions/env.ts +94 -0
  11. package/src/actions/init/runInit.ts +401 -0
  12. package/src/actions/login/keyless.ts +135 -0
  13. package/src/actions/login.ts +144 -0
  14. package/src/actions/pull/appendPlanVersionFixture.ts +373 -0
  15. package/src/actions/pull/applyPreview.ts +553 -0
  16. package/src/actions/pull/applySettingsPreview.ts +114 -0
  17. package/src/actions/pull/changedFixtureKeys.ts +88 -0
  18. package/src/actions/pull/listSourceFiles.ts +29 -0
  19. package/src/actions/pull/locateFixture.ts +78 -0
  20. package/src/actions/pull/resolveCollectionTarget.ts +198 -0
  21. package/src/actions/pull/rewriteConfig.ts +57 -0
  22. package/src/actions/pull/scaffoldConfig.ts +118 -0
  23. package/src/actions/pull.ts +399 -0
  24. package/src/actions/push/backfillInternalIds.ts +394 -0
  25. package/src/actions/push/deprecatedFields.ts +55 -0
  26. package/src/actions/push.ts +294 -0
  27. package/src/actions/reset/runReset.ts +58 -0
  28. package/src/actions/sandbox/createSandbox.ts +83 -0
  29. package/src/actions/sandbox/deleteSandbox.ts +91 -0
  30. package/src/actions/sandbox/listSandboxes.ts +28 -0
  31. package/src/actions/sandbox/types/sandboxClient.ts +16 -0
  32. package/src/actions/sandbox/useSandbox.ts +157 -0
  33. package/src/actions/sandbox/withSandboxScopeHint.ts +27 -0
  34. package/src/actions/skills/skills.ts +246 -0
  35. package/src/auth/announceAuthorizationUrl.ts +26 -0
  36. package/src/auth/browser/openSystemBrowser.ts +7 -0
  37. package/src/auth/browser/tryOpenBrowser.ts +17 -0
  38. package/src/auth/browser/watchLauncher.ts +46 -0
  39. package/src/auth/buildAuthorizationUrl.ts +36 -0
  40. package/src/auth/callbackPages.ts +126 -0
  41. package/src/auth/createOrgApiKeys.ts +46 -0
  42. package/src/auth/keyless.ts +119 -0
  43. package/src/auth/oauthConfig.ts +63 -0
  44. package/src/auth/runOAuthFlow.ts +230 -0
  45. package/src/auth/types/browserOpener.ts +5 -0
  46. package/src/auth/types/impersonationTokens.ts +23 -0
  47. package/src/auth/types/oauthTokens.ts +13 -0
  48. package/src/auth/types/orgApiKeys.ts +6 -0
  49. package/src/bin.ts +9 -0
  50. package/src/cli.ts +648 -0
  51. package/src/config/configPackageName.ts +9 -0
  52. package/src/config/legacyConfig.ts +25 -0
  53. package/src/config/loadConfig.ts +231 -0
  54. package/src/env/assertSandboxTarget.ts +20 -0
  55. package/src/env/loadEnv.ts +184 -0
  56. package/src/env/resolveTarget.ts +134 -0
  57. package/src/env/sandboxKeyName.ts +18 -0
  58. package/src/generated/apiRoutes.ts +3787 -0
  59. package/src/generated/client.ts +51564 -0
  60. package/src/generated/emit.ts +1163 -0
  61. package/src/generated/emitRuntime.ts +522 -0
  62. package/src/generated/features.ts +146 -0
  63. package/src/generated/labels.ts +29 -0
  64. package/src/generated/licenses.ts +287 -0
  65. package/src/generated/lintRules.ts +2207 -0
  66. package/src/generated/lintRuntime.ts +865 -0
  67. package/src/generated/plans.ts +1628 -0
  68. package/src/generated/referralPrograms.ts +22 -0
  69. package/src/generated/rewards.ts +58 -0
  70. package/src/generated/settings.ts +21 -0
  71. package/src/generated/skills.ts +305 -0
  72. package/src/generated/variants.ts +934 -0
  73. package/src/generated/wire.ts +334 -0
  74. package/src/http/autumnFetch.ts +30 -0
  75. package/src/index.ts +20 -0
  76. package/src/project/chooseConfigDir.ts +41 -0
  77. package/src/project/resolveProject.ts +115 -0
  78. package/src/project/rootMarker.ts +40 -0
  79. package/src/prompt/prompt.ts +186 -0
  80. package/src/prompt/select.ts +162 -0
  81. package/src/render/renderEnv.ts +77 -0
  82. package/src/render/renderPreview.ts +945 -0
  83. package/src/render/renderSandboxes.ts +92 -0
  84. package/src/render/stripTerminalControls.ts +19 -0
  85. package/src/repo/findRepoRoot.ts +79 -0
  86. package/src/surgery/appendPropertyEdit.ts +67 -0
  87. package/src/surgery/appendToArray.ts +87 -0
  88. package/src/surgery/appendToBinding.ts +19 -0
  89. package/src/surgery/appendToCollection.ts +40 -0
  90. package/src/surgery/appendToFixtureArray.ts +71 -0
  91. package/src/surgery/arrayBinding.ts +30 -0
  92. package/src/surgery/deleteFixtureLiteral.ts +81 -0
  93. package/src/surgery/deleteReference.ts +48 -0
  94. package/src/surgery/ensureBuilderImport.ts +65 -0
  95. package/src/surgery/findFixture.ts +238 -0
  96. package/src/surgery/fixtureEdit.ts +156 -0
  97. package/src/surgery/fixtureLocation.ts +32 -0
  98. package/src/surgery/insertCollection.ts +86 -0
  99. package/src/surgery/insertFirstProperty.ts +73 -0
  100. package/src/surgery/patchFixtureProperty.ts +152 -0
  101. package/src/surgery/patchSingletonProperty.ts +221 -0
  102. package/src/surgery/replaceFixture.ts +28 -0
  103. package/src/surgery/setFixtureProperty.ts +55 -0
  104. package/src/surgery/staticFixtureRule.ts +48 -0
  105. package/src/version.ts +5 -0
  106. package/dist/cli.js +0 -146296
  107. package/dist/compose/index.js +0 -122
  108. package/dist/src/compose/builders/builderFunctions.d.ts +0 -84
  109. package/dist/src/compose/builders/rewardFunctions.d.ts +0 -5
  110. package/dist/src/compose/builders/variantFunctions.d.ts +0 -2
  111. package/dist/src/compose/index.d.ts +0 -19
  112. package/dist/src/compose/models/featureModels.d.ts +0 -262
  113. package/dist/src/compose/models/index.d.ts +0 -3
  114. package/dist/src/compose/models/planModels.d.ts +0 -562
  115. package/dist/src/compose/models/rewardModels.d.ts +0 -52
  116. package/dist/src/compose/models/variantModels.d.ts +0 -34
  117. package/readme.md +0 -186
@@ -0,0 +1,144 @@
1
+ import { announceAuthorizationUrl } from "../auth/announceAuthorizationUrl";
2
+ import { openSystemBrowser } from "../auth/browser/openSystemBrowser";
3
+ import { createOrgApiKeys } from "../auth/createOrgApiKeys";
4
+ import { CLI_OAUTH_SCOPES } from "../auth/oauthConfig";
5
+ import {
6
+ type AuthorizationUrlListener,
7
+ runOAuthFlow,
8
+ } from "../auth/runOAuthFlow";
9
+ import type { BrowserOpener } from "../auth/types/browserOpener";
10
+ import type { ImpersonationTokens } from "../auth/types/impersonationTokens";
11
+ import type { AuthorizationOutcome } from "../auth/types/oauthTokens";
12
+ import type { OrgApiKeys } from "../auth/types/orgApiKeys";
13
+ import { loadEnvFiles, writeEnvValues } from "../env/loadEnv";
14
+ import {
15
+ resolveTarget,
16
+ type Target,
17
+ targetBaseUrl,
18
+ } from "../env/resolveTarget";
19
+ import { resolveProject } from "../project/resolveProject";
20
+
21
+ export type Authorize = ({
22
+ onAuthorizationUrl,
23
+ }: {
24
+ onAuthorizationUrl: AuthorizationUrlListener;
25
+ }) => Promise<AuthorizationOutcome>;
26
+
27
+ export type CreateApiKeys = ({
28
+ accessToken,
29
+ }: {
30
+ accessToken: string;
31
+ }) => Promise<OrgApiKeys>;
32
+
33
+ export type LoginOptions = {
34
+ cwd?: string;
35
+ configPath?: string;
36
+ /** Where to write progress. Injected so tests can capture it. */
37
+ write?: (text: string) => void;
38
+ openBrowser?: BrowserOpener;
39
+ /** Resolved by the CLI from its global flags; defaults to the env-driven target. */
40
+ target?: Target;
41
+ authorize?: Authorize;
42
+ createApiKeys?: CreateApiKeys;
43
+ };
44
+
45
+ export type LoginResult = {
46
+ envPath: string;
47
+ orgId?: string;
48
+ writtenKeys: string[];
49
+ };
50
+
51
+ const authorizeWith =
52
+ ({ target }: { target: Target }): Authorize =>
53
+ ({ onAuthorizationUrl }) =>
54
+ runOAuthFlow({
55
+ clientId: target.clientId,
56
+ backendUrl: targetBaseUrl({ target }),
57
+ scopes: CLI_OAUTH_SCOPES,
58
+ onAuthorizationUrl,
59
+ });
60
+
61
+ const createApiKeysWith =
62
+ ({ target }: { target: Target }): CreateApiKeys =>
63
+ ({ accessToken }) =>
64
+ createOrgApiKeys({ accessToken, backendUrl: targetBaseUrl({ target }) });
65
+
66
+ const keysToEnvValues = ({
67
+ keys,
68
+ }: {
69
+ keys: OrgApiKeys;
70
+ }): Record<string, string> => {
71
+ const values: Record<string, string> = {};
72
+ if (keys.sandboxKey) values.AUTUMN_SECRET_KEY = keys.sandboxKey;
73
+ if (keys.prodKey) values.AUTUMN_PROD_SECRET_KEY = keys.prodKey;
74
+ return values;
75
+ };
76
+
77
+ /** Same env names as api keys: every command already sends them as a bearer. */
78
+ const impersonationToEnvValues = ({
79
+ tokens,
80
+ }: {
81
+ tokens: ImpersonationTokens;
82
+ }): Record<string, string> => ({
83
+ AUTUMN_SECRET_KEY: tokens.sandboxToken,
84
+ AUTUMN_PROD_SECRET_KEY: tokens.liveToken,
85
+ });
86
+
87
+ const formatExpiry = (iso: string): string => {
88
+ const date = new Date(iso);
89
+ return Number.isNaN(date.getTime()) ? iso : date.toLocaleTimeString();
90
+ };
91
+
92
+ /**
93
+ * Authorize in a browser — or on any other machine — then mint the org's keys
94
+ * and put them where `loadEnvFiles` will find them again.
95
+ */
96
+ export const runLogin = async ({
97
+ cwd = process.cwd(),
98
+ configPath,
99
+ target,
100
+ write = (text) => process.stdout.write(text),
101
+ openBrowser = openSystemBrowser,
102
+ authorize,
103
+ createApiKeys,
104
+ }: LoginOptions = {}): Promise<LoginResult> => {
105
+ const dirs = resolveProject({ cwd, configFlag: configPath }).envDirs;
106
+ loadEnvFiles({ dirs });
107
+ // The same target every other command resolves: a local server or a
108
+ // staging URL authenticates against itself, never against production.
109
+ const resolved = target ?? resolveTarget({});
110
+ const authorizeStep = authorize ?? authorizeWith({ target: resolved });
111
+ const createApiKeysStep =
112
+ createApiKeys ?? createApiKeysWith({ target: resolved });
113
+
114
+ const outcome = await authorizeStep({
115
+ onAuthorizationUrl: ({ url }) =>
116
+ announceAuthorizationUrl({ url, write, openBrowser }),
117
+ });
118
+
119
+ // Impersonating a customer: one-hour tokens, no api keys minted on their org.
120
+ if (outcome.kind === "impersonation") {
121
+ const values = impersonationToEnvValues({ tokens: outcome.tokens });
122
+ const envPath = writeEnvValues({ dirs, values });
123
+ write(
124
+ `\nWrote impersonation tokens to ${envPath}. They expire at ${formatExpiry(outcome.tokens.expiresAt)}; run \`atmn login\` again after that.\n`,
125
+ );
126
+ return { envPath, writtenKeys: Object.keys(values) };
127
+ }
128
+
129
+ const keys = await createApiKeysStep({
130
+ accessToken: outcome.tokens.accessToken,
131
+ });
132
+ const values = keysToEnvValues({ keys });
133
+
134
+ if (Object.keys(values).length === 0) {
135
+ throw new Error("Authorization succeeded but no API keys were returned.");
136
+ }
137
+
138
+ const envPath = writeEnvValues({ dirs, values });
139
+ const writtenKeys = Object.keys(values);
140
+
141
+ write(`\nWrote ${writtenKeys.join(" and ")} to ${envPath}\n`);
142
+
143
+ return { envPath, orgId: keys.orgId, writtenKeys };
144
+ };
@@ -0,0 +1,373 @@
1
+ import { dirname, relative, resolve } from "node:path";
2
+ import { Lang, parse, type SgNode } from "@ast-grep/napi";
3
+ import { appendToBinding } from "../../surgery/appendToBinding";
4
+ import { appendToCollection } from "../../surgery/appendToCollection";
5
+ import { ensureBuilderImport } from "../../surgery/ensureBuilderImport";
6
+ import { lineStartOf } from "../../surgery/fixtureEdit";
7
+ import type { CollectionTarget } from "./resolveCollectionTarget";
8
+
9
+ const NAMED_IMPORT = /import\s+\{([^}]*)\}\s*from\s*["']([^"']+)["']/;
10
+
11
+ const importSpecifierForBuilder = ({
12
+ source,
13
+ builder,
14
+ }: {
15
+ source: string;
16
+ builder: string;
17
+ }): string | null => {
18
+ const root = parse(Lang.TypeScript, source).root();
19
+ for (const statement of root.findAll({
20
+ rule: { kind: "import_statement" },
21
+ })) {
22
+ const match = NAMED_IMPORT.exec(statement.text());
23
+ if (match === null) continue;
24
+ const importsBuilder = match[1].split(",").some((entry) => {
25
+ const [imported] = entry.trim().split(/\s+as\s+/);
26
+ return imported === builder;
27
+ });
28
+ if (importsBuilder) return match[2];
29
+ }
30
+ return null;
31
+ };
32
+
33
+ const referenceSpecifier = ({
34
+ from,
35
+ fixtureFile,
36
+ }: {
37
+ from: string;
38
+ fixtureFile: string;
39
+ }): string => {
40
+ const path = relative(dirname(from), fixtureFile)
41
+ .replaceAll("\\", "/")
42
+ .replace(/\.ts$/, "");
43
+ return path.startsWith(".") ? path : `./${path}`;
44
+ };
45
+
46
+ const builderSpecifierForFixture = ({
47
+ configPath,
48
+ fixtureFile,
49
+ specifier,
50
+ }: {
51
+ configPath: string;
52
+ fixtureFile: string;
53
+ specifier: string;
54
+ }): string =>
55
+ specifier.startsWith(".")
56
+ ? referenceSpecifier({
57
+ from: fixtureFile,
58
+ fixtureFile: resolve(dirname(configPath), specifier),
59
+ })
60
+ : specifier;
61
+
62
+ const ensureReferenceImport = ({
63
+ source,
64
+ name,
65
+ specifier,
66
+ }: {
67
+ source: string;
68
+ name: string;
69
+ specifier: string;
70
+ }): string => {
71
+ const root = parse(Lang.TypeScript, source).root();
72
+ for (const statement of root.findAll({
73
+ rule: { kind: "import_statement" },
74
+ })) {
75
+ const match = NAMED_IMPORT.exec(statement.text());
76
+ if (match === null || match[2] !== specifier) continue;
77
+ const names = match[1]
78
+ .split(",")
79
+ .map((entry) => entry.trim())
80
+ .filter(Boolean);
81
+ if (names.includes(name)) return source;
82
+ if (match[1].includes("\n")) break;
83
+ const updated = statement
84
+ .text()
85
+ .replace(`{${match[1]}}`, `{ ${[...names, name].join(", ")} }`);
86
+ return root.commitEdits([
87
+ {
88
+ startPos: statement.range().start.index,
89
+ endPos: statement.range().end.index,
90
+ insertedText: updated,
91
+ },
92
+ ]);
93
+ }
94
+ return `import { ${name} } from "${specifier}";\n${source}`;
95
+ };
96
+
97
+ const addExport = ({
98
+ source,
99
+ declaration,
100
+ beforeBinding,
101
+ }: {
102
+ source: string;
103
+ declaration: string;
104
+ beforeBinding?: string;
105
+ }): string => {
106
+ if (beforeBinding === undefined)
107
+ return `${source.trimEnd()}${source.trim() === "" ? "" : "\n\n"}${declaration}\n`;
108
+
109
+ const root = parse(Lang.TypeScript, source).root();
110
+ const declarator = root
111
+ .findAll({ rule: { kind: "variable_declarator" } })
112
+ .find((node) => node.field("name")?.text() === beforeBinding);
113
+ if (declarator === undefined)
114
+ return `${source.trimEnd()}\n\n${declaration}\n`;
115
+ let statement = declarator;
116
+ while (
117
+ statement.parent() !== null &&
118
+ statement.parent()?.kind() !== "program"
119
+ )
120
+ statement = statement.parent() ?? statement;
121
+ const insertAt = lineStartOf(source, statement.range().start.index);
122
+ return `${source.slice(0, insertAt)}${declaration}\n\n${source.slice(insertAt)}`;
123
+ };
124
+
125
+ export const planVersionExportName = ({
126
+ planId,
127
+ versionSlug,
128
+ takenSources = [],
129
+ }: {
130
+ planId: string;
131
+ versionSlug: string;
132
+ takenSources?: string[];
133
+ }): string => {
134
+ const raw = `${planId}_${versionSlug}`;
135
+ const sanitized = raw.replace(/[^A-Za-z0-9]/g, "_");
136
+ const preferred = /^[A-Za-z_]/.test(sanitized) ? sanitized : `_${sanitized}`;
137
+ if (!takenSources.some((source) => declaresName({ source, name: preferred })))
138
+ return preferred;
139
+
140
+ const encoded = raw.replace(
141
+ /[^A-Za-z0-9]/gu,
142
+ (character) => `_${character.codePointAt(0)?.toString(16)}_`,
143
+ );
144
+ const fallback = /^[A-Za-z_]/.test(encoded) ? encoded : `_${encoded}`;
145
+ let available = fallback;
146
+ let suffix = 2;
147
+ while (
148
+ takenSources.some((source) => declaresName({ source, name: available }))
149
+ ) {
150
+ available = `${fallback}_${suffix}`;
151
+ suffix += 1;
152
+ }
153
+ return available;
154
+ };
155
+
156
+ const declaresName = ({
157
+ source,
158
+ name,
159
+ }: {
160
+ source: string;
161
+ name: string;
162
+ }): boolean => {
163
+ const root = parse(Lang.TypeScript, source).root();
164
+ const variableBinding = root
165
+ .findAll({ rule: { kind: "variable_declarator" } })
166
+ .some((node) => {
167
+ if (!bindsTopLevel({ node })) return false;
168
+ const pattern = node.field("name");
169
+ if (pattern === null) return false;
170
+ return bindingPatternHasName({ pattern, name });
171
+ });
172
+ if (variableBinding) return true;
173
+ const namedDeclarationKinds = [
174
+ "function_declaration",
175
+ "generator_function_declaration",
176
+ "function_signature",
177
+ "class_declaration",
178
+ "abstract_class_declaration",
179
+ "enum_declaration",
180
+ "internal_module",
181
+ ];
182
+ for (const kind of namedDeclarationKinds) {
183
+ if (
184
+ root
185
+ .findAll({ rule: { kind } })
186
+ .some(
187
+ (node) =>
188
+ bindsTopLevel({ node }) && node.field("name")?.text() === name,
189
+ )
190
+ )
191
+ return true;
192
+ }
193
+ const importBinding = root
194
+ .findAll({ rule: { kind: "import_specifier" } })
195
+ .some((node) => {
196
+ const names = node
197
+ .text()
198
+ .trim()
199
+ .split(/\s+as\s+/);
200
+ return names[names.length - 1] === name;
201
+ });
202
+ if (importBinding) return true;
203
+ const importClauseBinding = root
204
+ .findAll({ rule: { kind: "import_clause" } })
205
+ .some((clause) =>
206
+ clause
207
+ .children()
208
+ .some(
209
+ (child) =>
210
+ (child.kind() === "identifier" ||
211
+ child.kind() === "namespace_import") &&
212
+ child.text().replace(/^\*\s+as\s+/, "") === name,
213
+ ),
214
+ );
215
+ if (importClauseBinding) return true;
216
+ const importRequireBinding = root
217
+ .findAll({ rule: { kind: "import_require_clause" } })
218
+ .some((clause) =>
219
+ clause
220
+ .findAll({ rule: { kind: "identifier", pattern: name } })
221
+ .some((identifier) => identifier.text() === name),
222
+ );
223
+ if (importRequireBinding) return true;
224
+ return root
225
+ .findAll({ rule: { kind: "export_specifier" } })
226
+ .some((specifier) => {
227
+ const names = specifier
228
+ .text()
229
+ .trim()
230
+ .split(/\s+as\s+/);
231
+ return names[names.length - 1] === name;
232
+ });
233
+ };
234
+
235
+ const bindingPatternHasName = ({
236
+ pattern,
237
+ name,
238
+ }: {
239
+ pattern: SgNode;
240
+ name: string;
241
+ }): boolean => {
242
+ if (
243
+ (pattern.kind() === "identifier" ||
244
+ pattern.kind() === "shorthand_property_identifier_pattern") &&
245
+ pattern.text() === name
246
+ )
247
+ return true;
248
+ const children = pattern.namedChildren();
249
+ if (
250
+ pattern.kind() === "pair_pattern" ||
251
+ pattern.kind() === "assignment_pattern" ||
252
+ pattern.kind() === "object_assignment_pattern" ||
253
+ pattern.kind() === "rest_pattern"
254
+ ) {
255
+ const binding =
256
+ pattern.kind() === "pair_pattern"
257
+ ? children[children.length - 1]
258
+ : children[0];
259
+ return binding === undefined
260
+ ? false
261
+ : bindingPatternHasName({ pattern: binding, name });
262
+ }
263
+ return children.some((child) =>
264
+ bindingPatternHasName({ pattern: child, name }),
265
+ );
266
+ };
267
+
268
+ const bindsTopLevel = ({ node }: { node: SgNode }): boolean => {
269
+ const wrappers = new Set<string | number>([
270
+ "lexical_declaration",
271
+ "variable_declaration",
272
+ "export_statement",
273
+ "ambient_declaration",
274
+ "expression_statement",
275
+ ]);
276
+ let current = node;
277
+ while (current.parent() !== null && current.parent()?.kind() !== "program") {
278
+ const parent = current.parent();
279
+ if (parent === null || !wrappers.has(parent.kind())) return false;
280
+ current = parent;
281
+ }
282
+ return current.parent()?.kind() === "program";
283
+ };
284
+
285
+ export const appendPlanVersionFixture = ({
286
+ configPath,
287
+ files,
288
+ target,
289
+ fixtureFile,
290
+ planId,
291
+ versionSlug,
292
+ fixture,
293
+ builder,
294
+ targetCollection,
295
+ builderCollection,
296
+ }: {
297
+ configPath: string;
298
+ files: Map<string, string>;
299
+ target: CollectionTarget;
300
+ fixtureFile: string;
301
+ planId: string;
302
+ versionSlug: string;
303
+ fixture: string;
304
+ builder: string;
305
+ targetCollection: string;
306
+ builderCollection: string;
307
+ }): boolean => {
308
+ const configSource = files.get(configPath) ?? "";
309
+ const originalFixtureSource = files.get(fixtureFile);
310
+ const targetSourceBefore = files.get(target.file) ?? "";
311
+ const exportName = planVersionExportName({
312
+ planId,
313
+ versionSlug,
314
+ takenSources: [targetSourceBefore, originalFixtureSource ?? ""],
315
+ });
316
+ const builderSpecifier = importSpecifierForBuilder({
317
+ source: configSource,
318
+ builder,
319
+ });
320
+ const seededFixtureSource =
321
+ originalFixtureSource ??
322
+ (builderSpecifier === null
323
+ ? ""
324
+ : `import { ${builder} } from "${builderSpecifierForFixture({ configPath, fixtureFile, specifier: builderSpecifier })}";\n`);
325
+ const withExport = addExport({
326
+ source: seededFixtureSource,
327
+ declaration: `export const ${exportName} = ${fixture};`,
328
+ ...(target.file === fixtureFile && target.kind === "binding"
329
+ ? { beforeBinding: target.name }
330
+ : {}),
331
+ });
332
+ const fixtureSource = ensureBuilderImport({
333
+ source: withExport,
334
+ builder,
335
+ collection: builderCollection,
336
+ });
337
+ const targetSource =
338
+ target.file === fixtureFile
339
+ ? fixtureSource
340
+ : (files.get(target.file) ?? "");
341
+ const withReference =
342
+ target.kind === "inline"
343
+ ? appendToCollection({
344
+ source: targetSource,
345
+ collection: targetCollection,
346
+ text: exportName,
347
+ })
348
+ : appendToBinding({
349
+ source: targetSource,
350
+ name: target.name,
351
+ text: exportName,
352
+ });
353
+ if (withReference === null) return false;
354
+
355
+ files.set(
356
+ fixtureFile,
357
+ target.file === fixtureFile ? withReference : fixtureSource,
358
+ );
359
+ if (target.file !== fixtureFile) {
360
+ files.set(
361
+ target.file,
362
+ ensureReferenceImport({
363
+ source: withReference,
364
+ name: exportName,
365
+ specifier: referenceSpecifier({
366
+ from: target.file,
367
+ fixtureFile,
368
+ }),
369
+ }),
370
+ );
371
+ }
372
+ return true;
373
+ };