@mandujs/core 0.54.12 → 0.54.14

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.
Files changed (80) hide show
  1. package/package.json +9 -1
  2. package/scripts/postinstall-lock.ts +153 -153
  3. package/src/a11y/run-audit.ts +15 -15
  4. package/src/agent/__tests__/context.test.ts +49 -1
  5. package/src/agent/context.ts +535 -535
  6. package/src/agent/index.ts +6 -6
  7. package/src/agent/plan.ts +282 -282
  8. package/src/agent/repair.ts +171 -171
  9. package/src/agent/sync.ts +200 -200
  10. package/src/agent/types.ts +8 -0
  11. package/src/agent/verify.ts +100 -2
  12. package/src/brain/doctor/analyzer.ts +7 -7
  13. package/src/bundler/__tests__/build-runner.ts +36 -16
  14. package/src/bundler/__tests__/cold-start.test.ts +60 -60
  15. package/src/bundler/__tests__/css.test.ts +20 -20
  16. package/src/bundler/__tests__/fast-refresh.test.ts +24 -17
  17. package/src/bundler/analyzer.ts +15 -15
  18. package/src/bundler/build.test.ts +136 -48
  19. package/src/bundler/build.ts +193 -122
  20. package/src/bundler/css.ts +42 -42
  21. package/src/bundler/manifest-schema.ts +21 -21
  22. package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
  23. package/src/bundler/plugins/block-generated-imports.ts +13 -13
  24. package/src/bundler/types.ts +31 -31
  25. package/src/client/island.ts +79 -79
  26. package/src/config/validate.ts +1 -1
  27. package/src/contract/schema.ts +7 -0
  28. package/src/deploy/inference/context.ts +82 -82
  29. package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
  30. package/src/devtools/client/components/panel/panel-container.tsx +1 -1
  31. package/src/error/formatter.ts +10 -1
  32. package/src/experimental/index.ts +10 -0
  33. package/src/filling/context.ts +17 -17
  34. package/src/filling/filling.ts +22 -1
  35. package/src/filling/index.ts +15 -1
  36. package/src/generator/generate.ts +30 -30
  37. package/src/generator/index.ts +3 -3
  38. package/src/generator/templates.ts +210 -210
  39. package/src/guard/check.ts +9 -9
  40. package/src/guard/config-guard.ts +13 -13
  41. package/src/guard/fs-routes-policy.ts +51 -51
  42. package/src/guard/index.ts +11 -11
  43. package/src/index.ts +0 -10
  44. package/src/internal/index.ts +25 -0
  45. package/src/kitchen/api/file-api.ts +11 -11
  46. package/src/report/index.ts +1 -1
  47. package/src/resource/__tests__/generator.test.ts +6 -6
  48. package/src/resource/__tests__/schema.test.ts +14 -14
  49. package/src/resource/ddl/__tests__/emit.test.ts +165 -165
  50. package/src/resource/ddl/emit.ts +146 -146
  51. package/src/resource/generator-schema.ts +11 -11
  52. package/src/resource/generators/slot.ts +72 -72
  53. package/src/resource/schema.ts +21 -21
  54. package/src/router/client-entry.test.ts +84 -41
  55. package/src/router/client-entry.ts +156 -89
  56. package/src/router/fs-routes.test.ts +90 -0
  57. package/src/router/fs-routes.ts +37 -29
  58. package/src/router/fs-scanner.ts +81 -33
  59. package/src/router/fs-types.ts +8 -5
  60. package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
  61. package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
  62. package/src/runtime/__tests__/page-render-response.test.ts +164 -103
  63. package/src/runtime/__tests__/request-middleware.test.ts +70 -70
  64. package/src/runtime/devtools-adapter.ts +68 -68
  65. package/src/runtime/escape.ts +34 -34
  66. package/src/runtime/image-feature.ts +15 -0
  67. package/src/runtime/observability-lifecycle.ts +290 -290
  68. package/src/runtime/page-render-response.ts +208 -106
  69. package/src/runtime/rate-limit.ts +231 -0
  70. package/src/runtime/request-middleware.ts +31 -31
  71. package/src/runtime/router.test.ts +4 -4
  72. package/src/runtime/router.ts +10 -12
  73. package/src/runtime/scheduler-lifecycle.ts +64 -0
  74. package/src/runtime/server.ts +148 -353
  75. package/src/runtime/ssr.ts +59 -59
  76. package/src/runtime/static-files.ts +289 -289
  77. package/src/runtime/streaming-ssr.ts +22 -22
  78. package/src/spec/schema.ts +4 -3
  79. package/src/watcher/__tests__/watcher.test.ts +59 -59
  80. package/src/watcher/watcher.ts +61 -61
@@ -6,7 +6,7 @@ import type { BundleManifest } from "../bundler/types";
6
6
  import { isSafeManduUrl } from "../bundler/manifest-schema";
7
7
  import type { HydrationConfig, HydrationPriority } from "../spec/schema";
8
8
  import { PORTS, TIMEOUTS } from "../constants";
