@simplysm/sd-cli 13.0.93 → 13.0.96

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 (67) hide show
  1. package/README.md +67 -44
  2. package/dist/capacitor/capacitor.d.ts +15 -1
  3. package/dist/capacitor/capacitor.d.ts.map +1 -1
  4. package/dist/capacitor/capacitor.js +52 -31
  5. package/dist/capacitor/capacitor.js.map +1 -1
  6. package/dist/electron/electron.d.ts +6 -2
  7. package/dist/electron/electron.d.ts.map +1 -1
  8. package/dist/electron/electron.js +12 -6
  9. package/dist/electron/electron.js.map +1 -1
  10. package/dist/orchestrators/DevOrchestrator.d.ts.map +1 -1
  11. package/dist/orchestrators/DevOrchestrator.js +22 -4
  12. package/dist/orchestrators/DevOrchestrator.js.map +1 -1
  13. package/dist/sd-cli-entry.d.ts.map +1 -1
  14. package/dist/sd-cli-entry.js +4 -1
  15. package/dist/sd-cli-entry.js.map +1 -1
  16. package/dist/utils/esbuild-config.d.ts +2 -0
  17. package/dist/utils/esbuild-config.d.ts.map +1 -1
  18. package/dist/utils/esbuild-config.js +86 -6
  19. package/dist/utils/esbuild-config.js.map +1 -1
  20. package/dist/utils/package-utils.d.ts.map +1 -1
  21. package/dist/utils/package-utils.js +7 -0
  22. package/dist/utils/package-utils.js.map +1 -1
  23. package/dist/utils/replace-deps.d.ts.map +1 -1
  24. package/dist/utils/replace-deps.js +17 -0
  25. package/dist/utils/replace-deps.js.map +1 -1
  26. package/dist/utils/worker-utils.d.ts +9 -1
  27. package/dist/utils/worker-utils.d.ts.map +1 -1
  28. package/dist/utils/worker-utils.js +7 -0
  29. package/dist/utils/worker-utils.js.map +1 -1
  30. package/dist/workers/client.worker.d.ts.map +1 -1
  31. package/dist/workers/client.worker.js +2 -1
  32. package/dist/workers/client.worker.js.map +1 -1
  33. package/dist/workers/dts.worker.d.ts.map +1 -1
  34. package/dist/workers/dts.worker.js +2 -1
  35. package/dist/workers/dts.worker.js.map +1 -1
  36. package/dist/workers/library.worker.d.ts.map +1 -1
  37. package/dist/workers/library.worker.js +2 -1
  38. package/dist/workers/library.worker.js.map +1 -1
  39. package/dist/workers/server-runtime.worker.d.ts +1 -0
  40. package/dist/workers/server-runtime.worker.d.ts.map +1 -1
  41. package/dist/workers/server-runtime.worker.js +36 -2
  42. package/dist/workers/server-runtime.worker.js.map +1 -1
  43. package/dist/workers/server.worker.d.ts.map +1 -1
  44. package/dist/workers/server.worker.js +144 -4
  45. package/dist/workers/server.worker.js.map +1 -1
  46. package/docs/architecture.md +14 -14
  47. package/docs/config-types.md +12 -2
  48. package/package.json +4 -4
  49. package/src/capacitor/capacitor.ts +59 -31
  50. package/src/electron/electron.ts +13 -6
  51. package/src/orchestrators/DevOrchestrator.ts +20 -1
  52. package/src/sd-cli-entry.ts +4 -1
  53. package/src/utils/esbuild-config.ts +86 -6
  54. package/src/utils/package-utils.ts +8 -0
  55. package/src/utils/replace-deps.ts +20 -0
  56. package/src/utils/worker-utils.ts +14 -1
  57. package/src/workers/client.worker.ts +3 -1
  58. package/src/workers/dts.worker.ts +3 -1
  59. package/src/workers/library.worker.ts +3 -1
  60. package/src/workers/server-runtime.worker.ts +42 -2
  61. package/src/workers/server.worker.ts +165 -3
  62. package/templates/init/package.json.hbs +3 -3
  63. package/templates/init/packages/client-admin/package.json.hbs +7 -7
  64. package/templates/init/packages/db-main/package.json.hbs +2 -2
  65. package/templates/init/packages/server/package.json.hbs +5 -5
  66. package/templates/init/tests-e2e/package.json.hbs +1 -1
  67. package/tests/capacitor.spec.ts +49 -0
