generate-ui-cli 2.1.7 → 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.
@@ -6,25 +6,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.login = login;
7
7
  const http_1 = __importDefault(require("http"));
8
8
  const url_1 = require("url");
9
- const promises_1 = require("readline/promises");
9
+ const child_process_1 = require("child_process");
10
10
  const config_1 = require("../runtime/config");
11
11
  const user_config_1 = require("../runtime/user-config");
12
12
  const open_browser_1 = require("../runtime/open-browser");
13
13
  const token_1 = require("../license/token");
14
14
  const permissions_1 = require("../license/permissions");
15
15
  const telemetry_1 = require("../telemetry");
16
+ const logger_1 = require("../runtime/logger");
16
17
  const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
17
18
  async function login(options) {
18
19
  void (0, telemetry_1.trackCommand)('login', options.telemetryEnabled);
20
+ (0, logger_1.logStep)('Starting login flow');
19
21
  const token = await waitForLogin();
20
22
  (0, token_1.saveToken)(token);
23
+ (0, logger_1.logDebug)('Token saved');
24
+ let permissionsLoaded = false;
25
+ let subscriptionReason = '';
21
26
  try {
22
- await (0, permissions_1.fetchPermissions)();
27
+ const permissions = await (0, permissions_1.fetchPermissions)();
28
+ permissionsLoaded = true;
29
+ subscriptionReason = String(permissions.subscription.reason ?? '').trim();
23
30
  }
24
31
  catch {
25
- // Cached permissions will be refreshed on next online command.
32
+ console.warn('⚠ Não foi possível validar a licença agora. Verifique sua conexão e rode o comando novamente se necessário.');
26
33
  }
27
- const email = await promptEmail();
34
+ const email = resolveLoginEmail();
28
35
  if (email) {
29
36
  (0, user_config_1.updateUserConfig)((config) => ({
30
37
  ...config,
@@ -32,27 +39,36 @@ async function login(options) {
32
39
  }));
33
40
  }
34
41
  await (0, telemetry_1.trackLogin)(email, options.telemetryEnabled);
35
- console.log('✔ Login completo');
42
+ console.log(permissionsLoaded
43
+ ? '✔ Login completo'
44
+ : '✔ Login completo (verificação pendente)');
45
+ if (permissionsLoaded && subscriptionReason) {
46
+ console.log(`ℹ Subscription: ${subscriptionReason}`);
47
+ }
36
48
  }
37
- async function promptEmail() {
38
- if (!process.stdin.isTTY)
39
- return null;
40
- const rl = (0, promises_1.createInterface)({
41
- input: process.stdin,
42
- output: process.stdout
43
- });
49
+ function resolveLoginEmail() {
50
+ const envEmail = process.env.GIT_AUTHOR_EMAIL ||
51
+ process.env.GIT_COMMITTER_EMAIL ||
52
+ process.env.EMAIL;
53
+ if (envEmail && envEmail.trim().length) {
54
+ return envEmail.trim();
55
+ }
44
56
  try {
45
- const value = await rl.question('Email (optional): ');
46
- const trimmed = value.trim();
47
- return trimmed.length ? trimmed : null;
57
+ const output = (0, child_process_1.execSync)('git config --get user.email', {
58
+ stdio: ['ignore', 'pipe', 'ignore']
59
+ })
60
+ .toString()
61
+ .trim();
62
+ return output.length ? output : null;
48
63
  }
49
- finally {
50
- rl.close();
64
+ catch {
65
+ return null;
51
66
  }
52
67
  }
53
68
  async function waitForLogin() {
54
69
  return new Promise((resolve, reject) => {
55
70
  let loginUrl = '';
71
+ let settled = false;
56
72
  const server = http_1.default.createServer((req, res) => {
57
73
  const requestUrl = req.url || '/';
58
74
  if (!requestUrl.startsWith('/callback')) {
@@ -68,7 +84,7 @@ async function waitForLogin() {
68
84
  res.end('Missing access token');
69
85
  return;
70
86
  }
71
- const expiresAt = expiresAtParam ||
87
+ const expiresAt = normalizeExpiresAt(expiresAtParam) ||
72
88
  new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
73
89
  res.writeHead(200, { 'Content-Type': 'text/html' });
74
90
  res.end(`<!doctype html>
@@ -79,72 +95,128 @@ async function waitForLogin() {
79
95
  <title>GenerateUI</title>
80
96
  <style>
81
97
  :root {
82
- --bg: #f3e8ff;
98
+ --bg-1: #f9f5ea;
99
+ --bg-2: #eef7f4;
83
100
  --card: #ffffff;
84
- --text: #2a1b3d;
85
- --muted: #6b5b7a;
86
- --primary: #7c3aed;
87
- --glow: rgba(124, 58, 237, 0.22);
101
+ --text: #39455f;
102
+ --muted: #76819a;
103
+ --accent: #6fd3c0;
104
+ --accent-2: #9fd8ff;
105
+ --border: #e1e7f2;
106
+ --shadow: rgba(76, 88, 120, 0.14);
88
107
  }
89
108
  * {
90
109
  box-sizing: border-box;
91
- font-family: "IBM Plex Serif", "Georgia", serif;
110
+ font-family: "Manrope", "Segoe UI", sans-serif;
92
111
  }
93
112
  body {
94
113
  margin: 0;
95
114
  min-height: 100vh;
96
115
  display: grid;
97
116
  place-items: center;
98
- background: radial-gradient(circle at top, #f5ebff, #e9d5ff);
117
+ background:
118
+ radial-gradient(circle at 15% 0%, #fff3c8 0%, var(--bg-1) 36%, transparent 60%),
119
+ radial-gradient(circle at 85% 0%, #e6f7ff 0%, var(--bg-2) 40%, transparent 70%),
120
+ linear-gradient(135deg, #fdfbf6, #f3f7fb);
99
121
  color: var(--text);
100
122
  }
101
123
  main {
102
- background: var(--card);
103
- padding: 52px 48px;
104
- border-radius: 24px;
105
- box-shadow: 0 24px 70px var(--glow);
106
- width: min(460px, 92vw);
107
- text-align: center;
124
+ background: linear-gradient(180deg, rgba(255,255,255,0.94), rgba(255,255,255,0.98));
125
+ padding: 44px;
126
+ border-radius: 28px;
127
+ border: 1px solid var(--border);
128
+ box-shadow: 0 24px 60px var(--shadow);
129
+ width: min(520px, 92vw);
130
+ text-align: left;
131
+ position: relative;
132
+ overflow: hidden;
133
+ }
134
+ main::before {
135
+ content: "";
136
+ position: absolute;
137
+ inset: -40% 25% auto auto;
138
+ width: 280px;
139
+ height: 280px;
140
+ background: radial-gradient(circle, rgba(111,211,192,0.35), transparent 70%);
141
+ pointer-events: none;
142
+ }
143
+ .label {
144
+ letter-spacing: 0.22em;
145
+ font-size: 12px;
146
+ color: var(--muted);
147
+ text-transform: uppercase;
108
148
  }
109
149
  h1 {
110
- margin: 0 0 12px;
150
+ margin: 10px 0 12px;
111
151
  font-size: 30px;
152
+ letter-spacing: 0.02em;
112
153
  }
113
154
  p {
114
155
  margin: 0 0 24px;
115
156
  color: var(--muted);
116
- font-size: 16px;
157
+ line-height: 1.5;
117
158
  }
118
159
  .pill {
119
- display: inline-block;
120
- background: #f5e9ff;
121
- color: var(--primary);
122
- padding: 8px 14px;
160
+ padding: 4px 10px;
123
161
  border-radius: 999px;
124
- font-weight: 600;
125
- letter-spacing: 0.2px;
162
+ background: rgba(255,255,255,0.65);
163
+ border: 1px solid var(--border);
164
+ font-size: 12px;
165
+ color: var(--muted);
166
+ }
167
+ .footer {
168
+ margin-top: 12px;
169
+ font-size: 13px;
170
+ color: var(--muted);
171
+ }
172
+ @media (max-width: 520px) {
173
+ main {
174
+ padding: 32px 24px;
175
+ text-align: center;
176
+ }
126
177
  }
127
178
  </style>
128
179
  </head>
129
180
  <body>
130
181
  <main>
131
- <h1>Let's Generate UI</h1>
132
- <p>Login completed successfully.</p>
133
- <span class="pill">You can close this window</span>
182
+ <div class="label">Generated UI</div>
183
+ <h1>Login completed</h1>
184
+ <p>You can now return to the terminal.</p>
185
+ <div class="footer">
186
+ <span class="pill">You can close this window</span>
187
+ </div>
134
188
  </main>
135
189
  </body>
136
190
  </html>`);
137
191
  clearTimeout(timeout);
138
192
  server.close();
193
+ settled = true;
194
+ process.off('SIGINT', handleSigint);
195
+ process.off('SIGTERM', handleSigint);
139
196
  resolve({ accessToken, expiresAt });
140
197
  });
198
+ const handleSigint = () => {
199
+ if (settled)
200
+ return;
201
+ settled = true;
202
+ clearTimeout(timeout);
203
+ server.close();
204
+ reject(new Error('Login canceled by user (SIGINT).'));
205
+ };
141
206
  const timeout = setTimeout(() => {
207
+ if (settled)
208
+ return;
209
+ settled = true;
142
210
  server.close();
211
+ process.off('SIGINT', handleSigint);
212
+ process.off('SIGTERM', handleSigint);
143
213
  const help = loginUrl
144
214
  ? ` Ensure the login page is reachable and try again: ${loginUrl}`
145
215
  : ` Ensure ${(0, config_1.getWebAuthUrl)()} and ${(0, config_1.getApiBaseUrl)()} are reachable.`;
146
216
  reject(new Error(`Login timed out.${help}`));
147
217
  }, LOGIN_TIMEOUT_MS);
218
+ process.on('SIGINT', handleSigint);
219
+ process.on('SIGTERM', handleSigint);
148
220
  server.listen(0, () => {
149
221
  const address = server.address();
150
222
  if (!address || typeof address === 'string') {
@@ -158,7 +230,24 @@ async function waitForLogin() {
158
230
  url.searchParams.set('api_base', (0, config_1.getApiBaseUrl)());
159
231
  loginUrl = url.toString();
160
232
  console.log(`Open this URL to finish login: ${loginUrl}`);
233
+ (0, logger_1.logDebug)(`Login callback listening on ${redirectUri}`);
161
234
  (0, open_browser_1.openBrowser)(loginUrl);
162
235
  });
163
236
  });
164
237
  }
238
+ function normalizeExpiresAt(value) {
239
+ if (!value)
240
+ return null;
241
+ const trimmed = value.trim();
242
+ if (!trimmed.length)
243
+ return null;
244
+ const asNumber = Number(trimmed);
245
+ if (Number.isFinite(asNumber)) {
246
+ const ms = asNumber < 1e12 ? asNumber * 1000 : asNumber;
247
+ return new Date(ms).toISOString();
248
+ }
249
+ const parsed = new Date(trimmed);
250
+ if (Number.isNaN(parsed.getTime()))
251
+ return null;
252
+ return parsed.toISOString();
253
+ }
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generatedRoutes = void 0;
4
+ exports.generatedRoutes = [];