generate-ui-cli 2.2.0 → 2.3.0

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.
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.merge = merge;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const child_process_1 = require("child_process");
11
+ const logger_1 = require("../runtime/logger");
12
+ const project_config_1 = require("../runtime/project-config");
13
+ async function merge(options) {
14
+ const projectConfig = (0, project_config_1.findProjectConfig)(process.cwd());
15
+ const configuredFeatures = (0, project_config_1.pickConfiguredPath)(projectConfig.config, 'features');
16
+ const featuresRoot = resolveFeaturesRoot(options.featuresPath, configuredFeatures, projectConfig.configPath);
17
+ const generatedRoot = path_1.default.join(featuresRoot, 'generated');
18
+ const overridesRoot = path_1.default.join(featuresRoot, 'overrides');
19
+ if (!options.feature) {
20
+ throw new Error('Missing --feature. Example: generate-ui merge --feature ProductsAdmin');
21
+ }
22
+ const folder = resolveFeatureFolder(options.feature, generatedRoot, overridesRoot);
23
+ const files = resolveFiles(folder, options.file);
24
+ if (files.length === 0) {
25
+ throw new Error('No files selected to compare.');
26
+ }
27
+ const selectedTool = options.tool || 'code';
28
+ (0, logger_1.logStep)(`Generated: ${generatedRoot}`);
29
+ (0, logger_1.logStep)(`Overrides: ${overridesRoot}`);
30
+ (0, logger_1.logStep)(`Merge tool: ${selectedTool}${selectedTool === 'code'
31
+ ? ' (merge editor, recommended)'
32
+ : selectedTool === 'code-diff'
33
+ ? ' (red/green diff)'
34
+ : ''}`);
35
+ let compared = 0;
36
+ for (const fileName of files) {
37
+ const generatedPath = path_1.default.join(generatedRoot, folder, fileName);
38
+ const overridePath = path_1.default.join(overridesRoot, folder, fileName);
39
+ if (!fs_1.default.existsSync(generatedPath)) {
40
+ console.warn(`⚠ Missing generated file: ${generatedPath}`);
41
+ continue;
42
+ }
43
+ if (!fs_1.default.existsSync(overridePath)) {
44
+ fs_1.default.mkdirSync(path_1.default.dirname(overridePath), { recursive: true });
45
+ fs_1.default.copyFileSync(generatedPath, overridePath);
46
+ console.log(`ℹ Created override file: ${overridePath}`);
47
+ }
48
+ openMergeTool(selectedTool, generatedPath, overridePath);
49
+ compared += 1;
50
+ }
51
+ if (compared === 0) {
52
+ throw new Error(`No comparable files found for feature "${folder}".`);
53
+ }
54
+ (0, logger_1.logTip)('Save changes in the overrides file (right side) to keep custom edits.');
55
+ }
56
+ function resolveFeaturesRoot(value, configured, configPath) {
57
+ const fromConfig = (0, project_config_1.resolveOptionalPath)(value, configured, configPath);
58
+ if (fromConfig) {
59
+ return normalizeFeaturesRoot(fromConfig);
60
+ }
61
+ const srcAppRoot = path_1.default.resolve(process.cwd(), 'src', 'app');
62
+ if (fs_1.default.existsSync(srcAppRoot)) {
63
+ return path_1.default.join(srcAppRoot, 'features');
64
+ }
65
+ throw new Error('Default features path not found.\n' +
66
+ 'Use --features <path> or set "features" (or "paths.features") in generateui-config.json.');
67
+ }
68
+ function normalizeFeaturesRoot(value) {
69
+ const isSrcApp = path_1.default.basename(value) === 'app' &&
70
+ path_1.default.basename(path_1.default.dirname(value)) === 'src';
71
+ if (isSrcApp)
72
+ return path_1.default.join(value, 'features');
73
+ return value;
74
+ }
75
+ function resolveFeatureFolder(value, generatedRoot, overridesRoot) {
76
+ const folders = listFeatureFolders(generatedRoot, overridesRoot);
77
+ if (folders.length === 0) {
78
+ throw new Error(`No feature folders found under ${generatedRoot} or ${overridesRoot}.`);
79
+ }
80
+ const direct = normalizeFeatureKey(value);
81
+ const matches = folders.filter(folder => normalizeFeatureKey(folder) === direct);
82
+ if (matches.length === 1)
83
+ return matches[0];
84
+ const contains = folders.filter(folder => normalizeFeatureKey(folder).includes(direct));
85
+ if (contains.length === 1)
86
+ return contains[0];
87
+ if (contains.length > 1) {
88
+ throw new Error(`Feature "${value}" is ambiguous. Matches: ${contains.join(', ')}`);
89
+ }
90
+ const sample = folders.slice(0, 12).join(', ');
91
+ throw new Error(`Feature not found: ${value}.\n` +
92
+ `Available features: ${sample}${folders.length > 12 ? ', ...' : ''}`);
93
+ }
94
+ function listFeatureFolders(generatedRoot, overridesRoot) {
95
+ const set = new Set();
96
+ for (const root of [generatedRoot, overridesRoot]) {
97
+ if (!fs_1.default.existsSync(root))
98
+ continue;
99
+ for (const entry of fs_1.default.readdirSync(root, { withFileTypes: true })) {
100
+ if (!entry.isDirectory())
101
+ continue;
102
+ set.add(entry.name);
103
+ }
104
+ }
105
+ return [...set].sort((a, b) => a.localeCompare(b));
106
+ }
107
+ function normalizeFeatureKey(value) {
108
+ return String(value)
109
+ .trim()
110
+ .replace(/Component$/i, '')
111
+ .replace(/[^a-zA-Z0-9]/g, '')
112
+ .toLowerCase();
113
+ }
114
+ function resolveFiles(folder, raw) {
115
+ const key = String(raw || 'component.ts').trim();
116
+ if (key === 'all') {
117
+ return [
118
+ `${folder}.component.ts`,
119
+ `${folder}.component.html`,
120
+ `${folder}.component.scss`
121
+ ];
122
+ }
123
+ const normalized = key.startsWith('.')
124
+ ? key.slice(1)
125
+ : key;
126
+ const suffix = normalized.startsWith('component')
127
+ ? normalized
128
+ : `component.${normalized}`;
129
+ return [`${folder}.${suffix}`];
130
+ }
131
+ function openMergeTool(tool, generatedPath, overridePath) {
132
+ const run = (cmd, args) => {
133
+ try {
134
+ (0, child_process_1.execFileSync)(cmd, args, { stdio: 'inherit' });
135
+ }
136
+ catch (error) {
137
+ const message = String(error?.message || '');
138
+ if (error?.code === 'ENOENT') {
139
+ throw new Error(`Merge tool "${cmd}" not found in PATH.\n` +
140
+ 'Recommended: install VS Code command in PATH and use --tool code.\n' +
141
+ "In VS Code run: 'Shell Command: Install code command in PATH'.\n" +
142
+ 'Or try one of these:\n' +
143
+ ' --tool code\n' +
144
+ ' --tool code-diff\n' +
145
+ ' --tool vimdiff\n' +
146
+ ' --tool diff');
147
+ }
148
+ if (cmd === 'opendiff' &&
149
+ message.includes('requires Xcode')) {
150
+ throw new Error('Tool "opendiff" requires full Xcode.\n' +
151
+ 'Use --tool vimdiff or --tool diff, or install full Xcode.');
152
+ }
153
+ throw error;
154
+ }
155
+ };
156
+ if (tool === 'code') {
157
+ openCodeMerge(run, generatedPath, overridePath);
158
+ return;
159
+ }
160
+ if (tool === 'code-diff') {
161
+ // Diff view: left = incoming generated, right = current overrides.
162
+ run('code', ['--wait', '--diff', generatedPath, overridePath]);
163
+ return;
164
+ }
165
+ if (tool === 'meld' || tool === 'kdiff3' || tool === 'bc') {
166
+ run('git', ['difftool', '--no-index', `--tool=${tool}`, generatedPath, overridePath]);
167
+ return;
168
+ }
169
+ run(tool, [generatedPath, overridePath]);
170
+ }
171
+ function openCodeMerge(run, generatedPath, overridePath) {
172
+ const ext = path_1.default.extname(overridePath) || '.txt';
173
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'generate-ui-merge-'));
174
+ const currentPath = path_1.default.join(tempDir, `current${ext}`);
175
+ const incomingPath = path_1.default.join(tempDir, `incoming${ext}`);
176
+ const basePath = path_1.default.join(tempDir, `base${ext}`);
177
+ const resultPath = path_1.default.join(tempDir, `result${ext}`);
178
+ try {
179
+ fs_1.default.copyFileSync(overridePath, currentPath);
180
+ fs_1.default.copyFileSync(generatedPath, incomingPath);
181
+ fs_1.default.copyFileSync(generatedPath, basePath);
182
+ fs_1.default.copyFileSync(overridePath, resultPath);
183
+ // VS Code merge editor:
184
+ // - current: user's override
185
+ // - incoming: regenerated output
186
+ // - base: fallback ancestor (best effort, regenerated)
187
+ // - result: file that will be saved back to overrides
188
+ run('code', [
189
+ '--wait',
190
+ '--merge',
191
+ currentPath,
192
+ incomingPath,
193
+ basePath,
194
+ resultPath
195
+ ]);
196
+ fs_1.default.copyFileSync(resultPath, overridePath);
197
+ }
198
+ finally {
199
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
200
+ }
201
+ }