@@ -4,7 +4,7 @@ import { err as errNs } from "@simplysm/core-common";
4
4
  import { consola } from "consola";
5
5
  import net from "net";
6
6
  import { pathToFileURL } from "url";
7
- import { registerCleanupHandlers } from "../utils/worker-utils";
7
+ import { registerCleanupHandlers, applyDebugLevel } from "../utils/worker-utils";
8
8
 
9
9
  //#region Types
10
10
 
@@ -14,6 +14,7 @@ import { registerCleanupHandlers } from "../utils/worker-utils";
14
14
  export interface ServerRuntimeStartInfo {
15
15
  mainJsPath: string;
16
16
  clientPorts: Record<string, number>;
17
+ env?: Record<string, string>;
17
18
  }
18
19
 
19
20
  /**
@@ -40,6 +41,8 @@ export interface ServerRuntimeWorkerEvents extends Record<string, unknown> {
40
41
 
41
42
  //#endregion
42
43
 
44
+ applyDebugLevel();
45
+
43
46
  const logger = consola.withTag("sd:cli:server-runtime:worker");
44
47
 
45
48
  /** Server instance (to be cleaned up) */
@@ -109,8 +112,20 @@ async function findAvailablePort(startPort: number, maxRetries = 20): Promise<nu
109
112
  */
