atmn 2.0.11 → 2.0.12

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.
@@ -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/applyPreview.ts","../src/actions/pull/applySettingsPreview.ts","../src/actions/pull/changedFixtureKeys.ts","../src/actions/pull/listSourceFiles.ts","../src/actions/pull/locateFixture.ts","../src/actions/pull/resolveCollectionTarget.ts","../src/actions/pull/rewriteConfig.ts","../src/actions/pull/scaffoldConfig.ts","../src/actions/push/backfillInternalIds.ts","../src/actions/push/deprecatedFields.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/skills.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/labels.ts","../src/generated/licenses.ts","../src/generated/lintRules.ts","../src/generated/lintRuntime.ts","../src/generated/plans.ts","../src/generated/referralPrograms.ts","../src/generated/rewards.ts","../src/generated/settings.ts","../src/generated/skills.ts","../src/generated/variants.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/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/patchFixtureProperty.ts","../src/surgery/patchSingletonProperty.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/init.test.ts","../test/keyless.test.ts","../test/legacyConfig.test.ts","../test/lintDocument.test.ts","../test/loadConfigNode.test.ts","../test/loadConfigV2.test.ts","../test/login.test.ts","../test/nodeNextTypes.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/pullOmitDefaults.test.ts","../test/pullPlans.test.ts","../test/pullStatesSlugs.test.ts","../test/pullTwoCollections.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"]}
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/listSourceFiles.ts","../src/actions/pull/locateFixture.ts","../src/actions/pull/resolveCollectionTarget.ts","../src/actions/pull/rewriteConfig.ts","../src/actions/pull/scaffoldConfig.ts","../src/actions/push/backfillInternalIds.ts","../src/actions/push/deprecatedFields.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/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/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/settings.ts","../src/generated/skills.ts","../src/generated/variants.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/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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "atmn",
3
- "version": "2.0.11",
3
+ "version": "2.0.12",
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": "b19c41f62894316db8ed1d5d6a87ad5f8c3f3798"
52
+ "gitHead": "778e19f6d88f8becb9faeeacb5967d9f0675aef0"
53
53
  }
@@ -29,6 +29,7 @@ import {
29
29
  runKeylessLogin,
30
30
  } from "../login/keyless";
31
31
  import { scaffoldConfig } from "../pull/scaffoldConfig";
32
+ import { ignoreBundledSkills } from "../skills/ignoreBundledSkills";
32
33
  import { installSkills, SKILLS_DIR_NAME } from "../skills/skills";
33
34
 
34
35
  /** The network, behind functions so a test can hand over fakes. */
