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.
- package/README.md +177 -35
- package/dist/commands/angular.js +343 -57
- package/dist/commands/generate.js +242 -30
- package/dist/commands/login.js +87 -25
- package/dist/commands/merge.js +201 -0
- package/dist/generators/angular/feature.generator.js +2262 -637
- package/dist/generators/angular/menu.generator.js +1 -1
- package/dist/generators/angular/routes.generator.js +8 -3
- package/dist/generators/screen.generator.js +100 -5
- package/dist/generators/screen.merge.js +70 -15
- package/dist/index.js +69 -8
- package/dist/license/guard.js +2 -1
- package/dist/license/permissions.js +62 -8
- package/dist/license/token.js +19 -0
- package/dist/postinstall.js +42 -2
- package/dist/runtime/config.js +4 -0
- package/dist/runtime/project-config.js +64 -0
- package/package.json +1 -1
|
@@ -10,17 +10,25 @@ const load_openapi_1 = require("../openapi/load-openapi");
|
|
|
10
10
|
const screen_generator_1 = require("../generators/screen.generator");
|
|
11
11
|
const screen_merge_1 = require("../generators/screen.merge");
|
|
12
12
|
const permissions_1 = require("../license/permissions");
|
|
13
|
-
const device_1 = require("../license/device");
|
|
14
13
|
const telemetry_1 = require("../telemetry");
|
|
15
14
|
const user_config_1 = require("../runtime/user-config");
|
|
16
15
|
const logger_1 = require("../runtime/logger");
|
|
16
|
+
const project_config_1 = require("../runtime/project-config");
|
|
17
17
|
async function generate(options) {
|
|
18
18
|
void (0, telemetry_1.trackCommand)('generate', options.telemetryEnabled);
|
|
19
|
+
const projectConfig = (0, project_config_1.findProjectConfig)(process.cwd());
|
|
20
|
+
const configuredOpenApi = (0, project_config_1.pickConfiguredPath)(projectConfig.config, 'openapi');
|
|
21
|
+
const configuredOutput = (0, project_config_1.pickConfiguredPath)(projectConfig.config, 'output') ??
|
|
22
|
+
(0, project_config_1.pickConfiguredPath)(projectConfig.config, 'schemas');
|
|
23
|
+
const openApiPath = (0, project_config_1.resolveOptionalPath)(options.openapi, configuredOpenApi, projectConfig.configPath);
|
|
24
|
+
if (!openApiPath) {
|
|
25
|
+
throw new Error('Missing OpenAPI file.\n' +
|
|
26
|
+
'Use --openapi <path> or set "openapi" (or "paths.openapi") in generateui-config.json.');
|
|
27
|
+
}
|
|
19
28
|
/**
|
|
20
29
|
* Caminho absoluto do OpenAPI (YAML)
|
|
21
30
|
* Ex: /Users/.../generateui-playground/realWorldOpenApi.yaml
|
|
22
31
|
*/
|
|
23
|
-
const openApiPath = path_1.default.resolve(process.cwd(), options.openapi);
|
|
24
32
|
(0, logger_1.logStep)(`OpenAPI: ${openApiPath}`);
|
|
25
33
|
/**
|
|
26
34
|
* Raiz do playground (onde está o YAML)
|
|
@@ -30,7 +38,7 @@ async function generate(options) {
|
|
|
30
38
|
/**
|
|
31
39
|
* Onde o Angular consome os arquivos
|
|
32
40
|
*/
|
|
33
|
-
const generateUiRoot = resolveGenerateUiRoot(projectRoot, options.output);
|
|
41
|
+
const generateUiRoot = resolveGenerateUiRoot(projectRoot, (0, project_config_1.resolveOptionalPath)(options.output, configuredOutput, projectConfig.configPath) ?? undefined);
|
|
34
42
|
(0, logger_1.logStep)(`Schemas output: ${generateUiRoot}`);
|
|
35
43
|
const generatedDir = path_1.default.join(generateUiRoot, 'generated');
|
|
36
44
|
const overlaysDir = path_1.default.join(generateUiRoot, 'overlays');
|
|
@@ -47,15 +55,14 @@ async function generate(options) {
|
|
|
47
55
|
*/
|
|
48
56
|
const routes = [];
|
|
49
57
|
const usedOperationIds = new Set();
|
|
58
|
+
const resourceMap = new Map();
|
|
59
|
+
const screenByOpId = new Map();
|
|
60
|
+
const viewDefaults = [];
|
|
50
61
|
const permissions = await (0, permissions_1.getPermissions)();
|
|
51
|
-
const
|
|
52
|
-
(0, logger_1.logDebug)(`License:
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
throw new Error('🔒 Você já utilizou sua geração gratuita.\n' +
|
|
56
|
-
'O plano Dev libera gerações ilimitadas, regeneração segura e UI inteligente.\n' +
|
|
57
|
-
'👉 Execute `generate-ui login` para continuar.\n' +
|
|
58
|
-
'Se você já fez login e ainda vê esta mensagem, tente novamente com a mesma versão do CLI e verifique a conexão com a API.');
|
|
62
|
+
const subscriptionReason = normalizeSubscriptionReason(permissions.subscription.reason);
|
|
63
|
+
(0, logger_1.logDebug)(`License: status=${permissions.subscription.status}, overrides=${permissions.features.uiOverrides}, safeRegen=${permissions.features.safeRegeneration}, intelligent=${permissions.features.intelligentGeneration}`);
|
|
64
|
+
if (subscriptionReason) {
|
|
65
|
+
console.log(`ℹ Subscription: ${subscriptionReason}`);
|
|
59
66
|
}
|
|
60
67
|
/**
|
|
61
68
|
* Parse do OpenAPI (já com $refs resolvidos)
|
|
@@ -67,6 +74,7 @@ async function generate(options) {
|
|
|
67
74
|
* Itera por todos os endpoints
|
|
68
75
|
*/
|
|
69
76
|
const operationIds = new Set();
|
|
77
|
+
const operationFileNames = new Set();
|
|
70
78
|
for (const [pathKey, pathItem] of Object.entries(paths)) {
|
|
71
79
|
for (const [method, rawOp] of Object.entries(pathItem)) {
|
|
72
80
|
const op = rawOp;
|
|
@@ -75,6 +83,8 @@ async function generate(options) {
|
|
|
75
83
|
const operationId = op.operationId ||
|
|
76
84
|
buildOperationId(method.toLowerCase(), pathKey, usedOperationIds);
|
|
77
85
|
operationIds.add(operationId);
|
|
86
|
+
operationFileNames.add(toSafeFileName(operationId));
|
|
87
|
+
recordResourceOp(resourceMap, pathKey, method.toLowerCase(), operationId, op);
|
|
78
88
|
const endpoint = {
|
|
79
89
|
operationId,
|
|
80
90
|
path: pathKey,
|
|
@@ -86,7 +96,8 @@ async function generate(options) {
|
|
|
86
96
|
* Gera o ScreenSchema completo
|
|
87
97
|
*/
|
|
88
98
|
const screenSchema = (0, screen_generator_1.generateScreen)(endpoint, api);
|
|
89
|
-
|
|
99
|
+
screenByOpId.set(operationId, screenSchema);
|
|
100
|
+
const fileName = `${toSafeFileName(operationId)}.screen.json`;
|
|
90
101
|
/**
|
|
91
102
|
* 1️⃣ generated → SEMPRE sobrescrito (base técnica)
|
|
92
103
|
*/
|
|
@@ -132,9 +143,57 @@ async function generate(options) {
|
|
|
132
143
|
: operationId),
|
|
133
144
|
group: inferRouteGroup(op, pathKey)
|
|
134
145
|
});
|
|
146
|
+
viewDefaults.push({
|
|
147
|
+
key: operationId,
|
|
148
|
+
view: 'list'
|
|
149
|
+
});
|
|
135
150
|
console.log(`✔ Generated ${operationId}`);
|
|
136
151
|
}
|
|
137
152
|
}
|
|
153
|
+
const canOverride = permissions.features.uiOverrides;
|
|
154
|
+
const canRegenerateSafely = permissions.features.safeRegeneration;
|
|
155
|
+
const intelligentEnabled = Boolean(permissions.features.intelligentGeneration);
|
|
156
|
+
if (intelligentEnabled) {
|
|
157
|
+
const adminSchemas = buildAdminSchemas(resourceMap, usedOperationIds, screenByOpId);
|
|
158
|
+
for (const admin of adminSchemas) {
|
|
159
|
+
const fileName = `${toSafeFileName(admin.api.operationId)}.screen.json`;
|
|
160
|
+
const generatedPath = path_1.default.join(generatedDir, fileName);
|
|
161
|
+
fs_1.default.writeFileSync(generatedPath, JSON.stringify(admin, null, 2));
|
|
162
|
+
const overlayPath = path_1.default.join(overlaysDir, fileName);
|
|
163
|
+
if (canOverride && canRegenerateSafely) {
|
|
164
|
+
const overlay = fs_1.default.existsSync(overlayPath)
|
|
165
|
+
? JSON.parse(fs_1.default.readFileSync(overlayPath, 'utf-8'))
|
|
166
|
+
: null;
|
|
167
|
+
const merged = (0, screen_merge_1.mergeScreen)(admin, overlay, null, {
|
|
168
|
+
openapiVersion: api?.info?.version || 'unknown',
|
|
169
|
+
debug: options.debug
|
|
170
|
+
});
|
|
171
|
+
fs_1.default.writeFileSync(overlayPath, JSON.stringify(merged.screen, null, 2));
|
|
172
|
+
}
|
|
173
|
+
else if (!fs_1.default.existsSync(overlayPath)) {
|
|
174
|
+
fs_1.default.writeFileSync(overlayPath, JSON.stringify(admin, null, 2));
|
|
175
|
+
}
|
|
176
|
+
routes.push({
|
|
177
|
+
path: admin.api.operationId,
|
|
178
|
+
operationId: admin.api.operationId,
|
|
179
|
+
label: admin.meta?.intelligent?.label,
|
|
180
|
+
group: admin.meta?.intelligent?.group ?? null
|
|
181
|
+
});
|
|
182
|
+
viewDefaults.push({
|
|
183
|
+
key: admin.api.operationId,
|
|
184
|
+
view: 'cards'
|
|
185
|
+
});
|
|
186
|
+
if (admin.meta?.intelligent?.listOperationId) {
|
|
187
|
+
viewDefaults.push({
|
|
188
|
+
key: admin.meta.intelligent.listOperationId,
|
|
189
|
+
view: 'list'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
operationIds.add(admin.api.operationId);
|
|
193
|
+
operationFileNames.add(toSafeFileName(admin.api.operationId));
|
|
194
|
+
console.log(`✨ Generated ${admin.api.operationId}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
138
197
|
(0, logger_1.logStep)(`Screens generated: ${routes.length}`);
|
|
139
198
|
/**
|
|
140
199
|
* 4️⃣ Gera arquivo de rotas
|
|
@@ -146,20 +205,38 @@ async function generate(options) {
|
|
|
146
205
|
* 4.3️⃣ Gera generateui-config.json (não sobrescreve)
|
|
147
206
|
*/
|
|
148
207
|
const configPath = path_1.default.join(generateUiRoot, '..', '..', 'generateui-config.json');
|
|
208
|
+
let configPayload = null;
|
|
149
209
|
if (!fs_1.default.existsSync(configPath)) {
|
|
150
|
-
|
|
210
|
+
configPayload = {
|
|
151
211
|
appTitle: 'Generate UI',
|
|
152
212
|
defaultRoute: '',
|
|
153
213
|
menu: {
|
|
154
214
|
autoInject: true
|
|
155
|
-
}
|
|
215
|
+
},
|
|
216
|
+
views: {}
|
|
156
217
|
};
|
|
157
|
-
fs_1.default.writeFileSync(configPath, JSON.stringify(
|
|
218
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(configPayload, null, 2));
|
|
158
219
|
(0, logger_1.logDebug)(`Config created: ${configPath}`);
|
|
159
220
|
}
|
|
160
221
|
else {
|
|
222
|
+
try {
|
|
223
|
+
configPayload = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
configPayload = null;
|
|
227
|
+
}
|
|
161
228
|
(0, logger_1.logDebug)(`Config found: ${configPath}`);
|
|
162
229
|
}
|
|
230
|
+
if (configPayload && viewDefaults.length) {
|
|
231
|
+
configPayload.views = configPayload.views || {};
|
|
232
|
+
for (const entry of viewDefaults) {
|
|
233
|
+
if (!configPayload.views[entry.key]) {
|
|
234
|
+
configPayload.views[entry.key] = entry.view;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(configPayload, null, 2));
|
|
238
|
+
(0, logger_1.logDebug)(`Config views updated: ${configPath}`);
|
|
239
|
+
}
|
|
163
240
|
/**
|
|
164
241
|
* 4.1️⃣ Gera menu inicial (override possível via menu.overrides.json)
|
|
165
242
|
*/
|
|
@@ -186,39 +263,44 @@ async function generate(options) {
|
|
|
186
263
|
.filter(file => file.endsWith('.screen.json'));
|
|
187
264
|
for (const file of overlayFiles) {
|
|
188
265
|
const opId = file.replace(/\.screen\.json$/, '');
|
|
189
|
-
if (!
|
|
266
|
+
if (!operationFileNames.has(opId)) {
|
|
190
267
|
fs_1.default.rmSync(path_1.default.join(overlaysDir, file));
|
|
191
268
|
if (options.debug) {
|
|
192
269
|
console.log(`✖ Removed overlay ${opId}`);
|
|
193
270
|
}
|
|
194
271
|
}
|
|
195
272
|
}
|
|
196
|
-
if (permissions.features.maxGenerations > -1) {
|
|
197
|
-
(0, device_1.incrementFreeGeneration)();
|
|
198
|
-
}
|
|
199
273
|
console.log('✔ Routes generated');
|
|
200
274
|
console.log('');
|
|
201
275
|
console.log('🎉 Next steps');
|
|
202
|
-
console.log(' 1)
|
|
203
|
-
console.log(' generate-ui
|
|
204
|
-
console.log(' 2)
|
|
205
|
-
console.log('
|
|
206
|
-
console.log('
|
|
276
|
+
console.log(' 1) Default full flow:');
|
|
277
|
+
console.log(' generate-ui generate');
|
|
278
|
+
console.log(' 2) Advanced (schemas only):');
|
|
279
|
+
console.log(' generate-ui schema');
|
|
280
|
+
console.log(' 3) Advanced (Angular only):');
|
|
281
|
+
console.log(' generate-ui angular');
|
|
282
|
+
console.log(' 4) Customize screens in generate-ui/overlays/');
|
|
283
|
+
console.log(' 5) Customize menu in generate-ui/menu.overrides.json (created once, never overwritten)');
|
|
284
|
+
console.log(' 6) Edit generateui-config.json to set appTitle/defaultRoute/menu.autoInject/views');
|
|
207
285
|
console.log('');
|
|
208
286
|
(0, logger_1.logTip)('Run with --dev to see detailed logs and file paths.');
|
|
209
287
|
}
|
|
288
|
+
function normalizeSubscriptionReason(reason) {
|
|
289
|
+
if (typeof reason !== 'string')
|
|
290
|
+
return '';
|
|
291
|
+
const trimmed = reason.trim();
|
|
292
|
+
return trimmed;
|
|
293
|
+
}
|
|
210
294
|
function resolveGenerateUiRoot(projectRoot, output) {
|
|
211
295
|
if (output) {
|
|
212
|
-
return path_1.default.
|
|
296
|
+
return path_1.default.isAbsolute(output)
|
|
297
|
+
? output
|
|
298
|
+
: path_1.default.resolve(process.cwd(), output);
|
|
213
299
|
}
|
|
214
300
|
const srcRoot = path_1.default.join(projectRoot, 'src');
|
|
215
301
|
if (fs_1.default.existsSync(srcRoot)) {
|
|
216
302
|
return path_1.default.join(srcRoot, 'generate-ui');
|
|
217
303
|
}
|
|
218
|
-
const frontendSrcRoot = path_1.default.join(projectRoot, 'frontend', 'src');
|
|
219
|
-
if (fs_1.default.existsSync(frontendSrcRoot)) {
|
|
220
|
-
return path_1.default.join(frontendSrcRoot, 'generate-ui');
|
|
221
|
-
}
|
|
222
304
|
return path_1.default.join(projectRoot, 'generate-ui');
|
|
223
305
|
}
|
|
224
306
|
function mergeParameters(pathParams, opParams) {
|
|
@@ -272,6 +354,119 @@ function inferRouteGroup(op, pathKey) {
|
|
|
272
354
|
.find(part => !part.startsWith('{') && !part.endsWith('}'));
|
|
273
355
|
return segment ? segment : null;
|
|
274
356
|
}
|
|
357
|
+
function recordResourceOp(map, pathKey, method, operationId, op) {
|
|
358
|
+
const isItemPath = /\/{[^}]+}$/.test(pathKey);
|
|
359
|
+
const basePath = isItemPath
|
|
360
|
+
? pathKey.replace(/\/{[^}]+}$/, '')
|
|
361
|
+
: pathKey;
|
|
362
|
+
const paramMatch = isItemPath
|
|
363
|
+
? pathKey.match(/\/{([^}]+)}$/)
|
|
364
|
+
: null;
|
|
365
|
+
const idParam = paramMatch ? paramMatch[1] : null;
|
|
366
|
+
const tag = Array.isArray(op?.tags) && op.tags.length
|
|
367
|
+
? String(op.tags[0]).trim()
|
|
368
|
+
: undefined;
|
|
369
|
+
const entry = map.get(basePath) || {
|
|
370
|
+
basePath
|
|
371
|
+
};
|
|
372
|
+
if (tag && !entry.tag)
|
|
373
|
+
entry.tag = tag;
|
|
374
|
+
if (idParam && !entry.idParam)
|
|
375
|
+
entry.idParam = idParam;
|
|
376
|
+
const payload = {
|
|
377
|
+
operationId,
|
|
378
|
+
method,
|
|
379
|
+
path: pathKey,
|
|
380
|
+
tag
|
|
381
|
+
};
|
|
382
|
+
if (!isItemPath && method === 'get')
|
|
383
|
+
entry.list = payload;
|
|
384
|
+
if (isItemPath && method === 'get')
|
|
385
|
+
entry.detail = payload;
|
|
386
|
+
if (isItemPath && (method === 'put' || method === 'patch')) {
|
|
387
|
+
entry.update = payload;
|
|
388
|
+
}
|
|
389
|
+
if (isItemPath && method === 'delete')
|
|
390
|
+
entry.remove = payload;
|
|
391
|
+
map.set(basePath, entry);
|
|
392
|
+
}
|
|
393
|
+
function buildAdminSchemas(resources, usedOperationIds, screenByOpId) {
|
|
394
|
+
const adminSchemas = [];
|
|
395
|
+
for (const resource of resources.values()) {
|
|
396
|
+
if (!resource.list)
|
|
397
|
+
continue;
|
|
398
|
+
const entity = inferEntityName(resource.basePath);
|
|
399
|
+
const baseId = toPascalCase(entity);
|
|
400
|
+
const baseOpId = `${baseId}Admin`;
|
|
401
|
+
let operationId = baseOpId;
|
|
402
|
+
let index = 2;
|
|
403
|
+
while (usedOperationIds.has(operationId)) {
|
|
404
|
+
operationId = `${baseOpId}${index}`;
|
|
405
|
+
index += 1;
|
|
406
|
+
}
|
|
407
|
+
usedOperationIds.add(operationId);
|
|
408
|
+
const label = `${toLabel(entity)} Admin`;
|
|
409
|
+
const group = resource.tag ? resource.tag : inferGroup(resource.basePath);
|
|
410
|
+
const listSchema = screenByOpId.get(resource.list.operationId);
|
|
411
|
+
const columns = listSchema?.data?.table?.columns &&
|
|
412
|
+
Array.isArray(listSchema.data.table.columns)
|
|
413
|
+
? listSchema.data.table.columns
|
|
414
|
+
: [];
|
|
415
|
+
const responseFormat = listSchema?.response?.format === 'cards' ||
|
|
416
|
+
listSchema?.response?.format === 'raw' ||
|
|
417
|
+
listSchema?.response?.format === 'table'
|
|
418
|
+
? listSchema.response.format
|
|
419
|
+
: columns.length > 0
|
|
420
|
+
? 'table'
|
|
421
|
+
: null;
|
|
422
|
+
adminSchemas.push({
|
|
423
|
+
meta: {
|
|
424
|
+
intelligent: {
|
|
425
|
+
kind: 'adminList',
|
|
426
|
+
label,
|
|
427
|
+
group,
|
|
428
|
+
listOperationId: resource.list.operationId,
|
|
429
|
+
detailOperationId: resource.detail?.operationId ?? null,
|
|
430
|
+
updateOperationId: resource.update?.operationId ?? null,
|
|
431
|
+
deleteOperationId: resource.remove?.operationId ?? null,
|
|
432
|
+
idParam: resource.idParam ?? null
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
entity: toLabel(entity),
|
|
436
|
+
description: 'Smart admin list generated from collection endpoints.',
|
|
437
|
+
data: {
|
|
438
|
+
table: {
|
|
439
|
+
columns
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
response: responseFormat
|
|
443
|
+
? { format: responseFormat }
|
|
444
|
+
: undefined,
|
|
445
|
+
api: {
|
|
446
|
+
operationId,
|
|
447
|
+
method: 'get',
|
|
448
|
+
endpoint: resource.list.path
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return adminSchemas;
|
|
453
|
+
}
|
|
454
|
+
function inferEntityName(pathKey) {
|
|
455
|
+
const segments = String(pathKey || '')
|
|
456
|
+
.split('/')
|
|
457
|
+
.filter(Boolean);
|
|
458
|
+
if (!segments.length)
|
|
459
|
+
return 'Resource';
|
|
460
|
+
const last = segments[segments.length - 1];
|
|
461
|
+
return last.replace(/[^a-zA-Z0-9]+/g, ' ');
|
|
462
|
+
}
|
|
463
|
+
function inferGroup(pathKey) {
|
|
464
|
+
const segment = String(pathKey || '')
|
|
465
|
+
.split('/')
|
|
466
|
+
.map(part => part.trim())
|
|
467
|
+
.filter(Boolean)[0];
|
|
468
|
+
return segment ? segment : null;
|
|
469
|
+
}
|
|
275
470
|
function buildMenuFromRoutes(routes) {
|
|
276
471
|
const groups = [];
|
|
277
472
|
const ungrouped = [];
|
|
@@ -312,11 +507,28 @@ function toKebab(value) {
|
|
|
312
507
|
.toLowerCase();
|
|
313
508
|
}
|
|
314
509
|
function toLabel(value) {
|
|
315
|
-
return String(value)
|
|
510
|
+
return stripDiacritics(String(value))
|
|
316
511
|
.replace(/[_-]/g, ' ')
|
|
317
512
|
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
318
513
|
.replace(/\b\w/g, char => char.toUpperCase());
|
|
319
514
|
}
|
|
515
|
+
function toSafeFileName(value) {
|
|
516
|
+
return String(value)
|
|
517
|
+
.replace(/[\\/]/g, '-')
|
|
518
|
+
.replace(/\s+/g, '-');
|
|
519
|
+
}
|
|
520
|
+
function stripDiacritics(value) {
|
|
521
|
+
return value
|
|
522
|
+
.normalize('NFD')
|
|
523
|
+
.replace(/[\u0300-\u036f]/g, '');
|
|
524
|
+
}
|
|
525
|
+
function toPascalCase(value) {
|
|
526
|
+
return String(value)
|
|
527
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
528
|
+
.filter(Boolean)
|
|
529
|
+
.map(part => part[0].toUpperCase() + part.slice(1))
|
|
530
|
+
.join('');
|
|
531
|
+
}
|
|
320
532
|
function httpVerbToPrefix(verb) {
|
|
321
533
|
switch (verb) {
|
|
322
534
|
case 'get':
|
package/dist/commands/login.js
CHANGED
|
@@ -22,9 +22,11 @@ async function login(options) {
|
|
|
22
22
|
(0, token_1.saveToken)(token);
|
|
23
23
|
(0, logger_1.logDebug)('Token saved');
|
|
24
24
|
let permissionsLoaded = false;
|
|
25
|
+
let subscriptionReason = '';
|
|
25
26
|
try {
|
|
26
|
-
await (0, permissions_1.fetchPermissions)();
|
|
27
|
+
const permissions = await (0, permissions_1.fetchPermissions)();
|
|
27
28
|
permissionsLoaded = true;
|
|
29
|
+
subscriptionReason = String(permissions.subscription.reason ?? '').trim();
|
|
28
30
|
}
|
|
29
31
|
catch {
|
|
30
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.');
|
|
@@ -40,6 +42,9 @@ async function login(options) {
|
|
|
40
42
|
console.log(permissionsLoaded
|
|
41
43
|
? '✔ Login completo'
|
|
42
44
|
: '✔ Login completo (verificação pendente)');
|
|
45
|
+
if (permissionsLoaded && subscriptionReason) {
|
|
46
|
+
console.log(`ℹ Subscription: ${subscriptionReason}`);
|
|
47
|
+
}
|
|
43
48
|
}
|
|
44
49
|
function resolveLoginEmail() {
|
|
45
50
|
const envEmail = process.env.GIT_AUTHOR_EMAIL ||
|
|
@@ -63,6 +68,7 @@ function resolveLoginEmail() {
|
|
|
63
68
|
async function waitForLogin() {
|
|
64
69
|
return new Promise((resolve, reject) => {
|
|
65
70
|
let loginUrl = '';
|
|
71
|
+
let settled = false;
|
|
66
72
|
const server = http_1.default.createServer((req, res) => {
|
|
67
73
|
const requestUrl = req.url || '/';
|
|
68
74
|
if (!requestUrl.startsWith('/callback')) {
|
|
@@ -89,72 +95,128 @@ async function waitForLogin() {
|
|
|
89
95
|
<title>GenerateUI</title>
|
|
90
96
|
<style>
|
|
91
97
|
:root {
|
|
92
|
-
--bg: #
|
|
98
|
+
--bg-1: #f9f5ea;
|
|
99
|
+
--bg-2: #eef7f4;
|
|
93
100
|
--card: #ffffff;
|
|
94
|
-
--text: #
|
|
95
|
-
--muted: #
|
|
96
|
-
--
|
|
97
|
-
--
|
|
101
|
+
--text: #39455f;
|
|
102
|
+
--muted: #76819a;
|
|
103
|
+
--accent: #6fd3c0;
|
|
104
|
+
--accent-2: #9fd8ff;
|
|
105
|
+
--border: #e1e7f2;
|
|
106
|
+
--shadow: rgba(76, 88, 120, 0.14);
|
|
98
107
|
}
|
|
99
108
|
* {
|
|
100
109
|
box-sizing: border-box;
|
|
101
|
-
font-family: "
|
|
110
|
+
font-family: "Manrope", "Segoe UI", sans-serif;
|
|
102
111
|
}
|
|
103
112
|
body {
|
|
104
113
|
margin: 0;
|
|
105
114
|
min-height: 100vh;
|
|
106
115
|
display: grid;
|
|
107
116
|
place-items: center;
|
|
108
|
-
background:
|
|
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);
|
|
109
121
|
color: var(--text);
|
|
110
122
|
}
|
|
111
123
|
main {
|
|
112
|
-
background:
|
|
113
|
-
padding:
|
|
114
|
-
border-radius:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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;
|
|
118
148
|
}
|
|
119
149
|
h1 {
|
|
120
|
-
margin:
|
|
150
|
+
margin: 10px 0 12px;
|
|
121
151
|
font-size: 30px;
|
|
152
|
+
letter-spacing: 0.02em;
|
|
122
153
|
}
|
|
123
154
|
p {
|
|
124
155
|
margin: 0 0 24px;
|
|
125
156
|
color: var(--muted);
|
|
126
|
-
|
|
157
|
+
line-height: 1.5;
|
|
127
158
|
}
|
|
128
159
|
.pill {
|
|
129
|
-
|
|
130
|
-
background: #f5e9ff;
|
|
131
|
-
color: var(--primary);
|
|
132
|
-
padding: 8px 14px;
|
|
160
|
+
padding: 4px 10px;
|
|
133
161
|
border-radius: 999px;
|
|
134
|
-
|
|
135
|
-
|
|
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
|
+
}
|
|
136
177
|
}
|
|
137
178
|
</style>
|
|
138
179
|
</head>
|
|
139
180
|
<body>
|
|
140
181
|
<main>
|
|
141
|
-
<
|
|
142
|
-
<
|
|
143
|
-
<
|
|
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>
|
|
144
188
|
</main>
|
|
145
189
|
</body>
|
|
146
190
|
</html>`);
|
|
147
191
|
clearTimeout(timeout);
|
|
148
192
|
server.close();
|
|
193
|
+
settled = true;
|
|
194
|
+
process.off('SIGINT', handleSigint);
|
|
195
|
+
process.off('SIGTERM', handleSigint);
|
|
149
196
|
resolve({ accessToken, expiresAt });
|
|
150
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
|
+
};
|
|
151
206
|
const timeout = setTimeout(() => {
|
|
207
|
+
if (settled)
|
|
208
|
+
return;
|
|
209
|
+
settled = true;
|
|
152
210
|
server.close();
|
|
211
|
+
process.off('SIGINT', handleSigint);
|
|
212
|
+
process.off('SIGTERM', handleSigint);
|
|
153
213
|
const help = loginUrl
|
|
154
214
|
? ` Ensure the login page is reachable and try again: ${loginUrl}`
|
|
155
215
|
: ` Ensure ${(0, config_1.getWebAuthUrl)()} and ${(0, config_1.getApiBaseUrl)()} are reachable.`;
|
|
156
216
|
reject(new Error(`Login timed out.${help}`));
|
|
157
217
|
}, LOGIN_TIMEOUT_MS);
|
|
218
|
+
process.on('SIGINT', handleSigint);
|
|
219
|
+
process.on('SIGTERM', handleSigint);
|
|
158
220
|
server.listen(0, () => {
|
|
159
221
|
const address = server.address();
|
|
160
222
|
if (!address || typeof address === 'string') {
|