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.
- 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 -146296
- 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 -562
- 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,522 @@
|
|
|
1
|
+
// Copied by @autumn/atmn-generator from packages/atmn-generator/src/emit/runtime/emitFixture.ts.
|
|
2
|
+
// Do not edit — change that file and run `bun generate` instead.
|
|
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
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type CollectionSpec = {
|
|
11
|
+
readonly builder: string;
|
|
12
|
+
readonly idField: string;
|
|
13
|
+
readonly responseIdField: string;
|
|
14
|
+
readonly keys: readonly string[];
|
|
15
|
+
/** Fixture paths the type demands: written even when the row's value is null. */
|
|
16
|
+
readonly required?: readonly string[];
|
|
17
|
+
/** Every fixture path a config may state, collection-relative (no `entitlementId`, `versioning`, …). */
|
|
18
|
+
readonly paths: readonly string[];
|
|
19
|
+
/** The emission default at each fixture path; a pulled value equal to it is left out. */
|
|
20
|
+
readonly defaults?: Readonly<Record<string, unknown>>;
|
|
21
|
+
/** Rows sharing idField are versions; pull keys them by id and slug. */
|
|
22
|
+
readonly versioned?: boolean;
|
|
23
|
+
/** Whether pull can address entries by idField alone. */
|
|
24
|
+
readonly pull: boolean;
|
|
25
|
+
/** Wire-named, item-rooted paths kept for existing catalogs only. */
|
|
26
|
+
readonly deprecated?: readonly { path: string; reason: string }[];
|
|
27
|
+
/** Set when the item is a union: one builder per branch, keyed by its wrapper. */
|
|
28
|
+
readonly branches?: readonly BranchSpec[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** One branch of a union item: the fixture lives under `key`, not at the root. */
|
|
32
|
+
export type BranchSpec = {
|
|
33
|
+
readonly builder: string;
|
|
34
|
+
readonly key: string;
|
|
35
|
+
readonly idField: string;
|
|
36
|
+
readonly keys: readonly string[];
|
|
37
|
+
readonly required?: readonly string[];
|
|
38
|
+
readonly paths: readonly string[];
|
|
39
|
+
readonly defaults?: Readonly<Record<string, unknown>>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** The branch an entry or row states, or undefined when it states none. */
|
|
43
|
+
export const branchOf = ({
|
|
44
|
+
spec,
|
|
45
|
+
row,
|
|
46
|
+
}: {
|
|
47
|
+
spec: CollectionSpec;
|
|
48
|
+
row: Record<string, unknown>;
|
|
49
|
+
}): BranchSpec | undefined =>
|
|
50
|
+
spec.branches?.find((branch) => row[branch.key] !== undefined);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A branched row read as if its branch were the whole collection: the builder,
|
|
54
|
+
* keys and paths become the branch's and the body becomes the row, so
|
|
55
|
+
* everything downstream stays union-blind.
|
|
56
|
+
*/
|
|
57
|
+
export const resolveBranch = ({
|
|
58
|
+
spec,
|
|
59
|
+
row,
|
|
60
|
+
}: {
|
|
61
|
+
spec: CollectionSpec;
|
|
62
|
+
row: Record<string, unknown>;
|
|
63
|
+
}): { spec: CollectionSpec; row: Record<string, unknown> } => {
|
|
64
|
+
const branch = branchOf({ spec, row });
|
|
65
|
+
if (!branch) return { spec, row };
|
|
66
|
+
return {
|
|
67
|
+
spec: branchSpecOf({ spec, branch }),
|
|
68
|
+
row: row[branch.key] as Record<string, unknown>,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const branchSpecOf = ({
|
|
73
|
+
spec,
|
|
74
|
+
branch,
|
|
75
|
+
}: {
|
|
76
|
+
spec: CollectionSpec;
|
|
77
|
+
branch: BranchSpec;
|
|
78
|
+
}): CollectionSpec => ({
|
|
79
|
+
...spec,
|
|
80
|
+
builder: branch.builder,
|
|
81
|
+
idField: branch.idField,
|
|
82
|
+
responseIdField: branch.idField,
|
|
83
|
+
keys: branch.keys,
|
|
84
|
+
required: branch.required,
|
|
85
|
+
paths: branch.paths,
|
|
86
|
+
defaults: branch.defaults,
|
|
87
|
+
branches: undefined,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/** Every shape an entry of the collection can take; just itself when unbranched. */
|
|
91
|
+
export const branchSpecs = ({
|
|
92
|
+
spec,
|
|
93
|
+
}: {
|
|
94
|
+
spec: CollectionSpec;
|
|
95
|
+
}): CollectionSpec[] =>
|
|
96
|
+
spec.branches?.map((branch) => branchSpecOf({ spec, branch })) ?? [spec];
|
|
97
|
+
|
|
98
|
+
export type SingletonSpec = {
|
|
99
|
+
/** The request-body field the object is sent as. */
|
|
100
|
+
readonly wireKey: string;
|
|
101
|
+
/** Fixture key, wire key and the server's default, in spec order. */
|
|
102
|
+
readonly fields: readonly {
|
|
103
|
+
readonly key: string;
|
|
104
|
+
readonly wireKey: string;
|
|
105
|
+
readonly default: unknown;
|
|
106
|
+
}[];
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type EmitFixtureContext = {
|
|
110
|
+
readonly featureTypes?: Readonly<Record<string, string>>;
|
|
111
|
+
/** Fixture builders registered for nested array paths, such as `variants`. */
|
|
112
|
+
readonly nestedBuilders?: Readonly<Record<string, string>>;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const PLAIN_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
116
|
+
|
|
117
|
+
const keyText = (key: string): string =>
|
|
118
|
+
PLAIN_IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A path the schema never describes further (`metadata`, a usage-limit
|
|
122
|
+
* filter's `properties` bag) is free-form: nothing declares its shape, so the
|
|
123
|
+
* row's own data IS the fixture's data, copied through untouched.
|
|
124
|
+
*/
|
|
125
|
+
const serializeVerbatim = ({
|
|
126
|
+
value,
|
|
127
|
+
indent,
|
|
128
|
+
}: {
|
|
129
|
+
value: unknown;
|
|
130
|
+
indent: string;
|
|
131
|
+
}): string => {
|
|
132
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
133
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
134
|
+
return String(value);
|
|
135
|
+
if (value === null || value === undefined) return "null";
|
|
136
|
+
|
|
137
|
+
if (Array.isArray(value)) {
|
|
138
|
+
if (value.length === 0) return "[]";
|
|
139
|
+
const items = value.map(
|
|
140
|
+
(entry) =>
|
|
141
|
+
`${indent}\t${serializeVerbatim({ value: entry, indent: `${indent}\t` })},`,
|
|
142
|
+
);
|
|
143
|
+
return `[\n${items.join("\n")}\n${indent}]`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const entries = Object.entries(value as Record<string, unknown>).filter(
|
|
147
|
+
([, entry]) => entry !== undefined,
|
|
148
|
+
);
|
|
149
|
+
if (entries.length === 0) return "{}";
|
|
150
|
+
const items = entries.map(
|
|
151
|
+
([key, entry]) =>
|
|
152
|
+
`${indent}\t${keyText(key)}: ${serializeVerbatim({ value: entry, indent: `${indent}\t` })},`,
|
|
153
|
+
);
|
|
154
|
+
return `{\n${items.join("\n")}\n${indent}}`;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
type PathIndex = {
|
|
158
|
+
paths: ReadonlySet<string>;
|
|
159
|
+
/** Container paths whose values are a record: every key is the user's data, not the schema's. */
|
|
160
|
+
records: ReadonlySet<string>;
|
|
161
|
+
/** Container paths the schema declares at least one child under. */
|
|
162
|
+
parents: ReadonlySet<string>;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const pathIndexOf = (specPaths: readonly string[]): PathIndex => {
|
|
166
|
+
const paths = new Set(specPaths);
|
|
167
|
+
const records = new Set<string>();
|
|
168
|
+
const parents = new Set<string>();
|
|
169
|
+
for (const path of specPaths) {
|
|
170
|
+
const segments = path.split(".");
|
|
171
|
+
for (let depth = 1; depth < segments.length; depth++) {
|
|
172
|
+
parents.add(segments.slice(0, depth).join("."));
|
|
173
|
+
}
|
|
174
|
+
const wildcard = segments.indexOf("*");
|
|
175
|
+
if (wildcard > 0) records.add(segments.slice(0, wildcard).join("."));
|
|
176
|
+
}
|
|
177
|
+
return { paths, records, parents };
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Recurses with the value's elided fixture path so nested extras a server row
|
|
182
|
+
* carries (`entitlementId`, an expanded `plan`, …) never leak into a fixture
|
|
183
|
+
* the type does not declare. Array entries keep the parent's path; a record's
|
|
184
|
+
* values keep every key, since the keys are the user's data; a path with no
|
|
185
|
+
* declared children at all falls through to `serializeVerbatim`.
|
|
186
|
+
*/
|
|
187
|
+
const serialize = ({
|
|
188
|
+
value,
|
|
189
|
+
path,
|
|
190
|
+
index,
|
|
191
|
+
indent,
|
|
192
|
+
includeMappings,
|
|
193
|
+
required,
|
|
194
|
+
context,
|
|
195
|
+
}: {
|
|
196
|
+
value: unknown;
|
|
197
|
+
path: string;
|
|
198
|
+
index: PathIndex;
|
|
199
|
+
indent: string;
|
|
200
|
+
includeMappings: boolean;
|
|
201
|
+
required: ReadonlySet<string>;
|
|
202
|
+
context?: EmitFixtureContext;
|
|
203
|
+
}): string => {
|
|
204
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
205
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
206
|
+
return String(value);
|
|
207
|
+
if (value === null || value === undefined) return "null";
|
|
208
|
+
|
|
209
|
+
if (Array.isArray(value)) {
|
|
210
|
+
if (value.length === 0) return "[]";
|
|
211
|
+
const nestedBuilder = context?.nestedBuilders?.[path];
|
|
212
|
+
const items = value.map((entry) => {
|
|
213
|
+
const serialized = serialize({
|
|
214
|
+
includeMappings,
|
|
215
|
+
required,
|
|
216
|
+
value: entry,
|
|
217
|
+
path,
|
|
218
|
+
index,
|
|
219
|
+
indent: `${indent}\t`,
|
|
220
|
+
context,
|
|
221
|
+
});
|
|
222
|
+
const fixture =
|
|
223
|
+
nestedBuilder === undefined
|
|
224
|
+
? serialized
|
|
225
|
+
: `${nestedBuilder}(${serialized})`;
|
|
226
|
+
return `${indent}\t${fixture},`;
|
|
227
|
+
});
|
|
228
|
+
return `[\n${items.join("\n")}\n${indent}]`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const isRecord = index.records.has(path);
|
|
232
|
+
if (!isRecord && !index.parents.has(path))
|
|
233
|
+
return serializeVerbatim({ value, indent });
|
|
234
|
+
|
|
235
|
+
const childPathOf = (key: string): string =>
|
|
236
|
+
isRecord ? `${path}.*` : `${path}.${key}`;
|
|
237
|
+
// A null from the server means unset, and omission says the same on the wire
|
|
238
|
+
// — unless the fixture type demands the key, which omission would not compile.
|
|
239
|
+
const entries = Object.entries(value as Record<string, unknown>).filter(
|
|
240
|
+
([key, entry]) =>
|
|
241
|
+
entry !== undefined &&
|
|
242
|
+
(entry !== null || required.has(childPathOf(key))) &&
|
|
243
|
+
(includeMappings || key !== "processors") &&
|
|
244
|
+
(isRecord || index.paths.has(childPathOf(key))),
|
|
245
|
+
);
|
|
246
|
+
if (entries.length === 0) return "{}";
|
|
247
|
+
const items = entries.map(([key, entry]) => {
|
|
248
|
+
const childPath = childPathOf(key);
|
|
249
|
+
return `${indent}\t${keyText(key)}: ${serialize({ includeMappings, required, value: entry, path: childPath, index, indent: `${indent}\t`, context })},`;
|
|
250
|
+
});
|
|
251
|
+
return `{\n${items.join("\n")}\n${indent}}`;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* A value at its spec default reads the same when omitted, so it is not
|
|
256
|
+
* written; a container whose every child was elided is not written either.
|
|
257
|
+
* `items` and other arrays carry no default, so an empty one still states
|
|
258
|
+
* "none". A path the type demands is kept whatever its value.
|
|
259
|
+
*/
|
|
260
|
+
const pruneDefaults = ({
|
|
261
|
+
value,
|
|
262
|
+
path,
|
|
263
|
+
index,
|
|
264
|
+
defaults,
|
|
265
|
+
required,
|
|
266
|
+
context,
|
|
267
|
+
}: {
|
|
268
|
+
value: unknown;
|
|
269
|
+
path: string;
|
|
270
|
+
index: PathIndex;
|
|
271
|
+
defaults: Readonly<Record<string, unknown>>;
|
|
272
|
+
required: ReadonlySet<string>;
|
|
273
|
+
context?: EmitFixtureContext;
|
|
274
|
+
}): unknown => {
|
|
275
|
+
if (required.has(path)) return value;
|
|
276
|
+
if (path in defaults && valuesEqual(value, defaults[path])) return undefined;
|
|
277
|
+
if (value === null || typeof value !== "object") return value;
|
|
278
|
+
// An entry that is all defaults still says "one row here", so it stays as {}.
|
|
279
|
+
if (Array.isArray(value)) {
|
|
280
|
+
return value.map(
|
|
281
|
+
(entry) =>
|
|
282
|
+
pruneDefaults({
|
|
283
|
+
value: entry,
|
|
284
|
+
path,
|
|
285
|
+
index,
|
|
286
|
+
defaults,
|
|
287
|
+
required,
|
|
288
|
+
context,
|
|
289
|
+
}) ?? (entry !== null && typeof entry === "object" ? {} : entry),
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
const isRecord = index.records.has(path);
|
|
293
|
+
if (!isRecord && !index.parents.has(path)) return value;
|
|
294
|
+
const record = value as Record<string, unknown>;
|
|
295
|
+
const isBooleanFeatureItem =
|
|
296
|
+
typeof record.featureId === "string" &&
|
|
297
|
+
context?.featureTypes?.[record.featureId] === "boolean";
|
|
298
|
+
const pruned: Record<string, unknown> = {};
|
|
299
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
300
|
+
if (isBooleanFeatureItem && (key === "included" || key === "unlimited"))
|
|
301
|
+
continue;
|
|
302
|
+
const childPath = isRecord ? `${path}.*` : `${path}.${key}`;
|
|
303
|
+
const child = pruneDefaults({
|
|
304
|
+
value: entry,
|
|
305
|
+
path: childPath,
|
|
306
|
+
index,
|
|
307
|
+
defaults,
|
|
308
|
+
required,
|
|
309
|
+
context,
|
|
310
|
+
});
|
|
311
|
+
if (child !== undefined) pruned[key] = child;
|
|
312
|
+
}
|
|
313
|
+
// An object the spec describes that ends up empty said nothing; a record
|
|
314
|
+
// (metadata) is the user's data and stays whatever it holds.
|
|
315
|
+
if (!isRecord && Object.keys(pruned).length === 0 && path !== "")
|
|
316
|
+
return undefined;
|
|
317
|
+
return pruned;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
/** A half-nulled display would fail the config lint, so it must not be written. */
|
|
321
|
+
const displayOf = (value: unknown): unknown => {
|
|
322
|
+
if (value === null || typeof value !== "object") return undefined;
|
|
323
|
+
const display = value as Record<string, unknown>;
|
|
324
|
+
return typeof display.singular === "string" &&
|
|
325
|
+
typeof display.plural === "string"
|
|
326
|
+
? display
|
|
327
|
+
: undefined;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const creditSchemaOf = (value: unknown): unknown => {
|
|
331
|
+
if (!Array.isArray(value)) return value;
|
|
332
|
+
return value.filter((entry) => {
|
|
333
|
+
if (entry === null || typeof entry !== "object") return true;
|
|
334
|
+
return (entry as Record<string, unknown>).meteredFeatureId !== "";
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const rowValueOf = ({
|
|
339
|
+
spec,
|
|
340
|
+
key,
|
|
341
|
+
row,
|
|
342
|
+
includeMappings,
|
|
343
|
+
}: {
|
|
344
|
+
spec: CollectionSpec;
|
|
345
|
+
key: string;
|
|
346
|
+
row: Record<string, unknown>;
|
|
347
|
+
includeMappings: boolean;
|
|
348
|
+
}): unknown => {
|
|
349
|
+
if (key === spec.idField) return row[spec.responseIdField];
|
|
350
|
+
// Pulled rows are never archived, so `archived` is not a pull's business.
|
|
351
|
+
if (key === "archived") return undefined;
|
|
352
|
+
if (key === "processors" && !includeMappings) return undefined;
|
|
353
|
+
const value = row[key];
|
|
354
|
+
if (key === "display") return displayOf(value);
|
|
355
|
+
if (key === "creditSchema") return creditSchemaOf(value);
|
|
356
|
+
// A deprecated field is kept only while it carries data.
|
|
357
|
+
if (
|
|
358
|
+
isDeprecatedKey({ spec, key }) &&
|
|
359
|
+
Array.isArray(value) &&
|
|
360
|
+
value.length === 0
|
|
361
|
+
)
|
|
362
|
+
return undefined;
|
|
363
|
+
return value;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const valuesEqual = (left: unknown, right: unknown): boolean => {
|
|
367
|
+
if (Object.is(left, right)) return true;
|
|
368
|
+
if (
|
|
369
|
+
typeof left !== "object" ||
|
|
370
|
+
typeof right !== "object" ||
|
|
371
|
+
left === null ||
|
|
372
|
+
right === null
|
|
373
|
+
)
|
|
374
|
+
return false;
|
|
375
|
+
if (Array.isArray(left) !== Array.isArray(right)) return false;
|
|
376
|
+
const leftRecord = left as Record<string, unknown>;
|
|
377
|
+
const rightRecord = right as Record<string, unknown>;
|
|
378
|
+
const keys = new Set([
|
|
379
|
+
...Object.keys(leftRecord),
|
|
380
|
+
...Object.keys(rightRecord),
|
|
381
|
+
]);
|
|
382
|
+
for (const key of keys) {
|
|
383
|
+
if (!valuesEqual(leftRecord[key], rightRecord[key])) return false;
|
|
384
|
+
}
|
|
385
|
+
return true;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const isDeprecatedKey = ({
|
|
389
|
+
spec,
|
|
390
|
+
key,
|
|
391
|
+
}: {
|
|
392
|
+
spec: CollectionSpec;
|
|
393
|
+
key: string;
|
|
394
|
+
}): boolean =>
|
|
395
|
+
(spec.deprecated ?? []).some((entry) => camelOf(entry.path) === key);
|
|
396
|
+
|
|
397
|
+
const camelOf = (snake: string): string =>
|
|
398
|
+
snake.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
399
|
+
|
|
400
|
+
/** One top-level fixture property as `key: value` text, or null when the row omits it. */
|
|
401
|
+
export const emitFixtureProperty = ({
|
|
402
|
+
spec: collectionSpec,
|
|
403
|
+
row: collectionRow,
|
|
404
|
+
key,
|
|
405
|
+
includeMappings,
|
|
406
|
+
indent,
|
|
407
|
+
context,
|
|
408
|
+
}: {
|
|
409
|
+
spec: CollectionSpec;
|
|
410
|
+
row: Record<string, unknown>;
|
|
411
|
+
key: string;
|
|
412
|
+
includeMappings: boolean;
|
|
413
|
+
indent: string;
|
|
414
|
+
context?: EmitFixtureContext;
|
|
415
|
+
}): string | null => {
|
|
416
|
+
const { spec, row } = resolveBranch({
|
|
417
|
+
spec: collectionSpec,
|
|
418
|
+
row: collectionRow,
|
|
419
|
+
});
|
|
420
|
+
const required = new Set(spec.required ?? []);
|
|
421
|
+
const index = pathIndexOf(spec.paths);
|
|
422
|
+
const value = pruneDefaults({
|
|
423
|
+
value: rowValueOf({ spec, key, row, includeMappings }),
|
|
424
|
+
path: key,
|
|
425
|
+
index,
|
|
426
|
+
defaults: spec.defaults ?? {},
|
|
427
|
+
required,
|
|
428
|
+
context,
|
|
429
|
+
});
|
|
430
|
+
if (value === undefined) return null;
|
|
431
|
+
if (value === null && !required.has(key)) return null;
|
|
432
|
+
return serialize({
|
|
433
|
+
includeMappings,
|
|
434
|
+
required,
|
|
435
|
+
value,
|
|
436
|
+
path: key,
|
|
437
|
+
index,
|
|
438
|
+
indent: `${indent}\t`,
|
|
439
|
+
context,
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
/** One element of a nested array path (`variants`) as fixture text, builder-wrapped. */
|
|
444
|
+
export const emitNestedFixture = ({
|
|
445
|
+
spec,
|
|
446
|
+
path,
|
|
447
|
+
row,
|
|
448
|
+
includeMappings,
|
|
449
|
+
indent,
|
|
450
|
+
context,
|
|
451
|
+
}: {
|
|
452
|
+
spec: CollectionSpec;
|
|
453
|
+
path: string;
|
|
454
|
+
row: Record<string, unknown>;
|
|
455
|
+
includeMappings: boolean;
|
|
456
|
+
indent: string;
|
|
457
|
+
context?: EmitFixtureContext;
|
|
458
|
+
}): string => {
|
|
459
|
+
const required = new Set(spec.required ?? []);
|
|
460
|
+
const index = pathIndexOf(spec.paths);
|
|
461
|
+
const value =
|
|
462
|
+
pruneDefaults({
|
|
463
|
+
value: row,
|
|
464
|
+
path,
|
|
465
|
+
index,
|
|
466
|
+
defaults: spec.defaults ?? {},
|
|
467
|
+
required,
|
|
468
|
+
context,
|
|
469
|
+
}) ?? {};
|
|
470
|
+
const serialized = serialize({
|
|
471
|
+
includeMappings,
|
|
472
|
+
required,
|
|
473
|
+
value,
|
|
474
|
+
path,
|
|
475
|
+
index,
|
|
476
|
+
indent,
|
|
477
|
+
context,
|
|
478
|
+
});
|
|
479
|
+
const builder = context?.nestedBuilders?.[path];
|
|
480
|
+
return builder === undefined ? serialized : `${builder}(${serialized})`;
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
export const emitFixture = ({
|
|
484
|
+
spec: collectionSpec,
|
|
485
|
+
row: collectionRow,
|
|
486
|
+
includeMappings,
|
|
487
|
+
indent,
|
|
488
|
+
context,
|
|
489
|
+
}: {
|
|
490
|
+
spec: CollectionSpec;
|
|
491
|
+
row: Record<string, unknown>;
|
|
492
|
+
includeMappings: boolean;
|
|
493
|
+
indent: string;
|
|
494
|
+
context?: EmitFixtureContext;
|
|
495
|
+
}): string => {
|
|
496
|
+
const { spec, row } = resolveBranch({
|
|
497
|
+
spec: collectionSpec,
|
|
498
|
+
row: collectionRow,
|
|
499
|
+
});
|
|
500
|
+
const index = pathIndexOf(spec.paths);
|
|
501
|
+
const required = new Set(spec.required ?? []);
|
|
502
|
+
const lines: string[] = [`${spec.builder}({`];
|
|
503
|
+
for (const key of spec.keys) {
|
|
504
|
+
const value = pruneDefaults({
|
|
505
|
+
value: rowValueOf({ spec, key, row, includeMappings }),
|
|
506
|
+
path: key,
|
|
507
|
+
index,
|
|
508
|
+
defaults: spec.defaults ?? {},
|
|
509
|
+
required,
|
|
510
|
+
context,
|
|
511
|
+
});
|
|
512
|
+
if (value === undefined) continue;
|
|
513
|
+
// A required key states null rather than vanishing: the fixture type
|
|
514
|
+
// demands it, so dropping it emits source that does not compile.
|
|
515
|
+
if (value === null && !required.has(key)) continue;
|
|
516
|
+
lines.push(
|
|
517
|
+
`${indent}\t${keyText(key)}: ${serialize({ includeMappings, required, value, path: key, index, indent: `${indent}\t`, context })},`,
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
lines.push(`${indent}})`);
|
|
521
|
+
return lines.join("\n");
|
|
522
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Generated by @autumn/atmn-generator from packages/openapi/openapi-internal.yml.
|
|
2
|
+
// Do not edit — run `bun generate` in packages/atmn-generator instead.
|
|
3
|
+
|
|
4
|
+
export type Feature = {
|
|
5
|
+
/** The name of the feature. */
|
|
6
|
+
name: string;
|
|
7
|
+
/** The type of the feature. 'single_use' features are consumed, like API calls, tokens, or messages. 'continuous_use' features are allocated, like seats, workspaces, or projects. 'credit_system' features are schemas that unify multiple 'single_use' features into a single credit system. */
|
|
8
|
+
type: "boolean" | "metered" | "credit_system" | "ai_credit_system";
|
|
9
|
+
/** Whether this feature is consumable. A consumable feature is one that periodically resets and is consumed rather than allocated (like credits, API requests, etc.). Applicable only for 'metered' features. */
|
|
10
|
+
consumable?: boolean;
|
|
11
|
+
/** A schema that maps metered feature IDs to flat or graduated credit costs. For classic credit systems only — AI credit systems use model_markups instead. */
|
|
12
|
+
creditSchema?: Array<
|
|
13
|
+
| {
|
|
14
|
+
/** ID of the metered feature that draws from this credit system. */
|
|
15
|
+
meteredFeatureId: string;
|
|
16
|
+
/** Number of metered-feature units priced together. Defaults to one when omitted. */
|
|
17
|
+
billingUnits?: number;
|
|
18
|
+
/** Named rates chosen by event properties. The most specific match sets the rate; with no match the item's own rate applies. */
|
|
19
|
+
dimensions?: Record<
|
|
20
|
+
string,
|
|
21
|
+
| {
|
|
22
|
+
/** Event properties this entry applies to. Every key must equal the tracked property, compared as strings. */
|
|
23
|
+
match: Record<string, string | number | boolean>;
|
|
24
|
+
/** Breaks ties between dimensions that match the same number of keys. Higher wins. */
|
|
25
|
+
priority?: number;
|
|
26
|
+
tierBehavior: "graduated";
|
|
27
|
+
tiers: Array<{
|
|
28
|
+
/** Inclusive upper usage boundary for this graduated tier. The final tier must be 'inf'. */
|
|
29
|
+
to: number | "inf";
|
|
30
|
+
/** Credits consumed per billing-unit group within this tier. */
|
|
31
|
+
creditCost: number;
|
|
32
|
+
}>;
|
|
33
|
+
}
|
|
34
|
+
| {
|
|
35
|
+
/** Event properties this entry applies to. Every key must equal the tracked property, compared as strings. */
|
|
36
|
+
match: Record<string, string | number | boolean>;
|
|
37
|
+
/** Breaks ties between dimensions that match the same number of keys. Higher wins. */
|
|
38
|
+
priority?: number;
|
|
39
|
+
/** Credits consumed per billing-unit group when this dimension matches. */
|
|
40
|
+
creditCost: number;
|
|
41
|
+
}
|
|
42
|
+
>;
|
|
43
|
+
/** Named adjustments chosen by event properties. Every match applies: factors multiply, then adds are summed. */
|
|
44
|
+
multipliers?: Record<
|
|
45
|
+
string,
|
|
46
|
+
{
|
|
47
|
+
/** Event properties this entry applies to. Every key must equal the tracked property, compared as strings. */
|
|
48
|
+
match: Record<string, string | number | boolean>;
|
|
49
|
+
/** Multiplies the matched rate. All matching multipliers stack. */
|
|
50
|
+
factor?: number;
|
|
51
|
+
/** Added to the rate after every factor is applied, in credits per billing-unit group. */
|
|
52
|
+
add?: number;
|
|
53
|
+
}
|
|
54
|
+
>;
|
|
55
|
+
tierBehavior: "graduated";
|
|
56
|
+
tiers: Array<{
|
|
57
|
+
/** Inclusive upper usage boundary for this graduated tier. The final tier must be 'inf'. */
|
|
58
|
+
to: number | "inf";
|
|
59
|
+
/** Credits consumed per billing-unit group within this tier. */
|
|
60
|
+
creditCost: number;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
| {
|
|
64
|
+
/** ID of the metered feature that draws from this credit system. */
|
|
65
|
+
meteredFeatureId: string;
|
|
66
|
+
/** Number of metered-feature units priced together. Defaults to one when omitted. */
|
|
67
|
+
billingUnits?: number;
|
|
68
|
+
/** Named rates chosen by event properties. The most specific match sets the rate; with no match the item's own rate applies. */
|
|
69
|
+
dimensions?: Record<
|
|
70
|
+
string,
|
|
71
|
+
| {
|
|
72
|
+
/** Event properties this entry applies to. Every key must equal the tracked property, compared as strings. */
|
|
73
|
+
match: Record<string, string | number | boolean>;
|
|
74
|
+
/** Breaks ties between dimensions that match the same number of keys. Higher wins. */
|
|
75
|
+
priority?: number;
|
|
76
|
+
tierBehavior: "graduated";
|
|
77
|
+
tiers: Array<{
|
|
78
|
+
/** Inclusive upper usage boundary for this graduated tier. The final tier must be 'inf'. */
|
|
79
|
+
to: number | "inf";
|
|
80
|
+
/** Credits consumed per billing-unit group within this tier. */
|
|
81
|
+
creditCost: number;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
/** Event properties this entry applies to. Every key must equal the tracked property, compared as strings. */
|
|
86
|
+
match: Record<string, string | number | boolean>;
|
|
87
|
+
/** Breaks ties between dimensions that match the same number of keys. Higher wins. */
|
|
88
|
+
priority?: number;
|
|
89
|
+
/** Credits consumed per billing-unit group when this dimension matches. */
|
|
90
|
+
creditCost: number;
|
|
91
|
+
}
|
|
92
|
+
>;
|
|
93
|
+
/** Named adjustments chosen by event properties. Every match applies: factors multiply, then adds are summed. */
|
|
94
|
+
multipliers?: Record<
|
|
95
|
+
string,
|
|
96
|
+
{
|
|
97
|
+
/** Event properties this entry applies to. Every key must equal the tracked property, compared as strings. */
|
|
98
|
+
match: Record<string, string | number | boolean>;
|
|
99
|
+
/** Multiplies the matched rate. All matching multipliers stack. */
|
|
100
|
+
factor?: number;
|
|
101
|
+
/** Added to the rate after every factor is applied, in credits per billing-unit group. */
|
|
102
|
+
add?: number;
|
|
103
|
+
}
|
|
104
|
+
>;
|
|
105
|
+
/** Credits consumed per billing-unit group. */
|
|
106
|
+
creditCost: number;
|
|
107
|
+
}
|
|
108
|
+
>;
|
|
109
|
+
/** Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration. */
|
|
110
|
+
modelMarkups?: Record<
|
|
111
|
+
string,
|
|
112
|
+
{
|
|
113
|
+
markup?: number;
|
|
114
|
+
inputCost?: number;
|
|
115
|
+
outputCost?: number;
|
|
116
|
+
}
|
|
117
|
+
> | null;
|
|
118
|
+
/** Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free. */
|
|
119
|
+
defaultMarkup?: number;
|
|
120
|
+
/** Per-provider default markup percentages for AI credit systems. Provider keys match the first segment of model_id. */
|
|
121
|
+
providerMarkups?: Record<
|
|
122
|
+
string,
|
|
123
|
+
{
|
|
124
|
+
markup: number;
|
|
125
|
+
}
|
|
126
|
+
> | null;
|
|
127
|
+
eventNames?: Array<string>;
|
|
128
|
+
/** The ID of the feature to create. */
|
|
129
|
+
featureId: string;
|
|
130
|
+
} & {
|
|
131
|
+
/** Address an existing feature by its stable id. Omit when creating — the server generates one. A differing feature_id alongside it is a rename. */
|
|
132
|
+
internalId?: string;
|
|
133
|
+
/** Archive or unarchive the feature. Omit to leave archived state unchanged. */
|
|
134
|
+
archived?: boolean;
|
|
135
|
+
/** Processor mappings for this feature. Omit keeps the current Stripe product and meter. */
|
|
136
|
+
processors?: {
|
|
137
|
+
stripe?: {
|
|
138
|
+
/** Stripe product ID this feature's usage prices bill under. */
|
|
139
|
+
productId?: string;
|
|
140
|
+
/** Stripe meter ID used to create this feature's metered price. */
|
|
141
|
+
meterId?: string;
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const feature = (input: Feature): Feature => input;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Generated by @autumn/atmn-generator from shared/utils/planV1Utils/diff/planPreviousAttributeLabels.ts
|
|
2
|
+
// and shared/utils/orgUtils/orgSettingsLabels.ts.
|
|
3
|
+
// Do not edit — run `bun generate` in packages/atmn-generator instead.
|
|
4
|
+
|
|
5
|
+
/** Wire-named (snake_case) keys of previous_attributes → what the terminal prints. */
|
|
6
|
+
export const PREVIOUS_ATTRIBUTE_LABELS: Readonly<Record<string, string>> = {
|
|
7
|
+
name: "Name",
|
|
8
|
+
description: "Description",
|
|
9
|
+
group: "Group",
|
|
10
|
+
add_on: "Add-on",
|
|
11
|
+
auto_enable: "Default plan",
|
|
12
|
+
free_trial: "Free trial",
|
|
13
|
+
config: "Config",
|
|
14
|
+
active: "Active version",
|
|
15
|
+
billing_controls: "Billing controls",
|
|
16
|
+
processors: "Processor mappings",
|
|
17
|
+
base_variant_id: "Base plan",
|
|
18
|
+
base_version: "Base version",
|
|
19
|
+
base_version_slug: "Base version slug",
|
|
20
|
+
cancel_on_past_due: "Cancel on past due",
|
|
21
|
+
reverse_deduction_order: "Reverse deduction order",
|
|
22
|
+
block_overdue_entitlements: "Block access while overdue",
|
|
23
|
+
invoice_memos: "Invoice memos",
|
|
24
|
+
disable_overage_billing: "Disable overage billing",
|
|
25
|
+
persist_free_overage: "Pay down overages",
|
|
26
|
+
automatic_tax: "Automatic tax",
|
|
27
|
+
multi_currency: "Multi-currency",
|
|
28
|
+
id: "ID",
|
|
29
|
+
};
|