@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
package/dist/inspect.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import { parseProviderPackageMetadata, } from "@beignet/core/providers";
|
|
3
|
+
import { parseProviderPackageMetadata } from "@beignet/core/providers";
|
|
5
4
|
import { createPainter } from "./ansi.js";
|
|
6
|
-
import { clientDirPath, clientFormsPath, clientIndexPath, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
|
|
5
|
+
import { clientDirPath, clientFormsPath, clientIndexPath, defaultBeignetConfig, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
|
|
7
6
|
import { formatGithubAnnotation, } from "./github-annotations.js";
|
|
7
|
+
import { detectedProviderVariants, diagnosticPackageJsonFile, formatProviderMetadataIssues, installedPackageNames, providerDoctorRulesForInstalledPackages, providerOperationalConfigFiles, providerRequiredEnvExists, providerRequiredTableExists, providerVariantRegistrationSeverity, readProviderListEntries, readProviderPackageMetadataSource, registrationTokenHint, serverProviderFiles, } from "./provider-audit.js";
|
|
8
8
|
import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, outboxRegistryValueInfo, } from "./registry-edits.js";
|
|
9
9
|
import { getCliVersion } from "./version.js";
|
|
10
10
|
export async function inspectApp(options = {}) {
|
|
@@ -157,6 +157,7 @@ const productionHardeningDiagnosticCodes = new Set([
|
|
|
157
157
|
"BEIGNET_PAYMENTS_STRIPE_MEMORY_PROVIDER",
|
|
158
158
|
"BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
|
|
159
159
|
"BEIGNET_PROVIDER_ENV_MISSING",
|
|
160
|
+
"BEIGNET_PROVIDER_TABLE_MISSING",
|
|
160
161
|
"BEIGNET_READINESS_ROUTE_MISSING",
|
|
161
162
|
"BEIGNET_TENANT_HEADER_AUTHORITY",
|
|
162
163
|
"BEIGNET_UPLOAD_AUTHORIZATION_MISSING",
|
|
@@ -1224,6 +1225,8 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
|
|
|
1224
1225
|
const installedPackages = installedPackageNames(packageJson);
|
|
1225
1226
|
const providerDoctorRules = await providerDoctorRulesForInstalledPackages(targetDir, packageJson);
|
|
1226
1227
|
const configFiles = operationalConfigFiles(files, config);
|
|
1228
|
+
const infrastructurePath = directoryPath(path.dirname(config.paths.infrastructurePorts));
|
|
1229
|
+
const schemaDir = `${infrastructurePath}/db/schema`;
|
|
1227
1230
|
for (const file of files) {
|
|
1228
1231
|
if (!file.endsWith(".ts") || file.endsWith(".test.ts"))
|
|
1229
1232
|
continue;
|
|
@@ -1313,7 +1316,7 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
|
|
|
1313
1316
|
// app actually registers; an unused variant must not warn.
|
|
1314
1317
|
for (const variant of detectedProviderVariants(provider.variants, providerEntries)) {
|
|
1315
1318
|
for (const envVar of variant.requiredEnv) {
|
|
1316
|
-
if (await
|
|
1319
|
+
if (await providerRequiredEnvExists(targetDir, configFiles, envVar, sourceCache)) {
|
|
1317
1320
|
continue;
|
|
1318
1321
|
}
|
|
1319
1322
|
diagnostics.push({
|
|
@@ -1323,11 +1326,22 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
|
|
|
1323
1326
|
message: `${provider.packageName} is installed and ${variant.displayName} is registered, but ${envVar} is not mentioned in app env/config files. ${variant.displayName} requires ${envVar}; add the environment variable or remove the provider registration.`,
|
|
1324
1327
|
});
|
|
1325
1328
|
}
|
|
1329
|
+
for (const tableName of configuredProviderRequiredTables(variant.requiredTables, config)) {
|
|
1330
|
+
if (await providerRequiredTableExists(targetDir, files, config, infrastructurePath, schemaDir, tableName, sourceCache)) {
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
diagnostics.push({
|
|
1334
|
+
severity: "warning",
|
|
1335
|
+
code: "BEIGNET_PROVIDER_TABLE_MISSING",
|
|
1336
|
+
file: schemaDir,
|
|
1337
|
+
message: `${provider.packageName} is installed and ${variant.displayName} is registered, but required table ${tableName} was not found in ${schemaDir}/, drizzle/, or app database setup files. Add it with beignet db schema generate, include it in your Drizzle schema/migrations, or remove the provider registration.`,
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1326
1340
|
}
|
|
1327
1341
|
continue;
|
|
1328
1342
|
}
|
|
1329
1343
|
for (const envVar of provider.requiredEnv ?? []) {
|
|
1330
|
-
if (await
|
|
1344
|
+
if (await providerRequiredEnvExists(targetDir, configFiles, envVar, sourceCache)) {
|
|
1331
1345
|
continue;
|
|
1332
1346
|
}
|
|
1333
1347
|
diagnostics.push({
|
|
@@ -1337,6 +1351,17 @@ async function inspectProductionReadiness(targetDir, files, config, convention)
|
|
|
1337
1351
|
message: `${provider.packageName} is installed, but ${envVar} is not mentioned in app env/config files. Add the expected environment variable or remove the unused provider package.`,
|
|
1338
1352
|
});
|
|
1339
1353
|
}
|
|
1354
|
+
for (const tableName of configuredProviderRequiredTables(provider.requiredTables ?? [], config)) {
|
|
1355
|
+
if (await providerRequiredTableExists(targetDir, files, config, infrastructurePath, schemaDir, tableName, sourceCache)) {
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
diagnostics.push({
|
|
1359
|
+
severity: "warning",
|
|
1360
|
+
code: "BEIGNET_PROVIDER_TABLE_MISSING",
|
|
1361
|
+
file: schemaDir,
|
|
1362
|
+
message: `${provider.packageName} is installed, but required table ${tableName} was not found in ${schemaDir}/, drizzle/, or app database setup files. Add it with beignet db schema generate, include it in your Drizzle schema/migrations, or remove the unused provider package.`,
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1340
1365
|
}
|
|
1341
1366
|
if (installedPackages.has("@beignet/provider-auth-better-auth")) {
|
|
1342
1367
|
if (!hasBetterAuthRoute(files, config)) {
|
|
@@ -1680,58 +1705,13 @@ function isFeatureUploadDefinition(file, config) {
|
|
|
1680
1705
|
return new RegExp(`^${escapeRegExp(featuresPath)}/[^/]+/uploads/[^/]+\\.ts$`).test(file);
|
|
1681
1706
|
}
|
|
1682
1707
|
function operationalConfigFiles(files, config) {
|
|
1683
|
-
return files
|
|
1684
|
-
if (file === ".env.example")
|
|
1685
|
-
return true;
|
|
1686
|
-
if (file === "drizzle.config.ts")
|
|
1687
|
-
return true;
|
|
1688
|
-
if (file === "lib/env.ts" || file === "lib/better-auth.ts")
|
|
1689
|
-
return true;
|
|
1690
|
-
if (isInfraOrServerFile(file, config) && file.endsWith(".ts"))
|
|
1691
|
-
return true;
|
|
1692
|
-
return false;
|
|
1693
|
-
});
|
|
1708
|
+
return providerOperationalConfigFiles(files, config);
|
|
1694
1709
|
}
|
|
1695
1710
|
async function readPackageJson(targetDir, files) {
|
|
1696
1711
|
if (!files.includes("package.json"))
|
|
1697
1712
|
return undefined;
|
|
1698
1713
|
return JSON.parse(await readFile(path.join(targetDir, "package.json"), "utf8"));
|
|
1699
1714
|
}
|
|
1700
|
-
async function providerDoctorRulesForInstalledPackages(targetDir, packageJson) {
|
|
1701
|
-
const packageNames = [...installedPackageNames(packageJson)];
|
|
1702
|
-
const rules = await Promise.all(packageNames.map(async (packageName) => {
|
|
1703
|
-
const source = await readProviderPackageMetadataSource(targetDir, packageName);
|
|
1704
|
-
if (!source)
|
|
1705
|
-
return undefined;
|
|
1706
|
-
const result = parseProviderPackageMetadata(source.metadata);
|
|
1707
|
-
if (!result.success)
|
|
1708
|
-
return undefined;
|
|
1709
|
-
return providerDoctorRuleFromMetadata(packageName, result.metadata);
|
|
1710
|
-
}));
|
|
1711
|
-
return rules.filter((rule) => Boolean(rule));
|
|
1712
|
-
}
|
|
1713
|
-
async function readProviderPackageMetadataSource(targetDir, packageName) {
|
|
1714
|
-
for (const candidate of providerPackageJsonCandidates(targetDir, packageName)) {
|
|
1715
|
-
const packageJson = await readJsonIfExists(candidate);
|
|
1716
|
-
const metadata = packageJson?.beignet?.provider;
|
|
1717
|
-
if (metadata !== undefined) {
|
|
1718
|
-
return { packageName, file: candidate, metadata };
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
return undefined;
|
|
1722
|
-
}
|
|
1723
|
-
function providerPackageJsonCandidates(targetDir, packageName) {
|
|
1724
|
-
const candidates = [
|
|
1725
|
-
path.join(targetDir, "node_modules", ...packageName.split("/"), "package.json"),
|
|
1726
|
-
];
|
|
1727
|
-
if (packageName.startsWith("@beignet/")) {
|
|
1728
|
-
candidates.push(path.resolve(
|
|
1729
|
-
// import.meta.dir is Bun-only; the published bin also runs under
|
|
1730
|
-
// plain Node, where only import.meta.url is available.
|
|
1731
|
-
path.dirname(fileURLToPath(import.meta.url)), "../../..", "packages", packageName.slice("@beignet/".length), "package.json"));
|
|
1732
|
-
}
|
|
1733
|
-
return candidates;
|
|
1734
|
-
}
|
|
1735
1715
|
async function readJsonIfExists(file) {
|
|
1736
1716
|
try {
|
|
1737
1717
|
return JSON.parse(await readFile(file, "utf8"));
|
|
@@ -1745,158 +1725,6 @@ async function readJsonIfExists(file) {
|
|
|
1745
1725
|
throw error;
|
|
1746
1726
|
}
|
|
1747
1727
|
}
|
|
1748
|
-
function providerDoctorRuleFromMetadata(packageName, metadata) {
|
|
1749
|
-
const registration = metadata.registration;
|
|
1750
|
-
return {
|
|
1751
|
-
packageName,
|
|
1752
|
-
displayName: metadata.displayName ?? packageName,
|
|
1753
|
-
tokens: registration?.tokens ?? [],
|
|
1754
|
-
appPorts: metadata.appPorts ?? [],
|
|
1755
|
-
requiredEnv: metadata.requiredEnv ?? [],
|
|
1756
|
-
registrationSeverity: registrationSeverityFromMetadata(registration),
|
|
1757
|
-
variants: metadata.variants?.map((variant) => ({
|
|
1758
|
-
name: variant.name,
|
|
1759
|
-
displayName: variant.displayName ?? variant.name,
|
|
1760
|
-
tokens: variant.registration?.tokens ?? [],
|
|
1761
|
-
requiredEnv: variant.requiredEnv ?? [],
|
|
1762
|
-
registrationSeverity: registrationSeverityFromMetadata(variant.registration),
|
|
1763
|
-
})),
|
|
1764
|
-
};
|
|
1765
|
-
}
|
|
1766
|
-
function registrationSeverityFromMetadata(registration) {
|
|
1767
|
-
return (registration?.severity ??
|
|
1768
|
-
(registration?.required === true ? "warning" : undefined));
|
|
1769
|
-
}
|
|
1770
|
-
function detectedProviderVariants(variants, providerEntries) {
|
|
1771
|
-
return variants.filter((variant) => variant.tokens.some((token) => providerEntries.some((entry) => entry.includes(token))));
|
|
1772
|
-
}
|
|
1773
|
-
function providerVariantRegistrationSeverity(variants) {
|
|
1774
|
-
const severities = variants.map((variant) => variant.registrationSeverity);
|
|
1775
|
-
if (severities.includes("warning"))
|
|
1776
|
-
return "warning";
|
|
1777
|
-
if (severities.includes("hint"))
|
|
1778
|
-
return "hint";
|
|
1779
|
-
return undefined;
|
|
1780
|
-
}
|
|
1781
|
-
async function readProviderListEntries(targetDir, files, config, sourceCache) {
|
|
1782
|
-
const providerFiles = serverProviderFiles(files, config);
|
|
1783
|
-
const providerSource = (await Promise.all(providerFiles.map((file) => readCachedSource(targetDir, file, sourceCache)))).join("\n");
|
|
1784
|
-
const providerListSource = extractProviderListSource(providerSource);
|
|
1785
|
-
return providerListSource === undefined
|
|
1786
|
-
? []
|
|
1787
|
-
: providerListEntries(providerListSource);
|
|
1788
|
-
}
|
|
1789
|
-
function registrationTokenHint(tokens) {
|
|
1790
|
-
const token = tokens[0];
|
|
1791
|
-
if (!token)
|
|
1792
|
-
return "the provider";
|
|
1793
|
-
return token.startsWith("create") ? `${token}()` : token;
|
|
1794
|
-
}
|
|
1795
|
-
function diagnosticPackageJsonFile(targetDir, file) {
|
|
1796
|
-
const relative = normalizePath(path.relative(targetDir, file));
|
|
1797
|
-
if (relative.startsWith("../"))
|
|
1798
|
-
return "package.json";
|
|
1799
|
-
return relative;
|
|
1800
|
-
}
|
|
1801
|
-
function formatProviderMetadataIssues(issues) {
|
|
1802
|
-
return issues.map((issue) => `${issue.path} ${issue.message}`).join("; ");
|
|
1803
|
-
}
|
|
1804
|
-
function installedPackageNames(packageJson) {
|
|
1805
|
-
return new Set([
|
|
1806
|
-
...Object.keys(packageJson?.dependencies ?? {}),
|
|
1807
|
-
...Object.keys(packageJson?.devDependencies ?? {}),
|
|
1808
|
-
...Object.keys(packageJson?.peerDependencies ?? {}),
|
|
1809
|
-
]);
|
|
1810
|
-
}
|
|
1811
|
-
function serverProviderFiles(files, config) {
|
|
1812
|
-
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
1813
|
-
const candidates = [`${serverDir}/providers.ts`, config.paths.server];
|
|
1814
|
-
return candidates.filter((file) => files.includes(file));
|
|
1815
|
-
}
|
|
1816
|
-
function extractProviderListSource(source) {
|
|
1817
|
-
const listMatches = [
|
|
1818
|
-
...source.matchAll(/\bproviders\s*=\s*\[([\s\S]*?)\]\s*(?:;|as const)/g),
|
|
1819
|
-
...source.matchAll(/\bproviders\s*:\s*\[([\s\S]*?)\]/g),
|
|
1820
|
-
];
|
|
1821
|
-
if (listMatches.length === 0)
|
|
1822
|
-
return undefined;
|
|
1823
|
-
return listMatches.map((match) => match[1]).join("\n");
|
|
1824
|
-
}
|
|
1825
|
-
function providerListEntries(source) {
|
|
1826
|
-
const entries = [];
|
|
1827
|
-
let current = "";
|
|
1828
|
-
let depth = 0;
|
|
1829
|
-
let quote;
|
|
1830
|
-
let escaped = false;
|
|
1831
|
-
let lineComment = false;
|
|
1832
|
-
let blockComment = false;
|
|
1833
|
-
for (let index = 0; index < source.length; index += 1) {
|
|
1834
|
-
const char = source[index];
|
|
1835
|
-
const next = source[index + 1];
|
|
1836
|
-
if (lineComment) {
|
|
1837
|
-
if (char === "\n") {
|
|
1838
|
-
lineComment = false;
|
|
1839
|
-
current += char;
|
|
1840
|
-
}
|
|
1841
|
-
continue;
|
|
1842
|
-
}
|
|
1843
|
-
if (blockComment) {
|
|
1844
|
-
if (char === "*" && next === "/") {
|
|
1845
|
-
blockComment = false;
|
|
1846
|
-
index += 1;
|
|
1847
|
-
}
|
|
1848
|
-
continue;
|
|
1849
|
-
}
|
|
1850
|
-
if (quote) {
|
|
1851
|
-
current += char;
|
|
1852
|
-
if (escaped) {
|
|
1853
|
-
escaped = false;
|
|
1854
|
-
continue;
|
|
1855
|
-
}
|
|
1856
|
-
if (char === "\\") {
|
|
1857
|
-
escaped = true;
|
|
1858
|
-
continue;
|
|
1859
|
-
}
|
|
1860
|
-
if (char === quote) {
|
|
1861
|
-
quote = undefined;
|
|
1862
|
-
}
|
|
1863
|
-
continue;
|
|
1864
|
-
}
|
|
1865
|
-
if (char === "/" && next === "/") {
|
|
1866
|
-
lineComment = true;
|
|
1867
|
-
index += 1;
|
|
1868
|
-
continue;
|
|
1869
|
-
}
|
|
1870
|
-
if (char === "/" && next === "*") {
|
|
1871
|
-
blockComment = true;
|
|
1872
|
-
index += 1;
|
|
1873
|
-
continue;
|
|
1874
|
-
}
|
|
1875
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
1876
|
-
quote = char;
|
|
1877
|
-
current += char;
|
|
1878
|
-
continue;
|
|
1879
|
-
}
|
|
1880
|
-
if (char === "(" || char === "[" || char === "{") {
|
|
1881
|
-
depth += 1;
|
|
1882
|
-
}
|
|
1883
|
-
else if (char === ")" || char === "]" || char === "}") {
|
|
1884
|
-
depth = Math.max(0, depth - 1);
|
|
1885
|
-
}
|
|
1886
|
-
if (char === "," && depth === 0) {
|
|
1887
|
-
const entry = current.trim();
|
|
1888
|
-
if (entry)
|
|
1889
|
-
entries.push(entry);
|
|
1890
|
-
current = "";
|
|
1891
|
-
continue;
|
|
1892
|
-
}
|
|
1893
|
-
current += char;
|
|
1894
|
-
}
|
|
1895
|
-
const entry = current.trim();
|
|
1896
|
-
if (entry)
|
|
1897
|
-
entries.push(entry);
|
|
1898
|
-
return entries;
|
|
1899
|
-
}
|
|
1900
1728
|
function findProviderEntryIndex(entries, tokens) {
|
|
1901
1729
|
return entries.findIndex((entry) => tokens.some((token) => entry.includes(token)));
|
|
1902
1730
|
}
|
|
@@ -2794,35 +2622,65 @@ async function inspectDatabaseLifecycleDrift(targetDir, files, config, conventio
|
|
|
2794
2622
|
const durableDrizzleTableRequirements = [
|
|
2795
2623
|
{
|
|
2796
2624
|
capability: "idempotency",
|
|
2797
|
-
|
|
2625
|
+
table: "idempotency",
|
|
2798
2626
|
code: "BEIGNET_IDEMPOTENCY_TABLE_MISSING",
|
|
2799
|
-
|
|
2800
|
-
|
|
2627
|
+
factoryNames: [
|
|
2628
|
+
"createDrizzleSqliteIdempotencyPort",
|
|
2629
|
+
"createDrizzlePostgresIdempotencyPort",
|
|
2630
|
+
"createDrizzleMysqlIdempotencyPort",
|
|
2631
|
+
],
|
|
2632
|
+
setupNames: [
|
|
2633
|
+
"createDrizzleSqliteIdempotencySetupStatements",
|
|
2634
|
+
"createDrizzlePostgresIdempotencySetupStatements",
|
|
2635
|
+
"createDrizzleMysqlIdempotencySetupStatements",
|
|
2636
|
+
],
|
|
2637
|
+
explicitFactoryPattern: "createDrizzle[A-Za-z]*Idempotency(?:Port)?",
|
|
2638
|
+
explicitSetupPattern: "createDrizzle[A-Za-z]*IdempotencySetupStatements",
|
|
2801
2639
|
},
|
|
2802
2640
|
{
|
|
2803
2641
|
capability: "outbox",
|
|
2804
|
-
|
|
2642
|
+
table: "outbox",
|
|
2805
2643
|
code: "BEIGNET_OUTBOX_TABLE_MISSING",
|
|
2806
|
-
|
|
2807
|
-
|
|
2644
|
+
factoryNames: [
|
|
2645
|
+
"createDrizzleSqliteOutboxPort",
|
|
2646
|
+
"createDrizzlePostgresOutboxPort",
|
|
2647
|
+
"createDrizzleMysqlOutboxPort",
|
|
2648
|
+
],
|
|
2649
|
+
setupNames: [
|
|
2650
|
+
"createDrizzleSqliteOutboxSetupStatements",
|
|
2651
|
+
"createDrizzlePostgresOutboxSetupStatements",
|
|
2652
|
+
"createDrizzleMysqlOutboxSetupStatements",
|
|
2653
|
+
],
|
|
2654
|
+
explicitFactoryPattern: "createDrizzle[A-Za-z]*Outbox(?:Port)?",
|
|
2655
|
+
explicitSetupPattern: "createDrizzle[A-Za-z]*OutboxSetupStatements",
|
|
2808
2656
|
},
|
|
2809
2657
|
{
|
|
2810
2658
|
capability: "audit",
|
|
2811
|
-
|
|
2659
|
+
table: "audit",
|
|
2812
2660
|
code: "BEIGNET_AUDIT_TABLE_MISSING",
|
|
2813
|
-
|
|
2814
|
-
|
|
2661
|
+
factoryNames: [
|
|
2662
|
+
"createDrizzleSqliteAuditLogPort",
|
|
2663
|
+
"createDrizzlePostgresAuditLogPort",
|
|
2664
|
+
"createDrizzleMysqlAuditLogPort",
|
|
2665
|
+
],
|
|
2666
|
+
setupNames: [
|
|
2667
|
+
"createDrizzleSqliteAuditLogSetupStatements",
|
|
2668
|
+
"createDrizzlePostgresAuditLogSetupStatements",
|
|
2669
|
+
"createDrizzleMysqlAuditLogSetupStatements",
|
|
2670
|
+
],
|
|
2671
|
+
explicitFactoryPattern: "createDrizzle[A-Za-z]*AuditLog(?:Port)?",
|
|
2672
|
+
explicitSetupPattern: "createDrizzle[A-Za-z]*AuditLogSetupStatements",
|
|
2815
2673
|
},
|
|
2816
2674
|
];
|
|
2817
2675
|
async function inspectDurableDrizzleTableDrift(targetDir, files, config, infrastructurePath, schemaDir, sourceCache) {
|
|
2818
2676
|
const diagnostics = [];
|
|
2819
2677
|
const usageFiles = durableDrizzleUsageFiles(files, config);
|
|
2820
2678
|
for (const requirement of durableDrizzleTableRequirements) {
|
|
2821
|
-
const tableNames = await configuredDurableDrizzleTableNames(targetDir, usageFiles, requirement, sourceCache);
|
|
2679
|
+
const tableNames = await configuredDurableDrizzleTableNames(targetDir, usageFiles, config, requirement, sourceCache);
|
|
2822
2680
|
if (tableNames.size === 0)
|
|
2823
2681
|
continue;
|
|
2824
2682
|
for (const tableName of tableNames) {
|
|
2825
|
-
if (await durableDrizzleTableExists(targetDir, files, infrastructurePath, schemaDir, requirement, tableName, sourceCache)) {
|
|
2683
|
+
if (await durableDrizzleTableExists(targetDir, files, config, infrastructurePath, schemaDir, requirement, tableName, sourceCache)) {
|
|
2826
2684
|
continue;
|
|
2827
2685
|
}
|
|
2828
2686
|
diagnostics.push({
|
|
@@ -2840,45 +2698,50 @@ function durableDrizzleUsageFiles(files, config) {
|
|
|
2840
2698
|
!isTestSourceFile(file) &&
|
|
2841
2699
|
isInfraOrServerFile(file, config));
|
|
2842
2700
|
}
|
|
2843
|
-
async function configuredDurableDrizzleTableNames(targetDir, files, requirement, sourceCache) {
|
|
2701
|
+
async function configuredDurableDrizzleTableNames(targetDir, files, config, requirement, sourceCache) {
|
|
2844
2702
|
const tableNames = new Set();
|
|
2703
|
+
const providerDefaultTableName = defaultBeignetConfig.database.tables[requirement.table];
|
|
2704
|
+
const configuredTableName = config.database.tables[requirement.table];
|
|
2845
2705
|
for (const file of files) {
|
|
2846
2706
|
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
2847
|
-
for (const tableName of
|
|
2707
|
+
for (const tableName of tableNamesFromBeignetProviderCalls(source, requirement.factoryNames, providerDefaultTableName, configuredTableName)) {
|
|
2708
|
+
tableNames.add(tableName);
|
|
2709
|
+
}
|
|
2710
|
+
for (const tableName of tableNamesFromExplicitCalls(source, requirement.explicitFactoryPattern, configuredTableName)) {
|
|
2848
2711
|
tableNames.add(tableName);
|
|
2849
2712
|
}
|
|
2850
2713
|
}
|
|
2851
2714
|
return tableNames;
|
|
2852
2715
|
}
|
|
2853
|
-
async function durableDrizzleTableExists(targetDir, files, infrastructurePath, schemaDir, requirement, tableName, sourceCache) {
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
!isTestSourceFile(file));
|
|
2857
|
-
for (const file of schemaFiles) {
|
|
2858
|
-
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
2859
|
-
if (source.includes(tableName))
|
|
2860
|
-
return true;
|
|
2716
|
+
async function durableDrizzleTableExists(targetDir, files, config, infrastructurePath, schemaDir, requirement, tableName, sourceCache) {
|
|
2717
|
+
if (await providerRequiredTableExists(targetDir, files, config, infrastructurePath, schemaDir, tableName, sourceCache)) {
|
|
2718
|
+
return true;
|
|
2861
2719
|
}
|
|
2862
2720
|
const setupFiles = files.filter((file) => !isTestSourceFile(file) &&
|
|
2863
2721
|
(file.startsWith("drizzle/") ||
|
|
2864
2722
|
file.startsWith(`${infrastructurePath}/db/`)) &&
|
|
2865
2723
|
/\.(?:ts|js|mts|mjs|cts|cjs|sql)$/.test(file));
|
|
2724
|
+
const providerDefaultTableName = defaultBeignetConfig.database.tables[requirement.table];
|
|
2725
|
+
const configuredTableName = config.database.tables[requirement.table];
|
|
2866
2726
|
for (const file of setupFiles) {
|
|
2867
2727
|
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
2868
|
-
if (
|
|
2869
|
-
|
|
2870
|
-
if (!requirement.setupPattern)
|
|
2871
|
-
continue;
|
|
2872
|
-
if (tableNamesFromCalls(source, requirement.setupPattern, requirement.defaultTableName).has(tableName)) {
|
|
2728
|
+
if (tableNamesFromBeignetProviderCalls(source, requirement.setupNames, providerDefaultTableName, configuredTableName).has(tableName) ||
|
|
2729
|
+
tableNamesFromExplicitCalls(source, requirement.explicitSetupPattern, configuredTableName).has(tableName)) {
|
|
2873
2730
|
return true;
|
|
2874
2731
|
}
|
|
2875
2732
|
}
|
|
2876
2733
|
return false;
|
|
2877
2734
|
}
|
|
2878
|
-
function
|
|
2879
|
-
|
|
2735
|
+
function tableNamesFromBeignetProviderCalls(source, exportNames, defaultTableName, configuredTableName) {
|
|
2736
|
+
const tableNames = new Set();
|
|
2737
|
+
for (const callee of beignetProviderCallees(source, exportNames)) {
|
|
2738
|
+
for (const tableName of tableNamesFromCallee(source, callee, defaultTableName, configuredTableName)) {
|
|
2739
|
+
tableNames.add(tableName);
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
return tableNames;
|
|
2880
2743
|
}
|
|
2881
|
-
function
|
|
2744
|
+
function tableNamesFromExplicitCalls(source, calleePattern, configuredTableName) {
|
|
2882
2745
|
const tableNames = new Set();
|
|
2883
2746
|
const callPattern = new RegExp(`\\b${calleePattern}\\s*\\(([\\s\\S]{0,800}?)\\)`, "g");
|
|
2884
2747
|
for (const match of source.matchAll(callPattern)) {
|
|
@@ -2886,12 +2749,71 @@ function tableNamesFromCalls(source, calleePattern, defaultTableName) {
|
|
|
2886
2749
|
const prefix = source.slice(Math.max(0, matchIndex - 32), matchIndex);
|
|
2887
2750
|
if (/\bfunction\s+$/.test(prefix))
|
|
2888
2751
|
continue;
|
|
2889
|
-
const
|
|
2890
|
-
|
|
2891
|
-
|
|
2752
|
+
const tableName = tableNameFromArgs(match[1] ?? "", configuredTableName);
|
|
2753
|
+
if (tableName)
|
|
2754
|
+
tableNames.add(tableName);
|
|
2755
|
+
}
|
|
2756
|
+
return tableNames;
|
|
2757
|
+
}
|
|
2758
|
+
function beignetProviderCallees(source, exportNames) {
|
|
2759
|
+
const exportNameSet = new Set(exportNames);
|
|
2760
|
+
const callees = [];
|
|
2761
|
+
for (const match of source.matchAll(/\bimport\s+(?:type\s+)?\{([^}]*)\}\s+from\s+["']@beignet\/provider-db-drizzle\/(?:sqlite|postgres|mysql)["']/g)) {
|
|
2762
|
+
for (const specifier of match[1].split(",")) {
|
|
2763
|
+
const cleaned = specifier.trim().replace(/^type\s+/, "");
|
|
2764
|
+
if (!cleaned)
|
|
2765
|
+
continue;
|
|
2766
|
+
const [imported, local] = cleaned.split(/\s+as\s+/);
|
|
2767
|
+
if (!exportNameSet.has(imported))
|
|
2768
|
+
continue;
|
|
2769
|
+
callees.push({ kind: "identifier", name: local?.trim() || imported });
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
for (const match of source.matchAll(/\bimport\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+["']@beignet\/provider-db-drizzle\/(?:sqlite|postgres|mysql)["']/g)) {
|
|
2773
|
+
for (const exportName of exportNames) {
|
|
2774
|
+
callees.push({
|
|
2775
|
+
kind: "namespace",
|
|
2776
|
+
namespace: match[1],
|
|
2777
|
+
name: exportName,
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
return callees;
|
|
2782
|
+
}
|
|
2783
|
+
function tableNamesFromCallee(source, callee, defaultTableName, configuredTableName) {
|
|
2784
|
+
const tableNames = new Set();
|
|
2785
|
+
const calleePattern = callee.kind === "identifier"
|
|
2786
|
+
? `\\b${escapeRegExp(callee.name)}`
|
|
2787
|
+
: `\\b${escapeRegExp(callee.namespace)}\\s*\\.\\s*${escapeRegExp(callee.name)}`;
|
|
2788
|
+
const callPattern = new RegExp(`${calleePattern}\\s*\\(([\\s\\S]{0,800}?)\\)`, "g");
|
|
2789
|
+
for (const match of source.matchAll(callPattern)) {
|
|
2790
|
+
const matchIndex = match.index ?? 0;
|
|
2791
|
+
const prefix = source.slice(Math.max(0, matchIndex - 32), matchIndex);
|
|
2792
|
+
if (/\bfunction\s+$/.test(prefix))
|
|
2793
|
+
continue;
|
|
2794
|
+
tableNames.add(tableNameFromArgs(match[1] ?? "", configuredTableName) ??
|
|
2795
|
+
defaultTableName);
|
|
2892
2796
|
}
|
|
2893
2797
|
return tableNames;
|
|
2894
2798
|
}
|
|
2799
|
+
function tableNameFromArgs(argsSource, configuredTableName) {
|
|
2800
|
+
const literal = /\btableName\s*:\s*["'`]([^"'`]+)["'`]/.exec(argsSource)?.[1];
|
|
2801
|
+
if (literal)
|
|
2802
|
+
return literal;
|
|
2803
|
+
if (/\btableName\s*:/.test(argsSource))
|
|
2804
|
+
return configuredTableName;
|
|
2805
|
+
return undefined;
|
|
2806
|
+
}
|
|
2807
|
+
function configuredProviderRequiredTables(tableNames, config) {
|
|
2808
|
+
return tableNames.map((tableName) => configuredProviderRequiredTable(tableName, config));
|
|
2809
|
+
}
|
|
2810
|
+
function configuredProviderRequiredTable(tableName, config) {
|
|
2811
|
+
for (const [key, defaultTableName] of Object.entries(defaultBeignetConfig.database.tables)) {
|
|
2812
|
+
if (tableName === defaultTableName)
|
|
2813
|
+
return config.database.tables[key];
|
|
2814
|
+
}
|
|
2815
|
+
return tableName;
|
|
2816
|
+
}
|
|
2895
2817
|
function hasRepositoryAdapterFile(files, infrastructureDir) {
|
|
2896
2818
|
return files.some((file) => file.startsWith(infrastructureDir) &&
|
|
2897
2819
|
file.endsWith("-repository.ts") &&
|