@gadmin2n/cli 0.0.158 → 0.0.160

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.
@@ -3,5 +3,6 @@ import { AbstractAction } from './abstract.action';
3
3
  export declare class PrismaAction extends AbstractAction {
4
4
  private buildMergedSchema;
5
5
  handle(inputs: Input[], options: Input[]): Promise<void>;
6
+ private printReport;
6
7
  private runProjectPostHook;
7
8
  }
@@ -51,39 +51,40 @@ function replaceFileContent(filePath, match, src, dest) {
51
51
  }
52
52
  class PrismaAction extends abstract_action_1.AbstractAction {
53
53
  buildMergedSchema() {
54
- shell
55
- .cat('server/prisma/.generator.prisma')
56
- .to('server/prisma/schema.prisma');
54
+ // 读 generator 头
55
+ const generatorHead = fs.readFileSync('server/prisma/.generator.prisma', 'utf8');
56
+ // 读用户 schema
57
57
  let schemaStr = '';
58
58
  if (shell.test('-d', 'config/prisma')) {
59
- schemaStr = shell.cat('config/prisma/*.prisma');
59
+ schemaStr = shell.cat('config/prisma/*.prisma').toString();
60
60
  }
61
61
  else {
62
- schemaStr = shell.cat('config/schema.prisma');
62
+ schemaStr = shell.cat('config/schema.prisma').toString();
63
63
  }
64
64
  const schemaRegex = /([\s\S]*?model\s\w*\s*\{)([^\}]*)(\})/gim; // https://regex101.com/
65
- let m, result = '';
65
+ let m, merged = '';
66
66
  while ((m = schemaRegex.exec(schemaStr)) !== null) {
67
67
  // This is necessary to avoid infinite loops with zero-width matches
68
68
  if (m.index === schemaRegex.lastIndex) {
69
69
  schemaRegex.lastIndex++;
70
70
  }
71
71
  let ignore = !!m[1].match(/\/\/\/\s*@IgnoreAutoField/);
72
- result += m[1];
72
+ merged += m[1];
73
73
  if (!ignore) {
74
- result += '\n id BigInt @id @default(autoincrement())';
74
+ merged += '\n id BigInt @id @default(autoincrement())';
75
75
  }
76
- result += m[2];
76
+ merged += m[2];
77
77
  if (!ignore) {
78
- result +=
78
+ merged +=
79
79
  ' creator String @db.VarChar(128) @map("creator")\n createdAt DateTime @default(now()) @map("created_at")\n updatedAt DateTime @updatedAt @default(now()) @map("updated_at")\n';
80
80
  }
81
- result += m[3];
81
+ merged += m[3];
82
82
  }
83
- new shell.ShellString(result).toEnd('server/prisma/schema.prisma');
83
+ // 一次原子写 + diff:内容未变则完全不 touch 文件,避免任何 watcher 感知抖动。
84
+ return (0, sync_fs_1.writeFileIfChanged)('server/prisma/schema.prisma', generatorHead + merged);
84
85
  }
