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
|
@@ -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,
|
|
9
|
+
function generateRoutes(routes, generatedRoot, overridesRoot, schemasRoot) {
|
|
10
10
|
const out = path_1.default.join(schemasRoot, 'routes.gen.ts');
|
|
11
|
-
const
|
|
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
|
|
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: '
|
|
22
|
+
return { primary: { type: 'submit', label: 'Search' } };
|
|
23
23
|
}
|
|
24
24
|
if (method === 'post') {
|
|
25
|
-
return { primary: { type: 'submit', label: '
|
|
25
|
+
return { primary: { type: 'submit', label: 'Create' } };
|
|
26
26
|
}
|
|
27
27
|
if (method === 'put' || method === 'patch') {
|
|
28
|
-
return { primary: { type: 'submit', label: '
|
|
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
|
-
|
|
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 →
|
|
21
|
+
* 1️⃣ Default flow: OpenAPI → Schemas → Angular
|
|
21
22
|
*/
|
|
22
23
|
program
|
|
23
24
|
.command('generate')
|
|
24
|
-
.description('Generate
|
|
25
|
-
.
|
|
26
|
-
.option('--output <path>', 'Output directory for 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
|
-
*
|
|
78
|
+
* 3️⃣ Advanced: Screen schemas → Angular code
|
|
46
79
|
*/
|
|
47
80
|
program
|
|
48
81
|
.command('angular')
|
|
49
|
-
.description('
|
|
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
|
-
*
|
|
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'));
|
package/dist/license/guard.js
CHANGED
|
@@ -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(`
|
|
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.
|
|
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
|
|
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 {
|
|
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.');
|
package/dist/license/token.js
CHANGED
|
@@ -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)
|
package/dist/postinstall.js
CHANGED
|
@@ -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
|
|
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('');
|
package/dist/runtime/config.js
CHANGED
|
@@ -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
|
+
}
|