@simplysm/sd-cli 14.0.19 → 14.0.22

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 (114) hide show
  1. package/dist/angular/vite-postcss-inline-plugin.d.ts.map +1 -1
  2. package/dist/angular/vite-postcss-inline-plugin.js +4 -1
  3. package/dist/angular/vite-postcss-inline-plugin.js.map +1 -1
  4. package/dist/capacitor/capacitor-android.d.ts +16 -0
  5. package/dist/capacitor/capacitor-android.d.ts.map +1 -0
  6. package/dist/capacitor/capacitor-android.js +289 -0
  7. package/dist/capacitor/capacitor-android.js.map +1 -0
  8. package/dist/capacitor/capacitor.d.ts +0 -49
  9. package/dist/capacitor/capacitor.d.ts.map +1 -1
  10. package/dist/capacitor/capacitor.js +4 -244
  11. package/dist/capacitor/capacitor.js.map +1 -1
  12. package/dist/commands/check.js +2 -2
  13. package/dist/commands/check.js.map +1 -1
  14. package/dist/commands/lint.d.ts +1 -42
  15. package/dist/commands/lint.d.ts.map +1 -1
  16. package/dist/commands/lint.js +1 -151
  17. package/dist/commands/lint.js.map +1 -1
  18. package/dist/commands/publish.d.ts.map +1 -1
  19. package/dist/commands/publish.js +2 -1
  20. package/dist/commands/publish.js.map +1 -1
  21. package/dist/commands/typecheck.d.ts +3 -40
  22. package/dist/commands/typecheck.d.ts.map +1 -1
  23. package/dist/commands/typecheck.js +3 -232
  24. package/dist/commands/typecheck.js.map +1 -1
  25. package/dist/electron/electron.js +11 -4
  26. package/dist/electron/electron.js.map +1 -1
  27. package/dist/engines/ViteEngine.js +1 -1
  28. package/dist/engines/ViteEngine.js.map +1 -1
  29. package/dist/engines/types.d.ts +2 -0
  30. package/dist/engines/types.d.ts.map +1 -1
  31. package/dist/infra/ResultCollector.d.ts +2 -2
  32. package/dist/infra/ResultCollector.d.ts.map +1 -1
  33. package/dist/infra/ResultCollector.js +1 -1
  34. package/dist/orchestrators/DevWatchOrchestrator.d.ts +2 -1
  35. package/dist/orchestrators/DevWatchOrchestrator.d.ts.map +1 -1
  36. package/dist/orchestrators/DevWatchOrchestrator.js +28 -16
  37. package/dist/orchestrators/DevWatchOrchestrator.js.map +1 -1
  38. package/dist/orchestrators/TypecheckOrchestrator.d.ts +74 -0
  39. package/dist/orchestrators/TypecheckOrchestrator.d.ts.map +1 -0
  40. package/dist/orchestrators/TypecheckOrchestrator.js +285 -0
  41. package/dist/orchestrators/TypecheckOrchestrator.js.map +1 -0
  42. package/dist/sd-cli.js +6 -1
  43. package/dist/sd-cli.js.map +1 -1
  44. package/dist/utils/lint-core.d.ts +43 -0
  45. package/dist/utils/lint-core.d.ts.map +1 -0
  46. package/dist/utils/lint-core.js +154 -0
  47. package/dist/utils/lint-core.js.map +1 -0
  48. package/dist/utils/lint-utils.d.ts +1 -1
  49. package/dist/utils/lint-utils.d.ts.map +1 -1
  50. package/dist/utils/output-utils.d.ts +2 -2
  51. package/dist/utils/output-utils.d.ts.map +1 -1
  52. package/dist/utils/output-utils.js.map +1 -1
  53. package/dist/utils/server-production-files.d.ts +22 -0
  54. package/dist/utils/server-production-files.d.ts.map +1 -0
  55. package/dist/utils/server-production-files.js +162 -0
  56. package/dist/utils/server-production-files.js.map +1 -0
  57. package/dist/utils/vite-config.js.map +1 -1
  58. package/dist/workers/client.worker.d.ts.map +1 -1
  59. package/dist/workers/client.worker.js +7 -1
  60. package/dist/workers/client.worker.js.map +1 -1
  61. package/dist/workers/lint.worker.d.ts +1 -1
  62. package/dist/workers/lint.worker.d.ts.map +1 -1
  63. package/dist/workers/lint.worker.js +1 -1
  64. package/dist/workers/lint.worker.js.map +1 -1
  65. package/dist/workers/server-build.worker.d.ts.map +1 -1
  66. package/dist/workers/server-build.worker.js +12 -161
  67. package/dist/workers/server-build.worker.js.map +1 -1
  68. package/package.json +4 -4
  69. package/src/angular/vite-postcss-inline-plugin.ts +5 -1
  70. package/src/capacitor/capacitor-android.ts +368 -0
  71. package/src/capacitor/capacitor.ts +4 -317
  72. package/src/commands/check.ts +2 -2
  73. package/src/commands/lint.ts +1 -201
  74. package/src/commands/publish.ts +2 -1
  75. package/src/commands/typecheck.ts +7 -292
  76. package/src/electron/electron.ts +4 -4
  77. package/src/engines/ViteEngine.ts +1 -1
  78. package/src/engines/types.ts +3 -0
  79. package/src/infra/ResultCollector.ts +2 -2
  80. package/src/orchestrators/DevWatchOrchestrator.ts +35 -20
  81. package/src/orchestrators/TypecheckOrchestrator.ts +364 -0
  82. package/src/sd-cli.ts +6 -1
  83. package/src/utils/lint-core.ts +205 -0
  84. package/src/utils/lint-utils.ts +1 -1
  85. package/src/utils/output-utils.ts +3 -3
  86. package/src/utils/server-production-files.ts +186 -0
  87. package/src/utils/vite-config.ts +1 -1
  88. package/src/workers/client.worker.ts +7 -1
  89. package/src/workers/lint.worker.ts +1 -1
  90. package/src/workers/server-build.worker.ts +11 -185
  91. package/tests/angular/vite-postcss-inline-plugin.spec.ts +10 -0
  92. package/tests/capacitor/capacitor-android-exports.verify.md +11 -0
  93. package/tests/capacitor/capacitor-android.spec.ts +219 -0
  94. package/tests/capacitor/capacitor-build.spec.ts +17 -21
  95. package/tests/capacitor/capacitor-icon.spec.ts +17 -19
  96. package/tests/capacitor/capacitor-init.spec.ts +18 -14
  97. package/tests/capacitor/capacitor-run.spec.ts +10 -24
  98. package/tests/capacitor/capacitor-workspace.spec.ts +10 -15
  99. package/tests/commands/check.spec.ts +2 -2
  100. package/tests/commands/lint.spec.ts +33 -194
  101. package/tests/commands/publish-set.verify.md +7 -0
  102. package/tests/electron/electron-symlink-cleanup.verify.md +8 -0
  103. package/tests/engines/vite-engine.spec.ts +29 -0
  104. package/tests/infra/result-collector.spec.ts +11 -0
  105. package/tests/orchestrators/dev-watch-orchestrator.spec.ts +70 -0
  106. package/tests/orchestrators/dist-delete-watcher.verify.md +10 -0
  107. package/tests/orchestrators/typecheck-orchestrator.spec.ts +180 -0
  108. package/tests/sd-cli-catch-all.verify.md +7 -0
  109. package/tests/utils/lint-core-import-paths.verify.md +10 -0
  110. package/tests/utils/lint-core.spec.ts +188 -0
  111. package/tests/utils/server-production-files-import-paths.verify.md +14 -0
  112. package/tests/workers/client-worker.spec.ts +92 -0
  113. package/tests/workers/server-build-context-dispose.verify.md +8 -0
  114. package/tests/workers/server-build-worker.spec.ts +39 -0
