@supacloud/compiler 0.21.1 → 0.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -1
- package/dist/cli.js +699 -359
- package/dist/config.d.ts +1 -1
- package/dist/database-contracts.d.ts +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +675 -342
- package/dist/sql-safety.d.ts +17 -0
- package/dist/static-di.d.ts +4 -0
- package/dist/type-safety.d.ts +8 -0
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -197,10 +197,24 @@ function renderApplication(graph, options) {
|
|
|
197
197
|
""
|
|
198
198
|
].join(`
|
|
199
199
|
`);
|
|
200
|
+
const commandGovernance = graph.modules.flatMap((module) => module.commands.map((command) => ({
|
|
201
|
+
module: module.name,
|
|
202
|
+
className: command.className,
|
|
203
|
+
name: command.name,
|
|
204
|
+
permission: command.permission ?? null,
|
|
205
|
+
rpc: command.rpc ?? null,
|
|
206
|
+
transaction: command.transaction ?? null,
|
|
207
|
+
audit: command.audit ?? null,
|
|
208
|
+
idempotency: command.idempotency ?? null
|
|
209
|
+
}))).sort((left, right) => `${left.module}:${left.name}`.localeCompare(`${right.module}:${right.name}`));
|
|
200
210
|
const manifest = {
|
|
201
211
|
version: 1,
|
|
202
212
|
modules: graph.modules,
|
|
203
|
-
externalTokens: graph.externalTokens
|
|
213
|
+
externalTokens: graph.externalTokens,
|
|
214
|
+
commandGovernance: {
|
|
215
|
+
defaults: { authorization: "required", audit: "required", idempotency: "required", transaction: "required" },
|
|
216
|
+
commands: commandGovernance
|
|
217
|
+
}
|
|
204
218
|
};
|
|
205
219
|
const clientCode = options.generateClient ? renderClient(graph, options) : undefined;
|
|
206
220
|
const openApiCode = options.generateOpenApi ? renderOpenApi(graph, options) : undefined;
|
|
@@ -355,7 +369,7 @@ class ModuleGenerator {
|
|
|
355
369
|
this.imports = imports;
|
|
356
370
|
this.pascal = pascalName(module.name);
|
|
357
371
|
if (module.providers.some((provider) => (provider.functionalInjects?.length ?? 0) > 0) || module.controllers.some((controller) => (controller.functionalInjects?.length ?? 0) > 0)) {
|
|
358
|
-
|
|
372
|
+
throw new Error("SC2012: Compiled DI requires constructor injection; property inject() is not supported.");
|
|
359
373
|
}
|
|
360
374
|
}
|
|
361
375
|
renderFactories() {
|
|
@@ -389,6 +403,7 @@ class ModuleGenerator {
|
|
|
389
403
|
lines.push(` jobs: ${this.renderJobs()},`);
|
|
390
404
|
if (this.module.aspects && this.module.aspects.length > 0) {
|
|
391
405
|
lines.push(` aspects: ${this.renderAspects(this.module.aspects)},`);
|
|
406
|
+
lines.push(` aspectPipeline: ${this.renderAspectPipeline(this.module.aspects)},`);
|
|
392
407
|
}
|
|
393
408
|
lines.push(`}`);
|
|
394
409
|
return lines.join(`
|
|
@@ -465,6 +480,7 @@ class ModuleGenerator {
|
|
|
465
480
|
}
|
|
466
481
|
if (route.aspects && route.aspects.length > 0) {
|
|
467
482
|
fields.push(`aspects: ${this.renderAspects(route.aspects)}`);
|
|
483
|
+
fields.push(`aspectPipeline: ${this.renderAspectPipeline(route.aspects)}`);
|
|
468
484
|
}
|
|
469
485
|
const invokerArgs = (route.handlerParams ?? []).map((hp) => {
|
|
470
486
|
if (hp.kind === "param") {
|
|
@@ -545,7 +561,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
545
561
|
`idempotency: ${JSON.stringify(command.idempotency)}`,
|
|
546
562
|
...command.rpc ? [`rpc: ${JSON.stringify(command.rpc)}`] : [],
|
|
547
563
|
...command.standalone ? ["standalone: true"] : [],
|
|
548
|
-
...command.aspects && command.aspects.length > 0 ? [`aspects: ${this.renderAspects(command.aspects)}`] : []
|
|
564
|
+
...command.aspects && command.aspects.length > 0 ? [`aspects: ${this.renderAspects(command.aspects)}`, `aspectPipeline: ${this.renderAspectPipeline(command.aspects)}`] : []
|
|
549
565
|
];
|
|
550
566
|
return `{ ${fields.join(", ")} }`;
|
|
551
567
|
}).join(", ")}]`;
|
|
@@ -578,6 +594,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
578
594
|
fields.push(`idempotency: ${JSON.stringify(job.idempotency)}`);
|
|
579
595
|
if (job.aspects && job.aspects.length > 0) {
|
|
580
596
|
fields.push(`aspects: ${this.renderAspects(job.aspects)}`);
|
|
597
|
+
fields.push(`aspectPipeline: ${this.renderAspectPipeline(job.aspects)}`);
|
|
581
598
|
}
|
|
582
599
|
return `{ ${fields.join(", ")}, }`;
|
|
583
600
|
}).join(", ")}]`;
|
|
@@ -585,6 +602,24 @@ ${indent(item, 2)}`).join(",")}
|
|
|
585
602
|
renderAspects(aspects) {
|
|
586
603
|
return `[${aspects.map((aspect) => this.imports.add(aspect.name, aspect.importPath, aspect.importModule)).join(", ")}]`;
|
|
587
604
|
}
|
|
605
|
+
renderAspectPipeline(aspects) {
|
|
606
|
+
const lines = [
|
|
607
|
+
`async (context, next, observe) => {`,
|
|
608
|
+
` const state = { active: true };`,
|
|
609
|
+
` const step${aspects.length} = compiledAspectNext(next, state);`
|
|
610
|
+
];
|
|
611
|
+
for (let index = aspects.length - 1;index >= 0; index--) {
|
|
612
|
+
const aspect = aspects[index];
|
|
613
|
+
if (!aspect)
|
|
614
|
+
continue;
|
|
615
|
+
const name = this.imports.add(aspect.name, aspect.importPath, aspect.importModule);
|
|
616
|
+
const stage = JSON.stringify(`aspect[${index}]:${aspect.name}`);
|
|
617
|
+
lines.push(` const step${index} = compiledAspectNext(() => observeCompiledAspect(observe, ${stage}, () => ${name}(context, step${index + 1})), state);`);
|
|
618
|
+
}
|
|
619
|
+
lines.push(` try { return await step0(); } finally { state.active = false; }`, `}`);
|
|
620
|
+
return lines.join(`
|
|
621
|
+
`);
|
|
622
|
+
}
|
|
588
623
|
renderServicesFactory() {
|
|
589
624
|
return [
|
|
590
625
|
`function create${this.pascal}Services(`,
|
|
@@ -699,7 +734,7 @@ ${indent(item, 2)}`).join(",")}
|
|
|
699
734
|
const args = provider.deps.map((dep, index) => this.typedDepExpr(dep, kind, this.depOptions(provider, dep), `ConstructorParameters<typeof ${useClass}>[${index}]`)).join(", ");
|
|
700
735
|
const local = this.localVar(isMulti ? provider.useClass ?? `${provider.token}Item` : provider.token, kind);
|
|
701
736
|
return {
|
|
702
|
-
constLine: `const ${local} = ${
|
|
737
|
+
constLine: `const ${local} = new ${useClass}(${args});`,
|
|
703
738
|
key,
|
|
704
739
|
expr: local
|
|
705
740
|
};
|
|
@@ -735,32 +770,11 @@ ${indent(item, 2)}`).join(",")}
|
|
|
735
770
|
const key = camelName(controller.className);
|
|
736
771
|
const local = this.localVar(controller.className, kind);
|
|
737
772
|
return {
|
|
738
|
-
constLine: `const ${local} = ${
|
|
773
|
+
constLine: `const ${local} = new ${className}(${args});`,
|
|
739
774
|
key,
|
|
740
775
|
expr: local
|
|
741
776
|
};
|
|
742
777
|
}
|
|
743
|
-
instantiate(className, args, kind, functionalInjects) {
|
|
744
|
-
if (!functionalInjects || functionalInjects.length === 0) {
|
|
745
|
-
return `new ${className}(${args})`;
|
|
746
|
-
}
|
|
747
|
-
const clauses = functionalInjects.map((entry) => {
|
|
748
|
-
const token = this.imports.add(entry.expression, entry.importPath, entry.importModule);
|
|
749
|
-
const value = this.depExpr(entry.token, kind, entry);
|
|
750
|
-
return `if (token === ${token}) return ${value} as T;`;
|
|
751
|
-
});
|
|
752
|
-
const missing = `if (options?.optional) return undefined; throw new Error("Static inject token not available: " + String(token));`;
|
|
753
|
-
const injector = [
|
|
754
|
-
`{`,
|
|
755
|
-
`get<T>(token: unknown, options?: { optional?: boolean; self?: boolean; skipSelf?: boolean; host?: boolean }): T | undefined {`,
|
|
756
|
-
...clauses,
|
|
757
|
-
missing,
|
|
758
|
-
`},`,
|
|
759
|
-
`}`
|
|
760
|
-
].join(`
|
|
761
|
-
`);
|
|
762
|
-
return `runInInjectionContext(${injector}, () => new ${className}(${args}))`;
|
|
763
|
-
}
|
|
764
778
|
localVar(token, kind) {
|
|
765
779
|
const locals = this.locals[kind];
|
|
766
780
|
const existing = locals.get(token);
|
|
@@ -1933,6 +1947,7 @@ var HEADER = "// GENERATED BY @supacloud/compiler — do not edit", INTERFACES =
|
|
|
1933
1947
|
title?: string;
|
|
1934
1948
|
data?: Record<string, unknown>;
|
|
1935
1949
|
aspects?: CompiledAspect[];
|
|
1950
|
+
aspectPipeline?: CompiledAspectPipeline;
|
|
1936
1951
|
invoker?: (
|
|
1937
1952
|
controller: unknown,
|
|
1938
1953
|
request: {
|
|
@@ -1956,6 +1971,7 @@ export interface CompiledCommand {
|
|
|
1956
1971
|
idempotency: "required" | "none";
|
|
1957
1972
|
standalone?: boolean;
|
|
1958
1973
|
aspects?: CompiledAspect[];
|
|
1974
|
+
aspectPipeline?: CompiledAspectPipeline;
|
|
1959
1975
|
}
|
|
1960
1976
|
|
|
1961
1977
|
export interface CompiledJob {
|
|
@@ -1970,6 +1986,7 @@ export interface CompiledJob {
|
|
|
1970
1986
|
maxAttempts?: number;
|
|
1971
1987
|
idempotency?: "required" | "none";
|
|
1972
1988
|
aspects?: CompiledAspect[];
|
|
1989
|
+
aspectPipeline?: CompiledAspectPipeline;
|
|
1973
1990
|
}
|
|
1974
1991
|
|
|
1975
1992
|
export interface CompiledAspectContext {
|
|
@@ -2017,7 +2034,24 @@ export interface CompiledModule {
|
|
|
2017
2034
|
commands: CompiledCommand[];
|
|
2018
2035
|
jobs: CompiledJob[];
|
|
2019
2036
|
aspects?: CompiledAspect[];
|
|
2020
|
-
|
|
2037
|
+
aspectPipeline?: CompiledAspectPipeline;
|
|
2038
|
+
}`, TYPE_GUARDS = `type CompiledAspectObserver = (stage: string, run: () => unknown | Promise<unknown>) => unknown | Promise<unknown>;
|
|
2039
|
+
type CompiledAspectPipeline = (context: CompiledAspectContext, next: () => unknown | Promise<unknown>, observe?: CompiledAspectObserver) => unknown | Promise<unknown>;
|
|
2040
|
+
|
|
2041
|
+
function compiledAspectNext(next: () => unknown | Promise<unknown>, state: { active: boolean }): () => Promise<unknown> {
|
|
2042
|
+
let called = false;
|
|
2043
|
+
return async () => {
|
|
2044
|
+
if (!state.active) throw new Error("Aspect continuation is closed");
|
|
2045
|
+
if (called) throw new Error("Aspect continuation called multiple times");
|
|
2046
|
+
called = true;
|
|
2047
|
+
return await next();
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
function observeCompiledAspect(observe: CompiledAspectObserver | undefined, stage: string, run: () => unknown | Promise<unknown>): unknown | Promise<unknown> {
|
|
2051
|
+
return observe ? observe(stage, run) : run();
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
2021
2055
|
return typeof value === "object" && value !== null;
|
|
2022
2056
|
}
|
|
2023
2057
|
|
|
@@ -2199,7 +2233,7 @@ var init_graphql_options = __esm(() => {
|
|
|
2199
2233
|
|
|
2200
2234
|
// src/graphql-inputs.ts
|
|
2201
2235
|
import { resolve as resolve3 } from "node:path";
|
|
2202
|
-
import * as
|
|
2236
|
+
import * as ts7 from "@typescript/typescript6";
|
|
2203
2237
|
function graphqlInputPaths(options) {
|
|
2204
2238
|
if (!options.graphql)
|
|
2205
2239
|
return [];
|
|
@@ -2210,7 +2244,7 @@ function graphqlInputPaths(options) {
|
|
|
2210
2244
|
}
|
|
2211
2245
|
const root = resolve3(options.rootDir);
|
|
2212
2246
|
const schema = resolve3(root, options.graphql.schema);
|
|
2213
|
-
const documents =
|
|
2247
|
+
const documents = ts7.sys.readDirectory(root, [".graphql", ".gql"], ["**/node_modules/**", "**/.git/**", resolve3(options.outDir)], options.graphql.documents ?? ["**/*.graphql", "**/*.gql"]).map((path) => resolve3(path)).filter((path) => path !== schema);
|
|
2214
2248
|
return [schema, ...[...new Set(documents)].sort()];
|
|
2215
2249
|
}
|
|
2216
2250
|
var init_graphql_inputs = __esm(() => {
|
|
@@ -2218,7 +2252,7 @@ var init_graphql_inputs = __esm(() => {
|
|
|
2218
2252
|
});
|
|
2219
2253
|
|
|
2220
2254
|
// src/graphql-runtime.ts
|
|
2221
|
-
import * as
|
|
2255
|
+
import * as ts8 from "@typescript/typescript6";
|
|
2222
2256
|
import { resolve as resolve4 } from "node:path";
|
|
2223
2257
|
function renderGraphqlValidators(source, operationNames) {
|
|
2224
2258
|
const fileName = resolve4("/__supacloud_graphql__/contracts.ts");
|
|
@@ -2230,18 +2264,18 @@ function renderGraphqlValidators(source, operationNames) {
|
|
|
2230
2264
|
noPropertyAccessFromIndexSignature: true,
|
|
2231
2265
|
noFallthroughCasesInSwitch: true,
|
|
2232
2266
|
skipLibCheck: false,
|
|
2233
|
-
target:
|
|
2267
|
+
target: ts8.ScriptTarget.ES2022,
|
|
2234
2268
|
lib: ["lib.es2022.d.ts"],
|
|
2235
2269
|
types: [],
|
|
2236
2270
|
noEmit: true
|
|
2237
2271
|
};
|
|
2238
|
-
const host =
|
|
2272
|
+
const host = ts8.createCompilerHost(options);
|
|
2239
2273
|
const getSourceFile = host.getSourceFile.bind(host);
|
|
2240
|
-
host.getSourceFile = (path, languageVersion, onError, shouldCreateNewSourceFile) => path === fileName ?
|
|
2241
|
-
const program =
|
|
2242
|
-
const diagnostics =
|
|
2274
|
+
host.getSourceFile = (path, languageVersion, onError, shouldCreateNewSourceFile) => path === fileName ? ts8.createSourceFile(path, source, languageVersion, true) : getSourceFile(path, languageVersion, onError, shouldCreateNewSourceFile);
|
|
2275
|
+
const program = ts8.createProgram([fileName], options, host);
|
|
2276
|
+
const diagnostics = ts8.getPreEmitDiagnostics(program);
|
|
2243
2277
|
if (diagnostics.length) {
|
|
2244
|
-
throw new Error(`Generated GraphQL types cannot be validated: ${diagnostics.map((item) =>
|
|
2278
|
+
throw new Error(`Generated GraphQL types cannot be validated: ${diagnostics.map((item) => ts8.flattenDiagnosticMessageText(item.messageText, `
|
|
2245
2279
|
`)).join("; ")}`);
|
|
2246
2280
|
}
|
|
2247
2281
|
const checker = program.getTypeChecker();
|
|
@@ -2271,25 +2305,25 @@ function renderGraphqlValidators(source, operationNames) {
|
|
|
2271
2305
|
return name;
|
|
2272
2306
|
}
|
|
2273
2307
|
function expression(type) {
|
|
2274
|
-
if (type.flags &
|
|
2308
|
+
if (type.flags & ts8.TypeFlags.Any)
|
|
2275
2309
|
return unsupported(type);
|
|
2276
|
-
if (type.flags &
|
|
2310
|
+
if (type.flags & ts8.TypeFlags.Unknown)
|
|
2277
2311
|
return "true";
|
|
2278
|
-
if (type.flags &
|
|
2312
|
+
if (type.flags & ts8.TypeFlags.Never)
|
|
2279
2313
|
return "false";
|
|
2280
|
-
if (type.flags &
|
|
2314
|
+
if (type.flags & ts8.TypeFlags.Null)
|
|
2281
2315
|
return "value === null";
|
|
2282
|
-
if (type.flags &
|
|
2316
|
+
if (type.flags & ts8.TypeFlags.Undefined)
|
|
2283
2317
|
return "value === undefined";
|
|
2284
2318
|
if (type.isStringLiteral() || type.isNumberLiteral())
|
|
2285
2319
|
return `value === ${JSON.stringify(type.value)}`;
|
|
2286
|
-
if (type.flags &
|
|
2320
|
+
if (type.flags & ts8.TypeFlags.BooleanLiteral)
|
|
2287
2321
|
return `value === ${checker.typeToString(type)}`;
|
|
2288
|
-
if (type.flags &
|
|
2322
|
+
if (type.flags & ts8.TypeFlags.String)
|
|
2289
2323
|
return 'typeof value === "string"';
|
|
2290
|
-
if (type.flags &
|
|
2324
|
+
if (type.flags & ts8.TypeFlags.Number)
|
|
2291
2325
|
return 'typeof value === "number" && Number.isFinite(value)';
|
|
2292
|
-
if (type.flags &
|
|
2326
|
+
if (type.flags & ts8.TypeFlags.Boolean)
|
|
2293
2327
|
return 'typeof value === "boolean"';
|
|
2294
2328
|
if (type.isUnion())
|
|
2295
2329
|
return type.types.map((part) => `${reference(part)}(value)`).join(" || ");
|
|
@@ -2298,16 +2332,16 @@ function renderGraphqlValidators(source, operationNames) {
|
|
|
2298
2332
|
if (checker.isTupleType(type))
|
|
2299
2333
|
return unsupported(type);
|
|
2300
2334
|
if (checker.isArrayType(type)) {
|
|
2301
|
-
const item = checker.getIndexTypeOfType(type,
|
|
2335
|
+
const item = checker.getIndexTypeOfType(type, ts8.IndexKind.Number);
|
|
2302
2336
|
if (!item)
|
|
2303
2337
|
return unsupported(type);
|
|
2304
2338
|
return `isGraphqlArray(value) && Array.from(value).every(${reference(item)})`;
|
|
2305
2339
|
}
|
|
2306
|
-
if (type.flags &
|
|
2340
|
+
if (type.flags & ts8.TypeFlags.Object) {
|
|
2307
2341
|
if (type.getCallSignatures().length || type.getConstructSignatures().length)
|
|
2308
2342
|
return unsupported(type);
|
|
2309
2343
|
const indexes = checker.getIndexInfosOfType(type);
|
|
2310
|
-
if (indexes.some((index) => !(index.keyType.flags &
|
|
2344
|
+
if (indexes.some((index) => !(index.keyType.flags & ts8.TypeFlags.String)))
|
|
2311
2345
|
return unsupported(type);
|
|
2312
2346
|
const properties = checker.getPropertiesOfType(type).map((property) => {
|
|
2313
2347
|
const declaration = property.valueDeclaration ?? property.declarations?.[0];
|
|
@@ -2316,7 +2350,7 @@ function renderGraphqlValidators(source, operationNames) {
|
|
|
2316
2350
|
const check = reference(checker.getTypeOfSymbolAtLocation(property, declaration));
|
|
2317
2351
|
const key = JSON.stringify(property.name);
|
|
2318
2352
|
const present = `Object.prototype.hasOwnProperty.call(value, ${key})`;
|
|
2319
|
-
return property.flags &
|
|
2353
|
+
return property.flags & ts8.SymbolFlags.Optional ? `(!${present} || ${check}(value[${key}]))` : `(${present} && ${check}(value[${key}]))`;
|
|
2320
2354
|
});
|
|
2321
2355
|
const indexedValues = indexes.map((index) => `Object.values(value).every(${reference(index.type)})`);
|
|
2322
2356
|
return ["isGraphqlRecord(value)", ...properties, ...indexedValues].join(" && ");
|
|
@@ -2574,10 +2608,155 @@ var init_graphql = __esm(() => {
|
|
|
2574
2608
|
init_graphql_runtime();
|
|
2575
2609
|
});
|
|
2576
2610
|
|
|
2577
|
-
// src/
|
|
2578
|
-
import { mkdir as mkdir5, readFile as readFile11 } from "node:fs/promises";
|
|
2611
|
+
// src/database-contracts.ts
|
|
2579
2612
|
import { createHash as createHash9 } from "node:crypto";
|
|
2580
|
-
import {
|
|
2613
|
+
import { mkdir as mkdir5, readFile as readFile11 } from "node:fs/promises";
|
|
2614
|
+
import { dirname as dirname10, relative as relative12, resolve as resolve15 } from "node:path";
|
|
2615
|
+
import * as ts13 from "@typescript/typescript6";
|
|
2616
|
+
function hash2(value) {
|
|
2617
|
+
return createHash9("sha256").update(value).digest("hex");
|
|
2618
|
+
}
|
|
2619
|
+
function importPath(out, path) {
|
|
2620
|
+
const value = relative12(out, path).replaceAll("\\", "/").replace(/\.(?:d\.)?[cm]?ts$/, "");
|
|
2621
|
+
return value.startsWith(".") ? value : `./${value}`;
|
|
2622
|
+
}
|
|
2623
|
+
function parseDatabaseContractsOptions(value, directory) {
|
|
2624
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
2625
|
+
throw new TypeError("Expected database contracts configuration");
|
|
2626
|
+
const allowed = ["rootDir", "outDir", "postgrestTypes", "drizzleSchema", "role", "graphql", "migrations"];
|
|
2627
|
+
if (Object.keys(value).some((name) => !allowed.includes(name)))
|
|
2628
|
+
throw new TypeError("Unknown database contracts option");
|
|
2629
|
+
const field = (name) => {
|
|
2630
|
+
const result = Reflect.get(value, name);
|
|
2631
|
+
if (typeof result !== "string" || !result.trim())
|
|
2632
|
+
throw new TypeError(`Missing database contracts ${name}`);
|
|
2633
|
+
return result;
|
|
2634
|
+
};
|
|
2635
|
+
const graphql = Reflect.get(value, "graphql");
|
|
2636
|
+
assertGraphqlOptions(graphql);
|
|
2637
|
+
const migrations = Reflect.get(value, "migrations");
|
|
2638
|
+
if (!Array.isArray(migrations) || !migrations.every((entry) => typeof entry === "string" && entry.endsWith(".sql"))) {
|
|
2639
|
+
throw new TypeError("migrations must be an ordered list of SQL files");
|
|
2640
|
+
}
|
|
2641
|
+
return {
|
|
2642
|
+
rootDir: resolve15(directory, field("rootDir")),
|
|
2643
|
+
outDir: resolve15(directory, field("outDir")),
|
|
2644
|
+
postgrestTypes: resolve15(directory, field("postgrestTypes")),
|
|
2645
|
+
drizzleSchema: resolve15(directory, field("drizzleSchema")),
|
|
2646
|
+
role: field("role"),
|
|
2647
|
+
graphql: { ...graphql, schema: resolve15(directory, graphql.schema) },
|
|
2648
|
+
migrations: migrations.map((file) => resolve15(directory, file))
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
async function generateDatabaseContracts(options, check = false) {
|
|
2652
|
+
const rootDir = resolve15(options.rootDir), outDir = resolve15(options.outDir);
|
|
2653
|
+
const postgrestTypes = resolve15(rootDir, options.postgrestTypes), drizzleSchema = resolve15(rootDir, options.drizzleSchema);
|
|
2654
|
+
const snapshot = await readFile11(postgrestTypes, "utf8");
|
|
2655
|
+
const syntax = ts13.createSourceFile(postgrestTypes, snapshot, ts13.ScriptTarget.Latest, true);
|
|
2656
|
+
const database = syntax.statements.find((node) => (ts13.isTypeAliasDeclaration(node) || ts13.isInterfaceDeclaration(node)) && node.name.text === "Database" && node.modifiers?.some((modifier) => modifier.kind === ts13.SyntaxKind.ExportKeyword));
|
|
2657
|
+
if (!database)
|
|
2658
|
+
throw new Error("PostgREST snapshot must export Database from the official type generator");
|
|
2659
|
+
const program = ts13.createProgram([postgrestTypes], {
|
|
2660
|
+
strict: true,
|
|
2661
|
+
noEmit: true,
|
|
2662
|
+
skipLibCheck: true,
|
|
2663
|
+
types: [],
|
|
2664
|
+
target: ts13.ScriptTarget.ES2022,
|
|
2665
|
+
module: ts13.ModuleKind.ESNext,
|
|
2666
|
+
moduleResolution: ts13.ModuleResolutionKind.Bundler
|
|
2667
|
+
});
|
|
2668
|
+
if (ts13.getPreEmitDiagnostics(program).length)
|
|
2669
|
+
throw new Error("PostgREST snapshot has TypeScript errors");
|
|
2670
|
+
const artifacts = await renderGraphql({
|
|
2671
|
+
rootDir,
|
|
2672
|
+
outDir,
|
|
2673
|
+
graphql: options.graphql
|
|
2674
|
+
});
|
|
2675
|
+
if (artifacts.diagnostics.some((item) => item.severity === "error")) {
|
|
2676
|
+
throw new Error(artifacts.diagnostics.map((item) => item.message).join(`
|
|
2677
|
+
`));
|
|
2678
|
+
}
|
|
2679
|
+
const inputs = {};
|
|
2680
|
+
const addInput = async (path) => {
|
|
2681
|
+
const absolute = resolve15(rootDir, path);
|
|
2682
|
+
inputs[relative12(rootDir, absolute).replaceAll("\\", "/")] = hash2(await readFile11(absolute, "utf8"));
|
|
2683
|
+
};
|
|
2684
|
+
await addInput(postgrestTypes);
|
|
2685
|
+
await addInput(drizzleSchema);
|
|
2686
|
+
const drizzleProgram = ts13.createProgram([drizzleSchema], {
|
|
2687
|
+
noEmit: true,
|
|
2688
|
+
moduleResolution: ts13.ModuleResolutionKind.Bundler,
|
|
2689
|
+
module: ts13.ModuleKind.ESNext,
|
|
2690
|
+
target: ts13.ScriptTarget.ES2022,
|
|
2691
|
+
types: [],
|
|
2692
|
+
skipLibCheck: true
|
|
2693
|
+
});
|
|
2694
|
+
for (const source of drizzleProgram.getSourceFiles()) {
|
|
2695
|
+
if (!source.isDeclarationFile && !source.fileName.includes("/node_modules/"))
|
|
2696
|
+
await addInput(source.fileName);
|
|
2697
|
+
}
|
|
2698
|
+
await addInput(resolve15(rootDir, options.graphql.schema));
|
|
2699
|
+
if (new Set(options.migrations.map((path) => resolve15(rootDir, path))).size !== options.migrations.length) {
|
|
2700
|
+
throw new Error("Duplicate migration in database contracts configuration");
|
|
2701
|
+
}
|
|
2702
|
+
for (const path of options.migrations)
|
|
2703
|
+
await addInput(path);
|
|
2704
|
+
const files = {
|
|
2705
|
+
...artifacts.files,
|
|
2706
|
+
"database.ts": [
|
|
2707
|
+
"// GENERATED BY @supacloud/compiler database-contracts. Do not edit.",
|
|
2708
|
+
`export type { Database } from ${JSON.stringify(importPath(outDir, postgrestTypes))};`,
|
|
2709
|
+
'export type { QueryData, QueryResult, QueryError } from "@supabase/supabase-js";',
|
|
2710
|
+
`export type DrizzleSchema = typeof import(${JSON.stringify(importPath(outDir, drizzleSchema))});`,
|
|
2711
|
+
'export * from "./graphql";',
|
|
2712
|
+
""
|
|
2713
|
+
].join(`
|
|
2714
|
+
`)
|
|
2715
|
+
};
|
|
2716
|
+
const manifest = {
|
|
2717
|
+
version: 1,
|
|
2718
|
+
role: options.role,
|
|
2719
|
+
inputs: Object.fromEntries(Object.entries(inputs).sort(([a], [b]) => a.localeCompare(b))),
|
|
2720
|
+
migrationOrder: options.migrations.map((path) => relative12(rootDir, resolve15(rootDir, path)).replaceAll("\\", "/")),
|
|
2721
|
+
outputs: Object.fromEntries(Object.entries(files).sort(([a], [b]) => a.localeCompare(b)).map(([file, text]) => [file, hash2(text)]))
|
|
2722
|
+
};
|
|
2723
|
+
files["database.manifest.json"] = JSON.stringify(manifest, null, 2) + `
|
|
2724
|
+
`;
|
|
2725
|
+
const mismatches = [];
|
|
2726
|
+
for (const [file, content] of Object.entries(files)) {
|
|
2727
|
+
const path = resolve15(outDir, file);
|
|
2728
|
+
let current;
|
|
2729
|
+
try {
|
|
2730
|
+
current = await readFile11(path, "utf8");
|
|
2731
|
+
} catch (error) {
|
|
2732
|
+
if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
|
|
2733
|
+
throw error;
|
|
2734
|
+
}
|
|
2735
|
+
if (current !== content)
|
|
2736
|
+
mismatches.push(file);
|
|
2737
|
+
}
|
|
2738
|
+
if (!check) {
|
|
2739
|
+
await mkdir5(outDir, { recursive: true });
|
|
2740
|
+
for (const [file, content] of Object.entries(files))
|
|
2741
|
+
await writeFileIfChanged(resolve15(outDir, file), content);
|
|
2742
|
+
}
|
|
2743
|
+
return { upToDate: mismatches.length === 0, mismatches, written: check ? [] : mismatches, manifest };
|
|
2744
|
+
}
|
|
2745
|
+
async function runDatabaseContractsFile(path, check = false) {
|
|
2746
|
+
const absolute = resolve15(path);
|
|
2747
|
+
const value = JSON.parse(await readFile11(absolute, "utf8"));
|
|
2748
|
+
return generateDatabaseContracts(parseDatabaseContractsOptions(value, dirname10(absolute)), check);
|
|
2749
|
+
}
|
|
2750
|
+
var init_database_contracts = __esm(() => {
|
|
2751
|
+
init_graphql();
|
|
2752
|
+
init_generate();
|
|
2753
|
+
init_graphql_options();
|
|
2754
|
+
});
|
|
2755
|
+
|
|
2756
|
+
// src/graphql-schema.ts
|
|
2757
|
+
import { mkdir as mkdir6, readFile as readFile12 } from "node:fs/promises";
|
|
2758
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
2759
|
+
import { dirname as dirname11, resolve as resolve16 } from "node:path";
|
|
2581
2760
|
async function pullGraphqlSchema(options) {
|
|
2582
2761
|
assertGraphqlOptions({ schema: options.output });
|
|
2583
2762
|
const endpoint = new URL(options.url);
|
|
@@ -2617,7 +2796,7 @@ async function pullGraphqlSchema(options) {
|
|
|
2617
2796
|
throw new Error("GraphQL schema export failed. Verify caller grants and enable introspection only in the intended development environment.");
|
|
2618
2797
|
}
|
|
2619
2798
|
const schema = lexicographicSortSchema(buildClientSchema(data));
|
|
2620
|
-
const path =
|
|
2799
|
+
const path = resolve16(options.output);
|
|
2621
2800
|
const content = path.endsWith(".json") ? JSON.stringify(introspectionFromSchema(schema), null, 2) + `
|
|
2622
2801
|
` : `# GENERATED BY supacloud-compiler graphql-schema. DO NOT EDIT.
|
|
2623
2802
|
# Database First: change database declarations, apply migrations, then re-export for the intended role.
|
|
@@ -2625,16 +2804,16 @@ async function pullGraphqlSchema(options) {
|
|
|
2625
2804
|
`;
|
|
2626
2805
|
let previous;
|
|
2627
2806
|
try {
|
|
2628
|
-
previous = await
|
|
2807
|
+
previous = await readFile12(path, "utf8");
|
|
2629
2808
|
} catch (error) {
|
|
2630
2809
|
if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
|
|
2631
2810
|
throw error;
|
|
2632
2811
|
}
|
|
2633
2812
|
const upToDate = previous === content;
|
|
2634
|
-
const schemaHash =
|
|
2813
|
+
const schemaHash = createHash10("sha256").update(content).digest("hex");
|
|
2635
2814
|
if (options.check)
|
|
2636
2815
|
return { path, schemaHash, upToDate, written: false };
|
|
2637
|
-
await
|
|
2816
|
+
await mkdir6(dirname11(path), { recursive: true });
|
|
2638
2817
|
await writeFileIfChanged(path, content);
|
|
2639
2818
|
return { path, schemaHash, upToDate: true, written: true };
|
|
2640
2819
|
}
|
|
@@ -2644,13 +2823,13 @@ var init_graphql_schema = __esm(() => {
|
|
|
2644
2823
|
});
|
|
2645
2824
|
|
|
2646
2825
|
// src/cli.ts
|
|
2647
|
-
import { resolve as
|
|
2648
|
-
import { readFile as
|
|
2826
|
+
import { resolve as resolve17 } from "node:path";
|
|
2827
|
+
import { readFile as readFile13 } from "node:fs/promises";
|
|
2649
2828
|
|
|
2650
2829
|
// src/analyze.ts
|
|
2651
2830
|
import { createHash as createHash3 } from "node:crypto";
|
|
2652
2831
|
import { relative as relative2, resolve as resolvePath, sep as sep2 } from "node:path";
|
|
2653
|
-
import * as
|
|
2832
|
+
import * as ts4 from "@typescript/typescript6";
|
|
2654
2833
|
|
|
2655
2834
|
// src/program.ts
|
|
2656
2835
|
import { createHash as createHash2 } from "node:crypto";
|
|
@@ -3300,6 +3479,7 @@ var COMPILER_DIAGNOSTIC_CODES = {
|
|
|
3300
3479
|
"missing-token-factory": { code: "SC2009", docsUrl: "https://supacloud.dev/errors/SC2009" },
|
|
3301
3480
|
"provider-type-mismatch": { code: "SC2010", docsUrl: "https://supacloud.dev/errors/SC2010" },
|
|
3302
3481
|
"unsupported-provider-helper": { code: "SC2011", docsUrl: "https://supacloud.dev/errors/SC2011" },
|
|
3482
|
+
"runtime-injection-disallowed": { code: "SC2012", docsUrl: "https://supacloud.dev/errors/SC2012" },
|
|
3303
3483
|
"command-missing-permission": { code: "SC4001", docsUrl: "https://supacloud.dev/errors/SC4001" },
|
|
3304
3484
|
"duplicate-command": { code: "SC4002", docsUrl: "https://supacloud.dev/errors/SC4002" },
|
|
3305
3485
|
"route-command-unresolved": { code: "SC4003", docsUrl: "https://supacloud.dev/errors/SC4003" },
|
|
@@ -3346,6 +3526,20 @@ var COMPILER_DIAGNOSTIC_CODES = {
|
|
|
3346
3526
|
function validateGraph(graph, options = false) {
|
|
3347
3527
|
const strict = typeof options === "boolean" ? options : options.strict ?? false;
|
|
3348
3528
|
const diagnostics = [];
|
|
3529
|
+
for (const module of graph.modules) {
|
|
3530
|
+
for (const owner of [...module.providers, ...module.controllers]) {
|
|
3531
|
+
if (owner.functionalInjects?.length)
|
|
3532
|
+
diagnostics.push({
|
|
3533
|
+
severity: "error",
|
|
3534
|
+
code: "runtime-injection-disallowed",
|
|
3535
|
+
errorCode: "SC2012",
|
|
3536
|
+
docsUrl: "https://supacloud.dev/errors/SC2012",
|
|
3537
|
+
file: owner.file,
|
|
3538
|
+
message: "Property inject() requires runtime token resolution. Compiled applications require constructor injection.",
|
|
3539
|
+
suggestion: "Move injected fields into typed constructor parameters with @Inject(TOKEN) where needed."
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3349
3543
|
let moduleBoundaries;
|
|
3350
3544
|
if (typeof options === "object") {
|
|
3351
3545
|
try {
|
|
@@ -3773,7 +3967,7 @@ function validateGraph(graph, options = false) {
|
|
|
3773
3967
|
if (typeof options === "object" && options.commandCapabilities) {
|
|
3774
3968
|
const hostCaps = options.commandCapabilities;
|
|
3775
3969
|
const rpcCaps = command.rpc && Object.hasOwn(hostCaps.rpc ?? {}, command.rpc) ? hostCaps.rpc?.[command.rpc] : undefined;
|
|
3776
|
-
if (hostCaps.requirePersistentAdapters && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true || hostCaps.permission !== true || !command.permission || !command.audit || command.idempotency !== "required" || rpcCaps
|
|
3970
|
+
if (hostCaps.requirePersistentAdapters && (command.rpc !== undefined && (!rpcCaps?.boundary || rpcCaps.audit !== true || rpcCaps.idempotency !== true) || hostCaps.permission !== true || !command.permission || !command.audit || command.idempotency !== "required" || command.rpc !== undefined && rpcCaps?.boundary === "database" && (rpcCaps.transaction !== true || command.transaction !== "required") || command.rpc === undefined && (hostCaps.audit !== true || hostCaps.idempotency !== true || hostCaps.transaction !== true || command.transaction !== "required"))) {
|
|
3777
3971
|
error("command-persistence-required", `Command ${command.name} requires an explicit persistent adapter, permission, audit and idempotency policy.`, module.file, module.line, "Register a named database/external adapter, enable permission checks and declare permission, audit and required idempotency on the command.");
|
|
3778
3972
|
}
|
|
3779
3973
|
if (rpcCaps?.boundary === "external" && (command.transaction === "required" || rpcCaps.transaction === true)) {
|
|
@@ -4126,6 +4320,67 @@ function detectOrphanModules(graph) {
|
|
|
4126
4320
|
}
|
|
4127
4321
|
return diagnostics;
|
|
4128
4322
|
}
|
|
4323
|
+
// src/static-di.ts
|
|
4324
|
+
import * as ts3 from "@typescript/typescript6";
|
|
4325
|
+
var runtimeApis = new Set([
|
|
4326
|
+
"inject",
|
|
4327
|
+
"createEnvironmentInjector",
|
|
4328
|
+
"runInInjectionContext",
|
|
4329
|
+
"EnvironmentInjector",
|
|
4330
|
+
"bootstrapBun",
|
|
4331
|
+
"runInScope",
|
|
4332
|
+
"runInRequestContext",
|
|
4333
|
+
"runInJobContext",
|
|
4334
|
+
"runInTransactionContext"
|
|
4335
|
+
]);
|
|
4336
|
+
function scanRuntimeDi(source, file) {
|
|
4337
|
+
const diagnostics = [];
|
|
4338
|
+
const namespaces = new Set;
|
|
4339
|
+
const report = (node) => diagnostics.push({
|
|
4340
|
+
severity: "error",
|
|
4341
|
+
code: "runtime-injection-disallowed",
|
|
4342
|
+
errorCode: "SC2012",
|
|
4343
|
+
docsUrl: "https://supacloud.dev/errors/SC2012",
|
|
4344
|
+
file,
|
|
4345
|
+
line: source.getLineAndCharacterOfPosition(node.getStart()).line + 1,
|
|
4346
|
+
message: "Compiled applications cannot import runtime DI. Use explicit constructors and generated scope factories."
|
|
4347
|
+
});
|
|
4348
|
+
for (const statement of source.statements) {
|
|
4349
|
+
if (!(ts3.isImportDeclaration(statement) || ts3.isExportDeclaration(statement)) || !statement.moduleSpecifier || !ts3.isStringLiteral(statement.moduleSpecifier) || !/^@supacloud\/app(?:\/|$)/.test(statement.moduleSpecifier.text))
|
|
4350
|
+
continue;
|
|
4351
|
+
if (ts3.isImportDeclaration(statement)) {
|
|
4352
|
+
if (statement.importClause?.isTypeOnly)
|
|
4353
|
+
continue;
|
|
4354
|
+
const binding = statement.importClause?.namedBindings;
|
|
4355
|
+
if (binding && ts3.isNamespaceImport(binding))
|
|
4356
|
+
namespaces.add(binding.name.text);
|
|
4357
|
+
if (binding && ts3.isNamedImports(binding))
|
|
4358
|
+
for (const item of binding.elements) {
|
|
4359
|
+
if (!item.isTypeOnly && runtimeApis.has((item.propertyName ?? item.name).text))
|
|
4360
|
+
report(item);
|
|
4361
|
+
}
|
|
4362
|
+
} else if (!statement.isTypeOnly) {
|
|
4363
|
+
if (!statement.exportClause)
|
|
4364
|
+
report(statement);
|
|
4365
|
+
else if (ts3.isNamedExports(statement.exportClause))
|
|
4366
|
+
for (const item of statement.exportClause.elements) {
|
|
4367
|
+
if (!item.isTypeOnly && runtimeApis.has((item.propertyName ?? item.name).text))
|
|
4368
|
+
report(item);
|
|
4369
|
+
}
|
|
4370
|
+
}
|
|
4371
|
+
}
|
|
4372
|
+
const visit = (node) => {
|
|
4373
|
+
if (ts3.isPropertyAccessExpression(node) && ts3.isIdentifier(node.expression) && namespaces.has(node.expression.text) && runtimeApis.has(node.name.text))
|
|
4374
|
+
report(node);
|
|
4375
|
+
if (ts3.isElementAccessExpression(node) && ts3.isIdentifier(node.expression) && namespaces.has(node.expression.text) && (!ts3.isStringLiteral(node.argumentExpression) || runtimeApis.has(node.argumentExpression.text)))
|
|
4376
|
+
report(node);
|
|
4377
|
+
if (ts3.isVariableDeclaration(node) && node.initializer && ts3.isIdentifier(node.initializer) && namespaces.has(node.initializer.text))
|
|
4378
|
+
report(node);
|
|
4379
|
+
ts3.forEachChild(node, visit);
|
|
4380
|
+
};
|
|
4381
|
+
visit(source);
|
|
4382
|
+
return diagnostics;
|
|
4383
|
+
}
|
|
4129
4384
|
|
|
4130
4385
|
// src/analyze.ts
|
|
4131
4386
|
var DEFAULT_INCLUDE = ["**/*.module.ts", "**/*.ts"];
|
|
@@ -4167,26 +4422,26 @@ function lineOf(node) {
|
|
|
4167
4422
|
return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
4168
4423
|
}
|
|
4169
4424
|
function variableName(decl) {
|
|
4170
|
-
return
|
|
4425
|
+
return ts4.isIdentifier(decl.name) ? decl.name.text : nodeText(decl.name);
|
|
4171
4426
|
}
|
|
4172
4427
|
function propertyName(name) {
|
|
4173
|
-
if (
|
|
4428
|
+
if (ts4.isIdentifier(name) || ts4.isPrivateIdentifier(name))
|
|
4174
4429
|
return name.text;
|
|
4175
|
-
if (
|
|
4430
|
+
if (ts4.isStringLiteral(name) || ts4.isNumericLiteral(name))
|
|
4176
4431
|
return name.text;
|
|
4177
4432
|
return nodeText(name);
|
|
4178
4433
|
}
|
|
4179
4434
|
function parameterName(param) {
|
|
4180
|
-
return
|
|
4435
|
+
return ts4.isIdentifier(param.name) ? param.name.text : nodeText(param.name);
|
|
4181
4436
|
}
|
|
4182
4437
|
function decoratorsOf(node) {
|
|
4183
|
-
return
|
|
4438
|
+
return ts4.canHaveDecorators(node) ? ts4.getDecorators(node) ?? [] : [];
|
|
4184
4439
|
}
|
|
4185
4440
|
function decoratorArguments(dec) {
|
|
4186
|
-
return
|
|
4441
|
+
return ts4.isCallExpression(dec.expression) ? dec.expression.arguments : [];
|
|
4187
4442
|
}
|
|
4188
4443
|
function hasMethod(cls, name) {
|
|
4189
|
-
return cls.members.some((member) => (
|
|
4444
|
+
return cls.members.some((member) => (ts4.isMethodDeclaration(member) || ts4.isGetAccessorDeclaration(member) || ts4.isSetAccessorDeclaration(member)) && member.name !== undefined && propertyName(member.name) === name);
|
|
4190
4445
|
}
|
|
4191
4446
|
function hasDestroyHook(cls) {
|
|
4192
4447
|
return hasMethod(cls, "onDestroy") || hasMethod(cls, "ngOnDestroy");
|
|
@@ -4196,7 +4451,7 @@ function descendantsOfKind(root, predicate) {
|
|
|
4196
4451
|
const visit = (node) => {
|
|
4197
4452
|
if (predicate(node))
|
|
4198
4453
|
result.push(node);
|
|
4199
|
-
|
|
4454
|
+
ts4.forEachChild(node, visit);
|
|
4200
4455
|
};
|
|
4201
4456
|
visit(root);
|
|
4202
4457
|
return result;
|
|
@@ -4205,7 +4460,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4205
4460
|
const session = cache?.programSession ?? createIncrementalProgramSession(rootDir);
|
|
4206
4461
|
if (cache)
|
|
4207
4462
|
cache.programSession = session;
|
|
4208
|
-
const rootNames =
|
|
4463
|
+
const rootNames = ts4.sys.readDirectory(rootDir, [".ts", ".tsx"], ["node_modules", "dist"], include ?? DEFAULT_INCLUDE);
|
|
4209
4464
|
const update = session.update(rootNames, changedPaths);
|
|
4210
4465
|
const program = update.program;
|
|
4211
4466
|
const checker = program.getTypeChecker();
|
|
@@ -4229,13 +4484,16 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4229
4484
|
nativeTraitFiles.set(trait.file, kinds);
|
|
4230
4485
|
}
|
|
4231
4486
|
for (const sf of sourceFiles) {
|
|
4487
|
+
if (!/\.(?:test|spec)\.[cm]?tsx?$/.test(sf.fileName)) {
|
|
4488
|
+
ctx.diagnostics.push(...scanRuntimeDi(sf, sourcePath(rootDir, sf.fileName)));
|
|
4489
|
+
}
|
|
4232
4490
|
indexFile(sf, ctx);
|
|
4233
4491
|
}
|
|
4234
4492
|
const candidates = [];
|
|
4235
4493
|
for (const sf of sourceFiles) {
|
|
4236
4494
|
const traits = nativeTraitFiles.get(sf.fileName);
|
|
4237
4495
|
if (!cache || traits?.has("module")) {
|
|
4238
|
-
for (const cls of sf.statements.filter(
|
|
4496
|
+
for (const cls of sf.statements.filter(ts4.isClassDeclaration)) {
|
|
4239
4497
|
const moduleDec = findDecorator(cls, "Module");
|
|
4240
4498
|
if (!moduleDec)
|
|
4241
4499
|
continue;
|
|
@@ -4252,14 +4510,14 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4252
4510
|
}
|
|
4253
4511
|
}
|
|
4254
4512
|
if (!cache || traits?.has("defineModule") || traits?.has("defineFeatureSlice")) {
|
|
4255
|
-
for (const call of descendantsOfKind(sf,
|
|
4513
|
+
for (const call of descendantsOfKind(sf, ts4.isCallExpression)) {
|
|
4256
4514
|
if (!["defineModule", "defineFeatureSlice"].includes(nodeText(call.expression)))
|
|
4257
4515
|
continue;
|
|
4258
4516
|
const parent = call.parent;
|
|
4259
|
-
if (!parent || !
|
|
4517
|
+
if (!parent || !ts4.isVariableDeclaration(parent))
|
|
4260
4518
|
continue;
|
|
4261
4519
|
const arg = call.arguments[0];
|
|
4262
|
-
if (!arg || !
|
|
4520
|
+
if (!arg || !ts4.isObjectLiteralExpression(arg))
|
|
4263
4521
|
continue;
|
|
4264
4522
|
candidates.push({
|
|
4265
4523
|
node: parent,
|
|
@@ -4403,7 +4661,7 @@ async function analyzeProject(rootDir, include, cache, changedPaths) {
|
|
|
4403
4661
|
const controllerDec = findDecorator(classInfo.decl, "Controller");
|
|
4404
4662
|
if (controllerDec) {
|
|
4405
4663
|
const arg = decoratorArguments(controllerDec)[0];
|
|
4406
|
-
const isStandalone = arg &&
|
|
4664
|
+
const isStandalone = arg && ts4.isObjectLiteralExpression(arg) && booleanProp(arg, "standalone");
|
|
4407
4665
|
if (isStandalone) {
|
|
4408
4666
|
const ctrl = parseController(classInfo.decl, ctx);
|
|
4409
4667
|
if (ctrl)
|
|
@@ -4528,16 +4786,16 @@ function collectModuleSourceClosure(module, ctx) {
|
|
|
4528
4786
|
ownedFiles.add(relativeFile);
|
|
4529
4787
|
for (const statement of sourceFile.statements) {
|
|
4530
4788
|
let moduleName;
|
|
4531
|
-
if (
|
|
4789
|
+
if (ts4.isImportDeclaration(statement) && ts4.isStringLiteral(statement.moduleSpecifier)) {
|
|
4532
4790
|
moduleName = statement.moduleSpecifier.text;
|
|
4533
|
-
} else if (
|
|
4791
|
+
} else if (ts4.isExportDeclaration(statement) && statement.moduleSpecifier && ts4.isStringLiteral(statement.moduleSpecifier)) {
|
|
4534
4792
|
moduleName = statement.moduleSpecifier.text;
|
|
4535
|
-
} else if (
|
|
4793
|
+
} else if (ts4.isImportEqualsDeclaration(statement) && ts4.isExternalModuleReference(statement.moduleReference) && ts4.isStringLiteral(statement.moduleReference.expression)) {
|
|
4536
4794
|
moduleName = statement.moduleReference.expression.text;
|
|
4537
4795
|
}
|
|
4538
4796
|
if (!moduleName || moduleName.startsWith("node:"))
|
|
4539
4797
|
continue;
|
|
4540
|
-
const resolved =
|
|
4798
|
+
const resolved = ts4.resolveModuleName(moduleName, sourceFile.fileName, ctx.program.getCompilerOptions(), ts4.sys).resolvedModule?.resolvedFileName;
|
|
4541
4799
|
if (resolved && isProjectSourcePath(resolved, ctx.rootDir) && !enqueued.has(resolved)) {
|
|
4542
4800
|
enqueued.add(resolved);
|
|
4543
4801
|
queue.push(resolved);
|
|
@@ -4559,15 +4817,15 @@ function isProjectSourceFile(sourceFile, rootDir) {
|
|
|
4559
4817
|
return isProjectSourcePath(sourceFile.fileName, rootDir) && /\.(tsx?|mts|cts)$/.test(sourceFile.fileName);
|
|
4560
4818
|
}
|
|
4561
4819
|
function indexFile(sf, ctx) {
|
|
4562
|
-
for (const cls of sf.statements.filter(
|
|
4820
|
+
for (const cls of sf.statements.filter(ts4.isClassDeclaration)) {
|
|
4563
4821
|
const name = cls.name?.text;
|
|
4564
4822
|
if (name && !ctx.classesByName.has(name)) {
|
|
4565
4823
|
ctx.classesByName.set(name, { name, decl: cls, file: sf.fileName });
|
|
4566
4824
|
}
|
|
4567
4825
|
}
|
|
4568
|
-
for (const statement of sf.statements.filter(
|
|
4826
|
+
for (const statement of sf.statements.filter(ts4.isVariableStatement)) {
|
|
4569
4827
|
for (const decl of statement.declarationList.declarations) {
|
|
4570
|
-
if (
|
|
4828
|
+
if (ts4.isIdentifier(decl.name) && !ctx.variablesByName.has(decl.name.text)) {
|
|
4571
4829
|
ctx.variablesByName.set(decl.name.text, decl);
|
|
4572
4830
|
}
|
|
4573
4831
|
const info = parseTokenVariable(decl, sf.fileName);
|
|
@@ -4579,16 +4837,16 @@ function indexFile(sf, ctx) {
|
|
|
4579
4837
|
}
|
|
4580
4838
|
function parseTokenVariable(decl, file) {
|
|
4581
4839
|
const init = decl.initializer;
|
|
4582
|
-
if (!init || !
|
|
4840
|
+
if (!init || !ts4.isNewExpression(init))
|
|
4583
4841
|
return;
|
|
4584
4842
|
if (nodeText(init.expression) !== "InjectionToken")
|
|
4585
4843
|
return;
|
|
4586
4844
|
const [nameArg, optionsArg] = init.arguments ?? [];
|
|
4587
4845
|
const info = { name: variableName(decl), file, line: lineOf(decl) };
|
|
4588
|
-
if (nameArg &&
|
|
4846
|
+
if (nameArg && ts4.isStringLiteral(nameArg)) {
|
|
4589
4847
|
info.stringName = nameArg.text;
|
|
4590
4848
|
}
|
|
4591
|
-
if (optionsArg &&
|
|
4849
|
+
if (optionsArg && ts4.isObjectLiteralExpression(optionsArg)) {
|
|
4592
4850
|
const scope = stringLiteralProp(optionsArg, "scope");
|
|
4593
4851
|
if (scope && isScope(scope)) {
|
|
4594
4852
|
info.scope = scope;
|
|
@@ -4608,22 +4866,22 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
4608
4866
|
const { options, className, file, line } = candidate;
|
|
4609
4867
|
const name = nameByNode.get(candidate.node) ?? className;
|
|
4610
4868
|
const featureSpec = parseFeatureSpec(getProp(options, "spec"), ctx);
|
|
4611
|
-
const tags = arrayProp(options, "tags").map((el) =>
|
|
4869
|
+
const tags = arrayProp(options, "tags").map((el) => ts4.isStringLiteral(el) ? el.text : nodeText(el).replace(/['"]/g, "")).filter(Boolean);
|
|
4612
4870
|
const aspects = parseAspectRefs(getProp(options, "aspects"), ctx, `module ${name}`);
|
|
4613
4871
|
const imports = arrayProp(options, "imports").map((el) => {
|
|
4614
4872
|
const unwrapped = unwrapForwardRef(el);
|
|
4615
|
-
const decl =
|
|
4873
|
+
const decl = ts4.isIdentifier(unwrapped) ? resolveDeclaration(unwrapped, ctx)[0] : undefined;
|
|
4616
4874
|
if (decl) {
|
|
4617
4875
|
const known = nameByNode.get(decl);
|
|
4618
4876
|
if (known)
|
|
4619
4877
|
return known;
|
|
4620
|
-
if (
|
|
4878
|
+
if (ts4.isClassDeclaration(decl)) {
|
|
4621
4879
|
const dec = findDecorator(decl, "Module");
|
|
4622
4880
|
const decOptions = dec && decoratorObjectArg(dec);
|
|
4623
4881
|
const decName = decOptions && stringLiteralProp(decOptions, "name");
|
|
4624
4882
|
return decName ?? decl.name?.text ?? nodeText(el);
|
|
4625
4883
|
}
|
|
4626
|
-
if (
|
|
4884
|
+
if (ts4.isVariableDeclaration(decl))
|
|
4627
4885
|
return variableName(decl);
|
|
4628
4886
|
}
|
|
4629
4887
|
return nodeText(el);
|
|
@@ -4637,7 +4895,7 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
4637
4895
|
providers.push(...parsedProviders);
|
|
4638
4896
|
continue;
|
|
4639
4897
|
}
|
|
4640
|
-
if (
|
|
4898
|
+
if (ts4.isCallExpression(el)) {
|
|
4641
4899
|
const helper = nodeText(el.expression).split(".").pop() ?? nodeText(el.expression);
|
|
4642
4900
|
warn(ctx, "unsupported-provider-helper", `无法静态展开 provider helper '${helper}';请改用显式 Provider 或实现编译器支持的 helper`, sourcePath(ctx.rootDir, el.getSourceFile().fileName), lineOf(el));
|
|
4643
4901
|
continue;
|
|
@@ -4647,10 +4905,10 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
4647
4905
|
providers.push(provider);
|
|
4648
4906
|
}
|
|
4649
4907
|
for (const el of arrayProp(options, "jobs")) {
|
|
4650
|
-
if (!
|
|
4908
|
+
if (!ts4.isIdentifier(el))
|
|
4651
4909
|
continue;
|
|
4652
4910
|
const decl = resolveDeclaration(el, ctx)[0];
|
|
4653
|
-
if (!decl || !
|
|
4911
|
+
if (!decl || !ts4.isClassDeclaration(decl))
|
|
4654
4912
|
continue;
|
|
4655
4913
|
const className = decl.name?.text ?? el.text;
|
|
4656
4914
|
const registeredProvider = providers.find((provider) => provider.token === className || provider.useClass === className);
|
|
@@ -4687,18 +4945,18 @@ function parseModule(candidate, nameByNode, ctx) {
|
|
|
4687
4945
|
const handlerClasses = [];
|
|
4688
4946
|
const seenHandlers = new Set;
|
|
4689
4947
|
const collectHandler = (expr) => {
|
|
4690
|
-
if (!
|
|
4948
|
+
if (!ts4.isIdentifier(expr))
|
|
4691
4949
|
return;
|
|
4692
4950
|
const decl = resolveDeclaration(expr, ctx)[0];
|
|
4693
|
-
if (decl &&
|
|
4951
|
+
if (decl && ts4.isClassDeclaration(decl) && !seenHandlers.has(decl.name?.text ?? "")) {
|
|
4694
4952
|
seenHandlers.add(decl.name?.text ?? "");
|
|
4695
4953
|
handlerClasses.push(decl);
|
|
4696
4954
|
}
|
|
4697
4955
|
};
|
|
4698
4956
|
for (const el of arrayProp(options, "providers")) {
|
|
4699
|
-
if (
|
|
4957
|
+
if (ts4.isIdentifier(el))
|
|
4700
4958
|
collectHandler(el);
|
|
4701
|
-
if (
|
|
4959
|
+
if (ts4.isObjectLiteralExpression(el)) {
|
|
4702
4960
|
const useClass = getProp(el, "useClass");
|
|
4703
4961
|
if (useClass)
|
|
4704
4962
|
collectHandler(useClass);
|
|
@@ -4798,17 +5056,17 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
|
|
|
4798
5056
|
if (seen.has(input))
|
|
4799
5057
|
return;
|
|
4800
5058
|
seen.add(input);
|
|
4801
|
-
if (
|
|
4802
|
-
const local = input.getSourceFile().statements.flatMap((statement) =>
|
|
5059
|
+
if (ts4.isIdentifier(input)) {
|
|
5060
|
+
const local = input.getSourceFile().statements.flatMap((statement) => ts4.isVariableStatement(statement) ? [...statement.declarationList.declarations] : []);
|
|
4803
5061
|
const resolved = resolveDeclaration(input, ctx)[0];
|
|
4804
|
-
const decl = (resolved &&
|
|
4805
|
-
if (decl &&
|
|
5062
|
+
const decl = (resolved && ts4.isVariableDeclaration(resolved) ? resolved : undefined) ?? ctx.variablesByName.get(input.text) ?? local.find((candidate) => ts4.isIdentifier(candidate.name) && candidate.name.text === input.text) ?? descendantsOfKind(input.getSourceFile(), ts4.isVariableDeclaration).find((candidate) => ts4.isIdentifier(candidate.name) && candidate.name.text === input.text);
|
|
5063
|
+
if (decl && ts4.isVariableDeclaration(decl))
|
|
4806
5064
|
return parseFeatureSpec(decl.initializer, ctx, seen);
|
|
4807
5065
|
}
|
|
4808
|
-
if (
|
|
5066
|
+
if (ts4.isCallExpression(input) && nodeText(input.expression) === "defineFeatureSpec") {
|
|
4809
5067
|
return parseFeatureSpec(input.arguments[0], ctx, seen);
|
|
4810
5068
|
}
|
|
4811
|
-
if (
|
|
5069
|
+
if (ts4.isAsExpression(input) || ts4.isSatisfiesExpression(input) || ts4.isParenthesizedExpression(input)) {
|
|
4812
5070
|
return parseFeatureSpec(input.expression, ctx, seen);
|
|
4813
5071
|
}
|
|
4814
5072
|
const invalid = () => {
|
|
@@ -4821,28 +5079,28 @@ function parseFeatureSpec(input, ctx, seen = new Set) {
|
|
|
4821
5079
|
});
|
|
4822
5080
|
return;
|
|
4823
5081
|
};
|
|
4824
|
-
if (!
|
|
5082
|
+
if (!ts4.isObjectLiteralExpression(input))
|
|
4825
5083
|
return invalid();
|
|
4826
5084
|
const name = stringLiteralProp(input, "name");
|
|
4827
5085
|
const statesExpr = getProp(input, "states");
|
|
4828
5086
|
const transitionObject = getProp(input, "transitions");
|
|
4829
|
-
if (!name || !statesExpr || !
|
|
5087
|
+
if (!name || !statesExpr || !ts4.isArrayLiteralExpression(statesExpr) || statesExpr.elements.some((state) => !ts4.isStringLiteral(state)) || !transitionObject || !ts4.isObjectLiteralExpression(transitionObject) || input.properties.some((property) => !ts4.isPropertyAssignment(property))) {
|
|
4830
5088
|
return invalid();
|
|
4831
5089
|
}
|
|
4832
5090
|
const states = [];
|
|
4833
5091
|
for (const state of statesExpr.elements) {
|
|
4834
|
-
if (!
|
|
5092
|
+
if (!ts4.isStringLiteral(state))
|
|
4835
5093
|
return invalid();
|
|
4836
5094
|
states.push(state.text);
|
|
4837
5095
|
}
|
|
4838
5096
|
const transitions = [];
|
|
4839
5097
|
for (const property of transitionObject.properties) {
|
|
4840
|
-
if (!
|
|
5098
|
+
if (!ts4.isPropertyAssignment(property) || ts4.isComputedPropertyName(property.name) || !ts4.isObjectLiteralExpression(property.initializer))
|
|
4841
5099
|
return invalid();
|
|
4842
5100
|
const options = property.initializer;
|
|
4843
5101
|
const from = stringLiteralProp(options, "from");
|
|
4844
5102
|
const to = stringLiteralProp(options, "to");
|
|
4845
|
-
if (!from || !to || options.properties.some((prop) => !
|
|
5103
|
+
if (!from || !to || options.properties.some((prop) => !ts4.isPropertyAssignment(prop)) || ["permission", "command", "route", "audit"].some((key) => getProp(options, key) && !stringLiteralProp(options, key)) || ["transaction", "idempotency"].some((key) => getProp(options, key) && !commandModeProp(options, key))) {
|
|
4846
5104
|
return invalid();
|
|
4847
5105
|
}
|
|
4848
5106
|
const permission = stringLiteralProp(options, "permission");
|
|
@@ -4869,21 +5127,21 @@ function resolveStaticObjectLiteral(input, ctx, seen = new Set) {
|
|
|
4869
5127
|
if (!input || seen.has(input))
|
|
4870
5128
|
return;
|
|
4871
5129
|
seen.add(input);
|
|
4872
|
-
if (
|
|
5130
|
+
if (ts4.isAsExpression(input) || ts4.isSatisfiesExpression(input) || ts4.isParenthesizedExpression(input)) {
|
|
4873
5131
|
return resolveStaticObjectLiteral(input.expression, ctx, seen);
|
|
4874
5132
|
}
|
|
4875
|
-
if (
|
|
4876
|
-
const declaration = resolveDeclaration(input, ctx).find(
|
|
5133
|
+
if (ts4.isIdentifier(input)) {
|
|
5134
|
+
const declaration = resolveDeclaration(input, ctx).find(ts4.isVariableDeclaration);
|
|
4877
5135
|
return declaration?.initializer ? resolveStaticObjectLiteral(declaration.initializer, ctx, seen) : undefined;
|
|
4878
5136
|
}
|
|
4879
|
-
if (
|
|
5137
|
+
if (ts4.isCallExpression(input)) {
|
|
4880
5138
|
const expressionName = nodeText(input.expression);
|
|
4881
5139
|
if (expressionName === "defineRouteContract" || expressionName.endsWith(".defineRouteContract")) {
|
|
4882
5140
|
return resolveStaticObjectLiteral(input.arguments[0], ctx, seen);
|
|
4883
5141
|
}
|
|
4884
5142
|
return;
|
|
4885
5143
|
}
|
|
4886
|
-
return
|
|
5144
|
+
return ts4.isObjectLiteralExpression(input) ? input : undefined;
|
|
4887
5145
|
}
|
|
4888
5146
|
function commandModeProp(object, name) {
|
|
4889
5147
|
const value = stringLiteralProp(object, name);
|
|
@@ -4918,9 +5176,9 @@ function parseProvider(el, exportsSet, ctx) {
|
|
|
4918
5176
|
const file = sourcePath(ctx.rootDir, el.getSourceFile().fileName);
|
|
4919
5177
|
const line = lineOf(el);
|
|
4920
5178
|
const unwrappedEl = unwrapForwardRef(el);
|
|
4921
|
-
if (
|
|
5179
|
+
if (ts4.isIdentifier(unwrappedEl)) {
|
|
4922
5180
|
const decl = resolveDeclaration(unwrappedEl, ctx)[0];
|
|
4923
|
-
const cls = decl &&
|
|
5181
|
+
const cls = decl && ts4.isClassDeclaration(decl) ? decl : undefined;
|
|
4924
5182
|
const className = cls?.name?.text ?? unwrappedEl.text;
|
|
4925
5183
|
const { deps, optionalDeps, selfDeps, skipSelfDeps, hostDeps, functionalInjects, missing } = cls ? classDeps(cls, ctx) : { deps: [], optionalDeps: [], selfDeps: [], skipSelfDeps: [], hostDeps: [], functionalInjects: [], missing: false };
|
|
4926
5184
|
const injectable = cls ? parseInjectableOptions(cls, ctx) : undefined;
|
|
@@ -4947,7 +5205,7 @@ function parseProvider(el, exportsSet, ctx) {
|
|
|
4947
5205
|
...cls ? { importPath: modulePath(ctx.rootDir, cls.getSourceFile().fileName) } : {}
|
|
4948
5206
|
};
|
|
4949
5207
|
}
|
|
4950
|
-
if (!
|
|
5208
|
+
if (!ts4.isObjectLiteralExpression(el))
|
|
4951
5209
|
return;
|
|
4952
5210
|
const provideExpr = getProp(el, "provide");
|
|
4953
5211
|
if (!provideExpr)
|
|
@@ -4962,8 +5220,8 @@ function parseProvider(el, exportsSet, ctx) {
|
|
|
4962
5220
|
const useExistingExpr = getProp(el, "useExisting");
|
|
4963
5221
|
if (useClassExpr) {
|
|
4964
5222
|
const unwrappedClass = unwrapForwardRef(useClassExpr);
|
|
4965
|
-
const decl =
|
|
4966
|
-
const cls = decl &&
|
|
5223
|
+
const decl = ts4.isIdentifier(unwrappedClass) ? resolveDeclaration(unwrappedClass, ctx)[0] : undefined;
|
|
5224
|
+
const cls = decl && ts4.isClassDeclaration(decl) ? decl : undefined;
|
|
4967
5225
|
const useClass = cls?.name?.text ?? nodeText(unwrappedClass);
|
|
4968
5226
|
let deps = explicitDeps;
|
|
4969
5227
|
let optionalDeps = [];
|
|
@@ -5019,7 +5277,7 @@ function parseProvider(el, exportsSet, ctx) {
|
|
|
5019
5277
|
}
|
|
5020
5278
|
if (useValueExpr) {
|
|
5021
5279
|
validateProviderCompatibility(provideExpr, useValueExpr, "value", token, ctx, file, line);
|
|
5022
|
-
const importPath =
|
|
5280
|
+
const importPath = ts4.isIdentifier(useValueExpr) ? importPathOf(useValueExpr, ctx) : undefined;
|
|
5023
5281
|
return {
|
|
5024
5282
|
token,
|
|
5025
5283
|
tokenKind,
|
|
@@ -5035,12 +5293,12 @@ function parseProvider(el, exportsSet, ctx) {
|
|
|
5035
5293
|
};
|
|
5036
5294
|
}
|
|
5037
5295
|
if (useFactoryExpr) {
|
|
5038
|
-
const factoryName =
|
|
5296
|
+
const factoryName = ts4.isIdentifier(useFactoryExpr) ? (() => {
|
|
5039
5297
|
const decl = resolveDeclaration(useFactoryExpr, ctx)[0];
|
|
5040
|
-
return decl && (
|
|
5298
|
+
return decl && (ts4.isFunctionDeclaration(decl) || ts4.isVariableDeclaration(decl)) ? (ts4.isFunctionDeclaration(decl) ? decl.name?.text : variableName(decl)) ?? useFactoryExpr.text : useFactoryExpr.text;
|
|
5041
5299
|
})() : nodeText(useFactoryExpr);
|
|
5042
5300
|
validateProviderCompatibility(provideExpr, useFactoryExpr, "factory", token, ctx, file, line);
|
|
5043
|
-
const importPath =
|
|
5301
|
+
const importPath = ts4.isIdentifier(useFactoryExpr) ? importPathOf(useFactoryExpr, ctx) : undefined;
|
|
5044
5302
|
return {
|
|
5045
5303
|
token,
|
|
5046
5304
|
tokenKind,
|
|
@@ -5076,20 +5334,20 @@ function parseProvider(el, exportsSet, ctx) {
|
|
|
5076
5334
|
function expandProviderExpressions(expressions, ctx, seen = new Set) {
|
|
5077
5335
|
const result = [];
|
|
5078
5336
|
for (const expression of expressions) {
|
|
5079
|
-
if (
|
|
5337
|
+
if (ts4.isSpreadElement(expression)) {
|
|
5080
5338
|
result.push(...expandProviderExpressions([expression.expression], ctx, seen));
|
|
5081
5339
|
continue;
|
|
5082
5340
|
}
|
|
5083
|
-
if (
|
|
5341
|
+
if (ts4.isIdentifier(expression)) {
|
|
5084
5342
|
const declaration = resolveDeclaration(expression, ctx)[0];
|
|
5085
|
-
if (declaration &&
|
|
5343
|
+
if (declaration && ts4.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
5086
5344
|
const key = `${declaration.getSourceFile().fileName}:${declaration.pos}`;
|
|
5087
5345
|
if (seen.has(key))
|
|
5088
5346
|
continue;
|
|
5089
5347
|
const initializer = declaration.initializer;
|
|
5090
|
-
if (
|
|
5348
|
+
if (ts4.isCallExpression(initializer) && isProviderHelper(initializer, "makeEnvironmentProviders")) {
|
|
5091
5349
|
const nested = initializer.arguments[0];
|
|
5092
|
-
if (nested &&
|
|
5350
|
+
if (nested && ts4.isArrayLiteralExpression(nested)) {
|
|
5093
5351
|
seen.add(key);
|
|
5094
5352
|
result.push(...expandProviderExpressions([...nested.elements], ctx, seen));
|
|
5095
5353
|
seen.delete(key);
|
|
@@ -5098,9 +5356,9 @@ function expandProviderExpressions(expressions, ctx, seen = new Set) {
|
|
|
5098
5356
|
}
|
|
5099
5357
|
}
|
|
5100
5358
|
}
|
|
5101
|
-
if (
|
|
5359
|
+
if (ts4.isCallExpression(expression) && isProviderHelper(expression, "makeEnvironmentProviders")) {
|
|
5102
5360
|
const nested = expression.arguments[0];
|
|
5103
|
-
if (nested &&
|
|
5361
|
+
if (nested && ts4.isArrayLiteralExpression(nested)) {
|
|
5104
5362
|
result.push(...expandProviderExpressions([...nested.elements], ctx, seen));
|
|
5105
5363
|
continue;
|
|
5106
5364
|
}
|
|
@@ -5113,7 +5371,7 @@ function isProviderHelper(expression, name) {
|
|
|
5113
5371
|
return nodeText(expression.expression).split(".").pop() === name;
|
|
5114
5372
|
}
|
|
5115
5373
|
function parseFunctionalProvider(expression, exportsSet, ctx) {
|
|
5116
|
-
if (!
|
|
5374
|
+
if (!ts4.isCallExpression(expression))
|
|
5117
5375
|
return;
|
|
5118
5376
|
const helper = nodeText(expression.expression).split(".").pop();
|
|
5119
5377
|
const args = expression.arguments;
|
|
@@ -5126,7 +5384,7 @@ function parseFunctionalProvider(expression, exportsSet, ctx) {
|
|
|
5126
5384
|
return [];
|
|
5127
5385
|
const { name: token, kind: tokenKind } = tokenNameOf(tokenExpr, ctx);
|
|
5128
5386
|
validateProviderCompatibility(tokenExpr, valueExpr, "value", token, ctx, file, line);
|
|
5129
|
-
const importPath =
|
|
5387
|
+
const importPath = ts4.isIdentifier(valueExpr) ? importPathOf(valueExpr, ctx) : undefined;
|
|
5130
5388
|
return [{
|
|
5131
5389
|
token,
|
|
5132
5390
|
tokenKind,
|
|
@@ -5145,7 +5403,7 @@ function parseFunctionalProvider(expression, exportsSet, ctx) {
|
|
|
5145
5403
|
if (!initializer)
|
|
5146
5404
|
return [];
|
|
5147
5405
|
const token = helper === "provideAppInitializer" ? "APP_INITIALIZER" : "ENVIRONMENT_INITIALIZER";
|
|
5148
|
-
const importPath =
|
|
5406
|
+
const importPath = ts4.isIdentifier(initializer) ? importPathOf(initializer, ctx) : undefined;
|
|
5149
5407
|
return [{
|
|
5150
5408
|
token,
|
|
5151
5409
|
tokenKind: "injection-token",
|
|
@@ -5164,7 +5422,7 @@ function parseFunctionalProvider(expression, exportsSet, ctx) {
|
|
|
5164
5422
|
const providers = [];
|
|
5165
5423
|
const routes = args[0];
|
|
5166
5424
|
if (routes) {
|
|
5167
|
-
const importPath =
|
|
5425
|
+
const importPath = ts4.isIdentifier(routes) ? importPathOf(routes, ctx) : undefined;
|
|
5168
5426
|
providers.push({
|
|
5169
5427
|
token: "ROUTE_CONFIG",
|
|
5170
5428
|
tokenKind: "injection-token",
|
|
@@ -5179,7 +5437,7 @@ function parseFunctionalProvider(expression, exportsSet, ctx) {
|
|
|
5179
5437
|
});
|
|
5180
5438
|
}
|
|
5181
5439
|
for (const feature of args.slice(1)) {
|
|
5182
|
-
if (!
|
|
5440
|
+
if (!ts4.isCallExpression(feature))
|
|
5183
5441
|
continue;
|
|
5184
5442
|
const featureName = nodeText(feature.expression).split(".").pop();
|
|
5185
5443
|
if (featureName === "withRouterConfig" && feature.arguments[0]) {
|
|
@@ -5196,8 +5454,8 @@ function parseFunctionalProvider(expression, exportsSet, ctx) {
|
|
|
5196
5454
|
});
|
|
5197
5455
|
} else if (featureName === "withTitleStrategy" && feature.arguments[0]) {
|
|
5198
5456
|
const strategy = feature.arguments[0];
|
|
5199
|
-
const isClass =
|
|
5200
|
-
const importPath =
|
|
5457
|
+
const isClass = ts4.isIdentifier(strategy) && Boolean(resolveDeclaration(strategy, ctx).find((declaration) => ts4.isClassDeclaration(declaration)));
|
|
5458
|
+
const importPath = ts4.isIdentifier(strategy) ? importPathOf(strategy, ctx) : undefined;
|
|
5201
5459
|
providers.push({
|
|
5202
5460
|
token: "TITLE_STRATEGY",
|
|
5203
5461
|
tokenKind: "injection-token",
|
|
@@ -5229,14 +5487,14 @@ function parseFunctionalProvider(expression, exportsSet, ctx) {
|
|
|
5229
5487
|
importModule: "@supacloud/app"
|
|
5230
5488
|
}];
|
|
5231
5489
|
for (const feature of args) {
|
|
5232
|
-
if (!
|
|
5490
|
+
if (!ts4.isCallExpression(feature))
|
|
5233
5491
|
continue;
|
|
5234
5492
|
const featureName = nodeText(feature.expression).split(".").pop();
|
|
5235
5493
|
if (featureName === "withInterceptors") {
|
|
5236
5494
|
for (const interceptorArg of feature.arguments) {
|
|
5237
|
-
const values =
|
|
5495
|
+
const values = ts4.isArrayLiteralExpression(interceptorArg) ? [...interceptorArg.elements] : [interceptorArg];
|
|
5238
5496
|
for (const value of values) {
|
|
5239
|
-
const importPath =
|
|
5497
|
+
const importPath = ts4.isIdentifier(value) ? importPathOf(value, ctx) : undefined;
|
|
5240
5498
|
providers.push({
|
|
5241
5499
|
token: "HTTP_INTERCEPTORS",
|
|
5242
5500
|
tokenKind: "injection-token",
|
|
@@ -5283,9 +5541,9 @@ function providerTokenValueType(expr, ctx) {
|
|
|
5283
5541
|
const typeArguments = typeArgumentsOf(type, ctx);
|
|
5284
5542
|
if (typeArguments.length > 0)
|
|
5285
5543
|
return typeArguments[0];
|
|
5286
|
-
if (
|
|
5544
|
+
if (ts4.isIdentifier(expr)) {
|
|
5287
5545
|
const declaration = resolveDeclaration(expr, ctx)[0];
|
|
5288
|
-
if (declaration &&
|
|
5546
|
+
if (declaration && ts4.isClassDeclaration(declaration)) {
|
|
5289
5547
|
return declaredClassType(declaration, ctx);
|
|
5290
5548
|
}
|
|
5291
5549
|
}
|
|
@@ -5293,9 +5551,9 @@ function providerTokenValueType(expr, ctx) {
|
|
|
5293
5551
|
}
|
|
5294
5552
|
function providerImplementationType(expr, kind, ctx) {
|
|
5295
5553
|
if (kind === "class" || kind === "existing") {
|
|
5296
|
-
if (
|
|
5554
|
+
if (ts4.isIdentifier(expr)) {
|
|
5297
5555
|
const declaration = resolveDeclaration(expr, ctx)[0];
|
|
5298
|
-
if (declaration &&
|
|
5556
|
+
if (declaration && ts4.isClassDeclaration(declaration)) {
|
|
5299
5557
|
return declaredClassType(declaration, ctx);
|
|
5300
5558
|
}
|
|
5301
5559
|
}
|
|
@@ -5305,7 +5563,7 @@ function providerImplementationType(expr, kind, ctx) {
|
|
|
5305
5563
|
}
|
|
5306
5564
|
if (kind === "factory") {
|
|
5307
5565
|
const type = ctx.checker.getTypeAtLocation(expr);
|
|
5308
|
-
const signature = ctx.checker.getSignaturesOfType(type,
|
|
5566
|
+
const signature = ctx.checker.getSignaturesOfType(type, ts4.SignatureKind.Call)[0];
|
|
5309
5567
|
return signature?.getReturnType();
|
|
5310
5568
|
}
|
|
5311
5569
|
return ctx.checker.getTypeAtLocation(expr);
|
|
@@ -5324,16 +5582,16 @@ function isTypeReference(type) {
|
|
|
5324
5582
|
return "target" in type;
|
|
5325
5583
|
}
|
|
5326
5584
|
function isUnknownOrAny(type) {
|
|
5327
|
-
return (type.flags & (
|
|
5585
|
+
return (type.flags & (ts4.TypeFlags.Any | ts4.TypeFlags.Unknown)) !== 0;
|
|
5328
5586
|
}
|
|
5329
5587
|
function parseController(input, ctx) {
|
|
5330
5588
|
let decl;
|
|
5331
|
-
if (
|
|
5589
|
+
if (ts4.isClassDeclaration(input)) {
|
|
5332
5590
|
decl = input;
|
|
5333
5591
|
} else {
|
|
5334
5592
|
const unwrapped = unwrapForwardRef(input);
|
|
5335
|
-
const resolved =
|
|
5336
|
-
if (resolved &&
|
|
5593
|
+
const resolved = ts4.isIdentifier(unwrapped) ? resolveDeclaration(unwrapped, ctx)[0] : undefined;
|
|
5594
|
+
if (resolved && ts4.isClassDeclaration(resolved)) {
|
|
5337
5595
|
decl = resolved;
|
|
5338
5596
|
}
|
|
5339
5597
|
}
|
|
@@ -5346,9 +5604,9 @@ function parseController(input, ctx) {
|
|
|
5346
5604
|
let standalone;
|
|
5347
5605
|
const pathArg = decoratorArguments(controllerDec)[0];
|
|
5348
5606
|
if (pathArg) {
|
|
5349
|
-
if (
|
|
5607
|
+
if (ts4.isStringLiteral(pathArg)) {
|
|
5350
5608
|
path = pathArg.text;
|
|
5351
|
-
} else if (
|
|
5609
|
+
} else if (ts4.isObjectLiteralExpression(pathArg)) {
|
|
5352
5610
|
const p = stringLiteralProp(pathArg, "path");
|
|
5353
5611
|
if (p)
|
|
5354
5612
|
path = p;
|
|
@@ -5371,7 +5629,7 @@ function parseController(input, ctx) {
|
|
|
5371
5629
|
}
|
|
5372
5630
|
}
|
|
5373
5631
|
}
|
|
5374
|
-
for (const method of decl.members.filter(
|
|
5632
|
+
for (const method of decl.members.filter(ts4.isMethodDeclaration)) {
|
|
5375
5633
|
for (const dec of decoratorsOf(method)) {
|
|
5376
5634
|
const name = decoratorName2(dec);
|
|
5377
5635
|
const httpMethod = name ? ROUTE_DECORATORS[name] : undefined;
|
|
@@ -5379,7 +5637,7 @@ function parseController(input, ctx) {
|
|
|
5379
5637
|
continue;
|
|
5380
5638
|
const args = decoratorArguments(dec);
|
|
5381
5639
|
const pathArg = args[0];
|
|
5382
|
-
const routePath = pathArg &&
|
|
5640
|
+
const routePath = pathArg && ts4.isStringLiteral(pathArg) ? pathArg.text : "/";
|
|
5383
5641
|
const route = {
|
|
5384
5642
|
method: httpMethod,
|
|
5385
5643
|
path: routePath,
|
|
@@ -5450,12 +5708,12 @@ function parseController(input, ctx) {
|
|
|
5450
5708
|
} else if (dName === "Headers") {
|
|
5451
5709
|
hasBindingDecorator = true;
|
|
5452
5710
|
const argument = dArgs[0];
|
|
5453
|
-
const bindingName = argument !== undefined &&
|
|
5711
|
+
const bindingName = argument !== undefined && ts4.isStringLiteral(argument) ? argument.text : undefined;
|
|
5454
5712
|
paramNode = { name: pName, kind: "headers", ...bindingName === undefined ? {} : { bindingName } };
|
|
5455
5713
|
} else if (dName === "Cookie") {
|
|
5456
5714
|
hasBindingDecorator = true;
|
|
5457
5715
|
const argument = dArgs[0];
|
|
5458
|
-
const bindingName = argument !== undefined &&
|
|
5716
|
+
const bindingName = argument !== undefined && ts4.isStringLiteral(argument) ? argument.text : undefined;
|
|
5459
5717
|
paramNode = { name: pName, kind: "cookie", ...bindingName === undefined ? {} : { bindingName } };
|
|
5460
5718
|
}
|
|
5461
5719
|
}
|
|
@@ -5517,20 +5775,20 @@ function parseController(input, ctx) {
|
|
|
5517
5775
|
}
|
|
5518
5776
|
} else if (dName === "Title") {
|
|
5519
5777
|
const tArg = mArgs[0];
|
|
5520
|
-
if (tArg &&
|
|
5778
|
+
if (tArg && ts4.isStringLiteral(tArg)) {
|
|
5521
5779
|
route.title = tArg.text;
|
|
5522
5780
|
}
|
|
5523
5781
|
} else if (dName === "Data") {
|
|
5524
5782
|
const dArg = mArgs[0];
|
|
5525
|
-
if (dArg &&
|
|
5783
|
+
if (dArg && ts4.isObjectLiteralExpression(dArg)) {
|
|
5526
5784
|
route.data = { ...route.data, ...parseObjectLiteralValues(dArg) };
|
|
5527
5785
|
}
|
|
5528
5786
|
} else if (dName === "Resolve") {
|
|
5529
5787
|
const rArg = mArgs[0];
|
|
5530
|
-
if (rArg &&
|
|
5788
|
+
if (rArg && ts4.isObjectLiteralExpression(rArg)) {
|
|
5531
5789
|
const resolvers = route.resolvers ?? {};
|
|
5532
5790
|
for (const prop of rArg.properties) {
|
|
5533
|
-
if (
|
|
5791
|
+
if (ts4.isPropertyAssignment(prop)) {
|
|
5534
5792
|
const rName = propertyName(prop.name);
|
|
5535
5793
|
const init = prop.initializer;
|
|
5536
5794
|
if (init)
|
|
@@ -5561,7 +5819,7 @@ function parseController(input, ctx) {
|
|
|
5561
5819
|
});
|
|
5562
5820
|
}
|
|
5563
5821
|
const contract = getProp(optionsObject, "contract");
|
|
5564
|
-
if (contract &&
|
|
5822
|
+
if (contract && ts4.isObjectLiteralExpression(contract)) {
|
|
5565
5823
|
route.contract = {};
|
|
5566
5824
|
for (const field of ["body", "response", "evidence"]) {
|
|
5567
5825
|
const value = stringLiteralProp(contract, field);
|
|
@@ -5579,15 +5837,15 @@ function parseController(input, ctx) {
|
|
|
5579
5837
|
}
|
|
5580
5838
|
for (const field of ["body", "params", "query", "headers", "cookie", "response"]) {
|
|
5581
5839
|
const schemaExpr = getProp(optionsObject, field);
|
|
5582
|
-
if (schemaExpr &&
|
|
5840
|
+
if (schemaExpr && ts4.isIdentifier(schemaExpr)) {
|
|
5583
5841
|
route[field] = nodeText(schemaExpr);
|
|
5584
5842
|
const importPath = importPathOf(schemaExpr, ctx);
|
|
5585
5843
|
if (importPath)
|
|
5586
5844
|
schemaImports[schemaExpr.text] = importPath;
|
|
5587
5845
|
const declaration = resolveDeclaration(schemaExpr, ctx)[0];
|
|
5588
5846
|
const typeName = ctx.checker.typeToString(ctx.checker.getTypeAtLocation(schemaExpr));
|
|
5589
|
-
const initializer = declaration &&
|
|
5590
|
-
const opaque = /\bT(?:Unknown|Any)\b/.test(typeName) || initializer &&
|
|
5847
|
+
const initializer = declaration && ts4.isVariableDeclaration(declaration) ? declaration.initializer : undefined;
|
|
5848
|
+
const opaque = /\bT(?:Unknown|Any)\b/.test(typeName) || initializer && ts4.isCallExpression(initializer) && ts4.isPropertyAccessExpression(initializer.expression) && ["Unknown", "Any"].includes(initializer.expression.name.text);
|
|
5591
5849
|
(route.schemaKinds ??= {})[field] = opaque ? "opaque" : "declared";
|
|
5592
5850
|
}
|
|
5593
5851
|
}
|
|
@@ -5596,7 +5854,7 @@ function parseController(input, ctx) {
|
|
|
5596
5854
|
const responses = {};
|
|
5597
5855
|
const selectors = new Map;
|
|
5598
5856
|
for (const property of responsesObject.properties) {
|
|
5599
|
-
if (!
|
|
5857
|
+
if (!ts4.isPropertyAssignment(property) || ts4.isComputedPropertyName(property.name))
|
|
5600
5858
|
continue;
|
|
5601
5859
|
const status = propertyName(property.name);
|
|
5602
5860
|
if (!isRouteResponseSelector(status)) {
|
|
@@ -5628,7 +5886,7 @@ function parseController(input, ctx) {
|
|
|
5628
5886
|
}
|
|
5629
5887
|
selectors.set(canonical, status);
|
|
5630
5888
|
const schemaExpr = property.initializer;
|
|
5631
|
-
if (!
|
|
5889
|
+
if (!ts4.isIdentifier(schemaExpr)) {
|
|
5632
5890
|
ctx.diagnostics.push({
|
|
5633
5891
|
severity: "error",
|
|
5634
5892
|
code: "invalid-route-response-map",
|
|
@@ -5643,8 +5901,8 @@ function parseController(input, ctx) {
|
|
|
5643
5901
|
schemaImports[schemaExpr.text] = importPath;
|
|
5644
5902
|
const declaration = resolveDeclaration(schemaExpr, ctx)[0];
|
|
5645
5903
|
const typeName = ctx.checker.typeToString(ctx.checker.getTypeAtLocation(schemaExpr));
|
|
5646
|
-
const initializer = declaration &&
|
|
5647
|
-
const opaque = /\bT(?:Unknown|Any)\b/.test(typeName) || initializer &&
|
|
5904
|
+
const initializer = declaration && ts4.isVariableDeclaration(declaration) ? declaration.initializer : undefined;
|
|
5905
|
+
const opaque = /\bT(?:Unknown|Any)\b/.test(typeName) || initializer && ts4.isCallExpression(initializer) && ts4.isPropertyAccessExpression(initializer.expression) && ["Unknown", "Any"].includes(initializer.expression.name.text);
|
|
5648
5906
|
const previousKind = route.schemaKinds?.response;
|
|
5649
5907
|
(route.schemaKinds ??= {}).response = opaque || previousKind === "opaque" ? "opaque" : "declared";
|
|
5650
5908
|
}
|
|
@@ -5652,18 +5910,18 @@ function parseController(input, ctx) {
|
|
|
5652
5910
|
route.responses = responses;
|
|
5653
5911
|
}
|
|
5654
5912
|
const commandExpr = getProp(optionsObject, "command");
|
|
5655
|
-
if (commandExpr &&
|
|
5913
|
+
if (commandExpr && ts4.isIdentifier(commandExpr)) {
|
|
5656
5914
|
const commandDecl = resolveDeclaration(commandExpr, ctx)[0];
|
|
5657
|
-
route.command = commandDecl &&
|
|
5915
|
+
route.command = commandDecl && ts4.isClassDeclaration(commandDecl) ? commandDecl.name?.text ?? commandExpr.text : commandExpr.text;
|
|
5658
5916
|
}
|
|
5659
5917
|
const guardsExpr = getProp(optionsObject, "guards");
|
|
5660
|
-
if (guardsExpr &&
|
|
5918
|
+
if (guardsExpr && ts4.isArrayLiteralExpression(guardsExpr)) {
|
|
5661
5919
|
for (const el of guardsExpr.elements) {
|
|
5662
5920
|
routeGuards.push(tokenText(el, ctx));
|
|
5663
5921
|
}
|
|
5664
5922
|
}
|
|
5665
5923
|
const canMatchExpr = getProp(optionsObject, "canMatch");
|
|
5666
|
-
if (canMatchExpr &&
|
|
5924
|
+
if (canMatchExpr && ts4.isArrayLiteralExpression(canMatchExpr)) {
|
|
5667
5925
|
const canMatchList = [];
|
|
5668
5926
|
for (const el of canMatchExpr.elements) {
|
|
5669
5927
|
canMatchList.push(tokenText(el, ctx));
|
|
@@ -5673,16 +5931,16 @@ function parseController(input, ctx) {
|
|
|
5673
5931
|
}
|
|
5674
5932
|
}
|
|
5675
5933
|
const canDeactivateExpr = getProp(optionsObject, "canDeactivate");
|
|
5676
|
-
if (canDeactivateExpr &&
|
|
5934
|
+
if (canDeactivateExpr && ts4.isArrayLiteralExpression(canDeactivateExpr)) {
|
|
5677
5935
|
for (const el of canDeactivateExpr.elements) {
|
|
5678
5936
|
routeCanDeactivate.push(tokenText(el, ctx));
|
|
5679
5937
|
}
|
|
5680
5938
|
}
|
|
5681
5939
|
const resolversExpr = getProp(optionsObject, "resolvers");
|
|
5682
|
-
if (resolversExpr &&
|
|
5940
|
+
if (resolversExpr && ts4.isObjectLiteralExpression(resolversExpr)) {
|
|
5683
5941
|
const resolvers = {};
|
|
5684
5942
|
for (const prop of resolversExpr.properties) {
|
|
5685
|
-
if (
|
|
5943
|
+
if (ts4.isPropertyAssignment(prop)) {
|
|
5686
5944
|
const rName = propertyName(prop.name);
|
|
5687
5945
|
const init = prop.initializer;
|
|
5688
5946
|
if (init)
|
|
@@ -5694,22 +5952,22 @@ function parseController(input, ctx) {
|
|
|
5694
5952
|
}
|
|
5695
5953
|
}
|
|
5696
5954
|
const redirectToExpr = getProp(optionsObject, "redirectTo");
|
|
5697
|
-
if (redirectToExpr &&
|
|
5955
|
+
if (redirectToExpr && ts4.isStringLiteral(redirectToExpr)) {
|
|
5698
5956
|
route.redirectTo = redirectToExpr.text;
|
|
5699
5957
|
}
|
|
5700
5958
|
const pathMatchExpr = getProp(optionsObject, "pathMatch");
|
|
5701
|
-
if (pathMatchExpr &&
|
|
5959
|
+
if (pathMatchExpr && ts4.isStringLiteral(pathMatchExpr)) {
|
|
5702
5960
|
const val = pathMatchExpr.text;
|
|
5703
5961
|
if (val === "full" || val === "prefix") {
|
|
5704
5962
|
route.pathMatch = val;
|
|
5705
5963
|
}
|
|
5706
5964
|
}
|
|
5707
5965
|
const titleExpr = getProp(optionsObject, "title");
|
|
5708
|
-
if (titleExpr &&
|
|
5966
|
+
if (titleExpr && ts4.isStringLiteral(titleExpr)) {
|
|
5709
5967
|
route.title = titleExpr.text;
|
|
5710
5968
|
}
|
|
5711
5969
|
const dataExpr = getProp(optionsObject, "data");
|
|
5712
|
-
if (dataExpr &&
|
|
5970
|
+
if (dataExpr && ts4.isObjectLiteralExpression(dataExpr)) {
|
|
5713
5971
|
route.data = { ...route.data, ...parseObjectLiteralValues(dataExpr) };
|
|
5714
5972
|
}
|
|
5715
5973
|
const aspects = parseAspectRefs(getProp(optionsObject, "aspects"), ctx, `route ${httpMethod} ${routePath}`);
|
|
@@ -5764,7 +6022,7 @@ function parseJobOptions(meta, owner, ctx) {
|
|
|
5764
6022
|
const expression = getProp(meta, field);
|
|
5765
6023
|
if (!expression)
|
|
5766
6024
|
continue;
|
|
5767
|
-
if (!
|
|
6025
|
+
if (!ts4.isIdentifier(expression)) {
|
|
5768
6026
|
jobOptionError(ctx, "invalid-job-schema", `${owner} 的 ${field} schema 必须是可静态解析的标识符引用,不能使用内联调用或动态表达式`, expression, "SC4019", `将 schema 提取为命名导出,例如 ${field}: ${field === "input" ? "JobInput" : "JobOutput"}。`);
|
|
5769
6027
|
continue;
|
|
5770
6028
|
}
|
|
@@ -5788,7 +6046,7 @@ function parseJobEnum(meta, field, allowed, owner, ctx, code, errorCode) {
|
|
|
5788
6046
|
const expression = getProp(meta, field);
|
|
5789
6047
|
if (!expression)
|
|
5790
6048
|
return;
|
|
5791
|
-
if (!
|
|
6049
|
+
if (!ts4.isStringLiteral(expression) || !allowed.includes(expression.text)) {
|
|
5792
6050
|
jobOptionError(ctx, code, `${owner} 的 ${field} 必须是 ${allowed.map((value) => JSON.stringify(value)).join(" 或 ")} 字符串字面量`, expression, errorCode);
|
|
5793
6051
|
return;
|
|
5794
6052
|
}
|
|
@@ -5798,7 +6056,7 @@ function parseJobInteger(meta, field, min, max, owner, ctx, code, errorCode) {
|
|
|
5798
6056
|
const expression = getProp(meta, field);
|
|
5799
6057
|
if (!expression)
|
|
5800
6058
|
return;
|
|
5801
|
-
const value =
|
|
6059
|
+
const value = ts4.isNumericLiteral(expression) ? Number(expression.text) : Number.NaN;
|
|
5802
6060
|
if (!Number.isSafeInteger(value) || value < min || value > max) {
|
|
5803
6061
|
jobOptionError(ctx, code, `${owner} 的 ${field} 必须是 ${min} 到 ${max} 之间的安全整数`, expression, errorCode);
|
|
5804
6062
|
return;
|
|
@@ -5819,8 +6077,8 @@ function jobOptionError(ctx, code, message, node, errorCode, suggestion) {
|
|
|
5819
6077
|
}
|
|
5820
6078
|
function jobSchemaKind(identifier, declaration, ctx) {
|
|
5821
6079
|
const typeName = ctx.checker.typeToString(ctx.checker.getTypeAtLocation(identifier));
|
|
5822
|
-
const initializer =
|
|
5823
|
-
const opaque = /\bT(?:Unknown|Any)\b/.test(typeName) || initializer &&
|
|
6080
|
+
const initializer = ts4.isVariableDeclaration(declaration) ? declaration.initializer : undefined;
|
|
6081
|
+
const opaque = /\bT(?:Unknown|Any)\b/.test(typeName) || initializer && ts4.isCallExpression(initializer) && ts4.isPropertyAccessExpression(initializer.expression) && ["Unknown", "Any"].includes(initializer.expression.name.text);
|
|
5824
6082
|
return opaque ? "opaque" : "declared";
|
|
5825
6083
|
}
|
|
5826
6084
|
function checkedRpc(meta, ctx) {
|
|
@@ -5842,7 +6100,7 @@ function checkedRpc(meta, ctx) {
|
|
|
5842
6100
|
}
|
|
5843
6101
|
function classDeps(cls, ctx) {
|
|
5844
6102
|
const injectable = parseInjectableOptions(cls, ctx);
|
|
5845
|
-
const ctor = cls.members.find(
|
|
6103
|
+
const ctor = cls.members.find(ts4.isConstructorDeclaration);
|
|
5846
6104
|
const deps = injectable?.deps ? [...injectable.deps] : [];
|
|
5847
6105
|
const optionalDeps = [];
|
|
5848
6106
|
const selfDeps = [];
|
|
@@ -5876,23 +6134,23 @@ function classDeps(cls, ctx) {
|
|
|
5876
6134
|
}
|
|
5877
6135
|
});
|
|
5878
6136
|
}
|
|
5879
|
-
for (const prop of cls.members.filter(
|
|
6137
|
+
for (const prop of cls.members.filter(ts4.isPropertyDeclaration)) {
|
|
5880
6138
|
const init = prop.initializer;
|
|
5881
|
-
if (init &&
|
|
6139
|
+
if (init && ts4.isCallExpression(init)) {
|
|
5882
6140
|
const callName = nodeText(init.expression).split(".").pop();
|
|
5883
6141
|
if (callName === "inject") {
|
|
5884
6142
|
const [tokenArg, optionsArg] = init.arguments;
|
|
5885
6143
|
if (tokenArg) {
|
|
5886
6144
|
const tokenName = tokenText(tokenArg, ctx);
|
|
5887
6145
|
const unwrappedToken = unwrapForwardRef(tokenArg);
|
|
5888
|
-
const known =
|
|
6146
|
+
const known = ts4.isStringLiteral(unwrappedToken) || ts4.isIdentifier(unwrappedToken) && (ctx.tokensByName.has(tokenName) || ctx.classesByName.has(tokenName));
|
|
5889
6147
|
if (!known) {
|
|
5890
6148
|
missing = true;
|
|
5891
6149
|
continue;
|
|
5892
6150
|
}
|
|
5893
6151
|
if (!deps.includes(tokenName))
|
|
5894
6152
|
deps.push(tokenName);
|
|
5895
|
-
const options = optionsArg &&
|
|
6153
|
+
const options = optionsArg && ts4.isObjectLiteralExpression(optionsArg) ? {
|
|
5896
6154
|
optional: booleanProp(optionsArg, "optional") ?? false,
|
|
5897
6155
|
self: booleanProp(optionsArg, "self") ?? false,
|
|
5898
6156
|
skipSelf: booleanProp(optionsArg, "skipSelf") ?? false,
|
|
@@ -5907,9 +6165,9 @@ function classDeps(cls, ctx) {
|
|
|
5907
6165
|
if (options.host && !hostDeps.includes(tokenName))
|
|
5908
6166
|
hostDeps.push(tokenName);
|
|
5909
6167
|
if (!functionalInjects.some((entry) => entry.token === tokenName)) {
|
|
5910
|
-
const declaration =
|
|
6168
|
+
const declaration = ts4.isIdentifier(unwrappedToken) ? resolveDeclaration(unwrappedToken, ctx)[0] : undefined;
|
|
5911
6169
|
const localFile = declaration && isProjectSourcePath(declaration.getSourceFile().fileName, ctx.rootDir) ? declaration.getSourceFile().fileName : undefined;
|
|
5912
|
-
const importModule = declaration && !localFile &&
|
|
6170
|
+
const importModule = declaration && !localFile && ts4.isIdentifier(unwrappedToken) ? importModuleOf(unwrappedToken, ctx) : undefined;
|
|
5913
6171
|
functionalInjects.push({
|
|
5914
6172
|
token: tokenName,
|
|
5915
6173
|
expression: nodeText(unwrappedToken),
|
|
@@ -5953,7 +6211,7 @@ function parseInjectableOptions(cls, ctx) {
|
|
|
5953
6211
|
}
|
|
5954
6212
|
function parseInjectParams(cls, ctx) {
|
|
5955
6213
|
const result = new Map;
|
|
5956
|
-
const ctor = cls.members.find(
|
|
6214
|
+
const ctor = cls.members.find(ts4.isConstructorDeclaration);
|
|
5957
6215
|
if (!ctor)
|
|
5958
6216
|
return result;
|
|
5959
6217
|
ctor.parameters.forEach((param, index) => {
|
|
@@ -5969,7 +6227,7 @@ function parseInjectParams(cls, ctx) {
|
|
|
5969
6227
|
}
|
|
5970
6228
|
function parseOptionalParams(cls) {
|
|
5971
6229
|
const result = new Set;
|
|
5972
|
-
const ctor = cls.members.find(
|
|
6230
|
+
const ctor = cls.members.find(ts4.isConstructorDeclaration);
|
|
5973
6231
|
if (!ctor)
|
|
5974
6232
|
return result;
|
|
5975
6233
|
ctor.parameters.forEach((param, index) => {
|
|
@@ -5984,7 +6242,7 @@ function parseOptionalParams(cls) {
|
|
|
5984
6242
|
}
|
|
5985
6243
|
function parseModifierParams(cls, modifierName) {
|
|
5986
6244
|
const result = new Set;
|
|
5987
|
-
const ctor = cls.members.find(
|
|
6245
|
+
const ctor = cls.members.find(ts4.isConstructorDeclaration);
|
|
5988
6246
|
if (!ctor)
|
|
5989
6247
|
return result;
|
|
5990
6248
|
ctor.parameters.forEach((param, index) => {
|
|
@@ -5996,13 +6254,13 @@ function parseModifierParams(cls, modifierName) {
|
|
|
5996
6254
|
return result;
|
|
5997
6255
|
}
|
|
5998
6256
|
function unwrapForwardRef(expr) {
|
|
5999
|
-
if (
|
|
6257
|
+
if (ts4.isCallExpression(expr)) {
|
|
6000
6258
|
const exprText = nodeText(expr.expression);
|
|
6001
6259
|
if (exprText === "forwardRef" || exprText.endsWith(".forwardRef")) {
|
|
6002
6260
|
const arg = expr.arguments[0];
|
|
6003
|
-
if (arg && (
|
|
6261
|
+
if (arg && (ts4.isArrowFunction(arg) || ts4.isFunctionExpression(arg))) {
|
|
6004
6262
|
const body = arg.body;
|
|
6005
|
-
if (body &&
|
|
6263
|
+
if (body && ts4.isExpression(body)) {
|
|
6006
6264
|
return unwrapForwardRef(body);
|
|
6007
6265
|
}
|
|
6008
6266
|
}
|
|
@@ -6012,13 +6270,13 @@ function unwrapForwardRef(expr) {
|
|
|
6012
6270
|
}
|
|
6013
6271
|
function tokenText(expr, ctx) {
|
|
6014
6272
|
const unwrapped = unwrapForwardRef(expr);
|
|
6015
|
-
if (
|
|
6273
|
+
if (ts4.isStringLiteral(unwrapped))
|
|
6016
6274
|
return unwrapped.text;
|
|
6017
|
-
if (
|
|
6275
|
+
if (ts4.isIdentifier(unwrapped)) {
|
|
6018
6276
|
const decl = resolveDeclaration(unwrapped, ctx)[0];
|
|
6019
|
-
if (decl &&
|
|
6277
|
+
if (decl && ts4.isClassDeclaration(decl))
|
|
6020
6278
|
return decl.name?.text ?? unwrapped.text;
|
|
6021
|
-
if (decl &&
|
|
6279
|
+
if (decl && ts4.isVariableDeclaration(decl))
|
|
6022
6280
|
return variableName(decl);
|
|
6023
6281
|
}
|
|
6024
6282
|
return nodeText(unwrapped);
|
|
@@ -6038,12 +6296,12 @@ function resolveScope(input, ctx) {
|
|
|
6038
6296
|
}
|
|
6039
6297
|
function tokenNameOf(expr, ctx) {
|
|
6040
6298
|
const unwrapped = unwrapForwardRef(expr);
|
|
6041
|
-
if (
|
|
6299
|
+
if (ts4.isIdentifier(unwrapped)) {
|
|
6042
6300
|
const decl = resolveDeclaration(unwrapped, ctx)[0];
|
|
6043
|
-
if (decl &&
|
|
6301
|
+
if (decl && ts4.isClassDeclaration(decl)) {
|
|
6044
6302
|
return { name: decl.name?.text ?? nodeText(expr), kind: "class" };
|
|
6045
6303
|
}
|
|
6046
|
-
if (decl &&
|
|
6304
|
+
if (decl && ts4.isVariableDeclaration(decl)) {
|
|
6047
6305
|
const name = variableName(decl);
|
|
6048
6306
|
return { name, kind: ctx.tokensByName.has(name) ? "injection-token" : "class" };
|
|
6049
6307
|
}
|
|
@@ -6059,10 +6317,10 @@ function resolveDeclaration(id, ctx) {
|
|
|
6059
6317
|
return [];
|
|
6060
6318
|
let declarations = symbol.declarations ?? [];
|
|
6061
6319
|
for (let guard = 0;guard < 4; guard += 1) {
|
|
6062
|
-
const isAlias = declarations.some((d) =>
|
|
6320
|
+
const isAlias = declarations.some((d) => ts4.isImportSpecifier(d) || ts4.isImportClause(d) || ts4.isNamespaceImport(d));
|
|
6063
6321
|
if (!isAlias)
|
|
6064
6322
|
break;
|
|
6065
|
-
if (!(symbol.flags &
|
|
6323
|
+
if (!(symbol.flags & ts4.SymbolFlags.Alias))
|
|
6066
6324
|
break;
|
|
6067
6325
|
const aliased = ctx.checker.getAliasedSymbol(symbol);
|
|
6068
6326
|
symbol = aliased;
|
|
@@ -6082,9 +6340,9 @@ function importModuleOf(id, ctx) {
|
|
|
6082
6340
|
for (const declaration of declarations) {
|
|
6083
6341
|
let current = declaration;
|
|
6084
6342
|
while (current) {
|
|
6085
|
-
if (
|
|
6343
|
+
if (ts4.isImportDeclaration(current)) {
|
|
6086
6344
|
const moduleSpecifier = current.moduleSpecifier;
|
|
6087
|
-
return
|
|
6345
|
+
return ts4.isStringLiteral(moduleSpecifier) ? moduleSpecifier.text : undefined;
|
|
6088
6346
|
}
|
|
6089
6347
|
current = current.parent;
|
|
6090
6348
|
}
|
|
@@ -6096,27 +6354,27 @@ function findDecorator(cls, name) {
|
|
|
6096
6354
|
}
|
|
6097
6355
|
function decoratorName2(dec) {
|
|
6098
6356
|
const expr = dec.expression;
|
|
6099
|
-
if (
|
|
6357
|
+
if (ts4.isCallExpression(expr)) {
|
|
6100
6358
|
return nodeText(expr.expression).split(".").pop();
|
|
6101
6359
|
}
|
|
6102
|
-
if (
|
|
6360
|
+
if (ts4.isIdentifier(expr))
|
|
6103
6361
|
return expr.text;
|
|
6104
6362
|
return;
|
|
6105
6363
|
}
|
|
6106
6364
|
function decoratorObjectArg(dec) {
|
|
6107
6365
|
const expr = dec.expression;
|
|
6108
|
-
if (!
|
|
6366
|
+
if (!ts4.isCallExpression(expr))
|
|
6109
6367
|
return;
|
|
6110
6368
|
const arg = expr.arguments[0];
|
|
6111
|
-
return arg &&
|
|
6369
|
+
return arg && ts4.isObjectLiteralExpression(arg) ? arg : undefined;
|
|
6112
6370
|
}
|
|
6113
6371
|
function getProp(obj, name) {
|
|
6114
|
-
const prop = obj.properties.find((item) => (
|
|
6372
|
+
const prop = obj.properties.find((item) => (ts4.isPropertyAssignment(item) || ts4.isShorthandPropertyAssignment(item)) && propertyName(item.name) === name);
|
|
6115
6373
|
if (!prop)
|
|
6116
6374
|
return;
|
|
6117
|
-
if (
|
|
6375
|
+
if (ts4.isPropertyAssignment(prop))
|
|
6118
6376
|
return prop.initializer;
|
|
6119
|
-
if (
|
|
6377
|
+
if (ts4.isShorthandPropertyAssignment(prop))
|
|
6120
6378
|
return prop.name;
|
|
6121
6379
|
return;
|
|
6122
6380
|
}
|
|
@@ -6124,10 +6382,10 @@ function toCompilerDiagnostic(diagnostic, rootDir) {
|
|
|
6124
6382
|
const file = diagnostic.file;
|
|
6125
6383
|
const position = file && diagnostic.start !== undefined ? file.getLineAndCharacterOfPosition(diagnostic.start) : undefined;
|
|
6126
6384
|
return {
|
|
6127
|
-
severity: diagnostic.category ===
|
|
6385
|
+
severity: diagnostic.category === ts4.DiagnosticCategory.Error ? "error" : "warn",
|
|
6128
6386
|
code: `typescript-${diagnostic.code}`,
|
|
6129
6387
|
errorCode: `TS${diagnostic.code}`,
|
|
6130
|
-
message:
|
|
6388
|
+
message: ts4.flattenDiagnosticMessageText(diagnostic.messageText, `
|
|
6131
6389
|
`),
|
|
6132
6390
|
...file ? { file: sourcePath(rootDir, file.fileName) } : {},
|
|
6133
6391
|
...position ? { line: position.line + 1 } : {}
|
|
@@ -6135,16 +6393,16 @@ function toCompilerDiagnostic(diagnostic, rootDir) {
|
|
|
6135
6393
|
}
|
|
6136
6394
|
function stringLiteralProp(obj, name) {
|
|
6137
6395
|
const expr = getProp(obj, name);
|
|
6138
|
-
return expr &&
|
|
6396
|
+
return expr && ts4.isStringLiteral(expr) ? expr.text : undefined;
|
|
6139
6397
|
}
|
|
6140
6398
|
function arrayProp(obj, name) {
|
|
6141
6399
|
const expr = getProp(obj, name);
|
|
6142
|
-
return expr &&
|
|
6400
|
+
return expr && ts4.isArrayLiteralExpression(expr) ? [...expr.elements] : [];
|
|
6143
6401
|
}
|
|
6144
6402
|
function parseAspectRefs(expression, ctx, owner) {
|
|
6145
6403
|
if (!expression)
|
|
6146
6404
|
return [];
|
|
6147
|
-
if (!
|
|
6405
|
+
if (!ts4.isArrayLiteralExpression(expression)) {
|
|
6148
6406
|
ctx.diagnostics.push({
|
|
6149
6407
|
severity: "error",
|
|
6150
6408
|
code: "dynamic-aspect-reference",
|
|
@@ -6159,7 +6417,7 @@ function parseAspectRefs(expression, ctx, owner) {
|
|
|
6159
6417
|
}
|
|
6160
6418
|
const refs = [];
|
|
6161
6419
|
for (const element of expression.elements) {
|
|
6162
|
-
if (
|
|
6420
|
+
if (ts4.isSpreadElement(element) || !ts4.isIdentifier(element)) {
|
|
6163
6421
|
ctx.diagnostics.push({
|
|
6164
6422
|
severity: "error",
|
|
6165
6423
|
code: "dynamic-aspect-reference",
|
|
@@ -6172,7 +6430,7 @@ function parseAspectRefs(expression, ctx, owner) {
|
|
|
6172
6430
|
});
|
|
6173
6431
|
continue;
|
|
6174
6432
|
}
|
|
6175
|
-
const declaration = resolveDeclaration(element, ctx).find((candidate) =>
|
|
6433
|
+
const declaration = resolveDeclaration(element, ctx).find((candidate) => ts4.isFunctionDeclaration(candidate) || ts4.isVariableDeclaration(candidate) && candidate.initializer !== undefined && (ts4.isArrowFunction(candidate.initializer) || ts4.isFunctionExpression(candidate.initializer)));
|
|
6176
6434
|
if (!declaration) {
|
|
6177
6435
|
ctx.diagnostics.push({
|
|
6178
6436
|
severity: "error",
|
|
@@ -6186,7 +6444,7 @@ function parseAspectRefs(expression, ctx, owner) {
|
|
|
6186
6444
|
});
|
|
6187
6445
|
continue;
|
|
6188
6446
|
}
|
|
6189
|
-
const name =
|
|
6447
|
+
const name = ts4.isFunctionDeclaration(declaration) ? declaration.name?.text : ts4.isVariableDeclaration(declaration) ? variableName(declaration) : undefined;
|
|
6190
6448
|
if (!name)
|
|
6191
6449
|
continue;
|
|
6192
6450
|
const declaredFile = declaration.getSourceFile().fileName;
|
|
@@ -6206,9 +6464,9 @@ function booleanProp(obj, name) {
|
|
|
6206
6464
|
const expr = getProp(obj, name);
|
|
6207
6465
|
if (!expr)
|
|
6208
6466
|
return;
|
|
6209
|
-
if (expr.kind ===
|
|
6467
|
+
if (expr.kind === ts4.SyntaxKind.TrueKeyword)
|
|
6210
6468
|
return true;
|
|
6211
|
-
if (expr.kind ===
|
|
6469
|
+
if (expr.kind === ts4.SyntaxKind.FalseKeyword)
|
|
6212
6470
|
return false;
|
|
6213
6471
|
return;
|
|
6214
6472
|
}
|
|
@@ -6222,15 +6480,15 @@ function parseBindingOptions(args, defaultName) {
|
|
|
6222
6480
|
let defaultValue;
|
|
6223
6481
|
const first = args[0];
|
|
6224
6482
|
const second = args[1];
|
|
6225
|
-
if (first &&
|
|
6483
|
+
if (first && ts4.isStringLiteral(first)) {
|
|
6226
6484
|
name = first.text;
|
|
6227
|
-
} else if (first &&
|
|
6485
|
+
} else if (first && ts4.isObjectLiteralExpression(first)) {
|
|
6228
6486
|
const nameProp = getProp(first, "name");
|
|
6229
|
-
if (nameProp &&
|
|
6487
|
+
if (nameProp && ts4.isStringLiteral(nameProp)) {
|
|
6230
6488
|
name = nameProp.text;
|
|
6231
6489
|
}
|
|
6232
6490
|
const trProp = getProp(first, "transform");
|
|
6233
|
-
if (trProp &&
|
|
6491
|
+
if (trProp && ts4.isStringLiteral(trProp)) {
|
|
6234
6492
|
const val = trProp.text;
|
|
6235
6493
|
if (val === "number" || val === "boolean" || val === "string") {
|
|
6236
6494
|
transform = val;
|
|
@@ -6241,9 +6499,9 @@ function parseBindingOptions(args, defaultName) {
|
|
|
6241
6499
|
defaultValue = parseLiteralValue(defProp);
|
|
6242
6500
|
}
|
|
6243
6501
|
}
|
|
6244
|
-
if (second &&
|
|
6502
|
+
if (second && ts4.isObjectLiteralExpression(second)) {
|
|
6245
6503
|
const trProp = getProp(second, "transform");
|
|
6246
|
-
if (trProp &&
|
|
6504
|
+
if (trProp && ts4.isStringLiteral(trProp)) {
|
|
6247
6505
|
const val = trProp.text;
|
|
6248
6506
|
if (val === "number" || val === "boolean" || val === "string") {
|
|
6249
6507
|
transform = val;
|
|
@@ -6257,18 +6515,18 @@ function parseBindingOptions(args, defaultName) {
|
|
|
6257
6515
|
return { name, ...transform ? { transform } : {}, default: defaultValue };
|
|
6258
6516
|
}
|
|
6259
6517
|
function parseLiteralValue(node) {
|
|
6260
|
-
if (
|
|
6518
|
+
if (ts4.isStringLiteral(node))
|
|
6261
6519
|
return node.text;
|
|
6262
|
-
if (
|
|
6520
|
+
if (ts4.isNumericLiteral(node))
|
|
6263
6521
|
return Number(node.text);
|
|
6264
|
-
if (node.kind ===
|
|
6522
|
+
if (node.kind === ts4.SyntaxKind.TrueKeyword)
|
|
6265
6523
|
return true;
|
|
6266
|
-
if (node.kind ===
|
|
6524
|
+
if (node.kind === ts4.SyntaxKind.FalseKeyword)
|
|
6267
6525
|
return false;
|
|
6268
|
-
if (
|
|
6526
|
+
if (ts4.isArrayLiteralExpression(node)) {
|
|
6269
6527
|
return node.elements.map(parseLiteralValue);
|
|
6270
6528
|
}
|
|
6271
|
-
if (
|
|
6529
|
+
if (ts4.isObjectLiteralExpression(node)) {
|
|
6272
6530
|
return parseObjectLiteralValues(node);
|
|
6273
6531
|
}
|
|
6274
6532
|
return;
|
|
@@ -6276,7 +6534,7 @@ function parseLiteralValue(node) {
|
|
|
6276
6534
|
function parseObjectLiteralValues(obj) {
|
|
6277
6535
|
const result = {};
|
|
6278
6536
|
for (const prop of obj.properties) {
|
|
6279
|
-
if (
|
|
6537
|
+
if (ts4.isPropertyAssignment(prop)) {
|
|
6280
6538
|
const name = propertyName(prop.name);
|
|
6281
6539
|
const init = prop.initializer;
|
|
6282
6540
|
if (init) {
|
|
@@ -6310,7 +6568,50 @@ import { join as join4 } from "node:path";
|
|
|
6310
6568
|
// src/type-safety.ts
|
|
6311
6569
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
6312
6570
|
import { dirname as dirname3, join as join3, relative as relative3, resolve as resolve2, sep as sep3 } from "node:path";
|
|
6313
|
-
import * as
|
|
6571
|
+
import * as ts6 from "@typescript/typescript6";
|
|
6572
|
+
|
|
6573
|
+
// src/sql-safety.ts
|
|
6574
|
+
import * as ts5 from "@typescript/typescript6";
|
|
6575
|
+
var SQL_SAFETY_DIAGNOSTIC_CODES = {
|
|
6576
|
+
"sql-result-assertion": { errorCode: "SC6007", docsUrl: "https://supacloud.dev/errors/SC6007" },
|
|
6577
|
+
"sql-raw-dynamic": { errorCode: "SC6008", docsUrl: "https://supacloud.dev/errors/SC6008" }
|
|
6578
|
+
};
|
|
6579
|
+
function scanDrizzleSql(sourceFile, checker, file, strict) {
|
|
6580
|
+
const diagnostics = [];
|
|
6581
|
+
const importedSql = (expression) => {
|
|
6582
|
+
const symbol = checker.getSymbolAtLocation(ts5.isPropertyAccessExpression(expression) ? expression.name : expression);
|
|
6583
|
+
if (!symbol)
|
|
6584
|
+
return false;
|
|
6585
|
+
const target = symbol.flags & ts5.SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol;
|
|
6586
|
+
return target.name === "sql" && (target.declarations ?? []).some((declaration) => /(?:^|\/)node_modules\/drizzle-orm\//.test(declaration.getSourceFile().fileName.replaceAll("\\", "/")));
|
|
6587
|
+
};
|
|
6588
|
+
const report = (code, node, message) => {
|
|
6589
|
+
diagnostics.push({
|
|
6590
|
+
severity: strict ? "error" : "warn",
|
|
6591
|
+
code,
|
|
6592
|
+
...SQL_SAFETY_DIAGNOSTIC_CODES[code],
|
|
6593
|
+
message,
|
|
6594
|
+
file,
|
|
6595
|
+
line: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1
|
|
6596
|
+
});
|
|
6597
|
+
};
|
|
6598
|
+
const visit = (node) => {
|
|
6599
|
+
if (ts5.isTaggedTemplateExpression(node) && importedSql(node.tag) && node.typeArguments?.some((type) => type.kind !== ts5.SyntaxKind.UnknownKeyword)) {
|
|
6600
|
+
report("sql-result-assertion", node, "Drizzle sql<T> asserts a result type without checking SQL or decoding rows. Use sql<unknown> and a schema decoder at the result boundary.");
|
|
6601
|
+
}
|
|
6602
|
+
if (ts5.isCallExpression(node) && ts5.isPropertyAccessExpression(node.expression) && node.expression.name.text === "raw" && importedSql(node.expression.expression)) {
|
|
6603
|
+
const argument = node.arguments[0];
|
|
6604
|
+
if (!argument || !ts5.isStringLiteral(argument) && !ts5.isNoSubstitutionTemplateLiteral(argument)) {
|
|
6605
|
+
report("sql-raw-dynamic", node, "Dynamic sql.raw bypasses parameter binding. Interpolate values with sql templates; keep reviewed static DDL in migrations.");
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
ts5.forEachChild(node, visit);
|
|
6609
|
+
};
|
|
6610
|
+
visit(sourceFile);
|
|
6611
|
+
return diagnostics;
|
|
6612
|
+
}
|
|
6613
|
+
|
|
6614
|
+
// src/type-safety.ts
|
|
6314
6615
|
var DEFAULT_EXCLUDES = [
|
|
6315
6616
|
"**/*.test.ts",
|
|
6316
6617
|
"**/*.spec.ts",
|
|
@@ -6323,6 +6624,7 @@ var DEFAULT_EXCLUDES = [
|
|
|
6323
6624
|
"**/*.d.ts"
|
|
6324
6625
|
];
|
|
6325
6626
|
var TYPE_SAFETY_DIAGNOSTIC_CODES = {
|
|
6627
|
+
...SQL_SAFETY_DIAGNOSTIC_CODES,
|
|
6326
6628
|
"generated-any": { errorCode: "SC6001", docsUrl: "https://supacloud.dev/errors/SC6001" },
|
|
6327
6629
|
"source-any": { errorCode: "SC6002", docsUrl: "https://supacloud.dev/errors/SC6002" },
|
|
6328
6630
|
"source-type-assertion": { errorCode: "SC6003", docsUrl: "https://supacloud.dev/errors/SC6003" },
|
|
@@ -6335,7 +6637,7 @@ function scanGeneratedArtifacts(artifacts, strict = true) {
|
|
|
6335
6637
|
for (const [file, content] of Object.entries(artifacts)) {
|
|
6336
6638
|
if (content === undefined)
|
|
6337
6639
|
continue;
|
|
6338
|
-
const sourceFile =
|
|
6640
|
+
const sourceFile = ts6.createSourceFile(file, content, ts6.ScriptTarget.Latest, true, ts6.ScriptKind.TS);
|
|
6339
6641
|
for (const node of descendantsOfKind2(sourceFile, isAnyKeyword)) {
|
|
6340
6642
|
diagnostics.push(makeDiagnostic("generated-any", `生成产物 ${file} 包含 any;严格生成模式要求使用 unknown、具体接口或泛型约束。`, sourceFile, node, strict));
|
|
6341
6643
|
}
|
|
@@ -6344,30 +6646,30 @@ function scanGeneratedArtifacts(artifacts, strict = true) {
|
|
|
6344
6646
|
}
|
|
6345
6647
|
function scanProductionSource(options) {
|
|
6346
6648
|
const rootDir = resolve2(options.rootDir);
|
|
6347
|
-
const configPath =
|
|
6649
|
+
const configPath = ts6.findConfigFile(rootDir, ts6.sys.fileExists) ?? join3(rootDir, "tsconfig.json");
|
|
6348
6650
|
const projectConfig = existsSync2(configPath) ? readProjectConfig2(configPath) : {
|
|
6349
6651
|
options: {
|
|
6350
6652
|
strict: true,
|
|
6351
6653
|
skipLibCheck: true,
|
|
6352
|
-
target:
|
|
6353
|
-
module:
|
|
6354
|
-
moduleResolution:
|
|
6654
|
+
target: ts6.ScriptTarget.ES2022,
|
|
6655
|
+
module: ts6.ModuleKind.ESNext,
|
|
6656
|
+
moduleResolution: ts6.ModuleResolutionKind.Bundler
|
|
6355
6657
|
},
|
|
6356
6658
|
errors: []
|
|
6357
6659
|
};
|
|
6358
6660
|
const include = options.include ?? ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
|
|
6359
|
-
const rootNames =
|
|
6661
|
+
const rootNames = ts6.sys.readDirectory(rootDir, [".ts", ".tsx", ".mts", ".cts"], ["node_modules", "dist"], include).filter((file) => isProductionSourcePath(rootDir, file, [...DEFAULT_EXCLUDES, ...options.exclude ?? []]));
|
|
6360
6662
|
const compilerOptions = { ...projectConfig.options, noEmit: true };
|
|
6361
|
-
const host =
|
|
6663
|
+
const host = ts6.createCompilerHost(compilerOptions);
|
|
6362
6664
|
host.getCurrentDirectory = () => dirname3(configPath);
|
|
6363
|
-
const program =
|
|
6665
|
+
const program = ts6.createProgram(rootNames, compilerOptions, host);
|
|
6364
6666
|
const outDir = options.outDir ? normalizeRelative(rootDir, options.outDir) : undefined;
|
|
6365
6667
|
const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
|
|
6366
6668
|
const sourceFiles = program.getSourceFiles().filter((sourceFile) => isProductionSource(rootDir, sourceFile, excludes, outDir));
|
|
6367
6669
|
const diagnostics = [...projectConfig.errors, ...program.getOptionsDiagnostics()].map((diagnostic) => ({
|
|
6368
6670
|
severity: "error",
|
|
6369
6671
|
code: "source-config",
|
|
6370
|
-
message:
|
|
6672
|
+
message: ts6.flattenDiagnosticMessageText(diagnostic.messageText, `
|
|
6371
6673
|
`),
|
|
6372
6674
|
file: diagnostic.file ? normalizeRelative(rootDir, diagnostic.file.fileName) : normalizeRelative(rootDir, configPath),
|
|
6373
6675
|
...diagnostic.file && diagnostic.start !== undefined ? { line: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start).line + 1 } : {},
|
|
@@ -6384,7 +6686,7 @@ function scanProductionSource(options) {
|
|
|
6384
6686
|
diagnostics.push({
|
|
6385
6687
|
severity: "error",
|
|
6386
6688
|
code: "source-typescript",
|
|
6387
|
-
message:
|
|
6689
|
+
message: ts6.flattenDiagnosticMessageText(diagnostic.messageText, `
|
|
6388
6690
|
`),
|
|
6389
6691
|
...diagnostic.file ? { file: normalizeRelative(rootDir, diagnostic.file.fileName) } : {},
|
|
6390
6692
|
...diagnostic.file && diagnostic.start !== undefined ? { line: diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start).line + 1 } : {},
|
|
@@ -6393,9 +6695,10 @@ function scanProductionSource(options) {
|
|
|
6393
6695
|
}
|
|
6394
6696
|
for (const sourceFile of sourceFiles) {
|
|
6395
6697
|
scanSourceFile(sourceFile, checker, rootDir, diagnostics, options.strict ?? false);
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6698
|
+
diagnostics.push(...scanDrizzleSql(sourceFile, checker, normalizeRelative(rootDir, sourceFile.fileName), options.strict ?? false));
|
|
6699
|
+
const scanner = ts6.createScanner(ts6.ScriptTarget.Latest, false, sourceFile.languageVariant, sourceFile.text);
|
|
6700
|
+
for (let kind = scanner.scan();kind !== ts6.SyntaxKind.EndOfFileToken; kind = scanner.scan()) {
|
|
6701
|
+
if ((kind === ts6.SyntaxKind.SingleLineCommentTrivia || kind === ts6.SyntaxKind.MultiLineCommentTrivia) && /@ts-(?:ignore|nocheck|expect-error)\b/.test(scanner.getTokenText())) {
|
|
6399
6702
|
diagnostics.push({
|
|
6400
6703
|
severity: "error",
|
|
6401
6704
|
code: "source-type-suppression",
|
|
@@ -6415,22 +6718,22 @@ function scanSourceFile(sourceFile, checker, rootDir, diagnostics, strict) {
|
|
|
6415
6718
|
diagnostics.push(makeDiagnostic("source-any", "生产源码使用了显式 any;请改用 unknown、具体接口或泛型约束。", sourceFile, node, strict, rootDir));
|
|
6416
6719
|
}
|
|
6417
6720
|
for (const node of descendants(sourceFile)) {
|
|
6418
|
-
if (
|
|
6419
|
-
if (
|
|
6721
|
+
if (ts6.isAsExpression(node)) {
|
|
6722
|
+
if (ts6.isAsExpression(node.parent) || ts6.isTypeAssertionExpression(node.parent))
|
|
6420
6723
|
continue;
|
|
6421
6724
|
const assertedType = node.type.getText(sourceFile);
|
|
6422
6725
|
if (assertedType === "const")
|
|
6423
6726
|
continue;
|
|
6424
6727
|
diagnostics.push(makeDiagnostic("source-type-assertion", `生产源码包含类型断言 ${node.getText(sourceFile)};请优先使用类型守卫、satisfies 或显式边界解析。`, sourceFile, node, strict, rootDir));
|
|
6425
|
-
} else if (
|
|
6426
|
-
if (
|
|
6728
|
+
} else if (ts6.isTypeAssertionExpression(node)) {
|
|
6729
|
+
if (ts6.isAsExpression(node.parent) || ts6.isTypeAssertionExpression(node.parent))
|
|
6427
6730
|
continue;
|
|
6428
6731
|
diagnostics.push(makeDiagnostic("source-type-assertion", `生产源码包含类型断言 ${node.getText(sourceFile)};请优先使用类型守卫、satisfies 或显式边界解析。`, sourceFile, node, strict, rootDir));
|
|
6429
|
-
} else if (
|
|
6732
|
+
} else if (ts6.isNonNullExpression(node)) {
|
|
6430
6733
|
diagnostics.push(makeDiagnostic("source-non-null-assertion", `生产源码包含非空断言 ${node.getText(sourceFile)};请显式处理 null/undefined。`, sourceFile, node, strict, rootDir));
|
|
6431
6734
|
}
|
|
6432
6735
|
}
|
|
6433
|
-
for (const declaration of descendantsOfKind2(sourceFile,
|
|
6736
|
+
for (const declaration of descendantsOfKind2(sourceFile, ts6.isVariableDeclaration)) {
|
|
6434
6737
|
const initializer = declaration.initializer;
|
|
6435
6738
|
if (!initializer || declaration.type)
|
|
6436
6739
|
continue;
|
|
@@ -6446,11 +6749,11 @@ function scanSourceFile(sourceFile, checker, rootDir, diagnostics, strict) {
|
|
|
6446
6749
|
if (isLetDeclaration(declaration) && isLiteralSyntax(initializer) && !isLiteralType(declarationType)) {
|
|
6447
6750
|
diagnostics.push(makeDiagnostic("source-implicit-widening", `变量 ${declaration.name.getText(sourceFile)} 的字面量类型从 ${checker.typeToString(initializerType, initializer)} 隐式宽化为 ${checker.typeToString(declarationType, declaration)};请补充类型或使用 const。`, sourceFile, declaration, strict, rootDir));
|
|
6448
6751
|
}
|
|
6449
|
-
if (
|
|
6752
|
+
if (ts6.isObjectLiteralExpression(initializer) && isConstDeclaration(declaration) && initializer.getText(sourceFile).length > 0 && initializer.properties.some((property) => ts6.isPropertyAssignment(property) && property.initializer !== undefined && !ts6.isAsExpression(property.initializer) && isLiteralExpression(property.initializer))) {
|
|
6450
6753
|
diagnostics.push(makeDiagnostic("source-implicit-widening", `常量对象 ${declaration.name.getText(sourceFile)} 的字面量属性会隐式宽化;请补充对象类型或使用 as const。`, sourceFile, declaration, strict, rootDir));
|
|
6451
6754
|
}
|
|
6452
6755
|
}
|
|
6453
|
-
for (const parameter of descendantsOfKind2(sourceFile,
|
|
6756
|
+
for (const parameter of descendantsOfKind2(sourceFile, ts6.isParameter)) {
|
|
6454
6757
|
if (parameter.type)
|
|
6455
6758
|
continue;
|
|
6456
6759
|
for (const name of bindingNames(parameter.name)) {
|
|
@@ -6461,10 +6764,10 @@ function scanSourceFile(sourceFile, checker, rootDir, diagnostics, strict) {
|
|
|
6461
6764
|
}
|
|
6462
6765
|
}
|
|
6463
6766
|
function readProjectConfig2(configPath) {
|
|
6464
|
-
const config =
|
|
6767
|
+
const config = ts6.readConfigFile(configPath, (file) => readFileSync2(file, "utf8"));
|
|
6465
6768
|
if (config.error)
|
|
6466
6769
|
return { options: {}, errors: [config.error] };
|
|
6467
|
-
const parsed =
|
|
6770
|
+
const parsed = ts6.parseJsonConfigFileContent(config.config, ts6.sys, dirname3(configPath));
|
|
6468
6771
|
return { options: parsed.options, errors: parsed.errors };
|
|
6469
6772
|
}
|
|
6470
6773
|
function isProductionSource(rootDir, sourceFile, excludes, outDir) {
|
|
@@ -6484,42 +6787,42 @@ function globMatches(value, pattern) {
|
|
|
6484
6787
|
return new RegExp(`^${escaped}$`).test(value);
|
|
6485
6788
|
}
|
|
6486
6789
|
function bindingNames(name) {
|
|
6487
|
-
if (
|
|
6790
|
+
if (ts6.isIdentifier(name))
|
|
6488
6791
|
return [name];
|
|
6489
|
-
return name.elements.flatMap((element) =>
|
|
6792
|
+
return name.elements.flatMap((element) => ts6.isBindingElement(element) ? bindingNames(element.name) : []);
|
|
6490
6793
|
}
|
|
6491
6794
|
function isLiteralExpression(node) {
|
|
6492
6795
|
if (!node)
|
|
6493
6796
|
return false;
|
|
6494
6797
|
return [
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6798
|
+
ts6.SyntaxKind.StringLiteral,
|
|
6799
|
+
ts6.SyntaxKind.NumericLiteral,
|
|
6800
|
+
ts6.SyntaxKind.TrueKeyword,
|
|
6801
|
+
ts6.SyntaxKind.FalseKeyword
|
|
6499
6802
|
].includes(node.kind);
|
|
6500
6803
|
}
|
|
6501
6804
|
function isLiteralSyntax(node) {
|
|
6502
|
-
return
|
|
6805
|
+
return ts6.isStringLiteral(node) || ts6.isNumericLiteral(node) || node.kind === ts6.SyntaxKind.TrueKeyword || node.kind === ts6.SyntaxKind.FalseKeyword;
|
|
6503
6806
|
}
|
|
6504
6807
|
function isLiteralType(type) {
|
|
6505
|
-
return (type.flags & (
|
|
6808
|
+
return (type.flags & (ts6.TypeFlags.StringLiteral | ts6.TypeFlags.NumberLiteral | ts6.TypeFlags.BooleanLiteral | ts6.TypeFlags.BigIntLiteral)) !== 0;
|
|
6506
6809
|
}
|
|
6507
6810
|
function isAnyType(type) {
|
|
6508
|
-
return (type.flags &
|
|
6811
|
+
return (type.flags & ts6.TypeFlags.Any) !== 0;
|
|
6509
6812
|
}
|
|
6510
6813
|
function isLetDeclaration(declaration) {
|
|
6511
|
-
return
|
|
6814
|
+
return ts6.isVariableDeclarationList(declaration.parent) && (declaration.parent.flags & ts6.NodeFlags.Let) !== 0;
|
|
6512
6815
|
}
|
|
6513
6816
|
function isConstDeclaration(declaration) {
|
|
6514
|
-
return
|
|
6817
|
+
return ts6.isVariableDeclarationList(declaration.parent) && (declaration.parent.flags & ts6.NodeFlags.Const) !== 0;
|
|
6515
6818
|
}
|
|
6516
6819
|
function descendants(root) {
|
|
6517
6820
|
const result = [];
|
|
6518
6821
|
const visit = (node) => {
|
|
6519
6822
|
result.push(node);
|
|
6520
|
-
|
|
6823
|
+
ts6.forEachChild(node, visit);
|
|
6521
6824
|
};
|
|
6522
|
-
|
|
6825
|
+
ts6.forEachChild(root, visit);
|
|
6523
6826
|
return result;
|
|
6524
6827
|
}
|
|
6525
6828
|
function descendantsOfKind2(root, predicate) {
|
|
@@ -6527,9 +6830,9 @@ function descendantsOfKind2(root, predicate) {
|
|
|
6527
6830
|
const visit = (node) => {
|
|
6528
6831
|
if (predicate(node))
|
|
6529
6832
|
result.push(node);
|
|
6530
|
-
|
|
6833
|
+
ts6.forEachChild(node, visit);
|
|
6531
6834
|
};
|
|
6532
|
-
|
|
6835
|
+
ts6.forEachChild(root, visit);
|
|
6533
6836
|
return result;
|
|
6534
6837
|
}
|
|
6535
6838
|
function makeDiagnostic(code, message, fileOrSourceFile, node, strict, rootDir) {
|
|
@@ -6550,7 +6853,7 @@ function normalizeRelative(rootDir, filePath) {
|
|
|
6550
6853
|
return relative3(rootDir, filePath).split(sep3).join("/").replace(/^\.\//, "");
|
|
6551
6854
|
}
|
|
6552
6855
|
function isAnyKeyword(node) {
|
|
6553
|
-
return node.kind ===
|
|
6856
|
+
return node.kind === ts6.SyntaxKind.AnyKeyword;
|
|
6554
6857
|
}
|
|
6555
6858
|
|
|
6556
6859
|
// src/route-contracts.ts
|
|
@@ -6634,15 +6937,28 @@ function validateRouteContracts(graph) {
|
|
|
6634
6937
|
}
|
|
6635
6938
|
|
|
6636
6939
|
// src/compile.ts
|
|
6940
|
+
function withDefaultGovernance(options) {
|
|
6941
|
+
return options.commandCapabilities === undefined ? { ...options, commandCapabilities: {
|
|
6942
|
+
requirePersistentAdapters: true,
|
|
6943
|
+
permission: true,
|
|
6944
|
+
audit: true,
|
|
6945
|
+
idempotency: true,
|
|
6946
|
+
transaction: true
|
|
6947
|
+
} } : options;
|
|
6948
|
+
}
|
|
6637
6949
|
async function renderOptionalGraphql(options) {
|
|
6638
6950
|
return options.graphql ? (await Promise.resolve().then(() => (init_graphql(), exports_graphql))).renderGraphql(options) : { diagnostics: [], files: {} };
|
|
6639
6951
|
}
|
|
6640
6952
|
async function compileProject(options) {
|
|
6953
|
+
options = withDefaultGovernance(options);
|
|
6641
6954
|
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
|
|
6642
6955
|
const diagnostics = [
|
|
6643
6956
|
...graph.diagnostics ?? [],
|
|
6644
6957
|
...validateGraph(graph, options)
|
|
6645
6958
|
];
|
|
6959
|
+
if (diagnostics.some((item) => item.code === "runtime-injection-disallowed")) {
|
|
6960
|
+
return { diagnostics, graph, written: [] };
|
|
6961
|
+
}
|
|
6646
6962
|
if (options.strict) {
|
|
6647
6963
|
for (const diagnostic of diagnostics) {
|
|
6648
6964
|
if (diagnostic.severity === "warn")
|
|
@@ -6696,11 +7012,15 @@ async function compileProject(options) {
|
|
|
6696
7012
|
return { diagnostics, graph, written, ...stats ? { stats } : {} };
|
|
6697
7013
|
}
|
|
6698
7014
|
async function checkProject(options) {
|
|
7015
|
+
options = withDefaultGovernance(options);
|
|
6699
7016
|
const graph = await analyzeProject(options.rootDir, options.include, options.cache, options.changedPaths);
|
|
6700
7017
|
const diagnostics = [
|
|
6701
7018
|
...graph.diagnostics ?? [],
|
|
6702
7019
|
...validateGraph(graph, options)
|
|
6703
7020
|
];
|
|
7021
|
+
if (diagnostics.some((item) => item.code === "runtime-injection-disallowed")) {
|
|
7022
|
+
return { diagnostics, graph, upToDate: false, mismatches: ["Runtime DI must be migrated to constructor injection."] };
|
|
7023
|
+
}
|
|
6704
7024
|
if (options.strict) {
|
|
6705
7025
|
for (const diagnostic of diagnostics) {
|
|
6706
7026
|
if (diagnostic.severity === "warn")
|
|
@@ -10722,6 +11042,13 @@ var DEFAULT_SUPACLOUD_CONFIG = {
|
|
|
10722
11042
|
generateOpenApi: true,
|
|
10723
11043
|
generatePermissions: true,
|
|
10724
11044
|
treeShakeUnusedProviders: true,
|
|
11045
|
+
commandCapabilities: {
|
|
11046
|
+
requirePersistentAdapters: true,
|
|
11047
|
+
permission: true,
|
|
11048
|
+
audit: true,
|
|
11049
|
+
idempotency: true,
|
|
11050
|
+
transaction: true
|
|
11051
|
+
},
|
|
10725
11052
|
moduleBoundaryPreset: "modular-monolith"
|
|
10726
11053
|
};
|
|
10727
11054
|
function defineSupacloudConfig(config = {}) {
|
|
@@ -10805,7 +11132,7 @@ function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
|
10805
11132
|
...resolved.openApi === undefined ? {} : { openApi: resolved.openApi },
|
|
10806
11133
|
generatePermissions: resolved.generatePermissions ?? DEFAULT_SUPACLOUD_CONFIG.generatePermissions,
|
|
10807
11134
|
moduleBoundaryPreset: resolved.moduleBoundaryPreset ?? DEFAULT_SUPACLOUD_CONFIG.moduleBoundaryPreset,
|
|
10808
|
-
commandCapabilities: resolved.commandCapabilities,
|
|
11135
|
+
commandCapabilities: resolved.commandCapabilities ?? DEFAULT_SUPACLOUD_CONFIG.commandCapabilities,
|
|
10809
11136
|
...resolved.moduleBoundaries ? { moduleBoundaries: resolved.moduleBoundaries } : {},
|
|
10810
11137
|
...resolved.typeSafety ? { typeSafety: resolved.typeSafety } : {},
|
|
10811
11138
|
...resolved.allowRouteCommandBindings === undefined ? {} : { allowRouteCommandBindings: resolved.allowRouteCommandBindings },
|
|
@@ -10839,7 +11166,7 @@ function compileOptionsFromConfig(config, cwd = process.cwd()) {
|
|
|
10839
11166
|
import { randomUUID } from "node:crypto";
|
|
10840
11167
|
import { lstat, readFile as readFile4, realpath, rename as rename2, unlink as unlink2, writeFile as writeFile2 } from "node:fs/promises";
|
|
10841
11168
|
import { dirname as dirname6, isAbsolute as isAbsolute2, relative as relative8, resolve as resolve8, sep as sep8 } from "node:path";
|
|
10842
|
-
import * as
|
|
11169
|
+
import * as ts9 from "@typescript/typescript6";
|
|
10843
11170
|
async function applyDiagnosticFix(fix, options = {}) {
|
|
10844
11171
|
if (!fix || typeof fix.targetFile !== "string")
|
|
10845
11172
|
throw new Error("Invalid DiagnosticFix");
|
|
@@ -10864,7 +11191,7 @@ async function applyDiagnosticFix(fix, options = {}) {
|
|
|
10864
11191
|
if (!current || current.initializer.getText(source) !== fix.expectedExpression) {
|
|
10865
11192
|
throw new Error("Command mode changed since diagnosis; analyze the project again");
|
|
10866
11193
|
}
|
|
10867
|
-
content = replaceProperty(source, object, fix.property,
|
|
11194
|
+
content = replaceProperty(source, object, fix.property, ts9.factory.createStringLiteral(fix.value));
|
|
10868
11195
|
break;
|
|
10869
11196
|
}
|
|
10870
11197
|
case "add_module_import": {
|
|
@@ -10875,15 +11202,15 @@ async function applyDiagnosticFix(fix, options = {}) {
|
|
|
10875
11202
|
source = parse3(file, withImport);
|
|
10876
11203
|
const object = unique(moduleObjects(source).filter((candidate) => !fix.targetModule || stringProperty(candidate, "name") === fix.targetModule), "target module");
|
|
10877
11204
|
const imports = property(object, "imports");
|
|
10878
|
-
if (imports && !
|
|
11205
|
+
if (imports && !ts9.isArrayLiteralExpression(imports.initializer)) {
|
|
10879
11206
|
throw new Error("Module imports must be a static array");
|
|
10880
11207
|
}
|
|
10881
|
-
const values = imports &&
|
|
10882
|
-
if (values.some(
|
|
11208
|
+
const values = imports && ts9.isArrayLiteralExpression(imports.initializer) ? imports.initializer.elements : [];
|
|
11209
|
+
if (values.some(ts9.isSpreadElement))
|
|
10883
11210
|
throw new Error("Module imports cannot contain spread elements");
|
|
10884
|
-
content = values.some((value) =>
|
|
11211
|
+
content = values.some((value) => ts9.isIdentifier(value) && value.text === fix.symbol) ? withImport : replaceProperty(source, object, "imports", ts9.factory.createArrayLiteralExpression([
|
|
10885
11212
|
...values,
|
|
10886
|
-
|
|
11213
|
+
ts9.factory.createIdentifier(fix.symbol)
|
|
10887
11214
|
]));
|
|
10888
11215
|
break;
|
|
10889
11216
|
}
|
|
@@ -10895,24 +11222,24 @@ async function applyDiagnosticFix(fix, options = {}) {
|
|
|
10895
11222
|
const command = findClass(source, fix.command);
|
|
10896
11223
|
const object = decoratorObject(command, "Command");
|
|
10897
11224
|
const current = property(object, "permission");
|
|
10898
|
-
if (current && (!
|
|
11225
|
+
if (current && (!ts9.isStringLiteral(current.initializer) || current.initializer.text !== permission)) {
|
|
10899
11226
|
throw new Error("Command permission already exists with a different value");
|
|
10900
11227
|
}
|
|
10901
|
-
content = current ? original : replaceProperty(source, object, "permission",
|
|
11228
|
+
content = current ? original : replaceProperty(source, object, "permission", ts9.factory.createStringLiteral(permission));
|
|
10902
11229
|
break;
|
|
10903
11230
|
}
|
|
10904
11231
|
case "add_route_parameter_binding": {
|
|
10905
11232
|
const controller = findClass(source, fix.controller);
|
|
10906
|
-
const method = unique(controller.members.filter((member) =>
|
|
10907
|
-
const parameter = unique(method.parameters.filter((candidate) =>
|
|
11233
|
+
const method = unique(controller.members.filter((member) => ts9.isMethodDeclaration(member) && nameOf(member.name) === fix.route), "route handler");
|
|
11234
|
+
const parameter = unique(method.parameters.filter((candidate) => ts9.isIdentifier(candidate.name) && candidate.name.text === fix.parameter), "same-named route parameter");
|
|
10908
11235
|
const binding = fix.binding === "param" ? "Param" : fix.binding === "query" ? "Query" : undefined;
|
|
10909
11236
|
if (!binding)
|
|
10910
11237
|
throw new Error("Invalid route binding");
|
|
10911
|
-
const decorators =
|
|
11238
|
+
const decorators = ts9.getDecorators(parameter) ?? [];
|
|
10912
11239
|
if (decorators.length > 0)
|
|
10913
11240
|
throw new Error("Parameter already has a decorator");
|
|
10914
|
-
const framework = unique(source.statements.filter((statement) =>
|
|
10915
|
-
if (!
|
|
11241
|
+
const framework = unique(source.statements.filter((statement) => ts9.isImportDeclaration(statement) && statement.importClause?.namedBindings && ts9.isNamedImports(statement.importClause.namedBindings) && statement.importClause.namedBindings.elements.some((element) => ["Controller", "Get", "Post", "Put", "Patch", "Delete", "Head", "Options"].includes(element.name.text))), "framework import");
|
|
11242
|
+
if (!ts9.isImportDeclaration(framework) || !ts9.isStringLiteral(framework.moduleSpecifier)) {
|
|
10916
11243
|
throw new Error("Framework import must be static");
|
|
10917
11244
|
}
|
|
10918
11245
|
const edited = original.slice(0, parameter.getStart(source)) + `@${binding}(${JSON.stringify(fix.parameter)}) ` + original.slice(parameter.getStart(source));
|
|
@@ -10938,15 +11265,15 @@ async function applyDiagnosticFix(fix, options = {}) {
|
|
|
10938
11265
|
return result;
|
|
10939
11266
|
}
|
|
10940
11267
|
function parse3(file, text) {
|
|
10941
|
-
const result =
|
|
11268
|
+
const result = ts9.transpileModule(text, {
|
|
10942
11269
|
fileName: file,
|
|
10943
11270
|
reportDiagnostics: true,
|
|
10944
|
-
compilerOptions: { target:
|
|
11271
|
+
compilerOptions: { target: ts9.ScriptTarget.ESNext, experimentalDecorators: true }
|
|
10945
11272
|
});
|
|
10946
|
-
if (result.diagnostics?.some((item) => item.category ===
|
|
11273
|
+
if (result.diagnostics?.some((item) => item.category === ts9.DiagnosticCategory.Error)) {
|
|
10947
11274
|
throw new Error("Cannot fix syntactically invalid TypeScript");
|
|
10948
11275
|
}
|
|
10949
|
-
return
|
|
11276
|
+
return ts9.createSourceFile(file, text, ts9.ScriptTarget.Latest, true, ts9.ScriptKind.TS);
|
|
10950
11277
|
}
|
|
10951
11278
|
function unique(items, description) {
|
|
10952
11279
|
if (items.length !== 1)
|
|
@@ -10960,50 +11287,50 @@ function identifier(value) {
|
|
|
10960
11287
|
function nameOf(name) {
|
|
10961
11288
|
if (!name)
|
|
10962
11289
|
return "";
|
|
10963
|
-
return
|
|
11290
|
+
return ts9.isIdentifier(name) || ts9.isStringLiteral(name) || ts9.isNumericLiteral(name) ? name.text : "";
|
|
10964
11291
|
}
|
|
10965
11292
|
function property(object, key) {
|
|
10966
|
-
if (object.properties.some((item) => !
|
|
11293
|
+
if (object.properties.some((item) => !ts9.isPropertyAssignment(item) || ts9.isComputedPropertyName(item.name))) {
|
|
10967
11294
|
throw new Error("Fix requires explicit static object properties");
|
|
10968
11295
|
}
|
|
10969
|
-
const values = object.properties.filter((item) =>
|
|
11296
|
+
const values = object.properties.filter((item) => ts9.isPropertyAssignment(item) && nameOf(item.name) === key);
|
|
10970
11297
|
if (values.length > 1)
|
|
10971
11298
|
throw new Error(`Duplicate '${key}' property`);
|
|
10972
11299
|
return values[0];
|
|
10973
11300
|
}
|
|
10974
11301
|
function stringProperty(object, key) {
|
|
10975
11302
|
const value = property(object, key)?.initializer;
|
|
10976
|
-
return value &&
|
|
11303
|
+
return value && ts9.isStringLiteral(value) ? value.text : undefined;
|
|
10977
11304
|
}
|
|
10978
11305
|
function replaceProperty(source, object, key, value) {
|
|
10979
11306
|
const previous = property(object, key);
|
|
10980
|
-
const replacement =
|
|
11307
|
+
const replacement = ts9.factory.createPropertyAssignment(key, value);
|
|
10981
11308
|
const properties = object.properties.map((item) => item === previous ? replacement : item);
|
|
10982
11309
|
if (!previous)
|
|
10983
11310
|
properties.push(replacement);
|
|
10984
|
-
const updated =
|
|
10985
|
-
return source.text.slice(0, object.getStart(source)) +
|
|
11311
|
+
const updated = ts9.factory.updateObjectLiteralExpression(object, properties);
|
|
11312
|
+
return source.text.slice(0, object.getStart(source)) + ts9.createPrinter().printNode(ts9.EmitHint.Expression, updated, source) + source.text.slice(object.end);
|
|
10986
11313
|
}
|
|
10987
11314
|
function findClass(source, name) {
|
|
10988
|
-
return unique(source.statements.filter((statement) =>
|
|
11315
|
+
return unique(source.statements.filter((statement) => ts9.isClassDeclaration(statement) && statement.name?.text === name), `class '${name}'`);
|
|
10989
11316
|
}
|
|
10990
11317
|
function decoratorObject(node, name) {
|
|
10991
|
-
const decorator = unique((
|
|
10992
|
-
const argument =
|
|
10993
|
-
if (!argument || !
|
|
11318
|
+
const decorator = unique((ts9.getDecorators(node) ?? []).filter((item) => ts9.isCallExpression(item.expression) && item.expression.expression.getText() === name), `@${name} decorator`);
|
|
11319
|
+
const argument = ts9.isCallExpression(decorator.expression) ? decorator.expression.arguments[0] : undefined;
|
|
11320
|
+
if (!argument || !ts9.isObjectLiteralExpression(argument))
|
|
10994
11321
|
throw new Error(`@${name} requires a static object`);
|
|
10995
11322
|
return argument;
|
|
10996
11323
|
}
|
|
10997
11324
|
function moduleObjects(source) {
|
|
10998
11325
|
const result = [];
|
|
10999
11326
|
for (const statement of source.statements) {
|
|
11000
|
-
if (
|
|
11327
|
+
if (ts9.isClassDeclaration(statement) && (ts9.getDecorators(statement) ?? []).some((item) => ts9.isCallExpression(item.expression) && item.expression.expression.getText() === "Module")) {
|
|
11001
11328
|
result.push(decoratorObject(statement, "Module"));
|
|
11002
11329
|
}
|
|
11003
|
-
if (
|
|
11330
|
+
if (ts9.isVariableStatement(statement)) {
|
|
11004
11331
|
for (const declaration of statement.declarationList.declarations) {
|
|
11005
11332
|
const call = declaration.initializer;
|
|
11006
|
-
if (call &&
|
|
11333
|
+
if (call && ts9.isCallExpression(call) && ["defineModule", "defineFeatureSlice"].includes(call.expression.getText()) && call.arguments[0] && ts9.isObjectLiteralExpression(call.arguments[0])) {
|
|
11007
11334
|
result.push(call.arguments[0]);
|
|
11008
11335
|
}
|
|
11009
11336
|
}
|
|
@@ -11017,19 +11344,19 @@ function importSymbol(source, path, symbol) {
|
|
|
11017
11344
|
const target = resolve8(dirname6(source.fileName), path).replace(/\.(tsx?|mts|cts)$/, "");
|
|
11018
11345
|
if (current === target)
|
|
11019
11346
|
return source.text;
|
|
11020
|
-
const matches = source.statements.filter((item) =>
|
|
11347
|
+
const matches = source.statements.filter((item) => ts9.isImportDeclaration(item) && ts9.isStringLiteral(item.moduleSpecifier) && item.moduleSpecifier.text === path);
|
|
11021
11348
|
if (matches.length > 1)
|
|
11022
11349
|
throw new Error(`Ambiguous imports from '${path}'`);
|
|
11023
11350
|
const match = matches[0];
|
|
11024
|
-
if (match &&
|
|
11351
|
+
if (match && ts9.isImportDeclaration(match) && match.importClause?.namedBindings && ts9.isNamedImports(match.importClause.namedBindings) && !match.importClause.isTypeOnly) {
|
|
11025
11352
|
if (match.importClause.namedBindings.elements.some((item) => item.name.text === symbol))
|
|
11026
11353
|
return source.text;
|
|
11027
11354
|
const bindings = match.importClause.namedBindings;
|
|
11028
|
-
const updated =
|
|
11355
|
+
const updated = ts9.factory.updateNamedImports(bindings, [
|
|
11029
11356
|
...bindings.elements,
|
|
11030
|
-
|
|
11357
|
+
ts9.factory.createImportSpecifier(false, undefined, ts9.factory.createIdentifier(symbol))
|
|
11031
11358
|
]);
|
|
11032
|
-
return source.text.slice(0, bindings.getStart(source)) +
|
|
11359
|
+
return source.text.slice(0, bindings.getStart(source)) + ts9.createPrinter().printNode(ts9.EmitHint.Unspecified, updated, source) + source.text.slice(bindings.end);
|
|
11033
11360
|
}
|
|
11034
11361
|
if (match)
|
|
11035
11362
|
throw new Error(`Import from '${path}' is not a named value import`);
|
|
@@ -11276,7 +11603,7 @@ function formatDeliveryPlan(result) {
|
|
|
11276
11603
|
import { mkdir as mkdir3, mkdtemp, readFile as readFile7, realpath as realpath3, rename as rename4, rm as rm2 } from "node:fs/promises";
|
|
11277
11604
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
11278
11605
|
import { dirname as dirname8, join as join8, relative as relative10, resolve as resolve11, sep as sep10 } from "node:path";
|
|
11279
|
-
import * as
|
|
11606
|
+
import * as ts11 from "@typescript/typescript6";
|
|
11280
11607
|
|
|
11281
11608
|
// src/delivery-files.ts
|
|
11282
11609
|
import { createHash as createHash8 } from "node:crypto";
|
|
@@ -11510,19 +11837,19 @@ function renderDeliveryTarget(graph, target, options) {
|
|
|
11510
11837
|
import { isBuiltin } from "node:module";
|
|
11511
11838
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
11512
11839
|
import { resolve as resolve10 } from "node:path";
|
|
11513
|
-
import * as
|
|
11840
|
+
import * as ts10 from "@typescript/typescript6";
|
|
11514
11841
|
function checkStaticImports(path, contents) {
|
|
11515
11842
|
if (!/\.[cm]?[jt]sx?$/.test(path))
|
|
11516
11843
|
return;
|
|
11517
|
-
const source =
|
|
11844
|
+
const source = ts10.createSourceFile(path, new TextDecoder().decode(contents), ts10.ScriptTarget.Latest, true);
|
|
11518
11845
|
function visit(node) {
|
|
11519
|
-
if (
|
|
11846
|
+
if (ts10.isCallExpression(node) && (node.expression.kind === ts10.SyntaxKind.ImportKeyword || ts10.isIdentifier(node.expression) && node.expression.text === "require")) {
|
|
11520
11847
|
const argument = node.arguments[0];
|
|
11521
|
-
if (!argument || !
|
|
11848
|
+
if (!argument || !ts10.isStringLiteral(argument) && !ts10.isNoSubstitutionTemplateLiteral(argument)) {
|
|
11522
11849
|
throw new Error("Computed module loading is not supported in independent delivery bundles.");
|
|
11523
11850
|
}
|
|
11524
11851
|
}
|
|
11525
|
-
|
|
11852
|
+
ts10.forEachChild(node, visit);
|
|
11526
11853
|
}
|
|
11527
11854
|
visit(source);
|
|
11528
11855
|
}
|
|
@@ -11611,7 +11938,7 @@ async function buildDeliveryProject(options, delivery) {
|
|
|
11611
11938
|
const settings = parseDeliveryOptions(delivery);
|
|
11612
11939
|
if (typeof Bun === "undefined")
|
|
11613
11940
|
throw new Error("Independent delivery builds require Bun.");
|
|
11614
|
-
const configPath =
|
|
11941
|
+
const configPath = ts11.findConfigFile(resolve11(options.rootDir), ts11.sys.fileExists);
|
|
11615
11942
|
if (!configPath)
|
|
11616
11943
|
throw new Error("Independent delivery builds require a project tsconfig.json.");
|
|
11617
11944
|
const lexicalProject = dirname8(configPath);
|
|
@@ -11627,8 +11954,8 @@ async function buildDeliveryProject(options, delivery) {
|
|
|
11627
11954
|
if (inside(generatedRoot, sourceRoot))
|
|
11628
11955
|
throw new Error("Output must not contain the application source root.");
|
|
11629
11956
|
const configInputs = new Map;
|
|
11630
|
-
const parsedConfig =
|
|
11631
|
-
...
|
|
11957
|
+
const parsedConfig = ts11.getParsedCommandLineOfConfigFile(await realpath3(configPath), {}, {
|
|
11958
|
+
...ts11.sys,
|
|
11632
11959
|
readFile(path) {
|
|
11633
11960
|
const contents = readFileSync3(path, "utf8");
|
|
11634
11961
|
configInputs.set(resolve11(path), digest(contents));
|
|
@@ -11714,7 +12041,7 @@ async function buildDeliveryProject(options, delivery) {
|
|
|
11714
12041
|
for (const [name, contents] of Object.entries(generated))
|
|
11715
12042
|
await writeArtifact(stage, `generated/${name}`, contents);
|
|
11716
12043
|
{
|
|
11717
|
-
const program =
|
|
12044
|
+
const program = ts11.createProgram({
|
|
11718
12045
|
rootNames: [
|
|
11719
12046
|
...parsedConfig.fileNames.filter((path) => !inside(generatedRoot, path)),
|
|
11720
12047
|
...Object.keys(generated).map((name) => join8(stage, "generated", name))
|
|
@@ -11722,12 +12049,12 @@ async function buildDeliveryProject(options, delivery) {
|
|
|
11722
12049
|
options: { ...parsedConfig.options, rootDir: project },
|
|
11723
12050
|
...parsedConfig.projectReferences ? { projectReferences: parsedConfig.projectReferences } : {}
|
|
11724
12051
|
});
|
|
11725
|
-
const diagnostics =
|
|
11726
|
-
if (diagnostics.some((item) => item.category ===
|
|
11727
|
-
return failed(diagnostics.filter((item) => item.category ===
|
|
12052
|
+
const diagnostics = ts11.getPreEmitDiagnostics(program);
|
|
12053
|
+
if (diagnostics.some((item) => item.category === ts11.DiagnosticCategory.Error)) {
|
|
12054
|
+
return failed(diagnostics.filter((item) => item.category === ts11.DiagnosticCategory.Error).map((item) => ({
|
|
11728
12055
|
severity: "error",
|
|
11729
12056
|
code: "delivery-generated-type-error",
|
|
11730
|
-
message:
|
|
12057
|
+
message: ts11.flattenDiagnosticMessageText(item.messageText, `
|
|
11731
12058
|
`),
|
|
11732
12059
|
...item.file ? { file: item.file.fileName } : {}
|
|
11733
12060
|
})));
|
|
@@ -11859,7 +12186,7 @@ async function buildDeliveryProject(options, delivery) {
|
|
|
11859
12186
|
// src/migrations.ts
|
|
11860
12187
|
import { rename as rename5, readFile as readFile9, writeFile as writeFile4, rm as rm3 } from "node:fs/promises";
|
|
11861
12188
|
import { relative as relative11, resolve as resolve13 } from "node:path";
|
|
11862
|
-
import * as
|
|
12189
|
+
import * as ts12 from "@typescript/typescript6";
|
|
11863
12190
|
|
|
11864
12191
|
// src/migration-policy.ts
|
|
11865
12192
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
@@ -11878,9 +12205,9 @@ function compilerVersion() {
|
|
|
11878
12205
|
}
|
|
11879
12206
|
function migrationDependencies() {
|
|
11880
12207
|
return {
|
|
11881
|
-
"@supacloud/app": "0.
|
|
12208
|
+
"@supacloud/app": "0.15.0",
|
|
11882
12209
|
"@supacloud/compiler": compilerVersion(),
|
|
11883
|
-
"@supacloud/elysia": "0.
|
|
12210
|
+
"@supacloud/elysia": "0.18.0",
|
|
11884
12211
|
elysia: "1.4.30",
|
|
11885
12212
|
typescript: "7.0.2"
|
|
11886
12213
|
};
|
|
@@ -11903,29 +12230,29 @@ async function checkMigrationDependencies(rootDir) {
|
|
|
11903
12230
|
// src/migrations.ts
|
|
11904
12231
|
var ROUTE_DECORATORS2 = new Set(["Get", "Post", "Put", "Patch", "Delete", "Head", "Options"]);
|
|
11905
12232
|
var MIGRATION_COMPILER_OPTIONS = {
|
|
11906
|
-
target:
|
|
11907
|
-
module:
|
|
11908
|
-
moduleResolution:
|
|
12233
|
+
target: ts12.ScriptTarget.ES2022,
|
|
12234
|
+
module: ts12.ModuleKind.ESNext,
|
|
12235
|
+
moduleResolution: ts12.ModuleResolutionKind.Bundler,
|
|
11909
12236
|
noEmit: true,
|
|
11910
12237
|
skipLibCheck: true
|
|
11911
12238
|
};
|
|
11912
12239
|
function migrationCompilerOptions(rootDir) {
|
|
11913
12240
|
if (!rootDir)
|
|
11914
12241
|
return MIGRATION_COMPILER_OPTIONS;
|
|
11915
|
-
const configPath =
|
|
12242
|
+
const configPath = ts12.findConfigFile(rootDir, ts12.sys.fileExists);
|
|
11916
12243
|
if (!configPath)
|
|
11917
12244
|
return MIGRATION_COMPILER_OPTIONS;
|
|
11918
12245
|
const configHost = {
|
|
11919
|
-
...
|
|
12246
|
+
...ts12.sys,
|
|
11920
12247
|
onUnRecoverableConfigFileDiagnostic: (_diagnostic) => {}
|
|
11921
12248
|
};
|
|
11922
|
-
const parsed =
|
|
12249
|
+
const parsed = ts12.getParsedCommandLineOfConfigFile(configPath, {}, configHost);
|
|
11923
12250
|
if (!parsed || parsed.errors.length > 0)
|
|
11924
12251
|
return MIGRATION_COMPILER_OPTIONS;
|
|
11925
12252
|
return { ...parsed.options, noEmit: true, skipLibCheck: true };
|
|
11926
12253
|
}
|
|
11927
12254
|
function propertyName2(property) {
|
|
11928
|
-
if (
|
|
12255
|
+
if (ts12.isIdentifier(property) || ts12.isStringLiteral(property) || ts12.isNumericLiteral(property))
|
|
11929
12256
|
return property.text;
|
|
11930
12257
|
return;
|
|
11931
12258
|
}
|
|
@@ -11935,7 +12262,7 @@ function lineOf2(sourceFile, node) {
|
|
|
11935
12262
|
function resolveSymbol(symbol, checker) {
|
|
11936
12263
|
if (!symbol)
|
|
11937
12264
|
return;
|
|
11938
|
-
for (let guard = 0;guard < 4 && (symbol.flags &
|
|
12265
|
+
for (let guard = 0;guard < 4 && (symbol.flags & ts12.SymbolFlags.Alias) !== 0; guard += 1) {
|
|
11939
12266
|
const aliased = checker.getAliasedSymbol(symbol);
|
|
11940
12267
|
if (aliased === symbol)
|
|
11941
12268
|
break;
|
|
@@ -11944,12 +12271,12 @@ function resolveSymbol(symbol, checker) {
|
|
|
11944
12271
|
return symbol;
|
|
11945
12272
|
}
|
|
11946
12273
|
function symbolForExpression(expression, checker) {
|
|
11947
|
-
const location =
|
|
12274
|
+
const location = ts12.isIdentifier(expression) ? expression : ts12.isPropertyAccessExpression(expression) ? expression.name : ts12.isElementAccessExpression(expression) && expression.argumentExpression && ts12.isStringLiteral(expression.argumentExpression) ? expression : undefined;
|
|
11948
12275
|
return location ? resolveSymbol(checker.getSymbolAtLocation(location), checker) : undefined;
|
|
11949
12276
|
}
|
|
11950
12277
|
function unwrapExpression(expression) {
|
|
11951
12278
|
let current = expression;
|
|
11952
|
-
while (
|
|
12279
|
+
while (ts12.isAsExpression(current) || ts12.isSatisfiesExpression(current) || ts12.isParenthesizedExpression(current) || ts12.isTypeAssertionExpression(current)) {
|
|
11953
12280
|
current = current.expression;
|
|
11954
12281
|
}
|
|
11955
12282
|
return current;
|
|
@@ -11962,7 +12289,7 @@ function isDefineRouteContractCall(node, checker) {
|
|
|
11962
12289
|
}
|
|
11963
12290
|
function isRouteDecoratorCall(node, checker) {
|
|
11964
12291
|
const name = node.expression.getText(node.getSourceFile());
|
|
11965
|
-
if (
|
|
12292
|
+
if (ts12.isIdentifier(node.expression) && ROUTE_DECORATORS2.has(node.expression.text))
|
|
11966
12293
|
return true;
|
|
11967
12294
|
if (ROUTE_DECORATORS2.has(name))
|
|
11968
12295
|
return true;
|
|
@@ -11976,14 +12303,14 @@ function resolveStaticObjectLiteral2(input, checker, seen = new Set) {
|
|
|
11976
12303
|
if (seen.has(expression))
|
|
11977
12304
|
return;
|
|
11978
12305
|
seen.add(expression);
|
|
11979
|
-
if (
|
|
12306
|
+
if (ts12.isObjectLiteralExpression(expression))
|
|
11980
12307
|
return expression;
|
|
11981
|
-
if (
|
|
12308
|
+
if (ts12.isCallExpression(expression) && isDefineRouteContractCall(expression, checker)) {
|
|
11982
12309
|
return resolveStaticObjectLiteral2(expression.arguments[0], checker, seen);
|
|
11983
12310
|
}
|
|
11984
12311
|
const symbol = symbolForExpression(expression, checker);
|
|
11985
12312
|
for (const declaration of symbol?.declarations ?? []) {
|
|
11986
|
-
if (
|
|
12313
|
+
if (ts12.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
11987
12314
|
const resolved = resolveStaticObjectLiteral2(declaration.initializer, checker, seen);
|
|
11988
12315
|
if (resolved)
|
|
11989
12316
|
return resolved;
|
|
@@ -11997,7 +12324,7 @@ function displayFile(rootDir, fileName) {
|
|
|
11997
12324
|
}
|
|
11998
12325
|
function createMigrationProgram(fileNames, sourceOverrides, rootDir) {
|
|
11999
12326
|
const compilerOptions = migrationCompilerOptions(rootDir);
|
|
12000
|
-
const host =
|
|
12327
|
+
const host = ts12.createCompilerHost(compilerOptions);
|
|
12001
12328
|
const getSourceFile = host.getSourceFile.bind(host);
|
|
12002
12329
|
const fileExists = host.fileExists.bind(host);
|
|
12003
12330
|
const readFile = host.readFile.bind(host);
|
|
@@ -12006,16 +12333,16 @@ function createMigrationProgram(fileNames, sourceOverrides, rootDir) {
|
|
|
12006
12333
|
host.readFile = (fileName) => sourceOverrides.get(resolve13(fileName)) ?? readFile(fileName);
|
|
12007
12334
|
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
|
|
12008
12335
|
const source = sourceOverrides.get(resolve13(fileName));
|
|
12009
|
-
return source === undefined ? getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) :
|
|
12336
|
+
return source === undefined ? getSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile) : ts12.createSourceFile(fileName, source, languageVersion, true);
|
|
12010
12337
|
};
|
|
12011
12338
|
host.getCurrentDirectory = () => rootDir ?? currentDirectory();
|
|
12012
|
-
return
|
|
12339
|
+
return ts12.createProgram(fileNames, compilerOptions, host);
|
|
12013
12340
|
}
|
|
12014
12341
|
function routeResponseProperties(object) {
|
|
12015
|
-
const responseProperties = object.properties.filter((property) =>
|
|
12342
|
+
const responseProperties = object.properties.filter((property) => ts12.isPropertyAssignment(property) && propertyName2(property.name) === "response");
|
|
12016
12343
|
return {
|
|
12017
12344
|
response: responseProperties[0],
|
|
12018
|
-
hasResponses: object.properties.some((property) => (
|
|
12345
|
+
hasResponses: object.properties.some((property) => (ts12.isPropertyAssignment(property) || ts12.isShorthandPropertyAssignment(property)) && propertyName2(property.name) === "responses"),
|
|
12019
12346
|
duplicateResponse: responseProperties.length > 1
|
|
12020
12347
|
};
|
|
12021
12348
|
}
|
|
@@ -12035,7 +12362,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
12035
12362
|
if (!includedFiles.has(sourcePath))
|
|
12036
12363
|
continue;
|
|
12037
12364
|
const visit = (node) => {
|
|
12038
|
-
if (
|
|
12365
|
+
if (ts12.isCallExpression(node) && isRouteDecoratorCall(node, checker)) {
|
|
12039
12366
|
const options = node.arguments[1];
|
|
12040
12367
|
const object = options && resolveStaticObjectLiteral2(options, checker);
|
|
12041
12368
|
if (object) {
|
|
@@ -12055,7 +12382,7 @@ function planRouteResponseMigration(sourceFiles, checker, rootDir, includedFiles
|
|
|
12055
12382
|
}
|
|
12056
12383
|
}
|
|
12057
12384
|
}
|
|
12058
|
-
|
|
12385
|
+
ts12.forEachChild(node, visit);
|
|
12059
12386
|
};
|
|
12060
12387
|
visit(sourceFile);
|
|
12061
12388
|
}
|
|
@@ -12215,7 +12542,7 @@ async function migrateProject(options) {
|
|
|
12215
12542
|
}
|
|
12216
12543
|
}
|
|
12217
12544
|
const include = options.include ?? ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"];
|
|
12218
|
-
const files =
|
|
12545
|
+
const files = ts12.sys.readDirectory(rootDir, [".ts", ".tsx", ".mts", ".cts"], ["node_modules", "dist", "generated"], include).sort();
|
|
12219
12546
|
const results = [];
|
|
12220
12547
|
const issues = [];
|
|
12221
12548
|
const pendingWrites = new Map;
|
|
@@ -12827,6 +13154,7 @@ Usage:
|
|
|
12827
13154
|
supacloud-compiler openapi-diff <base.json> <current.json> [options]
|
|
12828
13155
|
supacloud-compiler fix <fix.json> [options]
|
|
12829
13156
|
supacloud-compiler graphql-schema --url <project-url> --key-env <name> [--token-env <name>]
|
|
13157
|
+
supacloud-compiler database-contracts <config.json> [--check]
|
|
12830
13158
|
|
|
12831
13159
|
Commands:
|
|
12832
13160
|
compile Compile application modules and generate artifacts
|
|
@@ -12878,6 +13206,18 @@ async function run() {
|
|
|
12878
13206
|
process.exit(0);
|
|
12879
13207
|
}
|
|
12880
13208
|
const command = args[0];
|
|
13209
|
+
if (command === "database-contracts") {
|
|
13210
|
+
const path = args[1];
|
|
13211
|
+
if (!path || path.startsWith("-") || args.slice(2).some((arg) => arg !== "--check")) {
|
|
13212
|
+
throw new Error("database-contracts requires <config.json> and optional --check");
|
|
13213
|
+
}
|
|
13214
|
+
await Promise.resolve().then(() => init_database_contracts());
|
|
13215
|
+
const result = await runDatabaseContractsFile(path, args.includes("--check"));
|
|
13216
|
+
console.log(JSON.stringify(result, null, 2));
|
|
13217
|
+
if (args.includes("--check") && !result.upToDate)
|
|
13218
|
+
process.exitCode = 1;
|
|
13219
|
+
return;
|
|
13220
|
+
}
|
|
12881
13221
|
if (!command || !["compile", "check", "dev", "graph", "explain", "context", "doctor", "migrate", "fix", "graphql-schema", "plan", "build-delivery", "openapi-export", "openapi-diff"].includes(command)) {
|
|
12882
13222
|
console.error(`Error: unknown command "${command}"`);
|
|
12883
13223
|
printUsage();
|
|
@@ -13049,7 +13389,7 @@ async function run() {
|
|
|
13049
13389
|
const currentPath = openApiDiffPaths[1];
|
|
13050
13390
|
if (!basePath || !currentPath)
|
|
13051
13391
|
throw new Error("openapi-diff requires two JSON file paths");
|
|
13052
|
-
const result = diffOpenApiDocuments(await readOpenApiJson(
|
|
13392
|
+
const result = diffOpenApiDocuments(await readOpenApiJson(resolve17(process.cwd(), basePath)), await readOpenApiJson(resolve17(process.cwd(), currentPath)));
|
|
13053
13393
|
console.log(json ? JSON.stringify(result, null, 2) : formatOpenApiDiff(result));
|
|
13054
13394
|
if (!result.ok)
|
|
13055
13395
|
process.exitCode = 1;
|
|
@@ -13064,8 +13404,8 @@ async function run() {
|
|
|
13064
13404
|
if (!modulePath || !outputPath)
|
|
13065
13405
|
throw new Error("openapi-export requires an OpenAPI module and output path");
|
|
13066
13406
|
const result = await exportGeneratedOpenApiJson({
|
|
13067
|
-
modulePath:
|
|
13068
|
-
outputPath:
|
|
13407
|
+
modulePath: resolve17(process.cwd(), modulePath),
|
|
13408
|
+
outputPath: resolve17(process.cwd(), outputPath),
|
|
13069
13409
|
...openApiExportSpace === undefined ? {} : { space: openApiExportSpace }
|
|
13070
13410
|
});
|
|
13071
13411
|
console.log(json ? JSON.stringify({ ok: true, ...result }, null, 2) : result.written ? `OpenAPI JSON written: ${result.path}` : `OpenAPI JSON matches: ${result.path}`);
|
|
@@ -13073,7 +13413,7 @@ async function run() {
|
|
|
13073
13413
|
}
|
|
13074
13414
|
if (command === "migrate") {
|
|
13075
13415
|
const result = await migrateProject({
|
|
13076
|
-
rootDir: rootDir ?
|
|
13416
|
+
rootDir: rootDir ? resolve17(process.cwd(), rootDir) : process.cwd(),
|
|
13077
13417
|
write: !dryRun,
|
|
13078
13418
|
...fromVersion === undefined ? {} : { fromVersion },
|
|
13079
13419
|
...toVersion === undefined ? {} : { toVersion }
|
|
@@ -13101,8 +13441,8 @@ async function run() {
|
|
|
13101
13441
|
if (checkSchema && command !== "graphql-schema")
|
|
13102
13442
|
throw new Error("--check is only supported by graphql-schema");
|
|
13103
13443
|
const defaults = resolveSupacloudConfig(loadedConfig, process.cwd());
|
|
13104
|
-
const resolvedRoot = rootDir ?
|
|
13105
|
-
const resolvedOut = outDir ?
|
|
13444
|
+
const resolvedRoot = rootDir ? resolve17(process.cwd(), rootDir) : defaults.rootDir;
|
|
13445
|
+
const resolvedOut = outDir ? resolve17(process.cwd(), outDir) : defaults.outDir;
|
|
13106
13446
|
const configured = compileOptionsFromConfig({
|
|
13107
13447
|
...loadedConfig,
|
|
13108
13448
|
root: resolvedRoot,
|
|
@@ -13121,7 +13461,7 @@ async function run() {
|
|
|
13121
13461
|
let delivery = loadedConfig.delivery;
|
|
13122
13462
|
if (deliveryPath !== undefined) {
|
|
13123
13463
|
try {
|
|
13124
|
-
delivery = JSON.parse(await
|
|
13464
|
+
delivery = JSON.parse(await readFile13(resolve17(process.cwd(), deliveryPath), "utf8"));
|
|
13125
13465
|
} catch {
|
|
13126
13466
|
throw new DeliveryConfigurationError;
|
|
13127
13467
|
}
|
|
@@ -13168,7 +13508,7 @@ ${item.suggestion ?? ""}`).join(`
|
|
|
13168
13508
|
} else if (command === "fix") {
|
|
13169
13509
|
if (!query)
|
|
13170
13510
|
throw new Error("fix requires a JSON file containing one DiagnosticFix");
|
|
13171
|
-
const fix = JSON.parse(await
|
|
13511
|
+
const fix = JSON.parse(await readFile13(resolve17(process.cwd(), query), "utf8"));
|
|
13172
13512
|
const result = await applyDiagnosticFix(fix, { rootDir: resolvedRoot, dryRun });
|
|
13173
13513
|
console.log(JSON.stringify({ ok: true, ...result }, null, 2));
|
|
13174
13514
|
} else if (command === "compile") {
|