@vettvangur/design-system 0.0.21 → 0.0.22
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/dist/generate-razor-C60EPVNF.js +483 -0
- package/dist/generate-razor-CQcgq_a1.js +483 -0
- package/dist/generate-razor-CsWe4cvc.js +483 -0
- package/dist/generate-razor-Cw5q4SCA.js +483 -0
- package/dist/generate-razor-DQsJycIz.js +483 -0
- package/dist/generate-razor-Dq9lRRvR.js +501 -0
- package/dist/generate-razor-Dx5voqJG.js +483 -0
- package/dist/generate-tailwind-BdXcObSb.js +1335 -0
- package/dist/generate-tailwind-COaMMcky.js +1335 -0
- package/dist/generate-tailwind-DIkR3ZFl.js +1334 -0
- package/dist/generate-tailwind-DLcqQBa9.js +1342 -0
- package/dist/generate-tailwind-DsVzILdL.js +1334 -0
- package/dist/generate-tailwind-Oj-gHW6_.js +1335 -0
- package/dist/index.esm.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,1335 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs$1 from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import require$$0 from 'fs';
|
|
6
|
+
import require$$1 from 'path';
|
|
7
|
+
import require$$2 from 'os';
|
|
8
|
+
import require$$3 from 'crypto';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
import process$1 from 'node:process';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Return a list of button names from an exporter payload.
|
|
15
|
+
* @param {{components?: Record<string, any>}} payload
|
|
16
|
+
* @param {{stripPrefix?: boolean, sort?: boolean}} opts
|
|
17
|
+
* @returns {string[]}
|
|
18
|
+
*/
|
|
19
|
+
function parseButtons(payload, {
|
|
20
|
+
stripPrefix = false,
|
|
21
|
+
sort = true
|
|
22
|
+
} = {}) {
|
|
23
|
+
const src = payload?.components || {};
|
|
24
|
+
let names = Object.keys(src).filter(k => k.startsWith('button-'));
|
|
25
|
+
if (stripPrefix) names = names.map(k => k.slice('button-'.length));
|
|
26
|
+
if (sort) names.sort();
|
|
27
|
+
return names;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var config$1 = {};
|
|
31
|
+
|
|
32
|
+
var main$1 = {exports: {}};
|
|
33
|
+
|
|
34
|
+
var name = "dotenv";
|
|
35
|
+
var version = "16.4.5";
|
|
36
|
+
var description = "Loads environment variables from .env file";
|
|
37
|
+
var main = "lib/main.js";
|
|
38
|
+
var types = "lib/main.d.ts";
|
|
39
|
+
var exports = {
|
|
40
|
+
".": {
|
|
41
|
+
types: "./lib/main.d.ts",
|
|
42
|
+
require: "./lib/main.js",
|
|
43
|
+
"default": "./lib/main.js"
|
|
44
|
+
},
|
|
45
|
+
"./config": "./config.js",
|
|
46
|
+
"./config.js": "./config.js",
|
|
47
|
+
"./lib/env-options": "./lib/env-options.js",
|
|
48
|
+
"./lib/env-options.js": "./lib/env-options.js",
|
|
49
|
+
"./lib/cli-options": "./lib/cli-options.js",
|
|
50
|
+
"./lib/cli-options.js": "./lib/cli-options.js",
|
|
51
|
+
"./package.json": "./package.json"
|
|
52
|
+
};
|
|
53
|
+
var scripts = {
|
|
54
|
+
"dts-check": "tsc --project tests/types/tsconfig.json",
|
|
55
|
+
lint: "standard",
|
|
56
|
+
"lint-readme": "standard-markdown",
|
|
57
|
+
pretest: "npm run lint && npm run dts-check",
|
|
58
|
+
test: "tap tests/*.js --100 -Rspec",
|
|
59
|
+
"test:coverage": "tap --coverage-report=lcov",
|
|
60
|
+
prerelease: "npm test",
|
|
61
|
+
release: "standard-version"
|
|
62
|
+
};
|
|
63
|
+
var repository = {
|
|
64
|
+
type: "git",
|
|
65
|
+
url: "git://github.com/motdotla/dotenv.git"
|
|
66
|
+
};
|
|
67
|
+
var funding = "https://dotenvx.com";
|
|
68
|
+
var keywords = [
|
|
69
|
+
"dotenv",
|
|
70
|
+
"env",
|
|
71
|
+
".env",
|
|
72
|
+
"environment",
|
|
73
|
+
"variables",
|
|
74
|
+
"config",
|
|
75
|
+
"settings"
|
|
76
|
+
];
|
|
77
|
+
var readmeFilename = "README.md";
|
|
78
|
+
var license = "BSD-2-Clause";
|
|
79
|
+
var devDependencies = {
|
|
80
|
+
"@definitelytyped/dtslint": "^0.0.133",
|
|
81
|
+
"@types/node": "^18.11.3",
|
|
82
|
+
decache: "^4.6.1",
|
|
83
|
+
sinon: "^14.0.1",
|
|
84
|
+
standard: "^17.0.0",
|
|
85
|
+
"standard-markdown": "^7.1.0",
|
|
86
|
+
"standard-version": "^9.5.0",
|
|
87
|
+
tap: "^16.3.0",
|
|
88
|
+
tar: "^6.1.11",
|
|
89
|
+
typescript: "^4.8.4"
|
|
90
|
+
};
|
|
91
|
+
var engines = {
|
|
92
|
+
node: ">=12"
|
|
93
|
+
};
|
|
94
|
+
var browser = {
|
|
95
|
+
fs: false
|
|
96
|
+
};
|
|
97
|
+
var require$$4 = {
|
|
98
|
+
name: name,
|
|
99
|
+
version: version,
|
|
100
|
+
description: description,
|
|
101
|
+
main: main,
|
|
102
|
+
types: types,
|
|
103
|
+
exports: exports,
|
|
104
|
+
scripts: scripts,
|
|
105
|
+
repository: repository,
|
|
106
|
+
funding: funding,
|
|
107
|
+
keywords: keywords,
|
|
108
|
+
readmeFilename: readmeFilename,
|
|
109
|
+
license: license,
|
|
110
|
+
devDependencies: devDependencies,
|
|
111
|
+
engines: engines,
|
|
112
|
+
browser: browser
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
var hasRequiredMain;
|
|
116
|
+
function requireMain() {
|
|
117
|
+
if (hasRequiredMain) return main$1.exports;
|
|
118
|
+
hasRequiredMain = 1;
|
|
119
|
+
const fs = require$$0;
|
|
120
|
+
const path = require$$1;
|
|
121
|
+
const os = require$$2;
|
|
122
|
+
const crypto = require$$3;
|
|
123
|
+
const packageJson = require$$4;
|
|
124
|
+
const version = packageJson.version;
|
|
125
|
+
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
126
|
+
|
|
127
|
+
// Parse src into an Object
|
|
128
|
+
function parse(src) {
|
|
129
|
+
const obj = {};
|
|
130
|
+
|
|
131
|
+
// Convert buffer to string
|
|
132
|
+
let lines = src.toString();
|
|
133
|
+
|
|
134
|
+
// Convert line breaks to same format
|
|
135
|
+
lines = lines.replace(/\r\n?/mg, '\n');
|
|
136
|
+
let match;
|
|
137
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
138
|
+
const key = match[1];
|
|
139
|
+
|
|
140
|
+
// Default undefined or null to empty string
|
|
141
|
+
let value = match[2] || '';
|
|
142
|
+
|
|
143
|
+
// Remove whitespace
|
|
144
|
+
value = value.trim();
|
|
145
|
+
|
|
146
|
+
// Check if double quoted
|
|
147
|
+
const maybeQuote = value[0];
|
|
148
|
+
|
|
149
|
+
// Remove surrounding quotes
|
|
150
|
+
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2');
|
|
151
|
+
|
|
152
|
+
// Expand newlines if double quoted
|
|
153
|
+
if (maybeQuote === '"') {
|
|
154
|
+
value = value.replace(/\\n/g, '\n');
|
|
155
|
+
value = value.replace(/\\r/g, '\r');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Add to object
|
|
159
|
+
obj[key] = value;
|
|
160
|
+
}
|
|
161
|
+
return obj;
|
|
162
|
+
}
|
|
163
|
+
function _parseVault(options) {
|
|
164
|
+
const vaultPath = _vaultPath(options);
|
|
165
|
+
|
|
166
|
+
// Parse .env.vault
|
|
167
|
+
const result = DotenvModule.configDotenv({
|
|
168
|
+
path: vaultPath
|
|
169
|
+
});
|
|
170
|
+
if (!result.parsed) {
|
|
171
|
+
const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
172
|
+
err.code = 'MISSING_DATA';
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// handle scenario for comma separated keys - for use with key rotation
|
|
177
|
+
// example: DOTENV_KEY="dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=prod,dotenv://:key_7890@dotenvx.com/vault/.env.vault?environment=prod"
|
|
178
|
+
const keys = _dotenvKey(options).split(',');
|
|
179
|
+
const length = keys.length;
|
|
180
|
+
let decrypted;
|
|
181
|
+
for (let i = 0; i < length; i++) {
|
|
182
|
+
try {
|
|
183
|
+
// Get full key
|
|
184
|
+
const key = keys[i].trim();
|
|
185
|
+
|
|
186
|
+
// Get instructions for decrypt
|
|
187
|
+
const attrs = _instructions(result, key);
|
|
188
|
+
|
|
189
|
+
// Decrypt
|
|
190
|
+
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
191
|
+
break;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
// last key
|
|
194
|
+
if (i + 1 >= length) {
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
// try next key
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Parse decrypted .env string
|
|
202
|
+
return DotenvModule.parse(decrypted);
|
|
203
|
+
}
|
|
204
|
+
function _log(message) {
|
|
205
|
+
console.log(`[dotenv@${version}][INFO] ${message}`);
|
|
206
|
+
}
|
|
207
|
+
function _warn(message) {
|
|
208
|
+
console.log(`[dotenv@${version}][WARN] ${message}`);
|
|
209
|
+
}
|
|
210
|
+
function _debug(message) {
|
|
211
|
+
console.log(`[dotenv@${version}][DEBUG] ${message}`);
|
|
212
|
+
}
|
|
213
|
+
function _dotenvKey(options) {
|
|
214
|
+
// prioritize developer directly setting options.DOTENV_KEY
|
|
215
|
+
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
|
|
216
|
+
return options.DOTENV_KEY;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// secondary infra already contains a DOTENV_KEY environment variable
|
|
220
|
+
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
221
|
+
return process.env.DOTENV_KEY;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// fallback to empty string
|
|
225
|
+
return '';
|
|
226
|
+
}
|
|
227
|
+
function _instructions(result, dotenvKey) {
|
|
228
|
+
// Parse DOTENV_KEY. Format is a URI
|
|
229
|
+
let uri;
|
|
230
|
+
try {
|
|
231
|
+
uri = new URL(dotenvKey);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (error.code === 'ERR_INVALID_URL') {
|
|
234
|
+
const err = new Error('INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development');
|
|
235
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
throw error;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Get decrypt key
|
|
242
|
+
const key = uri.password;
|
|
243
|
+
if (!key) {
|
|
244
|
+
const err = new Error('INVALID_DOTENV_KEY: Missing key part');
|
|
245
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
246
|
+
throw err;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Get environment
|
|
250
|
+
const environment = uri.searchParams.get('environment');
|
|
251
|
+
if (!environment) {
|
|
252
|
+
const err = new Error('INVALID_DOTENV_KEY: Missing environment part');
|
|
253
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
254
|
+
throw err;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Get ciphertext payload
|
|
258
|
+
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
259
|
+
const ciphertext = result.parsed[environmentKey]; // DOTENV_VAULT_PRODUCTION
|
|
260
|
+
if (!ciphertext) {
|
|
261
|
+
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
262
|
+
err.code = 'NOT_FOUND_DOTENV_ENVIRONMENT';
|
|
263
|
+
throw err;
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
ciphertext,
|
|
267
|
+
key
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function _vaultPath(options) {
|
|
271
|
+
let possibleVaultPath = null;
|
|
272
|
+
if (options && options.path && options.path.length > 0) {
|
|
273
|
+
if (Array.isArray(options.path)) {
|
|
274
|
+
for (const filepath of options.path) {
|
|
275
|
+
if (fs.existsSync(filepath)) {
|
|
276
|
+
possibleVaultPath = filepath.endsWith('.vault') ? filepath : `${filepath}.vault`;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
possibleVaultPath = options.path.endsWith('.vault') ? options.path : `${options.path}.vault`;
|
|
281
|
+
}
|
|
282
|
+
} else {
|
|
283
|
+
possibleVaultPath = path.resolve(process.cwd(), '.env.vault');
|
|
284
|
+
}
|
|
285
|
+
if (fs.existsSync(possibleVaultPath)) {
|
|
286
|
+
return possibleVaultPath;
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
function _resolveHome(envPath) {
|
|
291
|
+
return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
292
|
+
}
|
|
293
|
+
function _configVault(options) {
|
|
294
|
+
_log('Loading env from encrypted .env.vault');
|
|
295
|
+
const parsed = DotenvModule._parseVault(options);
|
|
296
|
+
let processEnv = process.env;
|
|
297
|
+
if (options && options.processEnv != null) {
|
|
298
|
+
processEnv = options.processEnv;
|
|
299
|
+
}
|
|
300
|
+
DotenvModule.populate(processEnv, parsed, options);
|
|
301
|
+
return {
|
|
302
|
+
parsed
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function configDotenv(options) {
|
|
306
|
+
const dotenvPath = path.resolve(process.cwd(), '.env');
|
|
307
|
+
let encoding = 'utf8';
|
|
308
|
+
const debug = Boolean(options && options.debug);
|
|
309
|
+
if (options && options.encoding) {
|
|
310
|
+
encoding = options.encoding;
|
|
311
|
+
} else {
|
|
312
|
+
if (debug) {
|
|
313
|
+
_debug('No encoding is specified. UTF-8 is used by default');
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
let optionPaths = [dotenvPath]; // default, look for .env
|
|
317
|
+
if (options && options.path) {
|
|
318
|
+
if (!Array.isArray(options.path)) {
|
|
319
|
+
optionPaths = [_resolveHome(options.path)];
|
|
320
|
+
} else {
|
|
321
|
+
optionPaths = []; // reset default
|
|
322
|
+
for (const filepath of options.path) {
|
|
323
|
+
optionPaths.push(_resolveHome(filepath));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Build the parsed data in a temporary object (because we need to return it). Once we have the final
|
|
329
|
+
// parsed data, we will combine it with process.env (or options.processEnv if provided).
|
|
330
|
+
let lastError;
|
|
331
|
+
const parsedAll = {};
|
|
332
|
+
for (const path of optionPaths) {
|
|
333
|
+
try {
|
|
334
|
+
// Specifying an encoding returns a string instead of a buffer
|
|
335
|
+
const parsed = DotenvModule.parse(fs.readFileSync(path, {
|
|
336
|
+
encoding
|
|
337
|
+
}));
|
|
338
|
+
DotenvModule.populate(parsedAll, parsed, options);
|
|
339
|
+
} catch (e) {
|
|
340
|
+
if (debug) {
|
|
341
|
+
_debug(`Failed to load ${path} ${e.message}`);
|
|
342
|
+
}
|
|
343
|
+
lastError = e;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
let processEnv = process.env;
|
|
347
|
+
if (options && options.processEnv != null) {
|
|
348
|
+
processEnv = options.processEnv;
|
|
349
|
+
}
|
|
350
|
+
DotenvModule.populate(processEnv, parsedAll, options);
|
|
351
|
+
if (lastError) {
|
|
352
|
+
return {
|
|
353
|
+
parsed: parsedAll,
|
|
354
|
+
error: lastError
|
|
355
|
+
};
|
|
356
|
+
} else {
|
|
357
|
+
return {
|
|
358
|
+
parsed: parsedAll
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Populates process.env from .env file
|
|
364
|
+
function config(options) {
|
|
365
|
+
// fallback to original dotenv if DOTENV_KEY is not set
|
|
366
|
+
if (_dotenvKey(options).length === 0) {
|
|
367
|
+
return DotenvModule.configDotenv(options);
|
|
368
|
+
}
|
|
369
|
+
const vaultPath = _vaultPath(options);
|
|
370
|
+
|
|
371
|
+
// dotenvKey exists but .env.vault file does not exist
|
|
372
|
+
if (!vaultPath) {
|
|
373
|
+
_warn(`You set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}. Did you forget to build it?`);
|
|
374
|
+
return DotenvModule.configDotenv(options);
|
|
375
|
+
}
|
|
376
|
+
return DotenvModule._configVault(options);
|
|
377
|
+
}
|
|
378
|
+
function decrypt(encrypted, keyStr) {
|
|
379
|
+
const key = Buffer.from(keyStr.slice(-64), 'hex');
|
|
380
|
+
let ciphertext = Buffer.from(encrypted, 'base64');
|
|
381
|
+
const nonce = ciphertext.subarray(0, 12);
|
|
382
|
+
const authTag = ciphertext.subarray(-16);
|
|
383
|
+
ciphertext = ciphertext.subarray(12, -16);
|
|
384
|
+
try {
|
|
385
|
+
const aesgcm = crypto.createDecipheriv('aes-256-gcm', key, nonce);
|
|
386
|
+
aesgcm.setAuthTag(authTag);
|
|
387
|
+
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
const isRange = error instanceof RangeError;
|
|
390
|
+
const invalidKeyLength = error.message === 'Invalid key length';
|
|
391
|
+
const decryptionFailed = error.message === 'Unsupported state or unable to authenticate data';
|
|
392
|
+
if (isRange || invalidKeyLength) {
|
|
393
|
+
const err = new Error('INVALID_DOTENV_KEY: It must be 64 characters long (or more)');
|
|
394
|
+
err.code = 'INVALID_DOTENV_KEY';
|
|
395
|
+
throw err;
|
|
396
|
+
} else if (decryptionFailed) {
|
|
397
|
+
const err = new Error('DECRYPTION_FAILED: Please check your DOTENV_KEY');
|
|
398
|
+
err.code = 'DECRYPTION_FAILED';
|
|
399
|
+
throw err;
|
|
400
|
+
} else {
|
|
401
|
+
throw error;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Populate process.env with parsed values
|
|
407
|
+
function populate(processEnv, parsed, options = {}) {
|
|
408
|
+
const debug = Boolean(options && options.debug);
|
|
409
|
+
const override = Boolean(options && options.override);
|
|
410
|
+
if (typeof parsed !== 'object') {
|
|
411
|
+
const err = new Error('OBJECT_REQUIRED: Please check the processEnv argument being passed to populate');
|
|
412
|
+
err.code = 'OBJECT_REQUIRED';
|
|
413
|
+
throw err;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Set process.env
|
|
417
|
+
for (const key of Object.keys(parsed)) {
|
|
418
|
+
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
419
|
+
if (override === true) {
|
|
420
|
+
processEnv[key] = parsed[key];
|
|
421
|
+
}
|
|
422
|
+
if (debug) {
|
|
423
|
+
if (override === true) {
|
|
424
|
+
_debug(`"${key}" is already defined and WAS overwritten`);
|
|
425
|
+
} else {
|
|
426
|
+
_debug(`"${key}" is already defined and was NOT overwritten`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
} else {
|
|
430
|
+
processEnv[key] = parsed[key];
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const DotenvModule = {
|
|
435
|
+
configDotenv,
|
|
436
|
+
_configVault,
|
|
437
|
+
_parseVault,
|
|
438
|
+
config,
|
|
439
|
+
decrypt,
|
|
440
|
+
parse,
|
|
441
|
+
populate
|
|
442
|
+
};
|
|
443
|
+
main$1.exports.configDotenv = DotenvModule.configDotenv;
|
|
444
|
+
main$1.exports._configVault = DotenvModule._configVault;
|
|
445
|
+
main$1.exports._parseVault = DotenvModule._parseVault;
|
|
446
|
+
main$1.exports.config = DotenvModule.config;
|
|
447
|
+
main$1.exports.decrypt = DotenvModule.decrypt;
|
|
448
|
+
main$1.exports.parse = DotenvModule.parse;
|
|
449
|
+
main$1.exports.populate = DotenvModule.populate;
|
|
450
|
+
main$1.exports = DotenvModule;
|
|
451
|
+
return main$1.exports;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
var envOptions;
|
|
455
|
+
var hasRequiredEnvOptions;
|
|
456
|
+
function requireEnvOptions() {
|
|
457
|
+
if (hasRequiredEnvOptions) return envOptions;
|
|
458
|
+
hasRequiredEnvOptions = 1;
|
|
459
|
+
// ../config.js accepts options via environment variables
|
|
460
|
+
const options = {};
|
|
461
|
+
if (process.env.DOTENV_CONFIG_ENCODING != null) {
|
|
462
|
+
options.encoding = process.env.DOTENV_CONFIG_ENCODING;
|
|
463
|
+
}
|
|
464
|
+
if (process.env.DOTENV_CONFIG_PATH != null) {
|
|
465
|
+
options.path = process.env.DOTENV_CONFIG_PATH;
|
|
466
|
+
}
|
|
467
|
+
if (process.env.DOTENV_CONFIG_DEBUG != null) {
|
|
468
|
+
options.debug = process.env.DOTENV_CONFIG_DEBUG;
|
|
469
|
+
}
|
|
470
|
+
if (process.env.DOTENV_CONFIG_OVERRIDE != null) {
|
|
471
|
+
options.override = process.env.DOTENV_CONFIG_OVERRIDE;
|
|
472
|
+
}
|
|
473
|
+
if (process.env.DOTENV_CONFIG_DOTENV_KEY != null) {
|
|
474
|
+
options.DOTENV_KEY = process.env.DOTENV_CONFIG_DOTENV_KEY;
|
|
475
|
+
}
|
|
476
|
+
envOptions = options;
|
|
477
|
+
return envOptions;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
var cliOptions;
|
|
481
|
+
var hasRequiredCliOptions;
|
|
482
|
+
function requireCliOptions() {
|
|
483
|
+
if (hasRequiredCliOptions) return cliOptions;
|
|
484
|
+
hasRequiredCliOptions = 1;
|
|
485
|
+
const re = /^dotenv_config_(encoding|path|debug|override|DOTENV_KEY)=(.+)$/;
|
|
486
|
+
cliOptions = function optionMatcher(args) {
|
|
487
|
+
return args.reduce(function (acc, cur) {
|
|
488
|
+
const matches = cur.match(re);
|
|
489
|
+
if (matches) {
|
|
490
|
+
acc[matches[1]] = matches[2];
|
|
491
|
+
}
|
|
492
|
+
return acc;
|
|
493
|
+
}, {});
|
|
494
|
+
};
|
|
495
|
+
return cliOptions;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
var hasRequiredConfig;
|
|
499
|
+
function requireConfig() {
|
|
500
|
+
if (hasRequiredConfig) return config$1;
|
|
501
|
+
hasRequiredConfig = 1;
|
|
502
|
+
(function () {
|
|
503
|
+
requireMain().config(Object.assign({}, requireEnvOptions(), requireCliOptions()(process.argv)));
|
|
504
|
+
})();
|
|
505
|
+
return config$1;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
requireConfig();
|
|
509
|
+
|
|
510
|
+
// src/load-config.mjs
|
|
511
|
+
function req(name, val) {
|
|
512
|
+
if (val === undefined || val === null) {
|
|
513
|
+
throw new Error(`[vettvangur-styleguide :: load-config] Missing required config field: ${name}`);
|
|
514
|
+
}
|
|
515
|
+
return val;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Also load .env.local if present (priority over .env)
|
|
519
|
+
const rootCwd = process$1.cwd();
|
|
520
|
+
const envLocal = path.join(rootCwd, ".env.local");
|
|
521
|
+
if (fs.existsSync(envLocal)) {
|
|
522
|
+
const dotenv = await import('dotenv');
|
|
523
|
+
dotenv.config({
|
|
524
|
+
path: envLocal,
|
|
525
|
+
override: true
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
const cfgFile = path.resolve(rootCwd, "vettvangur.config.mjs");
|
|
529
|
+
const mod = await import(pathToFileURL(cfgFile).href);
|
|
530
|
+
const config = mod.default ?? mod;
|
|
531
|
+
const root = rootCwd;
|
|
532
|
+
|
|
533
|
+
// Accept either config.figma.key or env (FIGMA_KEY/FIGMA_TOKEN); file from config or FIGMA_FILE
|
|
534
|
+
const figmaKey = config?.figma?.key ?? process$1.env.FIGMA_KEY ?? process$1.env.FIGMA_TOKEN ?? null;
|
|
535
|
+
const figmaFile = config?.figma?.file ?? process$1.env.FIGMA_FILE ?? null;
|
|
536
|
+
const paths = {
|
|
537
|
+
root,
|
|
538
|
+
dest: path.resolve(root, req("dest", config.dest)),
|
|
539
|
+
public: path.resolve(root, req("public", config.public)),
|
|
540
|
+
assets: path.resolve(root, req("assets", config.assets)),
|
|
541
|
+
styles: path.resolve(root, req("styles.path", config.styles.path)),
|
|
542
|
+
styleEntries: req("styles.entries", config.styles.entries).map(p => path.isAbsolute(p) ? p : path.resolve(root, p)),
|
|
543
|
+
scripts: path.resolve(root, req("scripts.path", config.scripts.path)),
|
|
544
|
+
scriptEntries: req("scripts.entries", config.scripts.entries).map(p => path.isAbsolute(p) ? p : path.resolve(root, p)),
|
|
545
|
+
views: path.resolve(root, req("views", config.views)),
|
|
546
|
+
watch: config.watch,
|
|
547
|
+
figma: {
|
|
548
|
+
key: req("figma.key", figmaKey),
|
|
549
|
+
file: req("figma.file", figmaFile)
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// ---- HTTPS (safe, never throws) --------------------------------------------
|
|
554
|
+
function makeHttpsCfg(cfg) {
|
|
555
|
+
const ds = cfg?.devserver ?? {};
|
|
556
|
+
const h = ds.https;
|
|
557
|
+
if (h === false) return false;
|
|
558
|
+
if (h && typeof h === "object") {
|
|
559
|
+
if (h.key && h.cert) return {
|
|
560
|
+
key: h.key,
|
|
561
|
+
cert: h.cert
|
|
562
|
+
};
|
|
563
|
+
if (h.keyfile && h.certfile) {
|
|
564
|
+
try {
|
|
565
|
+
if (fs.existsSync(h.keyfile) && fs.existsSync(h.certfile)) {
|
|
566
|
+
return {
|
|
567
|
+
key: fs.readFileSync(h.keyfile),
|
|
568
|
+
cert: fs.readFileSync(h.certfile)
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
} catch {
|
|
572
|
+
/* swallow */
|
|
573
|
+
}
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (ds.keyfile && ds.certfile) {
|
|
578
|
+
try {
|
|
579
|
+
if (fs.existsSync(ds.keyfile) && fs.existsSync(ds.certfile)) {
|
|
580
|
+
return {
|
|
581
|
+
key: fs.readFileSync(ds.keyfile),
|
|
582
|
+
cert: fs.readFileSync(ds.certfile)
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
} catch {
|
|
586
|
+
/* swallow */
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
makeHttpsCfg(config);
|
|
592
|
+
|
|
593
|
+
// ---- flags -----------------------------------------------------------------
|
|
594
|
+
({
|
|
595
|
+
useSass: config.styles?.type === "sass",
|
|
596
|
+
useTailwind: config.styles?.type === "tailwind"
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// figma-exporter.mjs
|
|
600
|
+
// Usage: import exporter from './figma-exporter.mjs'; const json = await exporter('<FILE_KEY>');
|
|
601
|
+
// Requires: FIGMA_TOKEN env (or hardcode TOKEN below)
|
|
602
|
+
const tag$1 = chalk.cyan('[design-system]');
|
|
603
|
+
|
|
604
|
+
// ---------- utils ----------
|
|
605
|
+
const toKebab = s => s.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
606
|
+
const hex2 = n => n.toString(16).padStart(2, "0");
|
|
607
|
+
function rgba255(c) {
|
|
608
|
+
const a = typeof c.a === "number" ? c.a : 1;
|
|
609
|
+
return {
|
|
610
|
+
r: Math.round(c.r * 255),
|
|
611
|
+
g: Math.round(c.g * 255),
|
|
612
|
+
b: Math.round(c.b * 255),
|
|
613
|
+
a
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function colorToHexRgba(c) {
|
|
617
|
+
const r = Math.round(c.r * 255),
|
|
618
|
+
g = Math.round(c.g * 255),
|
|
619
|
+
b = Math.round(c.b * 255);
|
|
620
|
+
const a = typeof c.a === "number" ? c.a : 1;
|
|
621
|
+
return `#${hex2(r)}${hex2(g)}${hex2(b)}${a < 1 ? hex2(Math.round(a * 255)) : ""}`;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// ---------- text metric shims ----------
|
|
625
|
+
function readLineHeight(style) {
|
|
626
|
+
const s = style || {};
|
|
627
|
+
// 1) New object form
|
|
628
|
+
if (s.lineHeight && typeof s.lineHeight === "object") {
|
|
629
|
+
const u = s.lineHeight.unit || s.lineHeight.lineHeightUnit;
|
|
630
|
+
const v = s.lineHeight.value || s.lineHeight.lineHeightPx || s.lineHeight.percent;
|
|
631
|
+
if ((u === "PERCENT" || u === "FONT_SIZE_%") && typeof v === "number" && v > 0) {
|
|
632
|
+
return {
|
|
633
|
+
unit: "%",
|
|
634
|
+
value: v
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
if (u === "PIXELS" && typeof v === "number" && v > 0) {
|
|
638
|
+
return {
|
|
639
|
+
unit: "px",
|
|
640
|
+
value: v
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// 2) Legacy percent fields (prefer percent over px)
|
|
646
|
+
if (typeof s.lineHeightPercentFontSize === "number" && s.lineHeightPercentFontSize > 0) {
|
|
647
|
+
return {
|
|
648
|
+
unit: "%",
|
|
649
|
+
value: s.lineHeightPercentFontSize
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
if (typeof s.lineHeightPercent === "number" && s.lineHeightPercent > 0) {
|
|
653
|
+
return {
|
|
654
|
+
unit: "%",
|
|
655
|
+
value: s.lineHeightPercent
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// 3) If unit is percent but only px is present, derive percent
|
|
660
|
+
if ((s.lineHeightUnit === "FONT_SIZE_%" || s.lineHeightUnit === "PERCENT") && typeof s.lineHeightPx === "number" && typeof s.fontSize === "number" && s.fontSize > 0) {
|
|
661
|
+
const pct = s.lineHeightPx / s.fontSize * 100;
|
|
662
|
+
return {
|
|
663
|
+
unit: "%",
|
|
664
|
+
value: Math.round(pct * 100) / 100
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// 4) Plain px
|
|
669
|
+
if (typeof s.lineHeightPx === "number" && s.lineHeightPx > 0) {
|
|
670
|
+
return {
|
|
671
|
+
unit: "px",
|
|
672
|
+
value: s.lineHeightPx
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// 5) Last-resort derivation from numeric lineHeight + fontSize
|
|
677
|
+
if (typeof s.lineHeight === "number" && typeof s.fontSize === "number" && s.fontSize > 0) {
|
|
678
|
+
const pct = s.lineHeight / s.fontSize * 100;
|
|
679
|
+
return {
|
|
680
|
+
unit: "%",
|
|
681
|
+
value: Math.round(pct * 100) / 100
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
function readLetterSpacing(style) {
|
|
687
|
+
if (style?.letterSpacing && typeof style.letterSpacing === "object") {
|
|
688
|
+
const u = style.letterSpacing.unit || style.letterSpacingUnit;
|
|
689
|
+
const v = style.letterSpacing.value || style.letterSpacingPx || style.letterSpacing.percent;
|
|
690
|
+
if (u === "PIXELS" && typeof v === "number") return {
|
|
691
|
+
unit: "px",
|
|
692
|
+
value: v
|
|
693
|
+
};
|
|
694
|
+
if (u === "PERCENT" && typeof v === "number") return {
|
|
695
|
+
unit: "%",
|
|
696
|
+
value: v
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
if (typeof style?.letterSpacing === "number" && style?.letterSpacingUnit === "PIXELS") return {
|
|
700
|
+
unit: "px",
|
|
701
|
+
value: style.letterSpacing
|
|
702
|
+
};
|
|
703
|
+
if (typeof style?.letterSpacingPercent === "number") return {
|
|
704
|
+
unit: "%",
|
|
705
|
+
value: style.letterSpacingPercent
|
|
706
|
+
};
|
|
707
|
+
if (typeof style?.letterSpacingPercentFontSize === "number") return {
|
|
708
|
+
unit: "%",
|
|
709
|
+
value: style.letterSpacingPercentFontSize
|
|
710
|
+
};
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// ---------- HTTP ----------
|
|
715
|
+
async function jget(url) {
|
|
716
|
+
const HEADERS = {
|
|
717
|
+
"X-Figma-Token": paths.figma.key,
|
|
718
|
+
"Content-Type": "application/json"
|
|
719
|
+
};
|
|
720
|
+
const r = await fetch(url, {
|
|
721
|
+
headers: HEADERS
|
|
722
|
+
});
|
|
723
|
+
if (!r.ok) {
|
|
724
|
+
const body = await r.text().catch(() => "");
|
|
725
|
+
throw new Error(`${r.status} ${r.statusText} – ${url}\n${body}`);
|
|
726
|
+
}
|
|
727
|
+
return r.json();
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// ---------- variables (optional; Enterprise + file_variables:read) ----------
|
|
731
|
+
async function fetchVariablesLocal(fileKey) {
|
|
732
|
+
console.log(`${tag$1} fetching variables...`);
|
|
733
|
+
try {
|
|
734
|
+
const res = await jget(`https://api.figma.com/v1/files/${fileKey}/variables/local`);
|
|
735
|
+
const meta = res?.meta ?? {};
|
|
736
|
+
const vars = meta.variables ?? {};
|
|
737
|
+
const cols = meta.variableCollections ?? {};
|
|
738
|
+
const byCollection = {};
|
|
739
|
+
for (const colId of Object.keys(cols)) {
|
|
740
|
+
const c = cols[colId];
|
|
741
|
+
const colKey = toKebab(c.name);
|
|
742
|
+
byCollection[colKey] = {
|
|
743
|
+
id: c.id,
|
|
744
|
+
name: c.name,
|
|
745
|
+
modes: (c.modes || []).map(m => ({
|
|
746
|
+
id: m.modeId,
|
|
747
|
+
name: m.name,
|
|
748
|
+
key: toKebab(m.name)
|
|
749
|
+
})),
|
|
750
|
+
tokens: {}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
function resolveAlias(value, depth = 0) {
|
|
754
|
+
if (!value || typeof value !== "object") return value;
|
|
755
|
+
if (value.type === "VARIABLE_ALIAS" && value.id && depth < 20) {
|
|
756
|
+
const aliased = vars[value.id];
|
|
757
|
+
if (!aliased) return value;
|
|
758
|
+
const coll = cols[aliased.variableCollectionId];
|
|
759
|
+
const modeOrder = (coll?.modes || []).map(m => m.modeId);
|
|
760
|
+
const pick = modeOrder[0] || Object.keys(aliased.valuesByMode || {})[0];
|
|
761
|
+
return resolveAlias(aliased.valuesByMode?.[pick], depth + 1);
|
|
762
|
+
}
|
|
763
|
+
return value;
|
|
764
|
+
}
|
|
765
|
+
for (const vId of Object.keys(vars)) {
|
|
766
|
+
const v = vars[vId];
|
|
767
|
+
const coll = cols[v.variableCollectionId];
|
|
768
|
+
if (!coll) continue;
|
|
769
|
+
const colKey = toKebab(coll.name);
|
|
770
|
+
const nameKey = toKebab(v.name);
|
|
771
|
+
const token = {
|
|
772
|
+
id: v.id,
|
|
773
|
+
name: v.name,
|
|
774
|
+
type: v.resolvedType,
|
|
775
|
+
values: {}
|
|
776
|
+
};
|
|
777
|
+
for (const [modeId, raw] of Object.entries(v.valuesByMode || {})) {
|
|
778
|
+
const mode = (coll.modes || []).find(m => m.modeId === modeId);
|
|
779
|
+
if (!mode) continue;
|
|
780
|
+
const val = resolveAlias(raw);
|
|
781
|
+
if (v.resolvedType === "FLOAT" || v.resolvedType === "BOOLEAN" || v.resolvedType === "STRING") {
|
|
782
|
+
token.values[mode.name] = val ?? null;
|
|
783
|
+
} else if (v.resolvedType === "COLOR" && val && typeof val === "object" && "r" in val) {
|
|
784
|
+
token.values[mode.name] = {
|
|
785
|
+
hex: colorToHexRgba(val),
|
|
786
|
+
rgba: rgba255(val)
|
|
787
|
+
};
|
|
788
|
+
} else {
|
|
789
|
+
token.values[mode.name] = null;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
byCollection[colKey].tokens[nameKey] = token;
|
|
793
|
+
}
|
|
794
|
+
return byCollection;
|
|
795
|
+
} catch {
|
|
796
|
+
return {};
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// ---------- styles (two sources) ----------
|
|
801
|
+
async function fetchStyleNodeIds_fromFile(fileKey) {
|
|
802
|
+
const file = await jget(`https://api.figma.com/v1/files/${fileKey}`);
|
|
803
|
+
return file?.styles || {};
|
|
804
|
+
}
|
|
805
|
+
async function fetchStyleNodeIds_published(fileKey) {
|
|
806
|
+
try {
|
|
807
|
+
const res = await jget(`https://api.figma.com/v1/files/${fileKey}/styles`);
|
|
808
|
+
const arr = Array.isArray(res?.meta?.styles) ? res.meta.styles : [];
|
|
809
|
+
const map = {};
|
|
810
|
+
for (const s of arr) map[s.key] = s;
|
|
811
|
+
return map;
|
|
812
|
+
} catch {
|
|
813
|
+
return {};
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
async function fetchMergedStyleNodeIds(fileKey) {
|
|
817
|
+
const a = await fetchStyleNodeIds_fromFile(fileKey);
|
|
818
|
+
const b = await fetchStyleNodeIds_published(fileKey);
|
|
819
|
+
return {
|
|
820
|
+
...a,
|
|
821
|
+
...b
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// ---------- component + component-set discovery ----------
|
|
826
|
+
function walk(node, fn) {
|
|
827
|
+
if (!node || typeof node !== 'object') return;
|
|
828
|
+
fn(node);
|
|
829
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
830
|
+
for (const c of children) walk(c, fn);
|
|
831
|
+
}
|
|
832
|
+
async function fetchComponentAndSetIds(fileKey) {
|
|
833
|
+
console.log(`${tag$1} scanning for components...`);
|
|
834
|
+
const file = await jget(`https://api.figma.com/v1/files/${fileKey}`);
|
|
835
|
+
const ids = new Set();
|
|
836
|
+
walk(file?.document, n => {
|
|
837
|
+
if (n?.type === 'COMPONENT' || n?.type === 'COMPONENT_SET') {
|
|
838
|
+
ids.add(n.id);
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
return Array.from(ids);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// ---------- nodes (batched) ----------
|
|
845
|
+
async function fetchNodesByIds(fileKey, allIds) {
|
|
846
|
+
console.log(`${tag$1} reading nodes by id...`);
|
|
847
|
+
const ids = Array.from(new Set((allIds || []).filter(Boolean).map(String).map(s => s.trim()).filter(s => s.length > 0)));
|
|
848
|
+
const out = [];
|
|
849
|
+
const CHUNK = 50;
|
|
850
|
+
for (let i = 0; i < ids.length; i += CHUNK) {
|
|
851
|
+
const slice = ids.slice(i, i + CHUNK);
|
|
852
|
+
const qs = slice.map(encodeURIComponent).join(",");
|
|
853
|
+
const url = `https://api.figma.com/v1/files/${fileKey}/nodes?ids=${qs}`;
|
|
854
|
+
const res = await jget(url);
|
|
855
|
+
const nodes = res?.nodes || {};
|
|
856
|
+
for (const k of Object.keys(nodes)) {
|
|
857
|
+
const doc = nodes[k]?.document;
|
|
858
|
+
if (doc) out.push(doc);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return out;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// ---------- text props derivation ----------
|
|
865
|
+
function weightFromStyleName(s) {
|
|
866
|
+
if (!s) return undefined;
|
|
867
|
+
const x = s.toLowerCase();
|
|
868
|
+
if (/\b(100|thin)\b/.test(x)) return 100;
|
|
869
|
+
if (/\b(200|extralight|ultralight)\b/.test(x)) return 200;
|
|
870
|
+
if (/\b(300|light)\b/.test(x)) return 300;
|
|
871
|
+
if (/\b(400|regular|book|normal)\b/.test(x)) return 400;
|
|
872
|
+
if (/\b(500|medium)\b/.test(x)) return 500;
|
|
873
|
+
if (/\b(600|semibold|demibold)\b/.test(x)) return 600;
|
|
874
|
+
if (/\b(700|bold)\b/.test(x)) return 700;
|
|
875
|
+
if (/\b(800|extrabold|ultrabold|heavy)\b/.test(x)) return 800;
|
|
876
|
+
if (/\b(900|black|heavy)\b/.test(x)) return 900;
|
|
877
|
+
const m = s.match(/\b(100|200|300|400|500|600|700|800|900)\b/);
|
|
878
|
+
return m ? Number(m[1]) : undefined;
|
|
879
|
+
}
|
|
880
|
+
function deriveFontStyle(style) {
|
|
881
|
+
if (typeof style?.fontStyle === "string" && style.fontStyle.trim()) return style.fontStyle;
|
|
882
|
+
const ps = style?.fontPostScriptName || "";
|
|
883
|
+
const tail = ps.includes("-") ? ps.split("-")[1] : ps;
|
|
884
|
+
if (/italic/i.test(tail)) return "Italic";
|
|
885
|
+
if (/oblique/i.test(tail)) return "Oblique";
|
|
886
|
+
if (/regular|book|normal/i.test(tail)) return "Regular";
|
|
887
|
+
const token = (tail.match(/[A-Za-z]+$/) || [])[0];
|
|
888
|
+
return token ? token[0].toUpperCase() + token.slice(1) : undefined;
|
|
889
|
+
}
|
|
890
|
+
function readTextProps(n) {
|
|
891
|
+
const s = n.style || n;
|
|
892
|
+
const styleName = s.fontStyle || s.fontPostScriptName || "";
|
|
893
|
+
return {
|
|
894
|
+
fontFamily: s.fontFamily || n.fontFamily,
|
|
895
|
+
fontStyle: deriveFontStyle(s),
|
|
896
|
+
fontWeight: typeof s.fontWeight === "number" ? s.fontWeight : weightFromStyleName(styleName),
|
|
897
|
+
fontSize: s.fontSize ?? n.fontSize,
|
|
898
|
+
lineHeight: readLineHeight(s),
|
|
899
|
+
letterSpacing: readLetterSpacing(s),
|
|
900
|
+
paragraphSpacing: typeof s.paragraphSpacing === "number" ? s.paragraphSpacing : typeof n.paragraphSpacing === "number" ? n.paragraphSpacing : 0,
|
|
901
|
+
textCase: s.textCase || n.textCase || "ORIGINAL",
|
|
902
|
+
textDecoration: s.textDecoration || n.textDecoration || "NONE"
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ---------- normalizers: styles ----------
|
|
907
|
+
function emitPaintStylesJson(styleMetas, nodes) {
|
|
908
|
+
const byId = {};
|
|
909
|
+
for (const n of nodes) {
|
|
910
|
+
const paints = Array.isArray(n.paints) ? n.paints : Array.isArray(n.fills) ? n.fills : null;
|
|
911
|
+
if (!paints) continue;
|
|
912
|
+
const meta = Object.values(styleMetas).find(m => m.node_id === n.id && m.style_type === "FILL");
|
|
913
|
+
if (!meta) continue;
|
|
914
|
+
const solids = [];
|
|
915
|
+
for (const p of paints) {
|
|
916
|
+
if (p?.type !== "SOLID" || p?.visible === false) continue;
|
|
917
|
+
const a = typeof p.opacity === "number" ? p.opacity : 1;
|
|
918
|
+
solids.push({
|
|
919
|
+
hex: colorToHexRgba({
|
|
920
|
+
...p.color,
|
|
921
|
+
a
|
|
922
|
+
}),
|
|
923
|
+
rgba: rgba255({
|
|
924
|
+
...p.color,
|
|
925
|
+
a
|
|
926
|
+
})
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
if (solids.length) {
|
|
930
|
+
const key = toKebab(meta.name);
|
|
931
|
+
byId[key] = {
|
|
932
|
+
id: meta.key || meta.node_id,
|
|
933
|
+
name: meta.name,
|
|
934
|
+
paints: solids
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
console.log(`${tag$1} finished reading paint styles`);
|
|
939
|
+
return byId;
|
|
940
|
+
}
|
|
941
|
+
function emitTextStylesJson(styleMetas, nodes) {
|
|
942
|
+
const out = {};
|
|
943
|
+
for (const n of nodes) {
|
|
944
|
+
const meta = Object.values(styleMetas).find(m => m.node_id === n.id && m.style_type === "TEXT");
|
|
945
|
+
if (!meta) continue;
|
|
946
|
+
const key = toKebab(meta.name);
|
|
947
|
+
const p = readTextProps(n);
|
|
948
|
+
out[key] = {
|
|
949
|
+
id: meta.key || meta.node_id,
|
|
950
|
+
name: meta.name,
|
|
951
|
+
fontFamily: p.fontFamily,
|
|
952
|
+
fontStyle: p.fontStyle,
|
|
953
|
+
fontWeight: p.fontWeight,
|
|
954
|
+
fontSize: p.fontSize,
|
|
955
|
+
lineHeight: p.lineHeight,
|
|
956
|
+
letterSpacing: p.letterSpacing,
|
|
957
|
+
paragraphSpacing: p.paragraphSpacing,
|
|
958
|
+
textCase: p.textCase,
|
|
959
|
+
textDecoration: p.textDecoration
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
console.log(`${tag$1} finished reading text styles`);
|
|
963
|
+
return out;
|
|
964
|
+
}
|
|
965
|
+
function effectToJson(e) {
|
|
966
|
+
if (e?.visible === false) return null;
|
|
967
|
+
if (e?.type === "DROP_SHADOW" || e?.type === "INNER_SHADOW") {
|
|
968
|
+
const spread = typeof e.spread === "number" ? Math.round(e.spread) : 0;
|
|
969
|
+
return {
|
|
970
|
+
kind: e.type,
|
|
971
|
+
offset: {
|
|
972
|
+
x: Math.round(e.offset?.x ?? 0),
|
|
973
|
+
y: Math.round(e.offset?.y ?? 0)
|
|
974
|
+
},
|
|
975
|
+
radius: Math.round(e.radius ?? 0),
|
|
976
|
+
spread,
|
|
977
|
+
color: {
|
|
978
|
+
hex: colorToHexRgba(e.color),
|
|
979
|
+
rgba: rgba255(e.color)
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
if (e?.type === "LAYER_BLUR" || e?.type === "BACKGROUND_BLUR") {
|
|
984
|
+
return {
|
|
985
|
+
kind: e.type,
|
|
986
|
+
radius: Math.round(e.radius ?? 0)
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
return null;
|
|
990
|
+
}
|
|
991
|
+
function emitEffectStylesJson(styleMetas, nodes) {
|
|
992
|
+
const out = {};
|
|
993
|
+
for (const n of nodes) {
|
|
994
|
+
const meta = Object.values(styleMetas).find(m => m.node_id === n.id && m.style_type === "EFFECT");
|
|
995
|
+
if (!meta) continue;
|
|
996
|
+
const list = [];
|
|
997
|
+
const effects = Array.isArray(n.effects) ? n.effects : [];
|
|
998
|
+
for (const e of effects) {
|
|
999
|
+
const obj = effectToJson(e);
|
|
1000
|
+
if (obj) list.push(obj);
|
|
1001
|
+
}
|
|
1002
|
+
if (list.length) {
|
|
1003
|
+
const key = toKebab(meta.name);
|
|
1004
|
+
out[key] = {
|
|
1005
|
+
id: meta.key || meta.node_id,
|
|
1006
|
+
name: meta.name,
|
|
1007
|
+
effects: list
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
console.log(`${tag$1} finished reading effect styles`);
|
|
1012
|
+
return out;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// ---------- normalizers: component properties ----------
|
|
1016
|
+
function normalizeComponentPropDef(def) {
|
|
1017
|
+
// Figma REST: { name, type, defaultValue, variantOptions?, preferredValues? ... }
|
|
1018
|
+
const base = {
|
|
1019
|
+
name: def?.name ?? '',
|
|
1020
|
+
type: def?.type ?? 'TEXT'
|
|
1021
|
+
};
|
|
1022
|
+
if (def?.type === 'TEXT') {
|
|
1023
|
+
return {
|
|
1024
|
+
...base,
|
|
1025
|
+
default: def?.defaultValue ?? ''
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
if (def?.type === 'BOOLEAN') {
|
|
1029
|
+
return {
|
|
1030
|
+
...base,
|
|
1031
|
+
default: !!def?.defaultValue
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
if (def?.type === 'VARIANT') {
|
|
1035
|
+
const opts = Array.isArray(def?.variantOptions) ? def.variantOptions : [];
|
|
1036
|
+
const defVal = typeof def?.defaultValue === 'string' ? def.defaultValue : opts[0] ?? '';
|
|
1037
|
+
return {
|
|
1038
|
+
...base,
|
|
1039
|
+
options: opts,
|
|
1040
|
+
default: defVal
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
if (def?.type === 'INSTANCE_SWAP') {
|
|
1044
|
+
// preferredValues is an array of component IDs/keys; Figma may return "preferredValues"
|
|
1045
|
+
const prefs = Array.isArray(def?.preferredValues) ? def.preferredValues : [];
|
|
1046
|
+
const defVal = def?.defaultValue ?? null;
|
|
1047
|
+
return {
|
|
1048
|
+
...base,
|
|
1049
|
+
preferredValues: prefs,
|
|
1050
|
+
default: defVal
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
...base,
|
|
1055
|
+
default: def?.defaultValue ?? null
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function emitComponentPropsJson(nodes) {
|
|
1059
|
+
const out = {};
|
|
1060
|
+
for (const n of nodes) {
|
|
1061
|
+
if (n?.type !== 'COMPONENT' && n?.type !== 'COMPONENT_SET') continue;
|
|
1062
|
+
const defs = n.componentPropertyDefinitions || {};
|
|
1063
|
+
const props = {};
|
|
1064
|
+
for (const propId of Object.keys(defs)) {
|
|
1065
|
+
const def = defs[propId];
|
|
1066
|
+
const key = toKebab(def?.name ?? propId);
|
|
1067
|
+
props[key] = {
|
|
1068
|
+
id: propId,
|
|
1069
|
+
...normalizeComponentPropDef(def)
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if (Object.keys(props).length > 0) {
|
|
1073
|
+
const key = toKebab(n.name || n.id);
|
|
1074
|
+
out[key] = {
|
|
1075
|
+
id: n.id,
|
|
1076
|
+
name: n.name,
|
|
1077
|
+
kind: n.type,
|
|
1078
|
+
// COMPONENT or COMPONENT_SET
|
|
1079
|
+
props
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
console.log(`${tag$1} finished reading component properties`);
|
|
1084
|
+
return out;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// ---------- exporter ----------
|
|
1088
|
+
const exporter = async function (fileKey) {
|
|
1089
|
+
if (!fileKey) throw new Error("Missing FILE_KEY");
|
|
1090
|
+
console.log(`${tag$1} reading from figma`);
|
|
1091
|
+
const variablesByCollection = await fetchVariablesLocal(fileKey);
|
|
1092
|
+
const stylesMap = await fetchMergedStyleNodeIds(fileKey);
|
|
1093
|
+
const styleNodeIds = Object.values(stylesMap).filter(s => s && (s.style_type === "FILL" || s.style_type === "TEXT" || s.style_type === "EFFECT")).map(s => s.node_id);
|
|
1094
|
+
const componentIds = await fetchComponentAndSetIds(fileKey);
|
|
1095
|
+
|
|
1096
|
+
// Early-return case when no styles and no components
|
|
1097
|
+
if (styleNodeIds.length === 0 && componentIds.length === 0) {
|
|
1098
|
+
return {
|
|
1099
|
+
paint: {},
|
|
1100
|
+
text: {},
|
|
1101
|
+
effect: {},
|
|
1102
|
+
variables: variablesByCollection,
|
|
1103
|
+
components: {}
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
const styleNodes = styleNodeIds.length ? await fetchNodesByIds(fileKey, styleNodeIds) : [];
|
|
1107
|
+
const componentNodes = componentIds.length ? await fetchNodesByIds(fileKey, componentIds) : [];
|
|
1108
|
+
const paint = emitPaintStylesJson(stylesMap, styleNodes);
|
|
1109
|
+
const text = emitTextStylesJson(stylesMap, styleNodes);
|
|
1110
|
+
const effect = emitEffectStylesJson(stylesMap, styleNodes);
|
|
1111
|
+
const components = emitComponentPropsJson(componentNodes);
|
|
1112
|
+
return {
|
|
1113
|
+
paint,
|
|
1114
|
+
text,
|
|
1115
|
+
effect,
|
|
1116
|
+
variables: variablesByCollection,
|
|
1117
|
+
components
|
|
1118
|
+
};
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
const tag = chalk.cyan('[design-system]');
|
|
1122
|
+
|
|
1123
|
+
/*
|
|
1124
|
+
* This needs to genrate:
|
|
1125
|
+
* variables:
|
|
1126
|
+
* color.css
|
|
1127
|
+
* font.css
|
|
1128
|
+
* radius.css
|
|
1129
|
+
* shadow.css
|
|
1130
|
+
* typography.css
|
|
1131
|
+
* core:
|
|
1132
|
+
* body.css
|
|
1133
|
+
* headline.css
|
|
1134
|
+
*/
|
|
1135
|
+
|
|
1136
|
+
async function readFigma() {
|
|
1137
|
+
const figmaStyles = await exporter(paths.figma.file);
|
|
1138
|
+
return figmaStyles;
|
|
1139
|
+
}
|
|
1140
|
+
async function generateTailwind() {
|
|
1141
|
+
console.log(`${tag} generating tailwind..`);
|
|
1142
|
+
const figmaStyles = await readFigma(); // await fix
|
|
1143
|
+
|
|
1144
|
+
await generateColors(figmaStyles.paint);
|
|
1145
|
+
await generateTypography(figmaStyles.text);
|
|
1146
|
+
|
|
1147
|
+
// core utilities
|
|
1148
|
+
await generateBodies(figmaStyles.text);
|
|
1149
|
+
await generateHeadlines(figmaStyles.text);
|
|
1150
|
+
await generateButtons();
|
|
1151
|
+
console.log(`${tag} finished generating tailwind`);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ------- VARIABLES -------
|
|
1155
|
+
async function generateColors(data) {
|
|
1156
|
+
console.log(`${tag} generating colors...`);
|
|
1157
|
+
const outDir = path.join(paths.styles, 'config');
|
|
1158
|
+
await fs$1.mkdir(outDir, {
|
|
1159
|
+
recursive: true
|
|
1160
|
+
});
|
|
1161
|
+
const lines = [];
|
|
1162
|
+
for (const [key, entry] of Object.entries(data ?? {})) {
|
|
1163
|
+
const paints = entry?.paints ?? [];
|
|
1164
|
+
const p = paints[0] ?? {};
|
|
1165
|
+
// exporter already normalizes; prefer .hex/.value, else stringify fallback
|
|
1166
|
+
const value = (p.hex && String(p.hex)) ?? (p.value && String(p.value)) ?? (p.css && String(p.css)) ?? String(p);
|
|
1167
|
+
if (!value) {
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
lines.push(` --color-${key}: ${value};`);
|
|
1171
|
+
}
|
|
1172
|
+
const css = `/* Auto-generated. Do not edit by hand. */
|
|
1173
|
+
@theme {
|
|
1174
|
+
${lines.join('\n')}
|
|
1175
|
+
}
|
|
1176
|
+
`;
|
|
1177
|
+
const filePath = path.join(outDir, 'color.css');
|
|
1178
|
+
await fs$1.writeFile(filePath, css, 'utf8');
|
|
1179
|
+
console.log(`${tag} finished generating colors`);
|
|
1180
|
+
}
|
|
1181
|
+
async function generateButtons() {
|
|
1182
|
+
//shadow.css
|
|
1183
|
+
console.log(`${tag} generating buttons...`);
|
|
1184
|
+
const outDir = path.join(paths.styles, 'core');
|
|
1185
|
+
const getButtons = await parseButtons();
|
|
1186
|
+
const buttons = getButtons.map(b => {
|
|
1187
|
+
return `
|
|
1188
|
+
@utility ${b} {
|
|
1189
|
+
|
|
1190
|
+
}`;
|
|
1191
|
+
}).join('\n');
|
|
1192
|
+
const out = `/* Auto-generated. Do not edit by hand. */
|
|
1193
|
+
${buttons.join('\n\n')}
|
|
1194
|
+
`;
|
|
1195
|
+
await fs$1.writeFile(path.join(outDir, 'button-variants.css'), out, 'utf8');
|
|
1196
|
+
console.log(`${tag} finished generating buttons`);
|
|
1197
|
+
}
|
|
1198
|
+
async function generateTypography(data) {
|
|
1199
|
+
console.log(`${tag} generating typography...`);
|
|
1200
|
+
const outDir = path.join(paths.styles, 'config');
|
|
1201
|
+
await fs$1.mkdir(outDir, {
|
|
1202
|
+
recursive: true
|
|
1203
|
+
});
|
|
1204
|
+
const lines = [];
|
|
1205
|
+
for (const [key, entry] of Object.entries(data ?? {})) {
|
|
1206
|
+
const fontSize = entry?.fontSize ?? null;
|
|
1207
|
+
const lh = entry?.lineHeight ?? {};
|
|
1208
|
+
const lhVal = lh?.value ?? null;
|
|
1209
|
+
const lhUnit = lh?.unit ?? '';
|
|
1210
|
+
if (fontSize) lines.push(` --text-${key}: ${fontSize}px;`);
|
|
1211
|
+
if (lhVal) lines.push(` --leading-${key}: ${lhVal}${lhUnit};`);
|
|
1212
|
+
}
|
|
1213
|
+
const css = `/* Auto-generated. Do not edit by hand. */
|
|
1214
|
+
@theme {
|
|
1215
|
+
${lines.join('\n')}
|
|
1216
|
+
}
|
|
1217
|
+
`;
|
|
1218
|
+
const filePath = path.join(outDir, 'typography.css');
|
|
1219
|
+
await fs$1.writeFile(filePath, css, 'utf8');
|
|
1220
|
+
console.log(`${tag} finished generating typography`);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// ------- CORE (fixed grouping incl. semibold) -------
|
|
1224
|
+
|
|
1225
|
+
async function generateBodies(textMap = {}) {
|
|
1226
|
+
console.log(`${tag} generating bodies...`);
|
|
1227
|
+
const outDir = path.join(paths.styles, 'core');
|
|
1228
|
+
await fs$1.mkdir(outDir, {
|
|
1229
|
+
recursive: true
|
|
1230
|
+
});
|
|
1231
|
+
const keys = Object.keys(textMap || {}).filter(k => k.startsWith('body-'));
|
|
1232
|
+
|
|
1233
|
+
// Normalize base: remove a single "mobile" segment after "body-"
|
|
1234
|
+
function baseKey(k) {
|
|
1235
|
+
const parts = k.split('-');
|
|
1236
|
+
const idx = parts.indexOf('mobile');
|
|
1237
|
+
if (idx !== -1) parts.splice(idx, 1);
|
|
1238
|
+
return parts.join('-');
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/** @type {Map<string,{desktop:string|null,mobile:string|null}>} */
|
|
1242
|
+
const groups = new Map();
|
|
1243
|
+
for (const k of keys) {
|
|
1244
|
+
const base = baseKey(k);
|
|
1245
|
+
const g = groups.get(base) ?? {
|
|
1246
|
+
desktop: null,
|
|
1247
|
+
mobile: null
|
|
1248
|
+
};
|
|
1249
|
+
if (k.includes('-mobile-')) g.mobile = k;else g.desktop = k;
|
|
1250
|
+
groups.set(base, g);
|
|
1251
|
+
}
|
|
1252
|
+
const blocks = [];
|
|
1253
|
+
for (const [base, {
|
|
1254
|
+
desktop,
|
|
1255
|
+
mobile
|
|
1256
|
+
}] of [...groups.entries()].sort()) {
|
|
1257
|
+
const hasMobile = !!mobile;
|
|
1258
|
+
const hasDesktop = !!desktop;
|
|
1259
|
+
const mobileRef = hasMobile ? mobile : desktop;
|
|
1260
|
+
const desktopUpgrade = hasMobile && hasDesktop ? `\n @apply desktop-xs:text-${desktop} desktop-xs:leading-${desktop};` : ``;
|
|
1261
|
+
|
|
1262
|
+
// collapse “body-body-x” → “body-x”
|
|
1263
|
+
const isDouble = base.startsWith('body-body-');
|
|
1264
|
+
const utilName = isDouble ? base.replace(/^body-body-/, 'body-') : base;
|
|
1265
|
+
|
|
1266
|
+
// main utility
|
|
1267
|
+
blocks.push(`@utility ${utilName} {
|
|
1268
|
+
@apply text-${mobileRef} leading-${mobileRef};${desktopUpgrade}
|
|
1269
|
+
}`);
|
|
1270
|
+
|
|
1271
|
+
// only add numeric alias if no collapse happened
|
|
1272
|
+
if (!isDouble) {
|
|
1273
|
+
const m = /^body-body-(\d+)$/i.exec(base);
|
|
1274
|
+
if (m) {
|
|
1275
|
+
const n = m[1];
|
|
1276
|
+
blocks.push(`@utility body-${n} {
|
|
1277
|
+
@apply text-${mobileRef} leading-${mobileRef};${desktopUpgrade}
|
|
1278
|
+
}`);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
const out = `/* Auto-generated. Do not edit by hand. */
|
|
1283
|
+
${blocks.join('\n\n')}
|
|
1284
|
+
`;
|
|
1285
|
+
await fs$1.writeFile(path.join(outDir, 'body.css'), out, 'utf8');
|
|
1286
|
+
console.log(`${tag} finished generating bodies`);
|
|
1287
|
+
}
|
|
1288
|
+
async function generateHeadlines(textMap = {}) {
|
|
1289
|
+
console.log(`${tag} generating headlines...`);
|
|
1290
|
+
const outDir = path.join(paths.styles, 'core');
|
|
1291
|
+
await fs$1.mkdir(outDir, {
|
|
1292
|
+
recursive: true
|
|
1293
|
+
});
|
|
1294
|
+
const keys = Object.keys(textMap || {}).filter(k => k.startsWith('headline-'));
|
|
1295
|
+
|
|
1296
|
+
// collect ids present in either mobile or desktop
|
|
1297
|
+
const ids = new Set();
|
|
1298
|
+
for (const k of keys) {
|
|
1299
|
+
let m = /^headline-h(\d+)$/i.exec(k);
|
|
1300
|
+
if (m) {
|
|
1301
|
+
ids.add(+m[1]);
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
m = /^headline-mobile-h(\d+)$/i.exec(k);
|
|
1305
|
+
if (m) {
|
|
1306
|
+
ids.add(+m[1]);
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
const blocks = [];
|
|
1311
|
+
for (const n of [...ids].sort((a, b) => a - b)) {
|
|
1312
|
+
const mobileKey = keys.includes(`headline-mobile-h${n}`) ? `headline-mobile-h${n}` : `headline-h${n}`;
|
|
1313
|
+
const hasDesktop = keys.includes(`headline-h${n}`);
|
|
1314
|
+
const desktopKey = hasDesktop ? `headline-h${n}` : null;
|
|
1315
|
+
const desktopUpgrade = hasDesktop ? `\n @apply desktop-xs:text-${desktopKey} desktop-xs:leading-${desktopKey};` : ``;
|
|
1316
|
+
blocks.push(`@utility headline-h${n} {
|
|
1317
|
+
@apply text-${mobileKey} leading-${mobileKey};
|
|
1318
|
+
@apply font-default;${desktopUpgrade}
|
|
1319
|
+
}`);
|
|
1320
|
+
}
|
|
1321
|
+
const out = `/* Auto-generated. Do not edit by hand. */
|
|
1322
|
+
${blocks.join('\n\n')}
|
|
1323
|
+
`;
|
|
1324
|
+
await fs$1.writeFile(path.join(outDir, 'headline.css'), out, 'utf8');
|
|
1325
|
+
console.log(`${tag} finished generating headlines`);
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
var generateTailwind$1 = /*#__PURE__*/Object.freeze({
|
|
1329
|
+
__proto__: null,
|
|
1330
|
+
default: generateTailwind,
|
|
1331
|
+
generateTailwind: generateTailwind,
|
|
1332
|
+
readFigma: readFigma
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
export { generateTailwind$1 as g, parseButtons as p, readFigma as r };
|