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.
- package/README.md +216 -27
- package/dist/commands/angular.js +535 -40
- package/dist/commands/generate.js +350 -20
- package/dist/commands/login.js +131 -42
- package/dist/commands/merge.js +201 -0
- package/dist/generate-ui/routes.gen.js +4 -0
- package/dist/generators/angular/feature.generator.js +3183 -583
- package/dist/generators/angular/menu.generator.js +165 -0
- 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 +88 -12
- package/dist/license/guard.js +2 -1
- package/dist/license/permissions.js +63 -9
- package/dist/license/token.js +38 -3
- package/dist/postinstall.js +47 -0
- package/dist/runtime/config.js +4 -0
- package/dist/runtime/logger.js +29 -0
- package/dist/runtime/project-config.js +64 -0
- package/package.json +1 -1
|
@@ -0,0 +1,165 @@
|
|
|
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.generateMenu = generateMenu;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function generateMenu(schemasRoot) {
|
|
10
|
+
const menu = loadMenuConfig(schemasRoot) ??
|
|
11
|
+
buildMenuFromRoutes(loadRoutesConfig(schemasRoot)) ?? {
|
|
12
|
+
groups: [],
|
|
13
|
+
ungrouped: []
|
|
14
|
+
};
|
|
15
|
+
const out = path_1.default.join(schemasRoot, 'menu.gen.ts');
|
|
16
|
+
const content = `
|
|
17
|
+
export type GeneratedMenuItem = {
|
|
18
|
+
id: string
|
|
19
|
+
label: string
|
|
20
|
+
route: string
|
|
21
|
+
hidden?: boolean
|
|
22
|
+
icon?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type GeneratedMenuGroup = {
|
|
26
|
+
id: string
|
|
27
|
+
label: string
|
|
28
|
+
items: GeneratedMenuItem[]
|
|
29
|
+
hidden?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type GeneratedMenu = {
|
|
33
|
+
groups: GeneratedMenuGroup[]
|
|
34
|
+
ungrouped: GeneratedMenuItem[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const generatedMenu: GeneratedMenu = ${JSON.stringify(normalizeMenu(menu), null, 2)}
|
|
38
|
+
`;
|
|
39
|
+
fs_1.default.writeFileSync(out, content.trimStart());
|
|
40
|
+
}
|
|
41
|
+
function loadMenuConfig(schemasRoot) {
|
|
42
|
+
const overridePath = path_1.default.join(schemasRoot, 'menu.overrides.json');
|
|
43
|
+
const basePath = path_1.default.join(schemasRoot, 'menu.json');
|
|
44
|
+
if (fs_1.default.existsSync(overridePath)) {
|
|
45
|
+
const override = JSON.parse(fs_1.default.readFileSync(overridePath, 'utf-8'));
|
|
46
|
+
const hasOverride = Array.isArray(override?.groups) &&
|
|
47
|
+
override.groups.length > 0
|
|
48
|
+
? true
|
|
49
|
+
: Array.isArray(override?.ungrouped) &&
|
|
50
|
+
override.ungrouped.length > 0;
|
|
51
|
+
if (hasOverride) {
|
|
52
|
+
return override;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (fs_1.default.existsSync(basePath)) {
|
|
56
|
+
return JSON.parse(fs_1.default.readFileSync(basePath, 'utf-8'));
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function loadRoutesConfig(schemasRoot) {
|
|
61
|
+
const routesPath = path_1.default.join(schemasRoot, 'routes.json');
|
|
62
|
+
if (!fs_1.default.existsSync(routesPath))
|
|
63
|
+
return null;
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(fs_1.default.readFileSync(routesPath, 'utf-8'));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function normalizeMenu(value) {
|
|
72
|
+
return {
|
|
73
|
+
groups: Array.isArray(value?.groups)
|
|
74
|
+
? value.groups.map(normalizeGroup)
|
|
75
|
+
: [],
|
|
76
|
+
ungrouped: Array.isArray(value?.ungrouped)
|
|
77
|
+
? value.ungrouped.map(normalizeItem)
|
|
78
|
+
: []
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function normalizeGroup(value) {
|
|
82
|
+
return {
|
|
83
|
+
id: String(value?.id ?? ''),
|
|
84
|
+
label: String(value?.label ?? ''),
|
|
85
|
+
hidden: Boolean(value?.hidden) || undefined,
|
|
86
|
+
items: Array.isArray(value?.items)
|
|
87
|
+
? value.items.map(normalizeItem)
|
|
88
|
+
: []
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function normalizeItem(value) {
|
|
92
|
+
return {
|
|
93
|
+
id: String(value?.id ?? ''),
|
|
94
|
+
label: String(value?.label ?? ''),
|
|
95
|
+
route: String(value?.route ?? ''),
|
|
96
|
+
hidden: Boolean(value?.hidden) || undefined,
|
|
97
|
+
icon: value?.icon ? String(value.icon) : undefined
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function buildMenuFromRoutes(routes) {
|
|
101
|
+
if (!Array.isArray(routes) || routes.length === 0)
|
|
102
|
+
return null;
|
|
103
|
+
const groups = [];
|
|
104
|
+
const ungrouped = [];
|
|
105
|
+
const groupMap = new Map();
|
|
106
|
+
for (const route of routes) {
|
|
107
|
+
if (!route?.path || !route?.operationId)
|
|
108
|
+
continue;
|
|
109
|
+
const item = {
|
|
110
|
+
id: String(route.operationId),
|
|
111
|
+
label: toLabel(String(route.label ?? route.operationId)),
|
|
112
|
+
route: normalizeRoutePath(String(route.path ?? route.operationId ?? ''))
|
|
113
|
+
};
|
|
114
|
+
const rawGroup = route.group ? String(route.group) : '';
|
|
115
|
+
if (!rawGroup) {
|
|
116
|
+
ungrouped.push(item);
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const groupId = toKebab(rawGroup);
|
|
120
|
+
let group = groupMap.get(groupId);
|
|
121
|
+
if (!group) {
|
|
122
|
+
group = {
|
|
123
|
+
id: groupId,
|
|
124
|
+
label: toLabel(rawGroup),
|
|
125
|
+
items: []
|
|
126
|
+
};
|
|
127
|
+
groupMap.set(groupId, group);
|
|
128
|
+
groups.push(group);
|
|
129
|
+
}
|
|
130
|
+
group.items.push(item);
|
|
131
|
+
}
|
|
132
|
+
return { groups, ungrouped };
|
|
133
|
+
}
|
|
134
|
+
function toKebab(value) {
|
|
135
|
+
return String(value)
|
|
136
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
137
|
+
.replace(/[_\\s]+/g, '-')
|
|
138
|
+
.toLowerCase();
|
|
139
|
+
}
|
|
140
|
+
function toLabel(value) {
|
|
141
|
+
return String(value)
|
|
142
|
+
.replace(/[_-]/g, ' ')
|
|
143
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
144
|
+
.replace(/\\b\\w/g, char => char.toUpperCase());
|
|
145
|
+
}
|
|
146
|
+
function normalizeRoutePath(value) {
|
|
147
|
+
if (!value)
|
|
148
|
+
return value;
|
|
149
|
+
if (value.includes('/'))
|
|
150
|
+
return value.replace(/^\//, '');
|
|
151
|
+
const pascal = toPascalCase(value);
|
|
152
|
+
return toRouteSegment(pascal);
|
|
153
|
+
}
|
|
154
|
+
function toRouteSegment(value) {
|
|
155
|
+
if (!value)
|
|
156
|
+
return value;
|
|
157
|
+
return value[0].toLowerCase() + value.slice(1);
|
|
158
|
+
}
|
|
159
|
+
function toPascalCase(value) {
|
|
160
|
+
return String(value)
|
|
161
|
+
.split(/[^a-zA-Z0-9]+/)
|
|
162
|
+
.filter(Boolean)
|
|
163
|
+
.map(part => part[0].toUpperCase() + part.slice(1))
|
|
164
|
+
.join('');
|
|
165
|
+
}
|
|
@@ -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,25 +5,62 @@ 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");
|
|
11
|
+
const logger_1 = require("./runtime/logger");
|
|
10
12
|
const program = new commander_1.Command();
|
|
11
13
|
program
|
|
12
14
|
.name('generate-ui')
|
|
13
15
|
.description('Generate UI from OpenAPI')
|
|
14
16
|
.version((0, config_1.getCliVersion)())
|
|
15
|
-
.option('--no-telemetry', 'Disable telemetry')
|
|
17
|
+
.option('--no-telemetry', 'Disable telemetry')
|
|
18
|
+
.option('--dev', 'Enable verbose logs')
|
|
19
|
+
.option('--verbose', 'Enable verbose logs (same as --dev)');
|
|
16
20
|
/**
|
|
17
|
-
* 1️⃣ OpenAPI →
|
|
21
|
+
* 1️⃣ Default flow: OpenAPI → Schemas → Angular
|
|
18
22
|
*/
|
|
19
23
|
program
|
|
20
24
|
.command('generate')
|
|
21
|
-
.description('Generate
|
|
22
|
-
.
|
|
23
|
-
.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')
|
|
24
30
|
.option('-d, --debug', 'Explain merge decisions')
|
|
25
31
|
.action(async (options) => {
|
|
26
|
-
const { telemetry } = program.opts();
|
|
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)')
|
|
60
|
+
.option('-d, --debug', 'Explain merge decisions')
|
|
61
|
+
.action(async (options) => {
|
|
62
|
+
const { telemetry, dev, verbose } = program.opts();
|
|
63
|
+
(0, logger_1.setVerbose)(Boolean(dev || verbose));
|
|
27
64
|
try {
|
|
28
65
|
await (0, telemetry_1.trackGenerateCalled)();
|
|
29
66
|
await (0, generate_1.generate)({
|
|
@@ -38,19 +75,23 @@ program
|
|
|
38
75
|
}
|
|
39
76
|
});
|
|
40
77
|
/**
|
|
41
|
-
*
|
|
78
|
+
* 3️⃣ Advanced: Screen schemas → Angular code
|
|
42
79
|
*/
|
|
43
80
|
program
|
|
44
81
|
.command('angular')
|
|
45
|
-
.description('
|
|
82
|
+
.description('Advanced: generate Angular code from screen schemas')
|
|
46
83
|
.option('-s, --schemas <path>', 'Directory containing generate-ui (with overlays/)')
|
|
47
|
-
.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')
|
|
48
87
|
.action(async (options) => {
|
|
49
|
-
const { telemetry } = program.opts();
|
|
88
|
+
const { telemetry, dev, verbose } = program.opts();
|
|
89
|
+
(0, logger_1.setVerbose)(Boolean(dev || verbose));
|
|
50
90
|
try {
|
|
51
91
|
await (0, angular_1.angular)({
|
|
52
92
|
schemasPath: options.schemas,
|
|
53
93
|
featuresPath: options.features,
|
|
94
|
+
watch: options.watch !== false,
|
|
54
95
|
telemetryEnabled: telemetry
|
|
55
96
|
});
|
|
56
97
|
}
|
|
@@ -59,13 +100,14 @@ program
|
|
|
59
100
|
}
|
|
60
101
|
});
|
|
61
102
|
/**
|
|
62
|
-
*
|
|
103
|
+
* 4️⃣ Login (Dev plan)
|
|
63
104
|
*/
|
|
64
105
|
program
|
|
65
106
|
.command('login')
|
|
66
107
|
.description('Login to unlock Dev features')
|
|
67
108
|
.action(async () => {
|
|
68
|
-
const { telemetry } = program.opts();
|
|
109
|
+
const { telemetry, dev, verbose } = program.opts();
|
|
110
|
+
(0, logger_1.setVerbose)(Boolean(dev || verbose));
|
|
69
111
|
try {
|
|
70
112
|
await (0, login_1.login)({ telemetryEnabled: telemetry });
|
|
71
113
|
}
|
|
@@ -73,9 +115,43 @@ program
|
|
|
73
115
|
handleCliError(error);
|
|
74
116
|
}
|
|
75
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
|
+
});
|
|
76
143
|
function handleCliError(error) {
|
|
77
144
|
if (error instanceof Error) {
|
|
78
145
|
console.error(error.message.replace(/\\n/g, '\n'));
|
|
146
|
+
if ((0, logger_1.isVerbose)() && error.stack) {
|
|
147
|
+
console.error('');
|
|
148
|
+
console.error('🔎 Stack trace:');
|
|
149
|
+
console.error(error.stack);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.error('');
|
|
153
|
+
console.error('ℹ️ Tip: re-run with --dev to see detailed logs.');
|
|
154
|
+
}
|
|
79
155
|
}
|
|
80
156
|
else {
|
|
81
157
|
console.error('Unexpected error');
|
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
|
}
|