@mandujs/core 0.22.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mandujs/core",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -28,9 +28,16 @@
28
28
  "./id": "./src/id/index.ts",
29
29
  "./observability": "./src/observability/index.ts",
30
30
  "./perf": "./src/perf/index.ts",
31
+ "./perf/hmr-markers": "./src/perf/hmr-markers.ts",
31
32
  "./scheduler": "./src/scheduler/index.ts",
32
33
  "./storage/s3": "./src/storage/s3/index.ts",
33
34
  "./bundler/prerender": "./src/bundler/prerender.ts",
35
+ "./bundler/safe-build": "./src/bundler/safe-build.ts",
36
+ "./bundler/hmr-types": "./src/bundler/hmr-types.ts",
37
+ "./bundler/scenario-matrix": "./src/bundler/scenario-matrix.ts",
38
+ "./bundler/vendor-cache-types": "./src/bundler/vendor-cache-types.ts",
39
+ "./bundler/manifest-schema": "./src/bundler/manifest-schema.ts",
40
+ "./runtime/fast-refresh-types": "./src/runtime/fast-refresh-types.ts",
34
41
  "./*": "./src/*"
35
42
  },
36
43
  "files": [
@@ -1,110 +1,172 @@
1
- import path from "path";
2
- import { readJsonFile } from "../utils/bun";
3
- import type { ManduAdapter } from "../runtime/adapter";
4
- import type { ManduPlugin, ManduHooks } from "../plugins/hooks";
5
-
6
- export type GuardRuleSeverity = "error" | "warn" | "warning" | "off";
7
-
8
- export interface ManduConfig {
9
- adapter?: ManduAdapter;
10
- server?: {
11
- port?: number;
12
- hostname?: string;
13
- cors?:
14
- | boolean
15
- | {
16
- origin?: string | string[];
17
- methods?: string[];
18
- credentials?: boolean;
19
- };
20
- streaming?: boolean;
21
- rateLimit?:
22
- | boolean
23
- | {
24
- windowMs?: number;
25
- max?: number;
26
- message?: string;
27
- statusCode?: number;
28
- headers?: boolean;
29
- };
30
- };
31
- guard?: {
32
- preset?: "mandu" | "fsd" | "clean" | "hexagonal" | "atomic" | "cqrs";
33
- srcDir?: string;
34
- exclude?: string[];
35
- realtime?: boolean;
36
- rules?: Record<string, GuardRuleSeverity>;
37
- contractRequired?: GuardRuleSeverity;
38
- };
39
- build?: {
40
- outDir?: string;
41
- minify?: boolean;
42
- sourcemap?: boolean;
43
- splitting?: boolean;
44
- };
45
- dev?: {
46
- hmr?: boolean;
47
- watchDirs?: string[];
48
- /** Observability SQLite 영구 저장 (기본: true) */
49
- observability?: boolean;
50
- };
51
- fsRoutes?: {
52
- routesDir?: string;
53
- extensions?: string[];
54
- exclude?: string[];
55
- islandSuffix?: string;
56
- };
57
- seo?: {
58
- enabled?: boolean;
59
- defaultTitle?: string;
60
- titleTemplate?: string;
61
- };
62
- plugins?: ManduPlugin[];
63
- hooks?: Partial<ManduHooks>;
64
- }
65
-
66
- export const CONFIG_FILES = [
67
- "mandu.config.ts",
68
- "mandu.config.js",
69
- "mandu.config.json",
70
- path.join(".mandu", "guard.json"),
71
- ];
72
-
73
- export function coerceConfig(raw: unknown, source: string): ManduConfig {
74
- if (!raw || typeof raw !== "object") return {};
75
-
76
- // .mandu/guard.json can be guard-only
77
- if (source.endsWith("guard.json") && !("guard" in (raw as Record<string, unknown>))) {
78
- return { guard: raw as ManduConfig["guard"] };
79
- }
80
-
81
- return raw as ManduConfig;
82
- }
83
-
84
- export async function loadManduConfig(rootDir: string): Promise<ManduConfig> {
85
- for (const fileName of CONFIG_FILES) {
86
- const filePath = path.join(rootDir, fileName);
87
- if (!(await Bun.file(filePath).exists())) {
88
- continue;
89
- }
90
-
91
- if (fileName.endsWith(".json")) {
92
- try {
93
- const parsed = await readJsonFile(filePath);
94
- return coerceConfig(parsed, fileName);
95
- } catch {
96
- return {};
97
- }
98
- }
99
-
100
- try {
101
- const module = await import(filePath);
102
- const raw = module?.default ?? module;
103
- return coerceConfig(raw, fileName);
104
- } catch {
105
- return {};
106
- }
107
- }
108
-
109
- return {};
110
- }
1
+ import path from "path";
2
+ import { readJsonFile } from "../utils/bun";
3
+ import type { ManduAdapter } from "../runtime/adapter";
4
+ import type { ManduPlugin, ManduHooks } from "../plugins/hooks";
5
+
6
+ export type GuardRuleSeverity = "error" | "warn" | "warning" | "off";
7
+
8
+ /**
9
+ * Test block configuration (Phase 12.1 — testing ecosystem).
10
+ *
11
+ * Shapes the CLI `mandu test` command's discovery, fixture, and reporter
12
+ * behaviour. All fields are optional; omitting the block yields sensible
13
+ * defaults that match Next.js / SvelteKit user expectations:
14
+ *
15
+ * - unit → `**\/*.test.ts` / `**\/*.test.tsx`, 30s timeout
16
+ * - integration → `tests/integration/**\/*.test.ts`, in-memory fixtures
17
+ * - e2e → reserved for Phase 12.2 (ATE integration)
18
+ * - coverage → reserved for Phase 12.3 (bun + playwright merge)
19
+ */
20
+ export interface TestUnitConfig {
21
+ /** Glob patterns for unit test files. Default: `["**\/*.test.ts", "**\/*.test.tsx"]`. */
22
+ include?: string[];
23
+ /** Glob patterns to exclude (applied after `include`). Default: `["node_modules/**", ".mandu/**", "dist/**"]`. */
24
+ exclude?: string[];
25
+ /** Per-test timeout in milliseconds. Default: `30_000` (30s). */
26
+ timeout?: number;
27
+ }
28
+
29
+ export interface TestIntegrationConfig {
30
+ /** Glob patterns for integration test files. Default: `["tests/integration/**\/*.test.ts"]`. */
31
+ include?: string[];
32
+ /** Glob patterns to exclude. Default: same as unit defaults. */
33
+ exclude?: string[];
34
+ /**
35
+ * Database URL for fixtures. Default: `"sqlite::memory:"` (in-memory SQLite).
36
+ * Accepts any Bun.sql-compatible URL — see `@mandujs/core/db` for the schema matrix.
37
+ */
38
+ dbUrl?: string;
39
+ /**
40
+ * Session storage strategy for `createTestSession`.
41
+ * - `"memory"` (default): CookieSessionStorage with ephemeral secret
42
+ * - `"sqlite"`: bun:sqlite-backed (Phase 2.5 storage, requires Phase 4)
43
+ */
44
+ sessionStore?: "memory" | "sqlite";
45
+ /** Per-test timeout. Default: `60_000` (60s — integration work is slower). */
46
+ timeout?: number;
47
+ }
48
+
49
+ export interface TestE2EConfig {
50
+ /** Reserved for Phase 12.2. Currently a typed placeholder. */
51
+ reserved?: true;
52
+ }
53
+
54
+ export interface TestCoverageConfig {
55
+ /** Minimum line coverage percentage (0-100). Reserved for Phase 12.3. */
56
+ lines?: number;
57
+ /** Minimum branch coverage percentage (0-100). Reserved for Phase 12.3. */
58
+ branches?: number;
59
+ }
60
+
61
+ export interface TestConfig {
62
+ unit?: TestUnitConfig;
63
+ integration?: TestIntegrationConfig;
64
+ e2e?: TestE2EConfig;
65
+ coverage?: TestCoverageConfig;
66
+ }
67
+
68
+ export interface ManduConfig {
69
+ adapter?: ManduAdapter;
70
+ server?: {
71
+ port?: number;
72
+ hostname?: string;
73
+ cors?:
74
+ | boolean
75
+ | {
76
+ origin?: string | string[];
77
+ methods?: string[];
78
+ credentials?: boolean;
79
+ };
80
+ streaming?: boolean;
81
+ rateLimit?:
82
+ | boolean
83
+ | {
84
+ windowMs?: number;
85
+ max?: number;
86
+ message?: string;
87
+ statusCode?: number;
88
+ headers?: boolean;
89
+ };
90
+ };
91
+ guard?: {
92
+ preset?: "mandu" | "fsd" | "clean" | "hexagonal" | "atomic" | "cqrs";
93
+ srcDir?: string;
94
+ exclude?: string[];
95
+ realtime?: boolean;
96
+ rules?: Record<string, GuardRuleSeverity>;
97
+ contractRequired?: GuardRuleSeverity;
98
+ };
99
+ build?: {
100
+ outDir?: string;
101
+ minify?: boolean;
102
+ sourcemap?: boolean;
103
+ splitting?: boolean;
104
+ };
105
+ dev?: {
106
+ hmr?: boolean;
107
+ watchDirs?: string[];
108
+ /** Observability SQLite 영구 저장 (기본: true) */
109
+ observability?: boolean;
110
+ };
111
+ fsRoutes?: {
112
+ routesDir?: string;
113
+ extensions?: string[];
114
+ exclude?: string[];
115
+ islandSuffix?: string;
116
+ };
117
+ seo?: {
118
+ enabled?: boolean;
119
+ defaultTitle?: string;
120
+ titleTemplate?: string;
121
+ };
122
+ /** Phase 12.1 — `mandu test` configuration block. */
123
+ test?: TestConfig;
124
+ plugins?: ManduPlugin[];
125
+ hooks?: Partial<ManduHooks>;
126
+ }
127
+
128
+ export const CONFIG_FILES = [
129
+ "mandu.config.ts",
130
+ "mandu.config.js",
131
+ "mandu.config.json",
132
+ path.join(".mandu", "guard.json"),
133
+ ];
134
+
135
+ export function coerceConfig(raw: unknown, source: string): ManduConfig {
136
+ if (!raw || typeof raw !== "object") return {};
137
+
138
+ // .mandu/guard.json can be guard-only
139
+ if (source.endsWith("guard.json") && !("guard" in (raw as Record<string, unknown>))) {
140
+ return { guard: raw as ManduConfig["guard"] };
141
+ }
142
+
143
+ return raw as ManduConfig;
144
+ }
145
+
146
+ export async function loadManduConfig(rootDir: string): Promise<ManduConfig> {
147
+ for (const fileName of CONFIG_FILES) {
148
+ const filePath = path.join(rootDir, fileName);
149
+ if (!(await Bun.file(filePath).exists())) {
150
+ continue;
151
+ }
152
+
153
+ if (fileName.endsWith(".json")) {
154
+ try {
155
+ const parsed = await readJsonFile(filePath);
156
+ return coerceConfig(parsed, fileName);
157
+ } catch {
158
+ return {};
159
+ }
160
+ }
161
+
162
+ try {
163
+ const module = await import(filePath);
164
+ const raw = module?.default ?? module;
165
+ return coerceConfig(raw, fileName);
166
+ } catch {
167
+ return {};
168
+ }
169
+ }
170
+
171
+ return {};
172
+ }