@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,382 @@
|
|
|
1
|
+
// Shared constants and helpers for the /cluebase-doctor data-quality checks.
|
|
2
|
+
// Extracted from setup-doctor-quality for file-size limits; content is
|
|
3
|
+
// unchanged.
|
|
4
|
+
|
|
5
|
+
import { readSetupDoctorFile } from "./setup-doctor-file-boundary.mjs";
|
|
6
|
+
|
|
7
|
+
export const ALLOWED_GROUP_TYPES = Object.freeze(["organization"]);
|
|
8
|
+
|
|
9
|
+
export const FAKE_PLACEHOLDER_NAMES = Object.freeze([
|
|
10
|
+
"noreply@example.com",
|
|
11
|
+
"example@example.com",
|
|
12
|
+
"user@example.com",
|
|
13
|
+
"unknown",
|
|
14
|
+
"anonymous",
|
|
15
|
+
"null",
|
|
16
|
+
"undefined",
|
|
17
|
+
"user",
|
|
18
|
+
"guest",
|
|
19
|
+
"test",
|
|
20
|
+
"default",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const PLACEHOLDER_ENV_VALUES = Object.freeze([
|
|
24
|
+
"",
|
|
25
|
+
"your-key-here",
|
|
26
|
+
"xxx",
|
|
27
|
+
"yyy",
|
|
28
|
+
"zzz",
|
|
29
|
+
"todo",
|
|
30
|
+
"tbd",
|
|
31
|
+
"placeholder",
|
|
32
|
+
"changeme",
|
|
33
|
+
"change-me",
|
|
34
|
+
"your_key_here",
|
|
35
|
+
"<your-key>",
|
|
36
|
+
"your-project-key",
|
|
37
|
+
"your-service-key",
|
|
38
|
+
"your-api-key",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
export const TEST_DIR_PATTERNS = [
|
|
42
|
+
/(^|\/)test(s)?\//i,
|
|
43
|
+
/(^|\/)__tests?__\//i,
|
|
44
|
+
/(^|\/)spec\//i,
|
|
45
|
+
/\.test\.[a-z]+$/i,
|
|
46
|
+
/\.spec\.[a-z]+$/i,
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export const VENDORED_DIR_PATTERNS = [
|
|
50
|
+
/(^|\/)node_modules\//,
|
|
51
|
+
/(^|\/)vendor\//,
|
|
52
|
+
/(^|\/)vendored\//,
|
|
53
|
+
/(^|\/)\.next\//,
|
|
54
|
+
/(^|\/)dist\//,
|
|
55
|
+
/(^|\/)build\//,
|
|
56
|
+
/(^|\/)\.turbo\//,
|
|
57
|
+
/(^|\/)out\//,
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
export const DEV_HOST_PATTERNS = [
|
|
61
|
+
/https?:\/\/localhost(:\d+)?(\/|$)/i,
|
|
62
|
+
/https?:\/\/127\.0\.0\.1(:\d+)?(\/|$)/i,
|
|
63
|
+
/https?:\/\/0\.0\.0\.0(:\d+)?(\/|$)/i,
|
|
64
|
+
/https?:\/\/.*\.local(:\d+)?(\/|$)/i,
|
|
65
|
+
/https?:\/\/.*\.dev(:\d+)?(\/|$)/i,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
export const SENSITIVE_FIELD_PATTERNS = [
|
|
69
|
+
/password/i,
|
|
70
|
+
/passwd/i,
|
|
71
|
+
/secret/i,
|
|
72
|
+
/token/i,
|
|
73
|
+
/api[_-]?key/i,
|
|
74
|
+
/private[_-]?key/i,
|
|
75
|
+
/credit[_-]?card/i,
|
|
76
|
+
/ssn/i,
|
|
77
|
+
/cvv/i,
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export const VALID_SDK_PACKAGES = Object.freeze([
|
|
81
|
+
"@genn-inc/cluebase-frontend-sdk",
|
|
82
|
+
"@genn-inc/cluebase-backend-sdk",
|
|
83
|
+
"cluebase-backend-sdk",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
export const GROUP_OWNER_ACTIVE_KIND = "active_context_owner";
|
|
87
|
+
export const GROUP_OWNER_CONTEXT_CHANGE_KIND = "context_change";
|
|
88
|
+
|
|
89
|
+
export const GROUP_OWNER_KIND_ALIASES = Object.freeze({
|
|
90
|
+
active_context_owner: GROUP_OWNER_ACTIVE_KIND,
|
|
91
|
+
active_context_restore: GROUP_OWNER_ACTIVE_KIND,
|
|
92
|
+
guarded_active_context: GROUP_OWNER_ACTIVE_KIND,
|
|
93
|
+
context_owner: GROUP_OWNER_ACTIVE_KIND,
|
|
94
|
+
initial_context: "initial_context",
|
|
95
|
+
context_change: GROUP_OWNER_CONTEXT_CHANGE_KIND,
|
|
96
|
+
create_context: GROUP_OWNER_CONTEXT_CHANGE_KIND,
|
|
97
|
+
join_context: GROUP_OWNER_CONTEXT_CHANGE_KIND,
|
|
98
|
+
switch_context: GROUP_OWNER_CONTEXT_CHANGE_KIND,
|
|
99
|
+
onboarding_context: GROUP_OWNER_CONTEXT_CHANGE_KIND,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export const WRONG_SDK_PACKAGE_PATTERNS = [
|
|
103
|
+
/@genn-inc\/cluebase-cli/,
|
|
104
|
+
/@cluebase\/cli/,
|
|
105
|
+
/@cluebase\/frontend-sdk/,
|
|
106
|
+
/@cluebase\/backend-sdk/,
|
|
107
|
+
/@cluebase-ai\/cli/,
|
|
108
|
+
/@cluebase-ai\/cluebase-cli/,
|
|
109
|
+
/@cluebase-ai\/frontend-sdk/,
|
|
110
|
+
/@cluebase-ai\/backend-sdk/,
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
export const REQUIRED_PROJECT_KEY_ENV_NAMES = Object.freeze([
|
|
114
|
+
"CLUEBASE_PROJECT_KEY",
|
|
115
|
+
"NEXT_PUBLIC_CLUEBASE_PROJECT_KEY",
|
|
116
|
+
"VITE_CLUEBASE_PROJECT_KEY",
|
|
117
|
+
"REACT_APP_CLUEBASE_PROJECT_KEY",
|
|
118
|
+
"PUBLIC_CLUEBASE_PROJECT_KEY",
|
|
119
|
+
"NUXT_PUBLIC_CLUEBASE_PROJECT_KEY",
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
export const COMMON_ENV_KEY_TYPOS = Object.freeze({
|
|
123
|
+
"NEXT_PUBLIC_CLUEBASE_KEY": "NEXT_PUBLIC_CLUEBASE_PROJECT_KEY",
|
|
124
|
+
"CLUEBASE_KEY": "CLUEBASE_PROJECT_KEY",
|
|
125
|
+
"CLUEBASE_PROJ_KEY": "CLUEBASE_PROJECT_KEY",
|
|
126
|
+
"CLUEBASE_PROJECTKEY": "CLUEBASE_PROJECT_KEY",
|
|
127
|
+
"NEXT_PUBLIC_CLUEBASE_PROJECTKEY": "NEXT_PUBLIC_CLUEBASE_PROJECT_KEY",
|
|
128
|
+
"VITE_CLUEBASE_PROJECTKEY": "VITE_CLUEBASE_PROJECT_KEY",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export const isString = (value) => typeof value === "string" && value.length > 0;
|
|
132
|
+
export const isPlainObject = (value) =>
|
|
133
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
134
|
+
|
|
135
|
+
export const matchesAny = (value, patterns) => {
|
|
136
|
+
if (!isString(value)) return false;
|
|
137
|
+
return patterns.some((pattern) => pattern.test(value));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ============================================================
|
|
141
|
+
// Check result builder
|
|
142
|
+
// ============================================================
|
|
143
|
+
|
|
144
|
+
export const buildCheck = ({ id, severity, passed, message = null, details = null }) => ({
|
|
145
|
+
id,
|
|
146
|
+
severity,
|
|
147
|
+
passed: Boolean(passed),
|
|
148
|
+
message,
|
|
149
|
+
details,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export const errorPass = (id) => buildCheck({ id, severity: "error", passed: true });
|
|
153
|
+
export const warnPass = (id) => buildCheck({ id, severity: "warn", passed: true });
|
|
154
|
+
|
|
155
|
+
// ============================================================
|
|
156
|
+
// identify (= 顧客識別) 関連
|
|
157
|
+
// ============================================================
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
export const stringOrNull = (value) =>
|
|
161
|
+
typeof value === "string" && value.trim() ? value.trim() : null;
|
|
162
|
+
|
|
163
|
+
export const groupOwnerKindOf = (site) => {
|
|
164
|
+
const explicit =
|
|
165
|
+
stringOrNull(site?.group_owner_kind) ??
|
|
166
|
+
stringOrNull(site?.groupOwnerKind) ??
|
|
167
|
+
stringOrNull(site?.boundary_role);
|
|
168
|
+
if (explicit) {
|
|
169
|
+
const normalized = explicit.trim().toLowerCase().replaceAll("-", "_");
|
|
170
|
+
if (GROUP_OWNER_KIND_ALIASES[normalized]) {
|
|
171
|
+
return GROUP_OWNER_KIND_ALIASES[normalized];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const haystack = [
|
|
176
|
+
site?.file,
|
|
177
|
+
site?.rationale,
|
|
178
|
+
site?.evidence_snippet,
|
|
179
|
+
site?.field_acquisition_notes,
|
|
180
|
+
]
|
|
181
|
+
.filter(isString)
|
|
182
|
+
.join(" ")
|
|
183
|
+
.toLowerCase();
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
/active[_\s-]?context[_\s-]?owner/.test(haystack) ||
|
|
187
|
+
/lastcluebasegroupkey/.test(haystack) ||
|
|
188
|
+
/onboardingguard/.test(haystack) ||
|
|
189
|
+
/provider/.test(haystack) ||
|
|
190
|
+
/session restore/.test(haystack) ||
|
|
191
|
+
/auth-state listener/.test(haystack) ||
|
|
192
|
+
/onauthstatechange/.test(haystack)
|
|
193
|
+
) {
|
|
194
|
+
return GROUP_OWNER_ACTIVE_KIND;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (
|
|
198
|
+
/\b(create|join|switch|onboard)\b/.test(haystack) ||
|
|
199
|
+
/accept[-_\s]?invite/.test(haystack) ||
|
|
200
|
+
/create[-_\s]?workspace/.test(haystack) ||
|
|
201
|
+
/join[-_\s]?workspace/.test(haystack) ||
|
|
202
|
+
/switch[-_\s]?workspace/.test(haystack) ||
|
|
203
|
+
/setactiveworkspace/.test(haystack)
|
|
204
|
+
) {
|
|
205
|
+
return GROUP_OWNER_CONTEXT_CHANGE_KIND;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return null;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const groupSiteSurfaceOf = (site) => {
|
|
212
|
+
const explicit =
|
|
213
|
+
stringOrNull(site?.surface) ??
|
|
214
|
+
stringOrNull(site?.runtime) ??
|
|
215
|
+
stringOrNull(site?.side);
|
|
216
|
+
if (explicit) return explicit.trim().toLowerCase();
|
|
217
|
+
|
|
218
|
+
const file = stringOrNull(site?.file)?.toLowerCase() ?? "";
|
|
219
|
+
if (
|
|
220
|
+
/\.py$/.test(file) ||
|
|
221
|
+
/(^|\/)(backend|server|api|routes?|controllers?|views?)\//.test(file) ||
|
|
222
|
+
/(^|\/)route\.(ts|js)$/.test(file) ||
|
|
223
|
+
/(^|\/)\+server\.(ts|js)$/.test(file)
|
|
224
|
+
) {
|
|
225
|
+
return "backend";
|
|
226
|
+
}
|
|
227
|
+
return "frontend";
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const findDuplicateGroupOwnerViolations = (groupSites) => {
|
|
231
|
+
const bySurface = new Map();
|
|
232
|
+
for (const site of groupSites) {
|
|
233
|
+
if (!isString(site?.file)) continue;
|
|
234
|
+
const ownerKind = groupOwnerKindOf(site);
|
|
235
|
+
if (!ownerKind) continue;
|
|
236
|
+
const surface = groupSiteSurfaceOf(site);
|
|
237
|
+
const entry = bySurface.get(surface) ?? {
|
|
238
|
+
surface,
|
|
239
|
+
activeOwners: [],
|
|
240
|
+
contextChanges: [],
|
|
241
|
+
};
|
|
242
|
+
if (ownerKind === GROUP_OWNER_ACTIVE_KIND) {
|
|
243
|
+
entry.activeOwners.push(site);
|
|
244
|
+
}
|
|
245
|
+
if (ownerKind === GROUP_OWNER_CONTEXT_CHANGE_KIND) {
|
|
246
|
+
entry.contextChanges.push(site);
|
|
247
|
+
}
|
|
248
|
+
bySurface.set(surface, entry);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const violations = [];
|
|
252
|
+
for (const entry of bySurface.values()) {
|
|
253
|
+
if (entry.activeOwners.length === 0 || entry.contextChanges.length === 0) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
for (const activeOwner of entry.activeOwners) {
|
|
257
|
+
for (const contextChange of entry.contextChanges) {
|
|
258
|
+
violations.push({
|
|
259
|
+
surface: entry.surface,
|
|
260
|
+
activeOwner: {
|
|
261
|
+
file: activeOwner.file,
|
|
262
|
+
line: activeOwner.line,
|
|
263
|
+
group_owner_kind: groupOwnerKindOf(activeOwner),
|
|
264
|
+
},
|
|
265
|
+
duplicateHandler: {
|
|
266
|
+
file: contextChange.file,
|
|
267
|
+
line: contextChange.line,
|
|
268
|
+
group_owner_kind: groupOwnerKindOf(contextChange),
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return violations;
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
export const identifySitesOf = (discoveries) => {
|
|
278
|
+
if (!isPlainObject(discoveries)) return [];
|
|
279
|
+
return Array.isArray(discoveries.identify_sites) ? discoveries.identify_sites : [];
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export const groupSitesOf = (discoveries) => {
|
|
283
|
+
if (!isPlainObject(discoveries)) return [];
|
|
284
|
+
const organizationSites = Array.isArray(discoveries.group_sites)
|
|
285
|
+
? discoveries.group_sites
|
|
286
|
+
: [];
|
|
287
|
+
return organizationSites;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
export const resetSitesOf = (discoveries) => {
|
|
291
|
+
if (!isPlainObject(discoveries)) return [];
|
|
292
|
+
const logoutSites = Array.isArray(discoveries.reset_sites)
|
|
293
|
+
? discoveries.reset_sites
|
|
294
|
+
: [];
|
|
295
|
+
return logoutSites;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const initSitesOf = (discoveries) => {
|
|
299
|
+
if (!isPlainObject(discoveries)) return [];
|
|
300
|
+
const result = [];
|
|
301
|
+
for (const key of ["cluebase_init_frontend", "cluebase_init_backend"]) {
|
|
302
|
+
const entry = discoveries[key];
|
|
303
|
+
if (isPlainObject(entry) && isString(entry.file)) {
|
|
304
|
+
result.push(entry);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export const allInsertionSitesOf = (discoveries) => [
|
|
311
|
+
...identifySitesOf(discoveries),
|
|
312
|
+
...groupSitesOf(discoveries),
|
|
313
|
+
...resetSitesOf(discoveries),
|
|
314
|
+
...initSitesOf(discoveries),
|
|
315
|
+
];
|
|
316
|
+
|
|
317
|
+
export const readSiteFile = async ({ repoRoot, file, sourceFiles, signal }) => {
|
|
318
|
+
if (isPlainObject(sourceFiles) && file in sourceFiles) {
|
|
319
|
+
return typeof sourceFiles[file] === "string" ? sourceFiles[file] : null;
|
|
320
|
+
}
|
|
321
|
+
if (!repoRoot) return null;
|
|
322
|
+
return readSetupDoctorFile({
|
|
323
|
+
repoRoot,
|
|
324
|
+
path: file,
|
|
325
|
+
signal,
|
|
326
|
+
optional: true,
|
|
327
|
+
});
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
export const findInitCalls = (content) => {
|
|
331
|
+
const calls = [];
|
|
332
|
+
const matches = content.matchAll(/cluebase\.init\s*\(([\s\S]*?)\)/g);
|
|
333
|
+
for (const match of matches) {
|
|
334
|
+
calls.push(match[0]);
|
|
335
|
+
}
|
|
336
|
+
return calls;
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
export const countCluebaseInitCalls = (content) =>
|
|
340
|
+
(content.match(/cluebase\.init\s*\(/g) ?? []).length;
|
|
341
|
+
|
|
342
|
+
export const hasHardcodedKeys = (callSnippet) => {
|
|
343
|
+
// projectKey に literal string が直接書かれているか
|
|
344
|
+
const projectKeyLiteral = /projectKey\s*[:=]\s*["'`](pk_[a-z]+_[A-Za-z0-9_-]+)["'`]/.test(
|
|
345
|
+
callSnippet,
|
|
346
|
+
);
|
|
347
|
+
return projectKeyLiteral;
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
export const findHardcodedIdentifyLiterals = (content) => {
|
|
351
|
+
const calls = [];
|
|
352
|
+
// cluebase.identify("literal", ...) もしくは cluebase.identify('literal', ...)
|
|
353
|
+
const matches = content.matchAll(/cluebase\.identify\s*\(\s*(["'`])([^"'`]+)\1/g);
|
|
354
|
+
for (const match of matches) {
|
|
355
|
+
calls.push(match[0]);
|
|
356
|
+
}
|
|
357
|
+
return calls;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// ============================================================
|
|
361
|
+
// ERROR メッセージ構築
|
|
362
|
+
// ============================================================
|
|
363
|
+
|
|
364
|
+
export const buildErrorMessage = ({ detected, location, impact, actions }) => {
|
|
365
|
+
const actionLines = (actions ?? [])
|
|
366
|
+
.map((action, index) => ` ${index + 1}. ${action}`)
|
|
367
|
+
.join("\n");
|
|
368
|
+
return [
|
|
369
|
+
`× ERROR: ${detected}`,
|
|
370
|
+
` 場所: ${location}`,
|
|
371
|
+
` 影響: ${impact}`,
|
|
372
|
+
` 対応:`,
|
|
373
|
+
actionLines,
|
|
374
|
+
` setup は中断されました。`,
|
|
375
|
+
].join("\n");
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
// ============================================================
|
|
379
|
+
// 30 check Runner (= 全 check を並列実行 + 集計)
|
|
380
|
+
// ============================================================
|
|
381
|
+
|
|
382
|
+
// public API: 30 check を全実行
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// setup-doctor-quality: `/cluebase-doctor` の 30 check 厳格データ品質ガード。
|
|
2
|
+
//
|
|
3
|
+
// API 疎通だけでは検知不能な「動いているのに使えない」状態を fail-fast
|
|
4
|
+
// で halt する。 各 check は純粋関数で source / discoveries.json / env file
|
|
5
|
+
// を受け取り {id, severity, passed, message, details} を返す (= 副作用なし)。
|
|
6
|
+
// 個別 check の実装は setup-doctor-quality-checks-a / -checks-b、 共有定数・
|
|
7
|
+
// helper は setup-doctor-quality-shared にある。
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
checkC1,
|
|
11
|
+
checkC10,
|
|
12
|
+
checkC11,
|
|
13
|
+
checkC12,
|
|
14
|
+
checkC13,
|
|
15
|
+
checkC14,
|
|
16
|
+
checkC15,
|
|
17
|
+
checkC16,
|
|
18
|
+
checkC17,
|
|
19
|
+
checkC2,
|
|
20
|
+
checkC3,
|
|
21
|
+
checkC4,
|
|
22
|
+
checkC9,
|
|
23
|
+
} from "./setup-doctor-quality-checks-a.mjs";
|
|
24
|
+
import {
|
|
25
|
+
checkC18,
|
|
26
|
+
checkC19,
|
|
27
|
+
checkC20,
|
|
28
|
+
checkC21,
|
|
29
|
+
checkC22,
|
|
30
|
+
checkC23,
|
|
31
|
+
checkC24,
|
|
32
|
+
checkC25,
|
|
33
|
+
checkC26,
|
|
34
|
+
checkC27,
|
|
35
|
+
checkC28,
|
|
36
|
+
checkC29,
|
|
37
|
+
checkC30,
|
|
38
|
+
checkC5,
|
|
39
|
+
checkC6,
|
|
40
|
+
checkC7,
|
|
41
|
+
checkC8,
|
|
42
|
+
} from "./setup-doctor-quality-checks-b.mjs";
|
|
43
|
+
import { isPlainObject, isString } from "./setup-doctor-quality-shared.mjs";
|
|
44
|
+
import {
|
|
45
|
+
readSetupDoctorFile,
|
|
46
|
+
readSetupDoctorJson,
|
|
47
|
+
} from "./setup-doctor-file-boundary.mjs";
|
|
48
|
+
|
|
49
|
+
// Re-export the individual checks so `setup-doctor-quality.mjs` stays the public
|
|
50
|
+
// entry point for the check functions after the file-size split.
|
|
51
|
+
export {
|
|
52
|
+
checkC1,
|
|
53
|
+
checkC2,
|
|
54
|
+
checkC3,
|
|
55
|
+
checkC4,
|
|
56
|
+
checkC5,
|
|
57
|
+
checkC6,
|
|
58
|
+
checkC7,
|
|
59
|
+
checkC8,
|
|
60
|
+
checkC9,
|
|
61
|
+
checkC10,
|
|
62
|
+
checkC11,
|
|
63
|
+
checkC12,
|
|
64
|
+
checkC13,
|
|
65
|
+
checkC14,
|
|
66
|
+
checkC15,
|
|
67
|
+
checkC16,
|
|
68
|
+
checkC17,
|
|
69
|
+
checkC18,
|
|
70
|
+
checkC19,
|
|
71
|
+
checkC20,
|
|
72
|
+
checkC21,
|
|
73
|
+
checkC22,
|
|
74
|
+
checkC23,
|
|
75
|
+
checkC24,
|
|
76
|
+
checkC25,
|
|
77
|
+
checkC26,
|
|
78
|
+
checkC27,
|
|
79
|
+
checkC28,
|
|
80
|
+
checkC29,
|
|
81
|
+
checkC30,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export const runQualityChecks = async ({
|
|
85
|
+
discoveries = null,
|
|
86
|
+
envFiles = null,
|
|
87
|
+
packageJson = null,
|
|
88
|
+
repoRoot = ".",
|
|
89
|
+
sourceFiles = null,
|
|
90
|
+
signal,
|
|
91
|
+
}) => {
|
|
92
|
+
const ctx = {
|
|
93
|
+
discoveries,
|
|
94
|
+
envFiles,
|
|
95
|
+
packageJson,
|
|
96
|
+
repoRoot,
|
|
97
|
+
signal,
|
|
98
|
+
sourceFiles,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// 並列実行可能な (= sync / async) check を一括 await
|
|
102
|
+
const results = await Promise.all([
|
|
103
|
+
Promise.resolve(checkC1(discoveries)),
|
|
104
|
+
Promise.resolve(checkC2(discoveries)),
|
|
105
|
+
Promise.resolve(checkC3(discoveries)),
|
|
106
|
+
checkC4(ctx),
|
|
107
|
+
checkC5(ctx),
|
|
108
|
+
checkC6(ctx),
|
|
109
|
+
Promise.resolve(checkC7(discoveries)),
|
|
110
|
+
checkC8(ctx),
|
|
111
|
+
checkC9(ctx),
|
|
112
|
+
Promise.resolve(checkC10(discoveries)),
|
|
113
|
+
Promise.resolve(checkC11(discoveries)),
|
|
114
|
+
checkC12(ctx),
|
|
115
|
+
Promise.resolve(checkC13(discoveries)),
|
|
116
|
+
checkC14(ctx),
|
|
117
|
+
checkC15(ctx),
|
|
118
|
+
checkC16(ctx),
|
|
119
|
+
checkC17(ctx),
|
|
120
|
+
checkC18(ctx),
|
|
121
|
+
Promise.resolve(checkC19(discoveries)),
|
|
122
|
+
Promise.resolve(checkC20(discoveries)),
|
|
123
|
+
checkC21(ctx),
|
|
124
|
+
checkC22(ctx),
|
|
125
|
+
checkC23(ctx),
|
|
126
|
+
checkC24(ctx),
|
|
127
|
+
Promise.resolve(checkC25({ envFiles })),
|
|
128
|
+
Promise.resolve(checkC26({ envFiles })),
|
|
129
|
+
Promise.resolve(checkC27({ envFiles })),
|
|
130
|
+
checkC28(ctx),
|
|
131
|
+
checkC29(ctx),
|
|
132
|
+
checkC30(ctx),
|
|
133
|
+
]);
|
|
134
|
+
|
|
135
|
+
// 集計
|
|
136
|
+
const errorChecks = results.filter((c) => c.severity === "error");
|
|
137
|
+
const warnChecks = results.filter((c) => c.severity === "warn");
|
|
138
|
+
const failedErrors = errorChecks.filter((c) => !c.passed);
|
|
139
|
+
const failedWarns = warnChecks.filter((c) => !c.passed);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
checks: results,
|
|
143
|
+
summary: {
|
|
144
|
+
total: results.length,
|
|
145
|
+
errorTotal: errorChecks.length,
|
|
146
|
+
warnTotal: warnChecks.length,
|
|
147
|
+
errorFailed: failedErrors.length,
|
|
148
|
+
warnFailed: failedWarns.length,
|
|
149
|
+
passed: failedErrors.length === 0,
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// helper: 顧客 repo 標準位置から discoveries / env / package.json をロード
|
|
155
|
+
export const loadQualityInputs = async ({ repoRoot = ".", signal } = {}) => {
|
|
156
|
+
const discoveries = await readJsonIfExists(
|
|
157
|
+
repoRoot,
|
|
158
|
+
".cluebase/discoveries.json",
|
|
159
|
+
signal,
|
|
160
|
+
);
|
|
161
|
+
const packageJson = await readJsonIfExists(
|
|
162
|
+
repoRoot,
|
|
163
|
+
"package.json",
|
|
164
|
+
signal,
|
|
165
|
+
);
|
|
166
|
+
const envFiles = await readEnvFiles({ repoRoot, discoveries, signal });
|
|
167
|
+
return { discoveries, packageJson, envFiles };
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const readJsonIfExists = async (repoRoot, path, signal) => {
|
|
171
|
+
return readSetupDoctorJson({ repoRoot, path, signal, optional: true });
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const COMMON_ENV_FILE_NAMES = [
|
|
175
|
+
".env",
|
|
176
|
+
".env.local",
|
|
177
|
+
".env.development",
|
|
178
|
+
".env.production",
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
const readEnvFiles = async ({ repoRoot, discoveries, signal }) => {
|
|
182
|
+
const result = {};
|
|
183
|
+
const candidates = new Set();
|
|
184
|
+
// discoveries.env_files から
|
|
185
|
+
if (isPlainObject(discoveries) && isPlainObject(discoveries.env_files)) {
|
|
186
|
+
for (const kind of ["frontend", "backend"]) {
|
|
187
|
+
const entry = discoveries.env_files[kind];
|
|
188
|
+
if (isPlainObject(entry) && isString(entry.path)) {
|
|
189
|
+
candidates.add(entry.path);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// よくある場所
|
|
194
|
+
for (const name of COMMON_ENV_FILE_NAMES) {
|
|
195
|
+
candidates.add(name);
|
|
196
|
+
}
|
|
197
|
+
for (const candidate of candidates) {
|
|
198
|
+
const content = await readSetupDoctorFile({
|
|
199
|
+
repoRoot,
|
|
200
|
+
path: candidate,
|
|
201
|
+
signal,
|
|
202
|
+
optional: true,
|
|
203
|
+
});
|
|
204
|
+
if (content !== null) {
|
|
205
|
+
result[candidate] = content;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return result;
|
|
209
|
+
};
|