@simplysm/sd-cli 14.0.95 → 14.0.97

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.d.ts.map +1 -1
  2. package/dist/commands/init/generators/client.js +12 -0
  3. package/dist/commands/init/generators/client.js.map +1 -1
  4. package/dist/commands/init/generators/server.d.ts.map +1 -1
  5. package/dist/commands/init/generators/server.js +1 -0
  6. package/dist/commands/init/generators/server.js.map +1 -1
  7. package/dist/commands/init/normalize.d.ts.map +1 -1
  8. package/dist/commands/init/normalize.js +1 -0
  9. package/dist/commands/init/normalize.js.map +1 -1
  10. package/dist/commands/init/prompts.d.ts.map +1 -1
  11. package/dist/commands/init/prompts.js +8 -1
  12. package/dist/commands/init/prompts.js.map +1 -1
  13. package/dist/commands/init/types.d.ts +3 -0
  14. package/dist/commands/init/types.d.ts.map +1 -1
  15. package/dist/engines/EsbuildClientEngine.d.ts.map +1 -1
  16. package/dist/engines/EsbuildClientEngine.js +1 -0
  17. package/dist/engines/EsbuildClientEngine.js.map +1 -1
  18. package/dist/esbuild/esbuild-client-config.d.ts.map +1 -1
  19. package/dist/esbuild/esbuild-client-config.js +2 -11
  20. package/dist/esbuild/esbuild-client-config.js.map +1 -1
  21. package/dist/esbuild/esbuild-postcss-plugin.d.ts +4 -0
  22. package/dist/esbuild/esbuild-postcss-plugin.d.ts.map +1 -1
  23. package/dist/esbuild/esbuild-postcss-plugin.js +15 -0
  24. package/dist/esbuild/esbuild-postcss-plugin.js.map +1 -1
  25. package/dist/esbuild/esbuild-ssr-config.d.ts +27 -0
  26. package/dist/esbuild/esbuild-ssr-config.d.ts.map +1 -0
  27. package/dist/esbuild/esbuild-ssr-config.js +113 -0
  28. package/dist/esbuild/esbuild-ssr-config.js.map +1 -0
  29. package/dist/sd-config.types.d.ts +7 -0
  30. package/dist/sd-config.types.d.ts.map +1 -1
  31. package/dist/ssg/prerender.d.ts +19 -0
  32. package/dist/ssg/prerender.d.ts.map +1 -0
  33. package/dist/ssg/prerender.js +43 -0
  34. package/dist/ssg/prerender.js.map +1 -0
  35. package/dist/workers/client.worker.d.ts +2 -0
  36. package/dist/workers/client.worker.d.ts.map +1 -1
  37. package/dist/workers/client.worker.js +21 -0
  38. package/dist/workers/client.worker.js.map +1 -1
  39. package/package.json +6 -6
  40. package/src/commands/init/generators/client.ts +53 -0
  41. package/src/commands/init/generators/server.ts +5 -0
  42. package/src/commands/init/normalize.ts +1 -0
  43. package/src/commands/init/prompts.ts +9 -1
  44. package/src/commands/init/templates/client/package.json.hbs +2 -1
  45. package/src/commands/init/templates/client/src/app/home/home.view.ts.hbs +1 -1
  46. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.detail.ts.hbs +221 -0
  47. package/src/commands/init/templates/client/src/app/home/master/role-permission/role-permission.view.ts.hbs +106 -0
  48. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.detail.ts.hbs +277 -0
  49. package/src/commands/init/templates/client/src/app/home/master/role-permission/role.list.ts.hbs +537 -0
  50. package/src/commands/init/templates/client/src/app/home/master/user.detail.ts.hbs +337 -0
  51. package/src/commands/init/templates/client/src/app/home/master/user.list.ts.hbs +540 -0
  52. package/src/commands/init/templates/client/src/app/home/my-info/my-info.detail.ts.hbs +4 -6
  53. package/src/commands/init/templates/client/src/app/home/system/data-log/data-log.list.ts.hbs +355 -0
  54. package/src/commands/init/templates/client/src/app/home/system/system-log/system-log.list.ts.hbs +382 -0
  55. package/src/commands/init/templates/client/src/app/login/login.view.ts.hbs +3 -4
  56. package/src/commands/init/templates/client/src/app.root.ts.hbs +9 -3
  57. package/src/commands/init/templates/client/src/index.html.hbs +1 -0
  58. package/src/commands/init/templates/client/src/main.server.ts.hbs +24 -0
  59. package/src/commands/init/templates/client/src/main.ts.hbs +36 -18
  60. package/src/commands/init/templates/client/src/modals/text-view.modal.ts.hbs +30 -0
  61. package/src/commands/init/templates/client/src/routes.ts.hbs +22 -0
  62. package/src/commands/init/templates/client-common/src/index.ts.hbs +6 -4
  63. package/src/commands/init/templates/client-common/src/providers/app-auth.provider.ts.hbs +3 -3
  64. package/src/commands/init/templates/client-common/src/providers/app-orm.provider.ts.hbs +0 -11
  65. package/src/commands/init/templates/client-common/src/providers/app-service.provider.ts.hbs +24 -10
  66. package/src/commands/init/templates/client-common/src/providers/app-shared-data.provider.ts.hbs +2 -2
  67. package/src/commands/init/templates/common/package.json.hbs +2 -1
  68. package/src/commands/init/templates/common/src/app-structure.ts.hbs +7 -2
  69. package/src/commands/init/templates/common/src/auth-info-changed.event.ts.hbs +3 -1
  70. package/src/commands/init/templates/common/src/db/db-context.ts.hbs +2 -2
  71. package/src/commands/init/templates/common/src/db/tables/master/user.ts.hbs +2 -2
  72. package/src/commands/init/templates/common/src/db/tables/system/role.ts.hbs +1 -0
  73. package/src/commands/init/templates/common/src/index.ts.hbs +1 -1
  74. package/src/commands/init/templates/server/src/index.ts.hbs +3 -0
  75. package/src/commands/init/templates/server/src/main.ts.hbs +15 -1
  76. package/src/commands/init/templates/server/src/services/auth.service.ts.hbs +28 -22
  77. package/src/commands/init/templates/server/src/services/dev.service.ts.hbs +5 -5
  78. package/src/commands/init/templates/server/src/services/user.service.ts.hbs +191 -0
  79. package/src/commands/init/templates/workspace-root/sd.config.ts.hbs +3 -0
  80. package/src/commands/init/types.ts +3 -0
  81. package/src/engines/EsbuildClientEngine.ts +1 -0
  82. package/src/esbuild/esbuild-client-config.ts +2 -12
  83. package/src/esbuild/esbuild-postcss-plugin.ts +18 -0
  84. package/src/esbuild/esbuild-ssr-config.ts +149 -0
  85. package/src/sd-config.types.ts +7 -0
  86. package/src/ssg/prerender.ts +65 -0
  87. package/src/workers/client.worker.ts +26 -0
  88. package/tests/engines/base-engine.spec.ts +1 -26
  89. package/tests/init/__snapshots__/render.spec.ts.snap +38 -20
  90. package/tests/init/render.spec.ts +113 -33
  91. package/tests/utils/hmr-client-script.acc.spec.ts +0 -21
  92. package/tests/angular/vite-angular-plugin.spec.ts +0 -102
  93. package/tests/engines/engine-adapter-isolation.spec.ts +0 -79
  94. package/tests/runtime/signal-handler.spec.ts +0 -21
  95. package/tests/utils/angular-build.spec.ts +0 -109
  96. package/tests/utils/esbuild-client-config.acc.spec.ts +0 -438
  97. package/tests/utils/esbuild-client-config.spec.ts +0 -659
  98. package/tests/utils/hmr-client-script.spec.ts +0 -44
  99. package/tests/utils/tsconfig-angular.spec.ts +0 -9
