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,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
|
+
};
|