@omnixdp/typegen 0.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 +48 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +158 -0
- package/dist/fetch-schema.d.ts +3 -0
- package/dist/fetch-schema.js +132 -0
- package/dist/generate.d.ts +5 -0
- package/dist/generate.js +219 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +8 -0
- package/dist/schema.d.ts +58 -0
- package/dist/schema.js +2 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# @omnixdp/typegen
|
|
2
|
+
|
|
3
|
+
Generate TypeScript types for Omni xDP SDK responses from live CMS, Business Objects, and Ratings and Reviews schemas.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -D @omnixdp/typegen
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Generate
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx omnixdp-typegen \
|
|
15
|
+
--api-url https://api.example.com \
|
|
16
|
+
--management-token "$OMNIXDP_MANAGEMENT_TOKEN" \
|
|
17
|
+
--business-objects-token "$OMNIXDP_BO_TOKEN" \
|
|
18
|
+
--ratings-and-reviews-token "$OMNIXDP_RR_TOKEN" \
|
|
19
|
+
--cms-space marketing \
|
|
20
|
+
--bo-space products \
|
|
21
|
+
--rr-space storefront-reviews \
|
|
22
|
+
--out src/omnixdp.generated.ts
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Space arguments can be a space id, slug, or exact name. Regenerate the output after changing content types, taxonomy types, data types, or global fields in the admin.
|
|
26
|
+
|
|
27
|
+
## Check in CI
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx omnixdp-typegen --config omnixdp.typegen.config.js --check
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`--check` fails when the generated output is stale.
|
|
34
|
+
|
|
35
|
+
## Config File
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
module.exports = {
|
|
39
|
+
apiUrl: process.env.OMNIXDP_API_URL,
|
|
40
|
+
managementToken: process.env.OMNIXDP_MANAGEMENT_TOKEN,
|
|
41
|
+
businessObjectsToken: process.env.OMNIXDP_BO_TOKEN,
|
|
42
|
+
ratingsAndReviewsToken: process.env.OMNIXDP_RR_TOKEN,
|
|
43
|
+
cmsSpace: "marketing",
|
|
44
|
+
boSpace: "products",
|
|
45
|
+
rrSpace: "storefront-reviews",
|
|
46
|
+
out: "src/omnixdp.generated.ts"
|
|
47
|
+
};
|
|
48
|
+
```
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
const promises_1 = require("node:fs/promises");
|
|
28
|
+
const node_path_1 = require("node:path");
|
|
29
|
+
const node_url_1 = require("node:url");
|
|
30
|
+
const fetch_schema_1 = require("./fetch-schema");
|
|
31
|
+
const generate_1 = require("./generate");
|
|
32
|
+
async function main() {
|
|
33
|
+
const args = parseArgs(process.argv.slice(2));
|
|
34
|
+
if (args.help) {
|
|
35
|
+
printHelp();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const fileConfig = args.config ? await loadConfig(args.config) : await loadDefaultConfig();
|
|
39
|
+
const config = compactConfig({ ...fileConfig, ...args });
|
|
40
|
+
const out = config.out ?? "src/omnixdp.generated.ts";
|
|
41
|
+
if (!config.apiUrl) {
|
|
42
|
+
throw new Error("--api-url or apiUrl in config is required");
|
|
43
|
+
}
|
|
44
|
+
const schema = await (0, fetch_schema_1.fetchTypegenSchema)(config);
|
|
45
|
+
const output = (0, generate_1.generateTypes)({ schema });
|
|
46
|
+
const outPath = (0, node_path_1.resolve)(process.cwd(), out);
|
|
47
|
+
if (args.check) {
|
|
48
|
+
const current = await (0, promises_1.readFile)(outPath, "utf8").catch(() => "");
|
|
49
|
+
if (current !== output) {
|
|
50
|
+
throw new Error(`${out} is out of date. Run omnixdp-typegen to regenerate it.`);
|
|
51
|
+
}
|
|
52
|
+
console.log(`${out} is up to date.`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await (0, promises_1.mkdir)((0, node_path_1.dirname)(outPath), { recursive: true });
|
|
56
|
+
await (0, promises_1.writeFile)(outPath, output, "utf8");
|
|
57
|
+
console.log(`Generated ${out}`);
|
|
58
|
+
}
|
|
59
|
+
function parseArgs(argv) {
|
|
60
|
+
const out = {};
|
|
61
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
62
|
+
const arg = argv[i];
|
|
63
|
+
if (arg === "--help" || arg === "-h") {
|
|
64
|
+
out.help = true;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (arg === "--check") {
|
|
68
|
+
out.check = true;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (!arg.startsWith("--")) {
|
|
72
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
73
|
+
}
|
|
74
|
+
const key = arg.slice(2);
|
|
75
|
+
const value = argv[i + 1];
|
|
76
|
+
if (!value || value.startsWith("--")) {
|
|
77
|
+
throw new Error(`Missing value for ${arg}`);
|
|
78
|
+
}
|
|
79
|
+
i += 1;
|
|
80
|
+
assignArg(out, key, value);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function assignArg(out, key, value) {
|
|
85
|
+
switch (key) {
|
|
86
|
+
case "api-url":
|
|
87
|
+
out.apiUrl = value;
|
|
88
|
+
break;
|
|
89
|
+
case "management-token":
|
|
90
|
+
out.managementToken = value;
|
|
91
|
+
break;
|
|
92
|
+
case "business-objects-token":
|
|
93
|
+
out.businessObjectsToken = value;
|
|
94
|
+
break;
|
|
95
|
+
case "ratings-and-reviews-token":
|
|
96
|
+
out.ratingsAndReviewsToken = value;
|
|
97
|
+
break;
|
|
98
|
+
case "cms-space":
|
|
99
|
+
out.cmsSpace = value;
|
|
100
|
+
break;
|
|
101
|
+
case "bo-space":
|
|
102
|
+
out.boSpace = value;
|
|
103
|
+
break;
|
|
104
|
+
case "rr-space":
|
|
105
|
+
out.rrSpace = value;
|
|
106
|
+
break;
|
|
107
|
+
case "out":
|
|
108
|
+
out.out = value;
|
|
109
|
+
break;
|
|
110
|
+
case "config":
|
|
111
|
+
out.config = value;
|
|
112
|
+
break;
|
|
113
|
+
default:
|
|
114
|
+
throw new Error(`Unknown option: --${key}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function loadDefaultConfig() {
|
|
118
|
+
for (const file of ["omnixdp.typegen.config.ts", "omnixdp.typegen.config.js", "omnixdp.typegen.config.cjs", "omnixdp.typegen.config.json"]) {
|
|
119
|
+
const config = await loadConfig(file).catch((error) => {
|
|
120
|
+
if (error.code === "ENOENT")
|
|
121
|
+
return null;
|
|
122
|
+
throw error;
|
|
123
|
+
});
|
|
124
|
+
if (config)
|
|
125
|
+
return config;
|
|
126
|
+
}
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
async function loadConfig(file) {
|
|
130
|
+
const path = (0, node_path_1.resolve)(process.cwd(), file);
|
|
131
|
+
if (path.endsWith(".json")) {
|
|
132
|
+
return JSON.parse(await (0, promises_1.readFile)(path, "utf8"));
|
|
133
|
+
}
|
|
134
|
+
const imported = await Promise.resolve(`${(0, node_url_1.pathToFileURL)(path).href}`).then(s => __importStar(require(s)));
|
|
135
|
+
return (imported.default ?? imported.config ?? imported);
|
|
136
|
+
}
|
|
137
|
+
function compactConfig(config) {
|
|
138
|
+
return Object.fromEntries(Object.entries(config).filter(([, value]) => value !== undefined && value !== ""));
|
|
139
|
+
}
|
|
140
|
+
function printHelp() {
|
|
141
|
+
console.log(`Usage: omnixdp-typegen --api-url <url> [options]
|
|
142
|
+
|
|
143
|
+
Options:
|
|
144
|
+
--config <path> Load omnixdp typegen config
|
|
145
|
+
--management-token <token> CMS Management API bearer token
|
|
146
|
+
--business-objects-token <token> Business Objects API bearer token
|
|
147
|
+
--ratings-and-reviews-token <token> Ratings and Reviews API bearer token
|
|
148
|
+
--cms-space <id|slug|name> CMS space to generate
|
|
149
|
+
--bo-space <id|slug|name> Business Objects space to generate
|
|
150
|
+
--rr-space <id|slug|name> Ratings and Reviews space to generate
|
|
151
|
+
--out <path> Output file, default src/omnixdp.generated.ts
|
|
152
|
+
--check Fail if the output file is stale
|
|
153
|
+
`);
|
|
154
|
+
}
|
|
155
|
+
main().catch((error) => {
|
|
156
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
157
|
+
process.exitCode = 1;
|
|
158
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeApiUrl = normalizeApiUrl;
|
|
4
|
+
exports.fetchTypegenSchema = fetchTypegenSchema;
|
|
5
|
+
function normalizeApiUrl(apiUrl) {
|
|
6
|
+
const trimmed = apiUrl.trim().replace(/\/+$/, "");
|
|
7
|
+
if (!trimmed)
|
|
8
|
+
throw new Error("apiUrl is required");
|
|
9
|
+
return trimmed.endsWith("/v1") ? trimmed : `${trimmed}/v1`;
|
|
10
|
+
}
|
|
11
|
+
async function fetchTypegenSchema(config) {
|
|
12
|
+
const apiBase = normalizeApiUrl(config.apiUrl);
|
|
13
|
+
const [cms, businessObjects, ratingsAndReviews] = await Promise.all([
|
|
14
|
+
config.managementToken && config.cmsSpace
|
|
15
|
+
? fetchCmsSchema(apiBase, config.managementToken, config.cmsSpace)
|
|
16
|
+
: Promise.resolve({ contentTypes: [], taxonomyTypes: [] }),
|
|
17
|
+
config.businessObjectsToken && config.boSpace
|
|
18
|
+
? fetchBoSchema(apiBase, "business-objects", config.businessObjectsToken, config.boSpace)
|
|
19
|
+
: Promise.resolve({ dataTypes: [] }),
|
|
20
|
+
config.ratingsAndReviewsToken && config.rrSpace
|
|
21
|
+
? fetchBoSchema(apiBase, "ratings-and-reviews", config.ratingsAndReviewsToken, config.rrSpace)
|
|
22
|
+
: Promise.resolve({ dataTypes: [] })
|
|
23
|
+
]);
|
|
24
|
+
const canonical = JSON.stringify({ cms, businessObjects, ratingsAndReviews });
|
|
25
|
+
return {
|
|
26
|
+
generatedAt: new Date().toISOString(),
|
|
27
|
+
sourceHash: await hashText(canonical),
|
|
28
|
+
cms,
|
|
29
|
+
businessObjects,
|
|
30
|
+
ratingsAndReviews
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function fetchCmsSchema(apiBase, token, spaceRef) {
|
|
34
|
+
const space = await resolveSpace(apiBase, "management", token, spaceRef);
|
|
35
|
+
const [contentTypes, taxonomyTypes] = await Promise.all([
|
|
36
|
+
fetchModels(apiBase, "management", token, "content-types", space.id),
|
|
37
|
+
fetchModels(apiBase, "management", token, "taxonomy-types", space.id)
|
|
38
|
+
]);
|
|
39
|
+
return { space, contentTypes, taxonomyTypes };
|
|
40
|
+
}
|
|
41
|
+
async function fetchBoSchema(apiBase, app, token, spaceRef) {
|
|
42
|
+
const space = await resolveSpace(apiBase, app, token, spaceRef);
|
|
43
|
+
const dataTypes = await fetchModels(apiBase, app, token, "data-types", space.id);
|
|
44
|
+
return { space, dataTypes };
|
|
45
|
+
}
|
|
46
|
+
async function resolveSpace(apiBase, app, token, spaceRef) {
|
|
47
|
+
const spaces = await apiGet(apiBase, app, token, "spaces", { limit: "500" });
|
|
48
|
+
const wanted = spaceRef.trim();
|
|
49
|
+
const match = spaces.find((space) => space.id === wanted || space.slug === wanted || space.name === wanted);
|
|
50
|
+
if (!match?.id || !match.name || !match.slug) {
|
|
51
|
+
throw new Error(`Space "${spaceRef}" was not found in ${app}`);
|
|
52
|
+
}
|
|
53
|
+
return { id: match.id, name: match.name, slug: match.slug };
|
|
54
|
+
}
|
|
55
|
+
async function fetchModels(apiBase, app, token, resource, spaceId) {
|
|
56
|
+
const rows = await apiGet(apiBase, app, token, resource, { spaceId, limit: "500", includeArchived: "1" });
|
|
57
|
+
return rows
|
|
58
|
+
.filter((row) => Boolean(row.id && row.name && row.apiIdentifier))
|
|
59
|
+
.map((row) => ({
|
|
60
|
+
id: row.id,
|
|
61
|
+
name: row.name,
|
|
62
|
+
apiIdentifier: row.apiIdentifier,
|
|
63
|
+
fields: normalizeFields(row.fields ?? [])
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
async function apiGet(apiBase, app, token, resource, query) {
|
|
67
|
+
const url = new URL(`${apiBase}/${app}/${resource}`);
|
|
68
|
+
for (const [key, value] of Object.entries(query)) {
|
|
69
|
+
url.searchParams.set(key, value);
|
|
70
|
+
}
|
|
71
|
+
const response = await fetch(url, {
|
|
72
|
+
headers: {
|
|
73
|
+
accept: "application/json",
|
|
74
|
+
authorization: `Bearer ${token}`
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const body = (await response.json().catch(() => null));
|
|
78
|
+
if (!response.ok || !body || body.success !== true) {
|
|
79
|
+
const message = body && "error" in body ? body.error?.message : undefined;
|
|
80
|
+
throw new Error(`Failed to fetch ${app}/${resource}: ${message ?? response.statusText}`);
|
|
81
|
+
}
|
|
82
|
+
return body.data;
|
|
83
|
+
}
|
|
84
|
+
function normalizeFields(fields) {
|
|
85
|
+
return fields
|
|
86
|
+
.filter((field) => field.name && field.apiIdentifier && field.type)
|
|
87
|
+
.map((field) => {
|
|
88
|
+
const validations = isRecord(field.validations) ? field.validations : null;
|
|
89
|
+
return {
|
|
90
|
+
id: field.id,
|
|
91
|
+
name: field.name,
|
|
92
|
+
apiIdentifier: field.apiIdentifier,
|
|
93
|
+
type: field.type,
|
|
94
|
+
required: field.required === true,
|
|
95
|
+
multiple: field.multiple === true || validations?.__multiple === true,
|
|
96
|
+
localizable: field.localizable === true || validations?.__localizable === true,
|
|
97
|
+
deliveryApi: field.deliveryApi === true || validations?.__deliveryApi === true,
|
|
98
|
+
validations,
|
|
99
|
+
fields: nestedFields(field, validations),
|
|
100
|
+
globalFieldApiIdentifier: field.globalField?.apiIdentifier,
|
|
101
|
+
referenceTarget: readReferenceTarget(validations)
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function nestedFields(field, validations) {
|
|
106
|
+
if (Array.isArray(field.globalField?.fields))
|
|
107
|
+
return normalizeFields(field.globalField.fields);
|
|
108
|
+
const groupSchema = isRecord(validations?.groupSchema) ? validations.groupSchema : undefined;
|
|
109
|
+
const blockSchema = isRecord(validations?.blockSchema) ? validations.blockSchema : undefined;
|
|
110
|
+
const schema = groupSchema ?? blockSchema;
|
|
111
|
+
return Array.isArray(schema?.fields) ? normalizeFields(schema.fields) : undefined;
|
|
112
|
+
}
|
|
113
|
+
function readReferenceTarget(validations) {
|
|
114
|
+
const raw = validations?.referenceTarget;
|
|
115
|
+
if (!isRecord(raw))
|
|
116
|
+
return undefined;
|
|
117
|
+
return {
|
|
118
|
+
spaceId: typeof raw.spaceId === "string" ? raw.spaceId : undefined,
|
|
119
|
+
contentTypeId: typeof raw.contentTypeId === "string" ? raw.contentTypeId : undefined,
|
|
120
|
+
dataTypeId: typeof raw.dataTypeId === "string" ? raw.dataTypeId : undefined
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function isRecord(value) {
|
|
124
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
125
|
+
}
|
|
126
|
+
async function hashText(text) {
|
|
127
|
+
const bytes = new TextEncoder().encode(text);
|
|
128
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
129
|
+
return Array.from(new Uint8Array(digest))
|
|
130
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
131
|
+
.join("");
|
|
132
|
+
}
|
package/dist/generate.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateTypes = generateTypes;
|
|
4
|
+
function generateTypes({ schema }) {
|
|
5
|
+
const sections = [
|
|
6
|
+
renderHeader(schema),
|
|
7
|
+
renderSharedTypes(),
|
|
8
|
+
renderModelTypes("Cms", "ContentTypes", schema.cms.contentTypes),
|
|
9
|
+
renderModelTypes("Cms", "TaxonomyTypes", schema.cms.taxonomyTypes),
|
|
10
|
+
renderModelTypes("BusinessObject", "DataTypes", schema.businessObjects.dataTypes),
|
|
11
|
+
renderModelTypes("RatingsReview", "DataTypes", schema.ratingsAndReviews.dataTypes),
|
|
12
|
+
renderConvenienceAliases(schema)
|
|
13
|
+
].filter(Boolean);
|
|
14
|
+
return `${sections.join("\n\n")}\n`;
|
|
15
|
+
}
|
|
16
|
+
function renderHeader(schema) {
|
|
17
|
+
const lines = [
|
|
18
|
+
"/* eslint-disable */",
|
|
19
|
+
"// This file is generated by @omnixdp/typegen. Do not edit it manually.",
|
|
20
|
+
`// Generated at: ${schema.generatedAt}`,
|
|
21
|
+
`// Schema hash: ${schema.sourceHash}`
|
|
22
|
+
];
|
|
23
|
+
if (usesDeliveryTypes(schema)) {
|
|
24
|
+
lines.push("", "import type { DeliveryAsset, DeliveryEntry, DeliveryTaxonomyResponse } from \"@omnixdp/delivery\";");
|
|
25
|
+
}
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
function renderSharedTypes() {
|
|
29
|
+
return [
|
|
30
|
+
"export type OmnixdpJsonPrimitive = string | number | boolean | null;",
|
|
31
|
+
"export type OmnixdpJsonValue = OmnixdpJsonPrimitive | OmnixdpJsonValue[] | { [key: string]: OmnixdpJsonValue };",
|
|
32
|
+
"export type OmnixdpUploadValue = {",
|
|
33
|
+
" id?: string;",
|
|
34
|
+
" fileName?: string;",
|
|
35
|
+
" url?: string;",
|
|
36
|
+
" mimeType?: string | null;",
|
|
37
|
+
" sizeBytes?: number | null;",
|
|
38
|
+
" [key: string]: OmnixdpJsonValue | undefined;",
|
|
39
|
+
"};",
|
|
40
|
+
"export type OmnixdpRichTextValue = string | {",
|
|
41
|
+
" type: \"doc\";",
|
|
42
|
+
" content?: OmnixdpJsonValue[];",
|
|
43
|
+
" [key: string]: OmnixdpJsonValue | undefined;",
|
|
44
|
+
"};",
|
|
45
|
+
"export type OmnixdpDataTreeNode<TData> = {",
|
|
46
|
+
" id?: string;",
|
|
47
|
+
" data: TData;",
|
|
48
|
+
" children?: OmnixdpDataTreeNode<TData>[];",
|
|
49
|
+
" __fieldMeta?: Record<string, unknown>;",
|
|
50
|
+
"};",
|
|
51
|
+
"export type OmnixdpDataTreeValue<TData, TAggregation = Record<string, number>> = {",
|
|
52
|
+
" nodes: OmnixdpDataTreeNode<TData>[];",
|
|
53
|
+
" aggregation?: TAggregation & { children?: number };",
|
|
54
|
+
"};"
|
|
55
|
+
].join("\n");
|
|
56
|
+
}
|
|
57
|
+
function renderModelTypes(prefix, suffix, models) {
|
|
58
|
+
const typeBlocks = models.flatMap((model) => {
|
|
59
|
+
const typeName = modelTypeName(prefix, model);
|
|
60
|
+
return [
|
|
61
|
+
`export type ${typeName} = ${renderObjectType(model.fields, 0)};`
|
|
62
|
+
];
|
|
63
|
+
});
|
|
64
|
+
const mapName = `${prefix}${suffix}`;
|
|
65
|
+
const mapLines = [
|
|
66
|
+
`export type ${mapName} = {`,
|
|
67
|
+
...models.map((model) => ` ${quoteKey(model.apiIdentifier)}: ${modelTypeName(prefix, model)};`),
|
|
68
|
+
"};"
|
|
69
|
+
];
|
|
70
|
+
return [...typeBlocks, mapLines.join("\n")].join("\n\n");
|
|
71
|
+
}
|
|
72
|
+
function renderConvenienceAliases(schema) {
|
|
73
|
+
const lines = [];
|
|
74
|
+
if (schema.cms.contentTypes.length > 0) {
|
|
75
|
+
lines.push("export type CmsDeliveryEntry<TApiIdentifier extends keyof CmsContentTypes> = DeliveryEntry<CmsContentTypes[TApiIdentifier]>;");
|
|
76
|
+
}
|
|
77
|
+
if (schema.cms.taxonomyTypes.length > 0) {
|
|
78
|
+
lines.push("export type CmsTaxonomyResponse<TApiIdentifier extends keyof CmsTaxonomyTypes> = DeliveryTaxonomyResponse & {", " tree: Array<DeliveryTaxonomyResponse[\"tree\"][number] & { data?: CmsTaxonomyTypes[TApiIdentifier] | null }>;", "};");
|
|
79
|
+
}
|
|
80
|
+
if (schema.businessObjects.dataTypes.length > 0) {
|
|
81
|
+
lines.push("export type BusinessObjectDataObject<TApiIdentifier extends keyof BusinessObjectDataTypes> = {", " id: string;", " data: BusinessObjectDataTypes[TApiIdentifier];", " [key: string]: unknown;", "};");
|
|
82
|
+
}
|
|
83
|
+
if (schema.ratingsAndReviews.dataTypes.length > 0) {
|
|
84
|
+
lines.push("export type RatingsReviewDataObject<TApiIdentifier extends keyof RatingsReviewDataTypes> = {", " id: string;", " data: RatingsReviewDataTypes[TApiIdentifier];", " [key: string]: unknown;", "};");
|
|
85
|
+
}
|
|
86
|
+
if (schema.cms.contentTypes.length === 0 && schema.businessObjects.dataTypes.length === 0 && schema.ratingsAndReviews.dataTypes.length === 0) {
|
|
87
|
+
lines.push("// No models were fetched. Check the selected spaces and API tokens.");
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
function renderObjectType(fields, level) {
|
|
92
|
+
if (fields.length === 0)
|
|
93
|
+
return "Record<string, never>";
|
|
94
|
+
const indent = " ".repeat(level);
|
|
95
|
+
const nextIndent = " ".repeat(level + 1);
|
|
96
|
+
return [
|
|
97
|
+
"{",
|
|
98
|
+
...fields.map((field) => `${nextIndent}${quoteKey(field.apiIdentifier)}${field.required ? "" : "?"}: ${renderFieldType(field, level + 1)};`),
|
|
99
|
+
`${indent}}`
|
|
100
|
+
].join("\n");
|
|
101
|
+
}
|
|
102
|
+
function renderFieldType(field, level) {
|
|
103
|
+
const base = renderSingleFieldType(field, level);
|
|
104
|
+
return field.multiple && field.type !== "DATA_TREE" ? `Array<${base}>` : base;
|
|
105
|
+
}
|
|
106
|
+
function renderSingleFieldType(field, level) {
|
|
107
|
+
switch (field.type) {
|
|
108
|
+
case "TEXT":
|
|
109
|
+
case "MULTI_LINE_TEXT":
|
|
110
|
+
case "TITLE":
|
|
111
|
+
case "SLUG":
|
|
112
|
+
case "LINK":
|
|
113
|
+
case "URL":
|
|
114
|
+
case "MARKDOWN":
|
|
115
|
+
case "HTML":
|
|
116
|
+
case "CSS":
|
|
117
|
+
case "JAVASCRIPT":
|
|
118
|
+
case "XML":
|
|
119
|
+
case "REFERENCE":
|
|
120
|
+
case "CMS_ENTRY":
|
|
121
|
+
case "CMS_TAXONOMY":
|
|
122
|
+
case "RR_REVIEW":
|
|
123
|
+
case "RR_CATALOG":
|
|
124
|
+
case "BO_DATA_OBJECT":
|
|
125
|
+
case "BO_CATALOG":
|
|
126
|
+
case "COLOR":
|
|
127
|
+
return "string";
|
|
128
|
+
case "SELECT":
|
|
129
|
+
return optionUnion(field) ?? "string";
|
|
130
|
+
case "RADIO":
|
|
131
|
+
return `Array<${optionUnion(field) ?? "string"}>`;
|
|
132
|
+
case "NUMBER":
|
|
133
|
+
case "MONEY":
|
|
134
|
+
case "AGGREGATION":
|
|
135
|
+
return "number";
|
|
136
|
+
case "BOOLEAN":
|
|
137
|
+
return "boolean";
|
|
138
|
+
case "DATE":
|
|
139
|
+
return "string";
|
|
140
|
+
case "RICHTEXT":
|
|
141
|
+
return "OmnixdpRichTextValue";
|
|
142
|
+
case "JSON":
|
|
143
|
+
return "OmnixdpJsonValue";
|
|
144
|
+
case "MEDIA":
|
|
145
|
+
return "string | DeliveryAsset | null";
|
|
146
|
+
case "UPLOAD":
|
|
147
|
+
return "string | OmnixdpUploadValue | null";
|
|
148
|
+
case "GLOBALFIELD":
|
|
149
|
+
case "GROUP":
|
|
150
|
+
case "BLOCK":
|
|
151
|
+
case "MODULAR_BLOCK":
|
|
152
|
+
return field.fields?.length ? renderObjectType(field.fields, level) : "Record<string, unknown>";
|
|
153
|
+
case "DATA_TREE": {
|
|
154
|
+
const nested = field.fields ?? [];
|
|
155
|
+
const dataFields = nested.filter((child) => child.type !== "AGGREGATION");
|
|
156
|
+
const aggregationFields = nested.filter((child) => child.type === "AGGREGATION");
|
|
157
|
+
const dataType = dataFields.length ? renderObjectType(dataFields, level) : "Record<string, unknown>";
|
|
158
|
+
const aggregationType = aggregationFields.length ? renderObjectType(aggregationFields, level) : "Record<string, number>";
|
|
159
|
+
return `OmnixdpDataTreeValue<${dataType}, ${aggregationType}>`;
|
|
160
|
+
}
|
|
161
|
+
default:
|
|
162
|
+
return "unknown";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function optionUnion(field) {
|
|
166
|
+
const values = optionValues(field.validations);
|
|
167
|
+
if (values.length === 0)
|
|
168
|
+
return null;
|
|
169
|
+
return values.map((value) => JSON.stringify(value)).join(" | ");
|
|
170
|
+
}
|
|
171
|
+
function optionValues(validations) {
|
|
172
|
+
if (!validations)
|
|
173
|
+
return [];
|
|
174
|
+
const fromOptions = Array.isArray(validations.options)
|
|
175
|
+
? validations.options
|
|
176
|
+
.map((item) => (isRecord(item) && typeof item.value === "string" ? item.value.trim() : ""))
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
: [];
|
|
179
|
+
if (fromOptions.length > 0)
|
|
180
|
+
return unique(fromOptions);
|
|
181
|
+
const fromRadioValues = Array.isArray(validations.radioValues)
|
|
182
|
+
? validations.radioValues.map((item) => (typeof item === "string" ? item.trim() : "")).filter(Boolean)
|
|
183
|
+
: [];
|
|
184
|
+
if (fromRadioValues.length > 0)
|
|
185
|
+
return unique(fromRadioValues);
|
|
186
|
+
const labels = isRecord(validations.selectItemsLocales)
|
|
187
|
+
? Object.values(validations.selectItemsLocales)
|
|
188
|
+
.flatMap((items) => (Array.isArray(items) ? items : []))
|
|
189
|
+
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
: [];
|
|
192
|
+
return unique(labels);
|
|
193
|
+
}
|
|
194
|
+
function modelTypeName(prefix, model) {
|
|
195
|
+
return `${prefix}${pascalCase(model.apiIdentifier)}`;
|
|
196
|
+
}
|
|
197
|
+
function pascalCase(value) {
|
|
198
|
+
const words = value.split(/[^a-zA-Z0-9]+/).filter(Boolean);
|
|
199
|
+
const name = words.map((word) => `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join("");
|
|
200
|
+
return name || "Model";
|
|
201
|
+
}
|
|
202
|
+
function quoteKey(value) {
|
|
203
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value) ? value : JSON.stringify(value);
|
|
204
|
+
}
|
|
205
|
+
function unique(values) {
|
|
206
|
+
return [...new Set(values)];
|
|
207
|
+
}
|
|
208
|
+
function isRecord(value) {
|
|
209
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
210
|
+
}
|
|
211
|
+
function usesDeliveryTypes(schema) {
|
|
212
|
+
return (schema.cms.contentTypes.length > 0 ||
|
|
213
|
+
schema.cms.taxonomyTypes.length > 0 ||
|
|
214
|
+
schema.businessObjects.dataTypes.some((model) => model.fields.some(fieldUsesDeliveryAsset)) ||
|
|
215
|
+
schema.ratingsAndReviews.dataTypes.some((model) => model.fields.some(fieldUsesDeliveryAsset)));
|
|
216
|
+
}
|
|
217
|
+
function fieldUsesDeliveryAsset(field) {
|
|
218
|
+
return field.type === "MEDIA" || Boolean(field.fields?.some(fieldUsesDeliveryAsset));
|
|
219
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateTypes = exports.normalizeApiUrl = exports.fetchTypegenSchema = void 0;
|
|
4
|
+
var fetch_schema_1 = require("./fetch-schema");
|
|
5
|
+
Object.defineProperty(exports, "fetchTypegenSchema", { enumerable: true, get: function () { return fetch_schema_1.fetchTypegenSchema; } });
|
|
6
|
+
Object.defineProperty(exports, "normalizeApiUrl", { enumerable: true, get: function () { return fetch_schema_1.normalizeApiUrl; } });
|
|
7
|
+
var generate_1 = require("./generate");
|
|
8
|
+
Object.defineProperty(exports, "generateTypes", { enumerable: true, get: function () { return generate_1.generateTypes; } });
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type TypegenApplication = "cms" | "business-objects" | "ratings-and-reviews";
|
|
2
|
+
export type TypegenFieldKind = "TEXT" | "MULTI_LINE_TEXT" | "TITLE" | "SLUG" | "LINK" | "URL" | "NUMBER" | "MONEY" | "BOOLEAN" | "DATE" | "RICHTEXT" | "MARKDOWN" | "JSON" | "HTML" | "CSS" | "JAVASCRIPT" | "XML" | "REFERENCE" | "MEDIA" | "GLOBALFIELD" | "SELECT" | "RADIO" | "MODULAR_BLOCK" | "BLOCK" | "COLOR" | "GROUP" | "UPLOAD" | "RR_REVIEW" | "RR_CATALOG" | "BO_DATA_OBJECT" | "BO_CATALOG" | "CMS_ENTRY" | "CMS_TAXONOMY" | "DATA_TREE" | "AGGREGATION";
|
|
3
|
+
export type TypegenField = {
|
|
4
|
+
id?: string;
|
|
5
|
+
name: string;
|
|
6
|
+
apiIdentifier: string;
|
|
7
|
+
type: TypegenFieldKind | string;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
multiple?: boolean;
|
|
10
|
+
localizable?: boolean;
|
|
11
|
+
deliveryApi?: boolean;
|
|
12
|
+
validations?: Record<string, unknown> | null;
|
|
13
|
+
fields?: TypegenField[];
|
|
14
|
+
globalFieldApiIdentifier?: string;
|
|
15
|
+
referenceTarget?: {
|
|
16
|
+
spaceId?: string;
|
|
17
|
+
contentTypeId?: string;
|
|
18
|
+
dataTypeId?: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export type TypegenModel = {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
apiIdentifier: string;
|
|
25
|
+
fields: TypegenField[];
|
|
26
|
+
};
|
|
27
|
+
export type TypegenSpace = {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
slug: string;
|
|
31
|
+
};
|
|
32
|
+
export type TypegenSchema = {
|
|
33
|
+
generatedAt: string;
|
|
34
|
+
sourceHash: string;
|
|
35
|
+
cms: {
|
|
36
|
+
space?: TypegenSpace;
|
|
37
|
+
contentTypes: TypegenModel[];
|
|
38
|
+
taxonomyTypes: TypegenModel[];
|
|
39
|
+
};
|
|
40
|
+
businessObjects: {
|
|
41
|
+
space?: TypegenSpace;
|
|
42
|
+
dataTypes: TypegenModel[];
|
|
43
|
+
};
|
|
44
|
+
ratingsAndReviews: {
|
|
45
|
+
space?: TypegenSpace;
|
|
46
|
+
dataTypes: TypegenModel[];
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export type TypegenConfig = {
|
|
50
|
+
apiUrl: string;
|
|
51
|
+
managementToken?: string;
|
|
52
|
+
businessObjectsToken?: string;
|
|
53
|
+
ratingsAndReviewsToken?: string;
|
|
54
|
+
cmsSpace?: string;
|
|
55
|
+
boSpace?: string;
|
|
56
|
+
rrSpace?: string;
|
|
57
|
+
out?: string;
|
|
58
|
+
};
|
package/dist/schema.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omnixdp/typegen",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript type generator for Omni xDP SDK response models.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/omnixdp/platform.git",
|
|
9
|
+
"directory": "packages/typegen"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/omnixdp/platform/tree/main/packages/typegen#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/omnixdp/platform/issues"
|
|
14
|
+
},
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"bin": {
|
|
18
|
+
"omnixdp-typegen": "./dist/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"require": "./dist/index.js",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
37
|
+
"build": "pnpm run clean && tsc -p tsconfig.build.json",
|
|
38
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"prepublishOnly": "pnpm run build"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.7.4",
|
|
44
|
+
"typescript": "^5.6.3",
|
|
45
|
+
"vitest": "^4.1.5"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22.16.0"
|
|
49
|
+
}
|
|
50
|
+
}
|