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