@simplysm/sd-cli 14.0.66 → 14.0.70

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 (99) hide show
  1. package/dist/commands/init/generators/client-common.d.ts +3 -0
  2. package/dist/commands/init/generators/client-common.d.ts.map +1 -0
  3. package/dist/commands/init/generators/client-common.js +17 -0
  4. package/dist/commands/init/generators/client-common.js.map +1 -0
  5. package/dist/commands/init/generators/client.d.ts +3 -0
  6. package/dist/commands/init/generators/client.d.ts.map +1 -0
  7. package/dist/commands/init/generators/client.js +20 -0
  8. package/dist/commands/init/generators/client.js.map +1 -0
  9. package/dist/commands/init/generators/common.d.ts +3 -0
  10. package/dist/commands/init/generators/common.d.ts.map +1 -0
  11. package/dist/commands/init/generators/common.js +14 -0
  12. package/dist/commands/init/generators/common.js.map +1 -0
  13. package/dist/commands/init/generators/root.d.ts +3 -0
  14. package/dist/commands/init/generators/root.d.ts.map +1 -0
  15. package/dist/commands/init/generators/root.js +28 -0
  16. package/dist/commands/init/generators/root.js.map +1 -0
  17. package/dist/commands/init/generators/server.d.ts +3 -0
  18. package/dist/commands/init/generators/server.d.ts.map +1 -0
  19. package/dist/commands/init/generators/server.js +11 -0
  20. package/dist/commands/init/generators/server.js.map +1 -0
  21. package/dist/commands/init/init.d.ts +5 -0
  22. package/dist/commands/init/init.d.ts.map +1 -0
  23. package/dist/commands/init/init.js +44 -0
  24. package/dist/commands/init/init.js.map +1 -0
  25. package/dist/commands/init/normalize.d.ts +3 -0
  26. package/dist/commands/init/normalize.d.ts.map +1 -0
  27. package/dist/commands/init/normalize.js +42 -0
  28. package/dist/commands/init/normalize.js.map +1 -0
  29. package/dist/commands/init/prompts.d.ts +3 -0
  30. package/dist/commands/init/prompts.d.ts.map +1 -0
  31. package/dist/commands/init/prompts.js +89 -0
  32. package/dist/commands/init/prompts.js.map +1 -0
  33. package/dist/commands/init/render.d.ts +4 -0
  34. package/dist/commands/init/render.d.ts.map +1 -0
  35. package/dist/commands/init/render.js +20 -0
  36. package/dist/commands/init/render.js.map +1 -0
  37. package/dist/commands/init/template-paths.d.ts +2 -0
  38. package/dist/commands/init/template-paths.d.ts.map +1 -0
  39. package/dist/commands/init/template-paths.js +7 -0
  40. package/dist/commands/init/template-paths.js.map +1 -0
  41. package/dist/commands/init/types.d.ts +47 -0
  42. package/dist/commands/init/types.d.ts.map +1 -0
  43. package/dist/commands/init/types.js +2 -0
  44. package/dist/commands/init/types.js.map +1 -0
  45. package/dist/commands/init/validate.d.ts +4 -0
  46. package/dist/commands/init/validate.d.ts.map +1 -0
  47. package/dist/commands/init/validate.js +31 -0
  48. package/dist/commands/init/validate.js.map +1 -0
  49. package/dist/sd-cli-entry.d.ts.map +1 -1
  50. package/dist/sd-cli-entry.js +14 -1
  51. package/dist/sd-cli-entry.js.map +1 -1
  52. package/package.json +5 -4
  53. package/src/commands/init/generators/client-common.ts +29 -0
  54. package/src/commands/init/generators/client.ts +29 -0
  55. package/src/commands/init/generators/common.ts +22 -0
  56. package/src/commands/init/generators/root.ts +32 -0
  57. package/src/commands/init/generators/server.ts +15 -0
  58. package/src/commands/init/init.ts +54 -0
  59. package/src/commands/init/normalize.ts +46 -0
  60. package/src/commands/init/prompts.ts +101 -0
  61. package/src/commands/init/render.ts +25 -0
  62. package/src/commands/init/template-paths.ts +8 -0
  63. package/src/commands/init/templates/client/ngsw-config.json +27 -0
  64. package/src/commands/init/templates/client/package.json.hbs +29 -0
  65. package/src/commands/init/templates/client/src/AppPage.ts.hbs +18 -0
  66. package/src/commands/init/templates/client/src/index.html.hbs +12 -0
  67. package/src/commands/init/templates/client/src/main.ts.hbs +28 -0
  68. package/src/commands/init/templates/client/src/polyfills.ts +2 -0
  69. package/src/commands/init/templates/client/src/routes.ts +3 -0
  70. package/src/commands/init/templates/client/src/styles.scss +1 -0
  71. package/src/commands/init/templates/client/tsconfig.json +20 -0
  72. package/src/commands/init/templates/client-common/package.json.hbs +20 -0
  73. package/src/commands/init/templates/client-common/src/index.ts.hbs +11 -0
  74. package/src/commands/init/templates/client-common/src/providers/AppOrmProvider.ts.hbs +36 -0
  75. package/src/commands/init/templates/client-common/src/providers/AppServiceProvider.ts +27 -0
  76. package/src/commands/init/templates/client-common/tsconfig.json +20 -0
  77. package/src/commands/init/templates/common/package.json.hbs +10 -0
  78. package/src/commands/init/templates/common/src/MainDbContext.ts +4 -0
  79. package/src/commands/init/templates/common/src/index.ts.hbs +5 -0
  80. package/src/commands/init/templates/common/tsconfig.json +8 -0
  81. package/src/commands/init/templates/server/package.json.hbs +23 -0
  82. package/src/commands/init/templates/server/src/main.ts.hbs +25 -0
  83. package/src/commands/init/templates/server/tsconfig.json +8 -0
  84. package/src/commands/init/templates/workspace-root/.prettierignore +1 -0
  85. package/src/commands/init/templates/workspace-root/.prettierrc.yaml +12 -0
  86. package/src/commands/init/templates/workspace-root/eslint.config.ts +4 -0
  87. package/src/commands/init/templates/workspace-root/mise.toml.hbs +7 -0
  88. package/src/commands/init/templates/workspace-root/package.json.hbs +43 -0
  89. package/src/commands/init/templates/workspace-root/pnpm-workspace.yaml +17 -0
  90. package/src/commands/init/templates/workspace-root/sd.config.ts.hbs +63 -0
  91. package/src/commands/init/templates/workspace-root/tsconfig.json.hbs +30 -0
  92. package/src/commands/init/templates/workspace-root/vitest.config.ts.hbs +72 -0
  93. package/src/commands/init/types.ts +51 -0
  94. package/src/commands/init/validate.ts +41 -0
  95. package/src/sd-cli-entry.ts +19 -1
  96. package/tests/init/__snapshots__/render.spec.ts.snap +213 -0
  97. package/tests/init/normalize.spec.ts +114 -0
  98. package/tests/init/render.spec.ts +216 -0
  99. package/tests/init/validate.spec.ts +80 -0
