stera-ui 0.2.1 → 0.3.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/dist/{chunk-KIEC5SPW.js → chunk-26HP67ZM.js} +315 -2
- package/dist/{chunk-AXIACJ2C.js → chunk-3KADOWKC.js} +26 -4
- package/dist/cli.js +371 -281
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -2
- package/dist/schema/index.d.ts +287 -27
- package/dist/schema/index.js +3 -1
- package/dist/utils/index.d.ts +28 -2
- package/dist/utils/index.js +5 -1
- package/package.json +2 -1
|
@@ -334,7 +334,7 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
|
|
|
334
334
|
const written = [];
|
|
335
335
|
const skipped = [];
|
|
336
336
|
for (const item of items) {
|
|
337
|
-
for (const file of item.files) {
|
|
337
|
+
for (const file of item.files ?? []) {
|
|
338
338
|
const outputPath = resolveOutputPath(config, projectRoot, file);
|
|
339
339
|
const relativePath = path6.relative(projectRoot, outputPath);
|
|
340
340
|
if (fs5.existsSync(outputPath)) {
|
|
@@ -360,6 +360,317 @@ async function writeComponentFiles(items, config, projectRoot, options = {}) {
|
|
|
360
360
|
return { written, skipped };
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
// src/utils/update-fonts.ts
|
|
364
|
+
import { existsSync, promises as fs6 } from "fs";
|
|
365
|
+
import path7 from "path";
|
|
366
|
+
import {
|
|
367
|
+
Project,
|
|
368
|
+
ScriptKind,
|
|
369
|
+
SyntaxKind,
|
|
370
|
+
VariableDeclarationKind
|
|
371
|
+
} from "ts-morph";
|
|
372
|
+
var ROOT_FONT_VARIABLES = /* @__PURE__ */ new Set([
|
|
373
|
+
"--font-sans",
|
|
374
|
+
"--font-serif",
|
|
375
|
+
"--font-mono"
|
|
376
|
+
]);
|
|
377
|
+
function massageTreeForFonts(fonts, project) {
|
|
378
|
+
const result = {
|
|
379
|
+
cssVars: {},
|
|
380
|
+
dependencies: [],
|
|
381
|
+
cssImports: []
|
|
382
|
+
};
|
|
383
|
+
if (fonts.length === 0) return result;
|
|
384
|
+
const isNext = project.framework === "next";
|
|
385
|
+
for (const font of fonts) {
|
|
386
|
+
if (isNext) {
|
|
387
|
+
result.cssVars[font.font.variable] = `var(${font.font.variable})`;
|
|
388
|
+
} else {
|
|
389
|
+
const fontName = font.name.replace(/^font-/, "");
|
|
390
|
+
const fontSourceDep = font.font.dependency ?? `@fontsource-variable/${fontName}`;
|
|
391
|
+
result.dependencies.push(fontSourceDep);
|
|
392
|
+
result.cssImports.push(`@import "${fontSourceDep}";`);
|
|
393
|
+
result.cssVars[font.font.variable] = font.font.family;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
async function updateFonts(fonts, project, cwd, options = {}) {
|
|
399
|
+
if (fonts.length === 0) return;
|
|
400
|
+
if (project.framework !== "next") return;
|
|
401
|
+
const layoutPath = findLayoutFile(project, cwd);
|
|
402
|
+
if (!layoutPath) return;
|
|
403
|
+
if (options.silent) {
|
|
404
|
+
try {
|
|
405
|
+
const layoutContent = await fs6.readFile(layoutPath, "utf-8");
|
|
406
|
+
const updatedContent = transformLayoutFonts(layoutContent, fonts);
|
|
407
|
+
if (updatedContent !== layoutContent) {
|
|
408
|
+
await fs6.writeFile(layoutPath, updatedContent, "utf-8");
|
|
409
|
+
}
|
|
410
|
+
} catch {
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const spinner = createSpinner("Updating fonts");
|
|
415
|
+
try {
|
|
416
|
+
const layoutContent = await fs6.readFile(layoutPath, "utf-8");
|
|
417
|
+
const updatedContent = transformLayoutFonts(layoutContent, fonts);
|
|
418
|
+
if (updatedContent !== layoutContent) {
|
|
419
|
+
await fs6.writeFile(layoutPath, updatedContent, "utf-8");
|
|
420
|
+
}
|
|
421
|
+
spinner.succeed("Updated layout with font imports");
|
|
422
|
+
} catch {
|
|
423
|
+
spinner.fail("Failed to update fonts");
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function findLayoutFile(project, cwd) {
|
|
427
|
+
if (!project.layoutFile) return null;
|
|
428
|
+
const fullPath = path7.join(cwd, project.layoutFile);
|
|
429
|
+
return existsSync(fullPath) ? fullPath : null;
|
|
430
|
+
}
|
|
431
|
+
function transformLayoutFonts(input, fonts) {
|
|
432
|
+
const project = new Project({ compilerOptions: {} });
|
|
433
|
+
const sourceFile = project.createSourceFile("layout.tsx", input, {
|
|
434
|
+
scriptKind: ScriptKind.TSX
|
|
435
|
+
});
|
|
436
|
+
const googleFonts = fonts.filter((f) => f.font.provider === "google");
|
|
437
|
+
const fontVariableNames = [];
|
|
438
|
+
const fontUtilityClasses = [];
|
|
439
|
+
for (const font of googleFonts) {
|
|
440
|
+
const importName = font.font.import;
|
|
441
|
+
if (!importName) continue;
|
|
442
|
+
const existingImport = sourceFile.getImportDeclaration(
|
|
443
|
+
(decl) => decl.getModuleSpecifierValue() === "next/font/google"
|
|
444
|
+
);
|
|
445
|
+
let hasExistingImport = false;
|
|
446
|
+
if (existingImport) {
|
|
447
|
+
const namedImports = existingImport.getNamedImports();
|
|
448
|
+
hasExistingImport = namedImports.some(
|
|
449
|
+
(imp) => imp.getName() === importName
|
|
450
|
+
);
|
|
451
|
+
if (!hasExistingImport) {
|
|
452
|
+
existingImport.addNamedImport(importName);
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
sourceFile.addImportDeclaration({
|
|
456
|
+
moduleSpecifier: "next/font/google",
|
|
457
|
+
namedImports: [importName]
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
const varName = getFontVariableName(importName, font.font.variable);
|
|
461
|
+
const fontOptions = buildFontOptions(font);
|
|
462
|
+
const existingVarDecl = findFontVariableDeclaration(
|
|
463
|
+
sourceFile,
|
|
464
|
+
font.font.variable
|
|
465
|
+
);
|
|
466
|
+
if (hasExistingImport && !existingVarDecl && isRootFontVariable(font.font.variable)) {
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (existingVarDecl) {
|
|
470
|
+
existingVarDecl.setInitializer(`${importName}(${fontOptions})`);
|
|
471
|
+
if (existingVarDecl.getName() !== varName) {
|
|
472
|
+
existingVarDecl.rename(varName);
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
const insertPosition = findInsertPosition(sourceFile);
|
|
476
|
+
const statement = sourceFile.insertVariableStatement(insertPosition, {
|
|
477
|
+
declarationKind: VariableDeclarationKind.Const,
|
|
478
|
+
declarations: [
|
|
479
|
+
{
|
|
480
|
+
name: varName,
|
|
481
|
+
initializer: `${importName}(${fontOptions})`
|
|
482
|
+
}
|
|
483
|
+
]
|
|
484
|
+
});
|
|
485
|
+
statement.appendWhitespace("\n");
|
|
486
|
+
}
|
|
487
|
+
fontVariableNames.push(varName);
|
|
488
|
+
if (shouldApplyFontUtilityToHtml(font)) {
|
|
489
|
+
fontUtilityClasses.push(font.font.variable.replace("--", ""));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const fontFamilyClasses = /* @__PURE__ */ new Set(["font-sans", "font-serif", "font-mono"]);
|
|
493
|
+
const lastFontFamilyClass = [...fontUtilityClasses].reverse().find((cls) => fontFamilyClasses.has(cls));
|
|
494
|
+
const filteredUtilityClasses = fontUtilityClasses.filter(
|
|
495
|
+
(cls) => !fontFamilyClasses.has(cls)
|
|
496
|
+
);
|
|
497
|
+
if (lastFontFamilyClass) {
|
|
498
|
+
filteredUtilityClasses.unshift(lastFontFamilyClass);
|
|
499
|
+
}
|
|
500
|
+
if (fontVariableNames.length > 0) {
|
|
501
|
+
updateHtmlClassName(sourceFile, fontVariableNames, filteredUtilityClasses);
|
|
502
|
+
}
|
|
503
|
+
return sourceFile.getFullText();
|
|
504
|
+
}
|
|
505
|
+
function buildFontOptions(font) {
|
|
506
|
+
const options = {};
|
|
507
|
+
if (font.font.subsets?.length) options.subsets = font.font.subsets;
|
|
508
|
+
if (font.font.weight?.length) options.weight = font.font.weight;
|
|
509
|
+
options.variable = font.font.variable;
|
|
510
|
+
return JSON.stringify(options).replace(/"([^"]+)":/g, "$1:").replace(/"/g, "'");
|
|
511
|
+
}
|
|
512
|
+
function isRootFontVariable(variable) {
|
|
513
|
+
return ROOT_FONT_VARIABLES.has(variable);
|
|
514
|
+
}
|
|
515
|
+
function shouldApplyFontUtilityToHtml(font) {
|
|
516
|
+
return !font.font.selector && isRootFontVariable(font.font.variable);
|
|
517
|
+
}
|
|
518
|
+
function getFontVariableName(importName, variable) {
|
|
519
|
+
const baseName = toCamelCase(importName);
|
|
520
|
+
if (isRootFontVariable(variable)) return baseName;
|
|
521
|
+
return `${baseName}${toPascalCase(variable.replace(/^--font-/, ""))}`;
|
|
522
|
+
}
|
|
523
|
+
function toCamelCase(str) {
|
|
524
|
+
return str.split("_").map(
|
|
525
|
+
(part, index) => index === 0 ? part.toLowerCase() : part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
|
|
526
|
+
).join("");
|
|
527
|
+
}
|
|
528
|
+
function toPascalCase(str) {
|
|
529
|
+
return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
530
|
+
}
|
|
531
|
+
function findFontVariableDeclaration(sourceFile, variable) {
|
|
532
|
+
for (const statement of sourceFile.getVariableStatements()) {
|
|
533
|
+
for (const declaration of statement.getDeclarations()) {
|
|
534
|
+
const initializer = declaration.getInitializer();
|
|
535
|
+
if (!initializer || initializer.getKind() !== SyntaxKind.CallExpression)
|
|
536
|
+
continue;
|
|
537
|
+
const args = initializer.getArguments();
|
|
538
|
+
if (args.length === 0) continue;
|
|
539
|
+
const argText = args[0].getText();
|
|
540
|
+
if (argText.includes("variable:") && argText.includes(variable)) {
|
|
541
|
+
return declaration;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
function findInsertPosition(sourceFile) {
|
|
548
|
+
const imports = sourceFile.getImportDeclarations();
|
|
549
|
+
if (imports.length > 0) {
|
|
550
|
+
return imports[imports.length - 1].getChildIndex() + 1;
|
|
551
|
+
}
|
|
552
|
+
return 0;
|
|
553
|
+
}
|
|
554
|
+
function updateHtmlClassName(sourceFile, fontVariableNames, fontUtilityClasses) {
|
|
555
|
+
const jsxElements = sourceFile.getDescendantsOfKind(
|
|
556
|
+
SyntaxKind.JsxOpeningElement
|
|
557
|
+
);
|
|
558
|
+
for (const element of jsxElements) {
|
|
559
|
+
if (element.getTagNameNode().getText() !== "html") continue;
|
|
560
|
+
const newUtilityClasses = fontUtilityClasses.map((cls) => `"${cls}"`);
|
|
561
|
+
const newVarExpressions = fontVariableNames.map(
|
|
562
|
+
(name) => `${name}.variable`
|
|
563
|
+
);
|
|
564
|
+
const allNewArgs = [...newUtilityClasses, ...newVarExpressions];
|
|
565
|
+
const classNameAttr = element.getAttribute("className");
|
|
566
|
+
if (!classNameAttr) {
|
|
567
|
+
element.addAttribute({
|
|
568
|
+
name: "className",
|
|
569
|
+
initializer: `{cn(${allNewArgs.join(", ")})}`
|
|
570
|
+
});
|
|
571
|
+
ensureCnImport(sourceFile);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (classNameAttr.getKind() !== SyntaxKind.JsxAttribute) return;
|
|
575
|
+
const jsxAttr = classNameAttr.asKindOrThrow(SyntaxKind.JsxAttribute);
|
|
576
|
+
const initializer = jsxAttr.getInitializer();
|
|
577
|
+
if (!initializer) return;
|
|
578
|
+
if (initializer.getKind() === SyntaxKind.StringLiteral) {
|
|
579
|
+
const currentValue = initializer.getText().slice(1, -1);
|
|
580
|
+
ensureCnImport(sourceFile);
|
|
581
|
+
jsxAttr.setInitializer(
|
|
582
|
+
`{cn("${currentValue}", ${allNewArgs.join(", ")})}`
|
|
583
|
+
);
|
|
584
|
+
} else if (initializer.getKind() === SyntaxKind.JsxExpression) {
|
|
585
|
+
const jsxExpr = initializer.asKindOrThrow(SyntaxKind.JsxExpression);
|
|
586
|
+
const expr = jsxExpr.getExpression();
|
|
587
|
+
if (!expr) return;
|
|
588
|
+
const exprText = expr.getText();
|
|
589
|
+
if (exprText.startsWith("cn(")) {
|
|
590
|
+
const hasAllFontVars = newVarExpressions.every(
|
|
591
|
+
(v) => exprText.includes(v)
|
|
592
|
+
);
|
|
593
|
+
const hasAllUtilityClasses = fontUtilityClasses.every(
|
|
594
|
+
(cls) => exprText.includes(`"${cls}"`)
|
|
595
|
+
);
|
|
596
|
+
if (hasAllFontVars && hasAllUtilityClasses) continue;
|
|
597
|
+
let cleanedExpr = exprText;
|
|
598
|
+
for (const varExpr of newVarExpressions) {
|
|
599
|
+
cleanedExpr = cleanedExpr.replace(
|
|
600
|
+
new RegExp(`,?\\s*${varExpr.replace(".", "\\.")}`, "g"),
|
|
601
|
+
""
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
cleanedExpr = cleanedExpr.replace(/cn\(\s*,/, "cn(");
|
|
605
|
+
const newExpr = cleanedExpr.replace(
|
|
606
|
+
/\)$/,
|
|
607
|
+
`, ${allNewArgs.join(", ")})`
|
|
608
|
+
);
|
|
609
|
+
jsxExpr.replaceWithText(`{${newExpr}}`);
|
|
610
|
+
} else if (/^\w+\.variable$/.test(exprText)) {
|
|
611
|
+
ensureCnImport(sourceFile);
|
|
612
|
+
jsxExpr.replaceWithText(`{cn(${allNewArgs.join(", ")})}`);
|
|
613
|
+
} else if (exprText.startsWith("`") && exprText.endsWith("`")) {
|
|
614
|
+
const cnArgs = parseTemplateLiteralToCnArgs(exprText);
|
|
615
|
+
ensureCnImport(sourceFile);
|
|
616
|
+
const allNewArgsSet = new Set(allNewArgs);
|
|
617
|
+
const fontFamilyLiterals = new Set(
|
|
618
|
+
["font-sans", "font-serif", "font-mono"].map((c) => `"${c}"`)
|
|
619
|
+
);
|
|
620
|
+
const cleanedCnArgs = cnArgs.filter(
|
|
621
|
+
(arg) => !allNewArgsSet.has(arg) && !fontFamilyLiterals.has(arg)
|
|
622
|
+
);
|
|
623
|
+
jsxExpr.replaceWithText(
|
|
624
|
+
`{cn(${[...cleanedCnArgs, ...allNewArgs].join(", ")})}`
|
|
625
|
+
);
|
|
626
|
+
} else {
|
|
627
|
+
ensureCnImport(sourceFile);
|
|
628
|
+
jsxExpr.replaceWithText(`{cn(${exprText}, ${allNewArgs.join(", ")})}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function ensureCnImport(sourceFile) {
|
|
634
|
+
const existingImport = sourceFile.getImportDeclaration(
|
|
635
|
+
(decl) => decl.getNamedImports().some((imp) => imp.getName() === "cn")
|
|
636
|
+
);
|
|
637
|
+
if (!existingImport) {
|
|
638
|
+
const utilsImport = sourceFile.getImportDeclaration(
|
|
639
|
+
(decl) => decl.getModuleSpecifierValue().includes("/lib/utils")
|
|
640
|
+
);
|
|
641
|
+
if (utilsImport) {
|
|
642
|
+
const namedImports = utilsImport.getNamedImports();
|
|
643
|
+
if (!namedImports.some((imp) => imp.getName() === "cn")) {
|
|
644
|
+
utilsImport.addNamedImport("cn");
|
|
645
|
+
}
|
|
646
|
+
} else {
|
|
647
|
+
sourceFile.addImportDeclaration({
|
|
648
|
+
moduleSpecifier: "@/lib/utils",
|
|
649
|
+
namedImports: ["cn"]
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function parseTemplateLiteralToCnArgs(templateLiteral) {
|
|
655
|
+
const staticArgs = [];
|
|
656
|
+
const variableArgs = [];
|
|
657
|
+
const content = templateLiteral.slice(1, -1);
|
|
658
|
+
const parts = content.split(/(\$\{[^}]+\})/);
|
|
659
|
+
for (const part of parts) {
|
|
660
|
+
if (!part) continue;
|
|
661
|
+
if (part.startsWith("${") && part.endsWith("}")) {
|
|
662
|
+
const expr = part.slice(2, -1).trim();
|
|
663
|
+
if (expr) variableArgs.push(expr);
|
|
664
|
+
} else {
|
|
665
|
+
const staticParts = part.trim().split(/\s+/).filter(Boolean);
|
|
666
|
+
for (const staticPart of staticParts) {
|
|
667
|
+
staticArgs.push(`"${staticPart}"`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return [...staticArgs, ...variableArgs];
|
|
672
|
+
}
|
|
673
|
+
|
|
363
674
|
export {
|
|
364
675
|
applyTransforms,
|
|
365
676
|
transformImports,
|
|
@@ -378,5 +689,7 @@ export {
|
|
|
378
689
|
createSpinner,
|
|
379
690
|
detectPackageManager,
|
|
380
691
|
installDependencies,
|
|
381
|
-
writeComponentFiles
|
|
692
|
+
writeComponentFiles,
|
|
693
|
+
massageTreeForFonts,
|
|
694
|
+
updateFonts
|
|
382
695
|
};
|
|
@@ -5,7 +5,8 @@ var registryItemTypeSchema = z.enum([
|
|
|
5
5
|
"registry:component",
|
|
6
6
|
"registry:hook",
|
|
7
7
|
"registry:lib",
|
|
8
|
-
"registry:style"
|
|
8
|
+
"registry:style",
|
|
9
|
+
"registry:font"
|
|
9
10
|
]);
|
|
10
11
|
var registryItemFileSchema = z.object({
|
|
11
12
|
path: z.string(),
|
|
@@ -13,13 +14,33 @@ var registryItemFileSchema = z.object({
|
|
|
13
14
|
content: z.string().optional(),
|
|
14
15
|
target: z.string().optional()
|
|
15
16
|
});
|
|
16
|
-
var
|
|
17
|
+
var registryItemFontSchema = z.object({
|
|
18
|
+
family: z.string(),
|
|
19
|
+
provider: z.literal("google"),
|
|
20
|
+
import: z.string(),
|
|
21
|
+
variable: z.string(),
|
|
22
|
+
weight: z.array(z.string()).optional(),
|
|
23
|
+
subsets: z.array(z.string()).optional(),
|
|
24
|
+
selector: z.string().optional(),
|
|
25
|
+
dependency: z.string().optional()
|
|
26
|
+
});
|
|
27
|
+
var registryItemCommonSchema = z.object({
|
|
17
28
|
name: z.string(),
|
|
18
|
-
type: registryItemTypeSchema,
|
|
19
29
|
dependencies: z.array(z.string()).optional(),
|
|
20
30
|
registryDependencies: z.array(z.string()).optional(),
|
|
21
|
-
files: z.array(registryItemFileSchema)
|
|
31
|
+
files: z.array(registryItemFileSchema).optional()
|
|
22
32
|
});
|
|
33
|
+
var registryItemSchema = z.discriminatedUnion("type", [
|
|
34
|
+
registryItemCommonSchema.extend({
|
|
35
|
+
type: z.literal("registry:font"),
|
|
36
|
+
font: registryItemFontSchema,
|
|
37
|
+
files: z.array(registryItemFileSchema).optional()
|
|
38
|
+
}),
|
|
39
|
+
registryItemCommonSchema.extend({
|
|
40
|
+
type: registryItemTypeSchema.exclude(["registry:font"]),
|
|
41
|
+
files: z.array(registryItemFileSchema)
|
|
42
|
+
})
|
|
43
|
+
]);
|
|
23
44
|
var registryIndexSchema = z.object({
|
|
24
45
|
name: z.string(),
|
|
25
46
|
items: z.array(registryItemSchema)
|
|
@@ -44,6 +65,7 @@ var configSchema = z.object({
|
|
|
44
65
|
export {
|
|
45
66
|
registryItemTypeSchema,
|
|
46
67
|
registryItemFileSchema,
|
|
68
|
+
registryItemFontSchema,
|
|
47
69
|
registryItemSchema,
|
|
48
70
|
registryIndexSchema,
|
|
49
71
|
configSchema
|