@@ -0,0 +1,186 @@
1
+ import type { ServerBuildInfo } from "../workers/server-build.worker";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import { cpx } from "@simplysm/core-node";
5
+ import { consola } from "consola";
6
+ import { collectAllDependencyExternals } from "./esbuild-config";
7
+
8
+ const logger = consola.withTag("sd:cli:server-production-files");
9
+
10
+ /**
11
+ * 세 가지 소스에서 외부 모듈을 수집하고 병합한다.
12
+ * collectAllDependencyExternals를 통한 단일 패스 의존성 트리 순회를 사용한다.
13
+ */
14
+ export function collectAllExternals(pkgDir: string, manualExternals?: string[]): string[] {
15
+ logger.debug("의존성 트리 스캔 중...");
16
+ const { optionalPeerDeps, nativeModules } = collectAllDependencyExternals(pkgDir);
17
+
18
+ const manual = manualExternals ?? [];
19
+ return [...new Set([...optionalPeerDeps, ...nativeModules, ...manual])];
20
+ }
21
+
22
+ /**
23
+ * pnpm-lock.yaml의 packages 섹션을 파싱하여 name→version 맵을 생성한다.
24
+ * Lockfile v9 형식: `packages:` 섹션의 `'name@version':` 키를 파싱한다.
25
+ * YAML 파서 의존성을 피하기 위해 단순 라인 기반 파싱을 사용한다.
26
+ */
27
+ export function parseLockfileVersions(cwd: string): Map<string, string> {
28
+ const lockfilePath = path.join(cwd, "pnpm-lock.yaml");
29
+ if (!fs.existsSync(lockfilePath)) {
30
+ throw new Error(`pnpm-lock.yaml not found in ${cwd}. Run "pnpm install" first.`);
31
+ }
32
+
33
+ const content = fs.readFileSync(lockfilePath, "utf-8");
34
+ const map = new Map<string, string>();
35
+
36
+ // "packages:" 섹션을 찾고 "'@scope/name@1.2.3':" 또는 "'name@1.2.3':" 형태의 항목을 파싱
37
+ const lines = content.split("\n");
38
+ let inPackages = false;
39
+ for (const line of lines) {
40
+ if (line === "packages:") {
41
+ inPackages = true;
42
+ continue;
43
+ }
44
+ if (inPackages && line.length > 0 && !line.startsWith(" ") && !line.startsWith("'")) {
45
+ break; // 다음 최상위 섹션
46
+ }
47
+ if (!inPackages) continue;
48
+
49
+ // "'@scope/name@version':" 또는 "'name@version':" 매칭
50
+ const match = /^\s{2}'(.+)@(\d[^']*)':\s*$/.exec(line);
51
+ if (match != null) {
52
+ const name = match[1];
53
+ const version = match[2];
54
+ // 첫 번째 항목 유지 (lockfile은 각 버전을 한 번만 기록)
55
+ if (!map.has(name)) {
56
+ map.set(name, version);
57
+ }
58
+ }
59
+ }
60
+
61
+ return map;
62
+ }
63
+
64
+ /**
65
+ * pnpm-lock.yaml에서 주어진 모든 패키지의 잠긴 버전을 확인한다.
66
+ * lockfile에서 패키지를 찾을 수 없으면 에러를 던진다.
67
+ */
68
+ export function resolveLockedVersions(cwd: string, pkgNames: string[]): Record<string, string> {
69
+ const versionMap = parseLockfileVersions(cwd);
70
+ const result: Record<string, string> = {};
71
+ for (const name of pkgNames) {
72
+ const version = versionMap.get(name);
73
+ if (version == null) {
74
+ throw new Error(
75
+ `External dependency "${name}" not found in pnpm-lock.yaml. ` +
76
+ `Run "pnpm install" and try again.`,
77
+ );
78
+ }
79
+ result[name] = version;
80
+ }
81
+ return result;
82
+ }
83
+
84
+ /**
85
+ * 프로덕션 배포용 파일을 생성한다
86
+ */
87
+ export function generateProductionFiles(
88
+ info: ServerBuildInfo,
89
+ externals: string[],
90
+ ): void {
91
+ const distDir = path.join(info.pkgDir, "dist");
92
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(info.pkgDir, "package.json"), "utf-8"));
93
+
94
+ // dist/package.json
95
+ const distPkgJson: Record<string, unknown> = {
96
+ name: pkgJson.name,
97
+ version: pkgJson.version,
98
+ type: pkgJson.type,
99
+ };
100
+ if (externals.length > 0) {
101
+ distPkgJson["dependencies"] = resolveLockedVersions(info.cwd, externals);
102
+ }
103
+ if (info.packageManager === "volta") {
104
+ const nodeVersion = cpx.spawnSync("node", ["-v"]).stdout.trim();
105
+ distPkgJson["volta"] = { node: nodeVersion };
106
+ }
107
+ fs.writeFileSync(path.join(distDir, "package.json"), JSON.stringify(distPkgJson, undefined, 2));
108
+
109
+ // dist/mise.toml
110
+ if (info.packageManager === "mise") {
111
+ const rootMiseTomlPath = path.join(info.cwd, "mise.toml");
112
+ let nodeVersion = "20";
113
+ if (fs.existsSync(rootMiseTomlPath)) {
114
+ const miseContent = fs.readFileSync(rootMiseTomlPath, "utf-8");
115
+ const match = /node\s*=\s*"([^"]+)"/.exec(miseContent);
116
+ if (match != null) {
117
+ nodeVersion = match[1];
118
+ }
119
+ }
120
+ fs.writeFileSync(path.join(distDir, "mise.toml"), `[tools]\nnode = "${nodeVersion}"\n`);
121
+ }
122
+
123
+ // dist/openssl.cnf
124
+ fs.writeFileSync(
125
+ path.join(distDir, "openssl.cnf"),
126
+ [
127
+ "nodejs_conf = openssl_init",
128
+ "",
129
+ "[openssl_init]",
130
+ "providers = provider_sect",
131
+ "ssl_conf = ssl_sect",
132
+ "",
133
+ "[provider_sect]",
134
+ "default = default_sect",
135
+ "legacy = legacy_sect",
136
+ "",
137
+ "[default_sect]",
138
+ "activate = 1",
139
+ "",
140
+ "[legacy_sect]",
141
+ "activate = 1",
142
+ "",
143
+ "[ssl_sect]",
144
+ "system_default = system_default_sect",
145
+ "",
146
+ "[system_default_sect]",
147
+ "Options = UnsafeLegacyRenegotiation",
148
+ ].join("\n"),
149
+ );
150
+
151
+ // dist/pm2.config.cjs
152
+ if (info.pm2 != null) {
153
+ const pm2Name = info.pm2.name ?? pkgJson.name.replace(/@/g, "").replace(/[/\\]/g, "-");
154
+ const ignoreWatch = JSON.stringify([
155
+ "node_modules",
156
+ "www",
157
+ ...(info.pm2.ignoreWatchPaths ?? []),
158
+ ]);
159
+ const envObj: Record<string, string> = {
160
+ NODE_ENV: "production",
161
+ TZ: "Asia/Seoul",
162
+ ...(info.env ?? {}),
163
+ };
164
+ const envStr = JSON.stringify(envObj, undefined, 4);
165
+
166
+ const useInterpreter = info.packageManager !== "volta";
167
+
168
+ const pm2Config = [
169
+ ...(useInterpreter ? [`const cp = require("child_process");`, ``] : []),
170
+ `module.exports = {`,
171
+ ` name: ${JSON.stringify(pm2Name)},`,
172
+ ` script: "main.js",`,
173
+ ` watch: true,`,
174
+ ` watch_delay: 2000,`,
175
+ ` ignore_watch: ${ignoreWatch},`,
176
+ ...(useInterpreter ? [` interpreter: cp.execSync("mise which node").toString().trim(),`] : []),
177
+ ` interpreter_args: "--openssl-config=openssl.cnf",`,
178
+ ` env: ${envStr.replace(/\n/g, "\n ")},`,
179
+ ` arrayProcess: "concat",`,
180
+ ` useDelTargetNull: true,`,
181
+ `};`,
182
+ ].join("\n");
183
+
184
+ fs.writeFileSync(path.join(distDir, "pm2.config.cjs"), pm2Config);
185
+ }
186
+ }
@@ -207,7 +207,7 @@ export async function createClientViteConfig(
207
207
  optimizeDeps: {
208
208
  ...optimizeDepsConfig,
209
209
  esbuildOptions: {
210
- target: esbuildTarget as string[],
210
+ target: esbuildTarget,
211
211
  },
212
212
  },
213
213
  };
