@supacloud/compiler 0.6.2 → 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 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 resolve5 } from "node:path";
4
+ import { resolve as resolve6 } from "node:path";
5
5
 
6
6
  // src/analyze.ts
7
7
  import { createHash as createHash3 } from "node:crypto";
@@ -5084,6 +5084,67 @@ function watchProject(options) {
5084
5084
  };
5085
5085
  }
5086
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
+
5087
5148
  // src/cli.ts
5088
5149
  function isModuleBoundaryPresetName(value) {
5089
5150
  return value === "modular-monolith" || value === "feature-slices" || value === "vertical-slices" || value === "angular-enterprise" || value === "angular" || value === "clean-architecture" || value === "domain-driven";
@@ -5109,11 +5170,14 @@ Commands:
5109
5170
  doctor Run project and generated-artifact health checks
5110
5171
 
5111
5172
  Options:
5112
- --root, -r <dir> Application source root (default: current directory or first positional argument)
5113
- --out, -o <dir> Artifact output directory (default: <rootDir>/generated)
5114
- --strict Enable type-safety gates and treat all warnings as errors
5115
- --client Generate typed API client in client.ts
5116
- --permissions Generate typed permissions registry in permissions.ts
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
5117
5181
  --debounce <ms> Debounce source changes in dev mode (default: 100)
5118
5182
  --json Print machine-readable output for graph/explain/doctor
5119
5183
  --preset, -p <name> Architecture preset ('modular-monolith' | 'angular-enterprise' | 'clean-architecture')
@@ -5132,11 +5196,11 @@ async function run() {
5132
5196
  printUsage();
5133
5197
  process.exit(1);
5134
5198
  }
5135
- let rootDir = ".";
5199
+ let rootDir;
5136
5200
  let outDir;
5137
- let strict = false;
5138
- let generateClient = false;
5139
- let generatePermissions = false;
5201
+ let strict;
5202
+ let generateClient;
5203
+ let generatePermissions;
5140
5204
  let preset;
5141
5205
  let debounceMs = 100;
5142
5206
  let query;
@@ -5149,10 +5213,16 @@ async function run() {
5149
5213
  outDir = args[++i];
5150
5214
  } else if (arg === "--strict") {
5151
5215
  strict = true;
5216
+ } else if (arg === "--no-strict") {
5217
+ strict = false;
5152
5218
  } else if (arg === "--client") {
5153
5219
  generateClient = true;
5220
+ } else if (arg === "--no-client") {
5221
+ generateClient = false;
5154
5222
  } else if (arg === "--permissions") {
5155
5223
  generatePermissions = true;
5224
+ } else if (arg === "--no-permissions") {
5225
+ generatePermissions = false;
5156
5226
  } else if (arg === "--debounce") {
5157
5227
  debounceMs = Number(args[++i]);
5158
5228
  if (!Number.isFinite(debounceMs) || debounceMs < 0) {
@@ -5168,7 +5238,7 @@ async function run() {
5168
5238
  process.exit(1);
5169
5239
  }
5170
5240
  preset = presetArg;
5171
- } else if (!arg.startsWith("-") && rootDir === ".") {
5241
+ } else if (!arg.startsWith("-") && !rootDir) {
5172
5242
  if (command === "explain" && !query)
5173
5243
  query = arg;
5174
5244
  else
@@ -5177,17 +5247,24 @@ async function run() {
5177
5247
  query = arg;
5178
5248
  }
5179
5249
  }
5180
- const resolvedRoot = resolve5(process.cwd(), rootDir);
5181
- const resolvedOut = outDir ? resolve5(process.cwd(), outDir) : resolve5(resolvedRoot, "generated");
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
+ };
5182
5266
  if (command === "compile") {
5183
- const result = await compileProject({
5184
- rootDir: resolvedRoot,
5185
- outDir: resolvedOut,
5186
- strict,
5187
- moduleBoundaryPreset: preset,
5188
- generateClient,
5189
- generatePermissions
5190
- });
5267
+ const result = await compileProject(compileDefaults);
5191
5268
  printDiagnostics(result.diagnostics);
5192
5269
  const errors = result.diagnostics.filter((d) => d.severity === "error");
5193
5270
  if (errors.length > 0) {
@@ -5200,14 +5277,7 @@ Compilation succeeded. Generated artifacts:
5200
5277
  ${result.written.map((f) => ` - ${f}`).join(`
5201
5278
  `)}`);
5202
5279
  } else if (command === "check") {
5203
- const result = await checkProject({
5204
- rootDir: resolvedRoot,
5205
- outDir: resolvedOut,
5206
- strict,
5207
- moduleBoundaryPreset: preset,
5208
- generateClient,
5209
- generatePermissions
5210
- });
5280
+ const result = await checkProject(compileDefaults);
5211
5281
  printDiagnostics(result.diagnostics);
5212
5282
  const errors = result.diagnostics.filter((d) => d.severity === "error");
5213
5283
  if (errors.length > 0) {
@@ -5227,13 +5297,8 @@ Artifact drift detected:`);
5227
5297
  console.log("Artifact check passed: disk files match compiler output with no drift.");
5228
5298
  } else if (command === "dev") {
5229
5299
  const handle = watchProject({
5230
- rootDir: resolvedRoot,
5231
- outDir: resolvedOut,
5232
- strict,
5233
- moduleBoundaryPreset: preset,
5300
+ ...compileDefaults,
5234
5301
  debounceMs,
5235
- generateClient,
5236
- generatePermissions,
5237
5302
  onEvent: (event) => {
5238
5303
  if (event.type === "compile-start") {
5239
5304
  console.log(event.initial ? `
@@ -5284,12 +5349,7 @@ Source change detected; compiling...`);
5284
5349
  process.exit(1);
5285
5350
  }
5286
5351
  } else {
5287
- const result = await checkProject({
5288
- rootDir: resolvedRoot,
5289
- outDir: resolvedOut,
5290
- strict,
5291
- moduleBoundaryPreset: preset
5292
- });
5352
+ const result = await checkProject(compileDefaults);
5293
5353
  const doctor = doctorProject(resolvedRoot, resolvedOut, result.graph, result.upToDate, result.diagnostics);
5294
5354
  if (json) {
5295
5355
  console.log(JSON.stringify(doctor, null, 2));
@@ -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
@@ -5075,10 +5075,71 @@ function exportGraphDot(graph) {
5075
5075
  return lines.join(`
5076
5076
  `);
5077
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
+ }
5078
5138
  export {
5079
5139
  ANGULAR_ENTERPRISE_RULES,
5080
5140
  CLEAN_ARCHITECTURE_RULES,
5081
5141
  COMPILER_DIAGNOSTIC_CODES,
5142
+ DEFAULT_SUPACLOUD_CONFIG,
5082
5143
  MODULAR_MONOLITH_RULES,
5083
5144
  MODULE_BOUNDARY_PROFILES,
5084
5145
  ModuleDependencyGraph,
@@ -5086,11 +5147,13 @@ export {
5086
5147
  analyzeProject,
5087
5148
  camelName,
5088
5149
  checkProject,
5150
+ compileOptionsFromConfig,
5089
5151
  compileProject,
5090
5152
  compileTraits,
5091
5153
  createDependencyGraphCache,
5092
5154
  createIncrementalCompiler,
5093
5155
  createIncrementalProgramSession,
5156
+ defineSupacloudConfig,
5094
5157
  doctorProject,
5095
5158
  explainGraph,
5096
5159
  exportGraphDot,
@@ -5099,8 +5162,10 @@ export {
5099
5162
  generateApplication,
5100
5163
  getModuleBoundaryPreset,
5101
5164
  getModuleBoundaryProfile,
5165
+ loadSupacloudConfig,
5102
5166
  renderApplication,
5103
5167
  resolveModuleBoundaries,
5168
+ resolveSupacloudConfig,
5104
5169
  scanGeneratedArtifacts,
5105
5170
  scanProductionSource,
5106
5171
  validateGraph,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supacloud/compiler",
3
- "version": "0.6.2",
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",