@ojiepermana/angular 21.0.0 → 21.0.3
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/collection.json +25 -0
- package/fesm2022/ojiepermana-angular-generator-api.mjs +67 -0
- package/fesm2022/ojiepermana-angular-generator-api.mjs.map +1 -0
- package/fesm2022/ojiepermana-angular-layout.mjs +76 -56
- package/fesm2022/ojiepermana-angular-layout.mjs.map +1 -1
- package/fesm2022/ojiepermana-angular.mjs +1 -0
- package/fesm2022/ojiepermana-angular.mjs.map +1 -1
- package/generator/api/README.md +218 -0
- package/generator/api/bin/schematics/init/index.js +88 -0
- package/generator/api/bin/schematics/sdk/index.js +58 -0
- package/generator/api/bin/src/config/loader.js +41 -0
- package/generator/api/bin/src/config/schema.js +56 -0
- package/generator/api/bin/src/emit/client.js +246 -0
- package/generator/api/bin/src/emit/metadata.js +295 -0
- package/generator/api/bin/src/emit/models.js +106 -0
- package/generator/api/bin/src/emit/navigation.js +56 -0
- package/generator/api/bin/src/emit/operations.js +122 -0
- package/generator/api/bin/src/emit/public-api.js +54 -0
- package/generator/api/bin/src/emit/services.js +87 -0
- package/generator/api/bin/src/engine.js +65 -0
- package/generator/api/bin/src/layout/per-domain.js +346 -0
- package/generator/api/bin/src/parser/bundle.js +25 -0
- package/generator/api/bin/src/parser/ir.js +320 -0
- package/generator/api/bin/src/parser/types.js +7 -0
- package/generator/api/bin/src/render/template.js +58 -0
- package/generator/api/bin/src/writer/index.js +69 -0
- package/generator/api/schematics/init/schema.json +19 -0
- package/generator/api/schematics/sdk/schema.json +19 -0
- package/generator/api/sdk.config.example.json +22 -0
- package/generator/guide/README.md +84 -0
- package/generator/guide/bin/schematics/build/index.js +35 -0
- package/generator/guide/bin/schematics/init/index.js +70 -0
- package/generator/guide/bin/src/config/loader.js +50 -0
- package/generator/guide/bin/src/config/schema.js +12 -0
- package/generator/guide/bin/src/engine/component.js +73 -0
- package/generator/guide/bin/src/engine/frontmatter.js +42 -0
- package/generator/guide/bin/src/engine/index.js +42 -0
- package/generator/guide/bin/src/engine/naming.js +39 -0
- package/generator/guide/bin/src/engine/render.js +18 -0
- package/generator/guide/bin/src/engine/routes.js +106 -0
- package/generator/guide/bin/src/engine/walk.js +35 -0
- package/generator/guide/guide.config.example.json +9 -0
- package/generator/guide/schematics/build/schema.json +14 -0
- package/generator/guide/schematics/init/schema.json +19 -0
- package/package.json +10 -3
- package/types/ojiepermana-angular-generator-api.d.ts +85 -0
- package/types/ojiepermana-angular-layout.d.ts +2 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# SDK Generator
|
|
2
|
+
|
|
3
|
+
OpenAPI → Angular SDK generator shipped as the secondary entrypoint
|
|
4
|
+
`@ojiepermana/angular/generator/api`.
|
|
5
|
+
|
|
6
|
+
It generates a **lightweight Angular SDK** from any OpenAPI 3.x spec (including
|
|
7
|
+
3.2.0): typed `HttpClient` services, tree-shakeable fn modules, optional
|
|
8
|
+
metadata (permissions / validators), and a navigation tree.
|
|
9
|
+
|
|
10
|
+
## Local development in this repo
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# 1. Build the schematic runtime
|
|
14
|
+
bun run gen:sdk:build
|
|
15
|
+
|
|
16
|
+
# 2. Scaffold a workspace config
|
|
17
|
+
bun run gen:sdk:init
|
|
18
|
+
|
|
19
|
+
# 3. Edit sdk.config.json, then generate
|
|
20
|
+
bun run gen:sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Consumer usage after publish
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# inside an Angular workspace that installed @ojiepermana/angular
|
|
27
|
+
ng generate @ojiepermana/angular:sdk-init
|
|
28
|
+
ng generate @ojiepermana/angular:sdk
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Schematics
|
|
32
|
+
|
|
33
|
+
The entrypoint exposes two schematics, registered in the parent collection at [`projects/angular/collection.json`](../../collection.json):
|
|
34
|
+
|
|
35
|
+
| Schematic | Script | Purpose |
|
|
36
|
+
| ---------- | ---------------------- | --------------------------------------------------------------- |
|
|
37
|
+
| `sdk-init` | `bun run gen:sdk:init` | Create `sdk.config.json` at the workspace root from the example |
|
|
38
|
+
| `sdk` | `bun run gen:sdk` | Run the generator using `sdk.config.json` |
|
|
39
|
+
|
|
40
|
+
Both can be invoked directly with `ng generate` too:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bunx ng generate ./projects/angular/collection.json:sdk-init [--force] [--path=custom/sdk.config.json]
|
|
44
|
+
bunx ng generate ./projects/angular/collection.json:sdk [--dry-run] [--config=sdk.config.json] [--target=1]
|
|
45
|
+
|
|
46
|
+
# after publish / inside a consuming workspace:
|
|
47
|
+
ng generate @ojiepermana/angular:sdk-init [--force] [--path=custom/sdk.config.json]
|
|
48
|
+
ng generate @ojiepermana/angular:sdk [--dry-run] [--config=sdk.config.json] [--target=1]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### `init` options
|
|
52
|
+
|
|
53
|
+
| Option | Type | Default | Description |
|
|
54
|
+
| --------- | ------- | ----------------- | --------------------------------------------- |
|
|
55
|
+
| `--path` | string | `sdk.config.json` | Destination path, relative to workspace root. |
|
|
56
|
+
| `--force` | boolean | `false` | Overwrite the file if it already exists. |
|
|
57
|
+
|
|
58
|
+
### `sdk` options
|
|
59
|
+
|
|
60
|
+
| Option | Type | Default | Description |
|
|
61
|
+
| ----------- | ------ | ----------------- | ------------------------------------------------------------------ |
|
|
62
|
+
| `--config` | string | `sdk.config.json` | Path to the config file, relative to workspace root. |
|
|
63
|
+
| `--target` | string | _(all)_ | Only generate one target. Accepts a 1-based index or `clientName`. |
|
|
64
|
+
| `--dry-run` | flag | — | Preview file operations without writing anything. |
|
|
65
|
+
|
|
66
|
+
## Config shape
|
|
67
|
+
|
|
68
|
+
```jsonc
|
|
69
|
+
{
|
|
70
|
+
"$schema": "./node_modules/@ojiepermana/angular/generator/api/schematics/sdk/schema.json",
|
|
71
|
+
"targets": [
|
|
72
|
+
{
|
|
73
|
+
"input": "./openapi.yaml",
|
|
74
|
+
"output": "./sdk",
|
|
75
|
+
"mode": "standalone", // "standalone" | "library" | "secondary-entrypoint"
|
|
76
|
+
"clientName": "Api",
|
|
77
|
+
"packageName": "@my-scope/sdk", // only used in "library" mode
|
|
78
|
+
"packageVersion": "0.0.1", // only used in "library" mode
|
|
79
|
+
"rootUrl": "", // optional; empty string means same-origin requests
|
|
80
|
+
"splitByDomain": false, // optional, default false — see "Per-domain layout"
|
|
81
|
+
"splitDepth": "service", // "service" (default) | "tag"
|
|
82
|
+
"features": {
|
|
83
|
+
"models": true,
|
|
84
|
+
"operations": true,
|
|
85
|
+
"services": true,
|
|
86
|
+
"client": true,
|
|
87
|
+
"metadata": true,
|
|
88
|
+
"navigation": true,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Multiple targets are supported — one config run can emit several SDKs.
|
|
96
|
+
|
|
97
|
+
If `rootUrl` is omitted or left empty, the generated SDK uses same-origin
|
|
98
|
+
requests by default. Consumer apps can override it at runtime with
|
|
99
|
+
`provideApiConfiguration(...)`.
|
|
100
|
+
|
|
101
|
+
### Runtime base URL
|
|
102
|
+
|
|
103
|
+
The generated SDK does not read `sdk.config.json` at runtime. The value of
|
|
104
|
+
`targets[].rootUrl` is only used during code generation to seed the default
|
|
105
|
+
`ApiConfiguration.rootUrl` value.
|
|
106
|
+
|
|
107
|
+
- `rootUrl: ""` or omitted: requests use the current origin, for example
|
|
108
|
+
`/api/users` on the same host as the Angular app.
|
|
109
|
+
- `rootUrl: "https://api.example.com"`: the generated SDK defaults to that
|
|
110
|
+
absolute backend URL.
|
|
111
|
+
- Runtime override: consumer apps can replace the default by providing a new
|
|
112
|
+
value during bootstrap.
|
|
113
|
+
|
|
114
|
+
Example runtime override in a consumer app:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { bootstrapApplication } from '@angular/platform-browser';
|
|
118
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
119
|
+
|
|
120
|
+
import { AppComponent } from './app/app.component';
|
|
121
|
+
import { provideApiConfiguration } from '@my-scope/sdk';
|
|
122
|
+
|
|
123
|
+
bootstrapApplication(AppComponent, {
|
|
124
|
+
providers: [provideHttpClient(), provideApiConfiguration('https://api.example.com')],
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For standalone generated output inside the same workspace, import
|
|
129
|
+
`provideApiConfiguration` from the generated SDK barrel instead of an npm
|
|
130
|
+
package path.
|
|
131
|
+
|
|
132
|
+
## Per-domain layout
|
|
133
|
+
|
|
134
|
+
By default the generator emits a flat layout (`models/`, `fn/`, `services/`, …
|
|
135
|
+
all at the output root). Set `splitByDomain: true` to reorganise the output
|
|
136
|
+
into one folder per domain, derived from OpenAPI tags. Cross-domain models and
|
|
137
|
+
client primitives land in `shared/`.
|
|
138
|
+
|
|
139
|
+
```jsonc
|
|
140
|
+
{
|
|
141
|
+
"splitByDomain": true,
|
|
142
|
+
"splitDepth": "service", // or "tag"
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`splitDepth` controls granularity. It is only read when `splitByDomain` is
|
|
147
|
+
`true`.
|
|
148
|
+
|
|
149
|
+
| `splitDepth` | Folder strategy | Example (spec with tags `Access/Role`, `Access/Permission`, `Storage/GCS`, `Storage/S3`, `Auth`) |
|
|
150
|
+
| ------------ | ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
151
|
+
| `service` | One folder per **root** tag (each tag's `parent` chain collapses to its root). One folder per backend service. | `access/` (holds Role + Permission + Role-Permission + …), `storage/` (holds GCS + S3), `auth/`, `shared/`, root `public-api.ts` |
|
|
152
|
+
| `tag` | One folder per **leaf** tag, nested under the parent chain. Keeps fine-grained separation while staying grouped. | `access/role/`, `access/permission/`, `access/role-permission/`, `storage/gcs/`, `storage/s3/`, `auth/`, `shared/`, root `public-api.ts` |
|
|
153
|
+
|
|
154
|
+
Every domain folder contains `services/`, `fn/`, `models/`, `permissions/`,
|
|
155
|
+
and its own `public-api.ts` (which re-exports `shared/public-api` for
|
|
156
|
+
convenience). The root `public-api.ts` aggregates `shared` plus every domain,
|
|
157
|
+
so consumers can still do `import { UserService } from './sdk'` regardless of
|
|
158
|
+
layout.
|
|
159
|
+
|
|
160
|
+
Model ownership rule (per-domain mode):
|
|
161
|
+
|
|
162
|
+
- A model used by exactly one domain → emitted inside that domain's `models/`.
|
|
163
|
+
- A model shared across two or more domains → emitted inside `shared/models/`.
|
|
164
|
+
- Client primitives (`ApiConfiguration`, `BaseService`, `RequestBuilder`,
|
|
165
|
+
`StrictHttpResponse`, `Api`), metadata, validators, and the top-level
|
|
166
|
+
`permissions/index.ts` always live under `shared/`.
|
|
167
|
+
|
|
168
|
+
Example consumption when using `mode: 'library'` with `splitByDomain: true`:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
import { RoleService } from '@my-scope/sdk/access';
|
|
172
|
+
import { ApprovalInstanceService, SubmitRequest } from '@my-scope/sdk/approval';
|
|
173
|
+
import { GCSService } from '@my-scope/sdk/storage'; // splitDepth: 'service'
|
|
174
|
+
import { GCSService } from '@my-scope/sdk/storage/gcs'; // splitDepth: 'tag'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Output modes
|
|
178
|
+
|
|
179
|
+
| Mode | What it emits | Use when… |
|
|
180
|
+
| ---------------------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
181
|
+
| `standalone` | A plain folder (no `ng-package.json`). | You consume the SDK via path alias / `tsconfig.paths` inside the same app. |
|
|
182
|
+
| `library` | Standalone output **plus** `ng-package.json`, `package.json` (peerDeps), and `README.md`. | You want to build it with ng-packagr and publish to npm. |
|
|
183
|
+
| `secondary-entrypoint` | Standalone output **plus** a minimal `ng-package.json` pointing at `public-api.ts`. | You drop the folder inside an existing library so ng-packagr picks it up as a subpath. |
|
|
184
|
+
|
|
185
|
+
## Feature flags
|
|
186
|
+
|
|
187
|
+
All default to `true`. Turn off anything you don't need to shrink the output.
|
|
188
|
+
|
|
189
|
+
| Flag | Emits |
|
|
190
|
+
| ------------ | ------------------------------------------------------------------------------------------ |
|
|
191
|
+
| `models` | `models/*.ts` — flat interfaces, enum aliases, array aliases. |
|
|
192
|
+
| `operations` | `fn/<tag>/<operation-id>.ts` — tree-shakeable request functions with `.PATH`. |
|
|
193
|
+
| `services` | `services/<tag>.service.ts` — `@Injectable({providedIn:'root'})` wrappers. |
|
|
194
|
+
| `client` | `api-configuration.ts`, `base-service.ts`, `request-builder.ts`, `api.ts`. |
|
|
195
|
+
| `metadata` | `permissions/*`, `validators/*`, `metadata.ts`, `openapi-helpers.ts`. |
|
|
196
|
+
| `navigation` | `api.navigation.ts` — `NavigationItem[]` ready for `NavigationService.registerItems(...)`. |
|
|
197
|
+
|
|
198
|
+
## Pipeline
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
sdk.config.json → loader → spec (YAML/JSON) → IR → emitters → writer → Angular CLI Tree
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- `src/config/` — config schema + loader (supports JSONC and `.js`/`.cjs`).
|
|
205
|
+
- `src/parser/` — OpenAPI → intermediate representation.
|
|
206
|
+
- `src/emit/` — one module per output concern (models, operations, services, client, metadata, navigation, public-api).
|
|
207
|
+
- `src/layout/` — post-emit layout transforms (e.g. `splitByDomain` reorganisation).
|
|
208
|
+
- `src/writer/` — mode wrappers (standalone / library / secondary entrypoint).
|
|
209
|
+
- `public-api.ts` — published TypeScript entrypoint for `@ojiepermana/angular/generator/api`.
|
|
210
|
+
- `schematics/init/` — creates `sdk.config.json` from the example template.
|
|
211
|
+
- `schematics/sdk/` — orchestrates engine and writes virtual files into the CLI `Tree`.
|
|
212
|
+
|
|
213
|
+
## Generated runtime conventions
|
|
214
|
+
|
|
215
|
+
- Tree-shakeable: `import { listUsers } from './sdk/fn/user/list-users'` pulls only one HTTP call.
|
|
216
|
+
- Services: every operation gets `op()` (body `Observable<T>`) and `op$Response()` (full `StrictHttpResponse<T>`).
|
|
217
|
+
- `RequestBuilder` is intentionally minimal — no `style`/`explode` logic — to keep the output lightweight.
|
|
218
|
+
- All files carry a `DO NOT EDIT` banner and `/* eslint-disable */`.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = init;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
/**
|
|
7
|
+
* Default config template. The published package also ships a concrete
|
|
8
|
+
* `sdk.config.example.json`; this fallback only exists so `init` still works if
|
|
9
|
+
* the file is missing in unusual local/dev states.
|
|
10
|
+
*/
|
|
11
|
+
const FALLBACK_TEMPLATE = {
|
|
12
|
+
$schema: './node_modules/@ojiepermana/angular/generator/api/schematics/sdk/schema.json',
|
|
13
|
+
targets: [
|
|
14
|
+
{
|
|
15
|
+
input: './openapi.yaml',
|
|
16
|
+
output: './sdk',
|
|
17
|
+
mode: 'standalone',
|
|
18
|
+
clientName: 'Api',
|
|
19
|
+
rootUrl: '',
|
|
20
|
+
splitByDomain: false,
|
|
21
|
+
splitDepth: 'service',
|
|
22
|
+
features: {
|
|
23
|
+
models: true,
|
|
24
|
+
operations: true,
|
|
25
|
+
services: true,
|
|
26
|
+
client: true,
|
|
27
|
+
metadata: true,
|
|
28
|
+
navigation: true,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
function init(options = {}) {
|
|
34
|
+
return (tree, context) => {
|
|
35
|
+
const workspaceRoot = process.cwd();
|
|
36
|
+
const destRelative = options.path ?? 'config/sdk.config.json';
|
|
37
|
+
const treePath = destRelative.startsWith('/') ? destRelative : `/${destRelative}`;
|
|
38
|
+
if (tree.exists(treePath) && !options.force) {
|
|
39
|
+
throw new Error(`${destRelative} already exists. Re-run with --force to overwrite.`);
|
|
40
|
+
}
|
|
41
|
+
const content = loadTemplate(workspaceRoot, destRelative);
|
|
42
|
+
const buffer = Buffer.from(content, 'utf8');
|
|
43
|
+
if (tree.exists(treePath)) {
|
|
44
|
+
tree.overwrite(treePath, buffer);
|
|
45
|
+
context.logger.info(`[sdk:init] overwrote ${destRelative}`);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
tree.create(treePath, buffer);
|
|
49
|
+
context.logger.info(`[sdk:init] created ${destRelative}`);
|
|
50
|
+
}
|
|
51
|
+
context.logger.info(`[sdk:init] edit targets[].input and targets[].output, then run \`${getNextCommand(workspaceRoot)}\`.`);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function loadTemplate(workspaceRoot, destRelative) {
|
|
55
|
+
const candidates = [
|
|
56
|
+
(0, node_path_1.resolve)(workspaceRoot, 'sdk.config.example.json'),
|
|
57
|
+
(0, node_path_1.resolve)(__dirname, '../../../sdk.config.example.json'),
|
|
58
|
+
(0, node_path_1.resolve)(workspaceRoot, 'projects/angular/generator/api/sdk.config.example.json'),
|
|
59
|
+
];
|
|
60
|
+
for (const candidate of candidates) {
|
|
61
|
+
try {
|
|
62
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)(candidate, 'utf8'));
|
|
63
|
+
parsed.$schema = resolveSchemaPath(workspaceRoot, destRelative);
|
|
64
|
+
return `${JSON.stringify(parsed, null, 2)}\n`;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// try next
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return `${JSON.stringify({ ...FALLBACK_TEMPLATE, $schema: resolveSchemaPath(workspaceRoot, destRelative) }, null, 2)}\n`;
|
|
71
|
+
}
|
|
72
|
+
function resolveSchemaPath(workspaceRoot, destRelative) {
|
|
73
|
+
const prefix = relativePrefix(destRelative);
|
|
74
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(workspaceRoot, 'projects/angular/generator/api/schematics/sdk/schema.json'))) {
|
|
75
|
+
return `${prefix}projects/angular/generator/api/schematics/sdk/schema.json`;
|
|
76
|
+
}
|
|
77
|
+
return `${prefix}node_modules/@ojiepermana/angular/generator/api/schematics/sdk/schema.json`;
|
|
78
|
+
}
|
|
79
|
+
function relativePrefix(destRelative) {
|
|
80
|
+
const depth = destRelative.replace(/^\/+/, '').split('/').length - 1;
|
|
81
|
+
return depth <= 0 ? './' : '../'.repeat(depth);
|
|
82
|
+
}
|
|
83
|
+
function getNextCommand(workspaceRoot) {
|
|
84
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(workspaceRoot, 'projects/angular/collection.json'))) {
|
|
85
|
+
return 'bun run gen:sdk';
|
|
86
|
+
}
|
|
87
|
+
return 'ng generate @ojiepermana/angular:sdk';
|
|
88
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sdk = sdk;
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
|
+
const loader_1 = require("../../src/config/loader");
|
|
6
|
+
const engine_1 = require("../../src/engine");
|
|
7
|
+
/**
|
|
8
|
+
* Schematic factory. Reads `sdk.config.json` at the workspace root, runs the
|
|
9
|
+
* pure generator, and writes each emitted file into the Tree so
|
|
10
|
+
* `--dry-run` / `--force` behave as expected.
|
|
11
|
+
*/
|
|
12
|
+
function sdk(options = {}) {
|
|
13
|
+
return async (tree, context) => {
|
|
14
|
+
const workspaceRoot = process.cwd();
|
|
15
|
+
const configPath = options.config ?? 'config/sdk.config.json';
|
|
16
|
+
const targets = (0, loader_1.loadConfig)(configPath, workspaceRoot);
|
|
17
|
+
const selected = selectTargets(targets, options.target);
|
|
18
|
+
if (selected.length === 0) {
|
|
19
|
+
throw new Error(`No SDK targets matched "${options.target}"`);
|
|
20
|
+
}
|
|
21
|
+
for (const target of selected) {
|
|
22
|
+
const result = await (0, engine_1.generate)(target, workspaceRoot);
|
|
23
|
+
writeResult(tree, workspaceRoot, result, context);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function selectTargets(targets, selector) {
|
|
28
|
+
if (!selector)
|
|
29
|
+
return [...targets];
|
|
30
|
+
const idx = Number.parseInt(selector, 10);
|
|
31
|
+
if (!Number.isNaN(idx) && idx > 0 && idx <= targets.length) {
|
|
32
|
+
return [targets[idx - 1]];
|
|
33
|
+
}
|
|
34
|
+
const lc = selector.toLowerCase();
|
|
35
|
+
return targets.filter((t) => t.clientName.toLowerCase() === lc || t.output.toLowerCase().includes(lc) || t.packageName.toLowerCase() === lc);
|
|
36
|
+
}
|
|
37
|
+
function writeResult(tree, workspaceRoot, result, context) {
|
|
38
|
+
const relOutput = (0, node_path_1.relative)(workspaceRoot, result.outputDir) || '.';
|
|
39
|
+
context.logger.info(`[sdk] ${result.target.mode} → ${relOutput} ` +
|
|
40
|
+
`(schemas=${result.stats.schemas}, operations=${result.stats.operations}, ` +
|
|
41
|
+
`tags=${result.stats.tags}, files=${result.stats.files})`);
|
|
42
|
+
for (const file of result.files) {
|
|
43
|
+
const absolute = (0, node_path_1.resolve)(result.outputDir, file.path);
|
|
44
|
+
const treePath = normalizeTreePath(workspaceRoot, absolute);
|
|
45
|
+
const buffer = Buffer.from(file.content, 'utf8');
|
|
46
|
+
if (tree.exists(treePath)) {
|
|
47
|
+
tree.overwrite(treePath, buffer);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
tree.create(treePath, buffer);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function normalizeTreePath(workspaceRoot, absolute) {
|
|
55
|
+
const rel = (0, node_path_1.relative)(workspaceRoot, absolute);
|
|
56
|
+
const posix = rel.split(/\\+/).join('/');
|
|
57
|
+
return posix.startsWith('/') ? posix : `/${posix}`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadConfig = loadConfig;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const schema_1 = require("./schema");
|
|
7
|
+
/**
|
|
8
|
+
* Loads an SDK generator config from disk. Supports `.json` and `.js`/`.cjs`
|
|
9
|
+
* (require-based). `.ts` is intentionally not supported in the first iteration
|
|
10
|
+
* to avoid pulling an extra compile step — use `.json` or `.js` instead.
|
|
11
|
+
*/
|
|
12
|
+
function loadConfig(configPath, workspaceRoot) {
|
|
13
|
+
const absolute = (0, node_path_1.isAbsolute)(configPath) ? configPath : (0, node_path_1.resolve)(workspaceRoot, configPath);
|
|
14
|
+
if (!(0, node_fs_1.existsSync)(absolute)) {
|
|
15
|
+
throw new Error(`SDK config not found at ${absolute}`);
|
|
16
|
+
}
|
|
17
|
+
const ext = (0, node_path_1.extname)(absolute).toLowerCase();
|
|
18
|
+
let raw;
|
|
19
|
+
if (ext === '.json' || ext === '') {
|
|
20
|
+
const text = (0, node_fs_1.readFileSync)(absolute, 'utf8');
|
|
21
|
+
raw = JSON.parse(stripJsonComments(text));
|
|
22
|
+
}
|
|
23
|
+
else if (ext === '.js' || ext === '.cjs') {
|
|
24
|
+
const mod = require(absolute);
|
|
25
|
+
raw = extractDefaultExport(mod);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
throw new Error(`Unsupported SDK config extension: ${ext} (use .json or .js)`);
|
|
29
|
+
}
|
|
30
|
+
return (0, schema_1.resolveConfig)(raw);
|
|
31
|
+
}
|
|
32
|
+
function extractDefaultExport(mod) {
|
|
33
|
+
if (mod && typeof mod === 'object' && 'default' in mod) {
|
|
34
|
+
return mod.default;
|
|
35
|
+
}
|
|
36
|
+
return mod;
|
|
37
|
+
}
|
|
38
|
+
function stripJsonComments(text) {
|
|
39
|
+
// Minimal JSONC support: strip // line comments and /* */ block comments.
|
|
40
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1');
|
|
41
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Public config shape for the SDK generator.
|
|
4
|
+
*
|
|
5
|
+
* Users place an `sdk.config.json` at the workspace root describing one or more
|
|
6
|
+
* generation targets. All paths are resolved relative to the workspace root
|
|
7
|
+
* (i.e. the directory containing the config file).
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.resolveTarget = resolveTarget;
|
|
11
|
+
exports.resolveConfig = resolveConfig;
|
|
12
|
+
const DEFAULT_BANNER = '/* eslint-disable */\n/* Auto-generated by @ojiepermana/angular/generator/api. DO NOT EDIT. */';
|
|
13
|
+
const DEFAULT_FEATURES = {
|
|
14
|
+
models: true,
|
|
15
|
+
operations: true,
|
|
16
|
+
services: true,
|
|
17
|
+
client: true,
|
|
18
|
+
metadata: true,
|
|
19
|
+
navigation: true,
|
|
20
|
+
};
|
|
21
|
+
function resolveTarget(raw) {
|
|
22
|
+
if (!raw || typeof raw !== 'object') {
|
|
23
|
+
throw new Error('Invalid target: expected object');
|
|
24
|
+
}
|
|
25
|
+
if (!raw.input)
|
|
26
|
+
throw new Error('Target is missing required "input"');
|
|
27
|
+
if (!raw.output)
|
|
28
|
+
throw new Error('Target is missing required "output"');
|
|
29
|
+
const mode = raw.mode ?? 'standalone';
|
|
30
|
+
if (!['standalone', 'library', 'secondary-entrypoint'].includes(mode)) {
|
|
31
|
+
throw new Error(`Invalid target mode: ${mode}`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
input: raw.input,
|
|
35
|
+
output: raw.output,
|
|
36
|
+
mode,
|
|
37
|
+
clientName: raw.clientName ?? 'Api',
|
|
38
|
+
packageName: raw.packageName ?? '@local/sdk',
|
|
39
|
+
packageVersion: raw.packageVersion ?? '0.0.1',
|
|
40
|
+
rootUrl: raw.rootUrl,
|
|
41
|
+
features: { ...DEFAULT_FEATURES, ...(raw.features ?? {}) },
|
|
42
|
+
splitByDomain: raw.splitByDomain === true,
|
|
43
|
+
splitDepth: raw.splitDepth === 'tag' ? 'tag' : 'service',
|
|
44
|
+
banner: raw.banner ?? DEFAULT_BANNER,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function resolveConfig(raw) {
|
|
48
|
+
if (!raw || typeof raw !== 'object') {
|
|
49
|
+
throw new Error('Invalid SDK config: root must be an object');
|
|
50
|
+
}
|
|
51
|
+
const cfg = raw;
|
|
52
|
+
if (!Array.isArray(cfg.targets) || cfg.targets.length === 0) {
|
|
53
|
+
throw new Error('Invalid SDK config: "targets" must be a non-empty array');
|
|
54
|
+
}
|
|
55
|
+
return cfg.targets.map(resolveTarget);
|
|
56
|
+
}
|