@supacloud/compiler 0.6.1 → 0.7.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 +40 -0
- package/dist/cli.js +139 -51
- package/dist/config.d.ts +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +103 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,46 @@ SupaCloud 应用静态编译器:读取 `@supacloud/app` 装饰器元数据的
|
|
|
10
10
|
bun add @supacloud/compiler
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## 零配置项目
|
|
14
|
+
|
|
15
|
+
在项目根目录执行:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bunx supacloud-compiler compile
|
|
19
|
+
bunx supacloud-compiler dev
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
默认约定如下:
|
|
23
|
+
|
|
24
|
+
| 配置 | 默认值 |
|
|
25
|
+
| --- | --- |
|
|
26
|
+
| 源码目录 | `src` |
|
|
27
|
+
| 生成目录 | `generated` |
|
|
28
|
+
| 文件发现 | `**/*.module.ts`、`**/*.ts` |
|
|
29
|
+
| strict 类型安全门 | 开启 |
|
|
30
|
+
| typed client | 开启 |
|
|
31
|
+
| permissions manifest | 开启 |
|
|
32
|
+
| module boundary preset | `modular-monolith` |
|
|
33
|
+
| provider tree-shaking | 开启 |
|
|
34
|
+
|
|
35
|
+
需要覆盖默认值时,在项目根目录添加 `supacloud.config.ts`:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { defineSupacloudConfig } from "@supacloud/compiler";
|
|
39
|
+
|
|
40
|
+
export default defineSupacloudConfig({
|
|
41
|
+
root: "src",
|
|
42
|
+
outDir: "generated",
|
|
43
|
+
strict: true,
|
|
44
|
+
generateClient: true,
|
|
45
|
+
generatePermissions: true,
|
|
46
|
+
moduleBoundaryPreset: "modular-monolith",
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
命令行参数优先级高于配置文件。`--no-strict`、`--no-client` 和
|
|
51
|
+
`--no-permissions` 只建议用于本地迁移或调试;生产 CI 应保留默认 strict。
|
|
52
|
+
|
|
13
53
|
## API
|
|
14
54
|
|
|
15
55
|
```ts
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { resolve as
|
|
4
|
+
import { resolve as resolve6 } from "node:path";
|
|
5
5
|
|
|
6
6
|
// src/analyze.ts
|
|
7
7
|
import { createHash as createHash3 } from "node:crypto";
|
|
@@ -3056,13 +3056,9 @@ function renderClient(graph, _options) {
|
|
|
3056
3056
|
title: route.title,
|
|
3057
3057
|
data: route.data
|
|
3058
3058
|
});
|
|
3059
|
+
const routeParams = route.pathParams && route.pathParams.length > 0 || (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).length > 0 ? `{ ${(route.pathParams && route.pathParams.length > 0 ? route.pathParams : (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).map((p) => p.slice(1))).map((p) => `${p}: string | number`).join("; ")} }` : "Record<string, string | number>";
|
|
3059
3060
|
routeMethods.push(`
|
|
3060
|
-
${route.handler}: (
|
|
3061
|
-
params${route.pathParams && route.pathParams.length > 0 || (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).length > 0 ? "" : "?"}: ${route.pathParams && route.pathParams.length > 0 || (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).length > 0 ? `{ ${(route.pathParams && route.pathParams.length > 0 ? route.pathParams : (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).map((p) => p.slice(1))).map((p) => `${p}: string | number`).join("; ")} }` : "Record<string, string | number>"};
|
|
3062
|
-
query?: Record<string, unknown>;
|
|
3063
|
-
body?: unknown;
|
|
3064
|
-
headers?: Record<string, string>;
|
|
3065
|
-
}${route.pathParams && route.pathParams.length > 0 || (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).length > 0 ? "" : " = {}"}) => request(${JSON.stringify(route.method)}, ${JSON.stringify(fullPath)}, options),`);
|
|
3061
|
+
${route.handler}: makeRoute<{ params${routeParams.startsWith("{") ? "" : "?"}: ${routeParams}; query?: Record<string, unknown>; body?: unknown; headers?: Record<string, string> }>(${JSON.stringify(route.method)}, ${JSON.stringify(fullPath)}),`);
|
|
3066
3062
|
}
|
|
3067
3063
|
controllerEntries.push(`
|
|
3068
3064
|
${controllerKey}: {${routeMethods.join("")}
|
|
@@ -3079,6 +3075,13 @@ function renderClient(graph, _options) {
|
|
|
3079
3075
|
" headers?: Record<string, string>;",
|
|
3080
3076
|
"}",
|
|
3081
3077
|
"",
|
|
3078
|
+
"export type ResponseDecoder<T> = (value: unknown) => T;",
|
|
3079
|
+
"",
|
|
3080
|
+
"export type RouteMethod<Options extends ClientRequestOptions = ClientRequestOptions> = {",
|
|
3081
|
+
" <T>(options: Options, decode: ResponseDecoder<T>): Promise<T>;",
|
|
3082
|
+
" (options?: Options): Promise<unknown>;",
|
|
3083
|
+
"};",
|
|
3084
|
+
"",
|
|
3082
3085
|
"export type HttpInterceptorFn = (",
|
|
3083
3086
|
" req: { method: string; url: string; headers: Record<string, string>; body?: unknown },",
|
|
3084
3087
|
" next: (req: { method: string; url: string; headers: Record<string, string>; body?: unknown }) => Promise<Response>,",
|
|
@@ -3124,11 +3127,23 @@ function renderClient(graph, _options) {
|
|
|
3124
3127
|
" const fetcher = config.fetch ?? globalThis.fetch.bind(globalThis);",
|
|
3125
3128
|
' const baseUrl = (config.baseUrl ?? "").replace(/\\/+$/, "");',
|
|
3126
3129
|
"",
|
|
3127
|
-
" async function request<T
|
|
3130
|
+
" async function request<T>(",
|
|
3131
|
+
" method: string,",
|
|
3132
|
+
" path: string,",
|
|
3133
|
+
" options: ClientRequestOptions,",
|
|
3134
|
+
" decode: ResponseDecoder<T>,",
|
|
3135
|
+
" ): Promise<T>;",
|
|
3136
|
+
" async function request(",
|
|
3137
|
+
" method: string,",
|
|
3138
|
+
" path: string,",
|
|
3139
|
+
" options?: ClientRequestOptions,",
|
|
3140
|
+
" ): Promise<unknown>;",
|
|
3141
|
+
" async function request<T>(",
|
|
3128
3142
|
" method: string,",
|
|
3129
3143
|
" path: string,",
|
|
3130
3144
|
" options: ClientRequestOptions = {},",
|
|
3131
|
-
"
|
|
3145
|
+
" decode?: ResponseDecoder<T>,",
|
|
3146
|
+
" ): Promise<T | unknown> {",
|
|
3132
3147
|
" let url = `${baseUrl}${path}`;",
|
|
3133
3148
|
" if (options.params) {",
|
|
3134
3149
|
" for (const [key, value] of Object.entries(options.params)) {",
|
|
@@ -3169,10 +3184,23 @@ function renderClient(graph, _options) {
|
|
|
3169
3184
|
" throw new Error(`API request failed: ${method} ${path} -> ${response.status} ${errBody}`);",
|
|
3170
3185
|
" }",
|
|
3171
3186
|
' const contentType = response.headers?.get("content-type") ?? "";',
|
|
3187
|
+
" let value: unknown;",
|
|
3172
3188
|
' if (contentType.includes("application/json")) {',
|
|
3173
|
-
"
|
|
3189
|
+
" value = await response.json();",
|
|
3190
|
+
" } else {",
|
|
3191
|
+
" value = await response.text();",
|
|
3192
|
+
" }",
|
|
3193
|
+
" return decode ? decode(value) : value;",
|
|
3194
|
+
" }",
|
|
3195
|
+
"",
|
|
3196
|
+
" function makeRoute<Options extends ClientRequestOptions>(method: string, path: string): RouteMethod<Options> {",
|
|
3197
|
+
" function route<T>(options: Options, decode: ResponseDecoder<T>): Promise<T>;",
|
|
3198
|
+
" function route(options?: Options): Promise<unknown>;",
|
|
3199
|
+
" function route<T>(options?: Options, decode?: ResponseDecoder<T>): Promise<T | unknown> {",
|
|
3200
|
+
" const requestOptions = options ?? {};",
|
|
3201
|
+
" return decode ? request(method, path, requestOptions, decode) : request(method, path, requestOptions);",
|
|
3174
3202
|
" }",
|
|
3175
|
-
" return
|
|
3203
|
+
" return route;",
|
|
3176
3204
|
" }",
|
|
3177
3205
|
"",
|
|
3178
3206
|
" return {",
|
|
@@ -5056,6 +5084,67 @@ function watchProject(options) {
|
|
|
5056
5084
|
};
|
|
5057
5085
|
}
|
|
5058
5086
|
|
|
5087
|
+
// src/config.ts
|
|
5088
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
5089
|
+
import { join as join6, resolve as resolve5 } from "node:path";
|
|
5090
|
+
import { pathToFileURL } from "node:url";
|
|
5091
|
+
var DEFAULT_SUPACLOUD_CONFIG = {
|
|
5092
|
+
root: "src",
|
|
5093
|
+
outDir: "generated",
|
|
5094
|
+
include: ["**/*.module.ts", "**/*.ts"],
|
|
5095
|
+
strict: true,
|
|
5096
|
+
generateClient: true,
|
|
5097
|
+
generatePermissions: true,
|
|
5098
|
+
treeShakeUnusedProviders: true,
|
|
5099
|
+
moduleBoundaryPreset: "modular-monolith"
|
|
5100
|
+
};
|
|
5101
|
+
function defineSupacloudConfig(config = {}) {
|
|
5102
|
+
return {
|
|
5103
|
+
...DEFAULT_SUPACLOUD_CONFIG,
|
|
5104
|
+
...config,
|
|
5105
|
+
include: config.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include]
|
|
5106
|
+
};
|
|
5107
|
+
}
|
|
5108
|
+
function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
5109
|
+
const resolved = defineSupacloudConfig(config);
|
|
5110
|
+
return {
|
|
5111
|
+
rootDir: resolve5(cwd, resolved.root ?? DEFAULT_SUPACLOUD_CONFIG.root),
|
|
5112
|
+
outDir: resolve5(cwd, resolved.outDir ?? DEFAULT_SUPACLOUD_CONFIG.outDir),
|
|
5113
|
+
include: resolved.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include],
|
|
5114
|
+
strict: resolved.strict ?? DEFAULT_SUPACLOUD_CONFIG.strict,
|
|
5115
|
+
generateClient: resolved.generateClient ?? DEFAULT_SUPACLOUD_CONFIG.generateClient,
|
|
5116
|
+
generatePermissions: resolved.generatePermissions ?? DEFAULT_SUPACLOUD_CONFIG.generatePermissions,
|
|
5117
|
+
moduleBoundaryPreset: resolved.moduleBoundaryPreset ?? DEFAULT_SUPACLOUD_CONFIG.moduleBoundaryPreset,
|
|
5118
|
+
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders
|
|
5119
|
+
};
|
|
5120
|
+
}
|
|
5121
|
+
async function loadSupacloudConfig(cwd = process.cwd()) {
|
|
5122
|
+
const candidates = [
|
|
5123
|
+
join6(cwd, "supacloud.config.ts"),
|
|
5124
|
+
join6(cwd, "supacloud.config.mts"),
|
|
5125
|
+
join6(cwd, "supacloud.config.js"),
|
|
5126
|
+
join6(cwd, "supacloud.config.mjs")
|
|
5127
|
+
];
|
|
5128
|
+
const configPath = candidates.find((candidate) => existsSync5(candidate));
|
|
5129
|
+
if (!configPath)
|
|
5130
|
+
return defineSupacloudConfig();
|
|
5131
|
+
const imported = await import(pathToFileURL(configPath).href);
|
|
5132
|
+
return defineSupacloudConfig(imported.default ?? {});
|
|
5133
|
+
}
|
|
5134
|
+
function compileOptionsFromConfig(config, cwd = process.cwd()) {
|
|
5135
|
+
const resolved = resolveSupacloudConfig(config, cwd);
|
|
5136
|
+
return {
|
|
5137
|
+
rootDir: resolved.rootDir,
|
|
5138
|
+
outDir: resolved.outDir,
|
|
5139
|
+
include: resolved.include,
|
|
5140
|
+
strict: resolved.strict,
|
|
5141
|
+
generateClient: resolved.generateClient,
|
|
5142
|
+
generatePermissions: resolved.generatePermissions,
|
|
5143
|
+
moduleBoundaryPreset: resolved.moduleBoundaryPreset,
|
|
5144
|
+
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders
|
|
5145
|
+
};
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5059
5148
|
// src/cli.ts
|
|
5060
5149
|
function isModuleBoundaryPresetName(value) {
|
|
5061
5150
|
return value === "modular-monolith" || value === "feature-slices" || value === "vertical-slices" || value === "angular-enterprise" || value === "angular" || value === "clean-architecture" || value === "domain-driven";
|
|
@@ -5081,11 +5170,14 @@ Commands:
|
|
|
5081
5170
|
doctor Run project and generated-artifact health checks
|
|
5082
5171
|
|
|
5083
5172
|
Options:
|
|
5084
|
-
--root, -r <dir> Application source root (default:
|
|
5085
|
-
--out, -o <dir> Artifact output directory (default:
|
|
5086
|
-
--strict Enable type-safety gates and treat all warnings as errors
|
|
5087
|
-
--
|
|
5088
|
-
--
|
|
5173
|
+
--root, -r <dir> Application source root (default: ./src, or first positional argument)
|
|
5174
|
+
--out, -o <dir> Artifact output directory (default: ./generated)
|
|
5175
|
+
--strict Enable type-safety gates and treat all warnings as errors (default)
|
|
5176
|
+
--no-strict Disable strict diagnostics (local migration escape hatch)
|
|
5177
|
+
--client Generate typed API client in client.ts (default)
|
|
5178
|
+
--no-client Do not generate client.ts
|
|
5179
|
+
--permissions Generate typed permissions registry (default)
|
|
5180
|
+
--no-permissions Do not generate permissions.ts
|
|
5089
5181
|
--debounce <ms> Debounce source changes in dev mode (default: 100)
|
|
5090
5182
|
--json Print machine-readable output for graph/explain/doctor
|
|
5091
5183
|
--preset, -p <name> Architecture preset ('modular-monolith' | 'angular-enterprise' | 'clean-architecture')
|
|
@@ -5104,11 +5196,11 @@ async function run() {
|
|
|
5104
5196
|
printUsage();
|
|
5105
5197
|
process.exit(1);
|
|
5106
5198
|
}
|
|
5107
|
-
let rootDir
|
|
5199
|
+
let rootDir;
|
|
5108
5200
|
let outDir;
|
|
5109
|
-
let strict
|
|
5110
|
-
let generateClient
|
|
5111
|
-
let generatePermissions
|
|
5201
|
+
let strict;
|
|
5202
|
+
let generateClient;
|
|
5203
|
+
let generatePermissions;
|
|
5112
5204
|
let preset;
|
|
5113
5205
|
let debounceMs = 100;
|
|
5114
5206
|
let query;
|
|
@@ -5121,10 +5213,16 @@ async function run() {
|
|
|
5121
5213
|
outDir = args[++i];
|
|
5122
5214
|
} else if (arg === "--strict") {
|
|
5123
5215
|
strict = true;
|
|
5216
|
+
} else if (arg === "--no-strict") {
|
|
5217
|
+
strict = false;
|
|
5124
5218
|
} else if (arg === "--client") {
|
|
5125
5219
|
generateClient = true;
|
|
5220
|
+
} else if (arg === "--no-client") {
|
|
5221
|
+
generateClient = false;
|
|
5126
5222
|
} else if (arg === "--permissions") {
|
|
5127
5223
|
generatePermissions = true;
|
|
5224
|
+
} else if (arg === "--no-permissions") {
|
|
5225
|
+
generatePermissions = false;
|
|
5128
5226
|
} else if (arg === "--debounce") {
|
|
5129
5227
|
debounceMs = Number(args[++i]);
|
|
5130
5228
|
if (!Number.isFinite(debounceMs) || debounceMs < 0) {
|
|
@@ -5140,7 +5238,7 @@ async function run() {
|
|
|
5140
5238
|
process.exit(1);
|
|
5141
5239
|
}
|
|
5142
5240
|
preset = presetArg;
|
|
5143
|
-
} else if (!arg.startsWith("-") && rootDir
|
|
5241
|
+
} else if (!arg.startsWith("-") && !rootDir) {
|
|
5144
5242
|
if (command === "explain" && !query)
|
|
5145
5243
|
query = arg;
|
|
5146
5244
|
else
|
|
@@ -5149,17 +5247,24 @@ async function run() {
|
|
|
5149
5247
|
query = arg;
|
|
5150
5248
|
}
|
|
5151
5249
|
}
|
|
5152
|
-
const
|
|
5153
|
-
const
|
|
5250
|
+
const loadedConfig = await loadSupacloudConfig(process.cwd());
|
|
5251
|
+
const defaults = resolveSupacloudConfig(loadedConfig, process.cwd());
|
|
5252
|
+
const resolvedRoot = rootDir ? resolve6(process.cwd(), rootDir) : defaults.rootDir;
|
|
5253
|
+
const resolvedOut = outDir ? resolve6(process.cwd(), outDir) : defaults.outDir;
|
|
5254
|
+
const configured = compileOptionsFromConfig({
|
|
5255
|
+
...loadedConfig,
|
|
5256
|
+
root: resolvedRoot,
|
|
5257
|
+
outDir: resolvedOut,
|
|
5258
|
+
strict: strict ?? loadedConfig.strict,
|
|
5259
|
+
generateClient: generateClient ?? loadedConfig.generateClient,
|
|
5260
|
+
generatePermissions: generatePermissions ?? loadedConfig.generatePermissions
|
|
5261
|
+
}, process.cwd());
|
|
5262
|
+
const compileDefaults = {
|
|
5263
|
+
...configured,
|
|
5264
|
+
moduleBoundaryPreset: preset ?? configured.moduleBoundaryPreset
|
|
5265
|
+
};
|
|
5154
5266
|
if (command === "compile") {
|
|
5155
|
-
const result = await compileProject(
|
|
5156
|
-
rootDir: resolvedRoot,
|
|
5157
|
-
outDir: resolvedOut,
|
|
5158
|
-
strict,
|
|
5159
|
-
moduleBoundaryPreset: preset,
|
|
5160
|
-
generateClient,
|
|
5161
|
-
generatePermissions
|
|
5162
|
-
});
|
|
5267
|
+
const result = await compileProject(compileDefaults);
|
|
5163
5268
|
printDiagnostics(result.diagnostics);
|
|
5164
5269
|
const errors = result.diagnostics.filter((d) => d.severity === "error");
|
|
5165
5270
|
if (errors.length > 0) {
|
|
@@ -5172,14 +5277,7 @@ Compilation succeeded. Generated artifacts:
|
|
|
5172
5277
|
${result.written.map((f) => ` - ${f}`).join(`
|
|
5173
5278
|
`)}`);
|
|
5174
5279
|
} else if (command === "check") {
|
|
5175
|
-
const result = await checkProject(
|
|
5176
|
-
rootDir: resolvedRoot,
|
|
5177
|
-
outDir: resolvedOut,
|
|
5178
|
-
strict,
|
|
5179
|
-
moduleBoundaryPreset: preset,
|
|
5180
|
-
generateClient,
|
|
5181
|
-
generatePermissions
|
|
5182
|
-
});
|
|
5280
|
+
const result = await checkProject(compileDefaults);
|
|
5183
5281
|
printDiagnostics(result.diagnostics);
|
|
5184
5282
|
const errors = result.diagnostics.filter((d) => d.severity === "error");
|
|
5185
5283
|
if (errors.length > 0) {
|
|
@@ -5199,13 +5297,8 @@ Artifact drift detected:`);
|
|
|
5199
5297
|
console.log("Artifact check passed: disk files match compiler output with no drift.");
|
|
5200
5298
|
} else if (command === "dev") {
|
|
5201
5299
|
const handle = watchProject({
|
|
5202
|
-
|
|
5203
|
-
outDir: resolvedOut,
|
|
5204
|
-
strict,
|
|
5205
|
-
moduleBoundaryPreset: preset,
|
|
5300
|
+
...compileDefaults,
|
|
5206
5301
|
debounceMs,
|
|
5207
|
-
generateClient,
|
|
5208
|
-
generatePermissions,
|
|
5209
5302
|
onEvent: (event) => {
|
|
5210
5303
|
if (event.type === "compile-start") {
|
|
5211
5304
|
console.log(event.initial ? `
|
|
@@ -5256,12 +5349,7 @@ Source change detected; compiling...`);
|
|
|
5256
5349
|
process.exit(1);
|
|
5257
5350
|
}
|
|
5258
5351
|
} else {
|
|
5259
|
-
const result = await checkProject(
|
|
5260
|
-
rootDir: resolvedRoot,
|
|
5261
|
-
outDir: resolvedOut,
|
|
5262
|
-
strict,
|
|
5263
|
-
moduleBoundaryPreset: preset
|
|
5264
|
-
});
|
|
5352
|
+
const result = await checkProject(compileDefaults);
|
|
5265
5353
|
const doctor = doctorProject(resolvedRoot, resolvedOut, result.graph, result.upToDate, result.diagnostics);
|
|
5266
5354
|
if (json) {
|
|
5267
5355
|
console.log(JSON.stringify(doctor, null, 2));
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { CompileOptions, ModuleBoundaryPresetName } from "./types";
|
|
2
|
+
export interface SupaCloudConfig {
|
|
3
|
+
root?: string;
|
|
4
|
+
outDir?: string;
|
|
5
|
+
include?: string[];
|
|
6
|
+
strict?: boolean;
|
|
7
|
+
generateClient?: boolean;
|
|
8
|
+
generatePermissions?: boolean;
|
|
9
|
+
moduleBoundaryPreset?: ModuleBoundaryPresetName;
|
|
10
|
+
treeShakeUnusedProviders?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const DEFAULT_SUPACLOUD_CONFIG: Required<Omit<SupaCloudConfig, "include" | "moduleBoundaryPreset">> & {
|
|
13
|
+
include: string[];
|
|
14
|
+
moduleBoundaryPreset: ModuleBoundaryPresetName;
|
|
15
|
+
};
|
|
16
|
+
export declare function defineSupacloudConfig(config?: SupaCloudConfig): SupaCloudConfig;
|
|
17
|
+
export declare function resolveSupacloudConfig(config?: SupaCloudConfig, cwd?: string): {
|
|
18
|
+
rootDir: string;
|
|
19
|
+
outDir: string;
|
|
20
|
+
include: string[];
|
|
21
|
+
strict: boolean;
|
|
22
|
+
generateClient: boolean;
|
|
23
|
+
generatePermissions: boolean;
|
|
24
|
+
moduleBoundaryPreset: ModuleBoundaryPresetName;
|
|
25
|
+
treeShakeUnusedProviders: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare function loadSupacloudConfig(cwd?: string): Promise<SupaCloudConfig>;
|
|
28
|
+
export declare function compileOptionsFromConfig(config: SupaCloudConfig, cwd?: string): CompileOptions;
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export type { DoctorResult } from "./inspect";
|
|
|
16
16
|
export { validateGraph, COMPILER_DIAGNOSTIC_CODES } from "./validate";
|
|
17
17
|
export { scanGeneratedArtifacts, scanProductionSource } from "./type-safety";
|
|
18
18
|
export type { TypeSafetyScanOptions } from "./type-safety";
|
|
19
|
+
export { DEFAULT_SUPACLOUD_CONFIG, compileOptionsFromConfig, defineSupacloudConfig, loadSupacloudConfig, resolveSupacloudConfig, } from "./config";
|
|
20
|
+
export type { SupaCloudConfig } from "./config";
|
|
19
21
|
export { camelName } from "./util";
|
|
20
22
|
export { ANGULAR_ENTERPRISE_RULES, CLEAN_ARCHITECTURE_RULES, MODULAR_MONOLITH_RULES, MODULE_BOUNDARY_PROFILES, getModuleBoundaryPreset, getModuleBoundaryProfile, resolveModuleBoundaries, } from "./profiles";
|
|
21
23
|
export type { ApplicationGraph, AspectRefNode, CachedModuleEntry, CheckProjectResult, CommandExecutionCapabilities, CommandNode, CompileOptions, CompileResult, CompileStats, ControllerNode, DependencyGraphCache, DependencyGraphIndex, Diagnostic, ModuleBoundaryPresetName, ModuleBoundaryProfile, ModuleBoundaryRule, ModuleNode, JobNode, ProviderKind, ProviderNode, QueryNode, RouteNode, Scope, TokenKind, TypeSafetyOptions, ValidateOptions, WatchEvent, WatchHandle, WatchOptions, } from "./types";
|
package/dist/index.js
CHANGED
|
@@ -3050,13 +3050,9 @@ function renderClient(graph, _options) {
|
|
|
3050
3050
|
title: route.title,
|
|
3051
3051
|
data: route.data
|
|
3052
3052
|
});
|
|
3053
|
+
const routeParams = route.pathParams && route.pathParams.length > 0 || (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).length > 0 ? `{ ${(route.pathParams && route.pathParams.length > 0 ? route.pathParams : (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).map((p) => p.slice(1))).map((p) => `${p}: string | number`).join("; ")} }` : "Record<string, string | number>";
|
|
3053
3054
|
routeMethods.push(`
|
|
3054
|
-
${route.handler}: (
|
|
3055
|
-
params${route.pathParams && route.pathParams.length > 0 || (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).length > 0 ? "" : "?"}: ${route.pathParams && route.pathParams.length > 0 || (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).length > 0 ? `{ ${(route.pathParams && route.pathParams.length > 0 ? route.pathParams : (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).map((p) => p.slice(1))).map((p) => `${p}: string | number`).join("; ")} }` : "Record<string, string | number>"};
|
|
3056
|
-
query?: Record<string, unknown>;
|
|
3057
|
-
body?: unknown;
|
|
3058
|
-
headers?: Record<string, string>;
|
|
3059
|
-
}${route.pathParams && route.pathParams.length > 0 || (fullPath.match(/:([a-zA-Z0-9_]+)/g) ?? []).length > 0 ? "" : " = {}"}) => request(${JSON.stringify(route.method)}, ${JSON.stringify(fullPath)}, options),`);
|
|
3055
|
+
${route.handler}: makeRoute<{ params${routeParams.startsWith("{") ? "" : "?"}: ${routeParams}; query?: Record<string, unknown>; body?: unknown; headers?: Record<string, string> }>(${JSON.stringify(route.method)}, ${JSON.stringify(fullPath)}),`);
|
|
3060
3056
|
}
|
|
3061
3057
|
controllerEntries.push(`
|
|
3062
3058
|
${controllerKey}: {${routeMethods.join("")}
|
|
@@ -3073,6 +3069,13 @@ function renderClient(graph, _options) {
|
|
|
3073
3069
|
" headers?: Record<string, string>;",
|
|
3074
3070
|
"}",
|
|
3075
3071
|
"",
|
|
3072
|
+
"export type ResponseDecoder<T> = (value: unknown) => T;",
|
|
3073
|
+
"",
|
|
3074
|
+
"export type RouteMethod<Options extends ClientRequestOptions = ClientRequestOptions> = {",
|
|
3075
|
+
" <T>(options: Options, decode: ResponseDecoder<T>): Promise<T>;",
|
|
3076
|
+
" (options?: Options): Promise<unknown>;",
|
|
3077
|
+
"};",
|
|
3078
|
+
"",
|
|
3076
3079
|
"export type HttpInterceptorFn = (",
|
|
3077
3080
|
" req: { method: string; url: string; headers: Record<string, string>; body?: unknown },",
|
|
3078
3081
|
" next: (req: { method: string; url: string; headers: Record<string, string>; body?: unknown }) => Promise<Response>,",
|
|
@@ -3118,11 +3121,23 @@ function renderClient(graph, _options) {
|
|
|
3118
3121
|
" const fetcher = config.fetch ?? globalThis.fetch.bind(globalThis);",
|
|
3119
3122
|
' const baseUrl = (config.baseUrl ?? "").replace(/\\/+$/, "");',
|
|
3120
3123
|
"",
|
|
3121
|
-
" async function request<T
|
|
3124
|
+
" async function request<T>(",
|
|
3125
|
+
" method: string,",
|
|
3126
|
+
" path: string,",
|
|
3127
|
+
" options: ClientRequestOptions,",
|
|
3128
|
+
" decode: ResponseDecoder<T>,",
|
|
3129
|
+
" ): Promise<T>;",
|
|
3130
|
+
" async function request(",
|
|
3131
|
+
" method: string,",
|
|
3132
|
+
" path: string,",
|
|
3133
|
+
" options?: ClientRequestOptions,",
|
|
3134
|
+
" ): Promise<unknown>;",
|
|
3135
|
+
" async function request<T>(",
|
|
3122
3136
|
" method: string,",
|
|
3123
3137
|
" path: string,",
|
|
3124
3138
|
" options: ClientRequestOptions = {},",
|
|
3125
|
-
"
|
|
3139
|
+
" decode?: ResponseDecoder<T>,",
|
|
3140
|
+
" ): Promise<T | unknown> {",
|
|
3126
3141
|
" let url = `${baseUrl}${path}`;",
|
|
3127
3142
|
" if (options.params) {",
|
|
3128
3143
|
" for (const [key, value] of Object.entries(options.params)) {",
|
|
@@ -3163,10 +3178,23 @@ function renderClient(graph, _options) {
|
|
|
3163
3178
|
" throw new Error(`API request failed: ${method} ${path} -> ${response.status} ${errBody}`);",
|
|
3164
3179
|
" }",
|
|
3165
3180
|
' const contentType = response.headers?.get("content-type") ?? "";',
|
|
3181
|
+
" let value: unknown;",
|
|
3166
3182
|
' if (contentType.includes("application/json")) {',
|
|
3167
|
-
"
|
|
3183
|
+
" value = await response.json();",
|
|
3184
|
+
" } else {",
|
|
3185
|
+
" value = await response.text();",
|
|
3186
|
+
" }",
|
|
3187
|
+
" return decode ? decode(value) : value;",
|
|
3188
|
+
" }",
|
|
3189
|
+
"",
|
|
3190
|
+
" function makeRoute<Options extends ClientRequestOptions>(method: string, path: string): RouteMethod<Options> {",
|
|
3191
|
+
" function route<T>(options: Options, decode: ResponseDecoder<T>): Promise<T>;",
|
|
3192
|
+
" function route(options?: Options): Promise<unknown>;",
|
|
3193
|
+
" function route<T>(options?: Options, decode?: ResponseDecoder<T>): Promise<T | unknown> {",
|
|
3194
|
+
" const requestOptions = options ?? {};",
|
|
3195
|
+
" return decode ? request(method, path, requestOptions, decode) : request(method, path, requestOptions);",
|
|
3168
3196
|
" }",
|
|
3169
|
-
" return
|
|
3197
|
+
" return route;",
|
|
3170
3198
|
" }",
|
|
3171
3199
|
"",
|
|
3172
3200
|
" return {",
|
|
@@ -5047,10 +5075,71 @@ function exportGraphDot(graph) {
|
|
|
5047
5075
|
return lines.join(`
|
|
5048
5076
|
`);
|
|
5049
5077
|
}
|
|
5078
|
+
// src/config.ts
|
|
5079
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
5080
|
+
import { join as join6, resolve as resolve5 } from "node:path";
|
|
5081
|
+
import { pathToFileURL } from "node:url";
|
|
5082
|
+
var DEFAULT_SUPACLOUD_CONFIG = {
|
|
5083
|
+
root: "src",
|
|
5084
|
+
outDir: "generated",
|
|
5085
|
+
include: ["**/*.module.ts", "**/*.ts"],
|
|
5086
|
+
strict: true,
|
|
5087
|
+
generateClient: true,
|
|
5088
|
+
generatePermissions: true,
|
|
5089
|
+
treeShakeUnusedProviders: true,
|
|
5090
|
+
moduleBoundaryPreset: "modular-monolith"
|
|
5091
|
+
};
|
|
5092
|
+
function defineSupacloudConfig(config = {}) {
|
|
5093
|
+
return {
|
|
5094
|
+
...DEFAULT_SUPACLOUD_CONFIG,
|
|
5095
|
+
...config,
|
|
5096
|
+
include: config.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include]
|
|
5097
|
+
};
|
|
5098
|
+
}
|
|
5099
|
+
function resolveSupacloudConfig(config = {}, cwd = process.cwd()) {
|
|
5100
|
+
const resolved = defineSupacloudConfig(config);
|
|
5101
|
+
return {
|
|
5102
|
+
rootDir: resolve5(cwd, resolved.root ?? DEFAULT_SUPACLOUD_CONFIG.root),
|
|
5103
|
+
outDir: resolve5(cwd, resolved.outDir ?? DEFAULT_SUPACLOUD_CONFIG.outDir),
|
|
5104
|
+
include: resolved.include ?? [...DEFAULT_SUPACLOUD_CONFIG.include],
|
|
5105
|
+
strict: resolved.strict ?? DEFAULT_SUPACLOUD_CONFIG.strict,
|
|
5106
|
+
generateClient: resolved.generateClient ?? DEFAULT_SUPACLOUD_CONFIG.generateClient,
|
|
5107
|
+
generatePermissions: resolved.generatePermissions ?? DEFAULT_SUPACLOUD_CONFIG.generatePermissions,
|
|
5108
|
+
moduleBoundaryPreset: resolved.moduleBoundaryPreset ?? DEFAULT_SUPACLOUD_CONFIG.moduleBoundaryPreset,
|
|
5109
|
+
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders ?? DEFAULT_SUPACLOUD_CONFIG.treeShakeUnusedProviders
|
|
5110
|
+
};
|
|
5111
|
+
}
|
|
5112
|
+
async function loadSupacloudConfig(cwd = process.cwd()) {
|
|
5113
|
+
const candidates = [
|
|
5114
|
+
join6(cwd, "supacloud.config.ts"),
|
|
5115
|
+
join6(cwd, "supacloud.config.mts"),
|
|
5116
|
+
join6(cwd, "supacloud.config.js"),
|
|
5117
|
+
join6(cwd, "supacloud.config.mjs")
|
|
5118
|
+
];
|
|
5119
|
+
const configPath = candidates.find((candidate) => existsSync5(candidate));
|
|
5120
|
+
if (!configPath)
|
|
5121
|
+
return defineSupacloudConfig();
|
|
5122
|
+
const imported = await import(pathToFileURL(configPath).href);
|
|
5123
|
+
return defineSupacloudConfig(imported.default ?? {});
|
|
5124
|
+
}
|
|
5125
|
+
function compileOptionsFromConfig(config, cwd = process.cwd()) {
|
|
5126
|
+
const resolved = resolveSupacloudConfig(config, cwd);
|
|
5127
|
+
return {
|
|
5128
|
+
rootDir: resolved.rootDir,
|
|
5129
|
+
outDir: resolved.outDir,
|
|
5130
|
+
include: resolved.include,
|
|
5131
|
+
strict: resolved.strict,
|
|
5132
|
+
generateClient: resolved.generateClient,
|
|
5133
|
+
generatePermissions: resolved.generatePermissions,
|
|
5134
|
+
moduleBoundaryPreset: resolved.moduleBoundaryPreset,
|
|
5135
|
+
treeShakeUnusedProviders: resolved.treeShakeUnusedProviders
|
|
5136
|
+
};
|
|
5137
|
+
}
|
|
5050
5138
|
export {
|
|
5051
5139
|
ANGULAR_ENTERPRISE_RULES,
|
|
5052
5140
|
CLEAN_ARCHITECTURE_RULES,
|
|
5053
5141
|
COMPILER_DIAGNOSTIC_CODES,
|
|
5142
|
+
DEFAULT_SUPACLOUD_CONFIG,
|
|
5054
5143
|
MODULAR_MONOLITH_RULES,
|
|
5055
5144
|
MODULE_BOUNDARY_PROFILES,
|
|
5056
5145
|
ModuleDependencyGraph,
|
|
@@ -5058,11 +5147,13 @@ export {
|
|
|
5058
5147
|
analyzeProject,
|
|
5059
5148
|
camelName,
|
|
5060
5149
|
checkProject,
|
|
5150
|
+
compileOptionsFromConfig,
|
|
5061
5151
|
compileProject,
|
|
5062
5152
|
compileTraits,
|
|
5063
5153
|
createDependencyGraphCache,
|
|
5064
5154
|
createIncrementalCompiler,
|
|
5065
5155
|
createIncrementalProgramSession,
|
|
5156
|
+
defineSupacloudConfig,
|
|
5066
5157
|
doctorProject,
|
|
5067
5158
|
explainGraph,
|
|
5068
5159
|
exportGraphDot,
|
|
@@ -5071,8 +5162,10 @@ export {
|
|
|
5071
5162
|
generateApplication,
|
|
5072
5163
|
getModuleBoundaryPreset,
|
|
5073
5164
|
getModuleBoundaryProfile,
|
|
5165
|
+
loadSupacloudConfig,
|
|
5074
5166
|
renderApplication,
|
|
5075
5167
|
resolveModuleBoundaries,
|
|
5168
|
+
resolveSupacloudConfig,
|
|
5076
5169
|
scanGeneratedArtifacts,
|
|
5077
5170
|
scanProductionSource,
|
|
5078
5171
|
validateGraph,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supacloud/compiler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Static compiler for @supacloud/app metadata: builds the application graph from AST, validates it, and generates reflection-free factory code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|