@@ -0,0 +1,36 @@
1
+ import { inject, Injectable } from "@angular/core";
2
+ import { createOrmClientConnector, type OrmClientConnector } from "@simplysm/service-client";
3
+ import { MainDbContext } from "@{{workspaceName}}/common";
4
+ import { AppServiceProvider } from "./AppServiceProvider";
5
+
6
+ @Injectable({ providedIn: "root" })
7
+ export class AppOrmProvider {
8
+ private readonly _appService = inject(AppServiceProvider);
9
+
10
+ private _orm?: OrmClientConnector;
11
+ private get orm(): OrmClientConnector {
12
+ return (this._orm ??= createOrmClientConnector(this._appService.client));
13
+ }
14
+
15
+ connectAsync<R>(callback: (db: MainDbContext) => Promise<R>): Promise<R> {
16
+ return this.orm.connect(
17
+ {
18
+ DbClass: MainDbContext,
19
+ connOpt: { configName: "MAIN" },
20
+ dbContextOpt: { database: "{{workspaceNameUpper}}", schema: "dbo" },
21
+ },
22
+ callback,
23
+ );
24
+ }
25
+
26
+ connectWithoutTransAsync<R>(callback: (db: MainDbContext) => Promise<R>): Promise<R> {
27
+ return this.orm.connectWithoutTransaction(
28
+ {
29
+ DbClass: MainDbContext,
30
+ connOpt: { configName: "MAIN" },
31
+ dbContextOpt: { database: "{{workspaceNameUpper}}", schema: "dbo" },
32
+ },
33
+ callback,
34
+ );
35
+ }
36
+ }
@@ -0,0 +1,27 @@
1
+ import { inject, Injectable } from "@angular/core";
2
+ import { SdServiceClientFactoryProvider } from "@simplysm/angular";
3
+ import { env, num, parseBoolEnv } from "@simplysm/core-common";
4
+
5
+ export const APP_MAIN_SERVICE_KEY = "MAIN";
6
+
7
+ @Injectable({ providedIn: "root" })
8
+ export class AppServiceProvider {
9
+ private readonly _sdServiceClientFactory = inject(SdServiceClientFactoryProvider);
10
+
11
+ get client() {
12
+ return this._sdServiceClientFactory.get(APP_MAIN_SERVICE_KEY);
13
+ }
14
+
15
+ async connectAsync() {
16
+ await this._sdServiceClientFactory.connectAsync(
17
+ APP_MAIN_SERVICE_KEY,
18
+ Boolean(env("SERVER_HOST"))
19
+ ? {
20
+ host: env("SERVER_HOST"),
21
+ port: num.parseInt(env("SERVER_PORT")),
22
+ ssl: parseBoolEnv(env("SERVER_SSL")),
23
+ }
24
+ : {},
25
+ );
26
+ }
27
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
5
+ "outDir": "./dist",
6
+ "typeRoots": ["./node_modules/@types"],
7
+ "customConditions": ["browser"]
8
+ },
9
+ "angularCompilerOptions": {
10
+ "strictTemplates": true,
11
+ "strictInjectionParameters": true,
12
+ "strictInputAccessModifiers": true,
13
+ "strictStandalone": true,
14
+ "typeCheckHostBindings": true,
15
+ "enableI18nLegacyMessageIdFormat": false,
16
+ "extendedDiagnostics": {
17
+ "defaultCategory": "error"
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@{{workspaceName}}/common",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "private": true,
6
+ "dependencies": {
7
+ "@simplysm/core-common": "^14.0.0"{{#if hasDb}},
8
+ "@simplysm/orm-common": "^14.0.0"{{/if}}
9
+ }
10
+ }
@@ -0,0 +1,4 @@
1
+ import { DbContext } from "@simplysm/orm-common";
2
+
3
+ export class MainDbContext extends DbContext {
4
+ }
@@ -0,0 +1,5 @@
1
+ {{#if hasDb}}
2
+ export * from "./MainDbContext";
3
+ {{else}}
4
+ export {};
5
+ {{/if}}
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext"],
5
+ "outDir": "./dist",
6
+ "typeRoots": ["./node_modules/@types"]
7
+ }
8
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@{{workspaceName}}/server",
3
+ "version": "1.0.0",
4
+ "description": "{{description}} - Server",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "private": true,
8
+ "dependencies": {
9
+ "@{{workspaceName}}/common": "workspace:*",
10
+ "@simplysm/core-common": "^14.0.0",
11
+ "@simplysm/core-node": "^14.0.0",
12
+ "@simplysm/service-common": "^14.0.0",
13
+ "@simplysm/service-server": "^14.0.0"{{#if hasDb}},
14
+ "@simplysm/orm-common": "^14.0.0",
15
+ "@simplysm/orm-node": "^14.0.0"{{#if isMysql}},
16
+ "mysql2": "^3.22.0"{{/if}}{{#if isPostgres}},
17
+ "pg": "^8.13.0"{{/if}}{{#if isMssql}},
18
+ "mssql": "^11.0.0"{{/if}}{{/if}}
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.19.0"
22
+ }
23
+ }
@@ -0,0 +1,25 @@
1
+ import path from "node:path";
2
+ import { env, num, parseBoolEnv } from "@simplysm/core-common";
3
+ import { setupConsola } from "@simplysm/core-node";
4
+ import { createServiceServer, getConfig{{#if hasDb}}, OrmService{{/if}} } from "@simplysm/service-server";
5
+
6
+ setupConsola();
7
+
8
+ const rootConfig = await getConfig<Record<string, unknown>>(
9
+ path.resolve(import.meta.dirname, ".config.json"),
10
+ );
11
+ const authConfig = rootConfig?.["auth"] as { jwtSecret: string } | undefined;
12
+ if (authConfig?.jwtSecret == null) {
13
+ throw new Error("auth.jwtSecret 설정을 찾을 수 없습니다. .config.json을 확인하세요.");
14
+ }
15
+
16
+ export const server = createServiceServer({
17
+ rootPath: import.meta.dirname,
18
+ services: [{{#if hasDb}}OrmService{{/if}}],
19
+ port: num.parseInt(env("SERVER_PORT"))!,
20
+ auth: { jwtSecret: authConfig.jwtSecret },
21
+ });
22
+
23
+ if (!parseBoolEnv(env("DEV"))) {
24
+ await server.listen();
25
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "lib": ["ESNext"],
5
+ "outDir": "./dist",
6
+ "typeRoots": ["./node_modules/@types"]
7
+ }
8
+ }
@@ -0,0 +1,12 @@
1
+ printWidth: 100
2
+ tabWidth: 2
3
+ useTabs: false
4
+ semi: true
5
+ quoteProps: consistent
6
+ trailingComma: all
7
+ bracketSpacing: true
8
+ bracketSameLine: false
9
+ arrowParens: always
10
+ endOfLine: lf
11
+ htmlWhitespaceSensitivity: ignore
12
+ embeddedLanguageFormatting: auto
@@ -0,0 +1,4 @@
1
+ import type { Linter } from "eslint";
2
+ import simplysmRecommended from "@simplysm/lint/eslint-recommended";
3
+
4
+ export default [...simplysmRecommended] as Linter.Config[];
@@ -0,0 +1,7 @@
1
+ [tools]
2
+ node = "20"
3
+ pnpm = "11"
4
+ python = "3"
5
+ {{#if hasMobile}}
6
+ java = "temurin-21"
7
+ {{/if}}
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "{{workspaceName}}",
3
+ "version": "1.0.0",
4
+ "description": "{{description}}",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "private": true,
8
+ "workspaces": ["packages/*", "tests/*"],
9
+ "scripts": {
10
+ "dev": "sd-cli dev",
11
+ "build": "sd-cli build",
12
+ "pub": "sd-cli publish",
13
+ "pub:no-build": "sd-cli publish --no-build",
14
+ "---": "",
15
+ "check": "sd-cli check",
16
+ "typecheck": "sd-cli check --type typecheck",
17
+ "lint": "sd-cli check --type lint",
18
+ "test": "vitest run --reporter=dot --silent=passed-only",
19
+ "----": "",
20
+ {{#if hasMobile}}
21
+ "run-device": "sd-cli device -t {{firstMobileClientName}}",
22
+ "-----": "",
23
+ {{/if}}
24
+ "replace-deps": "sd-cli replace-deps",
25
+ "postinstall": "npx -y skills add angular/skills --skill angular-developer -a claude-code -y && playwright-cli install --skills && playwright install chromium"
26
+ },
27
+ "devDependencies": {
28
+ "@playwright/cli": "^0.1.11",
29
+ "@simplysm/lint": "^14.0.0",
30
+ "@simplysm/sd-claude": "^14.0.0",
31
+ "@simplysm/sd-cli": "^14.0.0",
32
+ "@types/node": "^20.19.0",
33
+ "@vitest/browser-playwright": "^4.1.0",
34
+ "@vitest/coverage-v8": "^4.1.0",
35
+ "eslint": "^9.39.0",
36
+ "jiti": "^2.7.0",
37
+ "prettier": "^3.8.0",
38
+ "typescript": "^5.9.0",
39
+ "vite": "^7.3.0",
40
+ "vite-tsconfig-paths": "^6.1.0",
41
+ "vitest": "^4.1.0"
42
+ }
43
+ }
@@ -0,0 +1,17 @@
1
+ packages:
2
+ - packages/*
3
+ - tests/*
4
+ allowBuilds:
5
+ '@parcel/watcher': true
6
+ '@simplysm/sd-claude': true
7
+ '@simplysm/sd-codex': true
8
+ bufferutil: true
9
+ core-js: true
10
+ cpu-features: true
11
+ esbuild: true
12
+ lmdb: true
13
+ msgpackr-extract: true
14
+ sharp: true
15
+ ssh2: true
16
+ unrs-resolver: true
17
+ utf-8-validate: true
@@ -0,0 +1,63 @@
1
+ import type { SdConfigFn } from "@simplysm/sd-cli";
2
+
3
+ const config: SdConfigFn = () => ({
4
+ packages: {
5
+ {{#if hasServer}}
6
+ "server": {
7
+ target: "server",
8
+ env: {
9
+ SERVER_PORT: "{{serverPort}}",
10
+ },
11
+ configs: {
12
+ {{#if hasDb}}
13
+ orm: {
14
+ MAIN: {
15
+ dialect: "{{dbDialect}}",
16
+ host: "localhost",
17
+ port: {{dbPort}},
18
+ username: "root",
19
+ password: "1234",
20
+ database: "{{workspaceNameUpper}}",
21
+ defaultIsolationLevel: "READ_UNCOMMITTED",
22
+ },
23
+ },
24
+ {{/if}}
25
+ auth: {
26
+ jwtSecret: "{{jwtSecret}}",
27
+ },
28
+ },
29
+ },
30
+ {{/if}}
31
+ {{#if hasCommon}}
32
+ "common": {
33
+ target: "neutral",
34
+ },
35
+ {{/if}}
36
+ {{#if hasClientCommon}}
37
+ "client-common": {
38
+ target: "browser",
39
+ },
40
+ {{/if}}
41
+ {{#each clients}}
42
+ "{{name}}": {
43
+ target: "client",
44
+ {{#if ../hasServer}}
45
+ server: "server",
46
+ {{/if}}
47
+ {{#if isMobile}}
48
+ capacitor: {
49
+ appId: "{{../mobileAppId}}",
50
+ appName: "{{../description}}",
51
+ plugins: {},
52
+ icon: "res/icon.png",
53
+ platform: {
54
+ android: {},
55
+ },
56
+ },
57
+ {{/if}}
58
+ },
59
+ {{/each}}
60
+ },
61
+ });
62
+
63
+ export default config;
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "lib": ["ESNext"],
6
+ "moduleResolution": "bundler",
7
+ "typeRoots": ["./node_modules/@types"],
8
+ "strict": true,
9
+ "noImplicitOverride": true,
10
+ "noImplicitReturns": true,
11
+ "noImplicitAny": true,
12
+ "noFallthroughCasesInSwitch": true,
13
+ "useUnknownInCatchVariables": true,
14
+ "noPropertyAccessFromIndexSignature": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "useDefineForClassFields": true,
17
+ "esModuleInterop": true,
18
+ "verbatimModuleSyntax": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+ "sourceMap": true,
22
+ "inlineSourceMap": false,
23
+ "skipLibCheck": true,
24
+ "importHelpers": true,
25
+ "baseUrl": ".",
26
+ "paths": {
27
+ "@{{workspaceName}}/*": ["packages/*/src/index.ts"]
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,72 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import { playwright } from "@vitest/browser-playwright";
3
+ import { sdAngularPlugin } from "@simplysm/sd-cli";
4
+ import tsconfigPaths from "vite-tsconfig-paths";
5
+
6
+ process.env["DEV"] = "true";
7
+ process.env["VER"] = "1.0.0-test";
8
+
9
+ export default defineConfig({
10
+ plugins: [tsconfigPaths()],
11
+ define: {
12
+ "import.meta.env.DEV": JSON.stringify("true"),
13
+ "import.meta.env.VER": JSON.stringify("1.0.0-test"),
14
+ },
15
+ test: {
16
+ testTimeout: 30000,
17
+ coverage: { provider: "v8", reportsDirectory: "./.coverage" },
18
+ projects: [
19
+ {{#if hasServer}}
20
+ {
21
+ extends: true,
22
+ test: {
23
+ name: "node",
24
+ environment: "node",
25
+ include: [
26
+ "packages/server/tests/**/*.spec.{ts,js,mjs,cjs}",
27
+ {{#if hasCommon}}
28
+ "packages/common/tests/**/*.spec.{ts,js,mjs,cjs}",
29
+ {{/if}}
30
+ ],
31
+ },
32
+ },
33
+ {{/if}}
34
+ {{#if hasClientCommon}}
35
+ {
36
+ extends: true,
37
+ plugins: [sdAngularPlugin({ pkg: "client-common" })],
38
+ test: {
39
+ name: "client-common",
40
+ setupFiles: ["packages/client-common/tests/vitest.setup.ts"],
41
+ include: ["packages/client-common/tests/**/*.spec.{ts,js,mjs,cjs}"],
42
+ browser: {
43
+ provider: playwright(),
44
+ enabled: true,
45
+ headless: true,
46
+ screenshotFailures: false,
47
+ instances: [{ browser: "chromium", viewport: { width: 1920, height: 1080 } }],
48
+ },
49
+ },
50
+ },
51
+ {{/if}}
52
+ {{#each clients}}
53
+ {
54
+ extends: true,
55
+ plugins: [sdAngularPlugin({ pkg: "{{name}}" })],
56
+ test: {
57
+ name: "{{name}}",
58
+ setupFiles: ["packages/{{name}}/tests/vitest.setup.ts"],
59
+ include: ["packages/{{name}}/tests/**/*.spec.{ts,js,mjs,cjs}"],
60
+ browser: {
61
+ provider: playwright(),
62
+ enabled: true,
63
+ headless: true,
64
+ screenshotFailures: false,
65
+ instances: [{ browser: "chromium", viewport: { width: 1920, height: 1080 } }],
66
+ },
67
+ },
68
+ },
69
+ {{/each}}
70
+ ],
71
+ },
72
+ });
@@ -0,0 +1,51 @@
1
+ export type ClientType = "web" | "mobile";
2
+ export type DbDialect = "mysql" | "postgres" | "mssql";
3
+
4
+ export interface ClientInputSpec {
5
+ name: string;
6
+ type: ClientType;
7
+ hasRouter: boolean;
8
+ }
9
+
10
+ export interface InitInput {
11
+ workspaceName: string;
12
+ description: string;
13
+ hasServer: boolean;
14
+ clients: ClientInputSpec[];
15
+ hasDb: boolean;
16
+ dbDialect?: DbDialect;
17
+ mobileAppId?: string;
18
+ serverPort?: number;
19
+ }
20
+
21
+ export interface ClientSpec {
22
+ name: string;
23
+ type: ClientType;
24
+ hasRouter: boolean;
25
+ isMobile: boolean;
26
+ }
27
+
28
+ export interface NormalizedInput {
29
+ workspaceName: string;
30
+ workspaceNameUpper: string;
31
+ description: string;
32
+ hasServer: boolean;
33
+ hasDb: boolean;
34
+ dbDialect?: DbDialect;
35
+ dbPort: number;
36
+ isMysql: boolean;
37
+ isPostgres: boolean;
38
+ isMssql: boolean;
39
+ serverPort: number;
40
+ mobileAppId?: string;
41
+ firstMobileClientName?: string;
42
+ clients: ClientSpec[];
43
+ hasCommon: boolean;
44
+ hasClientCommon: boolean;
45
+ hasMobile: boolean;
46
+ hasAnyClient: boolean;
47
+ }
48
+
49
+ export interface RenderData extends NormalizedInput {
50
+ jwtSecret: string;
51
+ }
@@ -0,0 +1,41 @@
1
+ import { fsx } from "@simplysm/core-node";
2
+ import type { InitInput } from "./types";
3
+
4
+ const KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
5
+
6
+ export async function validateBeforePrompt(cwd: string): Promise<void> {
7
+ if (!(await fsx.exists(cwd))) return;
8
+ const children = await fsx.readdir(cwd);
9
+ const significant = children.filter((n) => n !== ".git");
10
+ if (significant.length > 0) {
11
+ throw new Error(
12
+ `작업 디렉토리가 비어있지 않습니다 (감지된 항목: ${significant.join(", ")}). 빈 디렉토리에서 다시 실행하세요.`,
13
+ );
14
+ }
15
+ }
16
+
17
+ export function validateInput(input: InitInput): void {
18
+ if (!input.hasServer && input.clients.length === 0) {
19
+ throw new Error("server 도 client 도 없는 워크스페이스는 만들 수 없습니다.");
20
+ }
21
+
22
+ if (!KEBAB_CASE_RE.test(input.workspaceName)) {
23
+ throw new Error(
24
+ `워크스페이스 이름은 영문 kebab-case 여야 합니다 (예: my-workspace). 입력값: "${input.workspaceName}"`,
25
+ );
26
+ }
27
+
28
+ const clientNames = new Set<string>();
29
+ for (const c of input.clients) {
30
+ if (!KEBAB_CASE_RE.test(c.name)) {
31
+ throw new Error(
32
+ `client 이름은 영문 kebab-case 여야 합니다. 입력값: "${c.name}"`,
33
+ );
34
+ }
35
+ const normalized = c.name.startsWith("client-") ? c.name : `client-${c.name}`;
36
+ if (clientNames.has(normalized)) {
37
+ throw new Error(`client 이름이 중복됩니다: "${normalized}"`);
38
+ }
39
+ clientNames.add(normalized);
40
+ }
41
+ }
@@ -9,6 +9,7 @@ import { type CheckType, runCheck } from "./commands/check";
9
9
  import { runWatch } from "./commands/watch";
10
10
  import { runDev } from "./commands/dev";
11
11
  import { runBuild } from "./commands/build";
12
+ import { runInit } from "./commands/init/init";
12
13
  import { runPublish } from "./commands/publish/publish-command";
13
14
  import { runReplaceDeps } from "./commands/replace-deps";
14
15
  import path from "path";
@@ -23,7 +24,16 @@ const logger = createLazyLogger("sd:cli:entry");
23
24
  Error.stackTraceLimit = Infinity;
24
25
  EventEmitter.defaultMaxListeners = 100;
25
26
 
26
- const COMMAND_NAMES = ["check", "watch", "dev", "device", "build", "publish", "replace-deps"];
27
+ const COMMAND_NAMES = [
28
+ "check",
29
+ "watch",
30
+ "dev",
31
+ "device",
32
+ "build",
33
+ "publish",
34
+ "replace-deps",
35
+ "init",
36
+ ];
27
37
 
28
38
  async function collectYargsHelp(argv: string[]): Promise<string> {
29
39
  const lines: string[] = [];
@@ -322,6 +332,14 @@ export function createCliParser(argv: string[]): Argv {
322
332
  });
323
333
  },
324
334
  )
335
+ .command(
336
+ "init",
337
+ "Bootstrap a new SI workspace via interactive prompts",
338
+ (cmd) => cmd.version(false).hide("help"),
339
+ async () => {
340
+ await runInit({ cwd: process.cwd() });
341
+ },
342
+ )
325
343
  .demandCommand(1, "Please specify a command.")
326
344
  .strict()
327
345
  .fail((msg, err) => {