@@ -412,6 +412,9 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
412
412
  const polyfills = fs.existsSync(polyfillsPath) ? ["./src/polyfills.ts"] : undefined;
413
413
 
414
414
  let lintResult: LintWithProgramResult | undefined;
415
+ let buildSuccess = true;
416
+ let buildErrors: string[] | undefined;
417
+ let buildWarnings: string[] | undefined;
415
418
 
416
419
  const viteConfig = await createClientViteConfig({
417
420
  framework: info.framework,
@@ -423,6 +426,9 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
423
426
  env: info.env,
424
427
  enableLint: info.enableLint,
425
428
  onBuild: (result) => {
429
+ buildSuccess = result.success;
430
+ buildErrors = result.errors;
431
+ buildWarnings = result.warnings;
426
432
  if (result.lint != null) {
427
433
  lintResult = result.lint;
428
434
  }
@@ -443,7 +449,7 @@ async function build(info: ClientBuildInfo): Promise<ClientBuildResult> {
443
449
  writeConfigJson(path.join(info.pkgDir, "dist"), info.configs);
444
450
 
445
451
  logger.debug(`[${info.name}] client worker build 완료`);
446
- return { success: true, lint: lintResult };
452
+ return { success: buildSuccess, errors: buildErrors, warnings: buildWarnings, lint: lintResult };
447
453
  } catch (err) {
448
454
  const message = errNs.message(err);
449
455
  logger.debug(`[${info.name}] client worker build 예외: ${message}`);
@@ -1,5 +1,5 @@
1
1
  import { createWorker } from "@simplysm/core-node";
2
- import { executeLint, type LintOptions, type LintResult } from "../commands/lint";
2
+ import { executeLint, type LintOptions, type LintResult } from "../utils/lint-core";
3
3
 
4
4
  //#region Worker
5
5
 
@@ -2,7 +2,7 @@ import type ts from "typescript";
2
2
  import path from "path";
3
3
  import fs from "fs";
4
4
  import esbuild from "esbuild";
5
- import { cpx, createWorker, FsWatcher, pathx } from "@simplysm/core-node";
5
+ import { createWorker, FsWatcher, pathx } from "@simplysm/core-node";
6
6
  import { err as errNs } from "@simplysm/core-common";
7
7
  import { consola } from "consola";
8
8
  import type { BuildOutput } from "../engines/types";
@@ -14,9 +14,9 @@ import {
14
14
  } from "../utils/tsconfig";
15
15
  import {
16
16
  createServerEsbuildOptions,
17
- collectAllDependencyExternals,
18
17
  writeChangedOutputFiles,
19
18
  } from "../utils/esbuild-config";
19
+ import { collectAllExternals, generateProductionFiles } from "../utils/server-production-files";
20
20
  import { runTscPackageBuild } from "../utils/tsc-build";
21
21
  import { LintWithProgramRunner } from "../utils/lint-with-program";
22
22
  import { registerCleanupHandlers, createOnceGuard, setupWorkerConsola } from "../utils/worker-utils";
@@ -136,184 +136,6 @@ async function cleanup(): Promise<void> {
136
136
  }
137
137
  }
138
138
 
139
- /**
140
- * 세 가지 소스에서 외부 모듈을 수집하고 병합한다.
141
- * collectAllDependencyExternals를 통한 단일 패스 의존성 트리 순회를 사용한다.
142
- */
143
- function collectAllExternals(pkgDir: string, manualExternals?: string[]): string[] {
144
- logger.debug("의존성 트리 스캔 중...");
145
- const { optionalPeerDeps, nativeModules } = collectAllDependencyExternals(pkgDir);
146
-
147
- const manual = manualExternals ?? [];
148
- return [...new Set([...optionalPeerDeps, ...nativeModules, ...manual])];
149
- }
150
-
151
- /**
152
- * pnpm-lock.yaml의 packages 섹션을 파싱하여 name→version 맵을 생성한다.
153
- * Lockfile v9 형식: `packages:` 섹션의 `'name@version':` 키를 파싱한다.
154
- * YAML 파서 의존성을 피하기 위해 단순 라인 기반 파싱을 사용한다.
155
- */
156
- function parseLockfileVersions(cwd: string): Map<string, string> {
157
- const lockfilePath = path.join(cwd, "pnpm-lock.yaml");
158
- if (!fs.existsSync(lockfilePath)) {
159
- throw new Error(`pnpm-lock.yaml not found in ${cwd}. Run "pnpm install" first.`);
160
- }
161
-
162
- const content = fs.readFileSync(lockfilePath, "utf-8");
163
- const map = new Map<string, string>();
164
-
165
- // "packages:" 섹션을 찾고 "'@scope/name@1.2.3':" 또는 "'name@1.2.3':" 형태의 항목을 파싱
166
- const lines = content.split("\n");
167
- let inPackages = false;
168
- for (const line of lines) {
169
- if (line === "packages:") {
170
- inPackages = true;
171
- continue;
172
- }
173
- if (inPackages && line.length > 0 && !line.startsWith(" ") && !line.startsWith("'")) {
174
- break; // 다음 최상위 섹션
175
- }
176
- if (!inPackages) continue;
177
-
178
- // "'@scope/name@version':" 또는 "'name@version':" 매칭
179
- const match = /^\s{2}'(.+)@(\d[^']*)':\s*$/.exec(line);
180
- if (match != null) {
181
- const name = match[1];
182
- const version = match[2];
183
- // 첫 번째 항목 유지 (lockfile은 각 버전을 한 번만 기록)
184
- if (!map.has(name)) {
185
- map.set(name, version);
186
- }
187
- }
188
- }
189
-
190
- return map;
191
- }
192
-
193
- /**
194
- * pnpm-lock.yaml에서 주어진 모든 패키지의 잠긴 버전을 확인한다.
195
- * lockfile에서 패키지를 찾을 수 없으면 에러를 던진다.
196
- */
197
- function resolveLockedVersions(cwd: string, pkgNames: string[]): Record<string, string> {
198
- const versionMap = parseLockfileVersions(cwd);
199
- const result: Record<string, string> = {};
200
- for (const name of pkgNames) {
201
- const version = versionMap.get(name);
202
- if (version == null) {
203
- throw new Error(
204
- `External dependency "${name}" not found in pnpm-lock.yaml. ` +
205
- `Run "pnpm install" and try again.`,
206
- );
207
- }
208
- result[name] = version;
209
- }
210
- return result;
211
- }
212
-
213
- /**
214
- * 프로덕션 배포용 파일을 생성한다
215
- */
216
- function generateProductionFiles(
217
- info: ServerBuildInfo,
218
- externals: string[],
219
- ): void {
220
- const distDir = path.join(info.pkgDir, "dist");
221
- const pkgJson = JSON.parse(fs.readFileSync(path.join(info.pkgDir, "package.json"), "utf-8"));
222
-
223
- // dist/package.json
224
- const distPkgJson: Record<string, unknown> = {
225
- name: pkgJson.name,
226
- version: pkgJson.version,
227
- type: pkgJson.type,
228
- };
229
- if (externals.length > 0) {
230
- distPkgJson["dependencies"] = resolveLockedVersions(info.cwd, externals);
231
- }
232
- if (info.packageManager === "volta") {
233
- const nodeVersion = cpx.spawnSync("node", ["-v"]).stdout.trim();
234
- distPkgJson["volta"] = { node: nodeVersion };
235
- }
236
- fs.writeFileSync(path.join(distDir, "package.json"), JSON.stringify(distPkgJson, undefined, 2));
237
-
238
- // dist/mise.toml
239
- if (info.packageManager === "mise") {
240
- const rootMiseTomlPath = path.join(info.cwd, "mise.toml");
241
- let nodeVersion = "20";
242
- if (fs.existsSync(rootMiseTomlPath)) {
243
- const miseContent = fs.readFileSync(rootMiseTomlPath, "utf-8");
244
- const match = /node\s*=\s*"([^"]+)"/.exec(miseContent);
245
- if (match != null) {
246
- nodeVersion = match[1];
247
- }
248
- }
249
- fs.writeFileSync(path.join(distDir, "mise.toml"), `[tools]\nnode = "${nodeVersion}"\n`);
250
- }
251
-
252
- // dist/openssl.cnf
253
- fs.writeFileSync(
254
- path.join(distDir, "openssl.cnf"),
255
- [
256
- "nodejs_conf = openssl_init",
257
- "",
258
- "[openssl_init]",
259
- "providers = provider_sect",
260
- "ssl_conf = ssl_sect",
261
- "",
262
- "[provider_sect]",
263
- "default = default_sect",
264
- "legacy = legacy_sect",
265
- "",
266
- "[default_sect]",
267
- "activate = 1",
268
- "",
269
- "[legacy_sect]",
270
- "activate = 1",
271
- "",
272
- "[ssl_sect]",
273
- "system_default = system_default_sect",
274
- "",
275
- "[system_default_sect]",
276
- "Options = UnsafeLegacyRenegotiation",
277
- ].join("\n"),
278
- );
279
-
280
- // dist/pm2.config.cjs
281
- if (info.pm2 != null) {
282
- const pm2Name = info.pm2.name ?? pkgJson.name.replace(/@/g, "").replace(/[/\\]/g, "-");
283
- const ignoreWatch = JSON.stringify([
284
- "node_modules",
285
- "www",
286
- ...(info.pm2.ignoreWatchPaths ?? []),
287
- ]);
288
- const envObj: Record<string, string> = {
289
- NODE_ENV: "production",
290
- TZ: "Asia/Seoul",
291
- ...(info.env ?? {}),
292
- };
293
- const envStr = JSON.stringify(envObj, undefined, 4);
294
-
295
- const useInterpreter = info.packageManager !== "volta";
296
-
297
- const pm2Config = [
298
- ...(useInterpreter ? [`const cp = require("child_process");`, ``] : []),
299
- `module.exports = {`,
300
- ` name: ${JSON.stringify(pm2Name)},`,
301
- ` script: "main.js",`,
302
- ` watch: true,`,
303
- ` watch_delay: 2000,`,
304
- ` ignore_watch: ${ignoreWatch},`,
305
- ...(useInterpreter ? [` interpreter: cp.execSync("mise which node").toString().trim(),`] : []),
306
- ` interpreter_args: "--openssl-config=openssl.cnf",`,
307
- ` env: ${envStr.replace(/\n/g, "\n ")},`,
308
- ` arrayProcess: "concat",`,
309
- ` useDelTargetNull: true,`,
310
- `};`,
311
- ].join("\n");
312
-
313
- fs.writeFileSync(path.join(distDir, "pm2.config.cjs"), pm2Config);
314
- }
315
- }
316
-
317
139
  registerCleanupHandlers(cleanup, logger);