@@ -385,6 +386,7 @@ export const runInit = async ({
385
386
 
386
387
  const skillsDir = join(configDir, SKILLS_DIR_NAME);
387
388
  const { written } = installSkills({ dir: skillsDir, write: () => {} });
389
+ ignoreBundledSkills({ repoRoot, configDir });
388
390
  prompter.write(
389
391
  `${done(`Skills: ${relative(repoRoot, skillsDir)}/${written.join(", ")}`)}\n`,
390
392
  );
@@ -0,0 +1,100 @@
1
+ import { COLLECTIONS, NESTED_FIXTURES } from "../../generated/emit";
2
+ import { resolveBranch } from "../../generated/emitRuntime";
3
+ import { mappingAssignments } from "../../generated/mappingAssignments";
4
+ import { patchFixturePaths } from "../../surgery/patchFixturePaths";
5
+ import { readFixtureValue } from "../../surgery/readFixtureValue";
6
+ import { locateFixture } from "./locateFixture";
7
+
8
+ export const applyMappings = ({
9
+ catalog,
10
+ configPath,
11
+ files,
12
+ }: {
13
+ catalog: Record<string, unknown>;
14
+ configPath: string;
15
+ files: Map<string, string>;
16
+ }): {
17
+ replaced: string[];
18
+ unlocated: { collection: string; id: string; action: string }[];
19
+ } => {
20
+ const replaced: string[] = [];
21
+ const unlocated: { collection: string; id: string; action: string }[] = [];
22
+ const builders = Object.values(NESTED_FIXTURES).map(({ builder }) => builder);
23
+ for (const [collection, collectionSpec] of Object.entries(COLLECTIONS)) {
24
+ const rows = catalog[collection];
25
+ if (!collectionSpec.pull || !Array.isArray(rows)) continue;
26
+ for (const entry of rows) {
27
+ const { spec, row } = resolveBranch({ spec: collectionSpec, row: entry });
28
+ const id = row[spec.responseIdField];
29
+ if (!spec.mappings || typeof id !== "string" || row.archived === true)
30
+ continue;
31
+ const key = spec.versioned ? `${id}@${row.versionSlug ?? "v1"}` : id;
32
+ const located = locateFixture({
33
+ configPath,
34
+ files,
35
+ builder: spec.builder,
36
+ idField: spec.idField,
37
+ id,
38
+ internalId:
39
+ typeof row.internalId === "string" ? row.internalId : undefined,
40
+ where: spec.versioned
41
+ ? [
42
+ {
43
+ field: "versionSlug",
44
+ equals:
45
+ typeof row.versionSlug === "string" ? row.versionSlug : "v1",
46
+ absentMeans: "v1",
47
+ },
48
+ ]
49
+ : undefined,
50
+ allowDynamic: true,
51
+ requireUnique: true,
52
+ });
53
+ if (!located) {
54
+ unlocated.push({
55
+ collection,
56
+ id: key,
57
+ action: "cannot locate the fixture to write processor mappings",
58
+ });
59
+ continue;
60
+ }
61
+ const planned = mappingAssignments({
62
+ projection: spec.mappings,
63
+ local: readFixtureValue({
64
+ node: located.node,
65
+ builders: [spec.builder, ...builders],
66
+ }),
67
+ remote: row,
68
+ });
69
+ if (planned.errors.length) {
70
+ unlocated.push(
71
+ ...planned.errors.map((action) => ({ collection, id: key, action })),
72
+ );
73
+ continue;
74
+ }
75
+ if (!planned.assignments.length) continue;
76
+ const patched = patchFixturePaths({
77
+ source: located.source,
78
+ builder: located.builder,
79
+ idField: located.idField,
80
+ id: located.id,
81
+ where: located.where,
82
+ builders,
83
+ assignments: planned.assignments,
84
+ });
85
+ if (!patched || patched.skipped.length) {
86
+ unlocated.push({
87
+ collection,
88
+ id: key,
89
+ action:
90
+ "cannot safely splice processor mappings into a computed or ambiguous value",
91
+ });
92
+ continue;
93
+ }
94
+ if (patched.source === located.source) continue;
95
+ files.set(located.file, patched.source);
96
+ replaced.push(key);
97
+ }
98
+ }
99
+ return { replaced, unlocated };
100
+ };
@@ -2,7 +2,7 @@ import type { SgNode } from "@ast-grep/napi";
2
2
  import {
3
3
  type FixtureConstraint,
4
4
  type FixtureShape,
5
- findFixture,
5
+ findFixtures,
6
6
  } from "../../surgery/findFixture";
7
7
 
8
8
  /** The file, its source, the node, and the lookup that found it — reuse the
@@ -35,6 +35,7 @@ export const locateFixture = ({
35
35
  internalId,
36
36
  where,
37
37
  allowDynamic = false,
38
+ requireUnique = false,
38
39
  }: {
39
40
  configPath: string;
40
41
  files: Map<string, string>;
@@ -44,6 +45,7 @@ export const locateFixture = ({
44
45
  internalId?: string | null;
45
46
  where?: FixtureConstraint[];
46
47
  allowDynamic?: boolean;
48
+ requireUnique?: boolean;
47
49
  }): LocatedFixture | null => {
48
50
  const others = [...files.keys()].filter((file) => file !== configPath);
49
51
  const ordered = [configPath, ...others];
@@ -59,20 +61,33 @@ export const locateFixture = ({
59
61
  ];
60
62
  const shapes = Array.isArray(builder) ? builder : [builder];
61
63
  for (const attempt of attempts) {
64
+ const matches = new Map<string, LocatedFixture>();
62
65
  for (const file of ordered) {
63
66
  const source = files.get(file);
64
67
  if (source === undefined) continue;
65
68
  for (const shape of shapes) {
66
- const node = findFixture({
69
+ const nodes = findFixtures({
67
70
  source,
68
71
  builder: shape,
69
72
  allowDynamic,
70
73
  ...attempt,
71
74
  });
72
- if (node !== null)
73
- return { file, source, node, builder: shape, ...attempt };
75
+ for (const node of nodes) {
76
+ const located = { file, source, node, builder: shape, ...attempt };
77
+ if (!requireUnique) return located;
78
+ const range = node.range();
79
+ const key = JSON.stringify([
80
+ file,
81
+ range.start.index,
82
+ range.end.index,
83
+ ]);
84
+ matches.set(key, located);
85
+ if (matches.size > 1) return null;
86
+ }
74
87
  }
75
88
  }
89
+ const match = matches.values().next().value;
90
+ if (match !== undefined) return match;
76
91
  }
77
92
  return null;
78
93
  };
@@ -16,6 +16,7 @@ import { resolveProject } from "../project/resolveProject";
16
16
  import { writeRootMarker } from "../project/rootMarker";
17
17
  import { createPrompter, done, type Prompter } from "../prompt/prompt";
18
18
  import type { SettingsPreview } from "../render/renderPreview";
19
+ import { applyMappings } from "./pull/applyMappings";
19
20
  import { applyPreview, type PreviewEntry } from "./pull/applyPreview";
20
21
  import { applySettingsPreview } from "./pull/applySettingsPreview";
21
22
  import { listSourceFiles } from "./pull/listSourceFiles";
@@ -341,6 +342,27 @@ export const runPull = async ({
341
342
  );
342
343
  }
343
344
 
345
+ if (includeMappings) {
346
+ const managedCatalog = Object.fromEntries(
347
+ Object.entries(COLLECTIONS)
348
+ .filter(
349
+ ([, spec]) =>
350
+ spec.wireKey !== undefined && wire[spec.wireKey] !== undefined,
351
+ )
352
+ .map(([collection]) => [collection, catalogRows[collection]]),
353
+ );
354
+ const mapped = applyMappings({
355
+ catalog: managedCatalog,
356
+ configPath,
357
+ files,
358
+ });
359
+ unlocated.push(...mapped.unlocated);
360
+ for (const id of mapped.replaced) {
361
+ if (!replaced.includes(id) && !appended.includes(id)) replaced.push(id);
362
+ lines.push(`↳ wrote processor mappings into ${id}`);
363
+ }
364
+ }
365
+
344
366
  // A fixture that is not a plain literal cannot be edited in place; saying
345
367
  // so before writing anything beats a half-applied pull.
346
368
  if (unlocated.length > 0) throw new UnlocatableFixturesError({ unlocated });
@@ -0,0 +1,32 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { isAbsolute, join, relative, sep } from "node:path";
3
+ import { SKILLS_DIR_NAME } from "./skills";
4
+
5
+ export const ignoreBundledSkills = ({
6
+ repoRoot,
7
+ configDir,
8
+ }: {
9
+ repoRoot: string;
10
+ configDir: string;
11
+ }): void => {
12
+ const path = relative(repoRoot, join(configDir, SKILLS_DIR_NAME));
13
+ if (isAbsolute(path) || path === ".." || path.startsWith(`..${sep}`)) return;
14
+ if (/[\r\n]/.test(path)) return;
15
+ const pattern = `/${path
16
+ .split(sep)
17
+ .join("/")
18
+ .replace(/[\\*?[\] ]/g, "\\$&")}/`;
19
+ const ignorePath = join(repoRoot, ".gitignore");
20
+ const contents = existsSync(ignorePath)
21
+ ? readFileSync(ignorePath, "utf8")
22
+ : "";
23
+ if (contents.split(/\r?\n/).includes(pattern)) return;
24
+ const newline = contents.includes("\r\n") ? "\r\n" : "\n";
25
+ const separator =
26
+ contents.length > 0 && !contents.endsWith("\n") ? newline : "";
27
+ writeFileSync(
28
+ ignorePath,
29
+ `${contents}${separator}${pattern}${newline}`,
30
+ "utf8",
31
+ );
32
+ };
package/src/cli.ts CHANGED
@@ -21,6 +21,7 @@ import { runSandboxDelete } from "./actions/sandbox/deleteSandbox";
21
21
  import { runSandboxList } from "./actions/sandbox/listSandboxes";
22
22
  import { runSandboxUse } from "./actions/sandbox/useSandbox";
23
23
  import { withSandboxScopeHint } from "./actions/sandbox/withSandboxScopeHint";
24
+ import { ignoreBundledSkills } from "./actions/skills/ignoreBundledSkills";
24
25
  import {
25
26
  installSkills,
26
27
  linkSkills,
@@ -431,6 +432,8 @@ Linking a keyless org to an account:
431
432
  async (options: { dir?: string; link?: boolean }, command: Command) => {
432
433
  const dir = skillsDirOf({ command, dir: options.dir });
433
434
  installSkills({ dir, write: (text) => process.stdout.write(text) });
435
+ if (options.dir === undefined)
436
+ ignoreBundledSkills(projectOf({ command }));
434
437
  if (options.link === true)
435
438
  await linkSkills({
436
439
  dir,
@@ -449,6 +452,8 @@ Linking a keyless org to an account:
449
452
  .action((options: { dir?: string }, command: Command) => {
450
453
  const dir = skillsDirOf({ command, dir: options.dir });
451
454
  updateSkills({ dir, write: (text) => process.stdout.write(text) });
455
+ if (options.dir === undefined)
456
+ ignoreBundledSkills(projectOf({ command }));
452
457
  });
453
458
 
454
459
  program
@@ -5,6 +5,7 @@ import type { CollectionSpec, SingletonSpec } from "./emitRuntime";
5
5
 
6
6
  export const COLLECTIONS: Readonly<Record<string, CollectionSpec>> = {
7
7
  features: {
8
+ wireKey: "features",
8
9
  builder: "feature",
9
10
  idField: "featureId",
10
11
  responseIdField: "id",
@@ -80,11 +81,13 @@ export const COLLECTIONS: Readonly<Record<string, CollectionSpec>> = {
80
81
  "providerMarkups.*.markup",
81
82
  "type",
82
83
  ],
84
+ mappings: { properties: { processors: { mapping: true } } },
83
85
  defaults: {},
84
86
  pull: true,
85
87
  deprecated: [],
86
88
  },
87
89
  plans: {
90
+ wireKey: "plans",
88
91
  builder: "plan",
89
92
  idField: "planId",
90
93
  responseIdField: "id",
@@ -865,6 +868,43 @@ export const COLLECTIONS: Readonly<Record<string, CollectionSpec>> = {
865
868
  "variants.versionSlug",
866
869
  "versionSlug",
867
870
  ],
871
+ mappings: {
872
+ properties: {
873
+ processors: { mapping: true },
874
+ price: { properties: { processors: { mapping: true } } },
875
+ items: {
876
+ items: {
877
+ properties: {
878
+ price: { properties: { processors: { mapping: true } } },
879
+ },
880
+ },
881
+ identity: {
882
+ responseField: "mappingIdentity",
883
+ components: [
884
+ { paths: ["featureId"], default: "" },
885
+ { paths: ["price.billingMethod"], default: "" },
886
+ { paths: ["price.interval", "reset.interval"], default: "" },
887
+ {
888
+ paths: ["price.intervalCount", "reset.intervalCount"],
889
+ default: "",
890
+ defaultWhen: { component: 2, value: 1 },
891
+ },
892
+ ],
893
+ },
894
+ },
895
+ variants: {
896
+ items: {
897
+ properties: {
898
+ processors: { mapping: true, sourcePath: ["plan", "processors"] },
899
+ },
900
+ },
901
+ identity: {
902
+ responseField: "mappingIdentity",
903
+ components: [{ paths: ["variantPlanId"], default: "" }],
904
+ },
905
+ },
906
+ },
907
+ },
868
908
  defaults: {
869
909
  addOn: false,
870
910
  autoEnable: false,
@@ -961,6 +1001,7 @@ export const COLLECTIONS: Readonly<Record<string, CollectionSpec>> = {
961
1001
  ],
962
1002
  },
963
1003
  rewards: {
1004
+ wireKey: "rewards",
964
1005
  builder: "reward",
965
1006
  idField: "id",
966
1007
  responseIdField: "id",
@@ -1103,6 +1144,7 @@ export const COLLECTIONS: Readonly<Record<string, CollectionSpec>> = {
1103
1144
  ],
1104
1145
  },
1105
1146
  referralPrograms: {
1147
+ wireKey: "referral_programs",
1106
1148
  builder: "referralProgram",
1107
1149
  idField: "id",
1108
1150
  responseIdField: "id",
@@ -1,13 +1,11 @@
1
1
  // Copied by @autumn/atmn-generator from packages/atmn-generator/src/emit/runtime/emitFixture.ts.
2
2
  // Do not edit — change that file and run `bun generate` instead.
3
3
 
4
- /**
5
- * Copied verbatim into the CLI by the generator, so this file imports nothing:
6
- * a pulled server row becomes fixture source here, in the exact shape the
7
- * surgery module splices into the user's config.
8
- */
4
+ import type { MappingProjection } from "./mappingAssignments";
9
5
 
10
6
  export type CollectionSpec = {
7
+ readonly wireKey?: string;
8
+ readonly mappings?: MappingProjection;
11
9
  readonly builder: string;
12
10
  readonly idField: string;
13
11
  readonly responseIdField: string;
@@ -0,0 +1,204 @@
1
+ // Copied by @autumn/atmn-generator from packages/atmn-generator/src/emit/runtime/mappingAssignments.ts.
2
+ // Do not edit — change that file and run `bun generate` instead.
3
+
4
+ export type MappingIdentity = {
5
+ readonly responseField: string;
6
+ readonly components: readonly {
7
+ readonly paths: readonly string[];
8
+ readonly default: string | number;
9
+ readonly defaultWhen?: {
10
+ readonly component: number;
11
+ readonly value: string | number;
12
+ };
13
+ }[];
14
+ };
15
+
16
+ export type MappingProjection = {
17
+ readonly mapping?: boolean;
18
+ readonly sourcePath?: readonly string[];
19
+ readonly properties?: Readonly<Record<string, MappingProjection>>;
20
+ readonly items?: MappingProjection;
21
+ readonly identity?: MappingIdentity;
22
+ };
23
+
24
+ export type MappingAssignment = {
25
+ path: (string | number)[];
26
+ text: string | null;
27
+ };
28
+
29
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
30
+ typeof value === "object" && value !== null && !Array.isArray(value);
31
+
32
+ const valueAt = ({
33
+ value,
34
+ path,
35
+ }: {
36
+ value: unknown;
37
+ path: readonly string[];
38
+ }): unknown => {
39
+ for (const key of path) {
40
+ if (typeof value === "symbol") return value;
41
+ value = isRecord(value) ? value[key] : undefined;
42
+ }
43
+ return value;
44
+ };
45
+
46
+ export const mappingIdentityOf = ({
47
+ value,
48
+ identity,
49
+ }: {
50
+ value: unknown;
51
+ identity: MappingIdentity;
52
+ }): string | undefined => {
53
+ const values: (string | number)[] = [];
54
+ for (const component of identity.components) {
55
+ let selected: unknown;
56
+ for (const path of component.paths) {
57
+ selected = valueAt({ value, path: path.split(".") });
58
+ if (selected !== undefined && selected !== null) break;
59
+ }
60
+ if (selected === undefined || selected === null) {
61
+ selected =
62
+ component.defaultWhen && values[component.defaultWhen.component]
63
+ ? component.defaultWhen.value
64
+ : component.default;
65
+ }
66
+ if (typeof selected !== "string" && typeof selected !== "number")
67
+ return undefined;
68
+ values.push(selected);
69
+ }
70
+ return JSON.stringify(values);
71
+ };
72
+
73
+ const hasMappings = ({
74
+ node,
75
+ value,
76
+ local = false,
77
+ }: {
78
+ node: MappingProjection;
79
+ value: unknown;
80
+ local?: boolean;
81
+ }): boolean => {
82
+ if (value === undefined || value === null) return false;
83
+ if (node.mapping) return true;
84
+ const items = node.items;
85
+ if (items)
86
+ return (
87
+ Array.isArray(value) &&
88
+ value.some((entry) => hasMappings({ node: items, value: entry, local }))
89
+ );
90
+ return Object.entries(node.properties ?? {}).some(([key, child]) =>
91
+ hasMappings({
92
+ node: child,
93
+ value: valueAt({
94
+ value,
95
+ path: local ? [key] : (child.sourcePath ?? [key]),
96
+ }),
97
+ local,
98
+ }),
99
+ );
100
+ };
101
+
102
+ export const mappingAssignments = ({
103
+ projection,
104
+ local,
105
+ remote,
106
+ }: {
107
+ projection: MappingProjection;
108
+ local: unknown;
109
+ remote: unknown;
110
+ }): { assignments: MappingAssignment[]; errors: string[] } => {
111
+ const assignments: MappingAssignment[] = [];
112
+ const errors: string[] = [];
113
+ const visit = ({
114
+ node,
115
+ localValue,
116
+ remoteValue,
117
+ path,
118
+ }: {
119
+ node: MappingProjection;
120
+ localValue: unknown;
121
+ remoteValue: unknown;
122
+ path: (string | number)[];
123
+ }): void => {
124
+ if (node.mapping) {
125
+ if (remoteValue === undefined || remoteValue === null) {
126
+ if (localValue !== undefined) assignments.push({ path, text: null });
127
+ return;
128
+ }
129
+ assignments.push({ path, text: JSON.stringify(remoteValue) });
130
+ return;
131
+ }
132
+ if (
133
+ !hasMappings({ node, value: remoteValue }) &&
134
+ !hasMappings({ node, value: localValue, local: true })
135
+ )
136
+ return;
137
+ if (remoteValue === undefined || remoteValue === null) return;
138
+ if (typeof localValue === "symbol") {
139
+ errors.push(`${path.join(".")}: cannot address a computed value`);
140
+ return;
141
+ }
142
+ if (localValue === undefined || localValue === null) {
143
+ errors.push(
144
+ `${path.join(".")}: mapping container is not stated in the fixture`,
145
+ );
146
+ return;
147
+ }
148
+ if (node.items) {
149
+ if (
150
+ !Array.isArray(localValue) ||
151
+ !Array.isArray(remoteValue) ||
152
+ !node.identity
153
+ ) {
154
+ errors.push(`${path.join(".")}: array identity is unavailable`);
155
+ return;
156
+ }
157
+ const identity = node.identity;
158
+ const localKeys = localValue.map((value) =>
159
+ mappingIdentityOf({ value, identity }),
160
+ );
161
+ const remoteKeys = remoteValue.map((value) =>
162
+ valueAt({ value, path: identity.responseField.split(".") }),
163
+ );
164
+ for (const [index, value] of localValue.entries()) {
165
+ const key = localKeys[index];
166
+ const matches = remoteKeys.flatMap((candidate, position) =>
167
+ candidate === key ? [position] : [],
168
+ );
169
+ const match = matches[0];
170
+ if (
171
+ key === undefined ||
172
+ match === undefined ||
173
+ localKeys.filter((candidate) => candidate === key).length !== 1 ||
174
+ matches.length !== 1
175
+ ) {
176
+ errors.push(
177
+ `${[...path, index].join(".")}: no unique server identity match`,
178
+ );
179
+ continue;
180
+ }
181
+ visit({
182
+ node: node.items,
183
+ localValue: value,
184
+ remoteValue: remoteValue[match],
185
+ path: [...path, index],
186
+ });
187
+ }
188
+ return;
189
+ }
190
+ for (const [key, child] of Object.entries(node.properties ?? {})) {
191
+ visit({
192
+ node: child,
193
+ localValue: valueAt({ value: localValue, path: [key] }),
194
+ remoteValue: valueAt({
195
+ value: remoteValue,
196
+ path: child.sourcePath ?? [key],
197
+ }),
198
+ path: [...path, key],
199
+ });
200
+ }
201
+ };
202
+ visit({ node: projection, localValue: local, remoteValue: remote, path: [] });
203
+ return { assignments, errors };
204
+ };