110
113
  async function start(info: ServerRuntimeStartInfo): Promise<void> {
111
114
  try {
115
+ const startTime = performance.now();
116
+
117
+ // Inject environment variables into process.env before importing main.js
118
+ if (info.env != null) {
119
+ for (const [key, value] of Object.entries(info.env)) {
120
+ process.env[key] = value;
121
+ }
122
+ }
123
+
112
124
  // Import main.js (must export a server instance)
125
+ logger.debug("[start] Importing main.js...");
126
+ let stepStart = performance.now();
113
127
  const module = await import(pathToFileURL(info.mainJsPath).href);
128
+ logger.debug(`[start] main.js imported (${Math.round(performance.now() - stepStart)}ms)`);
114
129
  const server = module.server;
115
130
 
116
131
  if (server == null) {
@@ -121,15 +136,28 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
121
136
  serverInstance = server;
122
137
 
123
138
  // Find available port (auto-increment on port conflict)
139
+ logger.debug("[start] Finding available port...");
140
+ stepStart = performance.now();
124
141
  const originalPort = server.options.port;
125
142
  const availablePort = await findAvailablePort(originalPort);
126
143
  if (availablePort !== originalPort) {
127
144
  logger.info(`Port ${originalPort} in use, changing to ${availablePort}`);
128
145
  server.options.port = availablePort;
129
146
  }
147
+ logger.debug(
148
+ `[start] Port ${String(availablePort)} available (${Math.round(performance.now() - stepStart)}ms)`,
149
+ );
130
150
 
131
151
  // Configure Vite proxy (only if clientPorts exists)
132
- for (const [name, port] of Object.entries(info.clientPorts)) {
152
+ const clientEntries = Object.entries(info.clientPorts);
153
+ if (clientEntries.length > 0) {
154
+ logger.debug(
155
+ `[start] Configuring ${String(clientEntries.length)} Vite proxy(s)...`,
156
+ );
157
+ stepStart = performance.now();
158
+ }
159
+ for (const [name, port] of clientEntries) {
160
+ logger.debug(`[start] Registering proxy: /${name} -> http://127.0.0.1:${String(port)}`);
133
161
  await server.fastify.register(proxy, {
134
162
  prefix: `/${name}`,
135
163
  upstream: `http://127.0.0.1:${port}`,
@@ -137,9 +165,21 @@ async function start(info: ServerRuntimeStartInfo): Promise<void> {
137
165
  websocket: true,
138
166
  });
139
167
  }
168
+ if (clientEntries.length > 0) {
169
+ logger.debug(
170
+ `[start] Proxies configured (${Math.round(performance.now() - stepStart)}ms)`,
171
+ );
172
+ }
140
173
 
141
174
  // Start server
175
+ logger.debug("[start] Starting server listen...");
176
+ stepStart = performance.now();
142
177
  await server.listen();
178
+ logger.debug(`[start] Server listening (${Math.round(performance.now() - stepStart)}ms)`);
179
+
180
+ logger.debug(
181
+ `[start] Total runtime startup: ${Math.round(performance.now() - startTime)}ms`,
182
+ );
143
183
 
144
184
  sender.send("serverReady", { port: server.options.port });
145
185
  } catch (err) {
@@ -16,7 +16,7 @@ import {
16
16
  collectNativeModuleExternals,
17
17
  writeChangedOutputFiles,
18
18
  } from "../utils/esbuild-config";
19
- import { registerCleanupHandlers, createOnceGuard } from "../utils/worker-utils";
19
+ import { registerCleanupHandlers, createOnceGuard, applyDebugLevel } from "../utils/worker-utils";
20
20
  import { collectDeps } from "../utils/package-utils";
21
21
  import { copyPublicFiles, watchPublicFiles } from "../utils/copy-public";
22
22
 
@@ -101,6 +101,8 @@ export interface ServerWorkerEvents extends Record<string, unknown> {
101
101
 
102
102
  //#region Resource Management
103
103
 
104
+ applyDebugLevel();
105
+
104
106
  const logger = consola.withTag("sd:cli:server:worker");
105
107
 
106
108
  /** esbuild build context (to be cleaned up) */
@@ -151,8 +153,20 @@ async function cleanup(): Promise<void> {
151
153
  * 3. Manually specified in sd.config.ts
152
154
  */
153
155
  function collectAllExternals(pkgDir: string, manualExternals?: string[]): string[] {
156
+ logger.debug("[externals] Scanning optional peer deps...");
157
+ let stepStart = performance.now();
154
158
  const optionalPeerDeps = collectUninstalledOptionalPeerDeps(pkgDir);
159
+ logger.debug(
160
+ `[externals] Optional peer deps done: ${String(optionalPeerDeps.length)} found (${Math.round(performance.now() - stepStart)}ms)`,
161
+ );
162
+
163
+ logger.debug("[externals] Scanning native modules...");
164
+ stepStart = performance.now();
155
165
  const nativeModules = collectNativeModuleExternals(pkgDir);
166
+ logger.debug(
167
+ `[externals] Native modules done: ${String(nativeModules.length)} found (${Math.round(performance.now() - stepStart)}ms)`,
168
+ );
169
+
156
170
  const manual = manualExternals ?? [];
157
171
 
158
172
  const merged = [...new Set([...optionalPeerDeps, ...nativeModules, ...manual])];
@@ -307,20 +321,39 @@ async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
307
321
 
308
322
  try {
309
323
  // Parse tsconfig
324
+ logger.debug("[build] Parsing tsconfig...");
325
+ let stepStart = performance.now();
310
326
  const parsedConfig = parseRootTsconfig(info.cwd);
327
+ logger.debug(`[build] tsconfig parsed (${Math.round(performance.now() - stepStart)}ms)`);
328
+
329
+ stepStart = performance.now();
311
330
  const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
331
+ logger.debug(
332
+ `[build] Found ${String(entryPoints.length)} source files (${Math.round(performance.now() - stepStart)}ms)`,
333
+ );
312
334
 
313
335
  // Server target is node environment
336
+ logger.debug("[build] Getting compiler options...");
337
+ stepStart = performance.now();
314
338
  const compilerOptions = await getCompilerOptionsForPackage(
315
339
  parsedConfig.options,
316
340
  "node",
317
341
  info.pkgDir,
318
342
  );
343
+ logger.debug(
344
+ `[build] Compiler options ready (${Math.round(performance.now() - stepStart)}ms)`,
345
+ );
319
346
 
320
347
  // Collect all externals (optional peer deps + native modules + manual)
348
+ logger.debug("[build] Collecting externals...");
349
+ stepStart = performance.now();
321
350
  const external = collectAllExternals(info.pkgDir, info.externals);
351
+ logger.debug(
352
+ `[build] Collected ${String(external.length)} externals (${Math.round(performance.now() - stepStart)}ms)`,
353
+ );
322
354
 
323
355
  // One-time esbuild
356
+ logger.debug("[build] Creating esbuild options...");
324
357
  const esbuildOptions = createServerEsbuildOptions({
325
358
  pkgDir: info.pkgDir,
326
359
  entryPoints,
@@ -329,17 +362,30 @@ async function build(info: ServerBuildInfo): Promise<ServerBuildResult> {
329
362
  external,
330
363
  });
331
364
 
365
+ logger.debug("[build] Running esbuild...");
366
+ stepStart = performance.now();
332
367
  const result = await esbuild.build(esbuildOptions);
368
+ logger.debug(`[build] esbuild done (${Math.round(performance.now() - stepStart)}ms)`);
333
369
 
334
370
  // Generate .config.json
335
371
  const confDistPath = path.join(info.pkgDir, "dist", ".config.json");
336
372
  fs.writeFileSync(confDistPath, JSON.stringify(info.configs ?? {}, undefined, 2));
337
373
 
338
374
  // Copy public/ to dist/ (production build: no public-dev)
375
+ logger.debug("[build] Copying public files...");
376
+ stepStart = performance.now();
339
377
  await copyPublicFiles(info.pkgDir, false);
378
+ logger.debug(
379
+ `[build] Public files copied (${Math.round(performance.now() - stepStart)}ms)`,
380
+ );
340
381
 
341
382
  // Generate production files (package.json, mise.toml, openssl.cnf, pm2.config.cjs)
383
+ logger.debug("[build] Generating production files...");
384
+ stepStart = performance.now();
342
385
  generateProductionFiles(info, external);
386
+ logger.debug(
387
+ `[build] Production files generated (${Math.round(performance.now() - stepStart)}ms)`,
388
+ );
343
389
 
344
390
  const errors = result.errors.map((e) => e.text);
345
391
  const warnings = result.warnings.map((w) => w.text);
@@ -368,26 +414,53 @@ async function createAndBuildContext(
368
414
  isFirstBuild: boolean,
369
415
  resolveFirstBuild?: () => void,
370
416
  ): Promise<esbuild.BuildContext> {
417
+ const contextStart = performance.now();
418
+
419
+ logger.debug("[context] Parsing tsconfig...");
420
+ let stepStart = performance.now();
371
421
  const parsedConfig = parseRootTsconfig(info.cwd);
422
+ logger.debug(`[context] tsconfig parsed (${Math.round(performance.now() - stepStart)}ms)`);
423
+
424
+ stepStart = performance.now();
372
425
  const entryPoints = getPackageSourceFiles(info.pkgDir, parsedConfig);
426
+ logger.debug(
427
+ `[context] Found ${String(entryPoints.length)} source files (${Math.round(performance.now() - stepStart)}ms)`,
428
+ );
429
+
430
+ logger.debug("[context] Getting compiler options...");
431
+ stepStart = performance.now();
373
432
  const compilerOptions = await getCompilerOptionsForPackage(
374
433
  parsedConfig.options,
375
434
  "node",
376
435
  info.pkgDir,
377
436
  );
437
+ logger.debug(
438
+ `[context] Compiler options ready (${Math.round(performance.now() - stepStart)}ms)`,
439
+ );
378
440
 
379
441
  const mainJsPath = path.join(info.pkgDir, "dist", "main.js");
442
+
443
+ logger.debug("[context] Collecting externals...");
444
+ stepStart = performance.now();
380
445
  const external = collectAllExternals(info.pkgDir, info.externals);
446
+ logger.debug(
447
+ `[context] Collected ${String(external.length)} externals (${Math.round(performance.now() - stepStart)}ms)`,
448
+ );
449
+
450
+ logger.debug("[context] Creating esbuild options (dev mode, minify disabled)...");
381
451
  const baseOptions = createServerEsbuildOptions({
382
452
  pkgDir: info.pkgDir,
383
453
  entryPoints,
384
454
  compilerOptions,
385
455
  env: info.env,
386
456
  external,
457
+ dev: true,
387
458
  });
388
459
 
389
460
  let isBuildFirstTime = isFirstBuild;
390
461
 
462
+ logger.debug("[context] Creating esbuild context...");
463
+ stepStart = performance.now();
391
464
  const context = await esbuild.context({
392
465
  ...baseOptions,
393
466
  metafile: true,
@@ -396,14 +469,43 @@ async function createAndBuildContext(
396
469
  {
397
470
  name: "watch-notify",
398
471
  setup(pluginBuild) {
472
+ let consecutiveStarts = 0;
473
+
399
474
  pluginBuild.onStart(() => {
400
- sender.send("buildStart", {});
475
+ consecutiveStarts++;
476
+ logger.debug(`[esbuild] onStart (#${String(consecutiveStarts)})`);
477
+
478
+ if (consecutiveStarts > 3) {
479
+ // esbuild context.rebuild() silently retries on build errors (esbuild bug).
480
+ // Stop the retry loop and diagnose via esbuild.build().
481
+ void context.dispose().catch(() => {});
482
+
483
+ void esbuild
484
+ .build(baseOptions)
485
+ .catch((err: unknown) => {
486
+ sender.send("build", {
487
+ success: false,
488
+ mainJsPath,
489
+ errors: [errNs.message(err)],
490
+ });
491
+ })
492
+ .finally(() => {
493
+ resolveFirstBuild?.();
494
+ });
495
+ } else {
496
+ sender.send("buildStart", {});
497
+ }
401
498
  });
402
499
 
403
500
  pluginBuild.onEnd(async (result) => {
501
+ consecutiveStarts = 0;
502
+
404
503
  // Save metafile
405
504
  if (result.metafile != null) {
406
505
  lastMetafile = result.metafile;
506
+ logger.debug(
507
+ `[esbuild] Metafile: ${String(Object.keys(result.metafile.inputs).length)} inputs, ${String(Object.keys(result.metafile.outputs).length)} outputs`,
508
+ );
407
509
  }
408
510
 
409
511
  const errors = result.errors.map((e) => e.text);
@@ -413,7 +515,11 @@ async function createAndBuildContext(
413
515
  // Write output files and check for changes
414
516
  let hasOutputChange = false;
415
517
  if (success && result.outputFiles != null) {
518
+ const writeStart = performance.now();
416
519
  hasOutputChange = await writeChangedOutputFiles(result.outputFiles);
520
+ logger.debug(
521
+ `[esbuild] Output files written: changed=${String(hasOutputChange)}, count=${String(result.outputFiles.length)} (${Math.round(performance.now() - writeStart)}ms)`,
522
+ );
417
523
  }
418
524
 
419
525
  if (isBuildFirstTime && success) {
@@ -442,8 +548,33 @@ async function createAndBuildContext(
442
548
  },
443
549
  ],
444
550
  });
551
+ logger.debug(
552
+ `[context] esbuild context created (${Math.round(performance.now() - stepStart)}ms)`,
553
+ );
554
+
555
+ logger.debug("[context] Running initial rebuild...");
556
+ stepStart = performance.now();
557
+ const progressTimer = setInterval(() => {
558
+ logger.debug(
559
+ `[esbuild] Still building... (${Math.round((performance.now() - stepStart) / 1000)}s elapsed)`,
560
+ );
561
+ }, 5000);
562
+ try {
563
+ await context.rebuild();
564
+ } catch {
565
+ // context.rebuild() may reject with "Cannot rebuild" when disposed
566
+ // from onStart guard. The real error is reported via esbuild.build()
567
+ // fallback in onStart, so we suppress this rejection.
568
+ } finally {
569
+ clearInterval(progressTimer);
570
+ }
571
+ logger.debug(
572
+ `[context] Initial rebuild done (${Math.round(performance.now() - stepStart)}ms)`,
573
+ );
445
574
 
446
- await context.rebuild();
575
+ logger.debug(
576
+ `[context] Total context setup: ${Math.round(performance.now() - contextStart)}ms`,
577
+ );
447
578
 
448
579
  return context;
449
580
  }
@@ -457,6 +588,9 @@ async function startWatch(info: ServerWatchInfo): Promise<void> {
457
588
  guardStartWatch();
458
589
 
459
590
  try {
591
+ const watchStart = performance.now();
592
+ logger.debug("[startWatch] Starting watch setup...");
593
+
460
594
  // Promise to wait for first build completion
461
595
  let resolveFirstBuild!: () => void;
462
596
  const firstBuildPromise = new Promise<void>((resolve) => {
@@ -464,16 +598,36 @@ async function startWatch(info: ServerWatchInfo): Promise<void> {
464
598
  });
465
599
 
466
600
  // Create initial esbuild context and build
601
+ logger.debug("[startWatch] Creating initial esbuild context...");
602
+ let stepStart = performance.now();
467
603
  esbuildContext = await createAndBuildContext(info, true, resolveFirstBuild);
604
+ logger.debug(
605
+ `[startWatch] Initial context created (${Math.round(performance.now() - stepStart)}ms)`,
606
+ );
468
607
 
469
608
  // Wait for first build completion
609
+ logger.debug("[startWatch] Waiting for first build completion...");
610
+ stepStart = performance.now();
470
611
  await firstBuildPromise;
612
+ logger.debug(
613
+ `[startWatch] First build completed (${Math.round(performance.now() - stepStart)}ms)`,
614
+ );
471
615
 
472
616
  // Watch public/ and public-dev/ (dev mode includes public-dev)
617
+ logger.debug("[startWatch] Setting up public file watcher...");
618
+ stepStart = performance.now();
473
619
  publicWatcher = await watchPublicFiles(info.pkgDir, true);
620
+ logger.debug(
621
+ `[startWatch] Public watcher ready (${Math.round(performance.now() - stepStart)}ms)`,
622
+ );
474
623
 
475
624
  // Collect watch paths based on dependencies
625
+ logger.debug("[startWatch] Collecting dependencies for watch paths...");
626
+ stepStart = performance.now();
476
627
  const { workspaceDeps, replaceDeps } = collectDeps(info.pkgDir, info.cwd, info.replaceDeps);
628
+ logger.debug(
629
+ `[startWatch] Deps collected: workspace=${String(workspaceDeps.length)}, replace=${String(replaceDeps.length)} (${Math.round(performance.now() - stepStart)}ms)`,
630
+ );
477
631
 
478
632
  const watchPaths: string[] = [];
479
633
 
@@ -496,7 +650,15 @@ async function startWatch(info: ServerWatchInfo): Promise<void> {
496
650
  }
497
651
 
498
652
  // Start FsWatcher
653
+ logger.debug(`[startWatch] Starting FsWatcher with ${String(watchPaths.length)} paths...`);
654
+ stepStart = performance.now();
499
655
  srcWatcher = await FsWatcher.watch(watchPaths);
656
+ logger.debug(
657
+ `[startWatch] FsWatcher ready (${Math.round(performance.now() - stepStart)}ms)`,
658
+ );
659
+ logger.debug(
660
+ `[startWatch] Total watch setup: ${Math.round(performance.now() - watchStart)}ms`,
661
+ );
500
662
 
501
663
  // Handle file changes
502
664
  srcWatcher.onChange({ delay: 300 }, async (changes) => {
@@ -19,9 +19,9 @@
19
19
  "postinstall": "playwright install && playwright-cli install --skills"
20
20
  },
21
21
  "devDependencies": {
22
- "@simplysm/lint": "~13.0.93",
23
- "@simplysm/sd-cli": "~13.0.93",
24
- "@simplysm/sd-claude": "~13.0.93",
22
+ "@simplysm/lint": "~13.0.96",
23
+ "@simplysm/sd-cli": "~13.0.96",
24
+ "@simplysm/sd-claude": "~13.0.96",
25
25
  "@types/node": "^20.19.37",
26
26
  "eslint": "^9.39.4",
27
27
  "prettier": "^3.8.1",
@@ -6,13 +6,13 @@
6
6
  "private": true,
7
7
  "dependencies": {
8
8
  "@{{projectName}}/db-main": "workspace:*",
9
- "@simplysm/core-browser": "~13.0.93",
10
- "@simplysm/core-common": "~13.0.93",
11
- "@simplysm/excel": "~13.0.93",
12
- "@simplysm/orm-common": "~13.0.93",
13
- "@simplysm/service-client": "~13.0.93",
14
- "@simplysm/service-common": "~13.0.93",
15
- "@simplysm/solid": "~13.0.93",
9
+ "@simplysm/core-browser": "~13.0.96",
10
+ "@simplysm/core-common": "~13.0.96",
11
+ "@simplysm/excel": "~13.0.96",
12
+ "@simplysm/orm-common": "~13.0.96",
13
+ "@simplysm/service-client": "~13.0.96",
14
+ "@simplysm/service-common": "~13.0.96",
15
+ "@simplysm/solid": "~13.0.96",
16
16
  "@solid-primitives/event-listener": "^2.4.5",
17
17
  "@solidjs/router": "^0.15.4",
18
18
  "@tabler/icons-solidjs": "^3.40.0",
@@ -7,7 +7,7 @@
7
7
  ".": "./src/index.ts"
8
8
  },
9
9
  "dependencies": {
10
- "@simplysm/core-common": "~13.0.93",
11
- "@simplysm/orm-common": "~13.0.93"
10
+ "@simplysm/core-common": "~13.0.96",
11
+ "@simplysm/orm-common": "~13.0.96"
12
12
  }
13
13
  }
@@ -5,11 +5,11 @@
5
5
  "private": true,
6
6
  "dependencies": {
7
7
  "@{{projectName}}/db-main": "workspace:*",
8
- "@simplysm/core-common": "~13.0.93",
9
- "@simplysm/excel": "~13.0.93",
10
- "@simplysm/orm-common": "~13.0.93",
11
- "@simplysm/orm-node": "~13.0.93",
12
- "@simplysm/service-server": "~13.0.93",
8
+ "@simplysm/core-common": "~13.0.96",
9
+ "@simplysm/excel": "~13.0.96",
10
+ "@simplysm/orm-common": "~13.0.96",
11
+ "@simplysm/orm-node": "~13.0.96",
12
+ "@simplysm/service-server": "~13.0.96",
13
13
  "bcrypt": "^6.0.0",
14
14
  "pg": "^8.20.0",
15
15
  "pg-copy-streams": "^7.0.0"
@@ -6,7 +6,7 @@
6
6
  "private": true,
7
7
  "dependencies": {
8
8
  "@{{projectName}}/db-main": "workspace:*",
9
- "@simplysm/orm-node": "~13.0.93",
9
+ "@simplysm/orm-node": "~13.0.96",
10
10
  "bcrypt": "^6.0.0",
11
11
  "playwright": "^1.58.2"
12
12
  },
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("@simplysm/core-node", () => ({
4
+ fsx: {
5
+ readJson: vi.fn().mockResolvedValue({ name: "test", version: "1.0.0" }),
6
+ },
7
+ }));
8
+
9
+ vi.mock("consola", () => {
10
+ const withTag = () => ({ debug: vi.fn(), warn: vi.fn() });
11
+ const consola = { withTag };
12
+ return { default: consola, consola };
13
+ });
14
+
15
+ vi.mock("sharp", () => ({ default: vi.fn() }));
16
+ vi.mock("execa", () => ({ execa: vi.fn() }));
17
+
18
+ import { Capacitor } from "../src/capacitor/capacitor";
19
+
20
+ describe("Capacitor appName validation", () => {
21
+ const createConfig = (appName: string) => ({
22
+ appId: "com.test.app",
23
+ appName,
24
+ });
25
+
26
+ it("한글 포함 앱 이름을 허용해야 한다", async () => {
27
+ await expect(
28
+ Capacitor.create("/tmp", createConfig("AD-TEK 기업솔루션")),
29
+ ).resolves.toBeDefined();
30
+ });
31
+
32
+ it("ASCII 앱 이름을 허용해야 한다", async () => {
33
+ await expect(
34
+ Capacitor.create("/tmp", createConfig("My App-1")),
35
+ ).resolves.toBeDefined();
36
+ });
37
+
38
+ it("파일명 위험 특수문자를 거부해야 한다", async () => {
39
+ await expect(
40
+ Capacitor.create("/tmp", createConfig("App<script>")),
41
+ ).rejects.toThrow("invalid characters");
42
+ });
43
+
44
+ it("빈 문자열을 거부해야 한다", async () => {
45
+ await expect(
46
+ Capacitor.create("/tmp", createConfig("")),
47
+ ).rejects.toThrow("appName is required");
48
+ });
49
+ });