318
140
 
319
141
  //#endregion
@@ -610,11 +432,15 @@ async function startWatch(info: ServerWatchInfo): Promise<void> {
610
432
  const newExternal = cachedExternal;
611
433
 
612
434
  const oldContext = esbuildContext;
613
- if (info.output.js) {
614
- esbuildContext = await createEsbuildWatchContext(info, newEntryPoints, newExternal);
615
- }
616
- if (oldContext != null) {
617
- await oldContext.dispose();
435
+ esbuildContext = undefined; // 선제 초기화 — 생성 실패 시 disposed 참조 방지 (LOGIC-001)
436
+ try {
437
+ if (info.output.js) {
438
+ esbuildContext = await createEsbuildWatchContext(info, newEntryPoints, newExternal);
439
+ }
440
+ } finally {
441
+ if (oldContext != null) {
442
+ await oldContext.dispose();
443
+ }
618
444
  }
619
445
 
620
446
  const result = await rebuildAll();
@@ -39,6 +39,16 @@ describe("processPostCssInline", () => {
39
39
  expect(result).toBe(input);
40
40
  });
41
41
 
42
+ // Acceptance: double-quote 문자열의 인용부호가 보존된다
43
+ it("preserves double-quote style when processing inline CSS", async () => {
44
+ const input = `Component({ styles: [".host { color: blue; }"] })`;
45
+ const result = await processPostCssInline(input, "test.js", [testPlugin]);
46
+
47
+ expect(result).toContain("color: red");
48
+ // double-quote가 보존되어야 한다
49
+ expect(result).toMatch(/".host \{ color: red; \}"/);
50
+ });
51
+
42
52
  // Unit: multiple styles in array
43
53
  it("processes multiple styles in the array", async () => {
44
54
  const input = `Component({ styles: ['.a { color: blue; }', '.b { color: green; }'] })`;
@@ -0,0 +1,11 @@
1
+ # Capacitor Android 설정 분리 — LLM 검증
2
+
3
+ ## 검증 항목
4
+
5
+ - [x] configureAndroid이 capacitor-android.ts에서 export된다: line 13 `export async function configureAndroid`
6
+ - [x] findJava21이 capacitor-android.ts에서 export된다: line 53 `export async function findJava21`
7
+ - [x] findAndroidSdk가 capacitor-android.ts에서 export된다: line 76 `export async function findAndroidSdk`
8
+ - [x] 내부 configure 함수들(_configureJavaHomePath 등)은 export되지 않는다: `^export` 검색 결과 3개만 확인
9
+ - [x] Capacitor 클래스의 _validateTools가 findAndroidSdk/findJava21을 import하여 사용한다: line 8 import, line 217/229 호출
10
+ - [x] Capacitor 클래스에서 9개 Android 설정 private 메서드가 삭제되었다: `_configureAndroid` 등 검색 결과 0건
11
+ - [x] capacitor.ts에서 env import가 제거되었다: `import.*env.*from` 검색 결과 0건