@gadmin2n/cli 0.0.105 → 0.0.107

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 (50) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/actions/new.action.js +140 -94
  3. package/actions/update.action.d.ts +1 -0
  4. package/actions/update.action.js +564 -630
  5. package/commands/command.input.d.ts +1 -6
  6. package/commands/command.input.js +1 -7
  7. package/commands/command.loader.js +0 -2
  8. package/commands/new.command.js +8 -3
  9. package/commands/update.command.js +27 -37
  10. package/lib/template-merge/base-store.d.ts +16 -0
  11. package/lib/template-merge/base-store.js +127 -0
  12. package/lib/template-merge/binary-detect.d.ts +7 -0
  13. package/lib/template-merge/binary-detect.js +36 -0
  14. package/lib/template-merge/classify.d.ts +10 -0
  15. package/lib/template-merge/classify.js +61 -0
  16. package/lib/template-merge/config-validate.d.ts +5 -0
  17. package/lib/template-merge/config-validate.js +33 -0
  18. package/lib/template-merge/excludes.d.ts +7 -0
  19. package/lib/template-merge/excludes.js +109 -0
  20. package/lib/template-merge/index.d.ts +9 -8
  21. package/lib/template-merge/index.js +10 -8
  22. package/lib/template-merge/materialize.d.ts +18 -0
  23. package/lib/template-merge/materialize.js +129 -0
  24. package/lib/template-merge/merge3way.d.ts +15 -0
  25. package/lib/template-merge/merge3way.js +54 -0
  26. package/lib/template-merge/residual-scan.d.ts +10 -0
  27. package/lib/template-merge/residual-scan.js +31 -0
  28. package/lib/template-merge/types.d.ts +27 -0
  29. package/lib/template-merge/types.js +5 -0
  30. package/lib/ui/messages.d.ts +0 -2
  31. package/lib/ui/messages.js +0 -2
  32. package/package.json +2 -2
  33. package/commands/build.command.d.ts +0 -5
  34. package/commands/build.command.js +0 -50
  35. package/lib/template-merge/config-loader.d.ts +0 -15
  36. package/lib/template-merge/config-loader.js +0 -38
  37. package/lib/template-merge/env-merger.d.ts +0 -5
  38. package/lib/template-merge/env-merger.js +0 -57
  39. package/lib/template-merge/glob.d.ts +0 -5
  40. package/lib/template-merge/glob.js +0 -78
  41. package/lib/template-merge/json-merger.d.ts +0 -5
  42. package/lib/template-merge/json-merger.js +0 -269
  43. package/lib/template-merge/merger.d.ts +0 -30
  44. package/lib/template-merge/merger.js +0 -2
  45. package/lib/template-merge/prisma-merger.d.ts +0 -5
  46. package/lib/template-merge/prisma-merger.js +0 -112
  47. package/lib/template-merge/registry.d.ts +0 -25
  48. package/lib/template-merge/registry.js +0 -42
  49. package/lib/template-merge/ts-module-merger.d.ts +0 -16
  50. package/lib/template-merge/ts-module-merger.js +0 -193
