@beignet/cli 0.0.13 → 0.0.15
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 +37 -0
- package/README.md +22 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -1
- package/dist/inspect.js +185 -2
- package/dist/inspect.js.map +1 -1
- package/dist/make.d.ts +11 -0
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +1609 -1
- package/dist/make.js.map +1 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +14 -2
- package/dist/mcp.js.map +1 -1
- package/dist/templates/agents.d.ts +2 -2
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +9 -9
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.js +1 -1
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +13 -6
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.js +1 -1
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/todos.js +1 -1
- package/package.json +2 -2
- package/src/index.ts +35 -0
- package/src/inspect.ts +375 -2
- package/src/make.ts +1824 -1
- package/src/mcp.ts +16 -2
- package/src/templates/agents.ts +9 -10
- package/src/templates/base.ts +3 -3
- package/src/templates/server.ts +13 -6
- package/src/templates/shared.ts +1 -1
- package/src/templates/todos.ts +1 -1
- package/dist/templates/db.d.ts +0 -15
- package/dist/templates/db.d.ts.map +0 -1
- package/dist/templates/db.js +0 -1001
- package/dist/templates/db.js.map +0 -1
package/src/inspect.ts
CHANGED
|
@@ -1559,12 +1559,12 @@ async function inspectCanonicalConformance(
|
|
|
1559
1559
|
});
|
|
1560
1560
|
}
|
|
1561
1561
|
|
|
1562
|
-
if (!/defineRouteGroup\s*<\s*AppContext\s
|
|
1562
|
+
if (!/defineRouteGroup\s*<\s*AppContext\s*>\s*\(\s*\)\s*\(/.test(source)) {
|
|
1563
1563
|
diagnostics.push({
|
|
1564
1564
|
severity: "warning",
|
|
1565
1565
|
code: "BEIGNET_FEATURE_ROUTE_GROUP_CONTEXT",
|
|
1566
1566
|
file,
|
|
1567
|
-
message: `${file} should call defineRouteGroup<AppContext>(
|
|
1567
|
+
message: `${file} should call defineRouteGroup<AppContext>()({ ... }) so route groups preserve per-route contract and use-case type checks.`,
|
|
1568
1568
|
});
|
|
1569
1569
|
}
|
|
1570
1570
|
}
|
|
@@ -1975,6 +1975,30 @@ async function inspectProductionReadiness(
|
|
|
1975
1975
|
config,
|
|
1976
1976
|
sourceCache,
|
|
1977
1977
|
);
|
|
1978
|
+
const stripeMemoryProviderMismatch = await hasStripeMemoryProviderMismatch(
|
|
1979
|
+
targetDir,
|
|
1980
|
+
sourceCache,
|
|
1981
|
+
configFiles,
|
|
1982
|
+
installedPackages,
|
|
1983
|
+
providerEntries,
|
|
1984
|
+
);
|
|
1985
|
+
|
|
1986
|
+
diagnostics.push(
|
|
1987
|
+
...(await inspectEntitlementsProductionReadiness(
|
|
1988
|
+
targetDir,
|
|
1989
|
+
files,
|
|
1990
|
+
config,
|
|
1991
|
+
sourceCache,
|
|
1992
|
+
)),
|
|
1993
|
+
...(await inspectPaymentsProductionReadiness(
|
|
1994
|
+
targetDir,
|
|
1995
|
+
files,
|
|
1996
|
+
config,
|
|
1997
|
+
sourceCache,
|
|
1998
|
+
installedPackages,
|
|
1999
|
+
stripeMemoryProviderMismatch,
|
|
2000
|
+
)),
|
|
2001
|
+
);
|
|
1978
2002
|
|
|
1979
2003
|
for (const provider of providerDoctorRules) {
|
|
1980
2004
|
if (!installedPackages.has(provider.packageName)) continue;
|
|
@@ -2048,6 +2072,184 @@ async function inspectProductionReadiness(
|
|
|
2048
2072
|
return diagnostics;
|
|
2049
2073
|
}
|
|
2050
2074
|
|
|
2075
|
+
async function inspectEntitlementsProductionReadiness(
|
|
2076
|
+
targetDir: string,
|
|
2077
|
+
files: string[],
|
|
2078
|
+
config: ResolvedBeignetConfig,
|
|
2079
|
+
sourceCache: Map<string, string>,
|
|
2080
|
+
): Promise<InspectDiagnostic[]> {
|
|
2081
|
+
if (
|
|
2082
|
+
!(await usesEntitlementChecks(targetDir, files, sourceCache)) ||
|
|
2083
|
+
(await appPortsIncludesPort(
|
|
2084
|
+
targetDir,
|
|
2085
|
+
files,
|
|
2086
|
+
config,
|
|
2087
|
+
sourceCache,
|
|
2088
|
+
"entitlements",
|
|
2089
|
+
))
|
|
2090
|
+
) {
|
|
2091
|
+
return [];
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
return [
|
|
2095
|
+
{
|
|
2096
|
+
severity: "warning",
|
|
2097
|
+
code: "BEIGNET_ENTITLEMENTS_PORT_MISSING",
|
|
2098
|
+
file: config.paths.ports,
|
|
2099
|
+
message:
|
|
2100
|
+
"This app checks entitlements, but AppPorts does not declare an entitlements port. Add entitlements: EntitlementsPort; and wire a provider-backed entitlement resolver before deploying paid feature gates.",
|
|
2101
|
+
},
|
|
2102
|
+
];
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
async function inspectPaymentsProductionReadiness(
|
|
2106
|
+
targetDir: string,
|
|
2107
|
+
files: string[],
|
|
2108
|
+
config: ResolvedBeignetConfig,
|
|
2109
|
+
sourceCache: Map<string, string>,
|
|
2110
|
+
installedPackages: Set<string>,
|
|
2111
|
+
stripeMemoryProviderMismatch: boolean,
|
|
2112
|
+
): Promise<InspectDiagnostic[]> {
|
|
2113
|
+
if (
|
|
2114
|
+
!(
|
|
2115
|
+
hasBillingOrPaymentsFeature(files, config) ||
|
|
2116
|
+
installedPackages.has("@beignet/provider-payments-stripe")
|
|
2117
|
+
)
|
|
2118
|
+
) {
|
|
2119
|
+
return [];
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
const diagnostics: InspectDiagnostic[] = [];
|
|
2123
|
+
|
|
2124
|
+
if (stripeMemoryProviderMismatch) {
|
|
2125
|
+
diagnostics.push({
|
|
2126
|
+
severity: "warning",
|
|
2127
|
+
code: "BEIGNET_PAYMENTS_STRIPE_MEMORY_PROVIDER",
|
|
2128
|
+
file: serverProviderFiles(files, config)[0] ?? config.paths.server,
|
|
2129
|
+
message:
|
|
2130
|
+
"@beignet/provider-payments-stripe is installed and Stripe env/config is present, but server/providers.ts still registers memory payments. Register stripePaymentsProvider before deploying Stripe-backed billing, or remove the Stripe provider/config until the app is ready to switch.",
|
|
2131
|
+
});
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
if (!(await hasPaymentWebhookRoute(targetDir, files, config, sourceCache))) {
|
|
2135
|
+
diagnostics.push({
|
|
2136
|
+
severity: "warning",
|
|
2137
|
+
code: "BEIGNET_PAYMENTS_WEBHOOK_ROUTE_MISSING",
|
|
2138
|
+
file: directoryPath(config.paths.routes),
|
|
2139
|
+
message:
|
|
2140
|
+
"This app appears to use payments, but no route calls createPaymentWebhookRoute(...) or createWebhookRoute(...) for payments. Expose payment webhooks under app/api/webhooks/payments/route.ts or an equivalent configured route so provider events can update billing state.",
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
const billingContractsFile = `${directoryPath(config.paths.features)}/billing/contracts.ts`;
|
|
2145
|
+
if (files.includes(billingContractsFile)) {
|
|
2146
|
+
const source = await readCachedSource(
|
|
2147
|
+
targetDir,
|
|
2148
|
+
billingContractsFile,
|
|
2149
|
+
sourceCache,
|
|
2150
|
+
);
|
|
2151
|
+
const checkoutContract = exportedConstInitializer(
|
|
2152
|
+
source,
|
|
2153
|
+
"createCheckoutSession",
|
|
2154
|
+
);
|
|
2155
|
+
if (
|
|
2156
|
+
checkoutContract &&
|
|
2157
|
+
!(
|
|
2158
|
+
/\bidempotency\s*:/.test(checkoutContract) &&
|
|
2159
|
+
/["']idempotency-key["']/.test(checkoutContract)
|
|
2160
|
+
)
|
|
2161
|
+
) {
|
|
2162
|
+
diagnostics.push({
|
|
2163
|
+
severity: "warning",
|
|
2164
|
+
code: "BEIGNET_BILLING_CHECKOUT_IDEMPOTENCY_MISSING",
|
|
2165
|
+
file: billingContractsFile,
|
|
2166
|
+
contract: "createCheckoutSession",
|
|
2167
|
+
message:
|
|
2168
|
+
"The billing checkout contract appears to create payment sessions without idempotency metadata and an idempotency-key header. Mark checkout as idempotent so retries do not create duplicate payment sessions.",
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
const billingUseCasesFile = `${directoryPath(config.paths.features)}/billing/use-cases/index.ts`;
|
|
2174
|
+
if (files.includes(billingUseCasesFile)) {
|
|
2175
|
+
const source = await readCachedSource(
|
|
2176
|
+
targetDir,
|
|
2177
|
+
billingUseCasesFile,
|
|
2178
|
+
sourceCache,
|
|
2179
|
+
);
|
|
2180
|
+
if (
|
|
2181
|
+
/\bctx\.ports\.idempotency\b/.test(source) &&
|
|
2182
|
+
!(await appPortsIncludesIdempotency(
|
|
2183
|
+
targetDir,
|
|
2184
|
+
files,
|
|
2185
|
+
config,
|
|
2186
|
+
sourceCache,
|
|
2187
|
+
))
|
|
2188
|
+
) {
|
|
2189
|
+
diagnostics.push({
|
|
2190
|
+
severity: "warning",
|
|
2191
|
+
code: "BEIGNET_BILLING_IDEMPOTENCY_PORT_MISSING",
|
|
2192
|
+
file: config.paths.ports,
|
|
2193
|
+
message:
|
|
2194
|
+
"Billing use cases reference ctx.ports.idempotency, but AppPorts does not declare an idempotency port. Add idempotency to AppPorts and provider wiring, or remove the stale billing workflow dependency.",
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
const billingEntitlementsFile = `${directoryPath(config.paths.features)}/billing/entitlements.ts`;
|
|
2200
|
+
if (
|
|
2201
|
+
files.includes(billingEntitlementsFile) &&
|
|
2202
|
+
!(await hasBillingEntitlementsProviderWiring(
|
|
2203
|
+
targetDir,
|
|
2204
|
+
files,
|
|
2205
|
+
config,
|
|
2206
|
+
sourceCache,
|
|
2207
|
+
))
|
|
2208
|
+
) {
|
|
2209
|
+
diagnostics.push({
|
|
2210
|
+
severity: "warning",
|
|
2211
|
+
code: "BEIGNET_BILLING_ENTITLEMENTS_PROVIDER_MISSING",
|
|
2212
|
+
file: path.dirname(config.paths.infrastructurePorts),
|
|
2213
|
+
message:
|
|
2214
|
+
"features/billing/entitlements.ts exists, but no infra or server provider appears to call createBillingEntitlements(...). Wire the billing-backed entitlements port into AppPorts so paid feature gates use app billing state.",
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
return diagnostics;
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
async function hasStripeMemoryProviderMismatch(
|
|
2222
|
+
targetDir: string,
|
|
2223
|
+
sourceCache: Map<string, string>,
|
|
2224
|
+
configFiles: string[],
|
|
2225
|
+
installedPackages: Set<string>,
|
|
2226
|
+
providerEntries: string[],
|
|
2227
|
+
): Promise<boolean> {
|
|
2228
|
+
if (!installedPackages.has("@beignet/provider-payments-stripe")) {
|
|
2229
|
+
return false;
|
|
2230
|
+
}
|
|
2231
|
+
if (
|
|
2232
|
+
findProviderEntryIndex(providerEntries, ["stripePaymentsProvider"]) >= 0
|
|
2233
|
+
) {
|
|
2234
|
+
return false;
|
|
2235
|
+
}
|
|
2236
|
+
if (
|
|
2237
|
+
findProviderEntryIndex(providerEntries, [
|
|
2238
|
+
"createMemoryPaymentsProvider",
|
|
2239
|
+
"createMemoryPayments",
|
|
2240
|
+
]) < 0
|
|
2241
|
+
) {
|
|
2242
|
+
return false;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
return anyFileMatches(
|
|
2246
|
+
targetDir,
|
|
2247
|
+
configFiles,
|
|
2248
|
+
/\bSTRIPE_(?:SECRET_KEY|WEBHOOK_SECRET)\b/,
|
|
2249
|
+
sourceCache,
|
|
2250
|
+
);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2051
2253
|
// Local-dev ranges such as the generated-app conformance vendoring point each
|
|
2052
2254
|
// @beignet/* package at a different path on purpose, so they are exempt from
|
|
2053
2255
|
// the declared-range comparison. Their installed versions still participate in
|
|
@@ -2200,9 +2402,22 @@ async function inspectProviderRegistrationDrift(
|
|
|
2200
2402
|
config,
|
|
2201
2403
|
sourceCache,
|
|
2202
2404
|
);
|
|
2405
|
+
const stripeMemoryProviderMismatch = await hasStripeMemoryProviderMismatch(
|
|
2406
|
+
targetDir,
|
|
2407
|
+
sourceCache,
|
|
2408
|
+
operationalConfigFiles(files, config),
|
|
2409
|
+
installedPackages,
|
|
2410
|
+
providerEntries,
|
|
2411
|
+
);
|
|
2203
2412
|
|
|
2204
2413
|
for (const provider of providerDoctorRules) {
|
|
2205
2414
|
if (!installedPackages.has(provider.packageName)) continue;
|
|
2415
|
+
if (
|
|
2416
|
+
provider.packageName === "@beignet/provider-payments-stripe" &&
|
|
2417
|
+
stripeMemoryProviderMismatch
|
|
2418
|
+
) {
|
|
2419
|
+
continue;
|
|
2420
|
+
}
|
|
2206
2421
|
|
|
2207
2422
|
if (provider.variants !== undefined) {
|
|
2208
2423
|
if (
|
|
@@ -2641,6 +2856,164 @@ function hasBetterAuthRoute(
|
|
|
2641
2856
|
);
|
|
2642
2857
|
}
|
|
2643
2858
|
|
|
2859
|
+
function hasBillingOrPaymentsFeature(
|
|
2860
|
+
files: string[],
|
|
2861
|
+
config: ResolvedBeignetConfig,
|
|
2862
|
+
): boolean {
|
|
2863
|
+
const featuresPath = directoryPath(config.paths.features);
|
|
2864
|
+
return files.some(
|
|
2865
|
+
(file) =>
|
|
2866
|
+
file.startsWith(`${featuresPath}/billing/`) ||
|
|
2867
|
+
file.startsWith(`${featuresPath}/payments/`),
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
async function hasPaymentWebhookRoute(
|
|
2872
|
+
targetDir: string,
|
|
2873
|
+
files: string[],
|
|
2874
|
+
config: ResolvedBeignetConfig,
|
|
2875
|
+
sourceCache: Map<string, string>,
|
|
2876
|
+
): Promise<boolean> {
|
|
2877
|
+
const routesPath = directoryPath(config.paths.routes);
|
|
2878
|
+
const routeFiles = files.filter(
|
|
2879
|
+
(file) => file.startsWith(`${routesPath}/`) && file.endsWith("/route.ts"),
|
|
2880
|
+
);
|
|
2881
|
+
|
|
2882
|
+
for (const file of routeFiles) {
|
|
2883
|
+
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
2884
|
+
if (containsCallExpression(source, "createPaymentWebhookRoute")) {
|
|
2885
|
+
return true;
|
|
2886
|
+
}
|
|
2887
|
+
if (containsPaymentWebhookRoute(source)) {
|
|
2888
|
+
return true;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
return false;
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
function containsPaymentWebhookRoute(source: string): boolean {
|
|
2896
|
+
if (!containsCallExpression(source, "createWebhookRoute")) return false;
|
|
2897
|
+
|
|
2898
|
+
return (
|
|
2899
|
+
source.includes("payments.verifyWebhook") ||
|
|
2900
|
+
source.includes("paymentWebhook")
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
async function appPortsIncludesIdempotency(
|
|
2905
|
+
targetDir: string,
|
|
2906
|
+
files: string[],
|
|
2907
|
+
config: ResolvedBeignetConfig,
|
|
2908
|
+
sourceCache: Map<string, string>,
|
|
2909
|
+
): Promise<boolean> {
|
|
2910
|
+
return appPortsIncludesPort(
|
|
2911
|
+
targetDir,
|
|
2912
|
+
files,
|
|
2913
|
+
config,
|
|
2914
|
+
sourceCache,
|
|
2915
|
+
"idempotency",
|
|
2916
|
+
);
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
async function appPortsIncludesPort(
|
|
2920
|
+
targetDir: string,
|
|
2921
|
+
files: string[],
|
|
2922
|
+
config: ResolvedBeignetConfig,
|
|
2923
|
+
sourceCache: Map<string, string>,
|
|
2924
|
+
portName: string,
|
|
2925
|
+
): Promise<boolean> {
|
|
2926
|
+
if (!files.includes(config.paths.ports)) return false;
|
|
2927
|
+
|
|
2928
|
+
const source = await readCachedSource(
|
|
2929
|
+
targetDir,
|
|
2930
|
+
config.paths.ports,
|
|
2931
|
+
sourceCache,
|
|
2932
|
+
);
|
|
2933
|
+
const declarationStart = source.search(/\bexport\s+type\s+AppPorts\b/);
|
|
2934
|
+
if (declarationStart < 0) return false;
|
|
2935
|
+
|
|
2936
|
+
const bodyStart = source.indexOf("{", declarationStart);
|
|
2937
|
+
if (bodyStart < 0) return false;
|
|
2938
|
+
|
|
2939
|
+
const body = balancedObjectSource(source, bodyStart);
|
|
2940
|
+
return (
|
|
2941
|
+
body !== undefined &&
|
|
2942
|
+
new RegExp(`\\b${escapeRegExp(portName)}\\s*:`).test(body)
|
|
2943
|
+
);
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
async function usesEntitlementChecks(
|
|
2947
|
+
targetDir: string,
|
|
2948
|
+
files: string[],
|
|
2949
|
+
sourceCache: Map<string, string>,
|
|
2950
|
+
): Promise<boolean> {
|
|
2951
|
+
for (const file of files) {
|
|
2952
|
+
if (!/\.(?:ts|tsx|mts|cts)$/.test(file)) continue;
|
|
2953
|
+
if (isTestSourceFile(file)) continue;
|
|
2954
|
+
|
|
2955
|
+
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
2956
|
+
if (
|
|
2957
|
+
/\brequireEntitlement\s*\(/.test(source) ||
|
|
2958
|
+
/\bctx\.ports\.entitlements\b/.test(source)
|
|
2959
|
+
) {
|
|
2960
|
+
return true;
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
return false;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
async function hasBillingEntitlementsProviderWiring(
|
|
2968
|
+
targetDir: string,
|
|
2969
|
+
files: string[],
|
|
2970
|
+
config: ResolvedBeignetConfig,
|
|
2971
|
+
sourceCache: Map<string, string>,
|
|
2972
|
+
): Promise<boolean> {
|
|
2973
|
+
const infraPath = path.dirname(config.paths.infrastructurePorts);
|
|
2974
|
+
const serverPath = path.dirname(config.paths.server);
|
|
2975
|
+
const candidateFiles = files.filter(
|
|
2976
|
+
(file) =>
|
|
2977
|
+
/\.(?:ts|tsx|mts|cts)$/.test(file) &&
|
|
2978
|
+
!isTestSourceFile(file) &&
|
|
2979
|
+
(file.startsWith(`${infraPath}/`) || file.startsWith(`${serverPath}/`)),
|
|
2980
|
+
);
|
|
2981
|
+
|
|
2982
|
+
for (const file of candidateFiles) {
|
|
2983
|
+
const source = await readCachedSource(targetDir, file, sourceCache);
|
|
2984
|
+
if (containsCallExpression(source, "createBillingEntitlements")) {
|
|
2985
|
+
return true;
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
return false;
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
function isTestSourceFile(file: string): boolean {
|
|
2993
|
+
return (
|
|
2994
|
+
/(?:^|\/)tests\//.test(file) ||
|
|
2995
|
+
/\.(?:test|spec)\.(?:ts|tsx|mts|cts)$/.test(file)
|
|
2996
|
+
);
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
function exportedConstInitializer(
|
|
3000
|
+
source: string,
|
|
3001
|
+
exportName: string,
|
|
3002
|
+
): string | undefined {
|
|
3003
|
+
const declaration = new RegExp(
|
|
3004
|
+
`\\bexport\\s+const\\s+${escapeRegExp(exportName)}\\s*=`,
|
|
3005
|
+
).exec(source);
|
|
3006
|
+
if (declaration?.index === undefined) return undefined;
|
|
3007
|
+
|
|
3008
|
+
const start = declaration.index;
|
|
3009
|
+
const nextExport =
|
|
3010
|
+
/\n\s*export\s+(?:const|function|type|interface|class)\s+/g;
|
|
3011
|
+
nextExport.lastIndex = start + declaration[0].length;
|
|
3012
|
+
const next = nextExport.exec(source);
|
|
3013
|
+
|
|
3014
|
+
return source.slice(start, next?.index ?? source.length);
|
|
3015
|
+
}
|
|
3016
|
+
|
|
2644
3017
|
function featureNameFromContractFile(
|
|
2645
3018
|
file: string,
|
|
2646
3019
|
config: ResolvedBeignetConfig,
|