85
86
  handle(inputs, options) {
86
- var _a;
87
+ var _a, _b;
87
88
  return __awaiter(this, void 0, void 0, function* () {
88
89
  // 检查 prisma schema 配置是否存在
89
90
  const hasPrismaConfig = shell.test('-e', 'config/schema.prisma') ||
@@ -94,6 +95,18 @@ class PrismaAction extends abstract_action_1.AbstractAction {
94
95
  process.exit(-1);
95
96
  }
96
97
  const devMode = !!((_a = options.find((option) => option.name === 'dev')) === null || _a === void 0 ? void 0 : _a.value);
98
+ const verbose = !!((_b = options.find((option) => option.name === 'verbose')) === null || _b === void 0 ? void 0 : _b.value);
99
+ // 分发过程按类别记账,收尾统一打摘要 / --verbose 打完整清单
100
+ const stats = {
101
+ serverModulesIndex: [],
102
+ serverModulesNew: [],
103
+ serverModulesSkipped: [],
104
+ routesNew: [],
105
+ routesSkipped: [],
106
+ generated: [],
107
+ generatedUnchanged: [],
108
+ orphansRemoved: [], // web/src/generated/** 孤儿清理
109
+ };
97
110
  // 构建合并的 schema(两种模式都需要重建 schema.prisma)
98
111
  this.buildMergedSchema();
99
112
  // Staging 目录:generator 全量写入到 node_modules/.cache/*,CLI 再增量分发。
@@ -145,7 +158,10 @@ class PrismaAction extends abstract_action_1.AbstractAction {
145
158
  // 1.1 modules.index.ts —— 顶层入口
146
159
  const modulesIndexSrc = (0, path_1.join)(SERVER_STAGING, 'modules.index.ts');
147
160
  if (fs.existsSync(modulesIndexSrc)) {
148
- (0, sync_fs_1.copyFileIfChanged)(modulesIndexSrc, 'server/src/generated/modules.index.ts');
161
+ const rel = 'server/src/generated/modules.index.ts';
162
+ if ((0, sync_fs_1.copyFileIfChanged)(modulesIndexSrc, rel)) {
163
+ stats.serverModulesIndex.push(rel);
164
+ }
149
165
  }
150
166
  // 1.2 逐 model 首次落地到 server/src/modules/{model}(跳过 form.validator,它归 web 阶段)
151
167
  for (const modelName of stagingModels) {
@@ -155,10 +171,13 @@ class PrismaAction extends abstract_action_1.AbstractAction {
155
171
  if (rel === validatorInStaging)
156
172
  continue;
157
173
  const dest = (0, path_1.join)('server/src/modules', modelName, rel);
158
- if (fs.existsSync(dest))
174
+ if (fs.existsSync(dest)) {
175
+ stats.serverModulesSkipped.push(dest);
159
176
  continue;
177
+ }
160
178
  fs.mkdirSync((0, path_1.dirname)(dest), { recursive: true });
161
179
  fs.copyFileSync((0, path_1.join)(modelStaging, rel), dest);
180
+ stats.serverModulesNew.push(dest);
162
181
  }
163
182
  }
164
183
  }
@@ -174,10 +193,20 @@ class PrismaAction extends abstract_action_1.AbstractAction {
174
193
  if (!fs.existsSync(destRoutes)) {
175
194
  fs.mkdirSync((0, path_1.dirname)(destRoutes), { recursive: true });
176
195
  fs.copyFileSync(src, destRoutes);
196
+ stats.routesNew.push(destRoutes);
197
+ }
198
+ else {
199
+ stats.routesSkipped.push(destRoutes);
177
200
  }
178
201
  }
179
202
  else {
180
- (0, sync_fs_1.copyFileIfChanged)(src, (0, path_1.join)('web/src/generated', rel));
203
+ const dest = (0, path_1.join)('web/src/generated', rel);
204
+ if ((0, sync_fs_1.copyFileIfChanged)(src, dest)) {
205
+ stats.generated.push(dest);
206
+ }
207
+ else {
208
+ stats.generatedUnchanged.push(dest);
209
+ }
181
210
  webKeep.add(rel);
182
211
  }
183
212
  }
@@ -185,7 +214,13 @@ class PrismaAction extends abstract_action_1.AbstractAction {
185
214
  // 2.2 form.validator.ts(server generator 产出,需搬到 web/src/generated/props/{model}/)
186
215
  for (const modelName of stagingModels) {
187
216
  const validatorRel = `props/${modelName}/form.validator.ts`;
188
- (0, sync_fs_1.copyFileIfChanged)((0, path_1.join)(SERVER_STAGING, modelName, 'dto', `${modelName}.form.validator.ts`), (0, path_1.join)('web/src/generated', validatorRel));
217
+ const dest = (0, path_1.join)('web/src/generated', validatorRel);
218
+ if ((0, sync_fs_1.copyFileIfChanged)((0, path_1.join)(SERVER_STAGING, modelName, 'dto', `${modelName}.form.validator.ts`), dest)) {
219
+ stats.generated.push(dest);
220
+ }
221
+ else {
222
+ stats.generatedUnchanged.push(dest);
223
+ }
189
224
  webKeep.add(validatorRel);
190
225
  }
191
226
  // 2.3 Prisma / config 类型
@@ -195,19 +230,67 @@ class PrismaAction extends abstract_action_1.AbstractAction {
195
230
  let prismaContent = fs.readFileSync(prismaSrc, 'utf8');
196
231
  prismaContent = prismaContent.replace("import * as runtime from '@prisma/client/runtime/index';", 'declare const runtime : any');
197
232
  prismaContent = prismaContent.replace(/bigint/g, 'number');
198
- (0, sync_fs_1.writeFileIfChanged)((0, path_1.join)('web/src/generated', prismaRel), prismaContent);
233
+ const prismaDest = (0, path_1.join)('web/src/generated', prismaRel);
234
+ if ((0, sync_fs_1.writeFileIfChanged)(prismaDest, prismaContent)) {
235
+ stats.generated.push(prismaDest);
236
+ }
237
+ else {
238
+ stats.generatedUnchanged.push(prismaDest);
239
+ }
199
240
  webKeep.add(prismaRel);
200
241
  const configRel = 'types/config.types.d.ts';
201
- (0, sync_fs_1.copyFileIfChanged)('config/.types.d.ts', (0, path_1.join)('web/src/generated', configRel));
242
+ const configDest = (0, path_1.join)('web/src/generated', configRel);
243
+ if ((0, sync_fs_1.copyFileIfChanged)('config/.types.d.ts', configDest)) {
244
+ stats.generated.push(configDest);
245
+ }
246
+ else {
247
+ stats.generatedUnchanged.push(configDest);
248
+ }
202
249
  webKeep.add(configRel);
203
250
  // 2.4 孤儿清理:删除 web/src/generated 下所有本轮未产出的文件(例如被删除的 model)
204
- (0, sync_fs_1.pruneOrphans)('web/src/generated', webKeep);
251
+ stats.orphansRemoved = (0, sync_fs_1.pruneOrphans)('web/src/generated', webKeep).map((r) => (0, path_1.join)('web/src/generated', r));
252
+ // 打印摘要 / 完整清单
253
+ this.printReport(stats, verbose);
205
254
  // 分发全部完成后,若 server/package.json 定义了 postgadminGenerate,
206
255
  // 触发它。项目侧可用来做 dev server 通知、缓存清理等自定义收尾。
207
256
  this.runProjectPostHook();
208
257
  process.exit(0);
209
258
  });
210
259
  }
260
+ printReport(stats, verbose) {
261
+ const serverWritten = stats.serverModulesIndex.length + stats.serverModulesNew.length;
262
+ const webWritten = stats.generated.length + stats.routesNew.length;
263
+ console.log('');
264
+ console.log(chalk.bold('gadmin2 g prisma:'));
265
+ console.log(` ${chalk.cyan('server')}: ${serverWritten} written` +
266
+ ` (index=${stats.serverModulesIndex.length},` +
267
+ ` modules-new=${stats.serverModulesNew.length}),` +
268
+ ` ${stats.serverModulesSkipped.length} skipped`);
269
+ console.log(` ${chalk.cyan('web')}: ${webWritten} written` +
270
+ ` (routes-new=${stats.routesNew.length},` +
271
+ ` generated=${stats.generated.length}),` +
272
+ ` ${stats.routesSkipped.length + stats.generatedUnchanged.length} unchanged,` +
273
+ ` ${stats.orphansRemoved.length} orphan removed`);
274
+ if (!verbose) {
275
+ console.log(chalk.gray(' (use -v / --verbose to list files)'));
276
+ return;
277
+ }
278
+ const printGroup = (title, list, color) => {
279
+ if (list.length === 0)
280
+ return;
281
+ console.log(chalk.bold(`\n ${title} (${list.length}):`));
282
+ for (const p of list)
283
+ console.log(' ' + color(p));
284
+ };
285
+ printGroup('server modules.index (diff-write)', stats.serverModulesIndex, chalk.green);
286
+ printGroup('server modules (new)', stats.serverModulesNew, chalk.green);
287
+ printGroup('server modules (skipped, exists)', stats.serverModulesSkipped, chalk.gray);
288
+ printGroup('web routes (new)', stats.routesNew, chalk.green);
289
+ printGroup('web routes (skipped, exists)', stats.routesSkipped, chalk.gray);
290
+ printGroup('web generated (written)', stats.generated, chalk.green);
291
+ printGroup('web generated (unchanged)', stats.generatedUnchanged, chalk.gray);
292
+ printGroup('web orphans removed', stats.orphansRemoved, chalk.red);
293
+ }
211
294
  runProjectPostHook() {
212
295
  var _a;
213
296
  const pkgPath = 'server/package.json';
@@ -32,6 +32,7 @@ class GenerateCommand extends abstract_command_1.AbstractCommand {
32
32
  return { value: false, passedAsInput: true };
33
33
  })
34
34
  .option('-c, --collection [collectionName]', 'Schematics collection to use.')
35
+ .option('-v, --verbose', 'Print each affected src/ file (prisma).')
35
36
  .action((schematic, name, path, command) => __awaiter(this, void 0, void 0, function* () {
36
37
  const options = [];
37
38
  options.push({ name: 'dry-run', value: !!command.dryRun });
@@ -64,6 +65,7 @@ class GenerateCommand extends abstract_command_1.AbstractCommand {
64
65
  keepInputNameFormat: true,
65
66
  },
66
67
  });
68
+ options.push({ name: 'verbose', value: !!command.verbose });
67
69
  const inputs = [];
68
70
  inputs.push({ name: 'schematic', value: schematic });
69
71
  inputs.push({ name: 'name', value: name });
@@ -34,6 +34,10 @@ class GadminCollection extends abstract_collection_1.AbstractCollection {
34
34
  const opts = [];
35
35
  if (name.includes(':dev'))
36
36
  opts.push({ name: 'dev', value: true });
37
+ const verbose = options.find((o) => o.name === 'verbose');
38
+ if (verbose && verbose.value) {
39
+ opts.push({ name: 'verbose', value: true });
40
+ }
37
41
  return yield prisma.handle([], opts);
38
42
  }
39
43
  const schematic = this.validate(name);
@@ -37,5 +37,6 @@ export declare function syncDir(srcDir: string, destDir: string): {
37
37
  /**
38
38
  * 删除 destDir 下所有不在 keepFiles 里的文件(以及随之空掉的父目录)。
39
39
  * 用于「本轮 model 目录被删」的孤儿清理场景。
40
+ * @returns 被删除的文件相对路径列表(相对 destDir)
40
41
  */
41
- export declare function pruneOrphans(destDir: string, keepFiles: Set<string>): number;
42
+ export declare function pruneOrphans(destDir: string, keepFiles: Set<string>): string[];
@@ -115,15 +115,16 @@ exports.syncDir = syncDir;
115
115
  /**
116
116
  * 删除 destDir 下所有不在 keepFiles 里的文件(以及随之空掉的父目录)。
117
117
  * 用于「本轮 model 目录被删」的孤儿清理场景。
118
+ * @returns 被删除的文件相对路径列表(相对 destDir)
118
119
  */
119
120
  function pruneOrphans(destDir, keepFiles) {
120
- let deleted = 0;
121
+ const removed = [];
121
122
  for (const rel of listFilesRecursive(destDir)) {
122
123
  if (!keepFiles.has(rel)) {
123
124
  removeFileAndPruneEmptyDirs(path.join(destDir, rel), destDir);
124
- deleted++;
125
+ removed.push(rel);
125
126
  }
126
127
  }
127
- return deleted;
128
+ return removed;
128
129
  }
129
130
  exports.pruneOrphans = pruneOrphans;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gadmin2n/cli",
3
- "version": "0.0.158",
3
+ "version": "0.0.160",
4
4
  "description": "Gadmin - modern, fast, powerful node.js web framework (@cli)",
5
5
  "publishConfig": {
6
6
  "access": "public"