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.
@@ -108,7 +108,7 @@ function buildMenuFromRoutes(routes) {
108
108
  continue;
109
109
  const item = {
110
110
  id: String(route.operationId),
111
- label: String(route.label ?? route.operationId),
111
+ label: toLabel(String(route.label ?? route.operationId)),
112
112
  route: normalizeRoutePath(String(route.path ?? route.operationId ?? ''))
113
113
  };
114
114
  const rawGroup = route.group ? String(route.group) : '';
@@ -6,9 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.generateRoutes = generateRoutes;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
- function generateRoutes(routes, featuresRoot, schemasRoot) {
9
+ function generateRoutes(routes, generatedRoot, overridesRoot, schemasRoot) {
10
10
  const out = path_1.default.join(schemasRoot, 'routes.gen.ts');
11
- const featuresImportBase = buildRelativeImportBase(schemasRoot, featuresRoot);
11
+ const generatedImportBase = buildRelativeImportBase(schemasRoot, generatedRoot);
12
+ const overridesImportBase = buildRelativeImportBase(schemasRoot, overridesRoot);
12
13
  fs_1.default.mkdirSync(path_1.default.dirname(out), { recursive: true });
13
14
  const content = `
14
15
  import { Routes } from '@angular/router'
@@ -16,7 +17,11 @@ import { Routes } from '@angular/router'
16
17
  export const generatedRoutes: Routes = [
17
18
  ${routes
18
19
  .flatMap(r => {
19
- const baseImport = ensureRelativeImport(toPosixPath(path_1.default.join(featuresImportBase, r.folder, `${r.fileBase}.component`)));
20
+ const overridePath = path_1.default.join(overridesRoot, r.folder, `${r.fileBase}.component.ts`);
21
+ const importBase = fs_1.default.existsSync(overridePath)
22
+ ? overridesImportBase
23
+ : generatedImportBase;
24
+ const baseImport = ensureRelativeImport(toPosixPath(path_1.default.join(importBase, r.folder, `${r.fileBase}.component`)));
20
25
  const base = ` {
21
26
  path: '${r.path}',
22
27
  loadComponent: () =>
@@ -19,13 +19,13 @@ function inferScreen(method, hasInput) {
19
19
  }
20
20
  function inferActions(method, hasInput) {
21
21
  if (method === 'get' && hasInput) {
22
- return { primary: { type: 'submit', label: 'Buscar' } };
22
+ return { primary: { type: 'submit', label: 'Search' } };
23
23
  }
24
24
  if (method === 'post') {
25
- return { primary: { type: 'submit', label: 'Criar' } };
25
+ return { primary: { type: 'submit', label: 'Create' } };
26
26
  }
27
27
  if (method === 'put' || method === 'patch') {
28
- return { primary: { type: 'submit', label: 'Salvar' } };
28
+ return { primary: { type: 'submit', label: 'Save' } };
29
29
  }
30
30
  return {};
31
31
  }
@@ -49,6 +49,15 @@ function generateScreen(endpoint, api) {
49
49
  pathParams.length > 0;
50
50
  const openapiVersion = api?.info?.version || 'unknown';
51
51
  const screenMeta = buildMeta(endpoint.operationId, 'api', openapiVersion);
52
+ const columnKeys = method === 'get'
53
+ ? inferResponseColumns(endpoint)
54
+ : [];
55
+ const responseFormat = inferResponseFormat(endpoint);
56
+ const columns = columnKeys.map(key => ({
57
+ key,
58
+ label: toLabel(key),
59
+ visible: true
60
+ }));
52
61
  return {
53
62
  meta: screenMeta,
54
63
  entity: endpoint.summary
@@ -70,9 +79,90 @@ function generateScreen(endpoint, api) {
70
79
  layout: { type: 'single' },
71
80
  fields: decorateFields(fields, 'body', openapiVersion),
72
81
  actions: inferActions(method, hasInput),
73
- data: {}
82
+ response: responseFormat
83
+ ? { format: responseFormat }
84
+ : undefined,
85
+ data: {
86
+ table: {
87
+ columns
88
+ }
89
+ }
74
90
  };
75
91
  }
92
+ function inferResponseColumns(endpoint) {
93
+ const schema = getPrimaryResponseSchema(endpoint);
94
+ if (!schema)
95
+ return [];
96
+ return inferColumnsFromSchema(schema);
97
+ }
98
+ function inferResponseFormat(endpoint) {
99
+ const schema = getPrimaryResponseSchema(endpoint);
100
+ if (!schema)
101
+ return null;
102
+ if (!hasResponseData(schema))
103
+ return null;
104
+ return 'table';
105
+ }
106
+ function getPrimaryResponseSchema(endpoint) {
107
+ const responses = endpoint?.responses ?? {};
108
+ const candidate = responses['200'] ||
109
+ responses['201'] ||
110
+ responses.default ||
111
+ Object.values(responses)[0];
112
+ return candidate?.content?.['application/json']?.schema ?? null;
113
+ }
114
+ function hasResponseData(schema) {
115
+ if (!schema)
116
+ return false;
117
+ if (Array.isArray(schema.allOf)) {
118
+ return schema.allOf.some((entry) => hasResponseData(entry));
119
+ }
120
+ if (Array.isArray(schema.anyOf)) {
121
+ return schema.anyOf.some((entry) => hasResponseData(entry));
122
+ }
123
+ if (Array.isArray(schema.oneOf)) {
124
+ return schema.oneOf.some((entry) => hasResponseData(entry));
125
+ }
126
+ if (schema.type === 'array')
127
+ return true;
128
+ if (schema.type === 'object' &&
129
+ (schema.properties || schema.additionalProperties)) {
130
+ return true;
131
+ }
132
+ return inferColumnsFromSchema(schema).length > 0;
133
+ }
134
+ function inferColumnsFromSchema(schema) {
135
+ if (!schema)
136
+ return [];
137
+ if (Array.isArray(schema.allOf)) {
138
+ for (const entry of schema.allOf) {
139
+ const columns = inferColumnsFromSchema(entry);
140
+ if (columns.length)
141
+ return columns;
142
+ }
143
+ }
144
+ if (schema.type === 'array') {
145
+ return inferColumnsFromSchema(schema.items);
146
+ }
147
+ if (schema.type === 'object' && schema.properties) {
148
+ const commonKeys = [
149
+ 'data',
150
+ 'items',
151
+ 'results',
152
+ 'list',
153
+ 'records',
154
+ 'products'
155
+ ];
156
+ for (const key of commonKeys) {
157
+ const nested = schema.properties[key];
158
+ const columns = inferColumnsFromSchema(nested);
159
+ if (columns.length)
160
+ return columns;
161
+ }
162
+ return Object.keys(schema.properties);
163
+ }
164
+ return [];
165
+ }
76
166
  function extractQueryParams(endpoint, api) {
77
167
  const params = endpoint?.parameters ?? [];
78
168
  return params
@@ -110,11 +200,16 @@ function extractPathParams(path, api) {
110
200
  }));
111
201
  }
112
202
  function toLabel(value) {
113
- return String(value)
203
+ return stripDiacritics(String(value))
114
204
  .replace(/[_-]/g, ' ')
115
205
  .replace(/([a-z])([A-Z])/g, '$1 $2')
116
206
  .replace(/\b\w/g, char => char.toUpperCase());
117
207
  }
208
+ function stripDiacritics(value) {
209
+ return value
210
+ .normalize('NFD')
211
+ .replace(/[\u0300-\u036f]/g, '');
212
+ }
118
213
  function buildMeta(id, source, openapiVersion) {
119
214
  return {
120
215
  id,
@@ -10,6 +10,10 @@ const PRESENTATION_KEYS = [
10
10
  'group',
11
11
  'hidden'
12
12
  ];
13
+ const COLUMN_PRESENTATION_KEYS = [
14
+ 'label',
15
+ 'visible'
16
+ ];
13
17
  function mergeScreen(nextScreen, overlay, prevGenerated, options) {
14
18
  const debug = [];
15
19
  if (!overlay) {
@@ -25,7 +29,9 @@ function mergeScreen(nextScreen, overlay, prevGenerated, options) {
25
29
  entity: normalizedOverlay.entity ?? normalizedNext.entity,
26
30
  screen: normalizedOverlay.screen ?? normalizedNext.screen,
27
31
  layout: normalizedOverlay.layout ?? normalizedNext.layout,
28
- actions: mergeActions(normalizedNext.actions, normalizedOverlay.actions)
32
+ actions: mergeActions(normalizedNext.actions, normalizedOverlay.actions),
33
+ response: mergeResponse(normalizedNext.response, normalizedOverlay.response),
34
+ data: mergeData(normalizedNext.data, normalizedOverlay.data)
29
35
  };
30
36
  merged.api = {
31
37
  ...normalizedNext.api,
@@ -46,6 +52,62 @@ function mergeActions(nextActions, overlayActions) {
46
52
  }
47
53
  return merged;
48
54
  }
55
+ function mergeResponse(nextResponse, overlayResponse) {
56
+ if (!overlayResponse)
57
+ return nextResponse;
58
+ const allowed = ['table', 'cards', 'raw'];
59
+ const candidate = String(overlayResponse?.format || '')
60
+ .trim()
61
+ .toLowerCase();
62
+ if (!allowed.includes(candidate))
63
+ return nextResponse;
64
+ return {
65
+ ...(nextResponse || {}),
66
+ format: candidate
67
+ };
68
+ }
69
+ function mergeData(nextData, overlayData) {
70
+ const nextColumns = nextData?.table?.columns;
71
+ const overlayColumns = overlayData?.table?.columns;
72
+ if (!Array.isArray(nextColumns)) {
73
+ return nextData ?? {};
74
+ }
75
+ const overlayMap = new Map();
76
+ if (Array.isArray(overlayColumns)) {
77
+ for (const entry of overlayColumns) {
78
+ if (!entry || typeof entry !== 'object')
79
+ continue;
80
+ const key = String(entry.key ?? entry.id ?? entry.column ?? '').trim();
81
+ if (!key)
82
+ continue;
83
+ overlayMap.set(key, entry);
84
+ }
85
+ }
86
+ const mergedColumns = nextColumns.map((entry) => {
87
+ if (!entry || typeof entry !== 'object')
88
+ return entry;
89
+ const key = String(entry.key ?? entry.id ?? entry.column ?? '').trim();
90
+ if (!key)
91
+ return entry;
92
+ const overlay = overlayMap.get(key);
93
+ if (!overlay)
94
+ return entry;
95
+ const merged = { ...entry };
96
+ for (const prop of COLUMN_PRESENTATION_KEYS) {
97
+ if (overlay[prop] !== undefined) {
98
+ merged[prop] = overlay[prop];
99
+ }
100
+ }
101
+ return merged;
102
+ });
103
+ return {
104
+ ...(nextData || {}),
105
+ table: {
106
+ ...(nextData?.table || {}),
107
+ columns: mergedColumns
108
+ }
109
+ };
110
+ }
49
111
  function mergeFieldList(nextFields, overlayFields, prevFields, options, debug, scope) {
50
112
  const nextMap = indexById(nextFields, scope);
51
113
  const overlayMap = indexById(overlayFields, scope);
@@ -80,20 +142,6 @@ function mergeFieldList(nextFields, overlayFields, prevFields, options, debug, s
80
142
  for (const [id, nextField] of nextMap.entries()) {
81
143
  if (used.has(id))
82
144
  continue;
83
- const prevField = prevMap.get(id);
84
- if (prevField && !overlayMap.has(id)) {
85
- result.push({
86
- ...nextField,
87
- hidden: true,
88
- meta: {
89
- ...mergeMeta(nextField.meta, prevField.meta, options.openapiVersion),
90
- userRemoved: true,
91
- lastChangedBy: 'user'
92
- }
93
- });
94
- debug.push(`USER_REMOVED_TOMBSTONE ${id}`);
95
- continue;
96
- }
97
145
  const autoAdded = !nextField.required;
98
146
  result.push({
99
147
  ...nextField,
@@ -127,6 +175,13 @@ function mergeField(nextField, overlayField, prevField, options, debug) {
127
175
  if (prevField && prevField.type !== nextField.type) {
128
176
  merged.ui = undefined;
129
177
  merged.options = nextField.options ?? null;
178
+ merged.defaultValue = nextField.defaultValue;
179
+ merged.hidden = nextField.hidden;
180
+ merged.meta = {
181
+ ...meta,
182
+ userRemoved: false,
183
+ lastChangedBy: 'api'
184
+ };
130
185
  debug.push(`TYPE_CHANGED ${meta.id}`);
131
186
  }
132
187
  const prevEnum = Array.isArray(prevField?.options);
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ const commander_1 = require("commander");
5
5
  const generate_1 = require("./commands/generate");
6
6
  const angular_1 = require("./commands/angular");
7
7
  const login_1 = require("./commands/login");
8
+ const merge_1 = require("./commands/merge");
8
9
  const config_1 = require("./runtime/config");
9
10
  const telemetry_1 = require("./telemetry");
10
11
  const logger_1 = require("./runtime/logger");
@@ -17,13 +18,45 @@ program
17
18
  .option('--dev', 'Enable verbose logs')
18
19
  .option('--verbose', 'Enable verbose logs (same as --dev)');
19
20
  /**
20
- * 1️⃣ OpenAPI → Screen schemas
21
+ * 1️⃣ Default flow: OpenAPI → Schemas → Angular
21
22
  */
22
23
  program
23
24
  .command('generate')
24
- .description('Generate screen schemas from OpenAPI')
25
- .requiredOption('-o, --openapi <path>', 'OpenAPI file')
26
- .option('--output <path>', 'Output directory for generate-ui (default: ./src/generate-ui or ./generate-ui)')
25
+ .description('Generate schemas and Angular code (default flow)')
26
+ .option('-o, --openapi <path>', 'OpenAPI file (optional if configured in generateui-config.json)')
27
+ .option('--output <path>', 'Output directory for generate-ui (optional if configured in generateui-config.json)')
28
+ .option('-f, --features <path>', 'Angular features output directory (optional if configured in generateui-config.json)')
29
+ .option('-w, --watch', 'Keep watching .screen.json files after generation')
30
+ .option('-d, --debug', 'Explain merge decisions')
31
+ .action(async (options) => {
32
+ const { telemetry, dev, verbose } = program.opts();
33
+ (0, logger_1.setVerbose)(Boolean(dev || verbose));
34
+ try {
35
+ await (0, telemetry_1.trackGenerateCalled)();
36
+ await (0, generate_1.generate)({
37
+ openapi: options.openapi,
38
+ output: options.output,
39
+ debug: options.debug,
40
+ telemetryEnabled: telemetry
41
+ });
42
+ await (0, angular_1.angular)({
43
+ featuresPath: options.features,
44
+ watch: Boolean(options.watch),
45
+ telemetryEnabled: telemetry
46
+ });
47
+ }
48
+ catch (error) {
49
+ handleCliError(error);
50
+ }
51
+ });
52
+ /**
53
+ * 2️⃣ Advanced: OpenAPI → Screen schemas only
54
+ */
55
+ program
56
+ .command('schema')
57
+ .description('Advanced: generate screen schemas only')
58
+ .option('-o, --openapi <path>', 'OpenAPI file (optional if configured in generateui-config.json)')
59
+ .option('--output <path>', 'Output directory for generate-ui (optional if configured in generateui-config.json)')
27
60
  .option('-d, --debug', 'Explain merge decisions')
28
61
  .action(async (options) => {
29
62
  const { telemetry, dev, verbose } = program.opts();
@@ -42,13 +75,15 @@ program
42
75
  }
43
76
  });
44
77
  /**
45
- * 2️⃣ Screen schemas → Angular code
78
+ * 3️⃣ Advanced: Screen schemas → Angular code
46
79
  */
47
80
  program
48
81
  .command('angular')
49
- .description('Generate Angular code from screen schemas')
82
+ .description('Advanced: generate Angular code from screen schemas')
50
83
  .option('-s, --schemas <path>', 'Directory containing generate-ui (with overlays/)')
51
- .option('-f, --features <path>', 'Angular features output directory')
84
+ .option('-f, --features <path>', 'Angular features output directory (optional if configured in generateui-config.json)')
85
+ .option('-w, --watch', 'Watch .screen.json files and regenerate Angular on changes (default)')
86
+ .option('--no-watch', 'Run Angular generation once and exit')
52
87
  .action(async (options) => {
53
88
  const { telemetry, dev, verbose } = program.opts();
54
89
  (0, logger_1.setVerbose)(Boolean(dev || verbose));
@@ -56,6 +91,7 @@ program
56
91
  await (0, angular_1.angular)({
57
92
  schemasPath: options.schemas,
58
93
  featuresPath: options.features,
94
+ watch: options.watch !== false,
59
95
  telemetryEnabled: telemetry
60
96
  });
61
97
  }
@@ -64,7 +100,7 @@ program
64
100
  }
65
101
  });
66
102
  /**
67
- * 3️⃣ Login (Dev plan)
103
+ * 4️⃣ Login (Dev plan)
68
104
  */
69
105
  program
70
106
  .command('login')
@@ -79,6 +115,31 @@ program
79
115
  handleCliError(error);
80
116
  }
81
117
  });
118
+ /**
119
+ * 5️⃣ Compare generated vs overrides (interactive)
120
+ */
121
+ program
122
+ .command('merge')
123
+ .description('Compare generated vs overrides with an interactive diff tool')
124
+ .requiredOption('--feature <name>', 'Feature folder or operationId')
125
+ .option('-f, --features <path>', 'Angular features output directory')
126
+ .option('--file <name>', 'File to compare: component.ts, component.html, component.scss, or all')
127
+ .option('--tool <name>', 'Diff tool (code, meld, kdiff3, bc, or any executable)')
128
+ .action(async (options) => {
129
+ const { telemetry, dev, verbose } = program.opts();
130
+ (0, logger_1.setVerbose)(Boolean(dev || verbose));
131
+ try {
132
+ await (0, merge_1.merge)({
133
+ featuresPath: options.features,
134
+ feature: options.feature,
135
+ file: options.file,
136
+ tool: options.tool
137
+ });
138
+ }
139
+ catch (error) {
140
+ handleCliError(error);
141
+ }
142
+ });
82
143
  function handleCliError(error) {
83
144
  if (error instanceof Error) {
84
145
  console.error(error.message.replace(/\\n/g, '\n'));
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.requireFeature = requireFeature;
4
+ const config_1 = require("../runtime/config");
4
5
  function requireFeature(features, featureKey, reason) {
5
6
  if (!features[featureKey]) {
6
7
  const details = reason ? ` ${reason}` : '';
7
- throw new Error(`Requires Dev plan.${details} Execute \`generate-ui login\` to continue.`);
8
+ throw new Error(`This feature requires an active paid subscription.${details} Upgrade: ${(0, config_1.getDevPlanUrl)()}`);
8
9
  }
9
10
  }
@@ -14,12 +14,15 @@ const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.generateui');
14
14
  const PERMISSIONS_PATH = path_1.default.join(CONFIG_DIR, 'permissions.json');
15
15
  const DEV_OFFLINE_DAYS = 7;
16
16
  const FREE_DEFAULT = {
17
- plan: 'free',
18
17
  features: {
19
18
  intelligentGeneration: false,
20
19
  safeRegeneration: false,
21
20
  uiOverrides: false,
22
- maxGenerations: 1
21
+ maxGenerations: -1
22
+ },
23
+ subscription: {
24
+ status: 'anonymous',
25
+ reason: 'Login required to unlock paid features.'
23
26
  }
24
27
  };
25
28
  function ensureConfigDir() {
@@ -30,7 +33,7 @@ function readCache() {
30
33
  return null;
31
34
  try {
32
35
  const parsed = JSON.parse(fs_1.default.readFileSync(PERMISSIONS_PATH, 'utf-8'));
33
- if (!parsed.plan || !parsed.features)
36
+ if (!parsed.features || !parsed.subscription?.status)
34
37
  return null;
35
38
  return parsed;
36
39
  }
@@ -52,9 +55,7 @@ function writeCache(response) {
52
55
  async function fetchPermissions() {
53
56
  const apiBase = (0, config_1.getApiBaseUrl)();
54
57
  const token = (0, token_1.loadToken)();
55
- const headers = {
56
- 'Content-Type': 'application/json'
57
- };
58
+ const headers = {};
58
59
  if (token?.accessToken) {
59
60
  headers.Authorization = `Bearer ${token.accessToken}`;
60
61
  }
@@ -65,10 +66,48 @@ async function fetchPermissions() {
65
66
  if (!response.ok) {
66
67
  throw new Error('Failed to fetch permissions');
67
68
  }
68
- const data = (await response.json());
69
+ const raw = (await response.json());
70
+ const data = normalizePermissions(raw);
69
71
  writeCache(data);
70
72
  return data;
71
73
  }
74
+ function normalizePermissions(raw) {
75
+ const features = normalizeFeatures(raw?.features);
76
+ const subscription = normalizeSubscription(raw);
77
+ return { features, subscription };
78
+ }
79
+ function normalizeFeatures(raw) {
80
+ const fallback = FREE_DEFAULT.features;
81
+ return {
82
+ intelligentGeneration: typeof raw?.intelligentGeneration === 'boolean'
83
+ ? raw.intelligentGeneration
84
+ : fallback.intelligentGeneration,
85
+ safeRegeneration: typeof raw?.safeRegeneration === 'boolean'
86
+ ? raw.safeRegeneration
87
+ : fallback.safeRegeneration,
88
+ uiOverrides: typeof raw?.uiOverrides === 'boolean'
89
+ ? raw.uiOverrides
90
+ : fallback.uiOverrides,
91
+ maxGenerations: typeof raw?.maxGenerations === 'number'
92
+ ? raw.maxGenerations
93
+ : fallback.maxGenerations
94
+ };
95
+ }
96
+ function normalizeSubscription(raw) {
97
+ const source = raw?.subscription ?? {};
98
+ const status = String(source?.status ??
99
+ (raw?.plan === 'dev' ? 'active' : 'inactive'));
100
+ const reasonValue = source?.reason;
101
+ const normalizedStatus = status.toLowerCase();
102
+ return {
103
+ status,
104
+ reason: typeof reasonValue === 'string' && reasonValue.trim().length
105
+ ? reasonValue
106
+ : normalizedStatus === 'active'
107
+ ? null
108
+ : `Assinatura inativa. Faça upgrade para o plano Dev: ${(0, config_1.getDevPlanUrl)()}`
109
+ };
110
+ }
72
111
  function cacheIsValid(cache) {
73
112
  const expiresAt = new Date(cache.expiresAt).getTime();
74
113
  if (Number.isNaN(expiresAt))
@@ -81,9 +120,24 @@ async function getPermissions() {
81
120
  }
82
121
  catch {
83
122
  const tokenPresent = (0, token_1.tokenFileExists)();
123
+ const tokenState = (0, token_1.getTokenState)();
84
124
  const cache = readCache();
85
125
  if (cache && cacheIsValid(cache)) {
86
- return { plan: cache.plan, features: cache.features };
126
+ return {
127
+ features: cache.features,
128
+ subscription: cache.subscription
129
+ };
130
+ }
131
+ // If the user is logged in but the API is temporarily unavailable,
132
+ // fallback to the last known permissions to avoid blocking generation.
133
+ if (tokenPresent && cache) {
134
+ return {
135
+ features: cache.features,
136
+ subscription: cache.subscription
137
+ };
138
+ }
139
+ if (tokenState === 'expired') {
140
+ throw new Error(`Sua sessão expirou. Faça login novamente: ${(0, config_1.getWebAuthUrl)()}`);
87
141
  }
88
142
  if (tokenPresent) {
89
143
  throw new Error('Login concluído, mas não foi possível validar sua licença agora. Verifique sua conexão com a API e tente novamente.');
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.tokenFileExists = tokenFileExists;
7
7
  exports.loadToken = loadToken;
8
+ exports.getTokenState = getTokenState;
8
9
  exports.saveToken = saveToken;
9
10
  exports.clearToken = clearToken;
10
11
  const fs_1 = __importDefault(require("fs"));
@@ -38,6 +39,24 @@ function loadToken() {
38
39
  return null;
39
40
  }
40
41
  }
42
+ function getTokenState() {
43
+ if (!tokenFileExists())
44
+ return 'missing';
45
+ try {
46
+ const parsed = JSON.parse(fs_1.default.readFileSync(TOKEN_PATH, 'utf-8'));
47
+ if (!parsed?.accessToken || !parsed?.expiresAt)
48
+ return 'invalid';
49
+ const expiresAt = normalizeExpiresAt(parsed.expiresAt);
50
+ if (!expiresAt)
51
+ return 'invalid';
52
+ if (expiresAt <= Date.now())
53
+ return 'expired';
54
+ return 'valid';
55
+ }
56
+ catch {
57
+ return 'invalid';
58
+ }
59
+ }
41
60
  function normalizeExpiresAt(value) {
42
61
  const trimmed = String(value).trim();
43
62
  if (!trimmed.length)
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
6
8
  const package_json_1 = __importDefault(require("../package.json"));
7
9
  const TELEMETRY_URL = process.env.GENERATEUI_TELEMETRY_URL?.trim() ||
8
10
  'https://generateuibackend-production.up.railway.app/events';
@@ -25,11 +27,49 @@ async function sendInstallEvent() {
25
27
  // Postinstall must never block installation.
26
28
  }
27
29
  }
30
+ function seedProjectConfig() {
31
+ const isGlobalInstall = String(process.env.npm_config_global || '') === 'true';
32
+ if (isGlobalInstall)
33
+ return;
34
+ const initCwd = process.env.INIT_CWD;
35
+ if (!initCwd)
36
+ return;
37
+ const projectRoot = path_1.default.resolve(initCwd);
38
+ const packageJsonPath = path_1.default.join(projectRoot, 'package.json');
39
+ const srcAppPath = path_1.default.join(projectRoot, 'src', 'app');
40
+ if (!fs_1.default.existsSync(packageJsonPath) || !fs_1.default.existsSync(srcAppPath))
41
+ return;
42
+ const configPath = path_1.default.join(projectRoot, 'generateui-config.json');
43
+ if (fs_1.default.existsSync(configPath))
44
+ return;
45
+ const openApiCandidates = ['openapi.yaml', 'openapi.yml', 'openapi.json'];
46
+ const openapi = openApiCandidates.find(file => fs_1.default.existsSync(path_1.default.join(projectRoot, file))) || 'openapi.yaml';
47
+ const config = {
48
+ openapi,
49
+ schemas: 'src/generate-ui',
50
+ features: 'src/app/features',
51
+ appTitle: 'Store',
52
+ defaultRoute: '',
53
+ menu: {
54
+ autoInject: true
55
+ },
56
+ views: {
57
+ ProductsAdmin: 'cards'
58
+ }
59
+ };
60
+ try {
61
+ fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
62
+ console.log(` Created ${path_1.default.basename(configPath)} in project root`);
63
+ }
64
+ catch {
65
+ // Postinstall must never block installation.
66
+ }
67
+ }
28
68
  void sendInstallEvent();
69
+ seedProjectConfig();
29
70
  console.log('');
30
71
  console.log('👋 GenerateUI CLI installed');
31
72
  console.log(' Quick start:');
32
- console.log(' 1) generate-ui generate --openapi /path/to/openapi.yaml');
33
- console.log(' 2) generate-ui angular --schemas /path/to/generate-ui --features /path/to/app/src/app/features');
73
+ console.log(' 1) generate-ui generate');
34
74
  console.log(' Tip: customize screens in generate-ui/overlays and menu in generate-ui/menu.overrides.json');
35
75
  console.log('');
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getCliVersion = getCliVersion;
7
7
  exports.getApiBaseUrl = getApiBaseUrl;
8
8
  exports.getWebAuthUrl = getWebAuthUrl;
9
+ exports.getDevPlanUrl = getDevPlanUrl;
9
10
  const package_json_1 = __importDefault(require("../../package.json"));
10
11
  function getCliVersion() {
11
12
  return package_json_1.default.version || '0.0.0';
@@ -16,3 +17,6 @@ function getApiBaseUrl() {
16
17
  function getWebAuthUrl() {
17
18
  return ('https://generateuibackend-production.up.railway.app');
18
19
  }
20
+ function getDevPlanUrl() {
21
+ return 'https://generate-ui.com/plan/dev';
22
+ }