@simplysm/sd-cli 13.0.69 → 13.0.71

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 (201) hide show
  1. package/README.md +10 -957
  2. package/dist/builders/BaseBuilder.d.ts +23 -23
  3. package/dist/builders/BaseBuilder.d.ts.map +1 -1
  4. package/dist/builders/BaseBuilder.js +15 -15
  5. package/dist/builders/DtsBuilder.d.ts +4 -4
  6. package/dist/builders/DtsBuilder.js +1 -1
  7. package/dist/builders/LibraryBuilder.d.ts +3 -3
  8. package/dist/builders/types.d.ts +10 -10
  9. package/dist/capacitor/capacitor.d.ts +36 -36
  10. package/dist/capacitor/capacitor.js +63 -63
  11. package/dist/capacitor/capacitor.js.map +1 -1
  12. package/dist/commands/add-client.d.ts +8 -8
  13. package/dist/commands/add-client.js +15 -15
  14. package/dist/commands/add-client.js.map +1 -1
  15. package/dist/commands/add-server.d.ts +9 -9
  16. package/dist/commands/add-server.js +13 -13
  17. package/dist/commands/add-server.js.map +1 -1
  18. package/dist/commands/build.d.ts +9 -9
  19. package/dist/commands/check.js +3 -3
  20. package/dist/commands/check.js.map +1 -1
  21. package/dist/commands/dev.d.ts +9 -9
  22. package/dist/commands/device.d.ts +9 -9
  23. package/dist/commands/device.d.ts.map +1 -1
  24. package/dist/commands/device.js +17 -17
  25. package/dist/commands/device.js.map +1 -1
  26. package/dist/commands/init.d.ts +6 -6
  27. package/dist/commands/init.js +12 -12
  28. package/dist/commands/init.js.map +1 -1
  29. package/dist/commands/lint.d.ts +23 -23
  30. package/dist/commands/lint.d.ts.map +1 -1
  31. package/dist/commands/lint.js +25 -25
  32. package/dist/commands/lint.js.map +1 -1
  33. package/dist/commands/publish.d.ts +13 -13
  34. package/dist/commands/publish.d.ts.map +1 -1
  35. package/dist/commands/publish.js +61 -61
  36. package/dist/commands/publish.js.map +1 -1
  37. package/dist/commands/replace-deps.d.ts +3 -3
  38. package/dist/commands/replace-deps.d.ts.map +1 -1
  39. package/dist/commands/replace-deps.js +1 -1
  40. package/dist/commands/replace-deps.js.map +1 -1
  41. package/dist/commands/typecheck.d.ts +20 -20
  42. package/dist/commands/typecheck.d.ts.map +1 -1
  43. package/dist/commands/typecheck.js +20 -20
  44. package/dist/commands/typecheck.js.map +1 -1
  45. package/dist/commands/watch.d.ts +7 -7
  46. package/dist/electron/electron.d.ts +27 -27
  47. package/dist/electron/electron.js +32 -32
  48. package/dist/electron/electron.js.map +1 -1
  49. package/dist/infra/ResultCollector.d.ts +9 -9
  50. package/dist/infra/ResultCollector.js +5 -5
  51. package/dist/infra/SignalHandler.d.ts +7 -7
  52. package/dist/infra/SignalHandler.js +4 -4
  53. package/dist/infra/WorkerManager.d.ts +14 -14
  54. package/dist/infra/WorkerManager.js +11 -11
  55. package/dist/orchestrators/BuildOrchestrator.d.ts +19 -19
  56. package/dist/orchestrators/BuildOrchestrator.d.ts.map +1 -1
  57. package/dist/orchestrators/BuildOrchestrator.js +26 -26
  58. package/dist/orchestrators/BuildOrchestrator.js.map +1 -1
  59. package/dist/orchestrators/DevOrchestrator.d.ts +25 -25
  60. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  61. package/dist/orchestrators/DevOrchestrator.js +30 -30
  62. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  63. package/dist/orchestrators/WatchOrchestrator.d.ts +13 -13
  64. package/dist/orchestrators/WatchOrchestrator.js +17 -17
  65. package/dist/orchestrators/WatchOrchestrator.js.map +1 -1
  66. package/dist/sd-cli-entry.d.ts +2 -2
  67. package/dist/sd-cli-entry.js +38 -38
  68. package/dist/sd-cli-entry.js.map +1 -1
  69. package/dist/sd-cli.d.ts +2 -2
  70. package/dist/sd-cli.js +1 -1
  71. package/dist/sd-cli.js.map +1 -1
  72. package/dist/sd-config.types.d.ts +84 -84
  73. package/dist/sd-config.types.d.ts.map +1 -1
  74. package/dist/utils/build-env.d.ts +1 -1
  75. package/dist/utils/config-editor.d.ts +5 -5
  76. package/dist/utils/config-editor.js +2 -2
  77. package/dist/utils/config-editor.js.map +1 -1
  78. package/dist/utils/copy-public.d.ts +9 -9
  79. package/dist/utils/copy-src.d.ts +9 -9
  80. package/dist/utils/esbuild-config.d.ts +30 -30
  81. package/dist/utils/esbuild-config.d.ts.map +1 -1
  82. package/dist/utils/output-utils.d.ts +6 -6
  83. package/dist/utils/package-utils.d.ts +6 -6
  84. package/dist/utils/package-utils.js +1 -1
  85. package/dist/utils/package-utils.js.map +1 -1
  86. package/dist/utils/rebuild-manager.js +3 -3
  87. package/dist/utils/rebuild-manager.js.map +1 -1
  88. package/dist/utils/replace-deps.d.ts +25 -25
  89. package/dist/utils/replace-deps.js +3 -3
  90. package/dist/utils/replace-deps.js.map +1 -1
  91. package/dist/utils/sd-config.d.ts +3 -3
  92. package/dist/utils/sd-config.js +3 -3
  93. package/dist/utils/sd-config.js.map +1 -1
  94. package/dist/utils/tailwind-config-deps.d.ts +3 -3
  95. package/dist/utils/template.d.ts +8 -8
  96. package/dist/utils/tsconfig.d.ts +16 -16
  97. package/dist/utils/tsconfig.js +2 -2
  98. package/dist/utils/tsconfig.js.map +1 -1
  99. package/dist/utils/typecheck-serialization.d.ts +8 -8
  100. package/dist/utils/vite-config.d.ts +8 -8
  101. package/dist/utils/vite-config.d.ts.map +1 -1
  102. package/dist/utils/vite-config.js +3 -3
  103. package/dist/utils/worker-events.d.ts +12 -12
  104. package/dist/utils/worker-events.d.ts.map +1 -1
  105. package/dist/utils/worker-utils.d.ts +3 -3
  106. package/dist/utils/worker-utils.js +2 -2
  107. package/dist/utils/worker-utils.js.map +1 -1
  108. package/dist/workers/client.worker.d.ts +14 -14
  109. package/dist/workers/client.worker.d.ts.map +1 -1
  110. package/dist/workers/client.worker.js +1 -1
  111. package/dist/workers/client.worker.js.map +1 -1
  112. package/dist/workers/dts.worker.d.ts +13 -13
  113. package/dist/workers/dts.worker.d.ts.map +1 -1
  114. package/dist/workers/dts.worker.js +3 -3
  115. package/dist/workers/dts.worker.js.map +1 -1
  116. package/dist/workers/library.worker.d.ts +12 -12
  117. package/dist/workers/library.worker.js +1 -1
  118. package/dist/workers/library.worker.js.map +1 -1
  119. package/dist/workers/lint.worker.d.ts +1 -1
  120. package/dist/workers/server-runtime.worker.d.ts +6 -6
  121. package/dist/workers/server-runtime.worker.js +6 -6
  122. package/dist/workers/server-runtime.worker.js.map +1 -1
  123. package/dist/workers/server.worker.d.ts +20 -20
  124. package/dist/workers/server.worker.d.ts.map +1 -1
  125. package/dist/workers/server.worker.js +6 -6
  126. package/dist/workers/server.worker.js.map +1 -1
  127. package/package.json +8 -7
  128. package/src/builders/BaseBuilder.ts +33 -33
  129. package/src/builders/DtsBuilder.ts +5 -5
  130. package/src/builders/LibraryBuilder.ts +9 -9
  131. package/src/builders/types.ts +10 -10
  132. package/src/capacitor/capacitor.ts +119 -119
  133. package/src/commands/add-client.ts +31 -31
  134. package/src/commands/add-server.ts +34 -34
  135. package/src/commands/build.ts +9 -9
  136. package/src/commands/check.ts +5 -5
  137. package/src/commands/dev.ts +9 -9
  138. package/src/commands/device.ts +30 -30
  139. package/src/commands/init.ts +25 -25
  140. package/src/commands/lint.ts +64 -64
  141. package/src/commands/publish.ts +139 -139
  142. package/src/commands/replace-deps.ts +4 -4
  143. package/src/commands/typecheck.ts +74 -74
  144. package/src/commands/watch.ts +7 -7
  145. package/src/electron/electron.ts +51 -51
  146. package/src/infra/ResultCollector.ts +9 -9
  147. package/src/infra/SignalHandler.ts +7 -7
  148. package/src/infra/WorkerManager.ts +14 -14
  149. package/src/orchestrators/BuildOrchestrator.ts +76 -76
  150. package/src/orchestrators/DevOrchestrator.ts +88 -88
  151. package/src/orchestrators/WatchOrchestrator.ts +39 -39
  152. package/src/sd-cli-entry.ts +43 -43
  153. package/src/sd-cli.ts +15 -15
  154. package/src/sd-config.types.ts +85 -85
  155. package/src/utils/build-env.ts +1 -1
  156. package/src/utils/config-editor.ts +19 -19
  157. package/src/utils/copy-public.ts +17 -17
  158. package/src/utils/copy-src.ts +11 -11
  159. package/src/utils/esbuild-config.ts +33 -33
  160. package/src/utils/output-utils.ts +11 -11
  161. package/src/utils/package-utils.ts +12 -12
  162. package/src/utils/rebuild-manager.ts +3 -3
  163. package/src/utils/replace-deps.ts +361 -361
  164. package/src/utils/sd-config.ts +44 -44
  165. package/src/utils/tailwind-config-deps.ts +98 -98
  166. package/src/utils/template.ts +56 -56
  167. package/src/utils/tsconfig.ts +127 -127
  168. package/src/utils/typecheck-serialization.ts +86 -86
  169. package/src/utils/vite-config.ts +341 -341
  170. package/src/utils/worker-events.ts +16 -16
  171. package/src/utils/worker-utils.ts +45 -45
  172. package/src/workers/client.worker.ts +34 -34
  173. package/src/workers/dts.worker.ts +467 -467
  174. package/src/workers/library.worker.ts +314 -314
  175. package/src/workers/lint.worker.ts +16 -16
  176. package/src/workers/server-runtime.worker.ts +157 -157
  177. package/src/workers/server.worker.ts +572 -572
  178. package/templates/add-client/__CLIENT__/package.json.hbs +1 -1
  179. package/templates/add-server/__SERVER__/package.json.hbs +2 -2
  180. package/templates/init/package.json.hbs +3 -3
  181. package/tests/config-editor.spec.ts +160 -0
  182. package/tests/copy-src.spec.ts +50 -0
  183. package/tests/get-compiler-options-for-package.spec.ts +139 -0
  184. package/tests/get-package-source-files.spec.ts +181 -0
  185. package/tests/get-types-from-package-json.spec.ts +107 -0
  186. package/tests/infra/ResultCollector.spec.ts +39 -0
  187. package/tests/infra/SignalHandler.spec.ts +38 -0
  188. package/tests/infra/WorkerManager.spec.ts +97 -0
  189. package/tests/load-ignore-patterns.spec.ts +188 -0
  190. package/tests/load-sd-config.spec.ts +137 -0
  191. package/tests/package-utils.spec.ts +188 -0
  192. package/tests/parse-root-tsconfig.spec.ts +89 -0
  193. package/tests/replace-deps.spec.ts +308 -0
  194. package/tests/run-lint.spec.ts +415 -0
  195. package/tests/run-typecheck.spec.ts +653 -0
  196. package/tests/run-watch.spec.ts +75 -0
  197. package/tests/sd-cli.spec.ts +330 -0
  198. package/tests/tailwind-config-deps.spec.ts +30 -0
  199. package/tests/template.spec.ts +70 -0
  200. package/tests/utils/rebuild-manager.spec.ts +43 -0
  201. package/tests/write-changed-output-files.spec.ts +97 -0
