@gadmin2n/cli 0.0.157 → 0.0.158
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/actions/prisma.action.js +103 -89
- package/lib/schematics/gadmin.collection.js +1 -5
- package/lib/utils/sync-fs.d.ts +41 -0
- package/lib/utils/sync-fs.js +129 -0
- package/package.json +2 -2
package/actions/prisma.action.js
CHANGED
|
@@ -13,6 +13,7 @@ exports.PrismaAction = void 0;
|
|
|
13
13
|
const chalk = require("chalk");
|
|
14
14
|
const fs = require("fs");
|
|
15
15
|
const path_1 = require("path");
|
|
16
|
+
const sync_fs_1 = require("../lib/utils/sync-fs");
|
|
16
17
|
const abstract_action_1 = require("./abstract.action");
|
|
17
18
|
const shell = require("shelljs");
|
|
18
19
|
const readline = require("readline");
|
|
@@ -82,7 +83,7 @@ class PrismaAction extends abstract_action_1.AbstractAction {
|
|
|
82
83
|
new shell.ShellString(result).toEnd('server/prisma/schema.prisma');
|
|
83
84
|
}
|
|
84
85
|
handle(inputs, options) {
|
|
85
|
-
var _a
|
|
86
|
+
var _a;
|
|
86
87
|
return __awaiter(this, void 0, void 0, function* () {
|
|
87
88
|
// 检查 prisma schema 配置是否存在
|
|
88
89
|
const hasPrismaConfig = shell.test('-e', 'config/schema.prisma') ||
|
|
@@ -93,107 +94,121 @@ class PrismaAction extends abstract_action_1.AbstractAction {
|
|
|
93
94
|
process.exit(-1);
|
|
94
95
|
}
|
|
95
96
|
const devMode = !!((_a = options.find((option) => option.name === 'dev')) === null || _a === void 0 ? void 0 : _a.value);
|
|
96
|
-
|
|
97
|
-
const serverOnly = !!((_c = options.find((option) => option.name === 'serverOnly')) === null || _c === void 0 ? void 0 : _c.value);
|
|
98
|
-
// 构建合并的 schema(三种模式都需要重建 schema.prisma)
|
|
97
|
+
// 构建合并的 schema(两种模式都需要重建 schema.prisma)
|
|
99
98
|
this.buildMergedSchema();
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
else if (webOnly)
|
|
122
|
-
generateScript = 'npm run generate:web';
|
|
123
|
-
else if (serverOnly)
|
|
124
|
-
generateScript = 'npm run generate:server';
|
|
125
|
-
const { code } = shell.cd('server/').exec(generateScript);
|
|
99
|
+
// Staging 目录:generator 全量写入到 node_modules/.cache/*,CLI 再增量分发。
|
|
100
|
+
// 不放在 src/ 下,避免任何 watcher 感知到中间态的进出。
|
|
101
|
+
// 用绝对路径以便 (a) 在 shell.cd('server/') 之后仍指向正确位置;
|
|
102
|
+
// (b) 通过 env 传给 generator 子进程时无歧义。
|
|
103
|
+
const cwd = process.cwd();
|
|
104
|
+
const WEB_STAGING = (0, path_1.resolve)(cwd, 'web/node_modules/.cache/gadmin-web');
|
|
105
|
+
const SERVER_STAGING = (0, path_1.resolve)(cwd, 'server/node_modules/.cache/gadmin-server');
|
|
106
|
+
const WEB_ROUTES_DIR = (0, path_1.resolve)(cwd, 'web/src/routes');
|
|
107
|
+
// 每轮清空对应 staging,让「本轮 staging = 本轮完整产物」这个不变量成立,
|
|
108
|
+
// 后续孤儿清理直接依赖它。父目录(如 web/node_modules/.cache/)不存在时先创建,
|
|
109
|
+
// 避免 generator 首次运行时因缺目录报错。
|
|
110
|
+
fs.rmSync(WEB_STAGING, { recursive: true, force: true });
|
|
111
|
+
fs.mkdirSync(WEB_STAGING, { recursive: true });
|
|
112
|
+
fs.rmSync(SERVER_STAGING, { recursive: true, force: true });
|
|
113
|
+
fs.mkdirSync(SERVER_STAGING, { recursive: true });
|
|
114
|
+
// 执行代码生成(走 npm run 脚本)。通过 env 传 staging 路径,
|
|
115
|
+
// generator 会优先使用 env 覆盖 .prisma schema 里的 output 配置。
|
|
116
|
+
const generateScript = devMode ? 'npm run generate:dev' : 'npm run generate';
|
|
117
|
+
const { code } = shell.cd('server/').exec(generateScript, {
|
|
118
|
+
env: Object.assign(Object.assign({}, process.env), { GADMIN_WEB_STAGING: WEB_STAGING, GADMIN_SERVER_STAGING: SERVER_STAGING, GADMIN_WEB_ROUTES_DIR: WEB_ROUTES_DIR }),
|
|
119
|
+
});
|
|
126
120
|
if (code !== 0) {
|
|
127
121
|
process.exit(code);
|
|
128
122
|
}
|
|
129
123
|
shell.cd('../');
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
124
|
+
// 分发策略:先集中写 server/,再集中写 web/。
|
|
125
|
+
// 目的是把 nest --watch 的 2 波重启合成 1 波,把 Vite 的 4 波 HMR 合成 1 波。
|
|
126
|
+
//
|
|
127
|
+
// staging 结构参考:
|
|
128
|
+
// WEB_STAGING/
|
|
129
|
+
// routes/{model}/*.tsx → 首次落地到 web/src/routes/{model}
|
|
130
|
+
// props/{model}/*.ts, resources.tsx, models.index.tsx → diff-copy 到 web/src/generated/
|
|
131
|
+
// SERVER_STAGING/
|
|
132
|
+
// modules.index.ts → diff-copy 到 server/src/generated/modules.index.ts
|
|
133
|
+
// {model}/dto/{model}.form.validator.ts → diff-copy 到 web/src/generated/props/{model}/form.validator.ts(跨端)
|
|
134
|
+
// {model}/**(其余) → 首次落地到 server/src/modules/{model}/**
|
|
135
|
+
// 收集 server staging 里的 model 列表:凡有 dto/{name}.form.validator.ts 的一级子目录即视为 model
|
|
136
|
+
const stagingModels = fs.existsSync(SERVER_STAGING)
|
|
137
|
+
? fs
|
|
138
|
+
.readdirSync(SERVER_STAGING, { withFileTypes: true })
|
|
139
|
+
.filter((e) => e.isDirectory())
|
|
140
|
+
.map((e) => e.name)
|
|
141
|
+
.filter((name) => fs.existsSync((0, path_1.join)(SERVER_STAGING, name, 'dto', `${name}.form.validator.ts`)))
|
|
142
|
+
: [];
|
|
143
|
+
// ============ 阶段 1:server 集中写入 ============
|
|
144
|
+
if (fs.existsSync(SERVER_STAGING)) {
|
|
145
|
+
// 1.1 modules.index.ts —— 顶层入口
|
|
146
|
+
const modulesIndexSrc = (0, path_1.join)(SERVER_STAGING, 'modules.index.ts');
|
|
147
|
+
if (fs.existsSync(modulesIndexSrc)) {
|
|
148
|
+
(0, sync_fs_1.copyFileIfChanged)(modulesIndexSrc, 'server/src/generated/modules.index.ts');
|
|
149
|
+
}
|
|
150
|
+
// 1.2 逐 model 首次落地到 server/src/modules/{model}(跳过 form.validator,它归 web 阶段)
|
|
151
|
+
for (const modelName of stagingModels) {
|
|
152
|
+
const modelStaging = (0, path_1.join)(SERVER_STAGING, modelName);
|
|
153
|
+
const validatorInStaging = (0, path_1.join)('dto', `${modelName}.form.validator.ts`);
|
|
154
|
+
for (const rel of (0, sync_fs_1.listFilesRecursive)(modelStaging)) {
|
|
155
|
+
if (rel === validatorInStaging)
|
|
156
|
+
continue;
|
|
157
|
+
const dest = (0, path_1.join)('server/src/modules', modelName, rel);
|
|
158
|
+
if (fs.existsSync(dest))
|
|
159
|
+
continue;
|
|
160
|
+
fs.mkdirSync((0, path_1.dirname)(dest), { recursive: true });
|
|
161
|
+
fs.copyFileSync((0, path_1.join)(modelStaging, rel), dest);
|
|
162
|
+
}
|
|
147
163
|
}
|
|
148
164
|
}
|
|
149
|
-
//
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
shell.mv('-f', `server/src/generated/${modelName}/dto/${modelName}.form.validator.ts`, `web/src/generated/props/${modelName}/form.validator.ts`);
|
|
162
|
-
fs.cpSync(`server/src/generated/${modelName}`, `server/src/modules/${modelName}`, { recursive: true, force: false });
|
|
163
|
-
shell.rm('-f', `server/src/generated/${modelName}/*.ts`);
|
|
164
|
-
// 只删除生成器产生的 dto/entities 文件,保留用户自定义的文件(如 canvas.dto.ts)
|
|
165
|
-
for (const subDir of ['dto', 'entities']) {
|
|
166
|
-
const generatedSubDir = `server/src/generated/${modelName}/${subDir}`;
|
|
167
|
-
const modulesSubDir = `server/src/modules/${modelName}/${subDir}`;
|
|
168
|
-
if (fs.existsSync(modulesSubDir) && fs.existsSync(generatedSubDir)) {
|
|
169
|
-
for (const file of fs.readdirSync(generatedSubDir)) {
|
|
170
|
-
fs.rmSync((0, path_1.join)(modulesSubDir, file), { force: true });
|
|
171
|
-
}
|
|
172
|
-
// 目录已空则一并删除
|
|
173
|
-
if (fs.readdirSync(modulesSubDir).length === 0) {
|
|
174
|
-
fs.rmdirSync(modulesSubDir);
|
|
175
|
-
}
|
|
165
|
+
// ============ 阶段 2:web 集中写入 ============
|
|
166
|
+
const webKeep = new Set();
|
|
167
|
+
// 2.1 web staging 主产物
|
|
168
|
+
if (fs.existsSync(WEB_STAGING)) {
|
|
169
|
+
for (const rel of (0, sync_fs_1.listFilesRecursive)(WEB_STAGING)) {
|
|
170
|
+
const src = (0, path_1.join)(WEB_STAGING, rel);
|
|
171
|
+
if (rel.startsWith('routes' + path_1.sep) || rel.startsWith('routes/')) {
|
|
172
|
+
// 首次落地:不覆盖用户已编辑的页面
|
|
173
|
+
const destRoutes = (0, path_1.join)('web/src/routes', rel.replace(/^routes[\/\\]/, ''));
|
|
174
|
+
if (!fs.existsSync(destRoutes)) {
|
|
175
|
+
fs.mkdirSync((0, path_1.dirname)(destRoutes), { recursive: true });
|
|
176
|
+
fs.copyFileSync(src, destRoutes);
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
|
-
|
|
179
|
+
else {
|
|
180
|
+
(0, sync_fs_1.copyFileIfChanged)(src, (0, path_1.join)('web/src/generated', rel));
|
|
181
|
+
webKeep.add(rel);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
179
184
|
}
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
shell.sed('-i', prismaRegex, 'declare const runtime : any', 'web/src/generated/types/prisma.types.d.ts');
|
|
186
|
-
shell.sed('-i', new RegExp('bigint', 'g'), 'number', 'web/src/generated/types/prisma.types.d.ts');
|
|
187
|
-
fs.copyFileSync('config/.types.d.ts', 'web/src/generated/types/config.types.d.ts');
|
|
185
|
+
// 2.2 form.validator.ts(server generator 产出,需搬到 web/src/generated/props/{model}/)
|
|
186
|
+
for (const modelName of stagingModels) {
|
|
187
|
+
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));
|
|
189
|
+
webKeep.add(validatorRel);
|
|
188
190
|
}
|
|
191
|
+
// 2.3 Prisma / config 类型
|
|
192
|
+
fs.mkdirSync('web/src/generated/types', { recursive: true });
|
|
193
|
+
const prismaSrc = 'server/node_modules/.prisma/client/index.d.ts';
|
|
194
|
+
const prismaRel = 'types/prisma.types.d.ts';
|
|
195
|
+
let prismaContent = fs.readFileSync(prismaSrc, 'utf8');
|
|
196
|
+
prismaContent = prismaContent.replace("import * as runtime from '@prisma/client/runtime/index';", 'declare const runtime : any');
|
|
197
|
+
prismaContent = prismaContent.replace(/bigint/g, 'number');
|
|
198
|
+
(0, sync_fs_1.writeFileIfChanged)((0, path_1.join)('web/src/generated', prismaRel), prismaContent);
|
|
199
|
+
webKeep.add(prismaRel);
|
|
200
|
+
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));
|
|
202
|
+
webKeep.add(configRel);
|
|
203
|
+
// 2.4 孤儿清理:删除 web/src/generated 下所有本轮未产出的文件(例如被删除的 model)
|
|
204
|
+
(0, sync_fs_1.pruneOrphans)('web/src/generated', webKeep);
|
|
189
205
|
// 分发全部完成后,若 server/package.json 定义了 postgadminGenerate,
|
|
190
206
|
// 触发它。项目侧可用来做 dev server 通知、缓存清理等自定义收尾。
|
|
191
|
-
|
|
192
|
-
this.runProjectPostHook({ webOnly, serverOnly });
|
|
207
|
+
this.runProjectPostHook();
|
|
193
208
|
process.exit(0);
|
|
194
209
|
});
|
|
195
210
|
}
|
|
196
|
-
runProjectPostHook(
|
|
211
|
+
runProjectPostHook() {
|
|
197
212
|
var _a;
|
|
198
213
|
const pkgPath = 'server/package.json';
|
|
199
214
|
let pkg;
|
|
@@ -205,12 +220,11 @@ class PrismaAction extends abstract_action_1.AbstractAction {
|
|
|
205
220
|
}
|
|
206
221
|
if (!((_a = pkg === null || pkg === void 0 ? void 0 : pkg.scripts) === null || _a === void 0 ? void 0 : _a.postgadminGenerate))
|
|
207
222
|
return;
|
|
208
|
-
|
|
209
|
-
console.log(chalk.gray(`Running postgadminGenerate hook (mode=${mode}) ...`));
|
|
223
|
+
console.log(chalk.gray('Running postgadminGenerate hook ...'));
|
|
210
224
|
const { code } = shell
|
|
211
225
|
.cd('server/')
|
|
212
226
|
.exec('npm run --silent postgadminGenerate', {
|
|
213
|
-
env: Object.assign(
|
|
227
|
+
env: Object.assign({}, process.env),
|
|
214
228
|
});
|
|
215
229
|
shell.cd('../');
|
|
216
230
|
if (code !== 0) {
|
|
@@ -29,15 +29,11 @@ class GadminCollection extends abstract_collection_1.AbstractCollection {
|
|
|
29
29
|
execute: { get: () => super.execute }
|
|
30
30
|
});
|
|
31
31
|
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
-
if (['prisma', 'prisma:dev'
|
|
32
|
+
if (['prisma', 'prisma:dev'].includes(name)) {
|
|
33
33
|
const prisma = new prisma_action_1.PrismaAction();
|
|
34
34
|
const opts = [];
|
|
35
35
|
if (name.includes(':dev'))
|
|
36
36
|
opts.push({ name: 'dev', value: true });
|
|
37
|
-
if (name.includes(':web'))
|
|
38
|
-
opts.push({ name: 'webOnly', value: true });
|
|
39
|
-
if (name.includes(':server'))
|
|
40
|
-
opts.push({ name: 'serverOnly', value: true });
|
|
41
37
|
return yield prisma.handle([], opts);
|
|
42
38
|
}
|
|
43
39
|
const schematic = this.validate(name);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* 增量文件同步工具,专为「Prisma 生成产物 → dev server 目录」场景设计。
|
|
4
|
+
*
|
|
5
|
+
* 目标:只在内容真正变化时触发写操作,避免 HMR/dev server 因 mtime 抖动而
|
|
6
|
+
* 全量重扫。所有工具都基于「按字节比对旧内容」实现,不依赖 hash 与 manifest。
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* 内容一致时不写入。
|
|
10
|
+
* @returns true 表示实际写入了文件;false 表示内容未变、跳过写入
|
|
11
|
+
*/
|
|
12
|
+
export declare function writeFileIfChanged(filePath: string, content: string | Buffer): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* 内容一致时不写入。用于二进制/文本无差别的文件复制。
|
|
15
|
+
* @returns true 表示实际复制;false 表示内容未变、跳过
|
|
16
|
+
*/
|
|
17
|
+
export declare function copyFileIfChanged(src: string, dest: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* 递归列出目录下所有文件(相对路径)。目录不存在时返回空数组。
|
|
20
|
+
*/
|
|
21
|
+
export declare function listFilesRecursive(dir: string): string[];
|
|
22
|
+
/**
|
|
23
|
+
* 删除文件;若父目录变空则一并删除(向上递归到 stopAt 为止,不含 stopAt)。
|
|
24
|
+
*/
|
|
25
|
+
export declare function removeFileAndPruneEmptyDirs(filePath: string, stopAt: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* 把 srcDir 的内容增量同步到 destDir:
|
|
28
|
+
* - 新增/修改:按 diff copy 写入
|
|
29
|
+
* - 删除:destDir 中不在 srcDir 里的文件会被删除(以及空父目录)
|
|
30
|
+
*
|
|
31
|
+
* 注意:destDir 只由本函数管理时才可安全调用(例如 web/src/generated)。
|
|
32
|
+
*/
|
|
33
|
+
export declare function syncDir(srcDir: string, destDir: string): {
|
|
34
|
+
written: number;
|
|
35
|
+
deleted: number;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* 删除 destDir 下所有不在 keepFiles 里的文件(以及随之空掉的父目录)。
|
|
39
|
+
* 用于「本轮 model 目录被删」的孤儿清理场景。
|
|
40
|
+
*/
|
|
41
|
+
export declare function pruneOrphans(destDir: string, keepFiles: Set<string>): number;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pruneOrphans = exports.syncDir = exports.removeFileAndPruneEmptyDirs = exports.listFilesRecursive = exports.copyFileIfChanged = exports.writeFileIfChanged = void 0;
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
/**
|
|
7
|
+
* 增量文件同步工具,专为「Prisma 生成产物 → dev server 目录」场景设计。
|
|
8
|
+
*
|
|
9
|
+
* 目标:只在内容真正变化时触发写操作,避免 HMR/dev server 因 mtime 抖动而
|
|
10
|
+
* 全量重扫。所有工具都基于「按字节比对旧内容」实现,不依赖 hash 与 manifest。
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* 内容一致时不写入。
|
|
14
|
+
* @returns true 表示实际写入了文件;false 表示内容未变、跳过写入
|
|
15
|
+
*/
|
|
16
|
+
function writeFileIfChanged(filePath, content) {
|
|
17
|
+
try {
|
|
18
|
+
const prev = fs.readFileSync(filePath);
|
|
19
|
+
const next = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
20
|
+
if (prev.equals(next))
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
catch (_a) {
|
|
24
|
+
// 目标文件不存在 → 走写入分支
|
|
25
|
+
}
|
|
26
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
27
|
+
fs.writeFileSync(filePath, content);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
exports.writeFileIfChanged = writeFileIfChanged;
|
|
31
|
+
/**
|
|
32
|
+
* 内容一致时不写入。用于二进制/文本无差别的文件复制。
|
|
33
|
+
* @returns true 表示实际复制;false 表示内容未变、跳过
|
|
34
|
+
*/
|
|
35
|
+
function copyFileIfChanged(src, dest) {
|
|
36
|
+
const buf = fs.readFileSync(src);
|
|
37
|
+
return writeFileIfChanged(dest, buf);
|
|
38
|
+
}
|
|
39
|
+
exports.copyFileIfChanged = copyFileIfChanged;
|
|
40
|
+
/**
|
|
41
|
+
* 递归列出目录下所有文件(相对路径)。目录不存在时返回空数组。
|
|
42
|
+
*/
|
|
43
|
+
function listFilesRecursive(dir) {
|
|
44
|
+
if (!fs.existsSync(dir))
|
|
45
|
+
return [];
|
|
46
|
+
const out = [];
|
|
47
|
+
const walk = (rel) => {
|
|
48
|
+
const abs = path.join(dir, rel);
|
|
49
|
+
for (const entry of fs.readdirSync(abs, { withFileTypes: true })) {
|
|
50
|
+
const nextRel = rel ? path.join(rel, entry.name) : entry.name;
|
|
51
|
+
if (entry.isDirectory()) {
|
|
52
|
+
walk(nextRel);
|
|
53
|
+
}
|
|
54
|
+
else if (entry.isFile()) {
|
|
55
|
+
out.push(nextRel);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
walk('');
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
exports.listFilesRecursive = listFilesRecursive;
|
|
63
|
+
/**
|
|
64
|
+
* 删除文件;若父目录变空则一并删除(向上递归到 stopAt 为止,不含 stopAt)。
|
|
65
|
+
*/
|
|
66
|
+
function removeFileAndPruneEmptyDirs(filePath, stopAt) {
|
|
67
|
+
try {
|
|
68
|
+
fs.rmSync(filePath, { force: true });
|
|
69
|
+
}
|
|
70
|
+
catch (_a) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let dir = path.dirname(filePath);
|
|
74
|
+
const stopAbs = path.resolve(stopAt);
|
|
75
|
+
while (path.resolve(dir).startsWith(stopAbs) && path.resolve(dir) !== stopAbs) {
|
|
76
|
+
try {
|
|
77
|
+
const entries = fs.readdirSync(dir);
|
|
78
|
+
if (entries.length > 0)
|
|
79
|
+
return;
|
|
80
|
+
fs.rmdirSync(dir);
|
|
81
|
+
}
|
|
82
|
+
catch (_b) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
dir = path.dirname(dir);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
exports.removeFileAndPruneEmptyDirs = removeFileAndPruneEmptyDirs;
|
|
89
|
+
/**
|
|
90
|
+
* 把 srcDir 的内容增量同步到 destDir:
|
|
91
|
+
* - 新增/修改:按 diff copy 写入
|
|
92
|
+
* - 删除:destDir 中不在 srcDir 里的文件会被删除(以及空父目录)
|
|
93
|
+
*
|
|
94
|
+
* 注意:destDir 只由本函数管理时才可安全调用(例如 web/src/generated)。
|
|
95
|
+
*/
|
|
96
|
+
function syncDir(srcDir, destDir) {
|
|
97
|
+
const srcFiles = new Set(listFilesRecursive(srcDir));
|
|
98
|
+
const destFiles = listFilesRecursive(destDir);
|
|
99
|
+
let written = 0;
|
|
100
|
+
for (const rel of srcFiles) {
|
|
101
|
+
if (copyFileIfChanged(path.join(srcDir, rel), path.join(destDir, rel))) {
|
|
102
|
+
written++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
let deleted = 0;
|
|
106
|
+
for (const rel of destFiles) {
|
|
107
|
+
if (!srcFiles.has(rel)) {
|
|
108
|
+
removeFileAndPruneEmptyDirs(path.join(destDir, rel), destDir);
|
|
109
|
+
deleted++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { written, deleted };
|
|
113
|
+
}
|
|
114
|
+
exports.syncDir = syncDir;
|
|
115
|
+
/**
|
|
116
|
+
* 删除 destDir 下所有不在 keepFiles 里的文件(以及随之空掉的父目录)。
|
|
117
|
+
* 用于「本轮 model 目录被删」的孤儿清理场景。
|
|
118
|
+
*/
|
|
119
|
+
function pruneOrphans(destDir, keepFiles) {
|
|
120
|
+
let deleted = 0;
|
|
121
|
+
for (const rel of listFilesRecursive(destDir)) {
|
|
122
|
+
if (!keepFiles.has(rel)) {
|
|
123
|
+
removeFileAndPruneEmptyDirs(path.join(destDir, rel), destDir);
|
|
124
|
+
deleted++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return deleted;
|
|
128
|
+
}
|
|
129
|
+
exports.pruneOrphans = pruneOrphans;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gadmin2n/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.158",
|
|
4
4
|
"description": "Gadmin - modern, fast, powerful node.js web framework (@cli)",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@angular-devkit/core": "13.3.2",
|
|
48
48
|
"@angular-devkit/schematics": "13.3.2",
|
|
49
49
|
"@angular-devkit/schematics-cli": "13.3.2",
|
|
50
|
-
"@gadmin2n/schematics": "^0.0.
|
|
50
|
+
"@gadmin2n/schematics": "^0.0.128",
|
|
51
51
|
"abc": "^0.6.1",
|
|
52
52
|
"chalk": "3.0.0",
|
|
53
53
|
"chokidar": "3.5.3",
|