generate-ui-cli 2.0.0 → 2.1.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 +85 -23
- package/dist/commands/angular.js +54 -6
- package/dist/commands/generate.js +21 -2
- package/dist/commands/login.js +20 -1
- package/dist/generators/angular/feature.generator.js +15 -2
- package/dist/generators/angular/routes.generator.js +22 -5
- package/dist/index.js +4 -2
- package/dist/runtime/user-config.js +38 -0
- package/dist/telemetry.js +99 -22
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -65,8 +65,9 @@ What happens after this command:
|
|
|
65
65
|
- GenerateUI reads your OpenAPI and detects endpoints.
|
|
66
66
|
- It identifies CRUD-like operations (list, get by id, create, update, delete).
|
|
67
67
|
- It maps request/response schemas.
|
|
68
|
-
-
|
|
69
|
-
-
|
|
68
|
+
- If your project has a `src/` folder, GenerateUI creates `src/generate-ui/`.
|
|
69
|
+
- Otherwise it creates `generate-ui/` next to your OpenAPI file.
|
|
70
|
+
- Inside it you get `generated/`, `overlays/`, `screens.json`, `routes.json`, and `routes.gen.ts`.
|
|
70
71
|
|
|
71
72
|
What you should review now:
|
|
72
73
|
|
|
@@ -80,9 +81,7 @@ Tip: this is the best moment to adjust naming and structure before generating co
|
|
|
80
81
|
## 2) Generate Angular code from `screens.json`
|
|
81
82
|
|
|
82
83
|
```bash
|
|
83
|
-
generate-ui angular
|
|
84
|
-
--schemas /Users/nicoleprevid/Downloads/generateui-playground/frontend/src/app/assets/generate-ui \
|
|
85
|
-
--features /Users/nicoleprevid/Downloads/generateui-playground/frontend/src/app/features
|
|
84
|
+
generate-ui angular
|
|
86
85
|
```
|
|
87
86
|
|
|
88
87
|
What happens after this command:
|
|
@@ -104,6 +103,24 @@ What you should review now:
|
|
|
104
103
|
Note:
|
|
105
104
|
If your project uses custom routing, standalone components, or advanced layouts, you may need to adjust how routes are plugged in.
|
|
106
105
|
|
|
106
|
+
Defaults:
|
|
107
|
+
- `--schemas` defaults to the last generated path (stored in `~/.generateui/config.json`), otherwise `./src/generate-ui` (or `./frontend/src/generate-ui` / `./generate-ui`)
|
|
108
|
+
- `--features` defaults to `./src/app/features` when it exists, otherwise `./frontend/src/app/features` or `./features`
|
|
109
|
+
|
|
110
|
+
Optional paths:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
generate-ui angular \
|
|
114
|
+
--schemas /path/to/generate-ui \
|
|
115
|
+
--features /path/to/angular/features
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Custom output folder for `generate`:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
generate-ui generate --openapi youropenapi.yaml --output /path/to/generate-ui
|
|
122
|
+
```
|
|
123
|
+
|
|
107
124
|
## Login (Dev plan)
|
|
108
125
|
|
|
109
126
|
```bash
|
|
@@ -115,21 +132,26 @@ What happens after this command:
|
|
|
115
132
|
- You authenticate your device to unlock Dev features.
|
|
116
133
|
- Dev features include safe regeneration, UI overrides, and unlimited generations.
|
|
117
134
|
|
|
135
|
+
## Telemetry
|
|
136
|
+
|
|
137
|
+
GenerateUI collects anonymous usage data such as CLI version, OS, and executed commands to improve the product.
|
|
138
|
+
No source code or OpenAPI content is ever sent.
|
|
139
|
+
Telemetry can be disabled by setting `telemetry=false` in `~/.generateui/config.json` or by running with `--no-telemetry`.
|
|
140
|
+
|
|
118
141
|
## Plugging Routes into Your App
|
|
119
142
|
|
|
120
143
|
GenerateUI usually creates route files such as:
|
|
121
144
|
|
|
122
|
-
- `
|
|
123
|
-
- or per feature: `users.routes.ts`, `orders.routes.ts`
|
|
145
|
+
- `src/generate-ui/routes.gen.ts` or `frontend/src/generate-ui/routes.gen.ts` or `generate-ui/routes.gen.ts`
|
|
124
146
|
|
|
125
147
|
Example (Angular Router):
|
|
126
148
|
|
|
127
149
|
```ts
|
|
128
|
-
import {
|
|
150
|
+
import { generatedRoutes } from '../generate-ui/routes.gen';
|
|
129
151
|
|
|
130
152
|
export const routes = [
|
|
131
153
|
// ...your existing routes
|
|
132
|
-
...
|
|
154
|
+
...generatedRoutes
|
|
133
155
|
];
|
|
134
156
|
```
|
|
135
157
|
|
|
@@ -139,23 +161,63 @@ Things to pay attention to:
|
|
|
139
161
|
- authentication guards
|
|
140
162
|
- layout components (`<router-outlet>` placement)
|
|
141
163
|
|
|
164
|
+
## Angular >= 15 (standalone) setup
|
|
165
|
+
|
|
166
|
+
Step-by-step:
|
|
167
|
+
|
|
168
|
+
1) Generate files:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
generate-ui generate --openapi /path/to/openapi.yaml
|
|
172
|
+
generate-ui angular
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
2) Import generated routes in `src/app/app.routes.ts`:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { generatedRoutes } from '../generate-ui/routes.gen'
|
|
179
|
+
|
|
180
|
+
export const routes: Routes = [
|
|
181
|
+
// your existing routes
|
|
182
|
+
...generatedRoutes
|
|
183
|
+
]
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
3) Ensure `provideRouter` is used in `src/main.ts`:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { provideRouter } from '@angular/router'
|
|
190
|
+
import { routes } from './app/app.routes'
|
|
191
|
+
|
|
192
|
+
bootstrapApplication(AppComponent, {
|
|
193
|
+
providers: [provideRouter(routes)]
|
|
194
|
+
})
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
4) Check `@angular/router` is installed:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
npm ls @angular/router
|
|
201
|
+
```
|
|
202
|
+
|
|
142
203
|
## Example Generated Structure
|
|
143
204
|
|
|
144
205
|
```txt
|
|
145
|
-
src/
|
|
206
|
+
src/generate-ui/ or frontend/src/generate-ui/ or generate-ui/
|
|
207
|
+
generated/
|
|
208
|
+
overlays/
|
|
209
|
+
routes.json
|
|
210
|
+
routes.gen.ts
|
|
211
|
+
screens.json
|
|
212
|
+
frontend/src/app/features/
|
|
146
213
|
users/
|
|
147
|
-
users
|
|
148
|
-
users
|
|
149
|
-
users.
|
|
150
|
-
users.service.ts
|
|
151
|
-
users.types.ts
|
|
214
|
+
users.component.ts
|
|
215
|
+
users.service.gen.ts
|
|
216
|
+
users.gen.ts
|
|
152
217
|
orders/
|
|
153
|
-
orders
|
|
154
|
-
orders
|
|
155
|
-
orders.
|
|
156
|
-
orders.service.ts
|
|
157
|
-
orders.types.ts
|
|
158
|
-
generated.routes.ts
|
|
218
|
+
orders.component.ts
|
|
219
|
+
orders.service.gen.ts
|
|
220
|
+
orders.gen.ts
|
|
159
221
|
```
|
|
160
222
|
|
|
161
223
|
## After Generation: How to Customize Safely
|
|
@@ -170,9 +232,9 @@ Rule of thumb: the generated code is yours — generate once, then evolve freely
|
|
|
170
232
|
|
|
171
233
|
## Overrides and Regeneration Behavior
|
|
172
234
|
|
|
173
|
-
You can edit files inside `
|
|
235
|
+
You can edit files inside `overlays/` to customize labels, placeholders, hints, and other details. When your API changes and you regenerate, GenerateUI updates what is safe to change from the OpenAPI, but preserves what you defined in `overlays/` to avoid breaking your flow.
|
|
174
236
|
|
|
175
|
-
Even after the Angular TypeScript files are generated, changes you make in `
|
|
237
|
+
Even after the Angular TypeScript files are generated, changes you make in `overlays/` will be mirrored the next time you regenerate.
|
|
176
238
|
|
|
177
239
|
## Common Issues and Fixes
|
|
178
240
|
|
package/dist/commands/angular.js
CHANGED
|
@@ -9,12 +9,15 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const feature_generator_1 = require("../generators/angular/feature.generator");
|
|
10
10
|
const routes_generator_1 = require("../generators/angular/routes.generator");
|
|
11
11
|
const telemetry_1 = require("../telemetry");
|
|
12
|
+
const user_config_1 = require("../runtime/user-config");
|
|
12
13
|
async function angular(options) {
|
|
14
|
+
void (0, telemetry_1.trackCommand)('angular', options.telemetryEnabled);
|
|
15
|
+
const featuresRoot = resolveFeaturesRoot(options.featuresPath);
|
|
16
|
+
const schemasRoot = resolveSchemasRoot(options.schemasPath, featuresRoot);
|
|
13
17
|
/**
|
|
14
18
|
* Onde estão os schemas
|
|
15
|
-
* Ex:
|
|
19
|
+
* Ex: generate-ui
|
|
16
20
|
*/
|
|
17
|
-
const schemasRoot = path_1.default.resolve(process.cwd(), options.schemasPath);
|
|
18
21
|
const overlaysDir = path_1.default.join(schemasRoot, 'overlays');
|
|
19
22
|
if (!fs_1.default.existsSync(overlaysDir)) {
|
|
20
23
|
throw new Error(`Overlays directory not found: ${overlaysDir}`);
|
|
@@ -25,15 +28,60 @@ async function angular(options) {
|
|
|
25
28
|
/**
|
|
26
29
|
* Onde gerar as features Angular
|
|
27
30
|
*/
|
|
28
|
-
const featuresRoot = path_1.default.resolve(process.cwd(), options.featuresPath);
|
|
29
31
|
fs_1.default.mkdirSync(featuresRoot, { recursive: true });
|
|
30
32
|
const routes = [];
|
|
31
33
|
for (const file of screens) {
|
|
32
34
|
const schema = JSON.parse(fs_1.default.readFileSync(path_1.default.join(overlaysDir, file), 'utf-8'));
|
|
33
|
-
const route = (0, feature_generator_1.generateFeature)(schema, featuresRoot);
|
|
35
|
+
const route = (0, feature_generator_1.generateFeature)(schema, featuresRoot, schemasRoot);
|
|
34
36
|
routes.push(route);
|
|
35
37
|
}
|
|
36
|
-
(0, routes_generator_1.generateRoutes)(routes, featuresRoot);
|
|
37
|
-
await (0, telemetry_1.sendTelemetry)('generate', options.telemetryEnabled);
|
|
38
|
+
(0, routes_generator_1.generateRoutes)(routes, featuresRoot, schemasRoot);
|
|
38
39
|
console.log(`✔ Angular features generated at ${featuresRoot}`);
|
|
39
40
|
}
|
|
41
|
+
function resolveSchemasRoot(value, featuresRoot) {
|
|
42
|
+
if (value) {
|
|
43
|
+
return path_1.default.resolve(process.cwd(), value);
|
|
44
|
+
}
|
|
45
|
+
const config = (0, user_config_1.loadUserConfig)();
|
|
46
|
+
if (config?.lastSchemasPath) {
|
|
47
|
+
const resolved = path_1.default.resolve(config.lastSchemasPath);
|
|
48
|
+
if (fs_1.default.existsSync(path_1.default.join(resolved, 'overlays'))) {
|
|
49
|
+
return resolved;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const inferred = inferSchemasRootFromFeatures(featuresRoot);
|
|
53
|
+
if (inferred)
|
|
54
|
+
return inferred;
|
|
55
|
+
return resolveDefaultSchemasRoot();
|
|
56
|
+
}
|
|
57
|
+
function resolveFeaturesRoot(value) {
|
|
58
|
+
if (value) {
|
|
59
|
+
return path_1.default.resolve(process.cwd(), value);
|
|
60
|
+
}
|
|
61
|
+
const defaultApp = path_1.default.resolve(process.cwd(), 'src', 'app', 'features');
|
|
62
|
+
if (fs_1.default.existsSync(defaultApp)) {
|
|
63
|
+
return defaultApp;
|
|
64
|
+
}
|
|
65
|
+
const defaultFrontend = path_1.default.resolve(process.cwd(), 'frontend', 'src', 'app', 'features');
|
|
66
|
+
if (fs_1.default.existsSync(defaultFrontend)) {
|
|
67
|
+
return defaultFrontend;
|
|
68
|
+
}
|
|
69
|
+
return path_1.default.resolve(process.cwd(), 'features');
|
|
70
|
+
}
|
|
71
|
+
function inferSchemasRootFromFeatures(featuresRoot) {
|
|
72
|
+
const candidate = path_1.default.resolve(featuresRoot, '../../..', 'generate-ui');
|
|
73
|
+
if (fs_1.default.existsSync(path_1.default.join(candidate, 'overlays'))) {
|
|
74
|
+
return candidate;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function resolveDefaultSchemasRoot() {
|
|
79
|
+
const cwd = process.cwd();
|
|
80
|
+
if (fs_1.default.existsSync(path_1.default.join(cwd, 'src'))) {
|
|
81
|
+
return path_1.default.join(cwd, 'src', 'generate-ui');
|
|
82
|
+
}
|
|
83
|
+
if (fs_1.default.existsSync(path_1.default.join(cwd, 'frontend', 'src'))) {
|
|
84
|
+
return path_1.default.join(cwd, 'frontend', 'src', 'generate-ui');
|
|
85
|
+
}
|
|
86
|
+
return path_1.default.join(cwd, 'generate-ui');
|
|
87
|
+
}
|
|
@@ -12,7 +12,9 @@ const screen_merge_1 = require("../generators/screen.merge");
|
|
|
12
12
|
const permissions_1 = require("../license/permissions");
|
|
13
13
|
const device_1 = require("../license/device");
|
|
14
14
|
const telemetry_1 = require("../telemetry");
|
|
15
|
+
const user_config_1 = require("../runtime/user-config");
|
|
15
16
|
async function generate(options) {
|
|
17
|
+
void (0, telemetry_1.trackCommand)('generate', options.telemetryEnabled);
|
|
16
18
|
/**
|
|
17
19
|
* Caminho absoluto do OpenAPI (YAML)
|
|
18
20
|
* Ex: /Users/.../generateui-playground/realWorldOpenApi.yaml
|
|
@@ -25,9 +27,13 @@ async function generate(options) {
|
|
|
25
27
|
/**
|
|
26
28
|
* Onde o Angular consome os arquivos
|
|
27
29
|
*/
|
|
28
|
-
const generateUiRoot =
|
|
30
|
+
const generateUiRoot = resolveGenerateUiRoot(projectRoot, options.output);
|
|
29
31
|
const generatedDir = path_1.default.join(generateUiRoot, 'generated');
|
|
30
32
|
const overlaysDir = path_1.default.join(generateUiRoot, 'overlays');
|
|
33
|
+
(0, user_config_1.updateUserConfig)(config => ({
|
|
34
|
+
...config,
|
|
35
|
+
lastSchemasPath: generateUiRoot
|
|
36
|
+
}));
|
|
31
37
|
fs_1.default.mkdirSync(generatedDir, { recursive: true });
|
|
32
38
|
fs_1.default.mkdirSync(overlaysDir, { recursive: true });
|
|
33
39
|
/**
|
|
@@ -37,7 +43,6 @@ async function generate(options) {
|
|
|
37
43
|
const usedOperationIds = new Set();
|
|
38
44
|
const permissions = await (0, permissions_1.getPermissions)();
|
|
39
45
|
const device = (0, device_1.loadDeviceIdentity)();
|
|
40
|
-
await (0, telemetry_1.sendTelemetry)('generate', options.telemetryEnabled);
|
|
41
46
|
if (permissions.features.maxGenerations > -1 &&
|
|
42
47
|
device.freeGenerationsUsed >= permissions.features.maxGenerations) {
|
|
43
48
|
throw new Error('🔒 Você já utilizou sua geração gratuita.\n' +
|
|
@@ -142,6 +147,20 @@ async function generate(options) {
|
|
|
142
147
|
}
|
|
143
148
|
console.log('✔ Routes generated');
|
|
144
149
|
}
|
|
150
|
+
function resolveGenerateUiRoot(projectRoot, output) {
|
|
151
|
+
if (output) {
|
|
152
|
+
return path_1.default.resolve(process.cwd(), output);
|
|
153
|
+
}
|
|
154
|
+
const srcRoot = path_1.default.join(projectRoot, 'src');
|
|
155
|
+
if (fs_1.default.existsSync(srcRoot)) {
|
|
156
|
+
return path_1.default.join(srcRoot, 'generate-ui');
|
|
157
|
+
}
|
|
158
|
+
const frontendSrcRoot = path_1.default.join(projectRoot, 'frontend', 'src');
|
|
159
|
+
if (fs_1.default.existsSync(frontendSrcRoot)) {
|
|
160
|
+
return path_1.default.join(frontendSrcRoot, 'generate-ui');
|
|
161
|
+
}
|
|
162
|
+
return path_1.default.join(projectRoot, 'generate-ui');
|
|
163
|
+
}
|
|
145
164
|
function mergeParameters(pathParams, opParams) {
|
|
146
165
|
const all = [...(pathParams ?? []), ...(opParams ?? [])];
|
|
147
166
|
const seen = new Set();
|
package/dist/commands/login.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.login = login;
|
|
7
7
|
const http_1 = __importDefault(require("http"));
|
|
8
8
|
const url_1 = require("url");
|
|
9
|
+
const promises_1 = require("readline/promises");
|
|
9
10
|
const config_1 = require("../runtime/config");
|
|
10
11
|
const open_browser_1 = require("../runtime/open-browser");
|
|
11
12
|
const token_1 = require("../license/token");
|
|
@@ -13,6 +14,7 @@ const permissions_1 = require("../license/permissions");
|
|
|
13
14
|
const telemetry_1 = require("../telemetry");
|
|
14
15
|
const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
15
16
|
async function login(options) {
|
|
17
|
+
void (0, telemetry_1.trackCommand)('login', options.telemetryEnabled);
|
|
16
18
|
const token = await waitForLogin();
|
|
17
19
|
(0, token_1.saveToken)(token);
|
|
18
20
|
try {
|
|
@@ -21,9 +23,26 @@ async function login(options) {
|
|
|
21
23
|
catch {
|
|
22
24
|
// Cached permissions will be refreshed on next online command.
|
|
23
25
|
}
|
|
24
|
-
await (
|
|
26
|
+
const email = await promptEmail();
|
|
27
|
+
await (0, telemetry_1.trackLogin)(email, options.telemetryEnabled);
|
|
25
28
|
console.log('✔ Login completo');
|
|
26
29
|
}
|
|
30
|
+
async function promptEmail() {
|
|
31
|
+
if (!process.stdin.isTTY)
|
|
32
|
+
return null;
|
|
33
|
+
const rl = (0, promises_1.createInterface)({
|
|
34
|
+
input: process.stdin,
|
|
35
|
+
output: process.stdout
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
const value = await rl.question('Email (optional): ');
|
|
39
|
+
const trimmed = value.trim();
|
|
40
|
+
return trimmed.length ? trimmed : null;
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
rl.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
27
46
|
async function waitForLogin() {
|
|
28
47
|
return new Promise((resolve, reject) => {
|
|
29
48
|
const server = http_1.default.createServer((req, res) => {
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.generateFeature = generateFeature;
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
function generateFeature(schema, root) {
|
|
9
|
+
function generateFeature(schema, root, schemasRoot) {
|
|
10
10
|
const rawName = schema.api.operationId;
|
|
11
11
|
const name = toPascalCase(rawName);
|
|
12
12
|
const folder = toFolderName(name);
|
|
@@ -47,6 +47,7 @@ function generateFeature(schema, root) {
|
|
|
47
47
|
? String(schema.entity).trim()
|
|
48
48
|
: rawName;
|
|
49
49
|
const subtitle = `${method.toUpperCase()} ${endpoint}`;
|
|
50
|
+
const schemaImportPath = buildSchemaImportPath(featureDir, schemasRoot, rawName);
|
|
50
51
|
/**
|
|
51
52
|
* 1️⃣ Component (sempre sobrescreve)
|
|
52
53
|
*/
|
|
@@ -60,7 +61,7 @@ import { UiFieldComponent } from '../../ui/ui-field/ui-field.component'
|
|
|
60
61
|
import { UiButtonComponent } from '../../ui/ui-button/ui-button.component'
|
|
61
62
|
import { ${name}Service } from './${fileBase}.service.gen'
|
|
62
63
|
import { ${name}Gen } from './${fileBase}.gen'
|
|
63
|
-
import screenSchema from '
|
|
64
|
+
import screenSchema from '${schemaImportPath}'
|
|
64
65
|
|
|
65
66
|
@Component({
|
|
66
67
|
selector: 'app-${toKebab(name)}',
|
|
@@ -1209,3 +1210,15 @@ function toPascalCase(value) {
|
|
|
1209
1210
|
.map(part => part[0].toUpperCase() + part.slice(1))
|
|
1210
1211
|
.join('');
|
|
1211
1212
|
}
|
|
1213
|
+
function buildSchemaImportPath(featureDir, schemasRoot, rawName) {
|
|
1214
|
+
const schemaFile = path_1.default.join(schemasRoot, 'overlays', `${rawName}.screen.json`);
|
|
1215
|
+
let relativePath = path_1.default.relative(featureDir, schemaFile);
|
|
1216
|
+
relativePath = toPosixPath(relativePath);
|
|
1217
|
+
if (!relativePath.startsWith('.')) {
|
|
1218
|
+
relativePath = `./${relativePath}`;
|
|
1219
|
+
}
|
|
1220
|
+
return relativePath;
|
|
1221
|
+
}
|
|
1222
|
+
function toPosixPath(value) {
|
|
1223
|
+
return value.split(path_1.default.sep).join(path_1.default.posix.sep);
|
|
1224
|
+
}
|
|
@@ -6,9 +6,9 @@ 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) {
|
|
10
|
-
const
|
|
11
|
-
const
|
|
9
|
+
function generateRoutes(routes, featuresRoot, schemasRoot) {
|
|
10
|
+
const out = path_1.default.join(schemasRoot, 'routes.gen.ts');
|
|
11
|
+
const featuresImportBase = buildRelativeImportBase(schemasRoot, featuresRoot);
|
|
12
12
|
fs_1.default.mkdirSync(path_1.default.dirname(out), { recursive: true });
|
|
13
13
|
const content = `
|
|
14
14
|
import { Routes } from '@angular/router'
|
|
@@ -16,10 +16,11 @@ import { Routes } from '@angular/router'
|
|
|
16
16
|
export const generatedRoutes: Routes = [
|
|
17
17
|
${routes
|
|
18
18
|
.flatMap(r => {
|
|
19
|
+
const baseImport = ensureRelativeImport(toPosixPath(path_1.default.join(featuresImportBase, r.folder, `${r.fileBase}.component`)));
|
|
19
20
|
const base = ` {
|
|
20
21
|
path: '${r.path}',
|
|
21
22
|
loadComponent: () =>
|
|
22
|
-
import('
|
|
23
|
+
import('${baseImport}')
|
|
23
24
|
.then(m => m.${r.component})
|
|
24
25
|
}`;
|
|
25
26
|
const pascal = toPascalCase(r.path);
|
|
@@ -28,7 +29,7 @@ ${routes
|
|
|
28
29
|
const alias = ` {
|
|
29
30
|
path: '${pascal}',
|
|
30
31
|
loadComponent: () =>
|
|
31
|
-
import('
|
|
32
|
+
import('${baseImport}')
|
|
32
33
|
.then(m => m.${r.component})
|
|
33
34
|
}`;
|
|
34
35
|
return [base, alias];
|
|
@@ -43,3 +44,19 @@ function toPascalCase(value) {
|
|
|
43
44
|
return value;
|
|
44
45
|
return value[0].toUpperCase() + value.slice(1);
|
|
45
46
|
}
|
|
47
|
+
function buildRelativeImportBase(fromDir, toDir) {
|
|
48
|
+
let relativePath = path_1.default.relative(fromDir, toDir);
|
|
49
|
+
relativePath = toPosixPath(relativePath);
|
|
50
|
+
if (!relativePath.startsWith('.')) {
|
|
51
|
+
relativePath = `./${relativePath}`;
|
|
52
|
+
}
|
|
53
|
+
return relativePath;
|
|
54
|
+
}
|
|
55
|
+
function toPosixPath(value) {
|
|
56
|
+
return value.split(path_1.default.sep).join(path_1.default.posix.sep);
|
|
57
|
+
}
|
|
58
|
+
function ensureRelativeImport(value) {
|
|
59
|
+
if (value.startsWith('.'))
|
|
60
|
+
return value;
|
|
61
|
+
return `./${value}`;
|
|
62
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -19,12 +19,14 @@ program
|
|
|
19
19
|
.command('generate')
|
|
20
20
|
.description('Generate screen schemas from OpenAPI')
|
|
21
21
|
.requiredOption('-o, --openapi <path>', 'OpenAPI file')
|
|
22
|
+
.option('--output <path>', 'Output directory for generate-ui (default: ./src/generate-ui or ./generate-ui)')
|
|
22
23
|
.option('-d, --debug', 'Explain merge decisions')
|
|
23
24
|
.action(async (options) => {
|
|
24
25
|
const { telemetry } = program.opts();
|
|
25
26
|
try {
|
|
26
27
|
await (0, generate_1.generate)({
|
|
27
28
|
openapi: options.openapi,
|
|
29
|
+
output: options.output,
|
|
28
30
|
debug: options.debug,
|
|
29
31
|
telemetryEnabled: telemetry
|
|
30
32
|
});
|
|
@@ -39,8 +41,8 @@ program
|
|
|
39
41
|
program
|
|
40
42
|
.command('angular')
|
|
41
43
|
.description('Generate Angular code from screen schemas')
|
|
42
|
-
.
|
|
43
|
-
.
|
|
44
|
+
.option('-s, --schemas <path>', 'Directory containing generate-ui (with overlays/)')
|
|
45
|
+
.option('-f, --features <path>', 'Angular features output directory')
|
|
44
46
|
.action(async (options) => {
|
|
45
47
|
const { telemetry } = program.opts();
|
|
46
48
|
try {
|
|
@@ -0,0 +1,38 @@
|
|
|
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.getUserConfigPath = getUserConfigPath;
|
|
7
|
+
exports.loadUserConfig = loadUserConfig;
|
|
8
|
+
exports.saveUserConfig = saveUserConfig;
|
|
9
|
+
exports.updateUserConfig = updateUserConfig;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
function getUserConfigPath() {
|
|
14
|
+
return path_1.default.join(os_1.default.homedir(), '.generateui', 'config.json');
|
|
15
|
+
}
|
|
16
|
+
function loadUserConfig() {
|
|
17
|
+
const configPath = getUserConfigPath();
|
|
18
|
+
if (!fs_1.default.existsSync(configPath))
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
const raw = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
22
|
+
return JSON.parse(raw);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function saveUserConfig(config) {
|
|
29
|
+
const configPath = getUserConfigPath();
|
|
30
|
+
fs_1.default.mkdirSync(path_1.default.dirname(configPath), { recursive: true });
|
|
31
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
32
|
+
}
|
|
33
|
+
function updateUserConfig(updater) {
|
|
34
|
+
const current = loadUserConfig() ?? {};
|
|
35
|
+
const next = updater(current);
|
|
36
|
+
saveUserConfig(next);
|
|
37
|
+
return next;
|
|
38
|
+
}
|
package/dist/telemetry.js
CHANGED
|
@@ -1,40 +1,117 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.trackCommand = trackCommand;
|
|
4
|
+
exports.trackLogin = trackLogin;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
4
6
|
const config_1 = require("./runtime/config");
|
|
7
|
+
const user_config_1 = require("./runtime/user-config");
|
|
5
8
|
const device_1 = require("./license/device");
|
|
9
|
+
const TELEMETRY_URL = process.env.GENERATEUI_TELEMETRY_URL?.trim() ||
|
|
10
|
+
'https://api.generateui.dev/events';
|
|
11
|
+
const TELEMETRY_TIMEOUT_MS = 1000;
|
|
6
12
|
function getOsName() {
|
|
7
|
-
|
|
8
|
-
case 'darwin':
|
|
9
|
-
return 'macos';
|
|
10
|
-
case 'win32':
|
|
11
|
-
return 'windows';
|
|
12
|
-
default:
|
|
13
|
-
return 'linux';
|
|
14
|
-
}
|
|
13
|
+
return process.platform;
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
function loadOrCreateConfig() {
|
|
16
|
+
let config = (0, user_config_1.loadUserConfig)();
|
|
17
|
+
let isNew = false;
|
|
18
|
+
let shouldWrite = false;
|
|
19
|
+
if (!config) {
|
|
20
|
+
isNew = true;
|
|
21
|
+
shouldWrite = true;
|
|
22
|
+
config = {
|
|
23
|
+
installationId: (0, crypto_1.randomUUID)(),
|
|
24
|
+
telemetry: true
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
else if (!config.installationId) {
|
|
28
|
+
isNew = true;
|
|
29
|
+
shouldWrite = true;
|
|
30
|
+
config.installationId = (0, crypto_1.randomUUID)();
|
|
31
|
+
if (config.telemetry === undefined) {
|
|
32
|
+
config.telemetry = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (config.telemetry === undefined) {
|
|
36
|
+
shouldWrite = true;
|
|
37
|
+
config.telemetry = true;
|
|
38
|
+
}
|
|
39
|
+
if (shouldWrite) {
|
|
40
|
+
(0, user_config_1.saveUserConfig)(config);
|
|
41
|
+
}
|
|
42
|
+
const installationId = config.installationId ?? (0, crypto_1.randomUUID)();
|
|
43
|
+
if (installationId !== config.installationId) {
|
|
44
|
+
shouldWrite = true;
|
|
45
|
+
config.installationId = installationId;
|
|
46
|
+
}
|
|
47
|
+
if (shouldWrite) {
|
|
48
|
+
(0, user_config_1.saveUserConfig)(config);
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
config: {
|
|
52
|
+
installationId,
|
|
53
|
+
telemetry: config.telemetry
|
|
54
|
+
},
|
|
55
|
+
isNew
|
|
27
56
|
};
|
|
57
|
+
}
|
|
58
|
+
function isTelemetryEnabled(cliEnabled, config) {
|
|
59
|
+
if (!cliEnabled)
|
|
60
|
+
return false;
|
|
61
|
+
return config.telemetry !== false;
|
|
62
|
+
}
|
|
63
|
+
async function sendEvent(payload) {
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const timeout = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT_MS);
|
|
28
66
|
try {
|
|
29
|
-
await fetch(
|
|
67
|
+
await fetch(TELEMETRY_URL, {
|
|
30
68
|
method: 'POST',
|
|
31
69
|
headers: {
|
|
32
70
|
'Content-Type': 'application/json'
|
|
33
71
|
},
|
|
34
|
-
body: JSON.stringify(payload)
|
|
72
|
+
body: JSON.stringify(payload),
|
|
73
|
+
signal: controller.signal
|
|
35
74
|
});
|
|
36
75
|
}
|
|
37
76
|
catch {
|
|
38
77
|
// Telemetry must never block execution.
|
|
39
78
|
}
|
|
79
|
+
finally {
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function trackCommand(command, cliEnabled) {
|
|
84
|
+
const { config, isNew } = loadOrCreateConfig();
|
|
85
|
+
const enabled = isTelemetryEnabled(cliEnabled, config);
|
|
86
|
+
if (!enabled)
|
|
87
|
+
return;
|
|
88
|
+
const device = (0, device_1.loadDeviceIdentity)();
|
|
89
|
+
if (isNew) {
|
|
90
|
+
await sendEvent({
|
|
91
|
+
event: 'first_run',
|
|
92
|
+
installationId: config.installationId,
|
|
93
|
+
deviceId: device.deviceId,
|
|
94
|
+
os: getOsName(),
|
|
95
|
+
arch: process.arch,
|
|
96
|
+
cliVersion: (0, config_1.getCliVersion)()
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
await sendEvent({
|
|
100
|
+
event: 'command_run',
|
|
101
|
+
installationId: config.installationId,
|
|
102
|
+
command,
|
|
103
|
+
cliVersion: (0, config_1.getCliVersion)()
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async function trackLogin(email, cliEnabled) {
|
|
107
|
+
const { config } = loadOrCreateConfig();
|
|
108
|
+
const enabled = isTelemetryEnabled(cliEnabled, config);
|
|
109
|
+
if (!enabled)
|
|
110
|
+
return;
|
|
111
|
+
await sendEvent({
|
|
112
|
+
event: 'login',
|
|
113
|
+
installationId: config.installationId,
|
|
114
|
+
email: email ?? '',
|
|
115
|
+
cliVersion: (0, config_1.getCliVersion)()
|
|
116
|
+
});
|
|
40
117
|
}
|
package/package.json
CHANGED