@ojiepermana/angular-sdk 22.0.44 → 22.0.46
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/package.json +11 -5
- package/schematics/collection.json +20 -0
- package/schematics/init/index.js +90 -0
- package/schematics/init/schema.json +19 -0
- package/schematics/ng-add/index.js +134 -0
- package/schematics/ng-add/schema.json +14 -0
- package/schematics/sdk/index.js +76 -0
- package/schematics/sdk/schema.json +19 -0
- package/sdk.config.example.json +24 -0
- package/src/config/loader.js +41 -0
- package/src/config/schema.js +58 -0
- package/src/emit/client.js +248 -0
- package/src/emit/metadata.js +295 -0
- package/src/emit/models.js +106 -0
- package/src/emit/navigation.js +56 -0
- package/src/emit/operations.js +122 -0
- package/src/emit/public-api.js +54 -0
- package/src/emit/services.js +107 -0
- package/src/engine.js +65 -0
- package/src/layout/per-domain.js +359 -0
- package/src/parser/bundle.js +25 -0
- package/src/parser/ir.js +320 -0
- package/src/parser/types.js +7 -0
- package/src/render/template.js +58 -0
- package/src/writer/index.js +278 -0
package/src/parser/ir.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildIR = buildIR;
|
|
4
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];
|
|
5
|
+
/**
|
|
6
|
+
* Build the Intermediate Representation from a bundled OpenAPI document.
|
|
7
|
+
*
|
|
8
|
+
* Expectations about input:
|
|
9
|
+
* - `$ref` strings in the form `#/components/schemas/<Name>` (or parameters).
|
|
10
|
+
* - OpenAPI 3.0.x / 3.1.x / 3.2.x (nullable via `type: ['string', 'null']` or
|
|
11
|
+
* `nullable: true`).
|
|
12
|
+
*/
|
|
13
|
+
function buildIR(doc) {
|
|
14
|
+
const info = doc.info ?? {};
|
|
15
|
+
const servers = Array.isArray(doc.servers)
|
|
16
|
+
? doc.servers.map((s) => ({ url: String(s.url ?? ''), description: s.description }))
|
|
17
|
+
: [];
|
|
18
|
+
const tags = Array.isArray(doc.tags)
|
|
19
|
+
? doc.tags.map((t) => ({
|
|
20
|
+
name: String(t.name),
|
|
21
|
+
parent: t.parent,
|
|
22
|
+
kind: t.kind,
|
|
23
|
+
xIcon: t['x-icon'],
|
|
24
|
+
description: t.description,
|
|
25
|
+
}))
|
|
26
|
+
: [];
|
|
27
|
+
const schemas = buildSchemas(doc.components?.schemas);
|
|
28
|
+
const operations = buildOperations(doc.paths, doc.components?.parameters);
|
|
29
|
+
const security = buildSecuritySchemes(doc.components?.securitySchemes);
|
|
30
|
+
return {
|
|
31
|
+
title: String(info.title ?? 'API'),
|
|
32
|
+
version: String(info.version ?? '0.0.0'),
|
|
33
|
+
description: info.description,
|
|
34
|
+
servers,
|
|
35
|
+
tags,
|
|
36
|
+
navigation: buildNavigation(tags),
|
|
37
|
+
schemas,
|
|
38
|
+
operations,
|
|
39
|
+
security,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Schemas
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
function buildSchemas(rawSchemas) {
|
|
46
|
+
if (!rawSchemas)
|
|
47
|
+
return [];
|
|
48
|
+
const names = Object.keys(rawSchemas).sort();
|
|
49
|
+
return names.map((name) => buildSchema(name, rawSchemas[name]));
|
|
50
|
+
}
|
|
51
|
+
function buildSchema(name, raw) {
|
|
52
|
+
const required = Array.isArray(raw?.required) ? raw.required.slice() : [];
|
|
53
|
+
const properties = {};
|
|
54
|
+
// allOf — merge inline: fold each `properties` + required lists into the parent.
|
|
55
|
+
const parts = Array.isArray(raw?.allOf) ? raw.allOf : [];
|
|
56
|
+
const mergedProps = { ...(raw?.properties ?? {}) };
|
|
57
|
+
for (const part of parts) {
|
|
58
|
+
if (part?.properties)
|
|
59
|
+
Object.assign(mergedProps, part.properties);
|
|
60
|
+
if (Array.isArray(part?.required)) {
|
|
61
|
+
for (const r of part.required)
|
|
62
|
+
if (!required.includes(r))
|
|
63
|
+
required.push(r);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const [propName, propSchema] of Object.entries(mergedProps)) {
|
|
67
|
+
properties[propName] = buildFieldRule(propSchema, required.includes(propName));
|
|
68
|
+
}
|
|
69
|
+
// Primitive enum schemas → emit as alias.
|
|
70
|
+
const primitiveType = resolvePrimitiveType(raw);
|
|
71
|
+
if (primitiveType && Array.isArray(raw?.enum)) {
|
|
72
|
+
return {
|
|
73
|
+
name,
|
|
74
|
+
description: raw?.description,
|
|
75
|
+
required: [],
|
|
76
|
+
properties: {},
|
|
77
|
+
enumValues: raw.enum,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Array alias schemas.
|
|
81
|
+
if (resolvedTypeIs(raw, 'array') && raw?.items) {
|
|
82
|
+
const itemRef = refName(raw.items?.$ref);
|
|
83
|
+
const itemType = itemRef ? undefined : resolvePrimitiveType(raw.items);
|
|
84
|
+
return {
|
|
85
|
+
name,
|
|
86
|
+
description: raw?.description,
|
|
87
|
+
required: [],
|
|
88
|
+
properties: {},
|
|
89
|
+
arrayItemRef: itemRef ?? undefined,
|
|
90
|
+
arrayItemType: itemType ?? undefined,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
name,
|
|
95
|
+
description: raw?.description,
|
|
96
|
+
required,
|
|
97
|
+
properties,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function buildFieldRule(raw, required) {
|
|
101
|
+
const rule = { required };
|
|
102
|
+
// $ref → named reference.
|
|
103
|
+
const ref = refName(raw?.$ref);
|
|
104
|
+
if (ref) {
|
|
105
|
+
rule.ref = ref;
|
|
106
|
+
return rule;
|
|
107
|
+
}
|
|
108
|
+
// Array with items.
|
|
109
|
+
if (resolvedTypeIs(raw, 'array') && raw?.items) {
|
|
110
|
+
rule.type = 'array';
|
|
111
|
+
const itemsRef = refName(raw.items?.$ref);
|
|
112
|
+
if (itemsRef)
|
|
113
|
+
rule.itemsRef = itemsRef;
|
|
114
|
+
else
|
|
115
|
+
rule.itemsType = resolvePrimitiveType(raw.items) ?? 'unknown';
|
|
116
|
+
const itemEnum = raw.items?.enum;
|
|
117
|
+
if (Array.isArray(itemEnum))
|
|
118
|
+
rule.enumValues = itemEnum;
|
|
119
|
+
if (raw.description)
|
|
120
|
+
rule.description = raw.description;
|
|
121
|
+
rule.nullable = isNullable(raw);
|
|
122
|
+
return rule;
|
|
123
|
+
}
|
|
124
|
+
// Primitive / object.
|
|
125
|
+
rule.type = resolvePrimitiveType(raw) ?? 'unknown';
|
|
126
|
+
rule.nullable = isNullable(raw);
|
|
127
|
+
if (raw?.format)
|
|
128
|
+
rule.format = raw.format;
|
|
129
|
+
if (raw?.description)
|
|
130
|
+
rule.description = raw.description;
|
|
131
|
+
if (typeof raw?.minLength === 'number')
|
|
132
|
+
rule.minLength = raw.minLength;
|
|
133
|
+
if (typeof raw?.maxLength === 'number')
|
|
134
|
+
rule.maxLength = raw.maxLength;
|
|
135
|
+
if (typeof raw?.minimum === 'number')
|
|
136
|
+
rule.minimum = raw.minimum;
|
|
137
|
+
if (typeof raw?.maximum === 'number')
|
|
138
|
+
rule.maximum = raw.maximum;
|
|
139
|
+
if (typeof raw?.pattern === 'string')
|
|
140
|
+
rule.pattern = raw.pattern;
|
|
141
|
+
if (Array.isArray(raw?.enum))
|
|
142
|
+
rule.enumValues = raw.enum;
|
|
143
|
+
return rule;
|
|
144
|
+
}
|
|
145
|
+
function resolvePrimitiveType(raw) {
|
|
146
|
+
if (!raw)
|
|
147
|
+
return undefined;
|
|
148
|
+
const t = raw.type;
|
|
149
|
+
if (Array.isArray(t)) {
|
|
150
|
+
const nonNull = t.find((x) => x !== 'null');
|
|
151
|
+
return nonNull ?? undefined;
|
|
152
|
+
}
|
|
153
|
+
if (typeof t === 'string')
|
|
154
|
+
return t;
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
function resolvedTypeIs(raw, target) {
|
|
158
|
+
return resolvePrimitiveType(raw) === target;
|
|
159
|
+
}
|
|
160
|
+
function isNullable(raw) {
|
|
161
|
+
if (raw?.nullable === true)
|
|
162
|
+
return true;
|
|
163
|
+
const t = raw?.type;
|
|
164
|
+
if (Array.isArray(t) && t.includes('null'))
|
|
165
|
+
return true;
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
function refName(ref) {
|
|
169
|
+
if (typeof ref !== 'string')
|
|
170
|
+
return undefined;
|
|
171
|
+
const idx = ref.lastIndexOf('/');
|
|
172
|
+
return idx >= 0 ? ref.slice(idx + 1) : ref;
|
|
173
|
+
}
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Operations
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
function buildOperations(paths, sharedParams) {
|
|
178
|
+
if (!paths)
|
|
179
|
+
return [];
|
|
180
|
+
const operations = [];
|
|
181
|
+
for (const [pathKey, pathItem] of Object.entries(paths)) {
|
|
182
|
+
if (!pathItem || typeof pathItem !== 'object')
|
|
183
|
+
continue;
|
|
184
|
+
const pathLevelParams = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
|
|
185
|
+
for (const method of HTTP_METHODS) {
|
|
186
|
+
const op = pathItem[method];
|
|
187
|
+
if (!op)
|
|
188
|
+
continue;
|
|
189
|
+
operations.push(buildOperation(pathKey, method, op, pathLevelParams, sharedParams));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return operations.sort((a, b) => a.operationId.localeCompare(b.operationId));
|
|
193
|
+
}
|
|
194
|
+
function buildOperation(path, method, op, pathLevelParams, sharedParams) {
|
|
195
|
+
const tags = Array.isArray(op.tags) && op.tags.length ? op.tags.map(String) : ['Default'];
|
|
196
|
+
const params = mergeParams(pathLevelParams, op.parameters, sharedParams);
|
|
197
|
+
const body = op.requestBody;
|
|
198
|
+
const bodySchemaRef = refName(body?.content?.['application/json']?.schema?.$ref) ?? refName(body?.$ref) ?? undefined;
|
|
199
|
+
const responses = {};
|
|
200
|
+
let successStatus;
|
|
201
|
+
let successRef;
|
|
202
|
+
for (const [status, resp] of Object.entries(op.responses ?? {})) {
|
|
203
|
+
const r = resp;
|
|
204
|
+
const ref = refName(r?.content?.['application/json']?.schema?.$ref) ?? refName(r?.$ref) ?? undefined;
|
|
205
|
+
responses[status] = ref;
|
|
206
|
+
if (!successStatus && status.startsWith('2')) {
|
|
207
|
+
successStatus = status;
|
|
208
|
+
successRef = ref;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const securitySchemes = [];
|
|
212
|
+
if (Array.isArray(op.security)) {
|
|
213
|
+
for (const s of op.security) {
|
|
214
|
+
if (s && typeof s === 'object') {
|
|
215
|
+
for (const key of Object.keys(s)) {
|
|
216
|
+
if (!securitySchemes.includes(key))
|
|
217
|
+
securitySchemes.push(key);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const requiredPermissions = Array.isArray(op['x-required-permissions'])
|
|
223
|
+
? op['x-required-permissions'].map(String)
|
|
224
|
+
: [];
|
|
225
|
+
const authorizationNotes = Array.isArray(op['x-authorization-notes'])
|
|
226
|
+
? op['x-authorization-notes'].map(String)
|
|
227
|
+
: [];
|
|
228
|
+
const operationId = op.operationId ?? synthesizeOperationId(method, path);
|
|
229
|
+
return {
|
|
230
|
+
operationId,
|
|
231
|
+
method,
|
|
232
|
+
path,
|
|
233
|
+
tag: tags[0],
|
|
234
|
+
tags,
|
|
235
|
+
summary: op.summary,
|
|
236
|
+
description: op.description,
|
|
237
|
+
params,
|
|
238
|
+
bodySchemaRef,
|
|
239
|
+
bodyRequired: body?.required === true,
|
|
240
|
+
responses,
|
|
241
|
+
successStatus,
|
|
242
|
+
successRef,
|
|
243
|
+
securitySchemes,
|
|
244
|
+
requiredPermissions,
|
|
245
|
+
authorizationNotes,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function mergeParams(pathLevelParams, opParams, sharedParams) {
|
|
249
|
+
const all = [...(pathLevelParams ?? []), ...(opParams ?? [])];
|
|
250
|
+
const seen = new Set();
|
|
251
|
+
const out = [];
|
|
252
|
+
for (const raw of all) {
|
|
253
|
+
const resolved = resolveParamRef(raw, sharedParams);
|
|
254
|
+
if (!resolved?.name || !resolved?.in)
|
|
255
|
+
continue;
|
|
256
|
+
const key = `${resolved.in}:${resolved.name}`;
|
|
257
|
+
if (seen.has(key))
|
|
258
|
+
continue;
|
|
259
|
+
seen.add(key);
|
|
260
|
+
const loc = resolved.in === 'path' || resolved.in === 'query' || resolved.in === 'header' ? resolved.in : 'query';
|
|
261
|
+
const rule = buildFieldRule(resolved.schema ?? {}, !!resolved.required);
|
|
262
|
+
out.push({
|
|
263
|
+
name: resolved.name,
|
|
264
|
+
in: loc,
|
|
265
|
+
required: !!resolved.required,
|
|
266
|
+
rule,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
function resolveParamRef(raw, sharedParams) {
|
|
272
|
+
if (!raw)
|
|
273
|
+
return raw;
|
|
274
|
+
if (typeof raw.$ref !== 'string')
|
|
275
|
+
return raw;
|
|
276
|
+
const name = refName(raw.$ref);
|
|
277
|
+
if (!name || !sharedParams)
|
|
278
|
+
return raw;
|
|
279
|
+
return sharedParams[name] ?? raw;
|
|
280
|
+
}
|
|
281
|
+
function synthesizeOperationId(method, path) {
|
|
282
|
+
const cleaned = path
|
|
283
|
+
.replace(/\/+/g, '/')
|
|
284
|
+
.split('/')
|
|
285
|
+
.filter(Boolean)
|
|
286
|
+
.map((seg) => seg.replace(/^\{/, 'by-').replace(/\}$/, ''))
|
|
287
|
+
.join('-');
|
|
288
|
+
return `${method}-${cleaned || 'root'}`;
|
|
289
|
+
}
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// Security & navigation
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
function buildSecuritySchemes(raw) {
|
|
294
|
+
if (!raw)
|
|
295
|
+
return [];
|
|
296
|
+
return Object.entries(raw).map(([name, s]) => ({
|
|
297
|
+
name,
|
|
298
|
+
type: String(s.type ?? 'unknown'),
|
|
299
|
+
scheme: s.scheme,
|
|
300
|
+
bearerFormat: s.bearerFormat,
|
|
301
|
+
in: s.in,
|
|
302
|
+
}));
|
|
303
|
+
}
|
|
304
|
+
function buildNavigation(tags) {
|
|
305
|
+
const byName = new Map();
|
|
306
|
+
for (const t of tags) {
|
|
307
|
+
byName.set(t.name, { name: t.name, xIcon: t.xIcon, description: t.description, children: [] });
|
|
308
|
+
}
|
|
309
|
+
const roots = [];
|
|
310
|
+
for (const t of tags) {
|
|
311
|
+
const node = byName.get(t.name);
|
|
312
|
+
if (t.parent && byName.has(t.parent)) {
|
|
313
|
+
byName.get(t.parent).children.push(node);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
roots.push(node);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return roots;
|
|
320
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Intermediate Representation (IR) that emitters consume. Keeping this thin +
|
|
4
|
+
* plain-object makes emitters trivial to unit test without pulling in an OpenAPI
|
|
5
|
+
* parser in the test path.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tiny rendering helpers. We use template literals + small utilities rather
|
|
4
|
+
* than a template engine to keep the generator dependency-free and the
|
|
5
|
+
* emitter output predictable.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.kebabCase = kebabCase;
|
|
9
|
+
exports.pascalCase = pascalCase;
|
|
10
|
+
exports.camelCase = camelCase;
|
|
11
|
+
exports.finalize = finalize;
|
|
12
|
+
exports.comment = comment;
|
|
13
|
+
exports.joinBlocks = joinBlocks;
|
|
14
|
+
/** kebab-case (preserving digits) — matches the naming style in `old/api/`. */
|
|
15
|
+
function kebabCase(input) {
|
|
16
|
+
return String(input)
|
|
17
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
18
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
19
|
+
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
20
|
+
.replace(/^-+|-+$/g, '')
|
|
21
|
+
.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
/** PascalCase — used for class/interface names when sanitizing tags. */
|
|
24
|
+
function pascalCase(input) {
|
|
25
|
+
return String(input)
|
|
26
|
+
.replace(/[^a-zA-Z0-9]+/g, ' ')
|
|
27
|
+
.trim()
|
|
28
|
+
.split(/\s+/)
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.map((w) => w[0].toUpperCase() + w.slice(1))
|
|
31
|
+
.join('');
|
|
32
|
+
}
|
|
33
|
+
/** camelCase — used for variable / method / fn names. */
|
|
34
|
+
function camelCase(input) {
|
|
35
|
+
const pc = pascalCase(input);
|
|
36
|
+
return pc ? pc[0].toLowerCase() + pc.slice(1) : '';
|
|
37
|
+
}
|
|
38
|
+
/** Trim trailing whitespace on every line and ensure a single trailing newline. */
|
|
39
|
+
function finalize(text) {
|
|
40
|
+
const trimmed = text
|
|
41
|
+
.split('\n')
|
|
42
|
+
.map((line) => line.replace(/\s+$/g, ''))
|
|
43
|
+
.join('\n')
|
|
44
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
45
|
+
.replace(/^\s*\n/, '');
|
|
46
|
+
return trimmed.endsWith('\n') ? trimmed : trimmed + '\n';
|
|
47
|
+
}
|
|
48
|
+
/** Render an indented block-comment (1-line friendly). */
|
|
49
|
+
function comment(text) {
|
|
50
|
+
if (!text)
|
|
51
|
+
return '';
|
|
52
|
+
const safe = text.replace(/\*\//g, '*\\/');
|
|
53
|
+
return `/** ${safe} */`;
|
|
54
|
+
}
|
|
55
|
+
/** Join non-empty blocks with a blank line between them. */
|
|
56
|
+
function joinBlocks(blocks) {
|
|
57
|
+
return blocks.filter(Boolean).join('\n\n');
|
|
58
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeStandalone = writeStandalone;
|
|
4
|
+
exports.writeLibrary = writeLibrary;
|
|
5
|
+
exports.writeSecondaryEntrypoint = writeSecondaryEntrypoint;
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
8
|
+
const template_1 = require("../render/template");
|
|
9
|
+
const ROOT_ENTRYPOINT = '';
|
|
10
|
+
const VIRTUAL_ROOT = '/__sdk__';
|
|
11
|
+
/**
|
|
12
|
+
* No-op writer for the standalone mode — returns emitted files unchanged. Kept
|
|
13
|
+
* as a named function so modes are composable from a single switch at the
|
|
14
|
+
* engine layer.
|
|
15
|
+
*/
|
|
16
|
+
function writeStandalone(files) {
|
|
17
|
+
return files;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Library mode: add `ng-package.json`, `package.json`, and a top-level README
|
|
21
|
+
* so the `output` folder can be built with ng-packagr directly.
|
|
22
|
+
*/
|
|
23
|
+
function writeLibrary(files, ir, target, workspaceRoot, outputDir) {
|
|
24
|
+
const normalizedFiles = rewriteCrossEntrypointImports(files, target, workspaceRoot, outputDir);
|
|
25
|
+
const ngPackage = {
|
|
26
|
+
$schema: toPosixPath((0, node_path_1.relative)(outputDir, (0, node_path_1.resolve)(workspaceRoot, 'node_modules/ng-packagr/ng-package.schema.json'))),
|
|
27
|
+
dest: resolveLibraryDestPath(workspaceRoot, outputDir),
|
|
28
|
+
lib: { entryFile: 'public-api.ts' },
|
|
29
|
+
};
|
|
30
|
+
const pkg = {
|
|
31
|
+
name: target.packageName,
|
|
32
|
+
version: target.packageVersion,
|
|
33
|
+
description: `Generated SDK for ${ir.title} ${ir.version}`,
|
|
34
|
+
peerDependencies: {
|
|
35
|
+
'@angular/common': '>=22.0.0',
|
|
36
|
+
'@angular/core': '>=22.0.0',
|
|
37
|
+
rxjs: '>=7.8.0',
|
|
38
|
+
},
|
|
39
|
+
sideEffects: false,
|
|
40
|
+
};
|
|
41
|
+
return [
|
|
42
|
+
...normalizedFiles,
|
|
43
|
+
createNgPackageFile('ng-package.json', ngPackage),
|
|
44
|
+
...createNestedEntrypointNgPackages(normalizedFiles),
|
|
45
|
+
{
|
|
46
|
+
path: 'package.json',
|
|
47
|
+
content: (0, template_1.finalize)(JSON.stringify(pkg, null, 2)),
|
|
48
|
+
},
|
|
49
|
+
...createLibraryTsConfigFiles(workspaceRoot, outputDir),
|
|
50
|
+
{
|
|
51
|
+
path: 'README.md',
|
|
52
|
+
content: (0, template_1.finalize)(`# ${target.packageName}\n\nGenerated SDK for **${ir.title}** v${ir.version}.\n\nThis folder was generated by the local SDK generator. Do not edit by hand.\n`),
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Secondary-entrypoint mode: same file set as standalone, plus a minimal
|
|
58
|
+
* `ng-package.json` pointing at `public-api.ts`. Meant to live **inside** an
|
|
59
|
+
* existing library (e.g. `projects/angular/sdk-api/`) so ng-packagr picks it
|
|
60
|
+
* up automatically as a secondary entrypoint of the parent library.
|
|
61
|
+
*/
|
|
62
|
+
function writeSecondaryEntrypoint(files, _ir, target, workspaceRoot, outputDir) {
|
|
63
|
+
const normalizedFiles = rewriteCrossEntrypointImports(files, target, workspaceRoot, outputDir);
|
|
64
|
+
const ngPackage = {
|
|
65
|
+
lib: { entryFile: 'public-api.ts' },
|
|
66
|
+
};
|
|
67
|
+
return [
|
|
68
|
+
...normalizedFiles,
|
|
69
|
+
createNgPackageFile('ng-package.json', ngPackage),
|
|
70
|
+
...createNestedEntrypointNgPackages(normalizedFiles),
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
function rewriteCrossEntrypointImports(files, target, workspaceRoot, outputDir) {
|
|
74
|
+
const entrypointDirs = collectEntrypointDirs(files);
|
|
75
|
+
if (entrypointDirs.length <= 1) {
|
|
76
|
+
return [...files];
|
|
77
|
+
}
|
|
78
|
+
const packageImportBase = resolvePackageImportBase(target, workspaceRoot, outputDir);
|
|
79
|
+
if (!packageImportBase) {
|
|
80
|
+
return [...files];
|
|
81
|
+
}
|
|
82
|
+
const knownFiles = new Set(files.map((file) => file.path));
|
|
83
|
+
return files.map((file) => {
|
|
84
|
+
const owner = findEntrypointOwner(file.path, entrypointDirs);
|
|
85
|
+
if (owner === ROOT_ENTRYPOINT) {
|
|
86
|
+
return file;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...file,
|
|
90
|
+
content: file.content.replace(/(from\s+['"])([^'"]+)(['"])/g, (_match, prefix, spec, suffix) => {
|
|
91
|
+
const nextSpec = rewriteModuleSpecifier(spec, file.path, owner, entrypointDirs, knownFiles, packageImportBase);
|
|
92
|
+
return `${prefix}${nextSpec}${suffix}`;
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function rewriteModuleSpecifier(specifier, currentPath, currentOwner, entrypointDirs, knownFiles, packageImportBase) {
|
|
98
|
+
if (!specifier.startsWith('./') && !specifier.startsWith('../')) {
|
|
99
|
+
return specifier;
|
|
100
|
+
}
|
|
101
|
+
const targetPath = resolveVirtualFilePath(currentPath, specifier, knownFiles);
|
|
102
|
+
if (!targetPath) {
|
|
103
|
+
return specifier;
|
|
104
|
+
}
|
|
105
|
+
const targetOwner = findEntrypointOwner(targetPath, entrypointDirs);
|
|
106
|
+
if (targetOwner === currentOwner) {
|
|
107
|
+
return specifier;
|
|
108
|
+
}
|
|
109
|
+
return buildEntrypointImport(packageImportBase, targetOwner);
|
|
110
|
+
}
|
|
111
|
+
function resolveVirtualFilePath(currentPath, specifier, knownFiles) {
|
|
112
|
+
const currentAbs = node_path_1.posix.join(VIRTUAL_ROOT, currentPath);
|
|
113
|
+
const resolved = node_path_1.posix.normalize(node_path_1.posix.join((0, node_path_1.dirname)(currentAbs), specifier));
|
|
114
|
+
const candidates = [`${resolved}.ts`, `${resolved}/index.ts`, `${resolved}/public-api.ts`, resolved];
|
|
115
|
+
for (const candidate of candidates) {
|
|
116
|
+
if (!candidate.startsWith(`${VIRTUAL_ROOT}/`)) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const relativePath = candidate.slice(VIRTUAL_ROOT.length + 1);
|
|
120
|
+
if (knownFiles.has(relativePath)) {
|
|
121
|
+
return relativePath;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
function resolvePackageImportBase(target, workspaceRoot, outputDir) {
|
|
127
|
+
if (target.mode === 'library') {
|
|
128
|
+
return target.packageName;
|
|
129
|
+
}
|
|
130
|
+
if (target.mode !== 'secondary-entrypoint') {
|
|
131
|
+
return '';
|
|
132
|
+
}
|
|
133
|
+
return inferSecondaryEntrypointPackageName(workspaceRoot, outputDir, target.packageName);
|
|
134
|
+
}
|
|
135
|
+
function inferSecondaryEntrypointPackageName(workspaceRoot, outputDir, fallback) {
|
|
136
|
+
let currentDir = outputDir;
|
|
137
|
+
let nearestPackageDir;
|
|
138
|
+
let nearestPackagrDir;
|
|
139
|
+
while (isWithinWorkspace(currentDir, workspaceRoot)) {
|
|
140
|
+
const packageJsonPath = (0, node_path_1.resolve)(currentDir, 'package.json');
|
|
141
|
+
if ((0, node_fs_1.existsSync)(packageJsonPath)) {
|
|
142
|
+
nearestPackageDir ??= currentDir;
|
|
143
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.resolve)(currentDir, 'ng-package.json'))) {
|
|
144
|
+
nearestPackagrDir = currentDir;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (currentDir === workspaceRoot) {
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
const parentDir = (0, node_path_1.dirname)(currentDir);
|
|
152
|
+
if (parentDir === currentDir) {
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
currentDir = parentDir;
|
|
156
|
+
}
|
|
157
|
+
const packageDir = nearestPackagrDir ?? nearestPackageDir;
|
|
158
|
+
if (!packageDir) {
|
|
159
|
+
return fallback;
|
|
160
|
+
}
|
|
161
|
+
const packageName = readPackageName(packageDir);
|
|
162
|
+
if (!packageName) {
|
|
163
|
+
return fallback;
|
|
164
|
+
}
|
|
165
|
+
const relativeOutput = toPosixPath((0, node_path_1.relative)(packageDir, outputDir)).replace(/^\.\//, '');
|
|
166
|
+
if (!relativeOutput || relativeOutput === '.') {
|
|
167
|
+
return packageName;
|
|
168
|
+
}
|
|
169
|
+
return `${packageName}/${relativeOutput}`;
|
|
170
|
+
}
|
|
171
|
+
function readPackageName(packageDir) {
|
|
172
|
+
const packageJsonPath = (0, node_path_1.resolve)(packageDir, 'package.json');
|
|
173
|
+
if (!(0, node_fs_1.existsSync)(packageJsonPath)) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const raw = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, 'utf8'));
|
|
177
|
+
return typeof raw.name === 'string' && raw.name.length > 0 ? raw.name : undefined;
|
|
178
|
+
}
|
|
179
|
+
function isWithinWorkspace(candidate, workspaceRoot) {
|
|
180
|
+
const rel = (0, node_path_1.relative)(workspaceRoot, candidate);
|
|
181
|
+
return rel === '' || (!rel.startsWith('..') && !(0, node_path_1.isAbsolute)(rel));
|
|
182
|
+
}
|
|
183
|
+
function collectEntrypointDirs(files) {
|
|
184
|
+
const dirs = new Set();
|
|
185
|
+
for (const file of files) {
|
|
186
|
+
if (file.path === 'public-api.ts') {
|
|
187
|
+
dirs.add(ROOT_ENTRYPOINT);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (!file.path.endsWith('/public-api.ts')) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
dirs.add(file.path.slice(0, -'/public-api.ts'.length));
|
|
194
|
+
}
|
|
195
|
+
return [...dirs].sort((left, right) => right.length - left.length || left.localeCompare(right));
|
|
196
|
+
}
|
|
197
|
+
function findEntrypointOwner(filePath, entrypointDirs) {
|
|
198
|
+
for (const entrypointDir of entrypointDirs) {
|
|
199
|
+
if (entrypointDir === ROOT_ENTRYPOINT) {
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (filePath === `${entrypointDir}/public-api.ts` || filePath.startsWith(`${entrypointDir}/`)) {
|
|
203
|
+
return entrypointDir;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return ROOT_ENTRYPOINT;
|
|
207
|
+
}
|
|
208
|
+
function buildEntrypointImport(packageImportBase, entrypointDir) {
|
|
209
|
+
return entrypointDir ? `${packageImportBase}/${entrypointDir}` : packageImportBase;
|
|
210
|
+
}
|
|
211
|
+
function createNestedEntrypointNgPackages(files) {
|
|
212
|
+
return collectSecondaryEntrypointDirs(files).map((dir) => createNgPackageFile(`${dir}/ng-package.json`, {
|
|
213
|
+
lib: { entryFile: 'public-api.ts' },
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
function collectSecondaryEntrypointDirs(files) {
|
|
217
|
+
const dirs = new Set();
|
|
218
|
+
for (const file of files) {
|
|
219
|
+
if (!file.path.endsWith('/public-api.ts')) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
dirs.add(file.path.slice(0, -'/public-api.ts'.length));
|
|
223
|
+
}
|
|
224
|
+
return [...dirs].sort();
|
|
225
|
+
}
|
|
226
|
+
function resolveLibraryDestPath(workspaceRoot, outputDir) {
|
|
227
|
+
const outputFolderName = (0, node_path_1.basename)(outputDir) || 'sdk';
|
|
228
|
+
return toPosixPath((0, node_path_1.relative)(outputDir, (0, node_path_1.resolve)(workspaceRoot, 'dist', outputFolderName)));
|
|
229
|
+
}
|
|
230
|
+
function createLibraryTsConfigFiles(workspaceRoot, outputDir) {
|
|
231
|
+
const rootTsConfig = toPosixPath((0, node_path_1.relative)(outputDir, (0, node_path_1.resolve)(workspaceRoot, 'tsconfig.json')));
|
|
232
|
+
const outDir = toPosixPath((0, node_path_1.relative)(outputDir, (0, node_path_1.resolve)(workspaceRoot, 'out-tsc/lib')));
|
|
233
|
+
const distDir = toRecursiveGlob((0, node_path_1.relative)(outputDir, (0, node_path_1.resolve)(workspaceRoot, 'dist')));
|
|
234
|
+
const nodeModulesDir = toRecursiveGlob((0, node_path_1.relative)(outputDir, (0, node_path_1.resolve)(workspaceRoot, 'node_modules')));
|
|
235
|
+
const tsconfigLib = {
|
|
236
|
+
extends: rootTsConfig,
|
|
237
|
+
compilerOptions: {
|
|
238
|
+
outDir,
|
|
239
|
+
declaration: true,
|
|
240
|
+
declarationMap: true,
|
|
241
|
+
types: [],
|
|
242
|
+
},
|
|
243
|
+
include: ['public-api.ts', '**/public-api.ts'],
|
|
244
|
+
exclude: ['**/*.spec.ts', distDir, nodeModulesDir],
|
|
245
|
+
};
|
|
246
|
+
const tsconfigLibProd = {
|
|
247
|
+
extends: './tsconfig.lib.json',
|
|
248
|
+
compilerOptions: {
|
|
249
|
+
declarationMap: false,
|
|
250
|
+
},
|
|
251
|
+
angularCompilerOptions: {
|
|
252
|
+
compilationMode: 'partial',
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
return [
|
|
256
|
+
{
|
|
257
|
+
path: 'tsconfig.lib.json',
|
|
258
|
+
content: (0, template_1.finalize)(JSON.stringify(tsconfigLib, null, 2)),
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
path: 'tsconfig.lib.prod.json',
|
|
262
|
+
content: (0, template_1.finalize)(JSON.stringify(tsconfigLibProd, null, 2)),
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
}
|
|
266
|
+
function toRecursiveGlob(value) {
|
|
267
|
+
const normalized = toPosixPath(value).replace(/\/+$/, '');
|
|
268
|
+
return normalized === '.' ? './**' : `${normalized}/**`;
|
|
269
|
+
}
|
|
270
|
+
function toPosixPath(value) {
|
|
271
|
+
return value.split('\\').join('/');
|
|
272
|
+
}
|
|
273
|
+
function createNgPackageFile(path, content) {
|
|
274
|
+
return {
|
|
275
|
+
path,
|
|
276
|
+
content: (0, template_1.finalize)(JSON.stringify(content, null, 2)),
|
|
277
|
+
};
|
|
278
|
+
}
|