package/CHANGELOG.md ADDED
@@ -0,0 +1,17 @@
1
+ ## [Unreleased]
2
+
3
+ ### BREAKING CHANGES
4
+
5
+ - `gadmin2 update` 改造为 git-merge 风格三方合并:
6
+ - base 由 `.gadmin/.meta.json` 中记录的 `@gadmin2n/schematics` npm 版本号唯一标识,单次 update 时 `npm pack` 物化到 `os.tmpdir()`,结束即清理;不再写入 `.gadmin/template-base/` 目录
7
+ - 删除 4 个 Smart Merger 与逐文件 inquirer prompt
8
+ - `gadmin-cli.json` 中 `templateUpdate.policies` / `templateUpdate.mergers` 字段已下线,存在则启动报错
9
+ - 新增 `gadmin2 update status` 子命令、`--ours` / `--theirs` flag
10
+ - `gadmin2 update diff` 升级为三方展示
11
+ - `gadmin2 update` 现在每次执行都需要访问 npm registry(用于物化 base 对应版本的 schematics 包);CI 环境请预热 npm 缓存或配置内网 registry。
12
+
13
+ ### Migration
14
+
15
+ - 老实例首次跑新 CLI 时会进入"首次接入"分支:写入 `.gadmin/.meta.json`(含 `schematicsVersion`)、`.gadmin/README.md`,退出。无数据风险。
16
+ - 含废弃字段的 `gadmin-cli.json` 必须先删除字段才能继续。
17
+ - 之前版本生成的 `.gadmin/template-base/` 目录现在已被忽略,可安全删除并 commit。
@@ -37,105 +37,107 @@ class NewAction extends abstract_action_1.AbstractAction {
37
37
  const shouldSkipInstall = options.some((option) => option.name === 'skip-install' && option.value === true);
38
38
  const shouldSkipGit = options.some((option) => option.name === 'skip-git' && option.value === true);
39
39
  const projectDirectory = getProjectDirectory(getApplicationNameInput(inputs), directoryOption);
40
+ // 落盘用户提供的环境配置(dry-run 模式跳过,因为目录可能未实际生成)
41
+ if (!isDryRunEnabled) {
42
+ yield applyEnvConfig(projectDirectory, options).catch((err) => {
43
+ console.error(chalk.yellow(`[warn] Failed to apply env config: ${err && err.message ? err.message : err}`));
44
+ });
45
+ }
40
46
  if (!shouldSkipInstall) {
41
- if (options.find(option => option.name === 'server-framework' && option.value === 'nest')) {
42
- console.info('Begin to install server dependence ...');
43
- yield installPackages(options, isDryRunEnabled, (0, path_1.join)(projectDirectory, 'server'));
44
- console.info();
45
- }
46
- if (options.find(option => option.name === 'with-web' && option.value === true)) {
47
- console.info('Begin to install web dependence ...');
48
- yield installPackages(options, isDryRunEnabled, (0, path_1.join)(projectDirectory, 'web'));
49
- console.info();
50
- }
47
+ console.info('Begin to install server dependence ...');
48
+ yield installPackages(options, isDryRunEnabled, (0, path_1.join)(projectDirectory, 'server'));
49
+ console.info();
50
+ console.info('Begin to install web dependence ...');
51
+ yield installPackages(options, isDryRunEnabled, (0, path_1.join)(projectDirectory, 'web'));
52
+ console.info();
51
53
  }
52
54
  if (!isDryRunEnabled) {
53
55
  if (!shouldSkipGit) {
54
56
  yield initializeGitRepository(projectDirectory);
55
57
  yield createGitIgnoreFile(projectDirectory);
56
58
  }
57
- const inputPackageManager = (options.find((option) => option.name === 'package-manager').value || 'yarn').toLowerCase();
59
+ const inputPackageManager = (options.find((option) => option.name === 'package-manager')
60
+ .value || 'yarn').toLowerCase();
61
+ // 收集仍未填写、需要在引导中提示用户处理的环境变量
62
+ const viteAgentId = getOptionByName(options, 'vite-agent-id') || '';
63
+ const taihuToken = getOptionByName(options, 'taihu-app-token') || '';
64
+ const databaseUrl = getOptionByName(options, 'database-url') || '';
65
+ const missingEnv = [];
66
+ if (!viteAgentId) {
67
+ missingEnv.push({
68
+ var: 'VITE_AGENT_ID',
69
+ file: 'web/.env.local',
70
+ desc: 'knot Agent ID',
71
+ });
72
+ }
73
+ if (!taihuToken) {
74
+ missingEnv.push({
75
+ var: 'TAIHU_APP_TOKEN',
76
+ file: 'server/.env',
77
+ desc: '太湖应用 ID — 替换 TAIHU_APP_TOKEN= 后的 token',
78
+ });
79
+ }
80
+ if (!databaseUrl) {
81
+ missingEnv.push({
82
+ var: 'DATABASE_URL',
83
+ file: 'server/.env.local',
84
+ desc: 'PostgreSQL 连接串',
85
+ });
86
+ }
58
87
  console.info();
59
88
  console.info(ui_1.MESSAGES.PACKAGE_MANAGER_INSTALLATION_SUCCEED(projectDirectory));
60
89
  console.info(ui_1.MESSAGES.GET_STARTED_INFORMATION);
61
90
  console.info();
62
91
  console.info(chalk.gray(ui_1.MESSAGES.CHANGE_DIR_COMMAND(projectDirectory)));
92
+ let step = 1;
93
+ // Step: Install dependencies (only if user skipped install)
63
94
  if (shouldSkipInstall) {
64
- console.info('#Install dependence: ');
65
- if (options.find(option => option.name === 'server-framework' && option.value === 'nest')) {
66
- if (inputPackageManager === 'yarn') {
67
- if (options.find(option => option.name === 'with-web' && option.value === true)) {
68
- console.info(chalk.gray(`cd server && ${inputPackageManager}; cd ../web && ${inputPackageManager}; cd ..`));
69
- }
70
- else {
71
- console.info(chalk.gray(`cd server && ${inputPackageManager}; cd ..`));
72
- }
73
- }
74
- else {
75
- if (options.find(option => option.name === 'with-web' && option.value === true)) {
76
- console.info(chalk.gray(`cd server && ${inputPackageManager} install; cd ../web && ${inputPackageManager} install; cd ..`));
77
- }
78
- else {
79
- console.info(chalk.gray(`cd server && ${inputPackageManager} install; cd ..`));
80
- }
81
- }
95
+ console.info(chalk.bold(`# ${step}. Install dependencies:`));
96
+ if (inputPackageManager === 'yarn') {
97
+ console.info(chalk.gray(` cd server && ${inputPackageManager}; cd ../web && ${inputPackageManager}; cd ..`));
82
98
  }
83
- else { // go iris
84
- if (inputPackageManager === 'yarn') {
85
- if (options.find(option => option.name === 'with-web' && option.value === true)) {
86
- console.info(chalk.gray(`cd server && go get && cd bin/prisma-parse && ${inputPackageManager}; cd ../../web && ${inputPackageManager}; cd ..`));
87
- }
88
- else {
89
- console.info(chalk.gray(`cd server && go get && cd bin/prisma-parse && ${inputPackageManager}; cd ../..`));
90
- }
91
- }
92
- else {
93
- if (options.find(option => option.name === 'with-web' && option.value === true)) {
94
- console.info(chalk.gray(`cd server && go get && cd bin/prisma-parse && ${inputPackageManager} install; cd ../../web && ${inputPackageManager} install; cd ..`));
95
- }
96
- else {
97
- console.info(chalk.gray(`cd server && go get && cd bin/prisma-parse && ${inputPackageManager} install; cd ../..`));
98
- }
99
- }
99
+ else {
100
+ console.info(chalk.gray(` cd server && ${inputPackageManager} install; cd ../web && ${inputPackageManager} install; cd ..`));
100
101
  }
101
102
  console.info();
103
+ step++;
102
104
  }
103
- if (options.find(option => option.name === 'with-web' && option.value === true)) {
104
- console.info("#Change dev's coding agent(knot Agent) id:");
105
- console.info(chalk.gray("vi web/.env.local"));
105
+ // Step: Configure environment variables (only items the user skipped)
106
+ if (missingEnv.length > 0) {
107
+ console.info(chalk.bold(`# ${step}. Configure environment variables:`));
108
+ for (const m of missingEnv) {
109
+ console.info(` - ${chalk.cyan(m.var)} (${m.desc})`);
110
+ console.info(chalk.gray(` vi ${m.file}`));
111
+ }
106
112
  console.info();
113
+ step++;
107
114
  }
108
- console.info("#Change dev's env database config:");
109
- console.info(chalk.gray("vi server/.env.local"));
115
+ // Step: Generate code from prisma schema
116
+ console.info(chalk.bold(`# ${step}. Generate code from prisma schema:`));
117
+ console.info(chalk.gray(' gadmin2 g prisma:dev'));
110
118
  console.info();
111
- console.info("#Generate code by config/schema.prisma:");
112
- console.info(chalk.gray("gadmin2 g prisma:dev"));
113
- console.info();
114
- if (options.find(option => option.name === 'server-framework' && option.value === 'nest')) {
115
- console.info('#Start server devServer:');
116
- console.info(chalk.gray('cd server'));
117
- if (inputPackageManager === 'yarn') {
118
- console.info(chalk.gray(`${inputPackageManager} seed (only run once)`));
119
- console.info(chalk.gray(`${inputPackageManager} start:dev`));
120
- }
121
- else {
122
- console.info(chalk.gray(`${inputPackageManager} run seed (only run once)`));
123
- console.info(chalk.gray(`${inputPackageManager} run start:dev`));
124
- }
119
+ step++;
120
+ // Step: Start server (nest)
121
+ console.info(chalk.bold(`# ${step}. Start server (terminal 1):`));
122
+ console.info(chalk.gray(' cd server'));
123
+ if (inputPackageManager === 'yarn') {
124
+ console.info(chalk.gray(` ${inputPackageManager} seed (only run once)`));
125
+ console.info(chalk.gray(` ${inputPackageManager} start:dev`));
125
126
  }
126
127
  else {
127
- console.info(chalk.gray(`swag init -g main.go && go run main.go`));
128
+ console.info(chalk.gray(` ${inputPackageManager} run seed (only run once)`));
129
+ console.info(chalk.gray(` ${inputPackageManager} run start:dev`));
128
130
  }
129
- if (options.find(option => option.name === 'with-web' && option.value === true)) {
130
- console.info();
131
- console.info('#Start web devServer (In anthor terminal): ');
132
- console.info(chalk.gray('cd web'));
133
- if (inputPackageManager === 'yarn') {
134
- console.info(chalk.gray(`${inputPackageManager} dev`));
135
- }
136
- else {
137
- console.info(chalk.gray(`${inputPackageManager} run dev`));
138
- }
131
+ console.info();
132
+ step++;
133
+ // Step: Start web (always, since web is now mandatory)
134
+ console.info(chalk.bold(`# ${step}. Start web (terminal 2):`));
135
+ console.info(chalk.gray(' cd web'));
136
+ if (inputPackageManager === 'yarn') {
137
+ console.info(chalk.gray(` ${inputPackageManager} dev`));
138
+ }
139
+ else {
140
+ console.info(chalk.gray(` ${inputPackageManager} run dev`));
139
141
  }
140
142
  printCollective();
141
143
  }
@@ -161,24 +163,23 @@ const askForMissingInformation = (inputs, options) => __awaiter(void 0, void 0,
161
163
  const answers = yield prompt(questions);
162
164
  replaceInputMissingInformation(inputs, answers);
163
165
  }
164
- // 默认使用 gadmin2-game-angle-demo 模板、nest + react(antd) 技术栈
165
- replaceInputMissingInformation(options, { 'template': commands_1.Template.GameAngle });
166
- replaceInputMissingInformation(options, { 'server-framework': 'nest' });
167
- if (getOptionByName(options, 'with-web') === undefined) {
168
- const answers = yield askForWithWeb();
169
- answers['with-web'] = answers['with-web'] === 'yes';
170
- replaceInputMissingInformation(options, answers);
171
- }
166
+ // 默认使用 gadmin2-game-angle-demo 模板;server-framework new.command.ts 固定为 'nest'
167
+ replaceInputMissingInformation(options, { template: commands_1.Template.GameAngle });
168
+ // 收集环境配置(支持空字符串跳过)
169
+ yield promptIfMissing(options, 'vite-agent-id', 'VITE_AGENT_ID (knot Agent ID, leave blank to skip):');
170
+ yield promptIfMissing(options, 'taihu-app-token', 'TAIHU_APP_TOKEN (太湖应用 ID, leave blank to skip):');
171
+ yield promptIfMissing(options, 'database-url', 'DATABASE_URL (postgresql://user:pwd@host:port/db?schema=public, leave blank to skip):');
172
172
  });
173
- const askForWithWeb = () => __awaiter(void 0, void 0, void 0, function* () {
174
- const questions = [
175
- (0, questions_1.generateSelect)('with-web')(ui_1.MESSAGES.WITH_WEB_QUESTION)([
176
- commands_1.WithWeb.Yes,
177
- commands_1.WithWeb.No,
178
- ]),
179
- ];
173
+ /**
174
+ * 仅当 CLI 未提供该 option 时弹出 inquirer 提问。
175
+ * 用户直接回车则填入空字符串,后续逻辑会把空串视为"跳过"。
176
+ */
177
+ const promptIfMissing = (options, name, message) => __awaiter(void 0, void 0, void 0, function* () {
178
+ if (getOptionByName(options, name) !== undefined)
179
+ return;
180
180
  const prompt = inquirer.createPromptModule();
181
- return yield prompt(questions);
181
+ const answers = yield prompt([(0, questions_1.generateInput)(name, message)('')]);
182
+ replaceInputMissingInformation(options, answers);
182
183
  });
183
184
  const replaceInputMissingInformation = (inputs, answers) => {
184
185
  return inputs.map((input) => (input.value =
@@ -194,12 +195,57 @@ const generateApplicationFiles = (args, options) => __awaiter(void 0, void 0, vo
194
195
  const mapSchematicOptions = (options) => {
195
196
  return options.reduce((schematicOptions, option) => {
196
197
  if (option.name !== 'skip-install' &&
197
- option.value !== 'package-manager') {
198
+ option.value !== 'package-manager' &&
199
+ option.name !== 'vite-agent-id' &&
200
+ option.name !== 'taihu-app-token' &&
201
+ option.name !== 'database-url') {
198
202
  schematicOptions.push(new schematics_1.SchematicOption(option.name, option.value));
199
203
  }
200
204
  return schematicOptions;
201
205
  }, []);
202
206
  };
207
+ /**
208
+ * 在项目生成完毕后,把用户提供的环境配置写入对应文件。
209
+ * - VITE_AGENT_ID → web/.env.local (始终创建,含注释模板)
210
+ * - DATABASE_URL → server/.env.local (始终创建,含注释模板)
211
+ * - TAIHU_APP_TOKEN→ server/.env (仅当用户提供时才正则替换)
212
+ */
213
+ const applyEnvConfig = (projectDirectory, options) => __awaiter(void 0, void 0, void 0, function* () {
214
+ const viteAgentId = getOptionByName(options, 'vite-agent-id') || '';
215
+ const taihuToken = getOptionByName(options, 'taihu-app-token') || '';
216
+ const databaseUrl = getOptionByName(options, 'database-url') || '';
217
+ const writeFile = (0, util_1.promisify)(fs.writeFile);
218
+ const readFile = (0, util_1.promisify)(fs.readFile);
219
+ // (1) web/.env.local
220
+ const webEnvLocal = (0, path_1.join)(process.cwd(), projectDirectory, 'web', '.env.local');
221
+ const webContent = [
222
+ '# Local overrides for web/.env (do not commit)',
223
+ '# knot Agent ID — see https://knot.woa.com',
224
+ viteAgentId ? `VITE_AGENT_ID=${viteAgentId}` : '# VITE_AGENT_ID=',
225
+ ].join('\n') + '\n';
226
+ if (fs.existsSync((0, path_1.join)(process.cwd(), projectDirectory, 'web'))) {
227
+ yield writeFile(webEnvLocal, webContent);
228
+ }
229
+ // (2) server/.env.local
230
+ const serverEnvLocal = (0, path_1.join)(process.cwd(), projectDirectory, 'server', '.env.local');
231
+ const serverContent = [
232
+ '# Local overrides for server/.env (do not commit)',
233
+ '# PostgreSQL: postgresql://user:password@ip:port/db_name?schema=public',
234
+ databaseUrl ? `DATABASE_URL=${databaseUrl}` : '# DATABASE_URL=',
235
+ ].join('\n') + '\n';
236
+ if (fs.existsSync((0, path_1.join)(process.cwd(), projectDirectory, 'server'))) {
237
+ yield writeFile(serverEnvLocal, serverContent);
238
+ }
239
+ // (3) server/.env — sed 替换 TAIHU_APP_TOKEN=token 那行的 token
240
+ if (taihuToken) {
241
+ const serverEnv = (0, path_1.join)(process.cwd(), projectDirectory, 'server', '.env');
242
+ if (fs.existsSync(serverEnv)) {
243
+ const original = yield readFile(serverEnv, 'utf8');
244
+ const replaced = original.replace(/^TAIHU_APP_TOKEN=.*$/m, `TAIHU_APP_TOKEN=${taihuToken}`);
245
+ yield writeFile(serverEnv, replaced);
246
+ }
247
+ }
248
+ });
203
249
  const installPackages = (options, dryRunMode, installDirectory) => __awaiter(void 0, void 0, void 0, function* () {
204
250
  const inputPackageManager = options.find((option) => option.name === 'package-manager').value;
205
251
  let packageManager;
@@ -277,7 +323,7 @@ const printCollective = () => {
277
323
  };
278
324
  const print = (color = null) => (str = '') => {
279
325
  const terminalCols = (0, exports.retrieveCols)();
280
- const strLength = str.replace(/\u001b\[[0-9]{2}m/g, '').length;
326
+ const strLength = str.replace(/\[[0-9]{2}m/g, '').length;
281
327
  const leftPaddingLength = Math.floor((terminalCols - strLength) / 2);
282
328
  const leftPadding = ' '.repeat(Math.max(leftPaddingLength, 0));
283
329
  if (color) {
@@ -3,5 +3,6 @@ import { AbstractAction } from './abstract.action';
3
3
  export declare class UpdateAction extends AbstractAction {
4
4
  handle(inputs: Input[], options: Input[]): Promise<void>;
5
5
  private handleDiff;
6
+ private handleStatus;
6
7
  private handleUpdate;
7
8
  }