9
- import { decodeHtmlText, escapeHtmlAttr, escapeHtmlText, escapeJsonForInlineScript } from "./escape";
9
+ import { decodeHtmlText, escapeHtmlAttr, escapeHtmlText, escapeJsonForInlineScript } from "./escape";
10
10
  import { REACT_INTERNALS_SHIM_SCRIPT } from "./shims";
11
11
  import { generateFastRefreshPreamble } from "../bundler/dev";
12
12
  import { PREFETCH_HELPER_SCRIPT } from "../client/prefetch-helper";
@@ -46,7 +46,7 @@ export interface SSROptions {
46
46
  title?: string;
47
47
  lang?: string;
48
48
  /** 서버에서 로드한 데이터 (클라이언트로 전달) */
49
- serverData?: unknown;
49
+ serverData?: unknown;
50
50
  /** Hydration 설정 */
51
51
  hydration?: HydrationConfig;
52
52
  /** 번들 매니페스트 */
@@ -255,26 +255,26 @@ function generateHydrationScripts(
255
255
  ? Object.values(manifest.islands).filter((ib) => ib.route === routeId)
256
256
  : [];
257
257
 
258
- if (routeIslands.length > 0) {
259
- for (const ib of routeIslands) {
260
- const cacheBust = `${ib.js}${ib.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
261
- scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
262
- }
258
+ if (routeIslands.length > 0) {
259
+ for (const ib of routeIslands) {
260
+ const cacheBust = `${ib.js}${ib.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
261
+ scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
262
+ }
263
263
  } else {
264
264
  // Fallback: route-level bundle (backward compat)
265
265
  const bundle = manifest.bundles[routeId];
266
266
  if (bundle) {
267
267
  const cacheBust = `${bundle.js}${bundle.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
268
- scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
269
- }
270
- }
271
-
272
- if (manifest.partials) {
273
- for (const partial of Object.values(manifest.partials)) {
274
- const cacheBust = `${partial.js}${partial.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
275
- scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
276
- }
277
- }
268
+ scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
269
+ }
270
+ }
271
+
272
+ if (manifest.partials) {
273
+ for (const partial of Object.values(manifest.partials)) {
274
+ const cacheBust = `${partial.js}${partial.js.includes('?') ? '&' : '?'}v=${Date.now()}`;
275
+ scripts.push(`<link rel="modulepreload" href="${escapeHtmlAttr(cacheBust)}">`);
276
+ }
277
+ }
278
278
 
279
279
  // Runtime 로드 (hydrateIslands 실행 - dynamic import 사용)
280
280
  if (manifest.shared.runtime) {
@@ -619,9 +619,9 @@ export async function resolveAsyncElement(node: ReactNode): Promise<ReactNode> {
619
619
  return React.cloneElement(element, undefined, resolvedChildren);
620
620
  }
621
621
 
622
- export function renderToHTML(element: ReactElement, options: SSROptions = {}): string {
623
- const hasExplicitTitle = options.title !== undefined;
624
- const {
622
+ export function renderToHTML(element: ReactElement, options: SSROptions = {}): string {
623
+ const hasExplicitTitle = options.title !== undefined;
624
+ const {
625
625
  title = "Mandu App",
626
626
  lang = "ko",
627
627
  serverData,
@@ -713,14 +713,14 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
713
713
  const needsHydration =
714
714
  hydration && hydration.strategy !== "none" && routeId && bundleManifest;
715
715
 
716
- if (needsHydration && !islandPreWrapped) {
717
- // v0.8.0: bundleSrc를 data-mandu-src 속성으로 전달 (Runtime이 dynamic import로 로드)
718
- const bundle = bundleManifest.bundles[routeId];
719
- const bundleSrc = bundle?.js;
720
- if (bundleSrc) {
721
- content = wrapWithIsland(content, routeId, hydration.priority, bundleSrc);
722
- }
723
- }
716
+ if (needsHydration && !islandPreWrapped) {
717
+ // v0.8.0: bundleSrc를 data-mandu-src 속성으로 전달 (Runtime이 dynamic import로 로드)
718
+ const bundle = bundleManifest.bundles[routeId];
719
+ const bundleSrc = bundle?.js;
720
+ if (bundleSrc) {
721
+ content = wrapWithIsland(content, routeId, hydration.priority, bundleSrc);
722
+ }
723
+ }
724
724
 
725
725
  // Zero-JS 모드: island이 없는 페이지에서는 클라이언트 JS 번들을 전송하지 않음
726
726
  // HMR/DevTools는 dev 환경에서만 유지 (CSS 핫리로드 등)
@@ -731,10 +731,10 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
731
731
 
732
732
  if (needsHydration) {
733
733
  // 서버 데이터 스크립트 (클라이언트 hydration에서 사용)
734
- if (serverData !== undefined && routeId) {
735
- const wrappedData = {
736
- [routeId]: {
737
- serverData,
734
+ if (serverData !== undefined && routeId) {
735
+ const wrappedData = {
736
+ [routeId]: {
737
+ serverData,
738
738
  timestamp: Date.now(),
739
739
  },
740
740
  };
@@ -803,7 +803,7 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
803
803
  ? `<script>window.__MANDU_SPA__=false;</script>`
804
804
  : "";
805
805
 
806
- // #179: body 내 <link> 태그를 <head>로 호이스팅
806
+ // #179: body 내 <link> 태그를 <head>로 호이스팅
807
807
  // React 컴포넌트(layout.tsx 등)에서 <link>를 렌더링하면 body 안에 위치하게 되는데,
808
808
  // 폰트/스타일시트는 <head>에 있어야 FOUT 없이 로드됨
809
809
  const linkTagPattern = /<link\s[^>]*(?:rel=["'](?:stylesheet|preconnect|preload|icon|dns-prefetch)["'][^>]*|href=["'][^"']+["'][^>]*)\/?\s*>/gi;
@@ -812,19 +812,19 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
812
812
  hoistedLinks.push(match);
813
813
  return "";
814
814
  });
815
- const hoistedLinkTags = hoistedLinks.join("\n ");
816
-
817
- // #273 F15 — React 19 renders document metadata such as <title> from a
818
- // page component into the body string in this SSR path. Hoist the first
819
- // body title into <head> when no metadata/generateMetadata title was
820
- // provided, and always strip body titles to avoid duplicate/invalid HTML.
821
- let effectiveTitle = title;
822
- const titleTagPattern = /<title(?:\s[^>]*)?>([\s\S]*?)<\/title>/i;
823
- const bodyTitleMatch = bodyContent.match(titleTagPattern);
824
- const bodyWithoutTitle = bodyContent.replace(/<title(?:\s[^>]*)?>[\s\S]*?<\/title>/gi, "");
825
- if (!hasExplicitTitle && bodyTitleMatch) {
826
- effectiveTitle = decodeHtmlText(bodyTitleMatch[1] ?? title);
827
- }
815
+ const hoistedLinkTags = hoistedLinks.join("\n ");
816
+
817
+ // #273 F15 — React 19 renders document metadata such as <title> from a
818
+ // page component into the body string in this SSR path. Hoist the first
819
+ // body title into <head> when no metadata/generateMetadata title was
820
+ // provided, and always strip body titles to avoid duplicate/invalid HTML.
821
+ let effectiveTitle = title;
822
+ const titleTagPattern = /<title(?:\s[^>]*)?>([\s\S]*?)<\/title>/i;
823
+ const bodyTitleMatch = bodyContent.match(titleTagPattern);
824
+ const bodyWithoutTitle = bodyContent.replace(/<title(?:\s[^>]*)?>[\s\S]*?<\/title>/gi, "");
825
+ if (!hasExplicitTitle && bodyTitleMatch) {
826
+ effectiveTitle = decodeHtmlText(bodyTitleMatch[1] ?? title);
827
+ }
828
828
 
829
829
  // Phase 18.α — Dev Error Overlay injection.
830
830
  // Only emitted when `isDev` AND the user has not opted out (via
@@ -842,7 +842,7 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
842
842
  <head>
843
843
  <meta charset="UTF-8">
844
844
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
845
- <title>${escapeHtmlText(effectiveTitle)}</title>
845
+ <title>${escapeHtmlText(effectiveTitle)}</title>
846
846
  ${cssLinkTag}
847
847
  ${viewTransitionTag}
848
848
  ${prefetchScriptTag}
@@ -854,7 +854,7 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
854
854
  ${devErrorOverlayTag}
855
855
  </head>
856
856
  <body>
857
- <div id="root"${rootAttrs}>${bodyWithoutTitle}</div>
857
+ <div id="root"${rootAttrs}>${bodyWithoutTitle}</div>
858
858
  ${dataScript}
859
859
  ${routeScript}
860
860
  ${hydrationScripts}
@@ -871,11 +871,11 @@ export function renderToHTML(element: ReactElement, options: SSROptions = {}): s
871
871
  /**
872
872
  * Client-side Routing: 현재 라우트 정보 스크립트 생성
873
873
  */
874
- function generateRouteScript(
875
- routeId: string,
876
- pattern: string,
877
- _serverData?: unknown
878
- ): string {
874
+ function generateRouteScript(
875
+ routeId: string,
876
+ pattern: string,
877
+ _serverData?: unknown
878
+ ): string {
879
879
  const routeInfo = {
880
880
  id: routeId,
881
881
  pattern,
@@ -1176,12 +1176,12 @@ export function renderSSR(element: ReactElement, options: SSROptions = {}): Resp
1176
1176
  */
1177
1177
  export async function renderWithHydration(
1178
1178
  element: ReactElement,
1179
- options: SSROptions & {
1180
- routeId: string;
1181
- serverData: unknown;
1182
- hydration: HydrationConfig;
1183
- bundleManifest: BundleManifest;
1184
- }
1179
+ options: SSROptions & {
1180
+ routeId: string;
1181
+ serverData: unknown;
1182
+ hydration: HydrationConfig;
1183
+ bundleManifest: BundleManifest;
1184
+ }
1185
1185
  ): Promise<Response> {
1186
1186
  const html = renderToHTML(element, options);
1187
1187
  // Phase 7.2 R1 Agent C (H1) — same CSP header logic as renderSSR.