@@ -1,44 +1,44 @@
1
- import path from "path";
2
- import { createJiti } from "jiti";
3
- import { SdError } from "@simplysm/core-common";
4
- import { fsExists } from "@simplysm/core-node";
5
- import type { SdConfig, SdConfigParams } from "../sd-config.types";
6
-
7
- /**
8
- * sd.config.ts 로드
9
- * @returns SdConfig 객체
10
- * @throws sd.config.ts 없거나 형식이 잘못된 경우
11
- */
12
- export async function loadSdConfig(params: SdConfigParams): Promise<SdConfig> {
13
- const sdConfigPath = path.resolve(params.cwd, "sd.config.ts");
14
-
15
- if (!(await fsExists(sdConfigPath))) {
16
- throw new SdError(`sd.config.ts 파일을 찾을 없습니다: ${sdConfigPath}`);
17
- }
18
-
19
- const jiti = createJiti(import.meta.url);
20
- const sdConfigModule = await jiti.import(sdConfigPath);
21
-
22
- if (
23
- sdConfigModule == null ||
24
- typeof sdConfigModule !== "object" ||
25
- !("default" in sdConfigModule) ||
26
- typeof sdConfigModule.default !== "function"
27
- ) {
28
- throw new SdError(`sd.config.ts 함수를 default export해야 합니다: ${sdConfigPath}`);
29
- }
30
-
31
- const config = await sdConfigModule.default(params);
32
-
33
- if (
34
- config == null ||
35
- typeof config !== "object" ||
36
- !("packages" in config) ||
37
- config.packages == null ||
38
- typeof config.packages !== "object" ||
39
- Array.isArray(config.packages)
40
- ) {
41
- throw new SdError(`sd.config.ts 반환값이 올바른 형식이 아닙니다: ${sdConfigPath}`);
42
- }
43
- return config as SdConfig;
44
- }
1
+ import path from "path";
2
+ import { createJiti } from "jiti";
3
+ import { SdError } from "@simplysm/core-common";
4
+ import { fsExists } from "@simplysm/core-node";
5
+ import type { SdConfig, SdConfigParams } from "../sd-config.types";
6
+
7
+ /**
8
+ * Load sd.config.ts
9
+ * @returns SdConfig object
10
+ * @throws if sd.config.ts is missing or format is incorrect
11
+ */
12
+ export async function loadSdConfig(params: SdConfigParams): Promise<SdConfig> {
13
+ const sdConfigPath = path.resolve(params.cwd, "sd.config.ts");
14
+
15
+ if (!(await fsExists(sdConfigPath))) {
16
+ throw new SdError(`sd.config.ts file not found: ${sdConfigPath}`);
17
+ }
18
+
19
+ const jiti = createJiti(import.meta.url);
20
+ const sdConfigModule = await jiti.import(sdConfigPath);
21
+
22
+ if (
23
+ sdConfigModule == null ||
24
+ typeof sdConfigModule !== "object" ||
25
+ !("default" in sdConfigModule) ||
26
+ typeof sdConfigModule.default !== "function"
27
+ ) {
28
+ throw new SdError(`sd.config.ts must export a function as default: ${sdConfigPath}`);
29
+ }
30
+
31
+ const config = await sdConfigModule.default(params);
32
+
33
+ if (
34
+ config == null ||
35
+ typeof config !== "object" ||
36
+ !("packages" in config) ||
37
+ config.packages == null ||
38
+ typeof config.packages !== "object" ||
39
+ Array.isArray(config.packages)
40
+ ) {
41
+ throw new SdError(`sd.config.ts return value is not in the correct format: ${sdConfigPath}`);
42
+ }
43
+ return config as SdConfig;
44
+ }
@@ -1,98 +1,98 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- const jsExtensions = [".js", ".cjs", ".mjs"];
5
-
6
- const jsResolutionOrder = ["", ".js", ".cjs", ".mjs", ".ts", ".cts", ".mts", ".jsx", ".tsx"];
7
- const tsResolutionOrder = ["", ".ts", ".cts", ".mts", ".tsx", ".js", ".cjs", ".mjs", ".jsx"];
8
-
9
- function resolveWithExtension(file: string, extensions: string[]): string | null {
10
- for (const ext of extensions) {
11
- const full = `${file}${ext}`;
12
- if (fs.existsSync(full) && fs.statSync(full).isFile()) {
13
- return full;
14
- }
15
- }
16
- for (const ext of extensions) {
17
- const full = `${file}/index${ext}`;
18
- if (fs.existsSync(full) && fs.statSync(full).isFile()) {
19
- return full;
20
- }
21
- }
22
- return null;
23
- }
24
-
25
- function resolvePackageFile(specifier: string, fromDir: string): string | null {
26
- const parts = specifier.split("/");
27
- const pkgName = specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
28
- const subPath = specifier.startsWith("@") ? parts.slice(2).join("/") : parts.slice(1).join("/");
29
-
30
- let searchDir = fromDir;
31
- while (true) {
32
- const candidate = path.join(searchDir, "node_modules", pkgName);
33
- if (fs.existsSync(candidate)) {
34
- const realDir = fs.realpathSync(candidate);
35
- if (subPath) {
36
- return resolveWithExtension(path.join(realDir, subPath), tsResolutionOrder);
37
- }
38
- return resolveWithExtension(path.join(realDir, "index"), tsResolutionOrder);
39
- }
40
- const parent = path.dirname(searchDir);
41
- if (parent === searchDir) break;
42
- searchDir = parent;
43
- }
44
- return null;
45
- }
46
-
47
- /**
48
- * Tailwind config 파일의 의존성을 재귀적으로 수집한다.
49
- *
50
- * Tailwind 내장 `getModuleDependencies`는 상대 경로 import만 추적하지만,
51
- * 함수는 지정된 scope의 패키지 경로도 `node_modules` symlink를 풀어 실제 파일을 추적한다.
52
- */
53
- export function getTailwindConfigDeps(configPath: string, replaceDeps: string[]): string[] {
54
- const seen = new Set<string>();
55
-
56
- function isReplaceDepImport(specifier: string): boolean {
57
- return replaceDeps.some((dep) => specifier === dep || specifier.startsWith(dep + "/"));
58
- }
59
-
60
- function walk(absoluteFile: string): void {
61
- if (seen.has(absoluteFile)) return;
62
- if (!fs.existsSync(absoluteFile)) return;
63
- seen.add(absoluteFile);
64
-
65
- const base = path.dirname(absoluteFile);
66
- const ext = path.extname(absoluteFile);
67
- const extensions = jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder;
68
-
69
- let contents: string;
70
- try {
71
- contents = fs.readFileSync(absoluteFile, "utf-8");
72
- } catch {
73
- return;
74
- }
75
-
76
- for (const match of [
77
- ...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),
78
- ...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
79
- ...contents.matchAll(/require\(['"`](.+)['"`]\)/gi),
80
- ]) {
81
- const specifier = match[1];
82
- let resolved: string | null = null;
83
-
84
- if (specifier.startsWith(".")) {
85
- resolved = resolveWithExtension(path.resolve(base, specifier), extensions);
86
- } else if (isReplaceDepImport(specifier)) {
87
- resolved = resolvePackageFile(specifier, base);
88
- }
89
-
90
- if (resolved != null) {
91
- walk(resolved);
92
- }
93
- }
94
- }
95
-
96
- walk(path.resolve(configPath));
97
- return [...seen];
98
- }
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const jsExtensions = [".js", ".cjs", ".mjs"];
5
+
6
+ const jsResolutionOrder = ["", ".js", ".cjs", ".mjs", ".ts", ".cts", ".mts", ".jsx", ".tsx"];
7
+ const tsResolutionOrder = ["", ".ts", ".cts", ".mts", ".tsx", ".js", ".cjs", ".mjs", ".jsx"];
8
+
9
+ function resolveWithExtension(file: string, extensions: string[]): string | null {
10
+ for (const ext of extensions) {
11
+ const full = `${file}${ext}`;
12
+ if (fs.existsSync(full) && fs.statSync(full).isFile()) {
13
+ return full;
14
+ }
15
+ }
16
+ for (const ext of extensions) {
17
+ const full = `${file}/index${ext}`;
18
+ if (fs.existsSync(full) && fs.statSync(full).isFile()) {
19
+ return full;
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+
25
+ function resolvePackageFile(specifier: string, fromDir: string): string | null {
26
+ const parts = specifier.split("/");
27
+ const pkgName = specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
28
+ const subPath = specifier.startsWith("@") ? parts.slice(2).join("/") : parts.slice(1).join("/");
29
+
30
+ let searchDir = fromDir;
31
+ while (true) {
32
+ const candidate = path.join(searchDir, "node_modules", pkgName);
33
+ if (fs.existsSync(candidate)) {
34
+ const realDir = fs.realpathSync(candidate);
35
+ if (subPath) {
36
+ return resolveWithExtension(path.join(realDir, subPath), tsResolutionOrder);
37
+ }
38
+ return resolveWithExtension(path.join(realDir, "index"), tsResolutionOrder);
39
+ }
40
+ const parent = path.dirname(searchDir);
41
+ if (parent === searchDir) break;
42
+ searchDir = parent;
43
+ }
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Recursively collect dependencies of Tailwind config file
49
+ *
50
+ * Tailwind built-in `getModuleDependencies` only tracks relative path imports,
51
+ * but this function also resolves `node_modules` symlinks to track actual files for packages in specified scope.
52
+ */
53
+ export function getTailwindConfigDeps(configPath: string, replaceDeps: string[]): string[] {
54
+ const seen = new Set<string>();
55
+
56
+ function isReplaceDepImport(specifier: string): boolean {
57
+ return replaceDeps.some((dep) => specifier === dep || specifier.startsWith(dep + "/"));
58
+ }
59
+
60
+ function walk(absoluteFile: string): void {
61
+ if (seen.has(absoluteFile)) return;
62
+ if (!fs.existsSync(absoluteFile)) return;
63
+ seen.add(absoluteFile);
64
+
65
+ const base = path.dirname(absoluteFile);
66
+ const ext = path.extname(absoluteFile);
67
+ const extensions = jsExtensions.includes(ext) ? jsResolutionOrder : tsResolutionOrder;
68
+
69
+ let contents: string;
70
+ try {
71
+ contents = fs.readFileSync(absoluteFile, "utf-8");
72
+ } catch {
73
+ return;
74
+ }
75
+
76
+ for (const match of [
77
+ ...contents.matchAll(/import[\s\S]*?['"](.{3,}?)['"]/gi),
78
+ ...contents.matchAll(/import[\s\S]*from[\s\S]*?['"](.{3,}?)['"]/gi),
79
+ ...contents.matchAll(/require\(['"`](.+)['"`]\)/gi),
80
+ ]) {
81
+ const specifier = match[1];
82
+ let resolved: string | null = null;
83
+
84
+ if (specifier.startsWith(".")) {
85
+ resolved = resolveWithExtension(path.resolve(base, specifier), extensions);
86
+ } else if (isReplaceDepImport(specifier)) {
87
+ resolved = resolvePackageFile(specifier, base);
88
+ }
89
+
90
+ if (resolved != null) {
91
+ walk(resolved);
92
+ }
93
+ }
94
+ }
95
+
96
+ walk(path.resolve(configPath));
97
+ return [...seen];
98
+ }
@@ -1,56 +1,56 @@
1
- import path from "path";
2
- import Handlebars from "handlebars";
3
- import { fsCopy, fsMkdir, fsRead, fsReaddir, fsStat, fsWrite } from "@simplysm/core-node";
4
-
5
- /**
6
- * 템플릿 디렉토리를 재귀적으로 순회하며 Handlebars 렌더링 파일을 생성한다.
7
- *
8
- * - `.hbs` 확장자 파일: Handlebars 컴파일 → `.hbs` 제거한 이름으로 저장
9
- * - `.hbs` 결과가 문자열/공백만이면: 파일 생성 스킵
10
- * - 나머지 파일: 바이너리로 그대로 복사
11
- *
12
- * @param srcDir - 템플릿 소스 디렉토리
13
- * @param destDir - 출력 대상 디렉토리
14
- * @param context - Handlebars 템플릿 변수
15
- * @param dirReplacements - 디렉토리 이름 치환 (예: `{ __CLIENT__: "client-admin" }`)
16
- */
17
- export async function renderTemplateDir(
18
- srcDir: string,
19
- destDir: string,
20
- context: Record<string, unknown>,
21
- dirReplacements?: Record<string, string>,
22
- ): Promise<void> {
23
- await fsMkdir(destDir);
24
-
25
- const entries = await fsReaddir(srcDir);
26
-
27
- for (const entry of entries) {
28
- const srcPath = path.join(srcDir, entry);
29
- const stat = await fsStat(srcPath);
30
-
31
- if (stat.isDirectory()) {
32
- // 디렉토리 이름 치환 적용
33
- const destName = dirReplacements?.[entry] ?? entry;
34
- await renderTemplateDir(
35
- path.join(srcDir, entry),
36
- path.join(destDir, destName),
37
- context,
38
- dirReplacements,
39
- );
40
- } else if (entry.endsWith(".hbs")) {
41
- // Handlebars 템플릿 렌더링
42
- const source = await fsRead(srcPath);
43
- const template = Handlebars.compile(source, { noEscape: true });
44
- const result = template(context);
45
-
46
- // 결과면 파일 생성 스킵
47
- if (result.trim().length === 0) continue;
48
-
49
- const destFileName = entry.slice(0, -4); // .hbs 제거
50
- await fsWrite(path.join(destDir, destFileName), result);
51
- } else {
52
- // 바이너리 파일은 그대로 복사
53
- await fsCopy(srcPath, path.join(destDir, entry));
54
- }
55
- }
56
- }
1
+ import path from "path";
2
+ import Handlebars from "handlebars";
3
+ import { fsCopy, fsMkdir, fsRead, fsReaddir, fsStat, fsWrite } from "@simplysm/core-node";
4
+
5
+ /**
6
+ * Recursively traverse template directory, render with Handlebars, and generate files
7
+ *
8
+ * - `.hbs` extension files: Compile with Handlebars → save with `.hbs` removed from name
9
+ * - If `.hbs` result is empty string/whitespace only: skip file creation
10
+ * - Other files: copy as-is (binary)
11
+ *
12
+ * @param srcDir - Template source directory
13
+ * @param destDir - Output destination directory
14
+ * @param context - Handlebars template variables
15
+ * @param dirReplacements - Directory name substitution map (e.g., `{ __CLIENT__: "client-admin" }`)
16
+ */
17
+ export async function renderTemplateDir(
18
+ srcDir: string,
19
+ destDir: string,
20
+ context: Record<string, unknown>,
21
+ dirReplacements?: Record<string, string>,
22
+ ): Promise<void> {
23
+ await fsMkdir(destDir);
24
+
25
+ const entries = await fsReaddir(srcDir);
26
+
27
+ for (const entry of entries) {
28
+ const srcPath = path.join(srcDir, entry);
29
+ const stat = await fsStat(srcPath);
30
+
31
+ if (stat.isDirectory()) {
32
+ // Apply directory name substitution
33
+ const destName = dirReplacements?.[entry] ?? entry;
34
+ await renderTemplateDir(
35
+ path.join(srcDir, entry),
36
+ path.join(destDir, destName),
37
+ context,
38
+ dirReplacements,
39
+ );
40
+ } else if (entry.endsWith(".hbs")) {
41
+ // Render Handlebars template
42
+ const source = await fsRead(srcPath);
43
+ const template = Handlebars.compile(source, { noEscape: true });
44
+ const result = template(context);
45
+
46
+ // Skip file creation if result is empty or whitespace-only
47
+ if (result.trim().length === 0) continue;
48
+
49
+ const destFileName = entry.slice(0, -4); // Remove .hbs
50
+ await fsWrite(path.join(destDir, destFileName), result);
51
+ } else {
52
+ // Copy binary files as-is
53
+ await fsCopy(srcPath, path.join(destDir, entry));
54
+ }
55
+ }
56
+ }
@@ -1,127 +1,127 @@
1
- import ts from "typescript";
2
- import path from "path";
3
- import { fsExists, fsReadJson, pathIsChildPath } from "@simplysm/core-node";
4
- import { SdError } from "@simplysm/core-common";
5
-
6
- /**
7
- * DOM 관련 lib 패턴 - 브라우저 API를 포함하는 lib들
8
- * node 환경에서 제외되어야 하는 lib을 필터링할 사용 (lib.dom.d.ts, lib.webworker.d.ts )
9
- */
10
- const DOM_LIB_PATTERNS = ["dom", "webworker"] as const;
11
-
12
- /**
13
- * 패키지의 package.json에서 @types/* devDependencies 읽어 types 목록을 반환합니다.
14
- */
15
- export async function getTypesFromPackageJson(packageDir: string): Promise<string[]> {
16
- const packageJsonPath = path.join(packageDir, "package.json");
17
- if (!(await fsExists(packageJsonPath))) {
18
- return [];
19
- }
20
-
21
- const packageJson = await fsReadJson<{ devDependencies?: Record<string, string> }>(
22
- packageJsonPath,
23
- );
24
- const devDeps = packageJson.devDependencies ?? {};
25
-
26
- return Object.keys(devDeps)
27
- .filter((dep) => dep.startsWith("@types/"))
28
- .map((dep) => dep.replace("@types/", ""));
29
- }
30
-
31
- /**
32
- * 타입체크 환경
33
- * - node: DOM lib 제거 + node 타입 추가
34
- * - browser: node 타입 제거
35
- * - neutral: DOM lib 유지 + node 타입 추가 (Node/브라우저 공용 패키지용)
36
- */
37
- export type TypecheckEnv = "node" | "browser" | "neutral";
38
-
39
- /**
40
- * 패키지용 컴파일러 옵션 생성
41
- *
42
- * @param baseOptions 루트 tsconfig의 컴파일러 옵션
43
- * @param env 타입체크 환경 (node: DOM lib 제거 + node 타입 추가, browser: node 타입 제거)
44
- * @param packageDir 패키지 디렉토리 경로
45
- *
46
- * @remarks
47
- * types 옵션은 baseOptions.types 무시하고 패키지별로 새로 구성한다.
48
- * 이는 루트 tsconfig의 전역 타입이 패키지 환경에 맞지 않을 있기 때문이다.
49
- * (예: browser 패키지에 node 타입이 포함되는 것을 방지)
50
- */
51
- export async function getCompilerOptionsForPackage(
52
- baseOptions: ts.CompilerOptions,
53
- env: TypecheckEnv,
54
- packageDir: string,
55
- ): Promise<ts.CompilerOptions> {
56
- const options = { ...baseOptions };
57
- const packageTypes = await getTypesFromPackageJson(packageDir);
58
-
59
- // pnpm 환경: 패키지별 node_modules/@types 루트 node_modules/@types 모두 검색
60
- options.typeRoots = [
61
- path.join(packageDir, "node_modules", "@types"),
62
- path.join(process.cwd(), "node_modules", "@types"),
63
- ];
64
-
65
- switch (env) {
66
- case "node":
67
- options.lib = options.lib?.filter(
68
- (lib) => !DOM_LIB_PATTERNS.some((pattern) => lib.toLowerCase().includes(pattern)),
69
- );
70
- options.types = [...new Set([...packageTypes, "node"])];
71
- break;
72
- case "browser":
73
- options.types = packageTypes.filter((t) => t !== "node");
74
- break;
75
- case "neutral":
76
- options.types = [...new Set([...packageTypes, "node"])];
77
- break;
78
- }
79
-
80
- return options;
81
- }
82
-
83
- /**
84
- * 루트 tsconfig 파싱
85
- * @throws tsconfig.json을 읽거나 파싱할 없는 경우
86
- */
87
- export function parseRootTsconfig(cwd: string): ts.ParsedCommandLine {
88
- const tsconfigPath = path.join(cwd, "tsconfig.json");
89
- const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
90
-
91
- if (configFile.error) {
92
- const message = ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n");
93
- throw new SdError(`tsconfig.json 읽기 실패: ${message}`);
94
- }
95
-
96
- const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd);
97
-
98
- if (parsed.errors.length > 0) {
99
- const messages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n"));
100
- throw new SdError(`tsconfig.json 파싱 실패: ${messages.join("; ")}`);
101
- }
102
-
103
- return parsed;
104
- }
105
-
106
- /**
107
- * 패키지의 소스 파일 목록 가져오기 (tsconfig 기반)
108
- */
109
- export function getPackageSourceFiles(
110
- pkgDir: string,
111
- parsedConfig: ts.ParsedCommandLine,
112
- ): string[] {
113
- const pkgSrcDir = path.join(pkgDir, "src");
114
- return parsedConfig.fileNames.filter((f) => pathIsChildPath(f, pkgSrcDir));
115
- }
116
-
117
- /**
118
- * 패키지의 전체 파일 목록 가져오기 (src + tests 포함)
119
- */
120
- export function getPackageFiles(pkgDir: string, parsedConfig: ts.ParsedCommandLine): string[] {
121
- return parsedConfig.fileNames.filter((f) => {
122
- if (!pathIsChildPath(f, pkgDir)) return false;
123
- // 패키지 루트 직속 파일(설정 파일) 제외 기타 태스크에서 프로젝트 루트 파일과 동일하게 처리
124
- const relative = path.relative(pkgDir, f);
125
- return path.dirname(relative) !== ".";
126
- });
127
- }
1
+ import ts from "typescript";
2
+ import path from "path";
3
+ import { fsExists, fsReadJson, pathIsChildPath } from "@simplysm/core-node";
4
+ import { SdError } from "@simplysm/core-common";
5
+
6
+ /**
7
+ * DOM-related lib patterns - libs that include browser APIs
8
+ * Used when filtering libs that should be excluded from node environment (lib.dom.d.ts, lib.webworker.d.ts, etc)
9
+ */
10
+ const DOM_LIB_PATTERNS = ["dom", "webworker"] as const;
11
+
12
+ /**
13
+ * Read @types/* devDependencies from package.json and return types list
14
+ */
15
+ export async function getTypesFromPackageJson(packageDir: string): Promise<string[]> {
16
+ const packageJsonPath = path.join(packageDir, "package.json");
17
+ if (!(await fsExists(packageJsonPath))) {
18
+ return [];
19
+ }
20
+
21
+ const packageJson = await fsReadJson<{ devDependencies?: Record<string, string> }>(
22
+ packageJsonPath,
23
+ );
24
+ const devDeps = packageJson.devDependencies ?? {};
25
+
26
+ return Object.keys(devDeps)
27
+ .filter((dep) => dep.startsWith("@types/"))
28
+ .map((dep) => dep.replace("@types/", ""));
29
+ }
30
+
31
+ /**
32
+ * Type check environment
33
+ * - node: remove DOM lib + add node types
34
+ * - browser: remove node types
35
+ * - neutral: keep DOM lib + add node types (for Node/browser shared packages)
36
+ */
37
+ export type TypecheckEnv = "node" | "browser" | "neutral";
38
+
39
+ /**
40
+ * Create compiler options for package
41
+ *
42
+ * @param baseOptions - Compiler options from root tsconfig
43
+ * @param env - Type check environment (node: remove DOM lib + add node types, browser: remove node types)
44
+ * @param packageDir - Package directory path
45
+ *
46
+ * @remarks
47
+ * The types option ignores baseOptions.types and is newly constructed per package.
48
+ * This is because the global types in root tsconfig may not fit the package environment.
49
+ * (e.g., prevent node types from being included in browser packages)
50
+ */
51
+ export async function getCompilerOptionsForPackage(
52
+ baseOptions: ts.CompilerOptions,
53
+ env: TypecheckEnv,
54
+ packageDir: string,
55
+ ): Promise<ts.CompilerOptions> {
56
+ const options = { ...baseOptions };
57
+ const packageTypes = await getTypesFromPackageJson(packageDir);
58
+
59
+ // pnpm environment: search both package-specific node_modules/@types and root node_modules/@types
60
+ options.typeRoots = [
61
+ path.join(packageDir, "node_modules", "@types"),
62
+ path.join(process.cwd(), "node_modules", "@types"),
63
+ ];
64
+
65
+ switch (env) {
66
+ case "node":
67
+ options.lib = options.lib?.filter(
68
+ (lib) => !DOM_LIB_PATTERNS.some((pattern) => lib.toLowerCase().includes(pattern)),
69
+ );
70
+ options.types = [...new Set([...packageTypes, "node"])];
71
+ break;
72
+ case "browser":
73
+ options.types = packageTypes.filter((t) => t !== "node");
74
+ break;
75
+ case "neutral":
76
+ options.types = [...new Set([...packageTypes, "node"])];
77
+ break;
78
+ }
79
+
80
+ return options;
81
+ }
82
+
83
+ /**
84
+ * Parse root tsconfig
85
+ * @throws If unable to read or parse tsconfig.json
86
+ */
87
+ export function parseRootTsconfig(cwd: string): ts.ParsedCommandLine {
88
+ const tsconfigPath = path.join(cwd, "tsconfig.json");
89
+ const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
90
+
91
+ if (configFile.error) {
92
+ const message = ts.flattenDiagnosticMessageText(configFile.error.messageText, "\n");
93
+ throw new SdError(`Failed to read tsconfig.json: ${message}`);
94
+ }
95
+
96
+ const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, cwd);
97
+
98
+ if (parsed.errors.length > 0) {
99
+ const messages = parsed.errors.map((e) => ts.flattenDiagnosticMessageText(e.messageText, "\n"));
100
+ throw new SdError(`Failed to parse tsconfig.json: ${messages.join("; ")}`);
101
+ }
102
+
103
+ return parsed;
104
+ }
105
+
106
+ /**
107
+ * Get list of package source files (based on tsconfig)
108
+ */
109
+ export function getPackageSourceFiles(
110
+ pkgDir: string,
111
+ parsedConfig: ts.ParsedCommandLine,
112
+ ): string[] {
113
+ const pkgSrcDir = path.join(pkgDir, "src");
114
+ return parsedConfig.fileNames.filter((f) => pathIsChildPath(f, pkgSrcDir));
115
+ }
116
+
117
+ /**
118
+ * Get full list of package files (including src + tests)
119
+ */
120
+ export function getPackageFiles(pkgDir: string, parsedConfig: ts.ParsedCommandLine): string[] {
121
+ return parsedConfig.fileNames.filter((f) => {
122
+ if (!pathIsChildPath(f, pkgDir)) return false;
123
+ // Exclude files directly in package root (config files) — treated same as project root files in other tasks
124
+ const relative = path.relative(pkgDir, f);
125
+ return path.dirname(relative) !== ".";
126
+ });
127
+ }