@genn-inc/cluebase-cli 0.0.1
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 +101 -0
- package/bin/cluebase-cli.mjs +11 -0
- package/package.json +17 -0
- package/src/cli-command.mjs +515 -0
- package/src/cli-invocation.mjs +17 -0
- package/src/code-evidence-analyzer.mjs +2041 -0
- package/src/contracts.mjs +36 -0
- package/src/generated-code-evidence-contract.mjs +22 -0
- package/src/generated-sdk-version-contract.mjs +5 -0
- package/src/generated-source-path-policy.mjs +20 -0
- package/src/lifecycle-guard.mjs +202 -0
- package/src/path-policy.mjs +81 -0
- package/src/setup-ai-contract.mjs +221 -0
- package/src/setup-check-constants.mjs +110 -0
- package/src/setup-check-scan-a.mjs +849 -0
- package/src/setup-check-scan-b.mjs +994 -0
- package/src/setup-check.mjs +575 -0
- package/src/setup-discover-check.mjs +755 -0
- package/src/setup-doctor-deadline.mjs +221 -0
- package/src/setup-doctor-env.mjs +331 -0
- package/src/setup-doctor-file-boundary.mjs +426 -0
- package/src/setup-doctor-probe.mjs +719 -0
- package/src/setup-doctor-quality-checks-a.mjs +593 -0
- package/src/setup-doctor-quality-checks-b.mjs +638 -0
- package/src/setup-doctor-quality-shared.mjs +382 -0
- package/src/setup-doctor-quality.mjs +209 -0
- package/src/setup-doctor-route-scan.mjs +160 -0
- package/src/setup-doctor-sdk-probe.mjs +340 -0
- package/src/setup-doctor.mjs +545 -0
- package/src/setup-documents.mjs +112 -0
- package/src/setup-help.mjs +130 -0
- package/src/setup-prepare.mjs +360 -0
- package/src/setup-repository-discovery.mjs +764 -0
- package/src/setup-step-builders-discover.mjs +701 -0
- package/src/setup-step-builders-events.mjs +229 -0
- package/src/setup-step-builders-implement.mjs +710 -0
- package/src/setup-step-commands.mjs +427 -0
- package/src/setup-tool.mjs +27 -0
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
// setup-check source/dependency scan helpers (part 1). Extracted for
|
|
2
|
+
// file-size limits; content is unchanged.
|
|
3
|
+
|
|
4
|
+
import { access, readFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
6
|
+
import {
|
|
7
|
+
extractExecutableModuleStatements,
|
|
8
|
+
findLifecycleCallApiNames,
|
|
9
|
+
stripSourceNoise,
|
|
10
|
+
} from "./lifecycle-guard.mjs";
|
|
11
|
+
import {
|
|
12
|
+
listAllowedSourceFiles,
|
|
13
|
+
} from "./path-policy.mjs";
|
|
14
|
+
import {
|
|
15
|
+
SETUP_STEP_COMMANDS,
|
|
16
|
+
SETUP_STEP_SKILLS,
|
|
17
|
+
SETUP_COMMAND_ROOT_PARTS,
|
|
18
|
+
SETUP_CODEX_SKILL_ROOT_PARTS,
|
|
19
|
+
SOURCE_EXTENSIONS,
|
|
20
|
+
REQUIRED_LIFECYCLE_APIS,
|
|
21
|
+
REQUIRED_SETUP_DOCUMENT_IDS,
|
|
22
|
+
FRONTEND_SDK_PACKAGE,
|
|
23
|
+
DEPENDENCY_FILE_CANDIDATES,
|
|
24
|
+
} from "./setup-check-constants.mjs";
|
|
25
|
+
|
|
26
|
+
export const exists = async (path) => {
|
|
27
|
+
try {
|
|
28
|
+
await access(path);
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const readSetupManifest = async (repoRoot) => {
|
|
36
|
+
const manifestPath = join(repoRoot, ".cluebase", "setup-manifest.json");
|
|
37
|
+
if (!(await exists(manifestPath))) return undefined;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(await readFile(manifestPath, "utf8"));
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const validateSetupManifestContract = (manifest) => {
|
|
46
|
+
if (!manifest || typeof manifest !== "object") {
|
|
47
|
+
return {
|
|
48
|
+
checked: false,
|
|
49
|
+
findings: ["setup manifest is missing or unreadable"],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const findings = [];
|
|
53
|
+
if (
|
|
54
|
+
manifest.cli_invocation?.ai_help_command !==
|
|
55
|
+
"npx -y @genn-inc/cluebase-cli help --json"
|
|
56
|
+
) {
|
|
57
|
+
findings.push("cli_invocation.ai_help_command is missing or outdated");
|
|
58
|
+
}
|
|
59
|
+
if (manifest.lifecycle_verification?.owner !== "user") {
|
|
60
|
+
findings.push("lifecycle_verification.owner must be user");
|
|
61
|
+
}
|
|
62
|
+
if (
|
|
63
|
+
!String(manifest.lifecycle_verification?.rule ?? "").includes(
|
|
64
|
+
"verify the resulting Cluebase logs",
|
|
65
|
+
)
|
|
66
|
+
) {
|
|
67
|
+
findings.push(
|
|
68
|
+
"lifecycle_verification.rule must require Cluebase log verification",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
!Array.isArray(manifest.ai_owned_workstreams) ||
|
|
73
|
+
manifest.ai_owned_workstreams.length !== 1 ||
|
|
74
|
+
manifest.ai_owned_workstreams[0] !== "sdk_lifecycle_placement"
|
|
75
|
+
) {
|
|
76
|
+
findings.push("ai_owned_workstreams must be sdk_lifecycle_placement only");
|
|
77
|
+
}
|
|
78
|
+
const lifecycleApis = manifest.ai_implementation_scope?.lifecycle_apis;
|
|
79
|
+
if (
|
|
80
|
+
!Array.isArray(lifecycleApis) ||
|
|
81
|
+
lifecycleApis.length !== REQUIRED_LIFECYCLE_APIS.length ||
|
|
82
|
+
!REQUIRED_LIFECYCLE_APIS.every((apiName) => lifecycleApis.includes(apiName))
|
|
83
|
+
) {
|
|
84
|
+
findings.push(
|
|
85
|
+
"ai_implementation_scope.lifecycle_apis must contain only cluebase.init, cluebase.identify, cluebase.group, and cluebase.reset",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (
|
|
89
|
+
!Array.isArray(manifest.ai_implementation_scope?.out_of_scope_by_default) ||
|
|
90
|
+
!manifest.ai_implementation_scope.out_of_scope_by_default.includes(
|
|
91
|
+
"cluebase.track",
|
|
92
|
+
)
|
|
93
|
+
) {
|
|
94
|
+
findings.push("ai_implementation_scope must mark cluebase.track out of scope");
|
|
95
|
+
}
|
|
96
|
+
const documentation = manifest.documentation;
|
|
97
|
+
if (!documentation || typeof documentation !== "object") {
|
|
98
|
+
findings.push("documentation contract is missing");
|
|
99
|
+
} else {
|
|
100
|
+
if (!String(documentation.documents_url ?? "").trim()) {
|
|
101
|
+
findings.push("documentation.documents_url is missing");
|
|
102
|
+
}
|
|
103
|
+
const requiredDocIds = Array.isArray(documentation.required_doc_ids)
|
|
104
|
+
? documentation.required_doc_ids
|
|
105
|
+
: [];
|
|
106
|
+
for (const docId of REQUIRED_SETUP_DOCUMENT_IDS) {
|
|
107
|
+
if (!requiredDocIds.includes(docId)) {
|
|
108
|
+
findings.push(`documentation.required_doc_ids is missing ${docId}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
!documentation.framework_doc_ids_by_framework ||
|
|
113
|
+
typeof documentation.framework_doc_ids_by_framework !== "object"
|
|
114
|
+
) {
|
|
115
|
+
findings.push("documentation.framework_doc_ids_by_framework is missing");
|
|
116
|
+
}
|
|
117
|
+
if (!Array.isArray(documentation.selected_framework_doc_ids)) {
|
|
118
|
+
findings.push("documentation.selected_framework_doc_ids is missing");
|
|
119
|
+
}
|
|
120
|
+
if (
|
|
121
|
+
!Array.isArray(documentation.report_required_fields) ||
|
|
122
|
+
!documentation.report_required_fields.includes("consulted_document_ids")
|
|
123
|
+
) {
|
|
124
|
+
findings.push(
|
|
125
|
+
"documentation.report_required_fields must include consulted_document_ids",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const localEventDelivery = Array.isArray(manifest.required_final_verification)
|
|
130
|
+
? manifest.required_final_verification.find(
|
|
131
|
+
(entry) => entry?.id === "local_event_delivery",
|
|
132
|
+
)
|
|
133
|
+
: null;
|
|
134
|
+
if (
|
|
135
|
+
localEventDelivery?.command !==
|
|
136
|
+
"user runs the local customer frontend/backend, performs the real product flow, and checks Cluebase setup logs or published batch evidence"
|
|
137
|
+
) {
|
|
138
|
+
findings.push(
|
|
139
|
+
"required_final_verification.local_event_delivery must be user-operated",
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (
|
|
143
|
+
!String(localEventDelivery?.completion_meaning ?? "").includes(
|
|
144
|
+
"user_verification_pending",
|
|
145
|
+
)
|
|
146
|
+
) {
|
|
147
|
+
findings.push(
|
|
148
|
+
"required_final_verification.local_event_delivery must report user_verification_pending without user evidence",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const detectedServices = Array.isArray(manifest.detected_services)
|
|
152
|
+
? manifest.detected_services
|
|
153
|
+
: [];
|
|
154
|
+
if (detectedServices.length === 0) {
|
|
155
|
+
findings.push("detected_services must not be empty");
|
|
156
|
+
}
|
|
157
|
+
const hasNextFrontend = detectedServices.some(
|
|
158
|
+
(target) => target?.kind === "frontend" && target?.framework === "nextjs",
|
|
159
|
+
);
|
|
160
|
+
const frontendRuntime = Array.isArray(
|
|
161
|
+
manifest.required_env_scopes?.frontend_runtime,
|
|
162
|
+
)
|
|
163
|
+
? manifest.required_env_scopes.frontend_runtime
|
|
164
|
+
: [];
|
|
165
|
+
const _backendRuntime = Array.isArray(
|
|
166
|
+
manifest.required_env_scopes?.backend_runtime,
|
|
167
|
+
)
|
|
168
|
+
? manifest.required_env_scopes.backend_runtime
|
|
169
|
+
: [];
|
|
170
|
+
if (
|
|
171
|
+
(hasNextFrontend ||
|
|
172
|
+
frontendRuntime.some((name) =>
|
|
173
|
+
String(name).startsWith("NEXT_PUBLIC_CLUEBASE_"),
|
|
174
|
+
)) &&
|
|
175
|
+
!["NEXT_PUBLIC_CLUEBASE_PROJECT_KEY", "NEXT_PUBLIC_CLUEBASE_API_BASE_URL"].every(
|
|
176
|
+
(name) => frontendRuntime.includes(name),
|
|
177
|
+
)
|
|
178
|
+
) {
|
|
179
|
+
findings.push(
|
|
180
|
+
"Next.js frontend runtime env must use NEXT_PUBLIC_CLUEBASE_* names",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
checked: true,
|
|
185
|
+
findings,
|
|
186
|
+
};
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export const escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
190
|
+
|
|
191
|
+
export const validateSetupStepFiles = async ({ repoRoot }) => {
|
|
192
|
+
const missing = [];
|
|
193
|
+
for (const file of SETUP_STEP_COMMANDS) {
|
|
194
|
+
const commandPath = join(repoRoot, ...SETUP_COMMAND_ROOT_PARTS, file);
|
|
195
|
+
if (!(await exists(commandPath))) {
|
|
196
|
+
missing.push(join(...SETUP_COMMAND_ROOT_PARTS, file));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const skill of SETUP_STEP_SKILLS) {
|
|
200
|
+
const skillPath = join(
|
|
201
|
+
repoRoot,
|
|
202
|
+
...SETUP_CODEX_SKILL_ROOT_PARTS,
|
|
203
|
+
skill,
|
|
204
|
+
"SKILL.md",
|
|
205
|
+
);
|
|
206
|
+
if (!(await exists(skillPath))) {
|
|
207
|
+
missing.push(join(...SETUP_CODEX_SKILL_ROOT_PARTS, skill, "SKILL.md"));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return missing;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export const addCheck = (checks, id, passed, summary, details = {}) => {
|
|
214
|
+
checks.push({ id, passed, summary, ...details });
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
export const readAllowedSourceText = async ({
|
|
218
|
+
repoRoot,
|
|
219
|
+
allowedSourcePaths,
|
|
220
|
+
excludedSourcePaths,
|
|
221
|
+
}) => {
|
|
222
|
+
const files = await listAllowedSourceFiles({
|
|
223
|
+
repoRoot,
|
|
224
|
+
allowedSourcePaths,
|
|
225
|
+
excludedSourcePaths,
|
|
226
|
+
extensions: SOURCE_EXTENSIONS,
|
|
227
|
+
});
|
|
228
|
+
const sources = [];
|
|
229
|
+
for (const absolutePath of [...new Set(files)]) {
|
|
230
|
+
sources.push({
|
|
231
|
+
file_path: relative(resolve(repoRoot), absolutePath),
|
|
232
|
+
text: await readFile(absolutePath, "utf8"),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return sources;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export const readDependencyText = async ({ repoRoot, roots }) => {
|
|
239
|
+
const expandedRoots = roots.flatMap((root) =>
|
|
240
|
+
root.endsWith("/src") || root.endsWith("/app")
|
|
241
|
+
? [root, dirname(root)]
|
|
242
|
+
: [root],
|
|
243
|
+
);
|
|
244
|
+
const candidatePaths = [
|
|
245
|
+
...DEPENDENCY_FILE_CANDIDATES,
|
|
246
|
+
...expandedRoots.flatMap((root) =>
|
|
247
|
+
DEPENDENCY_FILE_CANDIDATES.map((file) => join(root, file)),
|
|
248
|
+
),
|
|
249
|
+
];
|
|
250
|
+
const sources = [];
|
|
251
|
+
for (const path of [...new Set(candidatePaths)]) {
|
|
252
|
+
const absolutePath = join(repoRoot, path);
|
|
253
|
+
if (!(await exists(absolutePath))) continue;
|
|
254
|
+
sources.push({
|
|
255
|
+
file_path: path,
|
|
256
|
+
text: await readFile(absolutePath, "utf8"),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return sources;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export const FRONTEND_ENV_FILE_CANDIDATES = [
|
|
263
|
+
".env",
|
|
264
|
+
".env.local",
|
|
265
|
+
".env.development",
|
|
266
|
+
".env.development.local",
|
|
267
|
+
".env.production",
|
|
268
|
+
".env.production.local",
|
|
269
|
+
".env.test",
|
|
270
|
+
".env.test.local",
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
export const readFrontendConfigText = async ({ repoRoot, roots }) => {
|
|
274
|
+
const expandedRoots = roots.flatMap((root) =>
|
|
275
|
+
root.endsWith("/src") || root.endsWith("/app") ? [dirname(root)] : [root],
|
|
276
|
+
);
|
|
277
|
+
const sources = [];
|
|
278
|
+
for (const root of [...new Set(expandedRoots)]) {
|
|
279
|
+
for (const fileName of FRONTEND_ENV_FILE_CANDIDATES) {
|
|
280
|
+
const filePath = join(root, fileName);
|
|
281
|
+
const absolutePath = join(repoRoot, filePath);
|
|
282
|
+
if (!(await exists(absolutePath))) continue;
|
|
283
|
+
sources.push({
|
|
284
|
+
file_path: filePath,
|
|
285
|
+
text: await readFile(absolutePath, "utf8"),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return sources;
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
export const setupSourcePaths = async ({
|
|
293
|
+
repoRoot,
|
|
294
|
+
request,
|
|
295
|
+
includeFrontend,
|
|
296
|
+
setupManifest,
|
|
297
|
+
}) => {
|
|
298
|
+
const requested = request?.allowed_source_paths?.length
|
|
299
|
+
? request.allowed_source_paths
|
|
300
|
+
: ["."];
|
|
301
|
+
if (!includeFrontend) return requested;
|
|
302
|
+
const detectedServiceRoots = Array.isArray(setupManifest?.detected_services)
|
|
303
|
+
? setupManifest.detected_services
|
|
304
|
+
.map((target) => target?.root_path)
|
|
305
|
+
.filter((root) => typeof root === "string" && root.trim())
|
|
306
|
+
: [];
|
|
307
|
+
const candidates = [
|
|
308
|
+
...requested,
|
|
309
|
+
...detectedServiceRoots,
|
|
310
|
+
...detectedServiceRoots.flatMap((root) => [
|
|
311
|
+
join(root, "src"),
|
|
312
|
+
join(root, "app"),
|
|
313
|
+
]),
|
|
314
|
+
"frontend/src",
|
|
315
|
+
"src",
|
|
316
|
+
"app",
|
|
317
|
+
"apps/admin/src",
|
|
318
|
+
"apps/visitor/src",
|
|
319
|
+
"apps/web/src",
|
|
320
|
+
"apps/frontend/src",
|
|
321
|
+
"apps/client/src",
|
|
322
|
+
"packages/frontend",
|
|
323
|
+
];
|
|
324
|
+
const existing = [];
|
|
325
|
+
for (const candidate of [...new Set(candidates)]) {
|
|
326
|
+
if (await exists(join(repoRoot, candidate))) {
|
|
327
|
+
existing.push(candidate);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return existing.length ? existing : requested;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
export const setupBackendRootPaths = (request) => {
|
|
334
|
+
const configuredRoots = [
|
|
335
|
+
...(Array.isArray(request?.backend_root_paths)
|
|
336
|
+
? request.backend_root_paths
|
|
337
|
+
: []),
|
|
338
|
+
request?.backend_root_path,
|
|
339
|
+
]
|
|
340
|
+
.filter((root) => typeof root === "string" && root.trim())
|
|
341
|
+
.map((root) => root.trim());
|
|
342
|
+
return configuredRoots.length
|
|
343
|
+
? [...new Set(configuredRoots)]
|
|
344
|
+
: (request?.allowed_source_paths ?? []);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
export const secretLeakPatterns = [
|
|
348
|
+
/\bcluebase_(?:[A-Za-z0-9]{32,}|(?:live|test)_[A-Za-z0-9_-]{16,})\b/,
|
|
349
|
+
/pk_(live|test)_[A-Za-z0-9_-]+/,
|
|
350
|
+
/sk_(live|test)_[A-Za-z0-9_-]+/,
|
|
351
|
+
/\bsk-proj-[A-Za-z0-9_-]{20,}\b/,
|
|
352
|
+
/\bsk-ant-[A-Za-z0-9_-]{20,}\b/,
|
|
353
|
+
/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{20,}\b/,
|
|
354
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/,
|
|
355
|
+
/npm_[A-Za-z0-9]{20,}/,
|
|
356
|
+
];
|
|
357
|
+
export const PUBLIC_CLUEBASE_SECRET_ENV_LINE_PATTERN =
|
|
358
|
+
/^\s*(?:NEXT_PUBLIC|VITE|PUBLIC|NUXT_PUBLIC|REACT_APP)_CLUEBASE_(?:API_KEY|AI_PROVIDER_API_KEY)\s*=/m;
|
|
359
|
+
|
|
360
|
+
export const findSecretLeaks = (sources) =>
|
|
361
|
+
sources.flatMap((source) =>
|
|
362
|
+
secretLeakPatterns.some((pattern) => pattern.test(source.text)) ||
|
|
363
|
+
PUBLIC_CLUEBASE_SECRET_ENV_LINE_PATTERN.test(source.text)
|
|
364
|
+
? [source.file_path]
|
|
365
|
+
: [],
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
export const startsWithRoot = (filePath, root) =>
|
|
369
|
+
filePath === root || filePath.startsWith(`${root.replace(/\/+$/, "")}/`);
|
|
370
|
+
|
|
371
|
+
export const packageJsonDependencyNames = (text) => {
|
|
372
|
+
try {
|
|
373
|
+
const parsed = JSON.parse(text);
|
|
374
|
+
return [
|
|
375
|
+
"dependencies",
|
|
376
|
+
"devDependencies",
|
|
377
|
+
"optionalDependencies",
|
|
378
|
+
"peerDependencies",
|
|
379
|
+
].flatMap((field) =>
|
|
380
|
+
parsed && typeof parsed[field] === "object" && parsed[field] !== null
|
|
381
|
+
? Object.keys(parsed[field])
|
|
382
|
+
: [],
|
|
383
|
+
);
|
|
384
|
+
} catch {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
export const packageJsonDependencies = (text) => {
|
|
390
|
+
try {
|
|
391
|
+
const parsed = JSON.parse(text);
|
|
392
|
+
return [
|
|
393
|
+
"dependencies",
|
|
394
|
+
"devDependencies",
|
|
395
|
+
"optionalDependencies",
|
|
396
|
+
"peerDependencies",
|
|
397
|
+
].reduce((result, field) => {
|
|
398
|
+
if (
|
|
399
|
+
parsed &&
|
|
400
|
+
typeof parsed[field] === "object" &&
|
|
401
|
+
parsed[field] !== null
|
|
402
|
+
) {
|
|
403
|
+
for (const [name, version] of Object.entries(parsed[field])) {
|
|
404
|
+
if (typeof version === "string") {
|
|
405
|
+
result.set(name, version);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return result;
|
|
410
|
+
}, new Map());
|
|
411
|
+
} catch {
|
|
412
|
+
return new Map();
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
export const packageJsonHasDependency = (text, packageName) =>
|
|
417
|
+
packageJsonDependencies(text).has(packageName);
|
|
418
|
+
|
|
419
|
+
export const packageJsonDependencyVersion = (text, packageName) =>
|
|
420
|
+
packageJsonDependencies(text).get(packageName) ?? null;
|
|
421
|
+
|
|
422
|
+
export const packageJsonSourcesWithDependency = (dependencySources, packageName) =>
|
|
423
|
+
dependencySources.filter(
|
|
424
|
+
(source) =>
|
|
425
|
+
source.file_path.endsWith("package.json") &&
|
|
426
|
+
packageJsonHasDependency(source.text, packageName),
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
export const nextPackageRoots = (dependencySources) =>
|
|
430
|
+
packageJsonSourcesWithDependency(dependencySources, "next").map((source) =>
|
|
431
|
+
dirname(source.file_path),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
export const frontendPackageRoots = (dependencySources) =>
|
|
435
|
+
dependencySources
|
|
436
|
+
.filter((source) => {
|
|
437
|
+
if (!source.file_path.endsWith("package.json")) return false;
|
|
438
|
+
const dependencies = new Set(packageJsonDependencyNames(source.text));
|
|
439
|
+
return [
|
|
440
|
+
FRONTEND_SDK_PACKAGE,
|
|
441
|
+
"next",
|
|
442
|
+
"vite",
|
|
443
|
+
"react",
|
|
444
|
+
"vue",
|
|
445
|
+
"@angular/core",
|
|
446
|
+
].some((packageName) => dependencies.has(packageName));
|
|
447
|
+
})
|
|
448
|
+
.map((source) => dirname(source.file_path));
|
|
449
|
+
|
|
450
|
+
export const sourceIsUnderAnyRoot = (source, roots) =>
|
|
451
|
+
roots.some((root) => {
|
|
452
|
+
const normalizedRoot = root.replace(/\/+$/, "");
|
|
453
|
+
return (
|
|
454
|
+
normalizedRoot === "." ||
|
|
455
|
+
normalizedRoot === "" ||
|
|
456
|
+
startsWithRoot(source.file_path, normalizedRoot)
|
|
457
|
+
);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
export const sourceIsUnderFrontendRoot = (source, roots) => {
|
|
461
|
+
const normalizedPath = source.file_path.replaceAll("\\", "/");
|
|
462
|
+
if (
|
|
463
|
+
/(?:^|\/)(?:frontend|apps\/(?:admin|visitor|web|frontend|client)|packages\/frontend)(?:\/|$)/.test(
|
|
464
|
+
normalizedPath,
|
|
465
|
+
)
|
|
466
|
+
) {
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
if (sourceImportsFrontendSdk(source)) return true;
|
|
470
|
+
return roots.some((root) => {
|
|
471
|
+
const normalizedRoot = root.replace(/\/+$/, "");
|
|
472
|
+
if (normalizedRoot === "." || normalizedRoot === "") {
|
|
473
|
+
return /^(?:src|app)\//.test(normalizedPath);
|
|
474
|
+
}
|
|
475
|
+
return startsWithRoot(normalizedPath, normalizedRoot);
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
export const hasUseClientDirective = (text) =>
|
|
480
|
+
/^\s*(?:"use client"|'use client')\s*;?/.test(text);
|
|
481
|
+
|
|
482
|
+
export const dependencySourceHasPackage = (source, packageName) => {
|
|
483
|
+
if (source.file_path.endsWith("package.json")) {
|
|
484
|
+
return packageJsonDependencyNames(source.text).includes(packageName);
|
|
485
|
+
}
|
|
486
|
+
const packagePattern = new RegExp(
|
|
487
|
+
`(^|[\\s"'=,{\\[]+)${escapeRegex(packageName)}($|[\\s"'=<>~!,}\\]]+)`,
|
|
488
|
+
"i",
|
|
489
|
+
);
|
|
490
|
+
return source.text
|
|
491
|
+
.split(/\r?\n/)
|
|
492
|
+
.map((line) => line.trim())
|
|
493
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
494
|
+
.some((line) => packagePattern.test(line));
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
export const dependencyHasAnyPackage = (dependencySources, packageNames) =>
|
|
498
|
+
dependencySources.some((source) =>
|
|
499
|
+
packageNames.some((packageName) =>
|
|
500
|
+
dependencySourceHasPackage(source, packageName),
|
|
501
|
+
),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
export const sourceHasPythonImport = (source, importName) => {
|
|
505
|
+
const importPattern = new RegExp(
|
|
506
|
+
`(^|\\n)\\s*(?:from\\s+${escapeRegex(importName)}\\b|import\\s+${escapeRegex(importName)}\\b)`,
|
|
507
|
+
);
|
|
508
|
+
return importPattern.test(
|
|
509
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
510
|
+
);
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
export const sourcesHavePythonImport = (sources, importNames) =>
|
|
514
|
+
sources.some((source) =>
|
|
515
|
+
importNames.some((importName) => sourceHasPythonImport(source, importName)),
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
export const sourceHasNodeImport = (source, importName) => {
|
|
519
|
+
const importPattern = new RegExp(
|
|
520
|
+
`(?:from\\s*["']${escapeRegex(importName)}(?:/[^"']*)?["']|` +
|
|
521
|
+
`import\\s*["']${escapeRegex(importName)}(?:/[^"']*)?["']|` +
|
|
522
|
+
`require\\(\\s*["']${escapeRegex(importName)}(?:/[^"']*)?["']\\s*\\))`,
|
|
523
|
+
);
|
|
524
|
+
return extractExecutableModuleStatements(source.text).some((statement) =>
|
|
525
|
+
importPattern.test(statement),
|
|
526
|
+
);
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
export const sourcesHaveNodeImport = (sources, importNames) =>
|
|
530
|
+
sources.some((source) =>
|
|
531
|
+
importNames.some((importName) => sourceHasNodeImport(source, importName)),
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
export const sourcesHaveBackendSdkImport = (sources, spec) =>
|
|
535
|
+
spec.language === "node"
|
|
536
|
+
? sourcesHaveNodeImport(sources, spec.imports)
|
|
537
|
+
: sourcesHavePythonImport(sources, spec.imports);
|
|
538
|
+
|
|
539
|
+
export const sourcesHaveFrontendSdkImport = (sources) =>
|
|
540
|
+
sources.some((source) => sourceImportsFrontendSdk(source));
|
|
541
|
+
|
|
542
|
+
export const sourceImportsFrontendSdk = (source) =>
|
|
543
|
+
sourceTextImportsFrontendSdk(source.text);
|
|
544
|
+
|
|
545
|
+
export const sourceTextImportsFrontendSdk = (text) =>
|
|
546
|
+
extractExecutableModuleStatements(text).some((statement) =>
|
|
547
|
+
new RegExp(
|
|
548
|
+
`(?:from\\s*["']${escapeRegex(FRONTEND_SDK_PACKAGE)}["']|import\\s*["']${escapeRegex(FRONTEND_SDK_PACKAGE)}["'])`,
|
|
549
|
+
).test(statement),
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
export const parseNamedSpecifiers = (specifiers) =>
|
|
553
|
+
specifiers
|
|
554
|
+
.split(",")
|
|
555
|
+
.map((specifier) => specifier.trim())
|
|
556
|
+
.filter(Boolean)
|
|
557
|
+
.map((specifier) => {
|
|
558
|
+
const match = /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/.exec(
|
|
559
|
+
specifier,
|
|
560
|
+
);
|
|
561
|
+
if (!match) return null;
|
|
562
|
+
return {
|
|
563
|
+
imported: match[1],
|
|
564
|
+
local: match[2] ?? match[1],
|
|
565
|
+
exported: match[2] ?? match[1],
|
|
566
|
+
};
|
|
567
|
+
})
|
|
568
|
+
.filter(Boolean);
|
|
569
|
+
|
|
570
|
+
export const sourceDirectlyProvidesApiFromFrontendSdk = (text, apiName) => {
|
|
571
|
+
const statements = extractExecutableModuleStatements(text);
|
|
572
|
+
const statementsJoined = statements.join("\n");
|
|
573
|
+
// Default-export 形式: `import cluebase from "@genn-inc/cluebase-frontend-sdk"` + `cluebase.identify(...)`
|
|
574
|
+
// apiName が namespace 形式 (= "cluebase.X") なら、 default import の local 名と prefix を比較し、
|
|
575
|
+
// 当該 method の呼出が source text 全体に存在するかを確認。
|
|
576
|
+
if (apiName.includes(".")) {
|
|
577
|
+
const [prefix, method] = apiName.split(".");
|
|
578
|
+
const defaultImportPattern = new RegExp(
|
|
579
|
+
`import\\s+([A-Za-z_$][\\w$]*)\\s+from\\s*["']${escapeRegex(FRONTEND_SDK_PACKAGE)}["']`,
|
|
580
|
+
);
|
|
581
|
+
const defaultImportMatch = statementsJoined.match(defaultImportPattern);
|
|
582
|
+
if (defaultImportMatch) {
|
|
583
|
+
const local = defaultImportMatch[1];
|
|
584
|
+
if (local === prefix) {
|
|
585
|
+
// 呼出 detection は source 全体 text に対して行う (= import/export 抽出ではなく)
|
|
586
|
+
const sourceClean = stripSourceNoise(text, { stripStrings: true });
|
|
587
|
+
const callPattern = new RegExp(
|
|
588
|
+
`\\b${escapeRegex(local)}\\.${escapeRegex(method)}\\s*\\(`,
|
|
589
|
+
);
|
|
590
|
+
if (callPattern.test(sourceClean)) {
|
|
591
|
+
return true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return false;
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
export const frontendSdkNamespaces = (text) =>
|
|
600
|
+
[
|
|
601
|
+
...extractExecutableModuleStatements(text)
|
|
602
|
+
.join("\n")
|
|
603
|
+
.matchAll(
|
|
604
|
+
/import\s*\*\s*as\s*([A-Za-z_$][\w$]*)\s*from\s*["']([^"']+)["']/g,
|
|
605
|
+
),
|
|
606
|
+
]
|
|
607
|
+
.filter((match) => match[2] === FRONTEND_SDK_PACKAGE)
|
|
608
|
+
.map((match) => match[1]);
|
|
609
|
+
|
|
610
|
+
export const sourceForwardsFrontendSdkApi = (source, apiName) => {
|
|
611
|
+
const text = source.text;
|
|
612
|
+
if (sourceDirectlyProvidesApiFromFrontendSdk(text, apiName)) return true;
|
|
613
|
+
return frontendSdkNamespaces(text).some((namespaceName) =>
|
|
614
|
+
new RegExp(
|
|
615
|
+
`export\\s+const\\s+${escapeRegex(apiName)}\\s*=\\s*${escapeRegex(namespaceName)}\\.${escapeRegex(apiName)}\\b`,
|
|
616
|
+
).test(text),
|
|
617
|
+
);
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
export const localImportSpecifiersForApi = (text, apiName) =>
|
|
621
|
+
[
|
|
622
|
+
...extractExecutableModuleStatements(text)
|
|
623
|
+
.join("\n")
|
|
624
|
+
.matchAll(/import\s*{([^}]+)}\s*from\s*["']([^"']+)["']/g),
|
|
625
|
+
]
|
|
626
|
+
.filter((match) => match[2].startsWith(".") || match[2].startsWith("@/"))
|
|
627
|
+
.flatMap((match) =>
|
|
628
|
+
parseNamedSpecifiers(match[1])
|
|
629
|
+
.filter(
|
|
630
|
+
(specifier) =>
|
|
631
|
+
specifier.imported === apiName && specifier.local === apiName,
|
|
632
|
+
)
|
|
633
|
+
.map(() => match[2]),
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
export const importSpecifiers = (text) =>
|
|
637
|
+
[
|
|
638
|
+
...extractExecutableModuleStatements(text)
|
|
639
|
+
.join("\n")
|
|
640
|
+
.matchAll(/import\s+(?:[\s\S]*?\s+from\s+)?["']([^"']+)["']/g),
|
|
641
|
+
]
|
|
642
|
+
.map((match) => match[1] ?? match[2])
|
|
643
|
+
.filter(Boolean);
|
|
644
|
+
|
|
645
|
+
export const candidateSourcePaths = (basePath) => [
|
|
646
|
+
basePath,
|
|
647
|
+
...SOURCE_EXTENSIONS.map((extension) => `${basePath}${extension}`),
|
|
648
|
+
...SOURCE_EXTENSIONS.map((extension) => join(basePath, `index${extension}`)),
|
|
649
|
+
];
|
|
650
|
+
|
|
651
|
+
export const resolveLocalSource = ({ importerPath, sourceByPath, specifier }) => {
|
|
652
|
+
const candidates = [];
|
|
653
|
+
if (specifier.startsWith(".")) {
|
|
654
|
+
candidates.push(
|
|
655
|
+
...candidateSourcePaths(join(dirname(importerPath), specifier)),
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
if (specifier.startsWith("@/")) {
|
|
659
|
+
const srcIndex = importerPath.lastIndexOf("/src/");
|
|
660
|
+
if (srcIndex >= 0) {
|
|
661
|
+
candidates.push(
|
|
662
|
+
...candidateSourcePaths(
|
|
663
|
+
join(
|
|
664
|
+
importerPath.slice(0, srcIndex + "/src".length),
|
|
665
|
+
specifier.slice(2),
|
|
666
|
+
),
|
|
667
|
+
),
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
for (const root of [
|
|
671
|
+
"src",
|
|
672
|
+
"frontend/src",
|
|
673
|
+
"apps/web/src",
|
|
674
|
+
"apps/admin/src",
|
|
675
|
+
"apps/visitor/src",
|
|
676
|
+
]) {
|
|
677
|
+
candidates.push(...candidateSourcePaths(join(root, specifier.slice(2))));
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
return candidates
|
|
681
|
+
.map((candidate) => sourceByPath.get(candidate))
|
|
682
|
+
.find(Boolean);
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
// Default import of `localName` from a LOCAL module (relative or `@/`).
|
|
686
|
+
// Returns the module specifier when found, else null. Used to trace an
|
|
687
|
+
// adapter singleton such as `import cluebase from "@/lib/cluebase"`.
|
|
688
|
+
export const localDefaultImportModule = (text, localName) => {
|
|
689
|
+
const statements = extractExecutableModuleStatements(text).join("\n");
|
|
690
|
+
const match = statements.match(
|
|
691
|
+
new RegExp(
|
|
692
|
+
`import\\s+${escapeRegex(localName)}\\s+from\\s*["']([^"']+)["']`,
|
|
693
|
+
),
|
|
694
|
+
);
|
|
695
|
+
if (!match) return null;
|
|
696
|
+
const specifier = match[1];
|
|
697
|
+
return specifier.startsWith(".") || specifier.startsWith("@/")
|
|
698
|
+
? specifier
|
|
699
|
+
: null;
|
|
700
|
+
};
|
|
701
|
+
|
|
702
|
+
// A local adapter module re-exports the frontend SDK default instance when it
|
|
703
|
+
// imports the SDK default and re-exports that same binding
|
|
704
|
+
// (`import cluebase from "@genn-inc/cluebase-frontend-sdk"; ...; export default cluebase;`).
|
|
705
|
+
// This is the prescribed `src/lib/cluebase.ts` singleton, so calls made through it
|
|
706
|
+
// are genuinely wired to the frontend SDK.
|
|
707
|
+
export const sourceReExportsFrontendSdkDefault = (text) => {
|
|
708
|
+
const joined = extractExecutableModuleStatements(text).join("\n");
|
|
709
|
+
const importMatch = joined.match(
|
|
710
|
+
new RegExp(
|
|
711
|
+
`import\\s+([A-Za-z_$][\\w$]*)\\s+from\\s*["']${escapeRegex(
|
|
712
|
+
FRONTEND_SDK_PACKAGE,
|
|
713
|
+
)}["']`,
|
|
714
|
+
),
|
|
715
|
+
);
|
|
716
|
+
if (!importMatch) return false;
|
|
717
|
+
const sdkLocal = importMatch[1];
|
|
718
|
+
return new RegExp(`export\\s+default\\s+${escapeRegex(sdkLocal)}\\b`).test(
|
|
719
|
+
joined,
|
|
720
|
+
);
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
export const sourceHasVerifiedFrontendSdkAccess = ({
|
|
724
|
+
apiNames,
|
|
725
|
+
source,
|
|
726
|
+
sourceByPath,
|
|
727
|
+
}) =>
|
|
728
|
+
apiNames.every((apiName) => {
|
|
729
|
+
if (sourceDirectlyProvidesApiFromFrontendSdk(source.text, apiName)) {
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
if (
|
|
733
|
+
localImportSpecifiersForApi(source.text, apiName).some((specifier) => {
|
|
734
|
+
const importedSource = resolveLocalSource({
|
|
735
|
+
importerPath: source.file_path,
|
|
736
|
+
sourceByPath,
|
|
737
|
+
specifier,
|
|
738
|
+
});
|
|
739
|
+
return importedSource
|
|
740
|
+
? sourceForwardsFrontendSdkApi(importedSource, apiName)
|
|
741
|
+
: false;
|
|
742
|
+
})
|
|
743
|
+
) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
// Adapter default re-export: the lifecycle file calls `cluebase.<method>()`
|
|
747
|
+
// where `cluebase` is default-imported from a local adapter that re-exports
|
|
748
|
+
// the frontend SDK default instance. This is the prescribed
|
|
749
|
+
// `src/lib/cluebase.ts` singleton imported from UI hooks, so it counts as
|
|
750
|
+
// verified SDK wiring.
|
|
751
|
+
if (apiName.includes(".")) {
|
|
752
|
+
const [prefix] = apiName.split(".");
|
|
753
|
+
const adapterModule = localDefaultImportModule(source.text, prefix);
|
|
754
|
+
if (adapterModule) {
|
|
755
|
+
const importedSource = resolveLocalSource({
|
|
756
|
+
importerPath: source.file_path,
|
|
757
|
+
sourceByPath,
|
|
758
|
+
specifier: adapterModule,
|
|
759
|
+
});
|
|
760
|
+
if (
|
|
761
|
+
importedSource &&
|
|
762
|
+
sourceReExportsFrontendSdkDefault(importedSource.text)
|
|
763
|
+
) {
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return false;
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
export const NEXT_PUBLIC_CLUEBASE_NAMES = ["CLUEBASE_PROJECT_KEY", "CLUEBASE_API_BASE_URL"];
|
|
772
|
+
export const processEnvAccessSource = (name) =>
|
|
773
|
+
`process\\.env(?:\\.${escapeRegex(name)}\\b|\\[\\s*["']${escapeRegex(
|
|
774
|
+
name,
|
|
775
|
+
)}["']\\s*\\])`;
|
|
776
|
+
export const processEnvAccessPattern = (name) =>
|
|
777
|
+
new RegExp(`\\b${processEnvAccessSource(name)}`);
|
|
778
|
+
export const processEnvDestructuringPattern = (name) =>
|
|
779
|
+
new RegExp(
|
|
780
|
+
`\\b(?:const|let|var)\\s*\\{[\\s\\S]{0,800}\\b${escapeRegex(
|
|
781
|
+
name,
|
|
782
|
+
)}\\b(?:\\s*:)?[\\s\\S]{0,800}\\}\\s*=\\s*process\\.env\\b`,
|
|
783
|
+
);
|
|
784
|
+
export const sourceReadsProcessEnvName = (text, name) =>
|
|
785
|
+
processEnvAccessPattern(name).test(text) ||
|
|
786
|
+
processEnvDestructuringPattern(name).test(text);
|
|
787
|
+
export const PUBLIC_CLUEBASE_SECRET_ENV_PATTERN =
|
|
788
|
+
/\b(?:process\.env(?:\.(?:(?:NEXT_PUBLIC|VITE|PUBLIC|NUXT_PUBLIC|REACT_APP)_CLUEBASE_(?:API_KEY|AI_PROVIDER_API_KEY))\b|\[\s*["'](?:(?:NEXT_PUBLIC|VITE|PUBLIC|NUXT_PUBLIC|REACT_APP)_CLUEBASE_(?:API_KEY|AI_PROVIDER_API_KEY))["']\s*\])|(?:const|let|var)\s*\{[\s\S]{0,800}\b(?:NEXT_PUBLIC|VITE|PUBLIC|NUXT_PUBLIC|REACT_APP)_CLUEBASE_(?:API_KEY|AI_PROVIDER_API_KEY)\b[\s\S]{0,800}\}\s*=\s*process\.env\b)/;
|
|
789
|
+
|
|
790
|
+
export const findFrontendLifecycleNonPublicEnvFiles = ({ frontendSources }) => {
|
|
791
|
+
return frontendSources
|
|
792
|
+
.filter(
|
|
793
|
+
(source) =>
|
|
794
|
+
sourceImportsFrontendSdk(source) ||
|
|
795
|
+
findLifecycleCallApiNames(
|
|
796
|
+
stripSourceNoise(source.text, { stripStrings: true }),
|
|
797
|
+
).length > 0,
|
|
798
|
+
)
|
|
799
|
+
.filter(
|
|
800
|
+
(source) =>
|
|
801
|
+
/\bprocess\.env(?:\.CLUEBASE_[A-Z0-9_]+\b|\[\s*["']CLUEBASE_[A-Z0-9_]+["']\s*\])/.test(
|
|
802
|
+
source.text,
|
|
803
|
+
) ||
|
|
804
|
+
/\b(?:const|let|var)\s*\{[\s\S]{0,800}\bCLUEBASE_[A-Z0-9_]+\b[\s\S]{0,800}\}\s*=\s*process\.env\b/.test(
|
|
805
|
+
source.text,
|
|
806
|
+
) ||
|
|
807
|
+
PUBLIC_CLUEBASE_SECRET_ENV_PATTERN.test(source.text) ||
|
|
808
|
+
NEXT_PUBLIC_CLUEBASE_NAMES.some((name) =>
|
|
809
|
+
sourceReadsProcessEnvName(source.text, name),
|
|
810
|
+
),
|
|
811
|
+
)
|
|
812
|
+
.map((source) => source.file_path);
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
export const sourceHasComponentScopedCluebaseInitCall = (text) => {
|
|
816
|
+
const source = stripSourceNoise(text, { stripStrings: true });
|
|
817
|
+
return (
|
|
818
|
+
/\buse(?:Effect|LayoutEffect|InsertionEffect)\s*\([\s\S]{0,1200}\bcluebase\.init\s*\(/.test(
|
|
819
|
+
source,
|
|
820
|
+
) ||
|
|
821
|
+
/\b(?:export\s+)?function\s+[A-Z][\w$]*\s*\([^)]*\)\s*\{[\s\S]{0,1600}\bcluebase\.init\s*\(/.test(
|
|
822
|
+
source,
|
|
823
|
+
) ||
|
|
824
|
+
/\bexport\s+default\s+function(?:\s+[A-Za-z_$][\w$]*)?\s*\([^)]*\)\s*\{[\s\S]{0,1600}\bcluebase\.init\s*\(/.test(
|
|
825
|
+
source,
|
|
826
|
+
) ||
|
|
827
|
+
/\bexport\s+default\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*(?:\{[\s\S]{0,1600}\bcluebase\.init\s*\(|[\s\S]{0,400}\bcluebase\.init\s*\()/.test(
|
|
828
|
+
source,
|
|
829
|
+
) ||
|
|
830
|
+
/\b(?:export\s+)?(?:const|let|var)\s+[A-Z][\w$]*\s*=\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*(?:\{[\s\S]{0,1600}\bcluebase\.init\s*\(|[\s\S]{0,400}\bcluebase\.init\s*\()/.test(
|
|
831
|
+
source,
|
|
832
|
+
)
|
|
833
|
+
);
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
export const sourceHasAuthCallbackScopedCluebaseInitCall = (text) => {
|
|
837
|
+
const source = stripSourceNoise(text, { stripStrings: true });
|
|
838
|
+
return (
|
|
839
|
+
/\bonSuccess\s*:\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*\{[\s\S]{0,1600}\bcluebase\.init\s*\(/.test(
|
|
840
|
+
source,
|
|
841
|
+
) ||
|
|
842
|
+
/\b(?:login|register|signIn|signUp|handleLogin|handleRegister)\s*(?:=\s*)?(?:async\s*)?(?:function\s*)?\([^)]*\)\s*\{[\s\S]{0,1600}\bcluebase\.init\s*\(/i.test(
|
|
843
|
+
source,
|
|
844
|
+
) ||
|
|
845
|
+
/\.(?:then|finally)\s*\(\s*(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*\{[\s\S]{0,1600}\bcluebase\.init\s*\(/.test(
|
|
846
|
+
source,
|
|
847
|
+
)
|
|
848
|
+
);
|
|
849
|
+
};
|