create-web-0to1 0.1.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.
Files changed (193) hide show
  1. package/README.md +68 -0
  2. package/internal/engine/create-feature-crud-script.ts +134 -0
  3. package/internal/engine/create-feature-crud.template.mjs +601 -0
  4. package/internal/engine/create-feature-script.ts +142 -0
  5. package/internal/engine/generator-engine.ts +546 -0
  6. package/internal/engine/standalone-feature-preset.ts +34 -0
  7. package/internal/meta/preset-plan.ts +41 -0
  8. package/internal/meta/runtime-copy-plan.ts +220 -0
  9. package/internal/meta/runtime-layout.ts +262 -0
  10. package/internal/meta/scaffold-manifest.ts +169 -0
  11. package/internal/meta/standalone-dependency-manifest.ts +75 -0
  12. package/package.json +45 -0
  13. package/scripts/create-app.mjs +1612 -0
  14. package/source/core/auth/auth-events.ts +13 -0
  15. package/source/core/error/app-error.ts +85 -0
  16. package/source/core/error/handle-app-error.client.ts +35 -0
  17. package/source/core/lib/dayjs.ts +25 -0
  18. package/source/core/query/query-client.ts +126 -0
  19. package/source/core/request/request-core.ts +210 -0
  20. package/source/core/routes/route-paths.ts +4 -0
  21. package/source/core/ui/button.tsx +24 -0
  22. package/source/core/ui/modal-store.ts +32 -0
  23. package/source/core/ui/text-input-field.tsx +36 -0
  24. package/source/core/utils/build-query-string.ts +30 -0
  25. package/source/core/utils/format/date.ts +41 -0
  26. package/source/core/utils/format/index.ts +3 -0
  27. package/source/core/utils/format/number.ts +13 -0
  28. package/source/core/utils/format/text.ts +15 -0
  29. package/source/core/utils/schema-utils.ts +27 -0
  30. package/source/wrappers/monorepo/core/internal.ts +21 -0
  31. package/source/wrappers/monorepo/core/src/index.ts +4 -0
  32. package/source/wrappers/monorepo/core-next/src/auth.client.ts +1 -0
  33. package/source/wrappers/monorepo/core-next/src/auth.server.ts +94 -0
  34. package/source/wrappers/monorepo/core-next/src/bootstrap.client.tsx +21 -0
  35. package/source/wrappers/monorepo/core-next/src/bootstrap.tsx +18 -0
  36. package/source/wrappers/monorepo/core-next/src/index.ts +1 -0
  37. package/source/wrappers/monorepo/core-react/src/app-providers.tsx +36 -0
  38. package/source/wrappers/monorepo/core-react/src/auth.ts +42 -0
  39. package/source/wrappers/monorepo/core-react/src/hydration.tsx +21 -0
  40. package/source/wrappers/monorepo/core-react/src/index.ts +7 -0
  41. package/source/wrappers/monorepo/core-react/src/provider.tsx +49 -0
  42. package/source/wrappers/monorepo/core-react/src/query-client.ts +48 -0
  43. package/source/wrappers/monorepo/core-react/src/query-error-handler.ts +62 -0
  44. package/source/wrappers/monorepo/core-react/src/query-keys.ts +22 -0
  45. package/source/wrappers/monorepo/request/core-fetch.ts +27 -0
  46. package/source/wrappers/monorepo/request/core-request.ts +93 -0
  47. package/source/wrappers/next/auth/auth-error-listener.tsx +34 -0
  48. package/source/wrappers/next/error/handle-app-error.server.ts +41 -0
  49. package/source/wrappers/next/query/hydration.tsx +20 -0
  50. package/source/wrappers/next/query/providers.tsx +35 -0
  51. package/source/wrappers/next/request/request.client.ts +24 -0
  52. package/source/wrappers/next/request/request.server.ts +64 -0
  53. package/source/wrappers/next/request/request.ts +52 -0
  54. package/source/wrappers/next/ui/global-modal.tsx +29 -0
  55. package/source/wrappers/react/auth/auth-error-listener.tsx +34 -0
  56. package/source/wrappers/react/query/providers.tsx +31 -0
  57. package/source/wrappers/react/request/request.client.ts +24 -0
  58. package/source/wrappers/react/request/request.ts +51 -0
  59. package/source/wrappers/react/ui/global-modal.tsx +27 -0
  60. package/templates/monorepo/.dockerignore +38 -0
  61. package/templates/monorepo/README.md +292 -0
  62. package/templates/monorepo/_gitignore +38 -0
  63. package/templates/monorepo/_npmrc +1 -0
  64. package/templates/monorepo/apps/project/Dockerfile +32 -0
  65. package/templates/monorepo/apps/project/eslint.config.mjs +4 -0
  66. package/templates/monorepo/apps/project/index.html +14 -0
  67. package/templates/monorepo/apps/project/index.ts +15 -0
  68. package/templates/monorepo/apps/project/package.json +21 -0
  69. package/templates/monorepo/apps/project/tsconfig.json +9 -0
  70. package/templates/monorepo/apps/project/vite.config.ts +6 -0
  71. package/templates/monorepo/apps/web/Dockerfile +43 -0
  72. package/templates/monorepo/apps/web/README.md +111 -0
  73. package/templates/monorepo/apps/web/_gitignore +36 -0
  74. package/templates/monorepo/apps/web/app/favicon.ico +0 -0
  75. package/templates/monorepo/apps/web/app/global-error.tsx +12 -0
  76. package/templates/monorepo/apps/web/app/globals.css +0 -0
  77. package/templates/monorepo/apps/web/app/layout.tsx +28 -0
  78. package/templates/monorepo/apps/web/app/page.tsx +7 -0
  79. package/templates/monorepo/apps/web/app/providers.tsx +25 -0
  80. package/templates/monorepo/apps/web/eslint.config.js +4 -0
  81. package/templates/monorepo/apps/web/next-env.d.ts +6 -0
  82. package/templates/monorepo/apps/web/next.config.js +4 -0
  83. package/templates/monorepo/apps/web/package.json +31 -0
  84. package/templates/monorepo/apps/web/public/file-text.svg +3 -0
  85. package/templates/monorepo/apps/web/public/globe.svg +10 -0
  86. package/templates/monorepo/apps/web/public/next.svg +1 -0
  87. package/templates/monorepo/apps/web/public/turborepo-dark.svg +19 -0
  88. package/templates/monorepo/apps/web/public/turborepo-light.svg +19 -0
  89. package/templates/monorepo/apps/web/public/vercel.svg +10 -0
  90. package/templates/monorepo/apps/web/public/window.svg +3 -0
  91. package/templates/monorepo/apps/web/tsconfig.json +20 -0
  92. package/templates/monorepo/package.json +24 -0
  93. package/templates/monorepo/packages/core/eslint.config.mjs +4 -0
  94. package/templates/monorepo/packages/core/package.json +32 -0
  95. package/templates/monorepo/packages/core/tsconfig.json +8 -0
  96. package/templates/monorepo/packages/core-next/eslint.config.mjs +13 -0
  97. package/templates/monorepo/packages/core-next/package.json +43 -0
  98. package/templates/monorepo/packages/core-next/tsconfig.json +8 -0
  99. package/templates/monorepo/packages/core-react/eslint.config.mjs +4 -0
  100. package/templates/monorepo/packages/core-react/package.json +34 -0
  101. package/templates/monorepo/packages/core-react/tsconfig.json +8 -0
  102. package/templates/monorepo/packages/eslint-config/README.md +3 -0
  103. package/templates/monorepo/packages/eslint-config/base.js +57 -0
  104. package/templates/monorepo/packages/eslint-config/next.js +22 -0
  105. package/templates/monorepo/packages/eslint-config/package.json +25 -0
  106. package/templates/monorepo/packages/eslint-config/react-internal.js +33 -0
  107. package/templates/monorepo/packages/typescript-config/base.json +19 -0
  108. package/templates/monorepo/packages/typescript-config/nextjs.json +12 -0
  109. package/templates/monorepo/packages/typescript-config/package.json +9 -0
  110. package/templates/monorepo/packages/typescript-config/react-library.json +7 -0
  111. package/templates/monorepo/packages/ui/eslint.config.mjs +4 -0
  112. package/templates/monorepo/packages/ui/package.json +26 -0
  113. package/templates/monorepo/packages/ui/src/button.tsx +20 -0
  114. package/templates/monorepo/packages/ui/src/card.tsx +27 -0
  115. package/templates/monorepo/packages/ui/src/code.tsx +11 -0
  116. package/templates/monorepo/packages/ui/tsconfig.json +8 -0
  117. package/templates/monorepo/pnpm-workspace.yaml +9 -0
  118. package/templates/monorepo/turbo/generators/config.js +1336 -0
  119. package/templates/monorepo/turbo/generators/templates/next-app/Dockerfile.tpl +30 -0
  120. package/templates/monorepo/turbo/generators/templates/next-app/README.md.tpl +118 -0
  121. package/templates/monorepo/turbo/generators/templates/next-app/app/global-error.tsx.tpl +12 -0
  122. package/templates/monorepo/turbo/generators/templates/next-app/app/globals.css.tpl +1 -0
  123. package/templates/monorepo/turbo/generators/templates/next-app/app/layout.tsx.tpl +29 -0
  124. package/templates/monorepo/turbo/generators/templates/next-app/app/page.tsx.tpl +7 -0
  125. package/templates/monorepo/turbo/generators/templates/next-app/app/providers.tsx.tpl +25 -0
  126. package/templates/monorepo/turbo/generators/templates/next-app/eslint.config.js.tpl +4 -0
  127. package/templates/monorepo/turbo/generators/templates/next-app/next.config.js.tpl +6 -0
  128. package/templates/monorepo/turbo/generators/templates/next-app/tsconfig.json.tpl +18 -0
  129. package/templates/monorepo/turbo/generators/templates/vite-app/Dockerfile.tpl +22 -0
  130. package/templates/monorepo/turbo/generators/templates/vite-app/README.plain.md.tpl +90 -0
  131. package/templates/monorepo/turbo/generators/templates/vite-app/README.react.md.tpl +107 -0
  132. package/templates/monorepo/turbo/generators/templates/vite-app/eslint.config.mjs.tpl +4 -0
  133. package/templates/monorepo/turbo/generators/templates/vite-app/index.html.tpl +12 -0
  134. package/templates/monorepo/turbo/generators/templates/vite-app/index.ts.tpl +22 -0
  135. package/templates/monorepo/turbo/generators/templates/vite-app/tsconfig.json.tpl +9 -0
  136. package/templates/monorepo/turbo/generators/templates/vite-app/vite.config.ts.tpl +6 -0
  137. package/templates/monorepo/turbo.json +28 -0
  138. package/templates/next/.env.example +2 -0
  139. package/templates/next/.prettierignore +9 -0
  140. package/templates/next/.prettierrc.json +9 -0
  141. package/templates/next/README.md +246 -0
  142. package/templates/next/_gitignore +44 -0
  143. package/templates/next/eslint.config.mjs +51 -0
  144. package/templates/next/next.config.ts +7 -0
  145. package/templates/next/package.json +24 -0
  146. package/templates/next/postcss.config.mjs +7 -0
  147. package/templates/next/scripts/create-feature-crud.mjs +5 -0
  148. package/templates/next/scripts/create-feature.mjs +5 -0
  149. package/templates/next/src/app/error.tsx +33 -0
  150. package/templates/next/src/app/globals.css +35 -0
  151. package/templates/next/src/app/layout.tsx +39 -0
  152. package/templates/next/src/app/login/page.tsx +17 -0
  153. package/templates/next/src/app/page.tsx +32 -0
  154. package/templates/next/src/app/providers.tsx +20 -0
  155. package/templates/next/tsconfig.json +34 -0
  156. package/templates/react/.env.example +1 -0
  157. package/templates/react/.prettierignore +10 -0
  158. package/templates/react/.prettierrc.json +9 -0
  159. package/templates/react/README.md +250 -0
  160. package/templates/react/_gitignore +31 -0
  161. package/templates/react/eslint.config.mjs +64 -0
  162. package/templates/react/package.json +19 -0
  163. package/templates/react/scripts/create-feature-crud.mjs +5 -0
  164. package/templates/react/scripts/create-feature.mjs +5 -0
  165. package/templates/react/src/app/app.tsx +15 -0
  166. package/templates/react/src/app/error-boundary.tsx +59 -0
  167. package/templates/react/src/app/frame.tsx +32 -0
  168. package/templates/react/src/app/globals.css +43 -0
  169. package/templates/react/src/app/not-found-page.tsx +23 -0
  170. package/templates/react/src/app/providers.tsx +16 -0
  171. package/templates/react/src/app/router.tsx +62 -0
  172. package/templates/react/src/main.tsx +12 -0
  173. package/templates/react/src/pages/index/page.tsx +36 -0
  174. package/templates/react/src/pages/login/page.tsx +18 -0
  175. package/templates/react/tsconfig.app.json +30 -0
  176. package/templates/react/tsconfig.json +4 -0
  177. package/templates/react/tsconfig.node.json +24 -0
  178. package/templates/react/vite.config.ts +14 -0
  179. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/00-/354/240/204/353/260/230/354/240/201/354/235/270-/355/217/264/353/215/224/352/265/254/354/241/260.md +150 -0
  180. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/01-/352/265/254/354/241/260/354/231/200-/353/235/274/354/232/260/355/214/205.md +186 -0
  181. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/02-/354/204/234/353/262/204/354/231/200-/355/201/264/353/235/274/354/235/264/354/226/270/355/212/270.md +86 -0
  182. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/03-/354/203/201/355/203/234/352/264/200/353/246/254.md +84 -0
  183. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/04-API/354/231/200-/353/215/260/354/235/264/355/204/260.md +199 -0
  184. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/05-/354/227/220/353/237/254/354/231/200-UI-/354/203/201/355/203/234.md +159 -0
  185. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/06-/355/217/274.md +116 -0
  186. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/07-/354/212/244/355/203/200/354/235/274/353/247/201/352/263/274-/354/240/221/352/267/274/354/204/261.md +73 -0
  187. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/08-/353/204/244/354/235/264/353/260/215-/354/204/244/354/240/225-/355/217/254/353/247/267/355/214/205.md +98 -0
  188. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/09-/353/235/274/354/232/260/355/212/270-/354/240/225/354/235/230.md +169 -0
  189. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/10-/354/273/244/353/260/213-/354/273/250/353/262/244/354/205/230.md +64 -0
  190. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/11-/352/270/260/355/203/200-/354/233/220/354/271/231.md +187 -0
  191. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/12-/354/213/244/353/254/264-/353/215/260/354/235/264/355/204/260-/355/214/250/355/204/264.md +302 -0
  192. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/13-/354/204/261/353/212/245-/354/233/220/354/271/231.md +175 -0
  193. package/templates/shared/docs//352/260/234/353/260/234/354/233/220/354/271/231/README.md +39 -0
