@gadmin2n/cli 0.0.91 → 0.0.92
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/index.d.ts +1 -1
- package/actions/index.js +1 -1
- package/actions/self-update.action.d.ts +5 -0
- package/actions/self-update.action.js +93 -0
- package/actions/update.action.d.ts +2 -0
- package/actions/update.action.js +945 -6
- package/commands/command.loader.js +2 -2
- package/commands/{template.command.d.ts → self-update.command.d.ts} +1 -1
- package/commands/self-update.command.js +35 -0
- package/commands/update.command.js +25 -8
- package/lib/install-manager.d.ts +48 -0
- package/lib/install-manager.js +125 -0
- package/lib/template-merge/config-loader.d.ts +15 -0
- package/lib/template-merge/config-loader.js +38 -0
- package/lib/template-merge/env-merger.d.ts +5 -0
- package/lib/template-merge/env-merger.js +57 -0
- package/lib/template-merge/glob.d.ts +5 -0
- package/lib/template-merge/glob.js +78 -0
- package/lib/template-merge/index.d.ts +8 -0
- package/lib/template-merge/index.js +24 -0
- package/lib/template-merge/json-merger.d.ts +5 -0
- package/lib/template-merge/json-merger.js +252 -0
- package/lib/template-merge/merger.d.ts +30 -0
- package/lib/template-merge/merger.js +2 -0
- package/lib/template-merge/prisma-merger.d.ts +5 -0
- package/lib/template-merge/prisma-merger.js +112 -0
- package/lib/template-merge/registry.d.ts +25 -0
- package/lib/template-merge/registry.js +42 -0
- package/lib/template-merge/ts-module-merger.d.ts +16 -0
- package/lib/template-merge/ts-module-merger.js +193 -0
- package/lib/version-check.d.ts +39 -0
- package/lib/version-check.js +157 -0
- package/package.json +2 -2
- package/actions/template.action.d.ts +0 -7
- package/actions/template.action.js +0 -570
- package/commands/template.command.js +0 -42
|
@@ -1,570 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.TemplateAction = void 0;
|
|
13
|
-
const chalk = require("chalk");
|
|
14
|
-
const fs = require("fs");
|
|
15
|
-
const path = require("path");
|
|
16
|
-
const inquirer = require("inquirer");
|
|
17
|
-
const child_process_1 = require("child_process");
|
|
18
|
-
const abstract_action_1 = require("./abstract.action");
|
|
19
|
-
const DEFAULT_EXCLUDES = [
|
|
20
|
-
'node_modules',
|
|
21
|
-
'.git',
|
|
22
|
-
'dist',
|
|
23
|
-
'generated',
|
|
24
|
-
'.DS_Store',
|
|
25
|
-
'.env.local',
|
|
26
|
-
'readme.md'
|
|
27
|
-
];
|
|
28
|
-
/**
|
|
29
|
-
* Convert a glob pattern to a RegExp.
|
|
30
|
-
* Supports: * (any chars except /), ** (any path segments), ? (single char)
|
|
31
|
-
*/
|
|
32
|
-
function globToRegex(pattern) {
|
|
33
|
-
let regexStr = '^';
|
|
34
|
-
let i = 0;
|
|
35
|
-
while (i < pattern.length) {
|
|
36
|
-
const c = pattern[i];
|
|
37
|
-
if (c === '*') {
|
|
38
|
-
if (pattern[i + 1] === '*') {
|
|
39
|
-
// ** matches any path segments
|
|
40
|
-
if (pattern[i + 2] === '/') {
|
|
41
|
-
regexStr += '(?:.+/)?';
|
|
42
|
-
i += 3;
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
regexStr += '.*';
|
|
46
|
-
i += 2;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
// * matches anything except /
|
|
51
|
-
regexStr += '[^/]*';
|
|
52
|
-
i++;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else if (c === '?') {
|
|
56
|
-
regexStr += '[^/]';
|
|
57
|
-
i++;
|
|
58
|
-
}
|
|
59
|
-
else if (c === '.' || c === '(' || c === ')' || c === '[' || c === ']' || c === '{' || c === '}' || c === '+' || c === '^' || c === '$' || c === '|' || c === '\\') {
|
|
60
|
-
regexStr += '\\' + c;
|
|
61
|
-
i++;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
regexStr += c;
|
|
65
|
-
i++;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
regexStr += '$';
|
|
69
|
-
return new RegExp(regexStr);
|
|
70
|
-
}
|
|
71
|
-
function matchesPattern(filePath, pattern) {
|
|
72
|
-
// Exact match or directory prefix match
|
|
73
|
-
if (filePath === pattern)
|
|
74
|
-
return true;
|
|
75
|
-
if (filePath.startsWith(pattern + '/'))
|
|
76
|
-
return true;
|
|
77
|
-
// Check each path segment for exact match (e.g. "node_modules" matches "a/node_modules/b")
|
|
78
|
-
const parts = filePath.split('/');
|
|
79
|
-
if (!pattern.includes('/') && !pattern.includes('*') && !pattern.includes('?')) {
|
|
80
|
-
if (parts.includes(pattern))
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
// Glob pattern matching
|
|
84
|
-
if (pattern.includes('*') || pattern.includes('?')) {
|
|
85
|
-
const regex = globToRegex(pattern);
|
|
86
|
-
return regex.test(filePath);
|
|
87
|
-
}
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
function loadTemplateConfig() {
|
|
91
|
-
const configPaths = [
|
|
92
|
-
path.join(process.cwd(), 'server/gadmin-cli.json'),
|
|
93
|
-
path.join(process.cwd(), 'gadmin-cli.json'),
|
|
94
|
-
];
|
|
95
|
-
let templateName;
|
|
96
|
-
let userExcludes = [];
|
|
97
|
-
for (const configPath of configPaths) {
|
|
98
|
-
if (fs.existsSync(configPath)) {
|
|
99
|
-
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
100
|
-
templateName = config.template;
|
|
101
|
-
if (Array.isArray(config.templateExcludes)) {
|
|
102
|
-
userExcludes = config.templateExcludes;
|
|
103
|
-
}
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (!templateName) {
|
|
108
|
-
throw new Error('Cannot find "template" field in gadmin-cli.json. ' +
|
|
109
|
-
'Make sure you are in a gadmin2 project root directory.');
|
|
110
|
-
}
|
|
111
|
-
return {
|
|
112
|
-
templateName,
|
|
113
|
-
excludes: [...DEFAULT_EXCLUDES, ...userExcludes],
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
function shouldExclude(filePath, excludes) {
|
|
117
|
-
for (const pattern of excludes) {
|
|
118
|
-
if (matchesPattern(filePath, pattern))
|
|
119
|
-
return true;
|
|
120
|
-
}
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
function collectFiles(dir, excludes, base = '') {
|
|
124
|
-
const results = [];
|
|
125
|
-
const entries = fs.readdirSync(path.join(dir, base), { withFileTypes: true });
|
|
126
|
-
for (const entry of entries) {
|
|
127
|
-
const rel = base ? `${base}/${entry.name}` : entry.name;
|
|
128
|
-
if (shouldExclude(rel, excludes))
|
|
129
|
-
continue;
|
|
130
|
-
if (entry.isDirectory()) {
|
|
131
|
-
results.push(...collectFiles(dir, excludes, rel));
|
|
132
|
-
}
|
|
133
|
-
else if (entry.isFile()) {
|
|
134
|
-
results.push(rel);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return results.sort();
|
|
138
|
-
}
|
|
139
|
-
function resolveTemplatePath(templateName) {
|
|
140
|
-
// Resolve @gadmin2n/schematics package path
|
|
141
|
-
// Try multiple locations: project's node_modules, CLI's own node_modules
|
|
142
|
-
let schematicsRoot;
|
|
143
|
-
const searchPaths = [
|
|
144
|
-
process.cwd(),
|
|
145
|
-
path.join(process.cwd(), 'server'),
|
|
146
|
-
path.resolve(__dirname, '..'), // CLI package root
|
|
147
|
-
];
|
|
148
|
-
for (const searchPath of searchPaths) {
|
|
149
|
-
try {
|
|
150
|
-
const pkgPath = require.resolve('@gadmin2n/schematics/package.json', {
|
|
151
|
-
paths: [searchPath],
|
|
152
|
-
});
|
|
153
|
-
schematicsRoot = path.dirname(pkgPath);
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
catch (_a) {
|
|
157
|
-
// continue trying next path
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (!schematicsRoot) {
|
|
161
|
-
throw new Error('Cannot resolve @gadmin2n/schematics package. ' +
|
|
162
|
-
'Make sure it is installed.');
|
|
163
|
-
}
|
|
164
|
-
// Check dist path first (published package), then src path (yalc/dev link)
|
|
165
|
-
// Also try with name variants (gadmin2n → gadmin2) for backward compatibility
|
|
166
|
-
const nameVariants = [templateName];
|
|
167
|
-
if (templateName.startsWith('gadmin2n-')) {
|
|
168
|
-
nameVariants.push(templateName.replace('gadmin2n-', 'gadmin2-'));
|
|
169
|
-
}
|
|
170
|
-
else if (templateName.startsWith('gadmin2-')) {
|
|
171
|
-
nameVariants.push(templateName.replace('gadmin2-', 'gadmin2n-'));
|
|
172
|
-
}
|
|
173
|
-
for (const name of nameVariants) {
|
|
174
|
-
const distPath = path.join(schematicsRoot, 'dist/lib/application/files', name);
|
|
175
|
-
const srcPath = path.join(schematicsRoot, 'src/lib/application/files', name);
|
|
176
|
-
if (fs.existsSync(distPath))
|
|
177
|
-
return distPath;
|
|
178
|
-
if (fs.existsSync(srcPath))
|
|
179
|
-
return srcPath;
|
|
180
|
-
}
|
|
181
|
-
throw new Error(`Template directory not found for "${templateName}" in ${schematicsRoot}`);
|
|
182
|
-
}
|
|
183
|
-
function createUnifiedDiff(filePath, instanceContent, templateContent) {
|
|
184
|
-
const instanceLines = instanceContent.split('\n');
|
|
185
|
-
const templateLines = templateContent.split('\n');
|
|
186
|
-
// Simple line-by-line diff output
|
|
187
|
-
const lines = [];
|
|
188
|
-
lines.push(chalk.bold(`--- a/${filePath} (instance)`));
|
|
189
|
-
lines.push(chalk.bold(`+++ b/${filePath} (template)`));
|
|
190
|
-
// Find differing regions using a simple approach
|
|
191
|
-
const maxLen = Math.max(instanceLines.length, templateLines.length);
|
|
192
|
-
const hunks = [];
|
|
193
|
-
let currentHunk = null;
|
|
194
|
-
for (let i = 0; i < maxLen; i++) {
|
|
195
|
-
const a = i < instanceLines.length ? instanceLines[i] : undefined;
|
|
196
|
-
const b = i < templateLines.length ? templateLines[i] : undefined;
|
|
197
|
-
if (a !== b) {
|
|
198
|
-
if (!currentHunk) {
|
|
199
|
-
currentHunk = { start: i, instanceLines: [], templateLines: [] };
|
|
200
|
-
}
|
|
201
|
-
if (a !== undefined)
|
|
202
|
-
currentHunk.instanceLines.push(a);
|
|
203
|
-
if (b !== undefined)
|
|
204
|
-
currentHunk.templateLines.push(b);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
if (currentHunk) {
|
|
208
|
-
hunks.push(currentHunk);
|
|
209
|
-
currentHunk = null;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (currentHunk)
|
|
214
|
-
hunks.push(currentHunk);
|
|
215
|
-
// Output hunks with context
|
|
216
|
-
for (const hunk of hunks.slice(0, 10)) { // Limit to first 10 hunks
|
|
217
|
-
const contextStart = Math.max(0, hunk.start - 2);
|
|
218
|
-
lines.push(chalk.cyan(`@@ -${hunk.start + 1},${hunk.instanceLines.length} +${hunk.start + 1},${hunk.templateLines.length} @@`));
|
|
219
|
-
// Context before
|
|
220
|
-
for (let i = contextStart; i < hunk.start; i++) {
|
|
221
|
-
if (i < instanceLines.length) {
|
|
222
|
-
lines.push(` ${instanceLines[i]}`);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
// Removed lines (instance)
|
|
226
|
-
for (const line of hunk.instanceLines) {
|
|
227
|
-
lines.push(chalk.red(`-${line}`));
|
|
228
|
-
}
|
|
229
|
-
// Added lines (template)
|
|
230
|
-
for (const line of hunk.templateLines) {
|
|
231
|
-
lines.push(chalk.green(`+${line}`));
|
|
232
|
-
}
|
|
233
|
-
// Context after
|
|
234
|
-
const afterStart = hunk.start + Math.max(hunk.instanceLines.length, hunk.templateLines.length);
|
|
235
|
-
for (let i = afterStart; i < Math.min(afterStart + 2, instanceLines.length); i++) {
|
|
236
|
-
lines.push(` ${instanceLines[i]}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (hunks.length > 10) {
|
|
240
|
-
lines.push(chalk.yellow(` ... and ${hunks.length - 10} more hunks`));
|
|
241
|
-
}
|
|
242
|
-
return lines.join('\n');
|
|
243
|
-
}
|
|
244
|
-
function createConflictContent(instanceContent, templateContent) {
|
|
245
|
-
return [
|
|
246
|
-
'<<<<<<< INSTANCE',
|
|
247
|
-
instanceContent,
|
|
248
|
-
'=======',
|
|
249
|
-
templateContent,
|
|
250
|
-
'>>>>>>> TEMPLATE',
|
|
251
|
-
].join('\n');
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Attempt a 3-way merge using git merge-file with an empty base.
|
|
255
|
-
* Both instance and template are treated as "additions from nothing".
|
|
256
|
-
* Identical sections pass through cleanly; differing sections produce conflicts.
|
|
257
|
-
*/
|
|
258
|
-
function tryAutoMerge(instanceContent, templateContent) {
|
|
259
|
-
const tmpDir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'gadmin-merge-'));
|
|
260
|
-
const basePath = path.join(tmpDir, 'base');
|
|
261
|
-
const oursPath = path.join(tmpDir, 'ours');
|
|
262
|
-
const theirsPath = path.join(tmpDir, 'theirs');
|
|
263
|
-
try {
|
|
264
|
-
fs.writeFileSync(basePath, '', 'utf-8');
|
|
265
|
-
fs.writeFileSync(oursPath, instanceContent, 'utf-8');
|
|
266
|
-
fs.writeFileSync(theirsPath, templateContent, 'utf-8');
|
|
267
|
-
try {
|
|
268
|
-
(0, child_process_1.execSync)(`git merge-file -L "INSTANCE" -L "BASE" -L "TEMPLATE" "${oursPath}" "${basePath}" "${theirsPath}"`, { stdio: 'pipe' });
|
|
269
|
-
const merged = fs.readFileSync(oursPath, 'utf-8');
|
|
270
|
-
return { merged, hasConflicts: false };
|
|
271
|
-
}
|
|
272
|
-
catch (err) {
|
|
273
|
-
if (err.status > 0) {
|
|
274
|
-
const merged = fs.readFileSync(oursPath, 'utf-8');
|
|
275
|
-
return { merged, hasConflicts: true };
|
|
276
|
-
}
|
|
277
|
-
return { merged: createConflictContent(instanceContent, templateContent), hasConflicts: true };
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
finally {
|
|
281
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
class TemplateAction extends abstract_action_1.AbstractAction {
|
|
285
|
-
handle(inputs, options) {
|
|
286
|
-
var _a;
|
|
287
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
288
|
-
const subcommand = (_a = inputs.find((i) => i.name === 'subcommand')) === null || _a === void 0 ? void 0 : _a.value;
|
|
289
|
-
if (subcommand === 'diff') {
|
|
290
|
-
yield this.handleDiff(options);
|
|
291
|
-
}
|
|
292
|
-
else if (subcommand === 'update') {
|
|
293
|
-
yield this.handleUpdate(options);
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
handleDiff(options) {
|
|
298
|
-
var _a;
|
|
299
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
300
|
-
const noContent = (_a = options.find((o) => o.name === 'no-content')) === null || _a === void 0 ? void 0 : _a.value;
|
|
301
|
-
let templateDir;
|
|
302
|
-
let excludes;
|
|
303
|
-
try {
|
|
304
|
-
const config = loadTemplateConfig();
|
|
305
|
-
templateDir = resolveTemplatePath(config.templateName);
|
|
306
|
-
excludes = config.excludes;
|
|
307
|
-
}
|
|
308
|
-
catch (err) {
|
|
309
|
-
console.error(chalk.red(err.message));
|
|
310
|
-
process.exit(1);
|
|
311
|
-
}
|
|
312
|
-
const instanceDir = process.cwd();
|
|
313
|
-
console.info(`\n${chalk.gray('Template:')} ${templateDir}`);
|
|
314
|
-
console.info(`${chalk.gray('Instance:')} ${instanceDir}\n`);
|
|
315
|
-
const templateFiles = collectFiles(templateDir, excludes);
|
|
316
|
-
const instanceFiles = collectFiles(instanceDir, excludes);
|
|
317
|
-
const templateSet = new Set(templateFiles);
|
|
318
|
-
const instanceSet = new Set(instanceFiles);
|
|
319
|
-
// Modified: both have, content differs
|
|
320
|
-
const modified = [];
|
|
321
|
-
// Only in template (would be added on update)
|
|
322
|
-
const onlyInTemplate = [];
|
|
323
|
-
// Only in instance (instance-specific)
|
|
324
|
-
const onlyInInstance = [];
|
|
325
|
-
for (const file of templateFiles) {
|
|
326
|
-
if (instanceSet.has(file)) {
|
|
327
|
-
const tContent = fs.readFileSync(path.join(templateDir, file), 'utf-8');
|
|
328
|
-
const iContent = fs.readFileSync(path.join(instanceDir, file), 'utf-8');
|
|
329
|
-
if (tContent !== iContent) {
|
|
330
|
-
modified.push(file);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
onlyInTemplate.push(file);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
for (const file of instanceFiles) {
|
|
338
|
-
if (!templateSet.has(file)) {
|
|
339
|
-
onlyInInstance.push(file);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// Summary
|
|
343
|
-
console.info(chalk.bold('═══════════════════════════════════════════════════'));
|
|
344
|
-
console.info(chalk.bold(' Template Diff Summary'));
|
|
345
|
-
console.info(chalk.bold('═══════════════════════════════════════════════════\n'));
|
|
346
|
-
if (modified.length > 0) {
|
|
347
|
-
console.info(chalk.yellow(`📝 Modified (${modified.length} files)`));
|
|
348
|
-
for (const f of modified) {
|
|
349
|
-
console.info(` ${f}`);
|
|
350
|
-
}
|
|
351
|
-
console.info();
|
|
352
|
-
}
|
|
353
|
-
if (onlyInTemplate.length > 0) {
|
|
354
|
-
console.info(chalk.green(`➕ Only in template (${onlyInTemplate.length} files)`));
|
|
355
|
-
for (const f of onlyInTemplate) {
|
|
356
|
-
console.info(` ${f}`);
|
|
357
|
-
}
|
|
358
|
-
console.info();
|
|
359
|
-
}
|
|
360
|
-
if (onlyInInstance.length > 0) {
|
|
361
|
-
console.info(chalk.cyan(`📄 Only in instance (${onlyInInstance.length} files)`));
|
|
362
|
-
for (const f of onlyInInstance) {
|
|
363
|
-
console.info(` ${f}`);
|
|
364
|
-
}
|
|
365
|
-
console.info();
|
|
366
|
-
}
|
|
367
|
-
if (modified.length === 0 && onlyInTemplate.length === 0 && onlyInInstance.length === 0) {
|
|
368
|
-
console.info(chalk.green('✅ No differences found. Instance matches template.\n'));
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
// Show diff content for modified files
|
|
372
|
-
if (!noContent && modified.length > 0) {
|
|
373
|
-
console.info(chalk.bold('───────────────────────────────────────────────────'));
|
|
374
|
-
console.info(chalk.bold(' File Diffs (instance vs template)'));
|
|
375
|
-
console.info(chalk.bold('───────────────────────────────────────────────────\n'));
|
|
376
|
-
for (const file of modified) {
|
|
377
|
-
const tContent = fs.readFileSync(path.join(templateDir, file), 'utf-8');
|
|
378
|
-
const iContent = fs.readFileSync(path.join(instanceDir, file), 'utf-8');
|
|
379
|
-
const diff = createUnifiedDiff(file, iContent, tContent);
|
|
380
|
-
console.info(diff);
|
|
381
|
-
console.info();
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
handleUpdate(options) {
|
|
387
|
-
var _a, _b;
|
|
388
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
389
|
-
const dryRun = (_a = options.find((o) => o.name === 'dry-run')) === null || _a === void 0 ? void 0 : _a.value;
|
|
390
|
-
const autoYes = (_b = options.find((o) => o.name === 'yes')) === null || _b === void 0 ? void 0 : _b.value;
|
|
391
|
-
let templateDir;
|
|
392
|
-
let excludes;
|
|
393
|
-
try {
|
|
394
|
-
const config = loadTemplateConfig();
|
|
395
|
-
templateDir = resolveTemplatePath(config.templateName);
|
|
396
|
-
excludes = config.excludes;
|
|
397
|
-
}
|
|
398
|
-
catch (err) {
|
|
399
|
-
console.error(chalk.red(err.message));
|
|
400
|
-
process.exit(1);
|
|
401
|
-
}
|
|
402
|
-
const instanceDir = process.cwd();
|
|
403
|
-
console.info(`\n${chalk.gray('Template:')} ${templateDir}`);
|
|
404
|
-
console.info(`${chalk.gray('Instance:')} ${instanceDir}\n`);
|
|
405
|
-
const templateFiles = collectFiles(templateDir, excludes);
|
|
406
|
-
const instanceFiles = collectFiles(instanceDir, excludes);
|
|
407
|
-
const instanceSet = new Set(instanceFiles);
|
|
408
|
-
// Categorize changes (template → instance direction)
|
|
409
|
-
const toUpdate = []; // exists in both, content differs
|
|
410
|
-
const toAdd = []; // only in template
|
|
411
|
-
for (const file of templateFiles) {
|
|
412
|
-
if (instanceSet.has(file)) {
|
|
413
|
-
const tContent = fs.readFileSync(path.join(templateDir, file), 'utf-8');
|
|
414
|
-
const iContent = fs.readFileSync(path.join(instanceDir, file), 'utf-8');
|
|
415
|
-
if (tContent !== iContent) {
|
|
416
|
-
toUpdate.push(file);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
toAdd.push(file);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
if (toUpdate.length === 0 && toAdd.length === 0) {
|
|
424
|
-
console.info(chalk.green('✅ Instance is up to date with template.\n'));
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
// Summary
|
|
428
|
-
console.info(chalk.bold('═══════════════════════════════════════════════════'));
|
|
429
|
-
console.info(chalk.bold(' Template Update Summary'));
|
|
430
|
-
console.info(chalk.bold('═══════════════════════════════════════════════════\n'));
|
|
431
|
-
if (toUpdate.length > 0) {
|
|
432
|
-
console.info(chalk.yellow(`📝 To update (${toUpdate.length} files)`));
|
|
433
|
-
for (const f of toUpdate) {
|
|
434
|
-
console.info(` ${f}`);
|
|
435
|
-
}
|
|
436
|
-
console.info();
|
|
437
|
-
}
|
|
438
|
-
if (toAdd.length > 0) {
|
|
439
|
-
console.info(chalk.green(`➕ To add (${toAdd.length} files)`));
|
|
440
|
-
for (const f of toAdd) {
|
|
441
|
-
console.info(` ${f}`);
|
|
442
|
-
}
|
|
443
|
-
console.info();
|
|
444
|
-
}
|
|
445
|
-
console.info(chalk.gray('───────────────────────────────────────────────────'));
|
|
446
|
-
console.info(chalk.gray(` Total: ${toUpdate.length} update | ${toAdd.length} add`));
|
|
447
|
-
console.info(chalk.gray('───────────────────────────────────────────────────\n'));
|
|
448
|
-
if (dryRun) {
|
|
449
|
-
console.info(chalk.gray('(dry-run mode, no changes made)\n'));
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
// Confirm proceed
|
|
453
|
-
if (!autoYes) {
|
|
454
|
-
const { confirm } = yield inquirer.prompt([
|
|
455
|
-
{
|
|
456
|
-
type: 'confirm',
|
|
457
|
-
name: 'confirm',
|
|
458
|
-
message: 'Proceed with update?',
|
|
459
|
-
default: false,
|
|
460
|
-
},
|
|
461
|
-
]);
|
|
462
|
-
if (!confirm) {
|
|
463
|
-
console.info('Cancelled.\n');
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
// Process modified files — per-file strategy selection
|
|
468
|
-
let skippedCount = 0;
|
|
469
|
-
let overwrittenCount = 0;
|
|
470
|
-
let mergedCount = 0;
|
|
471
|
-
let conflictCount = 0;
|
|
472
|
-
// Track "apply to all" choice
|
|
473
|
-
let applyAll = null;
|
|
474
|
-
for (let i = 0; i < toUpdate.length; i++) {
|
|
475
|
-
const file = toUpdate[i];
|
|
476
|
-
const tContent = fs.readFileSync(path.join(templateDir, file), 'utf-8');
|
|
477
|
-
const iContent = fs.readFileSync(path.join(instanceDir, file), 'utf-8');
|
|
478
|
-
let strategy = applyAll;
|
|
479
|
-
if (!strategy && !autoYes) {
|
|
480
|
-
// Show brief diff summary
|
|
481
|
-
const diff = createUnifiedDiff(file, iContent, tContent);
|
|
482
|
-
console.info(`\n${chalk.bold(`[${i + 1}/${toUpdate.length}]`)} ${chalk.yellow(file)}`);
|
|
483
|
-
console.info(diff);
|
|
484
|
-
console.info();
|
|
485
|
-
const { action } = yield inquirer.prompt([
|
|
486
|
-
{
|
|
487
|
-
type: 'list',
|
|
488
|
-
name: 'action',
|
|
489
|
-
message: `How to handle ${file}?`,
|
|
490
|
-
choices: [
|
|
491
|
-
{ name: 'Skip — keep instance as-is', value: 'skip', short: 'skip' },
|
|
492
|
-
{ name: 'Overwrite — use template version', value: 'overwrite', short: 'overwrite' },
|
|
493
|
-
{ name: 'Auto merge — attempt git merge', value: 'merge', short: 'merge' },
|
|
494
|
-
{ name: 'Conflict markers — write both versions for manual resolve', value: 'conflict', short: 'conflict' },
|
|
495
|
-
new inquirer.Separator(),
|
|
496
|
-
{ name: 'Skip ALL remaining', value: 'skip-all', short: 'skip all' },
|
|
497
|
-
{ name: 'Overwrite ALL remaining', value: 'overwrite-all', short: 'overwrite all' },
|
|
498
|
-
{ name: 'Conflict markers ALL remaining', value: 'conflict-all', short: 'conflict all' },
|
|
499
|
-
],
|
|
500
|
-
},
|
|
501
|
-
]);
|
|
502
|
-
if (action.endsWith('-all')) {
|
|
503
|
-
applyAll = action.replace('-all', '');
|
|
504
|
-
strategy = applyAll;
|
|
505
|
-
}
|
|
506
|
-
else {
|
|
507
|
-
strategy = action;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
else if (autoYes) {
|
|
511
|
-
strategy = 'conflict';
|
|
512
|
-
}
|
|
513
|
-
// Execute strategy
|
|
514
|
-
const destPath = path.join(instanceDir, file);
|
|
515
|
-
switch (strategy) {
|
|
516
|
-
case 'skip':
|
|
517
|
-
skippedCount++;
|
|
518
|
-
break;
|
|
519
|
-
case 'overwrite':
|
|
520
|
-
fs.writeFileSync(destPath, tContent, 'utf-8');
|
|
521
|
-
overwrittenCount++;
|
|
522
|
-
break;
|
|
523
|
-
case 'merge': {
|
|
524
|
-
const result = tryAutoMerge(iContent, tContent);
|
|
525
|
-
fs.writeFileSync(destPath, result.merged, 'utf-8');
|
|
526
|
-
if (result.hasConflicts) {
|
|
527
|
-
conflictCount++;
|
|
528
|
-
console.info(chalk.yellow(` ⚠️ ${file} — merge has conflicts`));
|
|
529
|
-
}
|
|
530
|
-
else {
|
|
531
|
-
mergedCount++;
|
|
532
|
-
console.info(chalk.green(` ✓ ${file} — merged cleanly`));
|
|
533
|
-
}
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
case 'conflict':
|
|
537
|
-
default:
|
|
538
|
-
fs.writeFileSync(destPath, createConflictContent(iContent, tContent), 'utf-8');
|
|
539
|
-
conflictCount++;
|
|
540
|
-
break;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
// Process new files (always add)
|
|
544
|
-
for (const file of toAdd) {
|
|
545
|
-
const destPath = path.join(instanceDir, file);
|
|
546
|
-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
547
|
-
fs.copyFileSync(path.join(templateDir, file), destPath);
|
|
548
|
-
}
|
|
549
|
-
// Final summary
|
|
550
|
-
console.info(chalk.bold('\n✅ Update complete!\n'));
|
|
551
|
-
const stats = [];
|
|
552
|
-
if (overwrittenCount > 0)
|
|
553
|
-
stats.push(`${overwrittenCount} overwritten`);
|
|
554
|
-
if (mergedCount > 0)
|
|
555
|
-
stats.push(`${mergedCount} merged`);
|
|
556
|
-
if (conflictCount > 0)
|
|
557
|
-
stats.push(`${conflictCount} with conflicts`);
|
|
558
|
-
if (skippedCount > 0)
|
|
559
|
-
stats.push(`${skippedCount} skipped`);
|
|
560
|
-
if (toAdd.length > 0)
|
|
561
|
-
stats.push(`${toAdd.length} added`);
|
|
562
|
-
console.info(` ${stats.join(' | ')}\n`);
|
|
563
|
-
if (conflictCount > 0) {
|
|
564
|
-
console.info(chalk.yellow(`⚠️ ${conflictCount} files have conflict markers. ` +
|
|
565
|
-
`Search for "<<<<<<< INSTANCE" to resolve them.\n`));
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
exports.TemplateAction = TemplateAction;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.TemplateCommand = void 0;
|
|
13
|
-
const abstract_command_1 = require("./abstract.command");
|
|
14
|
-
class TemplateCommand extends abstract_command_1.AbstractCommand {
|
|
15
|
-
load(program) {
|
|
16
|
-
program
|
|
17
|
-
.command('template <subcommand>')
|
|
18
|
-
.alias('tpl')
|
|
19
|
-
.description('Compare or sync with the project template.\n\n' +
|
|
20
|
-
' Subcommands:\n' +
|
|
21
|
-
' diff Show differences between instance and template\n' +
|
|
22
|
-
' update Pull updates from template into instance\n\n' +
|
|
23
|
-
' Update strategies (per-file interactive prompt):\n' +
|
|
24
|
-
' skip Keep instance file as-is\n' +
|
|
25
|
-
' overwrite Replace with template version\n' +
|
|
26
|
-
' merge Attempt auto merge via git merge-file\n' +
|
|
27
|
-
' conflict Write conflict markers for manual resolve\n')
|
|
28
|
-
.option('--no-content', '[diff] Only show file list, skip inline diff content.')
|
|
29
|
-
.option('-d, --dry-run', '[update] Show what would be updated without making changes.')
|
|
30
|
-
.option('-y, --yes', '[update] Skip per-file prompt, use conflict markers for all.')
|
|
31
|
-
.action((subcommand, command) => __awaiter(this, void 0, void 0, function* () {
|
|
32
|
-
const inputs = [];
|
|
33
|
-
inputs.push({ name: 'subcommand', value: subcommand });
|
|
34
|
-
const options = [];
|
|
35
|
-
options.push({ name: 'no-content', value: !command.content });
|
|
36
|
-
options.push({ name: 'dry-run', value: !!command.dryRun });
|
|
37
|
-
options.push({ name: 'yes', value: !!command.yes });
|
|
38
|
-
yield this.action.handle(inputs, options);
|
|
39
|
-
}));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
exports.TemplateCommand = TemplateCommand;
|