@beignet/cli 0.0.24 → 0.0.26
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/CHANGELOG.md +21 -0
- package/README.md +48 -5
- package/dist/config.d.ts +21 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +51 -0
- package/dist/config.js.map +1 -1
- package/dist/db.d.ts +30 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +201 -1
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +157 -235
- package/dist/inspect.js.map +1 -1
- package/dist/provider-audit.d.ts +131 -0
- package/dist/provider-audit.d.ts.map +1 -0
- package/dist/provider-audit.js +1013 -0
- package/dist/provider-audit.js.map +1 -0
- package/dist/templates/agents.js +2 -2
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +8 -1
- package/dist/templates/base.js.map +1 -1
- package/package.json +2 -2
- package/skills/app-structure/SKILL.md +27 -4
- package/src/config.ts +99 -0
- package/src/db.ts +316 -1
- package/src/index.ts +180 -1
- package/src/inspect.ts +298 -369
- package/src/provider-audit.ts +1681 -0
- package/src/templates/agents.ts +2 -2
- package/src/templates/base.ts +8 -1
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parseProviderPackageMetadata, } from "@beignet/core/providers";
|
|
5
|
+
import { defaultBeignetConfig, directoryPath, loadBeignetConfig, normalizePath, } from "./config.js";
|
|
6
|
+
export async function auditProviders(options = {}) {
|
|
7
|
+
const targetDir = path.resolve(options.cwd ?? process.cwd());
|
|
8
|
+
await assertDirectory(targetDir);
|
|
9
|
+
const files = await listFiles(targetDir);
|
|
10
|
+
const config = await loadBeignetConfig(targetDir, files);
|
|
11
|
+
const packageJson = await readPackageJson(targetDir, files);
|
|
12
|
+
const packageNames = await providerAuditPackageNames(targetDir, packageJson);
|
|
13
|
+
const sourceCache = new Map();
|
|
14
|
+
const providerEntries = await readProviderListEntries(targetDir, files, config, sourceCache);
|
|
15
|
+
const configFiles = providerOperationalConfigFiles(files, config);
|
|
16
|
+
const portsSource = files.includes(config.paths.ports)
|
|
17
|
+
? await readCachedSource(targetDir, config.paths.ports, sourceCache)
|
|
18
|
+
: "";
|
|
19
|
+
const infrastructurePath = directoryPath(path.dirname(config.paths.infrastructurePorts));
|
|
20
|
+
const schemaDir = `${infrastructurePath}/db/schema`;
|
|
21
|
+
const providers = [];
|
|
22
|
+
for (const packageName of packageNames) {
|
|
23
|
+
const source = await readProviderPackageMetadataSource(targetDir, packageName);
|
|
24
|
+
if (!source) {
|
|
25
|
+
providers.push(providerAuditEntryForMissingMetadata(packageName, packageJson));
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const parsed = parseProviderPackageMetadata(source.metadata);
|
|
29
|
+
if (!parsed.success) {
|
|
30
|
+
providers.push(providerAuditEntryForInvalidMetadata(packageName, packageJson, metadataSourcePath(targetDir, source.file), parsed.issues));
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
providers.push(await providerAuditEntryForRule({
|
|
34
|
+
targetDir,
|
|
35
|
+
files,
|
|
36
|
+
config,
|
|
37
|
+
configFiles,
|
|
38
|
+
infrastructurePath,
|
|
39
|
+
schemaDir,
|
|
40
|
+
sourceCache,
|
|
41
|
+
portsSource,
|
|
42
|
+
providerEntries,
|
|
43
|
+
sourceFile: source.file,
|
|
44
|
+
packageJson,
|
|
45
|
+
rule: providerDoctorRuleFromMetadata(packageName, parsed.metadata),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
providers.sort((left, right) => left.packageName.localeCompare(right.packageName));
|
|
49
|
+
return {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
targetDir,
|
|
52
|
+
providers,
|
|
53
|
+
summary: summarizeProviderAudit(providers),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function formatProviderAudit(result) {
|
|
57
|
+
if (result.providers.length === 0) {
|
|
58
|
+
return `No Beignet provider packages found in ${result.targetDir}.`;
|
|
59
|
+
}
|
|
60
|
+
const rows = result.providers.map((provider) => ({
|
|
61
|
+
packageName: provider.packageName,
|
|
62
|
+
metadata: provider.metadata.status,
|
|
63
|
+
registration: provider.registration,
|
|
64
|
+
env: requirementSummary(provider.requiredEnv),
|
|
65
|
+
tables: requirementSummary(provider.requiredTables),
|
|
66
|
+
appPorts: appPortsSummary(provider.appPorts),
|
|
67
|
+
notes: provider.notes.length === 0 ? "-" : provider.notes.join("; "),
|
|
68
|
+
}));
|
|
69
|
+
return [
|
|
70
|
+
`Provider audit for ${result.targetDir}`,
|
|
71
|
+
"",
|
|
72
|
+
table([
|
|
73
|
+
["PACKAGE", "METADATA", "REGISTRATION", "ENV", "TABLES", "APP PORTS"],
|
|
74
|
+
...rows.map((row) => [
|
|
75
|
+
row.packageName,
|
|
76
|
+
row.metadata,
|
|
77
|
+
row.registration,
|
|
78
|
+
row.env,
|
|
79
|
+
row.tables,
|
|
80
|
+
row.appPorts,
|
|
81
|
+
]),
|
|
82
|
+
]),
|
|
83
|
+
"",
|
|
84
|
+
...rows
|
|
85
|
+
.filter((row) => row.notes !== "-")
|
|
86
|
+
.map((row) => `${row.packageName}: ${row.notes}`),
|
|
87
|
+
"",
|
|
88
|
+
`Summary: ${result.summary.total} provider(s), ${result.summary.missingRegistration} missing registration, ${result.summary.missingEnv} missing env, ${result.summary.missingTables} missing tables, ${result.summary.missingAppPorts} missing app ports.`,
|
|
89
|
+
]
|
|
90
|
+
.filter((line, index, lines) => line !== "" || lines[index - 1] !== "")
|
|
91
|
+
.join("\n");
|
|
92
|
+
}
|
|
93
|
+
export async function providerDoctorRulesForInstalledPackages(targetDir, packageJson) {
|
|
94
|
+
const packageNames = [...installedPackageNames(packageJson)];
|
|
95
|
+
const rules = await Promise.all(packageNames.map(async (packageName) => {
|
|
96
|
+
const source = await readProviderPackageMetadataSource(targetDir, packageName);
|
|
97
|
+
if (!source)
|
|
98
|
+
return undefined;
|
|
99
|
+
const result = parseProviderPackageMetadata(source.metadata);
|
|
100
|
+
if (!result.success)
|
|
101
|
+
return undefined;
|
|
102
|
+
return providerDoctorRuleFromMetadata(packageName, result.metadata);
|
|
103
|
+
}));
|
|
104
|
+
return rules.filter((rule) => Boolean(rule));
|
|
105
|
+
}
|
|
106
|
+
export async function readProviderPackageMetadataSource(targetDir, packageName) {
|
|
107
|
+
for (const candidate of providerPackageJsonCandidates(targetDir, packageName)) {
|
|
108
|
+
const packageJson = await readJsonIfExists(candidate);
|
|
109
|
+
const metadata = packageJson?.beignet?.provider;
|
|
110
|
+
if (metadata !== undefined) {
|
|
111
|
+
return { packageName, file: candidate, metadata };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
export function providerDoctorRuleFromMetadata(packageName, metadata) {
|
|
117
|
+
const registration = metadata.registration;
|
|
118
|
+
return {
|
|
119
|
+
packageName,
|
|
120
|
+
displayName: metadata.displayName ?? packageName,
|
|
121
|
+
tokens: registration?.tokens ?? [],
|
|
122
|
+
appPorts: metadata.appPorts ?? [],
|
|
123
|
+
requiredEnv: metadata.requiredEnv ?? [],
|
|
124
|
+
requiredTables: metadata.requiredTables ?? [],
|
|
125
|
+
watchers: metadata.watchers ?? [],
|
|
126
|
+
registrationSeverity: registrationSeverityFromMetadata(registration),
|
|
127
|
+
variants: metadata.variants?.map((variant) => ({
|
|
128
|
+
name: variant.name,
|
|
129
|
+
displayName: variant.displayName ?? variant.name,
|
|
130
|
+
tokens: variant.registration?.tokens ?? [],
|
|
131
|
+
requiredEnv: variant.requiredEnv ?? [],
|
|
132
|
+
requiredTables: variant.requiredTables ?? [],
|
|
133
|
+
registrationSeverity: registrationSeverityFromMetadata(variant.registration),
|
|
134
|
+
})),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export function registrationSeverityFromMetadata(registration) {
|
|
138
|
+
return (registration?.severity ??
|
|
139
|
+
(registration?.required === true ? "warning" : undefined));
|
|
140
|
+
}
|
|
141
|
+
export function detectedProviderVariants(variants, providerEntries) {
|
|
142
|
+
return variants.filter((variant) => variant.tokens.some((token) => providerEntries.some((entry) => entry.includes(token))));
|
|
143
|
+
}
|
|
144
|
+
export function providerVariantRegistrationSeverity(variants) {
|
|
145
|
+
const severities = variants.map((variant) => variant.registrationSeverity);
|
|
146
|
+
if (severities.includes("warning"))
|
|
147
|
+
return "warning";
|
|
148
|
+
if (severities.includes("hint"))
|
|
149
|
+
return "hint";
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
export async function readProviderListEntries(targetDir, files, config, sourceCache) {
|
|
153
|
+
const providerFiles = serverProviderFiles(files, config);
|
|
154
|
+
const providerSource = (await Promise.all(providerFiles.map((file) => readCachedSource(targetDir, file, sourceCache)))).join("\n");
|
|
155
|
+
const providerListSource = extractProviderListSource(providerSource);
|
|
156
|
+
return providerListSource === undefined
|
|
157
|
+
? []
|
|
158
|
+
: providerListEntries(providerListSource);
|
|
159
|
+
}
|
|
160
|
+
export function registrationTokenHint(tokens) {
|
|
161
|
+
const token = tokens[0];
|
|
162
|
+
if (!token)
|
|
163
|
+
return "the provider";
|
|
164
|
+
return token.startsWith("create") ? `${token}()` : token;
|
|
165
|
+
}
|
|
166
|
+
export function diagnosticPackageJsonFile(targetDir, file) {
|
|
167
|
+
const relative = normalizePath(path.relative(targetDir, file));
|
|
168
|
+
if (relative.startsWith("../"))
|
|
169
|
+
return "package.json";
|
|
170
|
+
return relative;
|
|
171
|
+
}
|
|
172
|
+
export function formatProviderMetadataIssues(issues) {
|
|
173
|
+
return issues.map((issue) => `${issue.path} ${issue.message}`).join("; ");
|
|
174
|
+
}
|
|
175
|
+
export function installedPackageNames(packageJson) {
|
|
176
|
+
return new Set([
|
|
177
|
+
...Object.keys(packageJson?.dependencies ?? {}),
|
|
178
|
+
...Object.keys(packageJson?.devDependencies ?? {}),
|
|
179
|
+
...Object.keys(packageJson?.peerDependencies ?? {}),
|
|
180
|
+
]);
|
|
181
|
+
}
|
|
182
|
+
export function providerOperationalConfigFiles(files, config) {
|
|
183
|
+
return files.filter((file) => {
|
|
184
|
+
if (file === ".env.example")
|
|
185
|
+
return true;
|
|
186
|
+
if (file === "drizzle.config.ts")
|
|
187
|
+
return true;
|
|
188
|
+
if (file === "lib/env.ts" || file === "lib/better-auth.ts")
|
|
189
|
+
return true;
|
|
190
|
+
if (isInfraOrServerFile(file, config) && file.endsWith(".ts"))
|
|
191
|
+
return true;
|
|
192
|
+
return false;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
export async function providerRequiredEnvExists(targetDir, configFiles, envVar, sourceCache) {
|
|
196
|
+
for (const file of configFiles) {
|
|
197
|
+
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
198
|
+
if (source.includes(envVar))
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
export async function providerRequiredTableExists(targetDir, files, config, infrastructurePath, schemaDir, tableName, sourceCache) {
|
|
204
|
+
return databaseTableExists({
|
|
205
|
+
targetDir,
|
|
206
|
+
files,
|
|
207
|
+
config,
|
|
208
|
+
infrastructurePath,
|
|
209
|
+
schemaDir,
|
|
210
|
+
tableName,
|
|
211
|
+
sourceCache,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
const databaseSourceFilePattern = /\.(?:ts|tsx|js|jsx|mts|mjs|cts|cjs|sql)$/;
|
|
215
|
+
async function databaseTableExists(options) {
|
|
216
|
+
const sourceFiles = await databaseTableSourceFiles(options);
|
|
217
|
+
const seen = new Set();
|
|
218
|
+
for (const file of sourceFiles) {
|
|
219
|
+
if (seen.has(file.absolutePath))
|
|
220
|
+
continue;
|
|
221
|
+
seen.add(file.absolutePath);
|
|
222
|
+
const source = await readDatabaseSourceFile(options.targetDir, file, options.sourceCache);
|
|
223
|
+
if (sourceCreatesTable(source, options.tableName) ||
|
|
224
|
+
(file.kind === "schema" &&
|
|
225
|
+
sourceDeclaresSchemaTable(source, options.tableName))) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
async function databaseTableSourceFiles(options) {
|
|
232
|
+
return [
|
|
233
|
+
...defaultDatabaseTableSourceFiles(options.targetDir, options.files, options.infrastructurePath, options.schemaDir),
|
|
234
|
+
...(await configuredDatabaseSchemaSourceFiles(options.targetDir, options.config)),
|
|
235
|
+
...(await discoveredDatabaseSchemaImportFiles(options)),
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
function defaultDatabaseTableSourceFiles(targetDir, files, infrastructurePath, schemaDir) {
|
|
239
|
+
const schemaPrefix = `${schemaDir}/`;
|
|
240
|
+
return files
|
|
241
|
+
.filter((file) => !isTestSourceFile(file) &&
|
|
242
|
+
databaseSourceFilePattern.test(file) &&
|
|
243
|
+
(file.startsWith(schemaPrefix) ||
|
|
244
|
+
file.startsWith("drizzle/") ||
|
|
245
|
+
file.startsWith(`${infrastructurePath}/db/`)))
|
|
246
|
+
.map((file) => ({
|
|
247
|
+
absolutePath: path.join(targetDir, file),
|
|
248
|
+
kind: file.startsWith(schemaPrefix) ? "schema" : "setup",
|
|
249
|
+
relativePath: file,
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
252
|
+
async function configuredDatabaseSchemaSourceFiles(targetDir, config) {
|
|
253
|
+
const sourceFiles = [];
|
|
254
|
+
for (const source of config.database.schemaSources) {
|
|
255
|
+
sourceFiles.push(...(await resolveDatabaseSchemaSource(targetDir, source)));
|
|
256
|
+
}
|
|
257
|
+
return sourceFiles;
|
|
258
|
+
}
|
|
259
|
+
async function discoveredDatabaseSchemaImportFiles(options) {
|
|
260
|
+
const sourceFiles = [];
|
|
261
|
+
for (const file of databaseSchemaImportDiscoveryFiles(options.files, options.config, options.infrastructurePath, options.schemaDir)) {
|
|
262
|
+
const source = await readCachedSource(options.targetDir, file, options.sourceCache);
|
|
263
|
+
for (const specifier of moduleSpecifiers(source)) {
|
|
264
|
+
if (!looksLikeDatabaseSchemaSource(specifier))
|
|
265
|
+
continue;
|
|
266
|
+
sourceFiles.push(...(await resolveDatabaseSchemaSource(options.targetDir, specifier, file)));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return sourceFiles;
|
|
270
|
+
}
|
|
271
|
+
function databaseSchemaImportDiscoveryFiles(files, config, infrastructurePath, schemaDir) {
|
|
272
|
+
const candidates = new Set([
|
|
273
|
+
"drizzle.config.ts",
|
|
274
|
+
"drizzle.config.mts",
|
|
275
|
+
"drizzle.config.js",
|
|
276
|
+
"drizzle.config.mjs",
|
|
277
|
+
...serverProviderFiles(files, config),
|
|
278
|
+
]);
|
|
279
|
+
for (const file of files) {
|
|
280
|
+
if (!isTestSourceFile(file) &&
|
|
281
|
+
databaseSourceFilePattern.test(file) &&
|
|
282
|
+
(file.startsWith(`${infrastructurePath}/db/`) ||
|
|
283
|
+
file.startsWith(`${schemaDir}/`))) {
|
|
284
|
+
candidates.add(file);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return [...candidates].filter((file) => files.includes(file));
|
|
288
|
+
}
|
|
289
|
+
async function resolveDatabaseSchemaSource(targetDir, source, importerFile) {
|
|
290
|
+
if (source.startsWith("@/")) {
|
|
291
|
+
return collectSourceFilesFromAppPath(targetDir, source.slice(2));
|
|
292
|
+
}
|
|
293
|
+
if (source.startsWith(".")) {
|
|
294
|
+
if (!importerFile)
|
|
295
|
+
return [];
|
|
296
|
+
return collectSourceFilesFromAppPath(targetDir, path.posix.join(path.posix.dirname(importerFile), source));
|
|
297
|
+
}
|
|
298
|
+
if (path.isAbsolute(source))
|
|
299
|
+
return [];
|
|
300
|
+
if (!source.startsWith("@") && !importerFile) {
|
|
301
|
+
const appFiles = await collectSourceFilesFromAppPath(targetDir, source);
|
|
302
|
+
if (appFiles.length > 0)
|
|
303
|
+
return appFiles;
|
|
304
|
+
}
|
|
305
|
+
return collectSourceFilesFromPackageSpecifier(targetDir, source);
|
|
306
|
+
}
|
|
307
|
+
async function collectSourceFilesFromAppPath(targetDir, input) {
|
|
308
|
+
const normalized = normalizePath(path.posix.normalize(input)).replace(/^\/+/, "");
|
|
309
|
+
if (normalized === ".." || normalized.startsWith("../"))
|
|
310
|
+
return [];
|
|
311
|
+
return collectSourceFilesFromAbsolutePath(path.join(targetDir, normalized), targetDir);
|
|
312
|
+
}
|
|
313
|
+
async function collectSourceFilesFromPackageSpecifier(targetDir, specifier) {
|
|
314
|
+
const parsed = packageSpecifierParts(specifier);
|
|
315
|
+
if (!parsed)
|
|
316
|
+
return [];
|
|
317
|
+
const packageDir = path.join(targetDir, "node_modules", ...parsed.packageName.split("/"));
|
|
318
|
+
const directPath = path.join(packageDir, parsed.subpath);
|
|
319
|
+
const directFiles = await collectSourceFilesFromAbsolutePath(directPath, targetDir);
|
|
320
|
+
if (directFiles.length > 0)
|
|
321
|
+
return directFiles;
|
|
322
|
+
const packageJson = await readJsonIfExists(path.join(packageDir, "package.json"));
|
|
323
|
+
if (!packageJson)
|
|
324
|
+
return [];
|
|
325
|
+
const exported = packageExportTarget(packageJson.exports, parsed.subpath);
|
|
326
|
+
if (exported) {
|
|
327
|
+
const exportedFiles = await collectSourceFilesFromAbsolutePath(path.join(packageDir, exported.replace(/^\.\//, "")), targetDir);
|
|
328
|
+
if (exportedFiles.length > 0)
|
|
329
|
+
return exportedFiles;
|
|
330
|
+
}
|
|
331
|
+
if (!parsed.subpath) {
|
|
332
|
+
for (const entrypoint of [
|
|
333
|
+
packageJson.types,
|
|
334
|
+
packageJson.module,
|
|
335
|
+
packageJson.main,
|
|
336
|
+
]) {
|
|
337
|
+
if (!entrypoint)
|
|
338
|
+
continue;
|
|
339
|
+
const entrypointFiles = await collectSourceFilesFromAbsolutePath(path.join(packageDir, entrypoint), targetDir);
|
|
340
|
+
if (entrypointFiles.length > 0)
|
|
341
|
+
return entrypointFiles;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
async function collectSourceFilesFromAbsolutePath(absolutePath, targetDir) {
|
|
347
|
+
const filePath = await resolveSourceFilePath(absolutePath);
|
|
348
|
+
if (filePath) {
|
|
349
|
+
return [
|
|
350
|
+
{
|
|
351
|
+
absolutePath: filePath,
|
|
352
|
+
kind: "schema",
|
|
353
|
+
relativePath: relativePathInsideTarget(targetDir, filePath),
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
const stats = await statIfExists(absolutePath);
|
|
358
|
+
if (!stats?.isDirectory())
|
|
359
|
+
return [];
|
|
360
|
+
const files = [];
|
|
361
|
+
async function visit(dir) {
|
|
362
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
363
|
+
for (const entry of entries) {
|
|
364
|
+
if (entry.name === "node_modules" ||
|
|
365
|
+
entry.name === ".git" ||
|
|
366
|
+
entry.name === ".next" ||
|
|
367
|
+
entry.name === ".turbo" ||
|
|
368
|
+
entry.name === "coverage") {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
const entryPath = path.join(dir, entry.name);
|
|
372
|
+
if (entry.isDirectory()) {
|
|
373
|
+
await visit(entryPath);
|
|
374
|
+
}
|
|
375
|
+
else if (entry.isFile() &&
|
|
376
|
+
databaseSourceFilePattern.test(entry.name) &&
|
|
377
|
+
!isTestSourceFile(normalizePath(entryPath))) {
|
|
378
|
+
files.push({
|
|
379
|
+
absolutePath: entryPath,
|
|
380
|
+
kind: "schema",
|
|
381
|
+
relativePath: relativePathInsideTarget(targetDir, entryPath),
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
await visit(absolutePath);
|
|
387
|
+
return files;
|
|
388
|
+
}
|
|
389
|
+
async function resolveSourceFilePath(absolutePath) {
|
|
390
|
+
const candidates = [
|
|
391
|
+
absolutePath,
|
|
392
|
+
`${absolutePath}.ts`,
|
|
393
|
+
`${absolutePath}.tsx`,
|
|
394
|
+
`${absolutePath}.mts`,
|
|
395
|
+
`${absolutePath}.cts`,
|
|
396
|
+
`${absolutePath}.js`,
|
|
397
|
+
`${absolutePath}.jsx`,
|
|
398
|
+
`${absolutePath}.mjs`,
|
|
399
|
+
`${absolutePath}.cjs`,
|
|
400
|
+
`${absolutePath}.sql`,
|
|
401
|
+
path.join(absolutePath, "index.ts"),
|
|
402
|
+
path.join(absolutePath, "index.tsx"),
|
|
403
|
+
path.join(absolutePath, "index.mts"),
|
|
404
|
+
path.join(absolutePath, "index.cts"),
|
|
405
|
+
path.join(absolutePath, "index.js"),
|
|
406
|
+
path.join(absolutePath, "index.jsx"),
|
|
407
|
+
path.join(absolutePath, "index.mjs"),
|
|
408
|
+
path.join(absolutePath, "index.cjs"),
|
|
409
|
+
];
|
|
410
|
+
for (const candidate of candidates) {
|
|
411
|
+
const stats = await statIfExists(candidate);
|
|
412
|
+
if (stats?.isFile() && databaseSourceFilePattern.test(candidate)) {
|
|
413
|
+
return candidate;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return undefined;
|
|
417
|
+
}
|
|
418
|
+
async function readDatabaseSourceFile(targetDir, file, sourceCache) {
|
|
419
|
+
if (file.relativePath) {
|
|
420
|
+
return readCachedSource(targetDir, file.relativePath, sourceCache);
|
|
421
|
+
}
|
|
422
|
+
const cacheKey = `absolute:${file.absolutePath}`;
|
|
423
|
+
const cached = sourceCache.get(cacheKey);
|
|
424
|
+
if (cached !== undefined)
|
|
425
|
+
return cached;
|
|
426
|
+
const source = await readFile(file.absolutePath, "utf8");
|
|
427
|
+
sourceCache.set(cacheKey, source);
|
|
428
|
+
return source;
|
|
429
|
+
}
|
|
430
|
+
function relativePathInsideTarget(targetDir, absolutePath) {
|
|
431
|
+
const relativePath = normalizePath(path.relative(targetDir, absolutePath));
|
|
432
|
+
if (relativePath === "" || relativePath.startsWith("../"))
|
|
433
|
+
return undefined;
|
|
434
|
+
return relativePath;
|
|
435
|
+
}
|
|
436
|
+
async function statIfExists(filePath) {
|
|
437
|
+
try {
|
|
438
|
+
return await stat(filePath);
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
if (error.code === "ENOENT")
|
|
442
|
+
return undefined;
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function packageSpecifierParts(specifier) {
|
|
447
|
+
const parts = specifier.split("/").filter(Boolean);
|
|
448
|
+
if (parts.length === 0)
|
|
449
|
+
return undefined;
|
|
450
|
+
if (specifier.startsWith("@")) {
|
|
451
|
+
if (parts.length < 2)
|
|
452
|
+
return undefined;
|
|
453
|
+
return {
|
|
454
|
+
packageName: `${parts[0]}/${parts[1]}`,
|
|
455
|
+
subpath: parts.slice(2).join("/"),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
packageName: parts[0],
|
|
460
|
+
subpath: parts.slice(1).join("/"),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function packageExportTarget(exportsValue, subpath) {
|
|
464
|
+
if (exportsValue === undefined)
|
|
465
|
+
return undefined;
|
|
466
|
+
const key = subpath ? `./${subpath}` : ".";
|
|
467
|
+
if (typeof exportsValue === "string") {
|
|
468
|
+
return key === "." ? exportsValue : undefined;
|
|
469
|
+
}
|
|
470
|
+
if (Array.isArray(exportsValue)) {
|
|
471
|
+
for (const value of exportsValue) {
|
|
472
|
+
const target = packageExportTarget(value, subpath);
|
|
473
|
+
if (target)
|
|
474
|
+
return target;
|
|
475
|
+
}
|
|
476
|
+
return undefined;
|
|
477
|
+
}
|
|
478
|
+
if (!isRecord(exportsValue))
|
|
479
|
+
return undefined;
|
|
480
|
+
const exact = exportsValue[key];
|
|
481
|
+
const target = exportTargetValue(exact);
|
|
482
|
+
if (target)
|
|
483
|
+
return target;
|
|
484
|
+
for (const [pattern, value] of Object.entries(exportsValue)) {
|
|
485
|
+
if (!pattern.includes("*"))
|
|
486
|
+
continue;
|
|
487
|
+
const [prefix, suffix] = pattern.split("*");
|
|
488
|
+
if (!key.startsWith(prefix) || !key.endsWith(suffix))
|
|
489
|
+
continue;
|
|
490
|
+
const replacement = key.slice(prefix.length, key.length - suffix.length);
|
|
491
|
+
const patternTarget = exportTargetValue(value);
|
|
492
|
+
if (patternTarget)
|
|
493
|
+
return patternTarget.replace("*", replacement);
|
|
494
|
+
}
|
|
495
|
+
return undefined;
|
|
496
|
+
}
|
|
497
|
+
function exportTargetValue(value) {
|
|
498
|
+
if (typeof value === "string")
|
|
499
|
+
return value;
|
|
500
|
+
if (Array.isArray(value)) {
|
|
501
|
+
for (const item of value) {
|
|
502
|
+
const target = exportTargetValue(item);
|
|
503
|
+
if (target)
|
|
504
|
+
return target;
|
|
505
|
+
}
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
if (!isRecord(value))
|
|
509
|
+
return undefined;
|
|
510
|
+
for (const key of ["source", "types", "import", "default", "require"]) {
|
|
511
|
+
const target = exportTargetValue(value[key]);
|
|
512
|
+
if (target)
|
|
513
|
+
return target;
|
|
514
|
+
}
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
function moduleSpecifiers(source) {
|
|
518
|
+
const specifiers = new Set();
|
|
519
|
+
for (const match of source.matchAll(/\b(?:import|export)\b[\s\S]{0,300}?\bfrom\s*["']([^"']+)["']/g)) {
|
|
520
|
+
specifiers.add(match[1]);
|
|
521
|
+
}
|
|
522
|
+
for (const match of source.matchAll(/\bimport\s*["']([^"']+)["']/g)) {
|
|
523
|
+
specifiers.add(match[1]);
|
|
524
|
+
}
|
|
525
|
+
return [...specifiers];
|
|
526
|
+
}
|
|
527
|
+
function looksLikeDatabaseSchemaSource(specifier) {
|
|
528
|
+
return /(?:^|\/)(?:db|database|schema|schemas)(?:\/|$)/i.test(specifier);
|
|
529
|
+
}
|
|
530
|
+
function isRecord(value) {
|
|
531
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
532
|
+
}
|
|
533
|
+
function configuredProviderRequiredTables(tableNames, config) {
|
|
534
|
+
return tableNames.map((tableName) => configuredProviderRequiredTable(tableName, config));
|
|
535
|
+
}
|
|
536
|
+
function configuredProviderRequiredTable(tableName, config) {
|
|
537
|
+
for (const [key, defaultTableName] of Object.entries(defaultBeignetConfig.database.tables)) {
|
|
538
|
+
if (tableName === defaultTableName)
|
|
539
|
+
return config.database.tables[key];
|
|
540
|
+
}
|
|
541
|
+
return tableName;
|
|
542
|
+
}
|
|
543
|
+
async function providerAuditPackageNames(targetDir, packageJson) {
|
|
544
|
+
const names = [...installedPackageNames(packageJson)].sort();
|
|
545
|
+
const candidates = await Promise.all(names.map(async (packageName) => ({
|
|
546
|
+
packageName,
|
|
547
|
+
hasMetadata: (await readProviderPackageMetadataSource(targetDir, packageName)) !==
|
|
548
|
+
undefined,
|
|
549
|
+
})));
|
|
550
|
+
return candidates
|
|
551
|
+
.filter(({ packageName, hasMetadata }) => hasMetadata || isLikelyProviderPackageName(packageName))
|
|
552
|
+
.map(({ packageName }) => packageName);
|
|
553
|
+
}
|
|
554
|
+
function isLikelyProviderPackageName(packageName) {
|
|
555
|
+
return (packageName === "@beignet/devtools" ||
|
|
556
|
+
packageName.startsWith("@beignet/provider-") ||
|
|
557
|
+
packageName.includes("beignet-provider"));
|
|
558
|
+
}
|
|
559
|
+
function providerPackageJsonCandidates(targetDir, packageName) {
|
|
560
|
+
const candidates = [
|
|
561
|
+
path.join(targetDir, "node_modules", ...packageName.split("/"), "package.json"),
|
|
562
|
+
];
|
|
563
|
+
if (packageName.startsWith("@beignet/")) {
|
|
564
|
+
candidates.push(path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..", "packages", packageName.slice("@beignet/".length), "package.json"));
|
|
565
|
+
}
|
|
566
|
+
return candidates;
|
|
567
|
+
}
|
|
568
|
+
async function readJsonIfExists(file) {
|
|
569
|
+
try {
|
|
570
|
+
return JSON.parse(await readFile(file, "utf8"));
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (error instanceof Error &&
|
|
574
|
+
"code" in error &&
|
|
575
|
+
error.code === "ENOENT") {
|
|
576
|
+
return undefined;
|
|
577
|
+
}
|
|
578
|
+
throw error;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async function providerAuditEntryForRule(options) {
|
|
582
|
+
const detectedVariants = options.rule.variants
|
|
583
|
+
? detectedProviderVariants(options.rule.variants, options.providerEntries)
|
|
584
|
+
: [];
|
|
585
|
+
const variants = await Promise.all((options.rule.variants ?? []).map(async (variant) => providerVariantAudit({
|
|
586
|
+
...options,
|
|
587
|
+
variant,
|
|
588
|
+
registered: detectedVariants.includes(variant),
|
|
589
|
+
})));
|
|
590
|
+
const requiredEnv = options.rule.variants !== undefined
|
|
591
|
+
? aggregateVariantRequirement(variants, "requiredEnv")
|
|
592
|
+
: await requirementAudit(options.rule.requiredEnv ?? [], (envVar) => providerRequiredEnvExists(options.targetDir, options.configFiles, envVar, options.sourceCache));
|
|
593
|
+
const requiredTables = options.rule.variants !== undefined
|
|
594
|
+
? aggregateVariantRequirement(variants, "requiredTables")
|
|
595
|
+
: await requirementAudit(configuredProviderRequiredTables(options.rule.requiredTables ?? [], options.config), (tableName) => providerRequiredTableExists(options.targetDir, options.files, options.config, options.infrastructurePath, options.schemaDir, tableName, options.sourceCache));
|
|
596
|
+
const registration = options.rule.variants !== undefined
|
|
597
|
+
? variantRegistrationAudit(options.rule.variants, detectedVariants)
|
|
598
|
+
: registrationAudit(options.rule.tokens, options.rule.registrationSeverity, options.providerEntries);
|
|
599
|
+
const appPorts = appPortsAudit(options.rule.appPorts ?? [], options.portsSource);
|
|
600
|
+
const notes = providerAuditNotes({
|
|
601
|
+
metadataStatus: "valid",
|
|
602
|
+
registration,
|
|
603
|
+
requiredEnv,
|
|
604
|
+
requiredTables,
|
|
605
|
+
appPorts,
|
|
606
|
+
});
|
|
607
|
+
return {
|
|
608
|
+
packageName: options.rule.packageName,
|
|
609
|
+
displayName: options.rule.displayName,
|
|
610
|
+
declared: installedPackageNames(options.packageJson).has(options.rule.packageName),
|
|
611
|
+
metadata: {
|
|
612
|
+
status: "valid",
|
|
613
|
+
source: metadataSourcePath(options.targetDir, options.sourceFile),
|
|
614
|
+
issues: [],
|
|
615
|
+
},
|
|
616
|
+
registration,
|
|
617
|
+
requiredEnv,
|
|
618
|
+
requiredTables,
|
|
619
|
+
appPorts,
|
|
620
|
+
watchers: [...(options.rule.watchers ?? [])],
|
|
621
|
+
variants,
|
|
622
|
+
notes,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
async function providerVariantAudit(options) {
|
|
626
|
+
if (!options.registered) {
|
|
627
|
+
return {
|
|
628
|
+
name: options.variant.name,
|
|
629
|
+
displayName: options.variant.displayName,
|
|
630
|
+
registration: "inactive",
|
|
631
|
+
requiredEnv: inactiveRequirement(options.variant.requiredEnv),
|
|
632
|
+
requiredTables: inactiveRequirement(options.variant.requiredTables),
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
return {
|
|
636
|
+
name: options.variant.name,
|
|
637
|
+
displayName: options.variant.displayName,
|
|
638
|
+
registration: registrationAudit(options.variant.tokens, options.variant.registrationSeverity, options.providerEntries),
|
|
639
|
+
requiredEnv: await requirementAudit(options.variant.requiredEnv, (envVar) => providerRequiredEnvExists(options.targetDir, options.configFiles, envVar, options.sourceCache)),
|
|
640
|
+
requiredTables: await requirementAudit(configuredProviderRequiredTables(options.variant.requiredTables, options.config), (tableName) => providerRequiredTableExists(options.targetDir, options.files, options.config, options.infrastructurePath, options.schemaDir, tableName, options.sourceCache)),
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function providerAuditEntryForMissingMetadata(packageName, packageJson) {
|
|
644
|
+
return {
|
|
645
|
+
packageName,
|
|
646
|
+
displayName: packageName,
|
|
647
|
+
declared: installedPackageNames(packageJson).has(packageName),
|
|
648
|
+
metadata: { status: "missing", issues: [] },
|
|
649
|
+
registration: "not-required",
|
|
650
|
+
requiredEnv: emptyRequirement(),
|
|
651
|
+
requiredTables: emptyRequirement(),
|
|
652
|
+
appPorts: emptyAppPorts(),
|
|
653
|
+
watchers: [],
|
|
654
|
+
variants: [],
|
|
655
|
+
notes: ["provider metadata missing"],
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function providerAuditEntryForInvalidMetadata(packageName, packageJson, source, issues) {
|
|
659
|
+
return {
|
|
660
|
+
packageName,
|
|
661
|
+
displayName: packageName,
|
|
662
|
+
declared: installedPackageNames(packageJson).has(packageName),
|
|
663
|
+
metadata: {
|
|
664
|
+
status: "invalid",
|
|
665
|
+
source,
|
|
666
|
+
issues: [...issues],
|
|
667
|
+
},
|
|
668
|
+
registration: "not-required",
|
|
669
|
+
requiredEnv: emptyRequirement(),
|
|
670
|
+
requiredTables: emptyRequirement(),
|
|
671
|
+
appPorts: emptyAppPorts(),
|
|
672
|
+
watchers: [],
|
|
673
|
+
variants: [],
|
|
674
|
+
notes: [`invalid metadata: ${formatProviderMetadataIssues(issues)}`],
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function registrationAudit(tokens, severity, providerEntries) {
|
|
678
|
+
if (tokens.some((token) => providerEntries.some((entry) => entry.includes(token)))) {
|
|
679
|
+
return "registered";
|
|
680
|
+
}
|
|
681
|
+
if (severity === "warning")
|
|
682
|
+
return "missing";
|
|
683
|
+
if (severity === "hint")
|
|
684
|
+
return "optional-missing";
|
|
685
|
+
return "not-required";
|
|
686
|
+
}
|
|
687
|
+
function variantRegistrationAudit(variants, detectedVariants) {
|
|
688
|
+
if (detectedVariants.length > 0)
|
|
689
|
+
return "registered";
|
|
690
|
+
const severity = providerVariantRegistrationSeverity(variants);
|
|
691
|
+
if (severity === "warning")
|
|
692
|
+
return "missing";
|
|
693
|
+
if (severity === "hint")
|
|
694
|
+
return "optional-missing";
|
|
695
|
+
return "not-required";
|
|
696
|
+
}
|
|
697
|
+
async function requirementAudit(required, exists) {
|
|
698
|
+
if (required.length === 0)
|
|
699
|
+
return emptyRequirement();
|
|
700
|
+
const missing = [];
|
|
701
|
+
for (const requirement of required) {
|
|
702
|
+
if (await exists(requirement))
|
|
703
|
+
continue;
|
|
704
|
+
missing.push(requirement);
|
|
705
|
+
}
|
|
706
|
+
return {
|
|
707
|
+
status: missing.length === 0 ? "present" : "missing",
|
|
708
|
+
required: [...required],
|
|
709
|
+
missing,
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function inactiveRequirement(required) {
|
|
713
|
+
return {
|
|
714
|
+
status: "inactive",
|
|
715
|
+
required: [...required],
|
|
716
|
+
missing: [],
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
function emptyRequirement() {
|
|
720
|
+
return { status: "none", required: [], missing: [] };
|
|
721
|
+
}
|
|
722
|
+
function aggregateVariantRequirement(variants, key) {
|
|
723
|
+
const active = variants
|
|
724
|
+
.map((variant) => variant[key])
|
|
725
|
+
.filter((requirement) => requirement.status !== "inactive");
|
|
726
|
+
if (active.length === 0)
|
|
727
|
+
return { status: "inactive", required: [], missing: [] };
|
|
728
|
+
const required = unique(active.flatMap((requirement) => requirement.required));
|
|
729
|
+
const missing = unique(active.flatMap((requirement) => requirement.missing));
|
|
730
|
+
if (required.length === 0)
|
|
731
|
+
return emptyRequirement();
|
|
732
|
+
return {
|
|
733
|
+
status: missing.length === 0 ? "present" : "missing",
|
|
734
|
+
required,
|
|
735
|
+
missing,
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function appPortsAudit(required, portsSource) {
|
|
739
|
+
if (required.length === 0)
|
|
740
|
+
return emptyAppPorts();
|
|
741
|
+
const missing = required.filter((port) => !portsSource.includes(`${port.name}:`));
|
|
742
|
+
return {
|
|
743
|
+
status: missing.length === 0 ? "present" : "missing",
|
|
744
|
+
required: [...required],
|
|
745
|
+
missing,
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function emptyAppPorts() {
|
|
749
|
+
return { status: "none", required: [], missing: [] };
|
|
750
|
+
}
|
|
751
|
+
function providerAuditNotes(input) {
|
|
752
|
+
const notes = [];
|
|
753
|
+
if (input.metadataStatus === "invalid")
|
|
754
|
+
notes.push("metadata invalid");
|
|
755
|
+
if (input.metadataStatus === "missing")
|
|
756
|
+
notes.push("metadata missing");
|
|
757
|
+
if (input.registration === "missing")
|
|
758
|
+
notes.push("registration missing");
|
|
759
|
+
if (input.registration === "optional-missing") {
|
|
760
|
+
notes.push("optional provider not registered");
|
|
761
|
+
}
|
|
762
|
+
if (input.requiredEnv.missing.length > 0) {
|
|
763
|
+
notes.push(`missing env: ${input.requiredEnv.missing.join(", ")}`);
|
|
764
|
+
}
|
|
765
|
+
if (input.requiredTables.missing.length > 0) {
|
|
766
|
+
notes.push(`missing tables: ${input.requiredTables.missing.join(", ")}`);
|
|
767
|
+
}
|
|
768
|
+
if (input.appPorts.missing.length > 0) {
|
|
769
|
+
notes.push(`missing app ports: ${input.appPorts.missing
|
|
770
|
+
.map((port) => port.name)
|
|
771
|
+
.join(", ")}`);
|
|
772
|
+
}
|
|
773
|
+
return notes;
|
|
774
|
+
}
|
|
775
|
+
function summarizeProviderAudit(providers) {
|
|
776
|
+
return {
|
|
777
|
+
total: providers.length,
|
|
778
|
+
validMetadata: providers.filter((provider) => provider.metadata.status === "valid").length,
|
|
779
|
+
invalidMetadata: providers.filter((provider) => provider.metadata.status === "invalid").length,
|
|
780
|
+
missingMetadata: providers.filter((provider) => provider.metadata.status === "missing").length,
|
|
781
|
+
registered: providers.filter((provider) => provider.registration === "registered").length,
|
|
782
|
+
missingRegistration: providers.filter((provider) => provider.registration === "missing").length,
|
|
783
|
+
missingEnv: providers.filter((provider) => provider.requiredEnv.status === "missing").length,
|
|
784
|
+
missingTables: providers.filter((provider) => provider.requiredTables.status === "missing").length,
|
|
785
|
+
missingAppPorts: providers.filter((provider) => provider.appPorts.status === "missing").length,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
async function readPackageJson(targetDir, files) {
|
|
789
|
+
if (!files.includes("package.json"))
|
|
790
|
+
return undefined;
|
|
791
|
+
return JSON.parse(await readFile(path.join(targetDir, "package.json"), "utf8"));
|
|
792
|
+
}
|
|
793
|
+
async function assertDirectory(targetDir) {
|
|
794
|
+
try {
|
|
795
|
+
const stats = await stat(targetDir);
|
|
796
|
+
if (!stats.isDirectory()) {
|
|
797
|
+
throw new Error(`${targetDir} is not a directory.`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
catch (error) {
|
|
801
|
+
if (error instanceof Error && error.message.includes("not a directory")) {
|
|
802
|
+
throw error;
|
|
803
|
+
}
|
|
804
|
+
throw new Error(`Directory ${targetDir} does not exist.`);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
async function listFiles(targetDir) {
|
|
808
|
+
const files = [];
|
|
809
|
+
async function visit(relativeDir) {
|
|
810
|
+
const absoluteDir = path.join(targetDir, relativeDir);
|
|
811
|
+
const entries = await readdir(absoluteDir, { withFileTypes: true });
|
|
812
|
+
for (const entry of entries) {
|
|
813
|
+
if (entry.name === "node_modules" ||
|
|
814
|
+
entry.name === ".next" ||
|
|
815
|
+
entry.name === ".git" ||
|
|
816
|
+
entry.name === ".turbo" ||
|
|
817
|
+
entry.name === "coverage" ||
|
|
818
|
+
entry.name === "dist") {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
const relativePath = normalizePath(path.join(relativeDir, entry.name));
|
|
822
|
+
if (entry.isDirectory()) {
|
|
823
|
+
await visit(relativePath);
|
|
824
|
+
}
|
|
825
|
+
else if (entry.isFile()) {
|
|
826
|
+
files.push(relativePath);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
await visit("");
|
|
831
|
+
return files.sort();
|
|
832
|
+
}
|
|
833
|
+
async function readCachedSource(targetDir, file, sourceCache) {
|
|
834
|
+
const cached = sourceCache.get(file);
|
|
835
|
+
if (cached !== undefined)
|
|
836
|
+
return cached;
|
|
837
|
+
const source = await readFile(path.join(targetDir, file), "utf8");
|
|
838
|
+
sourceCache.set(file, source);
|
|
839
|
+
return source;
|
|
840
|
+
}
|
|
841
|
+
export function serverProviderFiles(files, config) {
|
|
842
|
+
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
843
|
+
const candidates = [`${serverDir}/providers.ts`, config.paths.server];
|
|
844
|
+
return candidates.filter((file) => files.includes(file));
|
|
845
|
+
}
|
|
846
|
+
function extractProviderListSource(source) {
|
|
847
|
+
const listMatches = [
|
|
848
|
+
...source.matchAll(/\bproviders\s*=\s*\[([\s\S]*?)\]\s*(?:;|as const)/g),
|
|
849
|
+
...source.matchAll(/\bproviders\s*:\s*\[([\s\S]*?)\]/g),
|
|
850
|
+
];
|
|
851
|
+
if (listMatches.length === 0)
|
|
852
|
+
return undefined;
|
|
853
|
+
return listMatches.map((match) => match[1]).join("\n");
|
|
854
|
+
}
|
|
855
|
+
function providerListEntries(source) {
|
|
856
|
+
const entries = [];
|
|
857
|
+
let current = "";
|
|
858
|
+
let depth = 0;
|
|
859
|
+
let quote;
|
|
860
|
+
let escaped = false;
|
|
861
|
+
let lineComment = false;
|
|
862
|
+
let blockComment = false;
|
|
863
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
864
|
+
const char = source[index];
|
|
865
|
+
const next = source[index + 1];
|
|
866
|
+
if (lineComment) {
|
|
867
|
+
if (char === "\n") {
|
|
868
|
+
lineComment = false;
|
|
869
|
+
current += char;
|
|
870
|
+
}
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (blockComment) {
|
|
874
|
+
if (char === "*" && next === "/") {
|
|
875
|
+
blockComment = false;
|
|
876
|
+
index += 1;
|
|
877
|
+
}
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (quote) {
|
|
881
|
+
current += char;
|
|
882
|
+
if (escaped) {
|
|
883
|
+
escaped = false;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (char === "\\") {
|
|
887
|
+
escaped = true;
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
if (char === quote) {
|
|
891
|
+
quote = undefined;
|
|
892
|
+
}
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (char === "/" && next === "/") {
|
|
896
|
+
lineComment = true;
|
|
897
|
+
index += 1;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
if (char === "/" && next === "*") {
|
|
901
|
+
blockComment = true;
|
|
902
|
+
index += 1;
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
906
|
+
quote = char;
|
|
907
|
+
current += char;
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
if (char === "(" || char === "[" || char === "{") {
|
|
911
|
+
depth += 1;
|
|
912
|
+
}
|
|
913
|
+
else if (char === ")" || char === "]" || char === "}") {
|
|
914
|
+
depth = Math.max(0, depth - 1);
|
|
915
|
+
}
|
|
916
|
+
if (char === "," && depth === 0) {
|
|
917
|
+
const entry = current.trim();
|
|
918
|
+
if (entry)
|
|
919
|
+
entries.push(entry);
|
|
920
|
+
current = "";
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
current += char;
|
|
924
|
+
}
|
|
925
|
+
const entry = current.trim();
|
|
926
|
+
if (entry)
|
|
927
|
+
entries.push(entry);
|
|
928
|
+
return entries;
|
|
929
|
+
}
|
|
930
|
+
function metadataSourcePath(targetDir, file) {
|
|
931
|
+
const targetRelative = normalizePath(path.relative(targetDir, file));
|
|
932
|
+
if (!targetRelative.startsWith("../"))
|
|
933
|
+
return targetRelative;
|
|
934
|
+
const cwdRelative = normalizePath(path.relative(process.cwd(), file));
|
|
935
|
+
return cwdRelative.startsWith("../") ? file : cwdRelative;
|
|
936
|
+
}
|
|
937
|
+
function isInfraOrServerFile(file, config) {
|
|
938
|
+
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
939
|
+
const infrastructureDir = directoryPath(path.dirname(config.paths.infrastructurePorts));
|
|
940
|
+
return (file === config.paths.server ||
|
|
941
|
+
file.startsWith(`${serverDir}/`) ||
|
|
942
|
+
file === config.paths.infrastructurePorts ||
|
|
943
|
+
file.startsWith(`${infrastructureDir}/`));
|
|
944
|
+
}
|
|
945
|
+
function isTestSourceFile(file) {
|
|
946
|
+
return (file.endsWith(".test.ts") ||
|
|
947
|
+
file.endsWith(".spec.ts") ||
|
|
948
|
+
file.includes("/tests/"));
|
|
949
|
+
}
|
|
950
|
+
function sourceCreatesTable(source, tableName) {
|
|
951
|
+
return new RegExp(`\\bCREATE\\s+TABLE\\b[\\s\\S]{0,200}${escapeRegExp(tableName)}\\b`, "i").test(source);
|
|
952
|
+
}
|
|
953
|
+
function sourceDeclaresSchemaTable(source, tableName) {
|
|
954
|
+
return (sourceDeclaresDrizzleTable(source, tableName) ||
|
|
955
|
+
sourceReexportsBeignetProviderSchemaTable(source, tableName));
|
|
956
|
+
}
|
|
957
|
+
function sourceDeclaresDrizzleTable(source, tableName) {
|
|
958
|
+
const quotedTableName = `["'\`]${escapeRegExp(tableName)}["'\`]`;
|
|
959
|
+
const declarationPatterns = [
|
|
960
|
+
`\\b(?:sqliteTable|pgTable|mysqlTable)\\s*\\(\\s*${quotedTableName}`,
|
|
961
|
+
`\\b[A-Za-z_$][\\w$]*Table\\s*\\(\\s*${quotedTableName}`,
|
|
962
|
+
`\\.table\\s*\\(\\s*${quotedTableName}`,
|
|
963
|
+
];
|
|
964
|
+
return declarationPatterns.some((pattern) => new RegExp(pattern).test(source));
|
|
965
|
+
}
|
|
966
|
+
function sourceReexportsBeignetProviderSchemaTable(source, tableName) {
|
|
967
|
+
const exportName = beignetProviderSchemaExportName(tableName);
|
|
968
|
+
if (!exportName)
|
|
969
|
+
return false;
|
|
970
|
+
return new RegExp(`\\bexport\\s*\\{[\\s\\S]{0,400}\\b${escapeRegExp(exportName)}\\b[\\s\\S]{0,400}\\}\\s*from\\s*["']@beignet/provider-db-drizzle/(?:sqlite|postgres|mysql)/schema["']`).test(source);
|
|
971
|
+
}
|
|
972
|
+
function beignetProviderSchemaExportName(tableName) {
|
|
973
|
+
if (tableName === "audit_log")
|
|
974
|
+
return "auditLog";
|
|
975
|
+
if (tableName === "idempotency_records")
|
|
976
|
+
return "idempotencyRecords";
|
|
977
|
+
if (tableName === "outbox_messages")
|
|
978
|
+
return "outboxMessages";
|
|
979
|
+
return undefined;
|
|
980
|
+
}
|
|
981
|
+
function escapeRegExp(value) {
|
|
982
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
983
|
+
}
|
|
984
|
+
function requirementSummary(requirement) {
|
|
985
|
+
if (requirement.status === "none")
|
|
986
|
+
return "none";
|
|
987
|
+
if (requirement.status === "inactive")
|
|
988
|
+
return "inactive";
|
|
989
|
+
if (requirement.status === "present") {
|
|
990
|
+
return `${requirement.required.length} ok`;
|
|
991
|
+
}
|
|
992
|
+
return `${requirement.missing.length}/${requirement.required.length} missing`;
|
|
993
|
+
}
|
|
994
|
+
function appPortsSummary(appPorts) {
|
|
995
|
+
if (appPorts.status === "none")
|
|
996
|
+
return "none";
|
|
997
|
+
if (appPorts.status === "present")
|
|
998
|
+
return `${appPorts.required.length} ok`;
|
|
999
|
+
return `${appPorts.missing.length}/${appPorts.required.length} missing`;
|
|
1000
|
+
}
|
|
1001
|
+
function unique(values) {
|
|
1002
|
+
return [...new Set(values)];
|
|
1003
|
+
}
|
|
1004
|
+
function table(rows) {
|
|
1005
|
+
const widths = rows[0].map((_, columnIndex) => Math.max(...rows.map((row) => row[columnIndex].length)));
|
|
1006
|
+
return rows
|
|
1007
|
+
.map((row) => row
|
|
1008
|
+
.map((cell, columnIndex) => cell.padEnd(widths[columnIndex]))
|
|
1009
|
+
.join(" ")
|
|
1010
|
+
.trimEnd())
|
|
1011
|
+
.join("\n");
|
|
1012
|
+
}
|
|
1013
|
+
//# sourceMappingURL=provider-audit.js.map
|