@@ -0,0 +1,34 @@
1
+ export type StandaloneFeaturePreset = 'react' | 'next';
2
+
3
+ export const standaloneFeaturePresetConfigs = {
4
+ react: {
5
+ packageJsonKey: 'react-0to1',
6
+ routeRootConst: 'pagesDir',
7
+ routeRootName: 'pages',
8
+ routeOutputLabel: 'page route',
9
+ optionalRouteHint:
10
+ "console.log('src/pages/**/page.tsx는 자동으로 React Router 경로가 됩니다.');\n",
11
+ envHint: `console.log(
12
+ 'VITE_API_BASE_URL 환경 변수를 설정한 뒤 실제 백엔드 규약에 맞게 엔드포인트를 연결하세요.',
13
+ );`,
14
+ },
15
+ next: {
16
+ packageJsonKey: 'next-0to1',
17
+ routeRootConst: 'appDir',
18
+ routeRootName: 'app',
19
+ routeOutputLabel: 'app route',
20
+ optionalRouteHint: '',
21
+ envHint:
22
+ "console.log('NEXT_PUBLIC_API_BASE_URL과 필요하면 API_BASE_URL 환경 변수를 설정한 뒤 실제 백엔드 규약에 맞게 엔드포인트를 연결하세요.');",
23
+ },
24
+ } as const satisfies Record<
25
+ StandaloneFeaturePreset,
26
+ {
27
+ packageJsonKey: string;
28
+ routeRootConst: string;
29
+ routeRootName: string;
30
+ routeOutputLabel: string;
31
+ optionalRouteHint: string;
32
+ envHint: string;
33
+ }
34
+ >;
@@ -0,0 +1,41 @@
1
+ import {
2
+ getScaffoldManifest,
3
+ type PresetId,
4
+ } from './scaffold-manifest.ts';
5
+ import { getRuntimeLayout } from './runtime-layout.ts';
6
+
7
+ export type PresetPlan = {
8
+ preset: PresetId;
9
+ templateDir: string;
10
+ scaffold: ReturnType<typeof getScaffoldManifest>;
11
+ runtime: ReturnType<typeof getRuntimeLayout>;
12
+ notes: string[];
13
+ };
14
+
15
+ const templateDirs = {
16
+ react: 'templates/react',
17
+ next: 'templates/next',
18
+ monorepo: 'templates/monorepo',
19
+ } as const satisfies Record<PresetId, string>;
20
+
21
+ export function getPresetPlan(preset: PresetId): PresetPlan {
22
+ return {
23
+ preset,
24
+ templateDir: templateDirs[preset],
25
+ scaffold: getScaffoldManifest(preset),
26
+ runtime: getRuntimeLayout(preset),
27
+ notes: [
28
+ 'templateDir는 정적 override file source입니다.',
29
+ 'runtime layout은 source/ 아래 TS/TSX runtime을 어디로 내보낼지 정합니다.',
30
+ '실제 create-app 구현은 이 plan을 읽어서 scaffold -> clean -> copy -> rewrite 순서로 실행합니다.',
31
+ ],
32
+ };
33
+ }
34
+
35
+ export function listPresetPlans(): PresetPlan[] {
36
+ return [
37
+ getPresetPlan('react'),
38
+ getPresetPlan('next'),
39
+ getPresetPlan('monorepo'),
40
+ ];
41
+ }
@@ -0,0 +1,220 @@
1
+ import * as path from 'node:path/posix';
2
+
3
+ import type { PresetId } from './scaffold-manifest.ts';
4
+ import {
5
+ getRuntimeLayout,
6
+ type RuntimeLayer,
7
+ type RuntimeRootMapping,
8
+ } from './runtime-layout.ts';
9
+
10
+ const SOURCE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'] as const;
11
+
12
+ type SourceFileExtension = (typeof SOURCE_FILE_EXTENSIONS)[number];
13
+
14
+ export type RuntimeCopyEntry = {
15
+ sourcePath: string;
16
+ outputPath: string;
17
+ layer: RuntimeLayer;
18
+ mapping: RuntimeRootMapping;
19
+ };
20
+
21
+ export type RewriteRuntimeImportsOptions = {
22
+ preset: PresetId;
23
+ sourcePath: string;
24
+ sourceText: string;
25
+ availableSourceFiles: string[];
26
+ };
27
+
28
+ function toPosixPath(value: string) {
29
+ return value.replaceAll('\\', '/');
30
+ }
31
+
32
+ function normalizeSourcePath(value: string) {
33
+ const normalized = path.normalize(toPosixPath(value));
34
+
35
+ if (normalized.startsWith('source/')) {
36
+ return normalized.slice('source/'.length);
37
+ }
38
+
39
+ return normalized;
40
+ }
41
+
42
+ function ensureRelativeSpecifier(value: string) {
43
+ if (value === '') {
44
+ return './';
45
+ }
46
+
47
+ return value.startsWith('.') ? value : `./${value}`;
48
+ }
49
+
50
+ function stripSourceExtension(value: string) {
51
+ const extension = path.extname(value) as SourceFileExtension | '';
52
+
53
+ if (SOURCE_FILE_EXTENSIONS.includes(extension as SourceFileExtension)) {
54
+ return value.slice(0, -extension.length);
55
+ }
56
+
57
+ return value;
58
+ }
59
+
60
+ function findRuntimeRootMapping(preset: PresetId, sourcePath: string) {
61
+ const normalizedSourcePath = normalizeSourcePath(sourcePath);
62
+ const { mappings } = getRuntimeLayout(preset);
63
+
64
+ return (
65
+ mappings.find(
66
+ (mapping) =>
67
+ normalizedSourcePath === mapping.sourceDir ||
68
+ normalizedSourcePath.startsWith(`${mapping.sourceDir}/`),
69
+ ) ?? null
70
+ );
71
+ }
72
+
73
+ function joinOutputPath(mapping: RuntimeRootMapping, sourcePath: string) {
74
+ const normalizedSourcePath = normalizeSourcePath(sourcePath);
75
+ const relativePath = path.relative(mapping.sourceDir, normalizedSourcePath);
76
+
77
+ if (!relativePath || relativePath === '.') {
78
+ return mapping.outputDir;
79
+ }
80
+
81
+ return path.join(mapping.outputDir, relativePath);
82
+ }
83
+
84
+ function resolveSourceModulePath(
85
+ sourcePath: string,
86
+ specifier: string,
87
+ availableSourceFiles: Set<string>,
88
+ ) {
89
+ const sourceDir = path.dirname(normalizeSourcePath(sourcePath));
90
+ const baseCandidate = path.normalize(path.join(sourceDir, specifier));
91
+
92
+ const directMatch = normalizeSourcePath(baseCandidate);
93
+ if (availableSourceFiles.has(directMatch)) {
94
+ return directMatch;
95
+ }
96
+
97
+ for (const extension of SOURCE_FILE_EXTENSIONS) {
98
+ const withExtension = normalizeSourcePath(`${baseCandidate}${extension}`);
99
+ if (availableSourceFiles.has(withExtension)) {
100
+ return withExtension;
101
+ }
102
+ }
103
+
104
+ for (const extension of SOURCE_FILE_EXTENSIONS) {
105
+ const asIndexFile = normalizeSourcePath(path.join(baseCandidate, `index${extension}`));
106
+ if (availableSourceFiles.has(asIndexFile)) {
107
+ return asIndexFile;
108
+ }
109
+ }
110
+
111
+ return null;
112
+ }
113
+
114
+ export function resolveRuntimeOutputPath(preset: PresetId, sourcePath: string) {
115
+ const mapping = findRuntimeRootMapping(preset, sourcePath);
116
+
117
+ if (!mapping) {
118
+ return null;
119
+ }
120
+
121
+ return joinOutputPath(mapping, sourcePath);
122
+ }
123
+
124
+ export function createRuntimeCopyPlan(preset: PresetId, sourcePaths: string[]) {
125
+ return sourcePaths
126
+ .map((sourcePath) => {
127
+ const normalizedSourcePath = normalizeSourcePath(sourcePath);
128
+ const mapping = findRuntimeRootMapping(preset, normalizedSourcePath);
129
+
130
+ if (!mapping) {
131
+ return null;
132
+ }
133
+
134
+ return {
135
+ sourcePath: normalizedSourcePath,
136
+ outputPath: joinOutputPath(mapping, normalizedSourcePath),
137
+ layer: mapping.layer,
138
+ mapping,
139
+ } satisfies RuntimeCopyEntry;
140
+ })
141
+ .filter((entry): entry is RuntimeCopyEntry => entry !== null)
142
+ .sort((left, right) => left.outputPath.localeCompare(right.outputPath));
143
+ }
144
+
145
+ export function rewriteRuntimeModuleSpecifier(
146
+ preset: PresetId,
147
+ sourcePath: string,
148
+ specifier: string,
149
+ availableSourceFiles: string[],
150
+ ) {
151
+ if (!specifier.startsWith('.')) {
152
+ return specifier;
153
+ }
154
+
155
+ const availableSourceFileSet = new Set(
156
+ availableSourceFiles.map((filePath) => normalizeSourcePath(filePath)),
157
+ );
158
+ const resolvedTargetSourcePath = resolveSourceModulePath(
159
+ sourcePath,
160
+ specifier,
161
+ availableSourceFileSet,
162
+ );
163
+
164
+ if (!resolvedTargetSourcePath) {
165
+ return specifier;
166
+ }
167
+
168
+ const currentOutputPath = resolveRuntimeOutputPath(preset, sourcePath);
169
+ const targetOutputPath = resolveRuntimeOutputPath(preset, resolvedTargetSourcePath);
170
+
171
+ if (!currentOutputPath || !targetOutputPath) {
172
+ return specifier;
173
+ }
174
+
175
+ const relativeOutputPath = path.relative(
176
+ path.dirname(currentOutputPath),
177
+ stripSourceExtension(targetOutputPath),
178
+ );
179
+
180
+ return ensureRelativeSpecifier(relativeOutputPath);
181
+ }
182
+
183
+ export function rewriteRuntimeImports({
184
+ preset,
185
+ sourcePath,
186
+ sourceText,
187
+ availableSourceFiles,
188
+ }: RewriteRuntimeImportsOptions) {
189
+ const replaceSpecifier = (specifier: string) =>
190
+ rewriteRuntimeModuleSpecifier(preset, sourcePath, specifier, availableSourceFiles);
191
+
192
+ const fromPattern = /(\bfrom\s+)(['"])([^'"]+)(\2)/g;
193
+ const sideEffectImportPattern = /(^\s*import\s+)(['"])([^'"]+)(\2)/gm;
194
+
195
+ const withRewrittenFromSpecifiers = sourceText.replace(
196
+ fromPattern,
197
+ (match, fromKeyword, quote, specifier, closingQuote) => {
198
+ const nextSpecifier = replaceSpecifier(specifier);
199
+
200
+ if (nextSpecifier === specifier) {
201
+ return match;
202
+ }
203
+
204
+ return `${fromKeyword}${quote}${nextSpecifier}${closingQuote}`;
205
+ },
206
+ );
207
+
208
+ return withRewrittenFromSpecifiers.replace(
209
+ sideEffectImportPattern,
210
+ (match, importKeyword, quote, specifier, closingQuote) => {
211
+ const nextSpecifier = replaceSpecifier(specifier);
212
+
213
+ if (nextSpecifier === specifier) {
214
+ return match;
215
+ }
216
+
217
+ return `${importKeyword}${quote}${nextSpecifier}${closingQuote}`;
218
+ },
219
+ );
220
+ }
@@ -0,0 +1,262 @@
1
+ import type { PresetId } from './scaffold-manifest.ts';
2
+
3
+ export type RuntimeLayer = 'core' | 'wrapper';
4
+
5
+ export type RuntimeRootMapping = {
6
+ // `source/` 기준 상대 경로입니다.
7
+ sourceDir: string;
8
+ // 생성 결과물 기준 상대 경로입니다.
9
+ outputDir: string;
10
+ layer: RuntimeLayer;
11
+ notes?: string[];
12
+ };
13
+
14
+ export type PresetRuntimeLayout = {
15
+ preset: PresetId;
16
+ notes: string[];
17
+ mappings: RuntimeRootMapping[];
18
+ };
19
+
20
+ function definePresetRuntimeLayout(
21
+ layout: PresetRuntimeLayout,
22
+ ): PresetRuntimeLayout {
23
+ return layout;
24
+ }
25
+
26
+ export const reactRuntimeLayout = definePresetRuntimeLayout({
27
+ preset: 'react',
28
+ notes: [
29
+ 'React standalone은 shared 도메인 폴더를 그대로 public surface로 사용합니다.',
30
+ '공통 core 구현도 src/shared 아래로 바로 내려서 wrapper 재노출 단계를 줄입니다.',
31
+ 'app entry 파일은 runtime이 아니라 templates/react가 소유합니다.',
32
+ ],
33
+ mappings: [
34
+ {
35
+ sourceDir: 'core/query',
36
+ outputDir: 'src/shared/query',
37
+ layer: 'core',
38
+ },
39
+ {
40
+ sourceDir: 'core/request',
41
+ outputDir: 'src/shared/api',
42
+ layer: 'core',
43
+ },
44
+ {
45
+ sourceDir: 'core/auth',
46
+ outputDir: 'src/shared/auth',
47
+ layer: 'core',
48
+ },
49
+ {
50
+ sourceDir: 'core/error',
51
+ outputDir: 'src/shared/error',
52
+ layer: 'core',
53
+ },
54
+ {
55
+ sourceDir: 'core/routes',
56
+ outputDir: 'src/shared/routes',
57
+ layer: 'core',
58
+ },
59
+ {
60
+ sourceDir: 'core/ui',
61
+ outputDir: 'src/shared/ui',
62
+ layer: 'core',
63
+ },
64
+ {
65
+ sourceDir: 'core/utils',
66
+ outputDir: 'src/shared/utils',
67
+ layer: 'core',
68
+ },
69
+ {
70
+ sourceDir: 'core/lib',
71
+ outputDir: 'src/shared/lib',
72
+ layer: 'core',
73
+ },
74
+ {
75
+ sourceDir: 'wrappers/react/query',
76
+ outputDir: 'src/shared/query',
77
+ layer: 'wrapper',
78
+ },
79
+ {
80
+ sourceDir: 'wrappers/react/request',
81
+ outputDir: 'src/shared/api',
82
+ layer: 'wrapper',
83
+ },
84
+ {
85
+ sourceDir: 'wrappers/react/auth',
86
+ outputDir: 'src/shared/auth',
87
+ layer: 'wrapper',
88
+ },
89
+ {
90
+ sourceDir: 'wrappers/react/error',
91
+ outputDir: 'src/shared/error',
92
+ layer: 'wrapper',
93
+ },
94
+ {
95
+ sourceDir: 'wrappers/react/ui',
96
+ outputDir: 'src/shared/ui',
97
+ layer: 'wrapper',
98
+ },
99
+ ],
100
+ });
101
+
102
+ export const nextRuntimeLayout = definePresetRuntimeLayout({
103
+ preset: 'next',
104
+ notes: [
105
+ 'Next standalone도 shared 도메인 폴더를 그대로 public surface로 사용합니다.',
106
+ '공통 core 구현도 src/shared 아래로 바로 내려서 wrapper 재노출 단계를 줄입니다.',
107
+ 'app entry 파일은 runtime이 아니라 templates/next가 소유합니다.',
108
+ ],
109
+ mappings: [
110
+ {
111
+ sourceDir: 'core/query',
112
+ outputDir: 'src/shared/query',
113
+ layer: 'core',
114
+ },
115
+ {
116
+ sourceDir: 'core/request',
117
+ outputDir: 'src/shared/api',
118
+ layer: 'core',
119
+ },
120
+ {
121
+ sourceDir: 'core/auth',
122
+ outputDir: 'src/shared/auth',
123
+ layer: 'core',
124
+ },
125
+ {
126
+ sourceDir: 'core/error',
127
+ outputDir: 'src/shared/error',
128
+ layer: 'core',
129
+ },
130
+ {
131
+ sourceDir: 'core/routes',
132
+ outputDir: 'src/shared/routes',
133
+ layer: 'core',
134
+ },
135
+ {
136
+ sourceDir: 'core/ui',
137
+ outputDir: 'src/shared/ui',
138
+ layer: 'core',
139
+ },
140
+ {
141
+ sourceDir: 'core/utils',
142
+ outputDir: 'src/shared/utils',
143
+ layer: 'core',
144
+ },
145
+ {
146
+ sourceDir: 'core/lib',
147
+ outputDir: 'src/shared/lib',
148
+ layer: 'core',
149
+ },
150
+ {
151
+ sourceDir: 'wrappers/next/query',
152
+ outputDir: 'src/shared/query',
153
+ layer: 'wrapper',
154
+ },
155
+ {
156
+ sourceDir: 'wrappers/next/request',
157
+ outputDir: 'src/shared/api',
158
+ layer: 'wrapper',
159
+ },
160
+ {
161
+ sourceDir: 'wrappers/next/auth',
162
+ outputDir: 'src/shared/auth',
163
+ layer: 'wrapper',
164
+ },
165
+ {
166
+ sourceDir: 'wrappers/next/error',
167
+ outputDir: 'src/shared/error',
168
+ layer: 'wrapper',
169
+ },
170
+ {
171
+ sourceDir: 'wrappers/next/ui',
172
+ outputDir: 'src/shared/ui',
173
+ layer: 'wrapper',
174
+ },
175
+ ],
176
+ });
177
+
178
+ export const monorepoRuntimeLayout = definePresetRuntimeLayout({
179
+ preset: 'monorepo',
180
+ notes: [
181
+ 'monorepo 공통 core 구현도 packages/core/src 아래 도메인 폴더로 바로 둡니다.',
182
+ 'packages/core는 request/config/schema 같은 공개 facade 역할도 같이 맡습니다.',
183
+ 'packages/core-react, packages/core-next는 배포 형태 차이만 담는 wrapper package입니다.',
184
+ ],
185
+ mappings: [
186
+ {
187
+ sourceDir: 'core/query',
188
+ outputDir: 'packages/core/src/query',
189
+ layer: 'core',
190
+ },
191
+ {
192
+ sourceDir: 'core/request',
193
+ outputDir: 'packages/core/src/request',
194
+ layer: 'core',
195
+ },
196
+ {
197
+ sourceDir: 'core/auth',
198
+ outputDir: 'packages/core/src/auth',
199
+ layer: 'core',
200
+ },
201
+ {
202
+ sourceDir: 'core/error',
203
+ outputDir: 'packages/core/src/error',
204
+ layer: 'core',
205
+ },
206
+ {
207
+ sourceDir: 'core/routes',
208
+ outputDir: 'packages/core/src/routes',
209
+ layer: 'core',
210
+ },
211
+ {
212
+ sourceDir: 'core/ui',
213
+ outputDir: 'packages/core/src/ui',
214
+ layer: 'core',
215
+ },
216
+ {
217
+ sourceDir: 'core/utils',
218
+ outputDir: 'packages/core/src/utils',
219
+ layer: 'core',
220
+ },
221
+ {
222
+ sourceDir: 'core/lib',
223
+ outputDir: 'packages/core/src/lib',
224
+ layer: 'core',
225
+ },
226
+ {
227
+ sourceDir: 'wrappers/monorepo/core/src',
228
+ outputDir: 'packages/core/src',
229
+ layer: 'wrapper',
230
+ },
231
+ {
232
+ sourceDir: 'wrappers/monorepo/core',
233
+ outputDir: 'packages/core/src',
234
+ layer: 'wrapper',
235
+ },
236
+ {
237
+ sourceDir: 'wrappers/monorepo/request',
238
+ outputDir: 'packages/core/src/request',
239
+ layer: 'wrapper',
240
+ },
241
+ {
242
+ sourceDir: 'wrappers/monorepo/core-react/src',
243
+ outputDir: 'packages/core-react/src',
244
+ layer: 'wrapper',
245
+ },
246
+ {
247
+ sourceDir: 'wrappers/monorepo/core-next/src',
248
+ outputDir: 'packages/core-next/src',
249
+ layer: 'wrapper',
250
+ },
251
+ ],
252
+ });
253
+
254
+ export const runtimeLayouts = {
255
+ react: reactRuntimeLayout,
256
+ next: nextRuntimeLayout,
257
+ monorepo: monorepoRuntimeLayout,
258
+ } as const satisfies Record<PresetId, PresetRuntimeLayout>;
259
+
260
+ export function getRuntimeLayout(preset: PresetId) {
261
+ return runtimeLayouts[preset];
262
+ }