@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/src/inspect.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
type ProviderPackageMetadata,
|
|
6
|
-
parseProviderPackageMetadata,
|
|
7
|
-
} from "@beignet/core/providers";
|
|
3
|
+
import { parseProviderPackageMetadata } from "@beignet/core/providers";
|
|
8
4
|
import { createPainter } from "./ansi.js";
|
|
9
5
|
import {
|
|
10
6
|
type BeignetConfig,
|
|
7
|
+
type BeignetDatabaseTables,
|
|
11
8
|
clientDirPath,
|
|
12
9
|
clientFormsPath,
|
|
13
10
|
clientIndexPath,
|
|
11
|
+
defaultBeignetConfig,
|
|
14
12
|
directoryPath,
|
|
15
13
|
loadBeignetConfig,
|
|
16
14
|
normalizePath,
|
|
@@ -21,6 +19,21 @@ import {
|
|
|
21
19
|
formatGithubAnnotation,
|
|
22
20
|
type GithubAnnotationSeverity,
|
|
23
21
|
} from "./github-annotations.js";
|
|
22
|
+
import {
|
|
23
|
+
detectedProviderVariants,
|
|
24
|
+
diagnosticPackageJsonFile,
|
|
25
|
+
formatProviderMetadataIssues,
|
|
26
|
+
installedPackageNames,
|
|
27
|
+
providerDoctorRulesForInstalledPackages,
|
|
28
|
+
providerOperationalConfigFiles,
|
|
29
|
+
providerRequiredEnvExists,
|
|
30
|
+
providerRequiredTableExists,
|
|
31
|
+
providerVariantRegistrationSeverity,
|
|
32
|
+
readProviderListEntries,
|
|
33
|
+
readProviderPackageMetadataSource,
|
|
34
|
+
registrationTokenHint,
|
|
35
|
+
serverProviderFiles,
|
|
36
|
+
} from "./provider-audit.js";
|
|
24
37
|
import {
|
|
25
38
|
type AppendResult,
|
|
26
39
|
appendToArrayExpression,
|
|
@@ -387,6 +400,7 @@ const productionHardeningDiagnosticCodes = new Set([
|
|
|
387
400
|
"BEIGNET_PAYMENTS_STRIPE_MEMORY_PROVIDER",
|
|
388
401
|
"BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
|
|
389
402
|
"BEIGNET_PROVIDER_ENV_MISSING",
|
|
403
|
+
"BEIGNET_PROVIDER_TABLE_MISSING",
|
|
390
404
|
"BEIGNET_READINESS_ROUTE_MISSING",
|
|
391
405
|
"BEIGNET_TENANT_HEADER_AUTHORITY",
|
|
392
406
|
"BEIGNET_UPLOAD_AUTHORIZATION_MISSING",
|
|
@@ -1917,30 +1931,6 @@ async function inspectRouteErrorCatalogDrift(
|
|
|
1917
1931
|
return diagnostics;
|
|
1918
1932
|
}
|
|
1919
1933
|
|
|
1920
|
-
type ProviderDoctorVariantRule = {
|
|
1921
|
-
name: string;
|
|
1922
|
-
displayName: string;
|
|
1923
|
-
tokens: readonly string[];
|
|
1924
|
-
requiredEnv: readonly string[];
|
|
1925
|
-
registrationSeverity?: "warning" | "hint";
|
|
1926
|
-
};
|
|
1927
|
-
|
|
1928
|
-
type ProviderDoctorRule = {
|
|
1929
|
-
packageName: string;
|
|
1930
|
-
displayName: string;
|
|
1931
|
-
tokens: readonly string[];
|
|
1932
|
-
appPorts?: readonly { name: string; type: string }[];
|
|
1933
|
-
requiredEnv?: readonly string[];
|
|
1934
|
-
registrationSeverity?: "warning" | "hint";
|
|
1935
|
-
variants?: readonly ProviderDoctorVariantRule[];
|
|
1936
|
-
};
|
|
1937
|
-
|
|
1938
|
-
type ProviderPackageMetadataSource = {
|
|
1939
|
-
packageName: string;
|
|
1940
|
-
file: string;
|
|
1941
|
-
metadata: unknown;
|
|
1942
|
-
};
|
|
1943
|
-
|
|
1944
1934
|
const readinessRelevantProviderPackages = new Set([
|
|
1945
1935
|
"@beignet/provider-auth-better-auth",
|
|
1946
1936
|
"@beignet/provider-db-drizzle",
|
|
@@ -1975,6 +1965,10 @@ async function inspectProductionReadiness(
|
|
|
1975
1965
|
packageJson,
|
|
1976
1966
|
);
|
|
1977
1967
|
const configFiles = operationalConfigFiles(files, config);
|
|
1968
|
+
const infrastructurePath = directoryPath(
|
|
1969
|
+
path.dirname(config.paths.infrastructurePorts),
|
|
1970
|
+
);
|
|
1971
|
+
const schemaDir = `${infrastructurePath}/db/schema`;
|
|
1978
1972
|
|
|
1979
1973
|
for (const file of files) {
|
|
1980
1974
|
if (!file.endsWith(".ts") || file.endsWith(".test.ts")) continue;
|
|
@@ -2120,7 +2114,12 @@ async function inspectProductionReadiness(
|
|
|
2120
2114
|
)) {
|
|
2121
2115
|
for (const envVar of variant.requiredEnv) {
|
|
2122
2116
|
if (
|
|
2123
|
-
await
|
|
2117
|
+
await providerRequiredEnvExists(
|
|
2118
|
+
targetDir,
|
|
2119
|
+
configFiles,
|
|
2120
|
+
envVar,
|
|
2121
|
+
sourceCache,
|
|
2122
|
+
)
|
|
2124
2123
|
) {
|
|
2125
2124
|
continue;
|
|
2126
2125
|
}
|
|
@@ -2131,12 +2130,44 @@ async function inspectProductionReadiness(
|
|
|
2131
2130
|
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.`,
|
|
2132
2131
|
});
|
|
2133
2132
|
}
|
|
2133
|
+
|
|
2134
|
+
for (const tableName of configuredProviderRequiredTables(
|
|
2135
|
+
variant.requiredTables,
|
|
2136
|
+
config,
|
|
2137
|
+
)) {
|
|
2138
|
+
if (
|
|
2139
|
+
await providerRequiredTableExists(
|
|
2140
|
+
targetDir,
|
|
2141
|
+
files,
|
|
2142
|
+
config,
|
|
2143
|
+
infrastructurePath,
|
|
2144
|
+
schemaDir,
|
|
2145
|
+
tableName,
|
|
2146
|
+
sourceCache,
|
|
2147
|
+
)
|
|
2148
|
+
) {
|
|
2149
|
+
continue;
|
|
2150
|
+
}
|
|
2151
|
+
diagnostics.push({
|
|
2152
|
+
severity: "warning",
|
|
2153
|
+
code: "BEIGNET_PROVIDER_TABLE_MISSING",
|
|
2154
|
+
file: schemaDir,
|
|
2155
|
+
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.`,
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2134
2158
|
}
|
|
2135
2159
|
continue;
|
|
2136
2160
|
}
|
|
2137
2161
|
|
|
2138
2162
|
for (const envVar of provider.requiredEnv ?? []) {
|
|
2139
|
-
if (
|
|
2163
|
+
if (
|
|
2164
|
+
await providerRequiredEnvExists(
|
|
2165
|
+
targetDir,
|
|
2166
|
+
configFiles,
|
|
2167
|
+
envVar,
|
|
2168
|
+
sourceCache,
|
|
2169
|
+
)
|
|
2170
|
+
) {
|
|
2140
2171
|
continue;
|
|
2141
2172
|
}
|
|
2142
2173
|
diagnostics.push({
|
|
@@ -2146,6 +2177,31 @@ async function inspectProductionReadiness(
|
|
|
2146
2177
|
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.`,
|
|
2147
2178
|
});
|
|
2148
2179
|
}
|
|
2180
|
+
|
|
2181
|
+
for (const tableName of configuredProviderRequiredTables(
|
|
2182
|
+
provider.requiredTables ?? [],
|
|
2183
|
+
config,
|
|
2184
|
+
)) {
|
|
2185
|
+
if (
|
|
2186
|
+
await providerRequiredTableExists(
|
|
2187
|
+
targetDir,
|
|
2188
|
+
files,
|
|
2189
|
+
config,
|
|
2190
|
+
infrastructurePath,
|
|
2191
|
+
schemaDir,
|
|
2192
|
+
tableName,
|
|
2193
|
+
sourceCache,
|
|
2194
|
+
)
|
|
2195
|
+
) {
|
|
2196
|
+
continue;
|
|
2197
|
+
}
|
|
2198
|
+
diagnostics.push({
|
|
2199
|
+
severity: "warning",
|
|
2200
|
+
code: "BEIGNET_PROVIDER_TABLE_MISSING",
|
|
2201
|
+
file: schemaDir,
|
|
2202
|
+
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.`,
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2149
2205
|
}
|
|
2150
2206
|
|
|
2151
2207
|
if (installedPackages.has("@beignet/provider-auth-better-auth")) {
|
|
@@ -2685,13 +2741,7 @@ function operationalConfigFiles(
|
|
|
2685
2741
|
files: string[],
|
|
2686
2742
|
config: ResolvedBeignetConfig,
|
|
2687
2743
|
): string[] {
|
|
2688
|
-
return files
|
|
2689
|
-
if (file === ".env.example") return true;
|
|
2690
|
-
if (file === "drizzle.config.ts") return true;
|
|
2691
|
-
if (file === "lib/env.ts" || file === "lib/better-auth.ts") return true;
|
|
2692
|
-
if (isInfraOrServerFile(file, config) && file.endsWith(".ts")) return true;
|
|
2693
|
-
return false;
|
|
2694
|
-
});
|
|
2744
|
+
return providerOperationalConfigFiles(files, config);
|
|
2695
2745
|
}
|
|
2696
2746
|
|
|
2697
2747
|
async function readPackageJson(
|
|
@@ -2712,83 +2762,6 @@ async function readPackageJson(
|
|
|
2712
2762
|
);
|
|
2713
2763
|
}
|
|
2714
2764
|
|
|
2715
|
-
async function providerDoctorRulesForInstalledPackages(
|
|
2716
|
-
targetDir: string,
|
|
2717
|
-
packageJson:
|
|
2718
|
-
| {
|
|
2719
|
-
dependencies?: Record<string, string>;
|
|
2720
|
-
devDependencies?: Record<string, string>;
|
|
2721
|
-
peerDependencies?: Record<string, string>;
|
|
2722
|
-
}
|
|
2723
|
-
| undefined,
|
|
2724
|
-
): Promise<ProviderDoctorRule[]> {
|
|
2725
|
-
const packageNames = [...installedPackageNames(packageJson)];
|
|
2726
|
-
const rules = await Promise.all(
|
|
2727
|
-
packageNames.map(async (packageName) => {
|
|
2728
|
-
const source = await readProviderPackageMetadataSource(
|
|
2729
|
-
targetDir,
|
|
2730
|
-
packageName,
|
|
2731
|
-
);
|
|
2732
|
-
if (!source) return undefined;
|
|
2733
|
-
const result = parseProviderPackageMetadata(source.metadata);
|
|
2734
|
-
if (!result.success) return undefined;
|
|
2735
|
-
return providerDoctorRuleFromMetadata(packageName, result.metadata);
|
|
2736
|
-
}),
|
|
2737
|
-
);
|
|
2738
|
-
|
|
2739
|
-
return rules.filter((rule): rule is ProviderDoctorRule => Boolean(rule));
|
|
2740
|
-
}
|
|
2741
|
-
|
|
2742
|
-
async function readProviderPackageMetadataSource(
|
|
2743
|
-
targetDir: string,
|
|
2744
|
-
packageName: string,
|
|
2745
|
-
): Promise<ProviderPackageMetadataSource | undefined> {
|
|
2746
|
-
for (const candidate of providerPackageJsonCandidates(
|
|
2747
|
-
targetDir,
|
|
2748
|
-
packageName,
|
|
2749
|
-
)) {
|
|
2750
|
-
const packageJson = await readJsonIfExists<{
|
|
2751
|
-
beignet?: { provider?: unknown };
|
|
2752
|
-
}>(candidate);
|
|
2753
|
-
const metadata = packageJson?.beignet?.provider;
|
|
2754
|
-
if (metadata !== undefined) {
|
|
2755
|
-
return { packageName, file: candidate, metadata };
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
return undefined;
|
|
2760
|
-
}
|
|
2761
|
-
|
|
2762
|
-
function providerPackageJsonCandidates(
|
|
2763
|
-
targetDir: string,
|
|
2764
|
-
packageName: string,
|
|
2765
|
-
): string[] {
|
|
2766
|
-
const candidates = [
|
|
2767
|
-
path.join(
|
|
2768
|
-
targetDir,
|
|
2769
|
-
"node_modules",
|
|
2770
|
-
...packageName.split("/"),
|
|
2771
|
-
"package.json",
|
|
2772
|
-
),
|
|
2773
|
-
];
|
|
2774
|
-
|
|
2775
|
-
if (packageName.startsWith("@beignet/")) {
|
|
2776
|
-
candidates.push(
|
|
2777
|
-
path.resolve(
|
|
2778
|
-
// import.meta.dir is Bun-only; the published bin also runs under
|
|
2779
|
-
// plain Node, where only import.meta.url is available.
|
|
2780
|
-
path.dirname(fileURLToPath(import.meta.url)),
|
|
2781
|
-
"../../..",
|
|
2782
|
-
"packages",
|
|
2783
|
-
packageName.slice("@beignet/".length),
|
|
2784
|
-
"package.json",
|
|
2785
|
-
),
|
|
2786
|
-
);
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
return candidates;
|
|
2790
|
-
}
|
|
2791
|
-
|
|
2792
2765
|
async function readJsonIfExists<T>(file: string): Promise<T | undefined> {
|
|
2793
2766
|
try {
|
|
2794
2767
|
return JSON.parse(await readFile(file, "utf8")) as T;
|
|
@@ -2804,218 +2777,6 @@ async function readJsonIfExists<T>(file: string): Promise<T | undefined> {
|
|
|
2804
2777
|
}
|
|
2805
2778
|
}
|
|
2806
2779
|
|
|
2807
|
-
function providerDoctorRuleFromMetadata(
|
|
2808
|
-
packageName: string,
|
|
2809
|
-
metadata: ProviderPackageMetadata,
|
|
2810
|
-
): ProviderDoctorRule {
|
|
2811
|
-
const registration = metadata.registration;
|
|
2812
|
-
|
|
2813
|
-
return {
|
|
2814
|
-
packageName,
|
|
2815
|
-
displayName: metadata.displayName ?? packageName,
|
|
2816
|
-
tokens: registration?.tokens ?? [],
|
|
2817
|
-
appPorts: metadata.appPorts ?? [],
|
|
2818
|
-
requiredEnv: metadata.requiredEnv ?? [],
|
|
2819
|
-
registrationSeverity: registrationSeverityFromMetadata(registration),
|
|
2820
|
-
variants: metadata.variants?.map((variant) => ({
|
|
2821
|
-
name: variant.name,
|
|
2822
|
-
displayName: variant.displayName ?? variant.name,
|
|
2823
|
-
tokens: variant.registration?.tokens ?? [],
|
|
2824
|
-
requiredEnv: variant.requiredEnv ?? [],
|
|
2825
|
-
registrationSeverity: registrationSeverityFromMetadata(
|
|
2826
|
-
variant.registration,
|
|
2827
|
-
),
|
|
2828
|
-
})),
|
|
2829
|
-
};
|
|
2830
|
-
}
|
|
2831
|
-
|
|
2832
|
-
function registrationSeverityFromMetadata(
|
|
2833
|
-
registration:
|
|
2834
|
-
| { required?: boolean; severity?: "warning" | "hint" }
|
|
2835
|
-
| undefined,
|
|
2836
|
-
): "warning" | "hint" | undefined {
|
|
2837
|
-
return (
|
|
2838
|
-
registration?.severity ??
|
|
2839
|
-
(registration?.required === true ? "warning" : undefined)
|
|
2840
|
-
);
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
function detectedProviderVariants(
|
|
2844
|
-
variants: readonly ProviderDoctorVariantRule[],
|
|
2845
|
-
providerEntries: readonly string[],
|
|
2846
|
-
): ProviderDoctorVariantRule[] {
|
|
2847
|
-
return variants.filter((variant) =>
|
|
2848
|
-
variant.tokens.some((token) =>
|
|
2849
|
-
providerEntries.some((entry) => entry.includes(token)),
|
|
2850
|
-
),
|
|
2851
|
-
);
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
|
-
function providerVariantRegistrationSeverity(
|
|
2855
|
-
variants: readonly ProviderDoctorVariantRule[],
|
|
2856
|
-
): "warning" | "hint" | undefined {
|
|
2857
|
-
const severities = variants.map((variant) => variant.registrationSeverity);
|
|
2858
|
-
if (severities.includes("warning")) return "warning";
|
|
2859
|
-
if (severities.includes("hint")) return "hint";
|
|
2860
|
-
return undefined;
|
|
2861
|
-
}
|
|
2862
|
-
|
|
2863
|
-
async function readProviderListEntries(
|
|
2864
|
-
targetDir: string,
|
|
2865
|
-
files: string[],
|
|
2866
|
-
config: ResolvedBeignetConfig,
|
|
2867
|
-
sourceCache: Map<string, string>,
|
|
2868
|
-
): Promise<string[]> {
|
|
2869
|
-
const providerFiles = serverProviderFiles(files, config);
|
|
2870
|
-
const providerSource = (
|
|
2871
|
-
await Promise.all(
|
|
2872
|
-
providerFiles.map((file) =>
|
|
2873
|
-
readCachedSource(targetDir, file, sourceCache),
|
|
2874
|
-
),
|
|
2875
|
-
)
|
|
2876
|
-
).join("\n");
|
|
2877
|
-
const providerListSource = extractProviderListSource(providerSource);
|
|
2878
|
-
return providerListSource === undefined
|
|
2879
|
-
? []
|
|
2880
|
-
: providerListEntries(providerListSource);
|
|
2881
|
-
}
|
|
2882
|
-
|
|
2883
|
-
function registrationTokenHint(tokens: readonly string[]): string {
|
|
2884
|
-
const token = tokens[0];
|
|
2885
|
-
if (!token) return "the provider";
|
|
2886
|
-
return token.startsWith("create") ? `${token}()` : token;
|
|
2887
|
-
}
|
|
2888
|
-
|
|
2889
|
-
function diagnosticPackageJsonFile(targetDir: string, file: string): string {
|
|
2890
|
-
const relative = normalizePath(path.relative(targetDir, file));
|
|
2891
|
-
if (relative.startsWith("../")) return "package.json";
|
|
2892
|
-
return relative;
|
|
2893
|
-
}
|
|
2894
|
-
|
|
2895
|
-
function formatProviderMetadataIssues(
|
|
2896
|
-
issues: readonly { path: string; message: string }[],
|
|
2897
|
-
): string {
|
|
2898
|
-
return issues.map((issue) => `${issue.path} ${issue.message}`).join("; ");
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2901
|
-
function installedPackageNames(
|
|
2902
|
-
packageJson:
|
|
2903
|
-
| {
|
|
2904
|
-
dependencies?: Record<string, string>;
|
|
2905
|
-
devDependencies?: Record<string, string>;
|
|
2906
|
-
peerDependencies?: Record<string, string>;
|
|
2907
|
-
}
|
|
2908
|
-
| undefined,
|
|
2909
|
-
): Set<string> {
|
|
2910
|
-
return new Set([
|
|
2911
|
-
...Object.keys(packageJson?.dependencies ?? {}),
|
|
2912
|
-
...Object.keys(packageJson?.devDependencies ?? {}),
|
|
2913
|
-
...Object.keys(packageJson?.peerDependencies ?? {}),
|
|
2914
|
-
]);
|
|
2915
|
-
}
|
|
2916
|
-
|
|
2917
|
-
function serverProviderFiles(
|
|
2918
|
-
files: string[],
|
|
2919
|
-
config: ResolvedBeignetConfig,
|
|
2920
|
-
): string[] {
|
|
2921
|
-
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
2922
|
-
const candidates = [`${serverDir}/providers.ts`, config.paths.server];
|
|
2923
|
-
return candidates.filter((file) => files.includes(file));
|
|
2924
|
-
}
|
|
2925
|
-
|
|
2926
|
-
function extractProviderListSource(source: string): string | undefined {
|
|
2927
|
-
const listMatches = [
|
|
2928
|
-
...source.matchAll(/\bproviders\s*=\s*\[([\s\S]*?)\]\s*(?:;|as const)/g),
|
|
2929
|
-
...source.matchAll(/\bproviders\s*:\s*\[([\s\S]*?)\]/g),
|
|
2930
|
-
];
|
|
2931
|
-
if (listMatches.length === 0) return undefined;
|
|
2932
|
-
return listMatches.map((match) => match[1]).join("\n");
|
|
2933
|
-
}
|
|
2934
|
-
|
|
2935
|
-
function providerListEntries(source: string): string[] {
|
|
2936
|
-
const entries: string[] = [];
|
|
2937
|
-
let current = "";
|
|
2938
|
-
let depth = 0;
|
|
2939
|
-
let quote: '"' | "'" | "`" | undefined;
|
|
2940
|
-
let escaped = false;
|
|
2941
|
-
let lineComment = false;
|
|
2942
|
-
let blockComment = false;
|
|
2943
|
-
|
|
2944
|
-
for (let index = 0; index < source.length; index += 1) {
|
|
2945
|
-
const char = source[index];
|
|
2946
|
-
const next = source[index + 1];
|
|
2947
|
-
|
|
2948
|
-
if (lineComment) {
|
|
2949
|
-
if (char === "\n") {
|
|
2950
|
-
lineComment = false;
|
|
2951
|
-
current += char;
|
|
2952
|
-
}
|
|
2953
|
-
continue;
|
|
2954
|
-
}
|
|
2955
|
-
|
|
2956
|
-
if (blockComment) {
|
|
2957
|
-
if (char === "*" && next === "/") {
|
|
2958
|
-
blockComment = false;
|
|
2959
|
-
index += 1;
|
|
2960
|
-
}
|
|
2961
|
-
continue;
|
|
2962
|
-
}
|
|
2963
|
-
|
|
2964
|
-
if (quote) {
|
|
2965
|
-
current += char;
|
|
2966
|
-
if (escaped) {
|
|
2967
|
-
escaped = false;
|
|
2968
|
-
continue;
|
|
2969
|
-
}
|
|
2970
|
-
if (char === "\\") {
|
|
2971
|
-
escaped = true;
|
|
2972
|
-
continue;
|
|
2973
|
-
}
|
|
2974
|
-
if (char === quote) {
|
|
2975
|
-
quote = undefined;
|
|
2976
|
-
}
|
|
2977
|
-
continue;
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
if (char === "/" && next === "/") {
|
|
2981
|
-
lineComment = true;
|
|
2982
|
-
index += 1;
|
|
2983
|
-
continue;
|
|
2984
|
-
}
|
|
2985
|
-
|
|
2986
|
-
if (char === "/" && next === "*") {
|
|
2987
|
-
blockComment = true;
|
|
2988
|
-
index += 1;
|
|
2989
|
-
continue;
|
|
2990
|
-
}
|
|
2991
|
-
|
|
2992
|
-
if (char === '"' || char === "'" || char === "`") {
|
|
2993
|
-
quote = char;
|
|
2994
|
-
current += char;
|
|
2995
|
-
continue;
|
|
2996
|
-
}
|
|
2997
|
-
|
|
2998
|
-
if (char === "(" || char === "[" || char === "{") {
|
|
2999
|
-
depth += 1;
|
|
3000
|
-
} else if (char === ")" || char === "]" || char === "}") {
|
|
3001
|
-
depth = Math.max(0, depth - 1);
|
|
3002
|
-
}
|
|
3003
|
-
|
|
3004
|
-
if (char === "," && depth === 0) {
|
|
3005
|
-
const entry = current.trim();
|
|
3006
|
-
if (entry) entries.push(entry);
|
|
3007
|
-
current = "";
|
|
3008
|
-
continue;
|
|
3009
|
-
}
|
|
3010
|
-
|
|
3011
|
-
current += char;
|
|
3012
|
-
}
|
|
3013
|
-
|
|
3014
|
-
const entry = current.trim();
|
|
3015
|
-
if (entry) entries.push(entry);
|
|
3016
|
-
return entries;
|
|
3017
|
-
}
|
|
3018
|
-
|
|
3019
2780
|
function findProviderEntryIndex(
|
|
3020
2781
|
entries: string[],
|
|
3021
2782
|
tokens: readonly string[],
|
|
@@ -4433,34 +4194,66 @@ async function inspectDatabaseLifecycleDrift(
|
|
|
4433
4194
|
|
|
4434
4195
|
type DurableDrizzleTableRequirement = {
|
|
4435
4196
|
capability: string;
|
|
4436
|
-
|
|
4197
|
+
table: keyof BeignetDatabaseTables;
|
|
4437
4198
|
code: string;
|
|
4438
|
-
|
|
4439
|
-
|
|
4199
|
+
factoryNames: readonly string[];
|
|
4200
|
+
setupNames: readonly string[];
|
|
4201
|
+
explicitFactoryPattern: string;
|
|
4202
|
+
explicitSetupPattern: string;
|
|
4440
4203
|
};
|
|
4441
4204
|
|
|
4442
4205
|
const durableDrizzleTableRequirements: readonly DurableDrizzleTableRequirement[] =
|
|
4443
4206
|
[
|
|
4444
4207
|
{
|
|
4445
4208
|
capability: "idempotency",
|
|
4446
|
-
|
|
4209
|
+
table: "idempotency",
|
|
4447
4210
|
code: "BEIGNET_IDEMPOTENCY_TABLE_MISSING",
|
|
4448
|
-
|
|
4449
|
-
|
|
4211
|
+
factoryNames: [
|
|
4212
|
+
"createDrizzleSqliteIdempotencyPort",
|
|
4213
|
+
"createDrizzlePostgresIdempotencyPort",
|
|
4214
|
+
"createDrizzleMysqlIdempotencyPort",
|
|
4215
|
+
],
|
|
4216
|
+
setupNames: [
|
|
4217
|
+
"createDrizzleSqliteIdempotencySetupStatements",
|
|
4218
|
+
"createDrizzlePostgresIdempotencySetupStatements",
|
|
4219
|
+
"createDrizzleMysqlIdempotencySetupStatements",
|
|
4220
|
+
],
|
|
4221
|
+
explicitFactoryPattern: "createDrizzle[A-Za-z]*Idempotency(?:Port)?",
|
|
4222
|
+
explicitSetupPattern: "createDrizzle[A-Za-z]*IdempotencySetupStatements",
|
|
4450
4223
|
},
|
|
4451
4224
|
{
|
|
4452
4225
|
capability: "outbox",
|
|
4453
|
-
|
|
4226
|
+
table: "outbox",
|
|
4454
4227
|
code: "BEIGNET_OUTBOX_TABLE_MISSING",
|
|
4455
|
-
|
|
4456
|
-
|
|
4228
|
+
factoryNames: [
|
|
4229
|
+
"createDrizzleSqliteOutboxPort",
|
|
4230
|
+
"createDrizzlePostgresOutboxPort",
|
|
4231
|
+
"createDrizzleMysqlOutboxPort",
|
|
4232
|
+
],
|
|
4233
|
+
setupNames: [
|
|
4234
|
+
"createDrizzleSqliteOutboxSetupStatements",
|
|
4235
|
+
"createDrizzlePostgresOutboxSetupStatements",
|
|
4236
|
+
"createDrizzleMysqlOutboxSetupStatements",
|
|
4237
|
+
],
|
|
4238
|
+
explicitFactoryPattern: "createDrizzle[A-Za-z]*Outbox(?:Port)?",
|
|
4239
|
+
explicitSetupPattern: "createDrizzle[A-Za-z]*OutboxSetupStatements",
|
|
4457
4240
|
},
|
|
4458
4241
|
{
|
|
4459
4242
|
capability: "audit",
|
|
4460
|
-
|
|
4243
|
+
table: "audit",
|
|
4461
4244
|
code: "BEIGNET_AUDIT_TABLE_MISSING",
|
|
4462
|
-
|
|
4463
|
-
|
|
4245
|
+
factoryNames: [
|
|
4246
|
+
"createDrizzleSqliteAuditLogPort",
|
|
4247
|
+
"createDrizzlePostgresAuditLogPort",
|
|
4248
|
+
"createDrizzleMysqlAuditLogPort",
|
|
4249
|
+
],
|
|
4250
|
+
setupNames: [
|
|
4251
|
+
"createDrizzleSqliteAuditLogSetupStatements",
|
|
4252
|
+
"createDrizzlePostgresAuditLogSetupStatements",
|
|
4253
|
+
"createDrizzleMysqlAuditLogSetupStatements",
|
|
4254
|
+
],
|
|
4255
|
+
explicitFactoryPattern: "createDrizzle[A-Za-z]*AuditLog(?:Port)?",
|
|
4256
|
+
explicitSetupPattern: "createDrizzle[A-Za-z]*AuditLogSetupStatements",
|
|
4464
4257
|
},
|
|
4465
4258
|
];
|
|
4466
4259
|
|
|
@@ -4479,6 +4272,7 @@ async function inspectDurableDrizzleTableDrift(
|
|
|
4479
4272
|
const tableNames = await configuredDurableDrizzleTableNames(
|
|
4480
4273
|
targetDir,
|
|
4481
4274
|
usageFiles,
|
|
4275
|
+
config,
|
|
4482
4276
|
requirement,
|
|
4483
4277
|
sourceCache,
|
|
4484
4278
|
);
|
|
@@ -4489,6 +4283,7 @@ async function inspectDurableDrizzleTableDrift(
|
|
|
4489
4283
|
await durableDrizzleTableExists(
|
|
4490
4284
|
targetDir,
|
|
4491
4285
|
files,
|
|
4286
|
+
config,
|
|
4492
4287
|
infrastructurePath,
|
|
4493
4288
|
schemaDir,
|
|
4494
4289
|
requirement,
|
|
@@ -4526,17 +4321,29 @@ function durableDrizzleUsageFiles(
|
|
|
4526
4321
|
async function configuredDurableDrizzleTableNames(
|
|
4527
4322
|
targetDir: string,
|
|
4528
4323
|
files: string[],
|
|
4324
|
+
config: ResolvedBeignetConfig,
|
|
4529
4325
|
requirement: DurableDrizzleTableRequirement,
|
|
4530
4326
|
sourceCache: Map<string, string>,
|
|
4531
4327
|
): Promise<Set<string>> {
|
|
4532
4328
|
const tableNames = new Set<string>();
|
|
4329
|
+
const providerDefaultTableName =
|
|
4330
|
+
defaultBeignetConfig.database.tables[requirement.table];
|
|
4331
|
+
const configuredTableName = config.database.tables[requirement.table];
|
|
4533
4332
|
|
|
4534
4333
|
for (const file of files) {
|
|
4535
4334
|
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
4536
|
-
for (const tableName of
|
|
4335
|
+
for (const tableName of tableNamesFromBeignetProviderCalls(
|
|
4537
4336
|
source,
|
|
4538
|
-
requirement.
|
|
4539
|
-
|
|
4337
|
+
requirement.factoryNames,
|
|
4338
|
+
providerDefaultTableName,
|
|
4339
|
+
configuredTableName,
|
|
4340
|
+
)) {
|
|
4341
|
+
tableNames.add(tableName);
|
|
4342
|
+
}
|
|
4343
|
+
for (const tableName of tableNamesFromExplicitCalls(
|
|
4344
|
+
source,
|
|
4345
|
+
requirement.explicitFactoryPattern,
|
|
4346
|
+
configuredTableName,
|
|
4540
4347
|
)) {
|
|
4541
4348
|
tableNames.add(tableName);
|
|
4542
4349
|
}
|
|
@@ -4548,21 +4355,25 @@ async function configuredDurableDrizzleTableNames(
|
|
|
4548
4355
|
async function durableDrizzleTableExists(
|
|
4549
4356
|
targetDir: string,
|
|
4550
4357
|
files: string[],
|
|
4358
|
+
config: ResolvedBeignetConfig,
|
|
4551
4359
|
infrastructurePath: string,
|
|
4552
4360
|
schemaDir: string,
|
|
4553
4361
|
requirement: DurableDrizzleTableRequirement,
|
|
4554
4362
|
tableName: string,
|
|
4555
4363
|
sourceCache: Map<string, string>,
|
|
4556
4364
|
): Promise<boolean> {
|
|
4557
|
-
|
|
4558
|
-
(
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4365
|
+
if (
|
|
4366
|
+
await providerRequiredTableExists(
|
|
4367
|
+
targetDir,
|
|
4368
|
+
files,
|
|
4369
|
+
config,
|
|
4370
|
+
infrastructurePath,
|
|
4371
|
+
schemaDir,
|
|
4372
|
+
tableName,
|
|
4373
|
+
sourceCache,
|
|
4374
|
+
)
|
|
4375
|
+
) {
|
|
4376
|
+
return true;
|
|
4566
4377
|
}
|
|
4567
4378
|
|
|
4568
4379
|
const setupFiles = files.filter(
|
|
@@ -4572,16 +4383,22 @@ async function durableDrizzleTableExists(
|
|
|
4572
4383
|
file.startsWith(`${infrastructurePath}/db/`)) &&
|
|
4573
4384
|
/\.(?:ts|js|mts|mjs|cts|cjs|sql)$/.test(file),
|
|
4574
4385
|
);
|
|
4386
|
+
const providerDefaultTableName =
|
|
4387
|
+
defaultBeignetConfig.database.tables[requirement.table];
|
|
4388
|
+
const configuredTableName = config.database.tables[requirement.table];
|
|
4575
4389
|
for (const file of setupFiles) {
|
|
4576
4390
|
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
4577
|
-
if (sourceCreatesTable(source, tableName)) return true;
|
|
4578
|
-
|
|
4579
|
-
if (!requirement.setupPattern) continue;
|
|
4580
4391
|
if (
|
|
4581
|
-
|
|
4392
|
+
tableNamesFromBeignetProviderCalls(
|
|
4393
|
+
source,
|
|
4394
|
+
requirement.setupNames,
|
|
4395
|
+
providerDefaultTableName,
|
|
4396
|
+
configuredTableName,
|
|
4397
|
+
).has(tableName) ||
|
|
4398
|
+
tableNamesFromExplicitCalls(
|
|
4582
4399
|
source,
|
|
4583
|
-
requirement.
|
|
4584
|
-
|
|
4400
|
+
requirement.explicitSetupPattern,
|
|
4401
|
+
configuredTableName,
|
|
4585
4402
|
).has(tableName)
|
|
4586
4403
|
) {
|
|
4587
4404
|
return true;
|
|
@@ -4591,17 +4408,30 @@ async function durableDrizzleTableExists(
|
|
|
4591
4408
|
return false;
|
|
4592
4409
|
}
|
|
4593
4410
|
|
|
4594
|
-
function
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4411
|
+
function tableNamesFromBeignetProviderCalls(
|
|
4412
|
+
source: string,
|
|
4413
|
+
exportNames: readonly string[],
|
|
4414
|
+
defaultTableName: string,
|
|
4415
|
+
configuredTableName: string,
|
|
4416
|
+
): Set<string> {
|
|
4417
|
+
const tableNames = new Set<string>();
|
|
4418
|
+
for (const callee of beignetProviderCallees(source, exportNames)) {
|
|
4419
|
+
for (const tableName of tableNamesFromCallee(
|
|
4420
|
+
source,
|
|
4421
|
+
callee,
|
|
4422
|
+
defaultTableName,
|
|
4423
|
+
configuredTableName,
|
|
4424
|
+
)) {
|
|
4425
|
+
tableNames.add(tableName);
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
return tableNames;
|
|
4599
4429
|
}
|
|
4600
4430
|
|
|
4601
|
-
function
|
|
4431
|
+
function tableNamesFromExplicitCalls(
|
|
4602
4432
|
source: string,
|
|
4603
4433
|
calleePattern: string,
|
|
4604
|
-
|
|
4434
|
+
configuredTableName: string,
|
|
4605
4435
|
): Set<string> {
|
|
4606
4436
|
const tableNames = new Set<string>();
|
|
4607
4437
|
const callPattern = new RegExp(
|
|
@@ -4614,16 +4444,115 @@ function tableNamesFromCalls(
|
|
|
4614
4444
|
const prefix = source.slice(Math.max(0, matchIndex - 32), matchIndex);
|
|
4615
4445
|
if (/\bfunction\s+$/.test(prefix)) continue;
|
|
4616
4446
|
|
|
4617
|
-
const
|
|
4618
|
-
|
|
4619
|
-
argsSource,
|
|
4620
|
-
)?.[1];
|
|
4621
|
-
tableNames.add(tableName ?? defaultTableName);
|
|
4447
|
+
const tableName = tableNameFromArgs(match[1] ?? "", configuredTableName);
|
|
4448
|
+
if (tableName) tableNames.add(tableName);
|
|
4622
4449
|
}
|
|
4623
4450
|
|
|
4624
4451
|
return tableNames;
|
|
4625
4452
|
}
|
|
4626
4453
|
|
|
4454
|
+
type BeignetProviderCallee =
|
|
4455
|
+
| { kind: "identifier"; name: string }
|
|
4456
|
+
| { kind: "namespace"; namespace: string; name: string };
|
|
4457
|
+
|
|
4458
|
+
function beignetProviderCallees(
|
|
4459
|
+
source: string,
|
|
4460
|
+
exportNames: readonly string[],
|
|
4461
|
+
): BeignetProviderCallee[] {
|
|
4462
|
+
const exportNameSet = new Set(exportNames);
|
|
4463
|
+
const callees: BeignetProviderCallee[] = [];
|
|
4464
|
+
|
|
4465
|
+
for (const match of source.matchAll(
|
|
4466
|
+
/\bimport\s+(?:type\s+)?\{([^}]*)\}\s+from\s+["']@beignet\/provider-db-drizzle\/(?:sqlite|postgres|mysql)["']/g,
|
|
4467
|
+
)) {
|
|
4468
|
+
for (const specifier of match[1].split(",")) {
|
|
4469
|
+
const cleaned = specifier.trim().replace(/^type\s+/, "");
|
|
4470
|
+
if (!cleaned) continue;
|
|
4471
|
+
const [imported, local] = cleaned.split(/\s+as\s+/);
|
|
4472
|
+
if (!exportNameSet.has(imported)) continue;
|
|
4473
|
+
callees.push({ kind: "identifier", name: local?.trim() || imported });
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
|
|
4477
|
+
for (const match of source.matchAll(
|
|
4478
|
+
/\bimport\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+["']@beignet\/provider-db-drizzle\/(?:sqlite|postgres|mysql)["']/g,
|
|
4479
|
+
)) {
|
|
4480
|
+
for (const exportName of exportNames) {
|
|
4481
|
+
callees.push({
|
|
4482
|
+
kind: "namespace",
|
|
4483
|
+
namespace: match[1],
|
|
4484
|
+
name: exportName,
|
|
4485
|
+
});
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
|
|
4489
|
+
return callees;
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
function tableNamesFromCallee(
|
|
4493
|
+
source: string,
|
|
4494
|
+
callee: BeignetProviderCallee,
|
|
4495
|
+
defaultTableName: string,
|
|
4496
|
+
configuredTableName: string,
|
|
4497
|
+
): Set<string> {
|
|
4498
|
+
const tableNames = new Set<string>();
|
|
4499
|
+
const calleePattern =
|
|
4500
|
+
callee.kind === "identifier"
|
|
4501
|
+
? `\\b${escapeRegExp(callee.name)}`
|
|
4502
|
+
: `\\b${escapeRegExp(callee.namespace)}\\s*\\.\\s*${escapeRegExp(
|
|
4503
|
+
callee.name,
|
|
4504
|
+
)}`;
|
|
4505
|
+
const callPattern = new RegExp(
|
|
4506
|
+
`${calleePattern}\\s*\\(([\\s\\S]{0,800}?)\\)`,
|
|
4507
|
+
"g",
|
|
4508
|
+
);
|
|
4509
|
+
|
|
4510
|
+
for (const match of source.matchAll(callPattern)) {
|
|
4511
|
+
const matchIndex = match.index ?? 0;
|
|
4512
|
+
const prefix = source.slice(Math.max(0, matchIndex - 32), matchIndex);
|
|
4513
|
+
if (/\bfunction\s+$/.test(prefix)) continue;
|
|
4514
|
+
|
|
4515
|
+
tableNames.add(
|
|
4516
|
+
tableNameFromArgs(match[1] ?? "", configuredTableName) ??
|
|
4517
|
+
defaultTableName,
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
return tableNames;
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
function tableNameFromArgs(
|
|
4525
|
+
argsSource: string,
|
|
4526
|
+
configuredTableName: string,
|
|
4527
|
+
): string | undefined {
|
|
4528
|
+
const literal = /\btableName\s*:\s*["'`]([^"'`]+)["'`]/.exec(argsSource)?.[1];
|
|
4529
|
+
if (literal) return literal;
|
|
4530
|
+
if (/\btableName\s*:/.test(argsSource)) return configuredTableName;
|
|
4531
|
+
return undefined;
|
|
4532
|
+
}
|
|
4533
|
+
|
|
4534
|
+
function configuredProviderRequiredTables(
|
|
4535
|
+
tableNames: readonly string[],
|
|
4536
|
+
config: ResolvedBeignetConfig,
|
|
4537
|
+
): string[] {
|
|
4538
|
+
return tableNames.map((tableName) =>
|
|
4539
|
+
configuredProviderRequiredTable(tableName, config),
|
|
4540
|
+
);
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4543
|
+
function configuredProviderRequiredTable(
|
|
4544
|
+
tableName: string,
|
|
4545
|
+
config: ResolvedBeignetConfig,
|
|
4546
|
+
): string {
|
|
4547
|
+
for (const [key, defaultTableName] of Object.entries(
|
|
4548
|
+
defaultBeignetConfig.database.tables,
|
|
4549
|
+
) as Array<[keyof BeignetDatabaseTables, string]>) {
|
|
4550
|
+
if (tableName === defaultTableName) return config.database.tables[key];
|
|
4551
|
+
}
|
|
4552
|
+
|
|
4553
|
+
return tableName;
|
|
4554
|
+
}
|
|
4555
|
+
|
|
4627
4556
|
function hasRepositoryAdapterFile(
|
|
4628
4557
|
files: string[],
|
|
4629
4558
|
infrastructureDir: string,
|