@uzum-tech/design-tokens-resolver 0.0.0-stage → 1.0.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 +641 -2
- package/lib/cli.cjs +810 -0
- package/lib/cli.d.ts +1 -0
- package/lib/client/design.client.d.ts +7 -0
- package/lib/client/http.client.d.ts +13 -0
- package/lib/config/env.config.d.ts +7 -0
- package/lib/constants/cli.constants.d.ts +4 -0
- package/lib/constants/design.constants.d.ts +25 -0
- package/lib/constants/document.constants.d.ts +4 -0
- package/lib/constants/http.constants.d.ts +4 -0
- package/lib/constants/regular-expressions.constants.d.ts +6 -0
- package/lib/core/config-file.resolver.d.ts +9 -0
- package/lib/core/config.resolver.d.ts +6 -0
- package/lib/core/design-tokens-resolver.d.ts +12 -0
- package/lib/errors/resolver.error.d.ts +9 -0
- package/lib/index.cjs +755 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.mjs +726 -0
- package/lib/mappers/cli.mapper.d.ts +7 -0
- package/lib/mappers/document.mapper.d.ts +12 -0
- package/lib/mappers/lock.mapper.d.ts +6 -0
- package/lib/mappers/resolve.mapper.d.ts +6 -0
- package/lib/types/config.types.d.ts +50 -0
- package/lib/types/document.types.d.ts +65 -0
- package/lib/types/error.types.d.ts +17 -0
- package/lib/types/http.types.d.ts +8 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/types/release.types.d.ts +31 -0
- package/lib/types/resolve.types.d.ts +41 -0
- package/lib/utils/args.utils.d.ts +9 -0
- package/lib/utils/file.utils.d.ts +3 -0
- package/lib/utils/hash.utils.d.ts +3 -0
- package/lib/utils/resolve.utils.d.ts +6 -0
- package/lib/utils/url.utils.d.ts +4 -0
- package/package.json +78 -4
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ArgsUtils: () => ArgsUtils,
|
|
24
|
+
ConfigFileResolver: () => ConfigFileResolver,
|
|
25
|
+
ConfigResolver: () => ConfigResolver,
|
|
26
|
+
ConfigTypes: () => ConfigTypes,
|
|
27
|
+
DesignClient: () => DesignClient,
|
|
28
|
+
DesignTokensResolver: () => DesignTokensResolver,
|
|
29
|
+
DocumentMapper: () => DocumentMapper,
|
|
30
|
+
ErrorTypes: () => ErrorTypes,
|
|
31
|
+
FileUtils: () => FileUtils,
|
|
32
|
+
HashUtils: () => HashUtils,
|
|
33
|
+
HttpClient: () => HttpClient,
|
|
34
|
+
HttpTypes: () => HttpTypes,
|
|
35
|
+
LockMapper: () => LockMapper,
|
|
36
|
+
ResolveMapper: () => ResolveMapper,
|
|
37
|
+
ResolveUtils: () => ResolveUtils,
|
|
38
|
+
ResolverError: () => ResolverError,
|
|
39
|
+
UrlUtils: () => UrlUtils,
|
|
40
|
+
default: () => DesignTokensResolver
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(index_exports);
|
|
43
|
+
|
|
44
|
+
// src/constants/design.constants.ts
|
|
45
|
+
var designPaths = {
|
|
46
|
+
release: "/dynamic/design/admin/token-releases/details",
|
|
47
|
+
releasePayload: "/dynamic/design/admin/token-release-payloads/details",
|
|
48
|
+
channel: "/dynamic/design/admin/token-channels/details"
|
|
49
|
+
};
|
|
50
|
+
var engineBaseUrls = {
|
|
51
|
+
local: "http://localhost:3003/api/v1",
|
|
52
|
+
development: "https://esb-data-engine-int-dev.kapitalbank.uz/api/v1",
|
|
53
|
+
production: "https://data-engine-int.kapitalbank.uz/api/v1"
|
|
54
|
+
};
|
|
55
|
+
var designConfigFiles = [
|
|
56
|
+
"design-tokens.config.mjs",
|
|
57
|
+
"design-tokens.config.js",
|
|
58
|
+
"design-tokens.config.cjs",
|
|
59
|
+
"design-tokens.config.json"
|
|
60
|
+
];
|
|
61
|
+
var JSON_CONFIG_SUFFIX = ".json";
|
|
62
|
+
var designDefaults = {
|
|
63
|
+
env: "production",
|
|
64
|
+
requestTimeoutInMs: 3e4,
|
|
65
|
+
retryCount: 2,
|
|
66
|
+
retryDelayInMs: 500,
|
|
67
|
+
fileName: "tokens.json",
|
|
68
|
+
lockFileName: "tokens.lock.json",
|
|
69
|
+
authEnvHint: "DESIGN_TOKENS_API_KEY \u043B\u0438\u0431\u043E DESIGN_TOKENS_USERNAME \u0438 DESIGN_TOKENS_PASSWORD"
|
|
70
|
+
};
|
|
71
|
+
var JSON_INDENT = 2;
|
|
72
|
+
var LINE_BREAK = "\n";
|
|
73
|
+
var HASH_ALGORITHM = "sha256";
|
|
74
|
+
var HASH_ENCODING = "hex";
|
|
75
|
+
|
|
76
|
+
// src/types/error.types.ts
|
|
77
|
+
var ErrorTypes;
|
|
78
|
+
((ErrorTypes2) => {
|
|
79
|
+
let Code;
|
|
80
|
+
((Code2) => {
|
|
81
|
+
Code2["CONFIG_INVALID"] = "CONFIG_INVALID";
|
|
82
|
+
Code2["REQUEST_FAILED"] = "REQUEST_FAILED";
|
|
83
|
+
Code2["RELEASE_NOT_FOUND"] = "RELEASE_NOT_FOUND";
|
|
84
|
+
Code2["CHANNEL_EMPTY"] = "CHANNEL_EMPTY";
|
|
85
|
+
Code2["INTEGRITY_MISMATCH"] = "INTEGRITY_MISMATCH";
|
|
86
|
+
Code2["WRITE_FAILED"] = "WRITE_FAILED";
|
|
87
|
+
})(Code = ErrorTypes2.Code || (ErrorTypes2.Code = {}));
|
|
88
|
+
})(ErrorTypes || (ErrorTypes = {}));
|
|
89
|
+
|
|
90
|
+
// src/constants/regular-expressions.constants.ts
|
|
91
|
+
var regularExpressions = {
|
|
92
|
+
trailingSlashes: /\/+$/,
|
|
93
|
+
leadingSlashes: /^\/+/,
|
|
94
|
+
kebabSegment: /-([a-z])/g,
|
|
95
|
+
dtcgAlias: /^\{(.+)\}$/
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/utils/url.utils.ts
|
|
99
|
+
var UrlUtils = class {
|
|
100
|
+
static join(...parts) {
|
|
101
|
+
const [head, ...rest] = parts.filter(Boolean);
|
|
102
|
+
const tail = rest.map((part) => part.replace(regularExpressions.leadingSlashes, "").replace(regularExpressions.trailingSlashes, "")).filter(Boolean);
|
|
103
|
+
const base = (head ?? "").replace(regularExpressions.trailingSlashes, "");
|
|
104
|
+
return [base, ...tail].join("/");
|
|
105
|
+
}
|
|
106
|
+
static toQuery(params) {
|
|
107
|
+
const search = new URLSearchParams();
|
|
108
|
+
for (const key of Object.keys(params)) {
|
|
109
|
+
const value = params[key];
|
|
110
|
+
if (!value) continue;
|
|
111
|
+
search.set(key, String(value));
|
|
112
|
+
}
|
|
113
|
+
const query = search.toString();
|
|
114
|
+
return query ? `?${query}` : "";
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/constants/http.constants.ts
|
|
119
|
+
var httpHeaders = {
|
|
120
|
+
authorization: "authorization",
|
|
121
|
+
apiKey: "x-api-key"
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// src/errors/resolver.error.ts
|
|
125
|
+
var ResolverError = class _ResolverError extends Error {
|
|
126
|
+
code;
|
|
127
|
+
statusCode;
|
|
128
|
+
body;
|
|
129
|
+
constructor(message, options) {
|
|
130
|
+
super(message, {
|
|
131
|
+
cause: options.cause
|
|
132
|
+
});
|
|
133
|
+
this.name = "ResolverError";
|
|
134
|
+
this.code = options.code;
|
|
135
|
+
this.statusCode = options.statusCode;
|
|
136
|
+
this.body = options.body;
|
|
137
|
+
Error.captureStackTrace?.(this, _ResolverError);
|
|
138
|
+
}
|
|
139
|
+
static is(error, code) {
|
|
140
|
+
const isResolverError = error instanceof _ResolverError;
|
|
141
|
+
return code ? isResolverError && error.code === code : isResolverError;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// src/types/http.types.ts
|
|
146
|
+
var HttpTypes;
|
|
147
|
+
((HttpTypes2) => {
|
|
148
|
+
let Status;
|
|
149
|
+
((Status2) => {
|
|
150
|
+
Status2[Status2["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
|
151
|
+
Status2[Status2["NOT_FOUND"] = 404] = "NOT_FOUND";
|
|
152
|
+
Status2[Status2["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
|
|
153
|
+
Status2[Status2["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
|
|
154
|
+
})(Status = HttpTypes2.Status || (HttpTypes2.Status = {}));
|
|
155
|
+
})(HttpTypes || (HttpTypes = {}));
|
|
156
|
+
|
|
157
|
+
// src/client/http.client.ts
|
|
158
|
+
var HttpClient = class _HttpClient {
|
|
159
|
+
constructor(config) {
|
|
160
|
+
this.config = config;
|
|
161
|
+
}
|
|
162
|
+
static isRetryable(status) {
|
|
163
|
+
return status === HttpTypes.Status.TOO_MANY_REQUESTS || status >= HttpTypes.Status.INTERNAL_SERVER_ERROR;
|
|
164
|
+
}
|
|
165
|
+
static wait(delayInMs) {
|
|
166
|
+
return new Promise((resolve2) => {
|
|
167
|
+
setTimeout(resolve2, delayInMs);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async getJson(path, notFoundCode) {
|
|
171
|
+
const response = await this.request(path, notFoundCode);
|
|
172
|
+
const body = await response.json();
|
|
173
|
+
return body.data ?? body;
|
|
174
|
+
}
|
|
175
|
+
async request(path, notFoundCode = ErrorTypes.Code.REQUEST_FAILED) {
|
|
176
|
+
const url = UrlUtils.join(this.config.baseUrl, path);
|
|
177
|
+
const response = await this.sendWithRetries(url);
|
|
178
|
+
if (response.ok) return response;
|
|
179
|
+
const status = response.status;
|
|
180
|
+
throw new ResolverError(
|
|
181
|
+
`Design tokens request failed: ${status} ${url}`,
|
|
182
|
+
{
|
|
183
|
+
code: status === HttpTypes.Status.NOT_FOUND ? notFoundCode : ErrorTypes.Code.REQUEST_FAILED,
|
|
184
|
+
statusCode: status,
|
|
185
|
+
body: await response.text()
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
async sendWithRetries(url) {
|
|
190
|
+
let attempt = 0;
|
|
191
|
+
while (true) {
|
|
192
|
+
const isLast = attempt >= this.config.retryCount;
|
|
193
|
+
try {
|
|
194
|
+
const response = await this.send(url);
|
|
195
|
+
if (isLast || !_HttpClient.isRetryable(response.status)) return response;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (isLast) throw error;
|
|
198
|
+
}
|
|
199
|
+
attempt += 1;
|
|
200
|
+
await _HttpClient.wait(this.config.retryDelayInMs * attempt);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async send(url) {
|
|
204
|
+
try {
|
|
205
|
+
return await fetch(url, {
|
|
206
|
+
headers: this.toHeaders(),
|
|
207
|
+
signal: AbortSignal.timeout(this.config.requestTimeoutInMs)
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
throw new ResolverError(
|
|
211
|
+
`Design tokens request failed: ${url}`,
|
|
212
|
+
{
|
|
213
|
+
code: ErrorTypes.Code.REQUEST_FAILED,
|
|
214
|
+
cause: error
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
toHeaders() {
|
|
220
|
+
const headers = {};
|
|
221
|
+
if (this.config.apiKey) {
|
|
222
|
+
headers[httpHeaders.apiKey] = this.config.apiKey;
|
|
223
|
+
}
|
|
224
|
+
if (this.config.auth) {
|
|
225
|
+
const credentials = Buffer.from(`${this.config.auth.username}:${this.config.auth.password}`).toString("base64");
|
|
226
|
+
headers[httpHeaders.authorization] = `Basic ${credentials}`;
|
|
227
|
+
}
|
|
228
|
+
return headers;
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// src/client/design.client.ts
|
|
233
|
+
var DesignClient = class extends HttpClient {
|
|
234
|
+
async getRelease(version) {
|
|
235
|
+
return this.getJson(
|
|
236
|
+
designPaths.release + UrlUtils.toQuery({
|
|
237
|
+
version
|
|
238
|
+
}),
|
|
239
|
+
ErrorTypes.Code.RELEASE_NOT_FOUND
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
async getChannel(slug) {
|
|
243
|
+
return this.getJson(
|
|
244
|
+
designPaths.channel + UrlUtils.toQuery({
|
|
245
|
+
slug
|
|
246
|
+
}),
|
|
247
|
+
ErrorTypes.Code.RELEASE_NOT_FOUND
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
async getDocument(version) {
|
|
251
|
+
const payload = await this.getJson(
|
|
252
|
+
designPaths.releasePayload + UrlUtils.toQuery({
|
|
253
|
+
version
|
|
254
|
+
}),
|
|
255
|
+
ErrorTypes.Code.RELEASE_NOT_FOUND
|
|
256
|
+
);
|
|
257
|
+
return payload.document;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/core/config.resolver.ts
|
|
262
|
+
var ConfigResolver = class _ConfigResolver {
|
|
263
|
+
static withDefaults(config) {
|
|
264
|
+
const baseUrl = _ConfigResolver.toBaseUrl(config);
|
|
265
|
+
_ConfigResolver.validateAuth(config);
|
|
266
|
+
return {
|
|
267
|
+
baseUrl,
|
|
268
|
+
auth: config.auth ?? null,
|
|
269
|
+
apiKey: config.apiKey ?? null,
|
|
270
|
+
requestTimeoutInMs: config.requestTimeoutInMs ?? designDefaults.requestTimeoutInMs,
|
|
271
|
+
retryCount: config.retryCount ?? designDefaults.retryCount,
|
|
272
|
+
retryDelayInMs: config.retryDelayInMs ?? designDefaults.retryDelayInMs
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
static toBaseUrl(config) {
|
|
276
|
+
if (config.baseUrl) return config.baseUrl;
|
|
277
|
+
const env = config.env ?? designDefaults.env;
|
|
278
|
+
const baseUrl = engineBaseUrls[env];
|
|
279
|
+
if (!baseUrl) {
|
|
280
|
+
throw new ResolverError(
|
|
281
|
+
`Unknown environment "${env}". Known: ${Object.keys(engineBaseUrls).join(", ")}`,
|
|
282
|
+
{
|
|
283
|
+
code: ErrorTypes.Code.CONFIG_INVALID
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
return baseUrl;
|
|
288
|
+
}
|
|
289
|
+
static validateAuth(config) {
|
|
290
|
+
if (!config.auth) return;
|
|
291
|
+
if (config.auth.username && config.auth.password) return;
|
|
292
|
+
throw new ResolverError(
|
|
293
|
+
'Design tokens config is invalid: both "username" and "password" are required for basic auth',
|
|
294
|
+
{
|
|
295
|
+
code: ErrorTypes.Code.CONFIG_INVALID
|
|
296
|
+
}
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// src/core/config-file.resolver.ts
|
|
302
|
+
var import_node_fs = require("node:fs");
|
|
303
|
+
var import_node_path = require("node:path");
|
|
304
|
+
var import_node_url = require("node:url");
|
|
305
|
+
var ConfigFileResolver = class _ConfigFileResolver {
|
|
306
|
+
static async load(startPath) {
|
|
307
|
+
const path = _ConfigFileResolver.find(startPath);
|
|
308
|
+
if (!path) return null;
|
|
309
|
+
return {
|
|
310
|
+
path,
|
|
311
|
+
config: await _ConfigFileResolver.read(path)
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
static find(startPath) {
|
|
315
|
+
let current = (0, import_node_path.resolve)(startPath);
|
|
316
|
+
const stopAt = (0, import_node_path.parse)(current).root;
|
|
317
|
+
let found = _ConfigFileResolver.findIn(current);
|
|
318
|
+
while (!found && current !== stopAt) {
|
|
319
|
+
current = (0, import_node_path.dirname)(current);
|
|
320
|
+
found = _ConfigFileResolver.findIn(current);
|
|
321
|
+
}
|
|
322
|
+
return found;
|
|
323
|
+
}
|
|
324
|
+
static findIn(directory) {
|
|
325
|
+
for (const name of designConfigFiles) {
|
|
326
|
+
const path = (0, import_node_path.join)(directory, name);
|
|
327
|
+
if ((0, import_node_fs.existsSync)(path)) return path;
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
static async read(path) {
|
|
332
|
+
try {
|
|
333
|
+
return path.endsWith(JSON_CONFIG_SUFFIX) ? _ConfigFileResolver.readJson(path) : await _ConfigFileResolver.readModule(path);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
if (ResolverError.is(error)) throw error;
|
|
336
|
+
throw new ResolverError(
|
|
337
|
+
`Failed to read design tokens config at ${path}`,
|
|
338
|
+
{
|
|
339
|
+
code: ErrorTypes.Code.CONFIG_INVALID,
|
|
340
|
+
cause: error
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
static readJson(path) {
|
|
346
|
+
return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
347
|
+
}
|
|
348
|
+
static async readModule(path) {
|
|
349
|
+
const imported = await import((0, import_node_url.pathToFileURL)(path).href);
|
|
350
|
+
const exported = imported.default;
|
|
351
|
+
if (!exported) {
|
|
352
|
+
throw new ResolverError(
|
|
353
|
+
`Design tokens config at ${path} has no default export`,
|
|
354
|
+
{
|
|
355
|
+
code: ErrorTypes.Code.CONFIG_INVALID
|
|
356
|
+
}
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return typeof exported === "function" ? await exported() : exported;
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
// src/constants/document.constants.ts
|
|
364
|
+
var DOCUMENT_PATH_SEPARATOR = ".";
|
|
365
|
+
var DOCUMENT_VALUE_KEY_SEPARATOR = ":";
|
|
366
|
+
var DOCUMENT_MAX_ALIAS_DEPTH = 20;
|
|
367
|
+
var DOCUMENT_ALIAS_VALUE_GROUP = 1;
|
|
368
|
+
|
|
369
|
+
// src/types/config.types.ts
|
|
370
|
+
var ConfigTypes;
|
|
371
|
+
((ConfigTypes2) => {
|
|
372
|
+
let Format;
|
|
373
|
+
((Format2) => {
|
|
374
|
+
Format2["FLAT"] = "FLAT";
|
|
375
|
+
Format2["DTCG"] = "DTCG";
|
|
376
|
+
})(Format = ConfigTypes2.Format || (ConfigTypes2.Format = {}));
|
|
377
|
+
let Env;
|
|
378
|
+
((Env2) => {
|
|
379
|
+
Env2["LOCAL"] = "local";
|
|
380
|
+
Env2["DEVELOPMENT"] = "development";
|
|
381
|
+
Env2["PRODUCTION"] = "production";
|
|
382
|
+
})(Env = ConfigTypes2.Env || (ConfigTypes2.Env = {}));
|
|
383
|
+
})(ConfigTypes || (ConfigTypes = {}));
|
|
384
|
+
|
|
385
|
+
// src/mappers/resolve.mapper.ts
|
|
386
|
+
var ResolveMapper = class _ResolveMapper {
|
|
387
|
+
static toScope(document) {
|
|
388
|
+
const modes = {};
|
|
389
|
+
for (const collection of document.collections) {
|
|
390
|
+
const defaultMode = collection.modes.find((mode) => mode.isDefault) ?? collection.modes[0];
|
|
391
|
+
if (!defaultMode) continue;
|
|
392
|
+
modes[collection.slug] = {
|
|
393
|
+
slugs: new Set(collection.modes.map((mode) => mode.slug)),
|
|
394
|
+
defaultSlug: defaultMode.slug
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
const literals = {};
|
|
398
|
+
for (const token of document.tokens) {
|
|
399
|
+
literals[_ResolveMapper.toLiteralKey(token.collection, token.mode, token.path)] = token.$value;
|
|
400
|
+
}
|
|
401
|
+
return {
|
|
402
|
+
literals,
|
|
403
|
+
modes
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
static toLiteralKey(collection, mode, path) {
|
|
407
|
+
return [collection, mode, path].join(DOCUMENT_VALUE_KEY_SEPARATOR);
|
|
408
|
+
}
|
|
409
|
+
static toTarget(alias, state) {
|
|
410
|
+
const separatorIndex = alias.indexOf(DOCUMENT_PATH_SEPARATOR);
|
|
411
|
+
if (separatorIndex <= 0) return null;
|
|
412
|
+
const collection = alias.slice(0, separatorIndex);
|
|
413
|
+
const collectionModes = state.scope.modes[collection];
|
|
414
|
+
if (!collectionModes) return null;
|
|
415
|
+
return {
|
|
416
|
+
path: alias.slice(separatorIndex + 1),
|
|
417
|
+
point: {
|
|
418
|
+
collection,
|
|
419
|
+
mode: collectionModes.slugs.has(state.point.mode) ? state.point.mode : collectionModes.defaultSlug
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/utils/resolve.utils.ts
|
|
426
|
+
var ResolveUtils = class _ResolveUtils {
|
|
427
|
+
static resolve(value, state) {
|
|
428
|
+
if (state.depth >= DOCUMENT_MAX_ALIAS_DEPTH) return value;
|
|
429
|
+
if (typeof value === "string") return _ResolveUtils.resolveAlias(value, state);
|
|
430
|
+
const deeper = {
|
|
431
|
+
...state,
|
|
432
|
+
depth: state.depth + 1
|
|
433
|
+
};
|
|
434
|
+
if (Array.isArray(value)) {
|
|
435
|
+
return value.map((item) => _ResolveUtils.resolve(item, deeper));
|
|
436
|
+
}
|
|
437
|
+
if (value === null || typeof value !== "object") return value;
|
|
438
|
+
const result = {};
|
|
439
|
+
for (const key of Object.keys(value)) {
|
|
440
|
+
result[key] = _ResolveUtils.resolve(value[key], deeper);
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
static parseAlias(value) {
|
|
445
|
+
const match = value.trim().match(regularExpressions.dtcgAlias);
|
|
446
|
+
return match ? match[DOCUMENT_ALIAS_VALUE_GROUP].trim() : null;
|
|
447
|
+
}
|
|
448
|
+
static resolveAlias(value, state) {
|
|
449
|
+
const alias = _ResolveUtils.parseAlias(value);
|
|
450
|
+
if (!alias) return value;
|
|
451
|
+
const target = ResolveMapper.toTarget(alias, state);
|
|
452
|
+
if (!target) return value;
|
|
453
|
+
const literal = state.scope.literals[ResolveMapper.toLiteralKey(
|
|
454
|
+
target.point.collection,
|
|
455
|
+
target.point.mode,
|
|
456
|
+
target.path
|
|
457
|
+
)];
|
|
458
|
+
if (literal === void 0) return value;
|
|
459
|
+
return _ResolveUtils.resolve(literal, {
|
|
460
|
+
scope: state.scope,
|
|
461
|
+
point: target.point,
|
|
462
|
+
depth: state.depth + 1
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// src/mappers/document.mapper.ts
|
|
468
|
+
var DocumentMapper = class _DocumentMapper {
|
|
469
|
+
static toJson(canonical, query) {
|
|
470
|
+
if (_DocumentMapper.isCanonical(query)) return canonical;
|
|
471
|
+
const document = JSON.parse(canonical);
|
|
472
|
+
const output = _DocumentMapper.toOutput(document, query);
|
|
473
|
+
return `${JSON.stringify(output, null, JSON_INDENT)}${LINE_BREAK}`;
|
|
474
|
+
}
|
|
475
|
+
static toOutput(document, query) {
|
|
476
|
+
if (query.format !== ConfigTypes.Format.DTCG) {
|
|
477
|
+
return _DocumentMapper.toFlatDocument(document, query);
|
|
478
|
+
}
|
|
479
|
+
if (query.collection) return _DocumentMapper.toDtcg(document, query);
|
|
480
|
+
const trees = {};
|
|
481
|
+
for (const collection of document.collections) {
|
|
482
|
+
trees[collection.slug] = _DocumentMapper.toDtcg(document, {
|
|
483
|
+
...query,
|
|
484
|
+
collection: collection.slug
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
return trees;
|
|
488
|
+
}
|
|
489
|
+
static isCanonical(query) {
|
|
490
|
+
return !query.collection && !query.resolve && (!query.format || query.format === ConfigTypes.Format.FLAT);
|
|
491
|
+
}
|
|
492
|
+
static toFlatDocument(document, query) {
|
|
493
|
+
return {
|
|
494
|
+
schemaVersion: document.schemaVersion,
|
|
495
|
+
collections: query.collection ? document.collections.filter((item) => item.slug === query.collection) : document.collections,
|
|
496
|
+
tokens: _DocumentMapper.toTokens(document, query)
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
static toTokens(document, query) {
|
|
500
|
+
const tokens = query.collection ? document.tokens.filter((token) => token.collection === query.collection) : document.tokens;
|
|
501
|
+
if (!query.resolve) return tokens;
|
|
502
|
+
const scope = ResolveMapper.toScope(document);
|
|
503
|
+
return tokens.map((token) => ({
|
|
504
|
+
...token,
|
|
505
|
+
$value: ResolveUtils.resolve(token.$value, {
|
|
506
|
+
scope,
|
|
507
|
+
point: {
|
|
508
|
+
collection: token.collection,
|
|
509
|
+
mode: token.mode
|
|
510
|
+
},
|
|
511
|
+
depth: 0
|
|
512
|
+
})
|
|
513
|
+
}));
|
|
514
|
+
}
|
|
515
|
+
static toDtcg(document, query) {
|
|
516
|
+
const collection = document.collections.find(
|
|
517
|
+
(item) => item.slug === query.collection
|
|
518
|
+
);
|
|
519
|
+
if (!collection) return {};
|
|
520
|
+
const byMode = _DocumentMapper.groupByMode(
|
|
521
|
+
_DocumentMapper.toTokens(document, query)
|
|
522
|
+
);
|
|
523
|
+
if (collection.modes.length <= 1) {
|
|
524
|
+
const [mode] = collection.modes;
|
|
525
|
+
return _DocumentMapper.toTree(byMode[mode?.slug] ?? []);
|
|
526
|
+
}
|
|
527
|
+
const root = {};
|
|
528
|
+
for (const mode of collection.modes) {
|
|
529
|
+
root[mode.name] = _DocumentMapper.toTree(byMode[mode.slug] ?? []);
|
|
530
|
+
}
|
|
531
|
+
return root;
|
|
532
|
+
}
|
|
533
|
+
static groupByMode(tokens) {
|
|
534
|
+
const grouped = {};
|
|
535
|
+
for (const token of tokens) {
|
|
536
|
+
grouped[token.mode] ??= [];
|
|
537
|
+
grouped[token.mode].push(token);
|
|
538
|
+
}
|
|
539
|
+
return grouped;
|
|
540
|
+
}
|
|
541
|
+
static toTree(tokens) {
|
|
542
|
+
const root = {};
|
|
543
|
+
for (const token of tokens) {
|
|
544
|
+
const segments = token.path.split(DOCUMENT_PATH_SEPARATOR);
|
|
545
|
+
const leaf = segments.pop();
|
|
546
|
+
if (!leaf) continue;
|
|
547
|
+
let cursor = root;
|
|
548
|
+
for (const segment of segments) {
|
|
549
|
+
const next = cursor[segment];
|
|
550
|
+
if (!next || typeof next !== "object") {
|
|
551
|
+
cursor[segment] = {};
|
|
552
|
+
}
|
|
553
|
+
cursor = cursor[segment];
|
|
554
|
+
}
|
|
555
|
+
cursor[leaf] = {
|
|
556
|
+
$type: token.$type,
|
|
557
|
+
$value: token.$value
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return root;
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// src/mappers/lock.mapper.ts
|
|
565
|
+
var LockMapper = class {
|
|
566
|
+
static toLock(release, source) {
|
|
567
|
+
return {
|
|
568
|
+
version: release.version,
|
|
569
|
+
contentHash: release.contentHash,
|
|
570
|
+
baseUrl: source.baseUrl,
|
|
571
|
+
byteSize: release.byteSize,
|
|
572
|
+
publishedAt: release.createdAt,
|
|
573
|
+
publishedBy: release.publishedBy,
|
|
574
|
+
sourceLabel: release.sourceLabel
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
static toJson(lock) {
|
|
578
|
+
return `${JSON.stringify(lock, null, JSON_INDENT)}${LINE_BREAK}`;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
// src/utils/file.utils.ts
|
|
583
|
+
var import_node_fs2 = require("node:fs");
|
|
584
|
+
var import_node_path2 = require("node:path");
|
|
585
|
+
var FileUtils = class {
|
|
586
|
+
static write(outputPath, fileName, body) {
|
|
587
|
+
const target = (0, import_node_path2.join)(outputPath, fileName);
|
|
588
|
+
try {
|
|
589
|
+
(0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(target), {
|
|
590
|
+
recursive: true
|
|
591
|
+
});
|
|
592
|
+
(0, import_node_fs2.writeFileSync)(target, body, "utf8");
|
|
593
|
+
return target;
|
|
594
|
+
} catch (error) {
|
|
595
|
+
throw new ResolverError(
|
|
596
|
+
`Failed to write design tokens to ${target}`,
|
|
597
|
+
{
|
|
598
|
+
code: ErrorTypes.Code.WRITE_FAILED,
|
|
599
|
+
cause: error
|
|
600
|
+
}
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
// src/utils/hash.utils.ts
|
|
607
|
+
var import_node_crypto = require("node:crypto");
|
|
608
|
+
var HashUtils = class {
|
|
609
|
+
static toContentHash(body) {
|
|
610
|
+
return (0, import_node_crypto.createHash)(HASH_ALGORITHM).update(body, "utf8").digest(HASH_ENCODING);
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// src/core/design-tokens-resolver.ts
|
|
615
|
+
var DesignTokensResolver = class _DesignTokensResolver {
|
|
616
|
+
client;
|
|
617
|
+
config;
|
|
618
|
+
constructor(config) {
|
|
619
|
+
this.config = ConfigResolver.withDefaults(config);
|
|
620
|
+
this.client = new DesignClient(this.config);
|
|
621
|
+
}
|
|
622
|
+
static checkIntegrity(release, canonical) {
|
|
623
|
+
const actual = HashUtils.toContentHash(canonical);
|
|
624
|
+
if (actual === release.contentHash) return;
|
|
625
|
+
throw new ResolverError(
|
|
626
|
+
`Design tokens integrity check failed for ${release.version}: expected ${release.contentHash}, got ${actual}`,
|
|
627
|
+
{
|
|
628
|
+
code: ErrorTypes.Code.INTEGRITY_MISMATCH
|
|
629
|
+
}
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
getSource() {
|
|
633
|
+
return {
|
|
634
|
+
baseUrl: this.config.baseUrl
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
async resolveVersion(target) {
|
|
638
|
+
if (target.version) return target.version;
|
|
639
|
+
if (!target.channel) {
|
|
640
|
+
throw new ResolverError(
|
|
641
|
+
'Either "version" or "channel" must be provided',
|
|
642
|
+
{
|
|
643
|
+
code: ErrorTypes.Code.CONFIG_INVALID
|
|
644
|
+
}
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
const channel = await this.client.getChannel(target.channel);
|
|
648
|
+
if (channel.version) return channel.version;
|
|
649
|
+
throw new ResolverError(
|
|
650
|
+
`Channel "${target.channel}" does not point at any version yet`,
|
|
651
|
+
{
|
|
652
|
+
code: ErrorTypes.Code.CHANNEL_EMPTY
|
|
653
|
+
}
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
async fetchVerified(target) {
|
|
657
|
+
const version = await this.resolveVersion(target);
|
|
658
|
+
const release = await this.client.getRelease(version);
|
|
659
|
+
const canonical = await this.client.getDocument(version);
|
|
660
|
+
_DesignTokensResolver.checkIntegrity(release, canonical);
|
|
661
|
+
return {
|
|
662
|
+
release,
|
|
663
|
+
body: DocumentMapper.toJson(canonical, target)
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
async resolve(request) {
|
|
667
|
+
const verified = await this.fetchVerified(request);
|
|
668
|
+
const tokensPath = FileUtils.write(
|
|
669
|
+
request.outputPath,
|
|
670
|
+
request.fileName ?? designDefaults.fileName,
|
|
671
|
+
verified.body
|
|
672
|
+
);
|
|
673
|
+
const lockPath = request.lock === false ? null : FileUtils.write(
|
|
674
|
+
request.outputPath,
|
|
675
|
+
designDefaults.lockFileName,
|
|
676
|
+
LockMapper.toJson(LockMapper.toLock(verified.release, this.getSource()))
|
|
677
|
+
);
|
|
678
|
+
return {
|
|
679
|
+
release: verified.release,
|
|
680
|
+
source: this.getSource(),
|
|
681
|
+
tokensPath,
|
|
682
|
+
lockPath,
|
|
683
|
+
bytesWritten: Buffer.byteLength(verified.body, "utf8")
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// src/constants/cli.constants.ts
|
|
689
|
+
var FLAG_PREFIX = "--";
|
|
690
|
+
var NEGATIVE_FLAG_PREFIX = "--no-";
|
|
691
|
+
|
|
692
|
+
// src/utils/args.utils.ts
|
|
693
|
+
var ArgsUtils = class _ArgsUtils {
|
|
694
|
+
static parse(args) {
|
|
695
|
+
const parsed = {};
|
|
696
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
697
|
+
const token = args[index];
|
|
698
|
+
if (!token?.startsWith(FLAG_PREFIX)) continue;
|
|
699
|
+
if (token.startsWith(NEGATIVE_FLAG_PREFIX)) {
|
|
700
|
+
parsed[_ArgsUtils.toKey(token.slice(NEGATIVE_FLAG_PREFIX.length))] = false;
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
const name = _ArgsUtils.toKey(token.slice(FLAG_PREFIX.length));
|
|
704
|
+
const next = args[index + 1];
|
|
705
|
+
if (!next || next.startsWith(FLAG_PREFIX)) {
|
|
706
|
+
parsed[name] = true;
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
parsed[name] = next;
|
|
710
|
+
index += 1;
|
|
711
|
+
}
|
|
712
|
+
return parsed;
|
|
713
|
+
}
|
|
714
|
+
static toFormat(value) {
|
|
715
|
+
if (typeof value !== "string") return;
|
|
716
|
+
const normalized = value.trim().toUpperCase();
|
|
717
|
+
return normalized === ConfigTypes.Format.DTCG ? ConfigTypes.Format.DTCG : ConfigTypes.Format.FLAT;
|
|
718
|
+
}
|
|
719
|
+
static toEnv(value) {
|
|
720
|
+
return _ArgsUtils.toNormalized(value);
|
|
721
|
+
}
|
|
722
|
+
static toText(value) {
|
|
723
|
+
return typeof value === "string" ? value : void 0;
|
|
724
|
+
}
|
|
725
|
+
static toNormalized(value) {
|
|
726
|
+
if (typeof value !== "string") return;
|
|
727
|
+
return value.trim().toLowerCase();
|
|
728
|
+
}
|
|
729
|
+
static toKey(name) {
|
|
730
|
+
return name.replace(
|
|
731
|
+
regularExpressions.kebabSegment,
|
|
732
|
+
(_, letter) => letter.toUpperCase()
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
737
|
+
0 && (module.exports = {
|
|
738
|
+
ArgsUtils,
|
|
739
|
+
ConfigFileResolver,
|
|
740
|
+
ConfigResolver,
|
|
741
|
+
ConfigTypes,
|
|
742
|
+
DesignClient,
|
|
743
|
+
DesignTokensResolver,
|
|
744
|
+
DocumentMapper,
|
|
745
|
+
ErrorTypes,
|
|
746
|
+
FileUtils,
|
|
747
|
+
HashUtils,
|
|
748
|
+
HttpClient,
|
|
749
|
+
HttpTypes,
|
|
750
|
+
LockMapper,
|
|
751
|
+
ResolveMapper,
|
|
752
|
+
ResolveUtils,
|
|
753
|
+
ResolverError,
|
|
754
|
+
UrlUtils
|
|
755
|
+
});
|