@@ -0,0 +1,277 @@
1
+ import {
2
+ ChangeDetectionStrategy,
3
+ Component,
4
+ effect,
5
+ inject,
6
+ input,
7
+ output,
8
+ signal,
9
+ untracked,
10
+ ViewEncapsulation,
11
+ } from "@angular/core";
12
+ import { obj } from "@simplysm/core-common";
13
+ import {
14
+ injectPermsSignal,
15
+ injectViewTypeSignal,
16
+ mark,
17
+ SdButton,
18
+ SdCrudDetail,
19
+ type SdModalContentDef,
20
+ SdTextfield,
21
+ SdToastProvider,
22
+ setupCanDeactivate,
23
+ } from "@simplysm/angular";
24
+ import { expr } from "@simplysm/orm-common";
25
+ import {
26
+ AppAuthProvider,
27
+ AppOrmProvider,
28
+ AppSharedDataProvider,
29
+ } from "@{{workspaceName}}/client-common";
30
+
31
+ @Component({
32
+ selector: "app-role-detail",
33
+ changeDetection: ChangeDetectionStrategy.OnPush,
34
+ encapsulation: ViewEncapsulation.None,
35
+ standalone: true,
36
+ imports: [SdCrudDetail, SdTextfield, SdButton],
37
+ template: `
38
+ <sd-crud-detail
39
+ [(ready)]="ready"
40
+ [initialized]="initialized()"
41
+ [(busyCount)]="busyCount"
42
+ [viewType]="viewType()"
43
+ [restricted]="!perms().includes('use')"
44
+ [readonly]="!perms().includes('edit')"
45
+ (submit)="onSubmit()"
46
+ >
47
+ @if (roleId() != null && perms().includes("edit")) {
48
+ <ng-template #bottomCommandTpl>
49
+ <div class="flex-row gap-sm">
50
+ @if (data().isDeleted) {
51
+ <sd-button [size]="'sm'" [theme]="'warning'" (click)="onRestore()">복구</sd-button>
52
+ } @else {
53
+ <sd-button [size]="'sm'" [theme]="'danger'" (click)="onDelete()">삭제</sd-button>
54
+ }
55
+ </div>
56
+ </ng-template>
57
+ }
58
+
59
+ <ng-template #contentTpl>
60
+ <div class="p-default">
61
+ <table class="form-table">
62
+ <tbody>
63
+ <tr>
64
+ <th>이름</th>
65
+ <td>
66
+ <sd-textfield
67
+ [type]="'text'"
68
+ [(value)]="data().name"
69
+ (valueChange)="mark(data)"
70
+ [required]="true"
71
+ [disabled]="!perms().includes('edit')"
72
+ />
73
+ </td>
74
+ </tr>
75
+ </tbody>
76
+ </table>
77
+ </div>
78
+ </ng-template>
79
+ </sd-crud-detail>
80
+ `,
81
+ })
82
+ export class RoleDetail implements SdModalContentDef<{ id: number } | undefined> {
83
+ private readonly _sdToast = inject(SdToastProvider);
84
+ private readonly _appOrm = inject(AppOrmProvider);
85
+ private readonly _appSharedData = inject(AppSharedDataProvider);
86
+ private readonly _appAuth = inject(AppAuthProvider);
87
+
88
+ roleId = input<number>();
89
+ close = output<{ id: number } | undefined>();
90
+
91
+ perms = injectPermsSignal(["master.role-permission"], ["use", "edit"]);
92
+ viewType = injectViewTypeSignal();
93
+
94
+ ready = signal(false);
95
+ initialized = signal(false);
96
+ busyCount = signal(0);
97
+
98
+ data = signal<IData>({});
99
+ private _orgData?: IData;
100
+
101
+ constructor() {
102
+ effect(() => {
103
+ this.roleId();
104
+
105
+ if (!this.perms().includes("use") || !this.ready()) {
106
+ this.initialized.set(true);
107
+ return;
108
+ }
109
+
110
+ void untracked(async () => {
111
+ this.busyCount.update((v) => v + 1);
112
+ await this._sdToast.try(async () => {
113
+ await this._refresh();
114
+ });
115
+ this.busyCount.update((v) => v - 1);
116
+ this.initialized.set(true);
117
+ });
118
+ });
119
+
120
+ setupCanDeactivate(() => this._checkIgnoreChanges());
121
+ }
122
+
123
+ async onSubmit(): Promise<void> {
124
+ if (this.busyCount() > 0) return;
125
+ if (!this.perms().includes("edit")) return;
126
+
127
+ const data = this.data();
128
+ if (this._orgData != null && obj.equal(data, this._orgData)) {
129
+ this._sdToast.info("변경사항이 없습니다.");
130
+ return;
131
+ }
132
+
133
+ this.busyCount.update((v) => v + 1);
134
+ await this._sdToast.try(async () => {
135
+ const roleId = this.roleId();
136
+ const {{userEntityCamel}}Id = this._appAuth.authInfo()?.{{userEntityCamel}}Id;
137
+
138
+ const savedId = await this._appOrm.connectAsync(async (db) => {
139
+ // 이름 중복 검증 (자기 자신 제외)
140
+ const isDuplicated = await db
141
+ .role()
142
+ .where((c) => [
143
+ expr.eq(c.name, data.name),
144
+ expr.eq(c.isDeleted, false),
145
+ ...(roleId == null ? [] : [expr.not(expr.eq(c.id, roleId))]),
146
+ ])
147
+ .exists();
148
+ if (isDuplicated) {
149
+ throw new Error("이미 존재하는 역할 이름입니다.");
150
+ }
151
+
152
+ if (roleId == null) {
153
+ const [inserted] = await db
154
+ .role()
155
+ .insert([{ name: data.name, isDeleted: false }], ["id"]);
156
+ await db.role().insertDataLog({ action: "등록", itemId: inserted.id, {{userEntityCamel}}Id });
157
+ return inserted.id;
158
+ }
159
+ await db
160
+ .role()
161
+ .where((c) => [expr.eq(c.id, roleId)])
162
+ .update(() => ({ name: data.name }));
163
+ await db.role().insertDataLog({ action: "수정", itemId: roleId, {{userEntityCamel}}Id });
164
+ return roleId;
165
+ });
166
+
167
+ await this._appSharedData.emitAsync("역할", [savedId]);
168
+ this._sdToast.success("저장되었습니다.");
169
+ this.close.emit({ id: savedId });
170
+ });
171
+ this.busyCount.update((v) => v - 1);
172
+ }
173
+
174
+ async onDelete(): Promise<void> {
175
+ if (this.busyCount() > 0) return;
176
+ if (!this.perms().includes("edit")) return;
177
+
178
+ const roleId = this.roleId();
179
+ if (roleId == null) return;
180
+
181
+ if (!confirm("삭제하시겠습니까?")) return;
182
+
183
+ this.busyCount.update((v) => v + 1);
184
+ await this._sdToast.try(async () => {
185
+ const {{userEntityCamel}}Id = this._appAuth.authInfo()?.{{userEntityCamel}}Id;
186
+ await this._appOrm.connectAsync(async (db) => {
187
+ await db
188
+ .role()
189
+ .where((c) => [expr.eq(c.id, roleId)])
190
+ .update(() => ({ isDeleted: true }));
191
+ await db.role().insertDataLog({ action: "삭제", itemId: roleId, {{userEntityCamel}}Id });
192
+ });
193
+
194
+ await this._appSharedData.emitAsync("역할", [roleId]);
195
+ this._sdToast.success("삭제되었습니다.");
196
+ this.close.emit({ id: roleId });
197
+ });
198
+ this.busyCount.update((v) => v - 1);
199
+ }
200
+
201
+ async onRestore(): Promise<void> {
202
+ if (this.busyCount() > 0) return;
203
+ if (!this.perms().includes("edit")) return;
204
+
205
+ const roleId = this.roleId();
206
+ if (roleId == null) return;
207
+
208
+ const roleName = this.data().name;
209
+
210
+ this.busyCount.update((v) => v + 1);
211
+ await this._sdToast.try(async () => {
212
+ const {{userEntityCamel}}Id = this._appAuth.authInfo()?.{{userEntityCamel}}Id;
213
+ await this._appOrm.connectAsync(async (db) => {
214
+ // 복구 전 이름 비삭제 유니크 재검증 (삭제된 동안 같은 이름의 활성 역할이 생겼을 수 있음).
215
+ const isNameDuplicated = await db
216
+ .role()
217
+ .where((c) => [
218
+ expr.eq(c.name, roleName),
219
+ expr.eq(c.isDeleted, false),
220
+ expr.not(expr.eq(c.id, roleId)),
221
+ ])
222
+ .exists();
223
+ if (isNameDuplicated) {
224
+ throw new Error("같은 이름의 활성 역할이 있어 복구할 수 없습니다.");
225
+ }
226
+
227
+ await db
228
+ .role()
229
+ .where((c) => [expr.eq(c.id, roleId)])
230
+ .update(() => ({ isDeleted: false }));
231
+ await db.role().insertDataLog({ action: "복구", itemId: roleId, {{userEntityCamel}}Id });
232
+ });
233
+
234
+ await this._appSharedData.emitAsync("역할", [roleId]);
235
+ this._sdToast.success("복구되었습니다.");
236
+ await this._refresh();
237
+ });
238
+ this.busyCount.update((v) => v - 1);
239
+ }
240
+
241
+ private async _refresh(): Promise<void> {
242
+ const roleId = this.roleId();
243
+ if (roleId == null) {
244
+ this.data.set({});
245
+ this._orgData = obj.clone(this.data());
246
+ return;
247
+ }
248
+
249
+ const loaded = await this._appOrm.connectAsync(async (db) => {
250
+ return db
251
+ .role()
252
+ .where((c) => [expr.eq(c.id, roleId)])
253
+ .select((c) => ({ name: c.name, isDeleted: c.isDeleted }))
254
+ .single();
255
+ });
256
+
257
+ if (loaded == null) throw new Error("역할을 찾을 수 없습니다.");
258
+
259
+ this.data.set({ name: loaded.name, isDeleted: loaded.isDeleted });
260
+ this._orgData = obj.clone(this.data());
261
+ }
262
+
263
+ private _checkIgnoreChanges(): boolean {
264
+ return (
265
+ this._orgData == null ||
266
+ obj.equal(this.data(), this._orgData) ||
267
+ confirm("변경사항이 있습니다. 무시하고 진행하시겠습니까?")
268
+ );
269
+ }
270
+
271
+ protected readonly mark = mark;
272
+ }
273
+
274
+ interface IData {
275
+ name?: string;
276
+ isDeleted?: boolean;
277
+ }