@simplysm/sd-cli 14.0.89 → 14.0.90
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.
- package/dist/commands/init/generators/client-common.d.ts.map +1 -1
- package/dist/commands/init/generators/client-common.js +6 -2
- package/dist/commands/init/generators/client-common.js.map +1 -1
- package/dist/commands/init/generators/client.d.ts.map +1 -1
- package/dist/commands/init/generators/client.js +13 -2
- package/dist/commands/init/generators/client.js.map +1 -1
- package/dist/commands/init/generators/common.d.ts.map +1 -1
- package/dist/commands/init/generators/common.js +16 -1
- package/dist/commands/init/generators/common.js.map +1 -1
- package/dist/commands/init/generators/server.d.ts.map +1 -1
- package/dist/commands/init/generators/server.js +9 -0
- package/dist/commands/init/generators/server.js.map +1 -1
- package/dist/commands/init/init.d.ts.map +1 -1
- package/dist/commands/init/init.js +11 -2
- package/dist/commands/init/init.js.map +1 -1
- package/dist/commands/init/normalize.d.ts.map +1 -1
- package/dist/commands/init/normalize.js +40 -4
- package/dist/commands/init/normalize.js.map +1 -1
- package/dist/commands/init/prompts.d.ts +1 -1
- package/dist/commands/init/prompts.d.ts.map +1 -1
- package/dist/commands/init/prompts.js +34 -3
- package/dist/commands/init/prompts.js.map +1 -1
- package/dist/commands/init/types.d.ts +16 -1
- package/dist/commands/init/types.d.ts.map +1 -1
- package/dist/commands/init/validate.d.ts.map +1 -1
- package/dist/commands/init/validate.js +3 -0
- package/dist/commands/init/validate.js.map +1 -1
- package/package.json +4 -4
- package/src/commands/init/generators/client-common.ts +18 -5
- package/src/commands/init/generators/client.ts +41 -2
- package/src/commands/init/generators/common.ts +56 -2
- package/src/commands/init/generators/server.ts +30 -0
- package/src/commands/init/init.ts +12 -2
- package/src/commands/init/normalize.ts +49 -4
- package/src/commands/init/prompts.ts +34 -3
- package/src/commands/init/templates/client/login-public/assets/logo-landscape.png +0 -0
- package/src/commands/init/templates/client/login-public/assets/logo.png +0 -0
- package/src/commands/init/templates/client/package.json.hbs +3 -2
- package/src/commands/init/templates/client/src/app/home/home.view.ts.hbs +137 -0
- package/src/commands/init/templates/client/src/app/home/main/main.view.ts.hbs +16 -0
- package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +265 -0
- package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +144 -0
- package/src/commands/init/templates/client/src/app.root.ts.hbs +64 -0
- package/src/commands/init/templates/client/src/index.html.hbs +75 -1
- package/src/commands/init/templates/client/src/main.ts.hbs +147 -7
- package/src/commands/init/templates/client/src/modals/dev.modal.ts.hbs +63 -0
- package/src/commands/init/templates/client/src/routes.ts.hbs +29 -0
- package/src/commands/init/templates/client-common/package.json.hbs +1 -0
- package/src/commands/init/templates/client-common/src/index.ts.hbs +6 -2
- package/src/commands/init/templates/client-common/src/providers/app-auth.provider.ts.hbs +90 -0
- package/src/commands/init/templates/client-common/src/providers/{AppOrmProvider.ts.hbs → app-orm.provider.ts.hbs} +5 -11
- package/src/commands/init/templates/client-common/src/providers/app-service.provider.ts.hbs +68 -0
- package/src/commands/init/templates/client-common/src/providers/app-shared-data.provider.ts.hbs +100 -0
- package/src/commands/init/templates/common/package.json.hbs +2 -1
- package/src/commands/init/templates/common/src/app-structure.ts.hbs +26 -0
- package/src/commands/init/templates/common/src/auth-info-changed.event.ts.hbs +3 -0
- package/src/commands/init/templates/common/src/db/db-context.ts.hbs +20 -0
- package/src/commands/init/templates/common/src/db/system-data-log.ext.ts.hbs +138 -0
- package/src/commands/init/templates/common/src/db/tables/master/user-config.ts.hbs +15 -0
- package/src/commands/init/templates/common/src/db/tables/master/user.ts.hbs +20 -0
- package/src/commands/init/templates/common/src/db/tables/system/role-permission.ts.hbs +16 -0
- package/src/commands/init/templates/common/src/db/tables/system/role.ts.hbs +16 -0
- package/src/commands/init/templates/common/src/db/tables/system/system-data-log.ts.hbs +23 -0
- package/src/commands/init/templates/common/src/db/tables/system/system-log.ts.hbs +21 -0
- package/src/commands/init/templates/common/src/index.ts.hbs +14 -1
- package/src/commands/init/templates/server/package.json.hbs +7 -3
- package/src/commands/init/templates/server/public-dev//354/264/210/352/270/260/355/231/224.xlsx +0 -0
- package/src/commands/init/templates/server/src/index.ts.hbs +4 -0
- package/src/commands/init/templates/server/src/main.ts.hbs +11 -1
- package/src/commands/init/templates/server/src/services/auth.service.ts.hbs +284 -0
- package/src/commands/init/templates/server/src/services/dev.service.ts.hbs +112 -0
- package/src/commands/init/templates/server/src/utils/orm.utils.ts.hbs +8 -0
- package/src/commands/init/templates/workspace-root/sd.config.ts.hbs +2 -2
- package/src/commands/init/types.ts +16 -1
- package/src/commands/init/validate.ts +6 -0
- package/tests/init/__snapshots__/render.spec.ts.snap +36 -9
- package/tests/init/normalize.spec.ts +95 -1
- package/tests/init/render.spec.ts +951 -10
- package/src/commands/init/templates/client/src/AppPage.ts.hbs +0 -18
- package/src/commands/init/templates/client/src/routes.ts +0 -3
- package/src/commands/init/templates/client-common/src/providers/AppServiceProvider.ts +0 -27
- package/src/commands/init/templates/common/src/DbContext.ts.hbs +0 -4
|
@@ -24,10 +24,49 @@ export async function generateClient(
|
|
|
24
24
|
|
|
25
25
|
await renderToFile(path.join(TPL, "package.json.hbs"), path.join(out, "package.json"), data);
|
|
26
26
|
await renderToFile(path.join(TPL, "src/main.ts.hbs"), path.join(out, "src/main.ts"), data);
|
|
27
|
-
await renderToFile(path.join(TPL, "src/
|
|
27
|
+
await renderToFile(path.join(TPL, "src/app.root.ts.hbs"), path.join(out, "src/app.root.ts"), data);
|
|
28
28
|
await renderToFile(path.join(TPL, "src/index.html.hbs"), path.join(out, "src/index.html"), data);
|
|
29
29
|
|
|
30
|
+
if (data.hasDb) {
|
|
31
|
+
await renderToFile(
|
|
32
|
+
path.join(TPL, "src/modals/dev.modal.ts.hbs"),
|
|
33
|
+
path.join(out, "src/modals/dev.modal.ts"),
|
|
34
|
+
data,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
if (client.hasRouter) {
|
|
31
|
-
await
|
|
39
|
+
await renderToFile(path.join(TPL, "src/routes.ts.hbs"), path.join(out, "src/routes.ts"), data);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (data.hasAuth && client.hasRouter) {
|
|
43
|
+
await renderToFile(
|
|
44
|
+
path.join(TPL, "src/app/login/login.view.ts.hbs"),
|
|
45
|
+
path.join(out, "src/app/login/login.view.ts"),
|
|
46
|
+
data,
|
|
47
|
+
);
|
|
48
|
+
await renderToFile(
|
|
49
|
+
path.join(TPL, "src/app/home/home.view.ts.hbs"),
|
|
50
|
+
path.join(out, "src/app/home/home.view.ts"),
|
|
51
|
+
data,
|
|
52
|
+
);
|
|
53
|
+
await renderToFile(
|
|
54
|
+
path.join(TPL, "src/app/home/main/main.view.ts.hbs"),
|
|
55
|
+
path.join(out, "src/app/home/main/main.view.ts"),
|
|
56
|
+
data,
|
|
57
|
+
);
|
|
58
|
+
await renderToFile(
|
|
59
|
+
path.join(TPL, "src/app/home/my-info/my-info.detail.ts.hbs"),
|
|
60
|
+
path.join(out, "src/app/home/my-info/my-info.detail.ts"),
|
|
61
|
+
data,
|
|
62
|
+
);
|
|
63
|
+
await copyFixed(
|
|
64
|
+
path.join(TPL, "login-public/assets/logo.png"),
|
|
65
|
+
path.join(out, "public/assets/logo.png"),
|
|
66
|
+
);
|
|
67
|
+
await copyFixed(
|
|
68
|
+
path.join(TPL, "login-public/assets/logo-landscape.png"),
|
|
69
|
+
path.join(out, "public/assets/logo-landscape.png"),
|
|
70
|
+
);
|
|
32
71
|
}
|
|
33
72
|
}
|
|
@@ -13,11 +13,65 @@ export async function generateCommon(cwd: string, data: RenderData): Promise<voi
|
|
|
13
13
|
await renderToFile(path.join(TPL, "package.json.hbs"), path.join(out, "package.json"), data);
|
|
14
14
|
await renderToFile(path.join(TPL, "src/index.ts.hbs"), path.join(out, "src/index.ts"), data);
|
|
15
15
|
|
|
16
|
+
if (data.hasAuth) {
|
|
17
|
+
await renderToFile(
|
|
18
|
+
path.join(TPL, "src/app-structure.ts.hbs"),
|
|
19
|
+
path.join(out, "src/app-structure.ts"),
|
|
20
|
+
data,
|
|
21
|
+
);
|
|
22
|
+
await renderToFile(
|
|
23
|
+
path.join(TPL, "src/auth-info-changed.event.ts.hbs"),
|
|
24
|
+
path.join(out, "src/auth-info-changed.event.ts"),
|
|
25
|
+
data,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
if (data.hasDb) {
|
|
30
|
+
const dbTpl = path.join(TPL, "src/db");
|
|
31
|
+
const dbOut = path.join(out, "src", data.dbFolderName);
|
|
32
|
+
|
|
17
33
|
await renderToFile(
|
|
18
|
-
path.join(
|
|
19
|
-
path.join(
|
|
34
|
+
path.join(dbTpl, "db-context.ts.hbs"),
|
|
35
|
+
path.join(dbOut, `${data.dbContextFileName}.ts`),
|
|
20
36
|
data,
|
|
21
37
|
);
|
|
38
|
+
await renderToFile(
|
|
39
|
+
path.join(dbTpl, "system-data-log.ext.ts.hbs"),
|
|
40
|
+
path.join(dbOut, "system-data-log.ext.ts"),
|
|
41
|
+
data,
|
|
42
|
+
);
|
|
43
|
+
await renderToFile(
|
|
44
|
+
path.join(dbTpl, "tables/system/system-data-log.ts.hbs"),
|
|
45
|
+
path.join(dbOut, "tables/system/system-data-log.ts"),
|
|
46
|
+
data,
|
|
47
|
+
);
|
|
48
|
+
await renderToFile(
|
|
49
|
+
path.join(dbTpl, "tables/system/system-log.ts.hbs"),
|
|
50
|
+
path.join(dbOut, "tables/system/system-log.ts"),
|
|
51
|
+
data,
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (data.hasAuth) {
|
|
55
|
+
await renderToFile(
|
|
56
|
+
path.join(dbTpl, "tables/system/role.ts.hbs"),
|
|
57
|
+
path.join(dbOut, "tables/system/role.ts"),
|
|
58
|
+
data,
|
|
59
|
+
);
|
|
60
|
+
await renderToFile(
|
|
61
|
+
path.join(dbTpl, "tables/system/role-permission.ts.hbs"),
|
|
62
|
+
path.join(dbOut, "tables/system/role-permission.ts"),
|
|
63
|
+
data,
|
|
64
|
+
);
|
|
65
|
+
await renderToFile(
|
|
66
|
+
path.join(dbTpl, "tables/master/user.ts.hbs"),
|
|
67
|
+
path.join(dbOut, "tables/master", `${data.userEntityKebab}.ts`),
|
|
68
|
+
data,
|
|
69
|
+
);
|
|
70
|
+
await renderToFile(
|
|
71
|
+
path.join(dbTpl, "tables/master/user-config.ts.hbs"),
|
|
72
|
+
path.join(dbOut, "tables/master", `${data.userEntityKebab}-config.ts`),
|
|
73
|
+
data,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
22
76
|
}
|
|
23
77
|
}
|
|
@@ -12,4 +12,34 @@ export async function generateServer(cwd: string, data: RenderData): Promise<voi
|
|
|
12
12
|
|
|
13
13
|
await renderToFile(path.join(TPL, "package.json.hbs"), path.join(out, "package.json"), data);
|
|
14
14
|
await renderToFile(path.join(TPL, "src/main.ts.hbs"), path.join(out, "src/main.ts"), data);
|
|
15
|
+
|
|
16
|
+
if (data.hasDb) {
|
|
17
|
+
await renderToFile(
|
|
18
|
+
path.join(TPL, "src/utils/orm.utils.ts.hbs"),
|
|
19
|
+
path.join(out, "src/utils/orm.utils.ts"),
|
|
20
|
+
data,
|
|
21
|
+
);
|
|
22
|
+
await renderToFile(
|
|
23
|
+
path.join(TPL, "src/services/dev.service.ts.hbs"),
|
|
24
|
+
path.join(out, "src/services/dev.service.ts"),
|
|
25
|
+
data,
|
|
26
|
+
);
|
|
27
|
+
await renderToFile(
|
|
28
|
+
path.join(TPL, "src/index.ts.hbs"),
|
|
29
|
+
path.join(out, "src/index.ts"),
|
|
30
|
+
data,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (data.hasAuth) {
|
|
35
|
+
await renderToFile(
|
|
36
|
+
path.join(TPL, "src/services/auth.service.ts.hbs"),
|
|
37
|
+
path.join(out, "src/services/auth.service.ts"),
|
|
38
|
+
data,
|
|
39
|
+
);
|
|
40
|
+
await copyFixed(
|
|
41
|
+
path.join(TPL, "public-dev/초기화.xlsx"),
|
|
42
|
+
path.join(out, "public-dev/초기화.xlsx"),
|
|
43
|
+
);
|
|
44
|
+
}
|
|
15
45
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import crypto from "crypto";
|
|
2
|
+
import path from "path";
|
|
2
3
|
import { createLogger } from "@simplysm/core-common";
|
|
3
4
|
import { generateClient } from "./generators/client";
|
|
4
5
|
import { generateClientCommon } from "./generators/client-common";
|
|
@@ -9,6 +10,7 @@ import { normalize } from "./normalize";
|
|
|
9
10
|
import { promptInit } from "./prompts";
|
|
10
11
|
import type { RenderData } from "./types";
|
|
11
12
|
import { validateBeforePrompt, validateInput } from "./validate";
|
|
13
|
+
import { shellSpawn } from "../../utils/shell-spawn";
|
|
12
14
|
|
|
13
15
|
const logger = createLogger("sd:cli:init");
|
|
14
16
|
|
|
@@ -19,7 +21,7 @@ export interface InitOptions {
|
|
|
19
21
|
export async function runInit(opts: InitOptions): Promise<void> {
|
|
20
22
|
await validateBeforePrompt(opts.cwd);
|
|
21
23
|
|
|
22
|
-
const input = await promptInit();
|
|
24
|
+
const input = await promptInit(path.basename(opts.cwd));
|
|
23
25
|
validateInput(input);
|
|
24
26
|
|
|
25
27
|
const normalized = normalize(input);
|
|
@@ -40,7 +42,15 @@ export async function runInit(opts: InitOptions): Promise<void> {
|
|
|
40
42
|
|
|
41
43
|
logger.info("워크스페이스 부트스트랩 완료.");
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
logger.info("의존성 설치 및 도구 준비 중...");
|
|
46
|
+
await shellSpawn("mise", ["trust"], { cwd: opts.cwd, stdio: "inherit" });
|
|
47
|
+
await shellSpawn("mise", ["install"], { cwd: opts.cwd, stdio: "inherit" });
|
|
48
|
+
await shellSpawn("pnpm", ["install"], { cwd: opts.cwd, stdio: "inherit" });
|
|
49
|
+
await shellSpawn("pnpm", ["approve-builds", "--all"], { cwd: opts.cwd, stdio: "inherit" });
|
|
50
|
+
await shellSpawn("pnpm", ["up", "-r"], { cwd: opts.cwd, stdio: "inherit" });
|
|
51
|
+
logger.info("의존성 설치 및 도구 준비 완료.");
|
|
52
|
+
|
|
53
|
+
const steps: string[] = [];
|
|
44
54
|
if (data.hasDb) {
|
|
45
55
|
steps.push("sd.config.ts 의 configs.orm.MAIN 접속 정보 (host/port/user/password/database) 를 실제 값으로 수정");
|
|
46
56
|
}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
import { str } from "@simplysm/core-common";
|
|
1
2
|
import type { ClientSpec, InitInput, NormalizedInput } from "./types";
|
|
2
3
|
|
|
3
4
|
const DB_PORTS: Record<NonNullable<InitInput["dbDialect"]>, number> = {
|
|
4
5
|
mysql: 3306,
|
|
5
|
-
|
|
6
|
+
postgresql: 5432,
|
|
6
7
|
mssql: 1433,
|
|
7
8
|
};
|
|
8
9
|
|
|
10
|
+
const DB_CREDENTIALS: Record<
|
|
11
|
+
NonNullable<InitInput["dbDialect"]>,
|
|
12
|
+
{ username: string; password: string }
|
|
13
|
+
> = {
|
|
14
|
+
mysql: { username: "root", password: "1234" },
|
|
15
|
+
postgresql: { username: "postgres", password: "postgres" },
|
|
16
|
+
mssql: { username: "sa", password: "1234" },
|
|
17
|
+
};
|
|
18
|
+
|
|
9
19
|
function toDbContextBase(name: string): string {
|
|
10
20
|
return name.replace(/DbContext$/i, "");
|
|
11
21
|
}
|
|
@@ -20,19 +30,44 @@ function toDbContextNameUpper(name: string): string {
|
|
|
20
30
|
return toDbContextBase(name).toUpperCase();
|
|
21
31
|
}
|
|
22
32
|
|
|
33
|
+
function toDbContextKebab(name: string): string {
|
|
34
|
+
return toDbContextBase(name)
|
|
35
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
36
|
+
.replace(/[\s_]+/g, "-")
|
|
37
|
+
.toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function toDbContextFileName(name: string): string {
|
|
41
|
+
return `${toDbContextKebab(name)}.db-context`;
|
|
42
|
+
}
|
|
43
|
+
|
|
23
44
|
export function normalize(input: InitInput): NormalizedInput {
|
|
45
|
+
const hasDb = input.hasServer && input.hasDb;
|
|
46
|
+
const hasAuth = hasDb && (input.hasAuth ?? false);
|
|
47
|
+
|
|
24
48
|
const clients: ClientSpec[] = input.clients.map((c) => {
|
|
25
49
|
const name = c.name.startsWith("client-") ? c.name : `client-${c.name}`;
|
|
26
50
|
const isMobile = c.type === "mobile";
|
|
51
|
+
const hasRouter = isMobile ? false : c.hasRouter;
|
|
52
|
+
const baseName = name.startsWith("client-") ? name.slice("client-".length) : name;
|
|
27
53
|
return {
|
|
28
54
|
name,
|
|
29
55
|
type: c.type,
|
|
30
|
-
hasRouter
|
|
56
|
+
hasRouter,
|
|
31
57
|
isMobile,
|
|
58
|
+
appStructureName: `${str.toCamelCase(baseName)}AppStructureItems`,
|
|
59
|
+
needsNgIcons: (hasRouter && hasAuth) || hasDb,
|
|
32
60
|
};
|
|
33
61
|
});
|
|
34
62
|
|
|
35
|
-
const
|
|
63
|
+
const routerClients = clients.filter((c) => c.hasRouter);
|
|
64
|
+
const appStructureNames =
|
|
65
|
+
routerClients.length > 0
|
|
66
|
+
? routerClients.map((c) => c.appStructureName)
|
|
67
|
+
: ["appStructureItems"];
|
|
68
|
+
|
|
69
|
+
const userEntityKebab = hasAuth ? (input.userEntityName ?? "user") : "";
|
|
70
|
+
const userEntityLabel = hasAuth ? (input.userEntityLabel ?? "사용자") : "";
|
|
36
71
|
const hasCommon = input.hasServer;
|
|
37
72
|
const hasClientCommon = input.hasServer || clients.length >= 2;
|
|
38
73
|
const hasMobile = clients.some((c) => c.isMobile);
|
|
@@ -45,11 +80,21 @@ export function normalize(input: InitInput): NormalizedInput {
|
|
|
45
80
|
hasDb,
|
|
46
81
|
dbDialect: hasDb ? input.dbDialect : undefined,
|
|
47
82
|
dbPort: hasDb && input.dbDialect != null ? DB_PORTS[input.dbDialect] : 0,
|
|
83
|
+
dbUsername: hasDb && input.dbDialect != null ? DB_CREDENTIALS[input.dbDialect].username : "",
|
|
84
|
+
dbPassword: hasDb && input.dbDialect != null ? DB_CREDENTIALS[input.dbDialect].password : "",
|
|
48
85
|
isMysql: hasDb && input.dbDialect === "mysql",
|
|
49
|
-
isPostgres: hasDb && input.dbDialect === "
|
|
86
|
+
isPostgres: hasDb && input.dbDialect === "postgresql",
|
|
50
87
|
isMssql: hasDb && input.dbDialect === "mssql",
|
|
51
88
|
dbContextClassName: hasDb ? toDbContextClassName(input.dbContextName ?? "main") : "",
|
|
52
89
|
dbContextNameUpper: hasDb ? toDbContextNameUpper(input.dbContextName ?? "main") : "",
|
|
90
|
+
dbContextFileName: hasDb ? toDbContextFileName(input.dbContextName ?? "main") : "",
|
|
91
|
+
dbFolderName: hasDb ? `db-${toDbContextKebab(input.dbContextName ?? "main")}` : "",
|
|
92
|
+
hasAuth,
|
|
93
|
+
userEntityPascal: hasAuth ? str.toPascalCase(userEntityKebab) : "",
|
|
94
|
+
userEntityCamel: hasAuth ? str.toCamelCase(userEntityKebab) : "",
|
|
95
|
+
userEntityKebab,
|
|
96
|
+
userEntityLabel,
|
|
97
|
+
appStructureNames,
|
|
53
98
|
serverPort: input.serverPort ?? 40080,
|
|
54
99
|
mobileAppId: hasMobile ? input.mobileAppId : undefined,
|
|
55
100
|
firstMobileClientName: clients.find((c) => c.isMobile)?.name,
|
|
@@ -4,9 +4,10 @@ import type { ClientInputSpec, ClientType, DbDialect, InitInput } from "./types"
|
|
|
4
4
|
const KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
5
5
|
const APPID_RE = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)+$/;
|
|
6
6
|
|
|
7
|
-
export async function promptInit(): Promise<InitInput> {
|
|
7
|
+
export async function promptInit(workspaceNameDefault: string): Promise<InitInput> {
|
|
8
8
|
const workspaceName = await input({
|
|
9
9
|
message: "워크스페이스 이름 (영문 kebab-case, 예: my-project):",
|
|
10
|
+
default: KEBAB_CASE_RE.test(workspaceNameDefault) ? workspaceNameDefault : undefined,
|
|
10
11
|
validate: (v) => KEBAB_CASE_RE.test(v) || "영문 kebab-case 만 허용됩니다.",
|
|
11
12
|
});
|
|
12
13
|
|
|
@@ -22,6 +23,9 @@ export async function promptInit(): Promise<InitInput> {
|
|
|
22
23
|
let hasDb = false;
|
|
23
24
|
let dbDialect: DbDialect | undefined;
|
|
24
25
|
let dbContextName: string | undefined;
|
|
26
|
+
let hasAuth = false;
|
|
27
|
+
let userEntityName: string | undefined;
|
|
28
|
+
let userEntityLabel: string | undefined;
|
|
25
29
|
let serverPort: number | undefined;
|
|
26
30
|
if (hasServer) {
|
|
27
31
|
hasDb = await confirm({
|
|
@@ -33,7 +37,7 @@ export async function promptInit(): Promise<InitInput> {
|
|
|
33
37
|
message: "DB dialect 를 선택하세요:",
|
|
34
38
|
choices: [
|
|
35
39
|
{ name: "MySQL", value: "mysql" },
|
|
36
|
-
{ name: "PostgreSQL", value: "
|
|
40
|
+
{ name: "PostgreSQL", value: "postgresql" },
|
|
37
41
|
{ name: "MSSQL", value: "mssql" },
|
|
38
42
|
],
|
|
39
43
|
});
|
|
@@ -43,10 +47,34 @@ export async function promptInit(): Promise<InitInput> {
|
|
|
43
47
|
validate: (v) =>
|
|
44
48
|
/^[A-Za-z][A-Za-z0-9]*$/.test(v) || "영문 (대소문자) + 숫자만, 첫 글자는 영문",
|
|
45
49
|
});
|
|
50
|
+
|
|
51
|
+
hasAuth = await confirm({
|
|
52
|
+
message: "사용자 인증을 사용할까요? (사용자/역할/권한/로그 테이블 부트스트랩)",
|
|
53
|
+
default: true,
|
|
54
|
+
});
|
|
55
|
+
if (hasAuth) {
|
|
56
|
+
const useDefaultEntity = await confirm({
|
|
57
|
+
message: "사용자 엔티티를 user / 사용자 로 만들까요?",
|
|
58
|
+
default: true,
|
|
59
|
+
});
|
|
60
|
+
if (useDefaultEntity) {
|
|
61
|
+
userEntityName = "user";
|
|
62
|
+
userEntityLabel = "사용자";
|
|
63
|
+
} else {
|
|
64
|
+
userEntityName = await input({
|
|
65
|
+
message: "사용자 엔티티 영문 식별자 (kebab-case, 예: employee):",
|
|
66
|
+
validate: (v) => KEBAB_CASE_RE.test(v) || "영문 kebab-case 만 허용됩니다.",
|
|
67
|
+
});
|
|
68
|
+
userEntityLabel = await input({
|
|
69
|
+
message: "사용자 엔티티 한글 라벨 (예: 직원):",
|
|
70
|
+
validate: (v) => v.trim().length > 0 || "라벨을 입력하세요.",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
46
74
|
}
|
|
47
75
|
|
|
48
76
|
const portStr = await input({
|
|
49
|
-
message: "server port
|
|
77
|
+
message: "server port:",
|
|
50
78
|
default: "40080",
|
|
51
79
|
validate: (v) => {
|
|
52
80
|
const n = Number(v);
|
|
@@ -103,6 +131,9 @@ export async function promptInit(): Promise<InitInput> {
|
|
|
103
131
|
hasDb,
|
|
104
132
|
dbDialect,
|
|
105
133
|
dbContextName,
|
|
134
|
+
hasAuth,
|
|
135
|
+
userEntityName,
|
|
136
|
+
userEntityLabel,
|
|
106
137
|
mobileAppId,
|
|
107
138
|
serverPort,
|
|
108
139
|
};
|
|
Binary file
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
"type": "module",
|
|
7
7
|
"private": true,
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"consola": "^3.4.2",
|
|
10
9
|
"@angular/common": "^21.2.0",
|
|
11
10
|
"@angular/core": "^21.2.0",
|
|
12
11
|
"@angular/platform-browser": "^21.2.0",
|
|
@@ -16,7 +15,9 @@
|
|
|
16
15
|
"@{{workspaceName}}/client-common": "workspace:*"{{/if}}{{#if hasServer}},
|
|
17
16
|
"@simplysm/service-client": "^14.0.0",
|
|
18
17
|
"@simplysm/service-common": "^14.0.0"{{/if}}{{#if client.hasRouter}},
|
|
19
|
-
"@angular/router": "^21.2.0"{{/if}}{{#if
|
|
18
|
+
"@angular/router": "^21.2.0"{{/if}}{{#if client.needsNgIcons}},
|
|
19
|
+
"@ng-icons/core": "^33.2.3",
|
|
20
|
+
"@ng-icons/tabler-icons": "^33.2.3"{{/if}}{{#if hasDb}},
|
|
20
21
|
"@{{workspaceName}}/common": "workspace:*",
|
|
21
22
|
"@simplysm/orm-common": "^14.0.0"{{/if}}{{#if client.isMobile}},
|
|
22
23
|
"@capacitor/core": "^7.0.0"{{/if}}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
computed,
|
|
5
|
+
effect,
|
|
6
|
+
inject,
|
|
7
|
+
signal,
|
|
8
|
+
untracked,
|
|
9
|
+
ViewEncapsulation,
|
|
10
|
+
} from "@angular/core";
|
|
11
|
+
import { Router, RouterOutlet } from "@angular/router";
|
|
12
|
+
import {
|
|
13
|
+
SdAppStructureProvider,
|
|
14
|
+
SdBusyContainer,
|
|
15
|
+
SdRouterLink,
|
|
16
|
+
SdSidebar,
|
|
17
|
+
SdSidebarContainer,
|
|
18
|
+
SdSidebarMenu,
|
|
19
|
+
SdSidebarUser,
|
|
20
|
+
type SdSidebarUserMenu,
|
|
21
|
+
SdToastProvider,
|
|
22
|
+
} from "@simplysm/angular";
|
|
23
|
+
import { AppAuthProvider } from "@{{workspaceName}}/client-common";
|
|
24
|
+
import { NgIcon } from "@ng-icons/core";
|
|
25
|
+
import { tablerUserCircle } from "@ng-icons/tabler-icons";
|
|
26
|
+
|
|
27
|
+
@Component({
|
|
28
|
+
selector: "app-home-view",
|
|
29
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
30
|
+
encapsulation: ViewEncapsulation.None,
|
|
31
|
+
standalone: true,
|
|
32
|
+
imports: [
|
|
33
|
+
RouterOutlet,
|
|
34
|
+
SdBusyContainer,
|
|
35
|
+
SdSidebarContainer,
|
|
36
|
+
SdSidebar,
|
|
37
|
+
SdSidebarMenu,
|
|
38
|
+
SdSidebarUser,
|
|
39
|
+
SdRouterLink,
|
|
40
|
+
NgIcon,
|
|
41
|
+
],
|
|
42
|
+
template: `
|
|
43
|
+
<sd-busy-container [busy]="busyCount() > 0">
|
|
44
|
+
@if (initialized()) {
|
|
45
|
+
<sd-sidebar-container>
|
|
46
|
+
<sd-sidebar class="flex-column">
|
|
47
|
+
<div>
|
|
48
|
+
<div
|
|
49
|
+
class="p-default-xl sh-topbar"
|
|
50
|
+
style="cursor: pointer;"
|
|
51
|
+
[sdRouterLink]="{ link: '/home/main' }"
|
|
52
|
+
>
|
|
53
|
+
<img
|
|
54
|
+
alt="로고"
|
|
55
|
+
src="assets/logo-landscape.png"
|
|
56
|
+
style="height: 100%; width: auto;"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<sd-sidebar-user [userMenu]="userMenu">
|
|
61
|
+
<div style="float: left;" class="pr-default">
|
|
62
|
+
<ng-icon [svg]="tablerUserCircle" [size]="'3em'" />
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div>\{{ authInfo()?.name }}</div>
|
|
66
|
+
<small>\{{ authInfo()?.roleName }}</small>
|
|
67
|
+
</sd-sidebar-user>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="flex-fill">
|
|
71
|
+
<sd-sidebar-menu [menus]="menus()" />
|
|
72
|
+
</div>
|
|
73
|
+
</sd-sidebar>
|
|
74
|
+
|
|
75
|
+
<div class="fill">
|
|
76
|
+
<router-outlet />
|
|
77
|
+
</div>
|
|
78
|
+
</sd-sidebar-container>
|
|
79
|
+
}
|
|
80
|
+
</sd-busy-container>
|
|
81
|
+
`,
|
|
82
|
+
})
|
|
83
|
+
export class HomeView {
|
|
84
|
+
private readonly _sdToast = inject(SdToastProvider);
|
|
85
|
+
private readonly _appAuth = inject(AppAuthProvider);
|
|
86
|
+
private readonly _sdAppStructure = inject(SdAppStructureProvider);
|
|
87
|
+
private readonly _router = inject(Router);
|
|
88
|
+
|
|
89
|
+
busyCount = signal(0);
|
|
90
|
+
initialized = signal(false);
|
|
91
|
+
|
|
92
|
+
authInfo = computed(() => this._appAuth.authInfo());
|
|
93
|
+
menus = computed(() => this._sdAppStructure.usableMenus());
|
|
94
|
+
|
|
95
|
+
userMenu: SdSidebarUserMenu = {
|
|
96
|
+
title: "내 계정 정보",
|
|
97
|
+
menus: [
|
|
98
|
+
{
|
|
99
|
+
title: "내 정보 수정",
|
|
100
|
+
onClick: async () => {
|
|
101
|
+
await this._router.navigate(["/home/my-info"]);
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
title: "로그아웃",
|
|
106
|
+
onClick: async () => {
|
|
107
|
+
await this._appAuth.logout();
|
|
108
|
+
await this._router.navigate(["/login"]);
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
constructor() {
|
|
115
|
+
effect(() => {
|
|
116
|
+
void untracked(async () => {
|
|
117
|
+
if (this._appAuth.authInfo() != null) {
|
|
118
|
+
this.initialized.set(true);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
this.busyCount.update((v) => v + 1);
|
|
123
|
+
await this._sdToast.try(async () => {
|
|
124
|
+
const ok = await this._appAuth.tryReloadAuth();
|
|
125
|
+
if (!ok) {
|
|
126
|
+
await this._router.navigateByUrl("/login", { replaceUrl: true });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
this.busyCount.update((v) => v - 1);
|
|
131
|
+
this.initialized.set(true);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected readonly tablerUserCircle = tablerUserCircle;
|
|
137
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from "@angular/core";
|
|
2
|
+
import { injectViewTypeSignal, SdBaseContainer } from "@simplysm/angular";
|
|
3
|
+
|
|
4
|
+
@Component({
|
|
5
|
+
selector: "app-main-view",
|
|
6
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
7
|
+
encapsulation: ViewEncapsulation.None,
|
|
8
|
+
standalone: true,
|
|
9
|
+
template: `
|
|
10
|
+
<sd-base-container [initialized]="true" [viewType]="viewType()" />
|
|
11
|
+
`,
|
|
12
|
+
imports: [SdBaseContainer],
|
|
13
|
+
})
|
|
14
|
+
export class MainView {
|
|
15
|
+
viewType = injectViewTypeSignal();
|
|
16
|
+
}
|