@digdir/designsystemet 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ If a [config file](#using-a-config-file) you can also re-run with `"clean": true
26
26
  > ⚠️ **WARNING** ⚠️
27
27
  > The design tokens created by this tool are considered an implementation detail, and is subject
28
28
  > to change at any time without being considered a breaking change. We **only** support customisations
29
- > done through the CLI options. Direct editing of the design tokens are **not** supported.
29
+ > done through the CLI options and config. Direct editing of the design tokens are **not** supported.
30
30
  >
31
31
  > Since tokens may be added or removed at any time, it is necessary to routinely re-run this
32
32
  > command when upgrading the libraries. This will remove any direct edits to the design tokens.
@@ -105,3 +105,11 @@ npx @digdir/designsystemet tokens build
105
105
  #### Complex config example
106
106
 
107
107
  Have a look at the `*.config.json` files under the `packages/cli` in the Github repo for more complex examples.
108
+
109
+ #### Create config from existing tokens
110
+
111
+ You can get a minimal config file, meaning without overrides, generated from existing design tokens using the following command:
112
+
113
+ ```sh
114
+ npx @digdir/designsystemet generate-config-from-tokens --dir <path to design tokens>
115
+ ```
@@ -42,6 +42,10 @@
42
42
  },
43
43
  "severity": {
44
44
  "danger": "#ff00ff"
45
+ },
46
+ "linkVisited": {
47
+ "light": "#ff1234",
48
+ "dark": "#1234ff"
45
49
  }
46
50
  }
47
51
  },
@@ -387,21 +387,26 @@ var colorCategorySchema = z.record(
387
387
  error: `Color names cannot include reserved names: ${RESERVED_COLORS.join(", ")}`
388
388
  }).describe("An object with one or more color definitions. The property name is used as the color name.");
389
389
  var colorModeOverrideSchema = z.object({
390
- light: colorSchema.optional(),
391
- dark: colorSchema.optional()
390
+ light: colorSchema.optional().describe("A hex color that overrides light mode"),
391
+ dark: colorSchema.optional().describe("A hex color that overrides dark mode")
392
392
  }).describe('Override values for semantic color tokens like "background-subtle", "border-default", etc.');
393
393
  var colorWeightOverrideSchema = z.partialRecord(z.enum([...colorNames]), colorModeOverrideSchema).describe('The name of the color to add overrides for, e.g. "accent"');
394
394
  var semanticColorOverrideSchema = z.record(z.string(), colorWeightOverrideSchema).describe("An object with color names as keys");
395
395
  var severityColorOverrideSchema = z.partialRecord(z.enum(baseColorNames), colorSchema.describe("A hex color, which is used for creating a color scale")).optional().describe("An object with severity color names as keys");
396
+ var linkVisitedOverrideSchema = z.object({
397
+ light: colorSchema.optional().describe("A hex color that overrides light mode"),
398
+ dark: colorSchema.optional().describe("A hex color that overrides dark mode")
399
+ }).describe('Overrides for the "link-visited" color');
396
400
  var overridesSchema = z.object({
397
401
  colors: semanticColorOverrideSchema.optional(),
398
- severity: severityColorOverrideSchema.optional()
402
+ severity: severityColorOverrideSchema.optional(),
403
+ linkVisited: linkVisitedOverrideSchema.optional()
399
404
  }).describe("Overrides for generated design tokens. Currently only supports colors defined in your theme").optional();
400
405
  var themeSchema = z.object({
401
406
  colors: z.object({
402
407
  main: colorCategorySchema,
403
408
  support: colorCategorySchema.optional().default({}),
404
- neutral: colorSchema
409
+ neutral: colorSchema.describe("A hex color, which is used for creating a color scale.")
405
410
  }).meta({ description: "Defines the colors for this theme" }),
406
411
  typography: z.object({
407
412
  fontFamily: z.string().meta({ description: "Sets the font-family for this theme" })
@@ -1 +1 @@
1
- {"version":3,"file":"designsystemet.d.ts","sourceRoot":"","sources":["../../bin/designsystemet.ts"],"names":[],"mappings":";AAcA,eAAO,MAAM,WAAW,4iBASvB,CAAC"}
1
+ {"version":3,"file":"designsystemet.d.ts","sourceRoot":"","sources":["../../bin/designsystemet.ts"],"names":[],"mappings":";AAeA,eAAO,MAAM,WAAW,4iBASvB,CAAC"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  // bin/designsystemet.ts
4
4
  import { Argument, createCommand, program } from "@commander-js/extra-typings";
5
- import pc11 from "picocolors";
5
+ import pc12 from "picocolors";
6
6
  import * as R29 from "ramda";
7
7
 
8
8
  // src/colors/colorMetadata.ts
@@ -410,13 +410,13 @@ var mkdir = async (dir, dry) => {
410
410
  }
411
411
  return fs.mkdir(dir, { recursive: true });
412
412
  };
413
- var writeFile = async (path5, data, dry) => {
413
+ var writeFile = async (path6, data, dry) => {
414
414
  if (dry) {
415
- console.log(`${pc2.blue("writeFile")} ${path5}`);
415
+ console.log(`${pc2.blue("writeFile")} ${path6}`);
416
416
  return Promise.resolve();
417
417
  }
418
- return fs.writeFile(path5, data, { encoding: "utf-8" }).catch((error) => {
419
- console.error(pc2.red(`Error writing file: ${path5}`));
418
+ return fs.writeFile(path6, data, { encoding: "utf-8" }).catch((error) => {
419
+ console.error(pc2.red(`Error writing file: ${path6}`));
420
420
  console.error(pc2.red(error));
421
421
  throw error;
422
422
  });
@@ -430,13 +430,13 @@ var cleanDir = async (dir, dry) => {
430
430
  \u{1F525} Cleaning dir ${pc2.red(`${dir.trim()}`)} `);
431
431
  return fs.rm(dir, { recursive: true, force: true });
432
432
  };
433
- var readFile = async (path5, dry, allowFileNotFound) => {
433
+ var readFile = async (path6, dry, allowFileNotFound) => {
434
434
  if (dry) {
435
- console.log(`${pc2.blue("readFile")} ${path5}`);
435
+ console.log(`${pc2.blue("readFile")} ${path6}`);
436
436
  return Promise.resolve("");
437
437
  }
438
438
  try {
439
- return await fs.readFile(path5, "utf-8");
439
+ return await fs.readFile(path6, "utf-8");
440
440
  } catch (error) {
441
441
  if (allowFileNotFound && error.code === "ENOENT") {
442
442
  return "";
@@ -861,7 +861,7 @@ import pc5 from "picocolors";
861
861
  // package.json
862
862
  var package_default = {
863
863
  name: "@digdir/designsystemet",
864
- version: "1.7.1",
864
+ version: "1.7.3",
865
865
  description: "CLI for Designsystemet",
866
866
  author: "Designsystemet team",
867
867
  engines: {
@@ -913,7 +913,8 @@ var package_default = {
913
913
  "test:tokens-build-config-tailwind": "pnpm run designsystemet tokens build -t ./temp/config/design-tokens -o ./temp/config/build --clean --experimental-tailwind",
914
914
  "test:tokens-create-and-build-options": "pnpm test:tokens-create-options && pnpm test:tokens-build",
915
915
  "test:tokens-create-and-build-config": "pnpm test:tokens-create-config && pnpm test:tokens-build-config",
916
- test: "node -v && pnpm test:tokens-create-and-build-options && pnpm test:tokens-create-and-build-config",
916
+ "test:generate-config-from-tokens": "pnpm run designsystemet generate-config-from-tokens -d ../../internal/design-tokens --dry",
917
+ test: "node -v && pnpm test:tokens-create-and-build-options && pnpm test:generate-config-from-tokens && pnpm test:tokens-create-and-build-config",
917
918
  "digdir:tokens-build": "pnpm run designsystemet tokens build -t ../../internal/design-tokens -o ../../packages/theme/brand --clean --experimental-tailwind",
918
919
  "digdir:tokens-create": "pnpm run designsystemet tokens create --config ./configs/digdir.config.json",
919
920
  "update:template": "tsx ./src/scripts/update-template.ts",
@@ -928,7 +929,7 @@ var package_default = {
928
929
  "change-case": "^5.4.4",
929
930
  "chroma-js": "^3.1.2",
930
931
  "colorjs.io": "^0.6.0-alpha.1",
931
- commander: "^14.0.1",
932
+ commander: "^14.0.2",
932
933
  "fast-glob": "^3.3.3",
933
934
  hsluv: "^1.0.1",
934
935
  "object-hash": "^3.0.0",
@@ -942,9 +943,9 @@ var package_default = {
942
943
  devDependencies: {
943
944
  "@tokens-studio/types": "0.5.2",
944
945
  "@types/apca-w3": "^0.1.3",
945
- "@types/chroma-js": "^3.1.1",
946
+ "@types/chroma-js": "^3.1.2",
946
947
  "@types/fs-extra": "^11.0.4",
947
- "@types/node": "^22.18.11",
948
+ "@types/node": "^22.19.0",
948
949
  "@types/object-hash": "^3.0.6",
949
950
  "@types/ramda": "^0.31.1",
950
951
  "fs-extra": "^11.3.2",
@@ -992,8 +993,8 @@ var pathStartsWithOneOf = R5.curry(
992
993
  }
993
994
  const tokenPath = mapToLowerCase(token.path);
994
995
  const matchPathsStartingWith = R5.map((pathOrString) => {
995
- const path5 = typeof pathOrString === "string" ? [pathOrString] : pathOrString;
996
- return R5.startsWith(mapToLowerCase(path5), tokenPath);
996
+ const path6 = typeof pathOrString === "string" ? [pathOrString] : pathOrString;
997
+ return R5.startsWith(mapToLowerCase(path6), tokenPath);
997
998
  }, paths);
998
999
  return hasAnyTruth(matchPathsStartingWith);
999
1000
  }
@@ -2428,8 +2429,8 @@ ${dynamicColors}`;
2428
2429
  import pc6 from "picocolors";
2429
2430
  import * as R20 from "ramda";
2430
2431
  var defaultFileHeader2 = `build: v${package_default.version}`;
2431
- var getFileNameWithoutExtension = (path5) => {
2432
- const pathSegments = path5.split("/");
2432
+ var getFileNameWithoutExtension = (path6) => {
2433
+ const pathSegments = path6.split("/");
2433
2434
  return pathSegments[pathSegments.length - 1].split(".").slice(0, -1).join(".");
2434
2435
  };
2435
2436
  var createThemeCSSFiles = ({
@@ -3864,6 +3865,7 @@ var generateColorScheme = (themeName, colorScheme2, colors2, overrides) => {
3864
3865
  baseColorsWithOverrides
3865
3866
  );
3866
3867
  const linkColor = generateColor(generateColorScale(dsLinkColor, colorScheme2));
3868
+ const linkOverride = overrides?.linkVisited?.[colorScheme2] ? { $type: "color", $value: overrides.linkVisited[colorScheme2] } : void 0;
3867
3869
  return {
3868
3870
  [themeName]: {
3869
3871
  ...main,
@@ -3871,7 +3873,7 @@ var generateColorScheme = (themeName, colorScheme2, colors2, overrides) => {
3871
3873
  neutral,
3872
3874
  ...globalColors,
3873
3875
  link: {
3874
- visited: linkColor[12]
3876
+ visited: linkOverride || linkColor[12]
3875
3877
  }
3876
3878
  }
3877
3879
  };
@@ -4346,18 +4348,218 @@ var createTokens = async (opts) => {
4346
4348
  return { tokenSets };
4347
4349
  };
4348
4350
 
4351
+ // src/tokens/generate-config.ts
4352
+ import fs3 from "fs/promises";
4353
+ import path3 from "path";
4354
+ import pc9 from "picocolors";
4355
+ async function readJsonFile(filePath) {
4356
+ try {
4357
+ const content = await fs3.readFile(filePath, "utf-8");
4358
+ return JSON.parse(content);
4359
+ } catch (err) {
4360
+ throw new Error(`Failed to read token file at ${filePath}: ${err instanceof Error ? err.message : String(err)}`);
4361
+ }
4362
+ }
4363
+ function extractBaseColor(colorScale) {
4364
+ if ("12" in colorScale && typeof colorScale["12"] === "object" && "$value" in colorScale["12"]) {
4365
+ const token = colorScale["12"];
4366
+ if (token.$type === "color") {
4367
+ return token.$value;
4368
+ }
4369
+ }
4370
+ return null;
4371
+ }
4372
+ async function discoverThemes(tokensDir) {
4373
+ const lightModePath = path3.join(tokensDir, "themes");
4374
+ try {
4375
+ const files = await fs3.readdir(lightModePath);
4376
+ const themes = files.filter((file) => file.endsWith(".json")).map((file) => file.replace(".json", ""));
4377
+ return themes;
4378
+ } catch {
4379
+ throw new Error(`Could not find themes. Make sure ${pc9.blue(lightModePath)} exists and contains theme JSON files.`);
4380
+ }
4381
+ }
4382
+ async function readThemeTokens(tokensDir, themeName) {
4383
+ const themePath = path3.join(tokensDir, "primitives", "modes", "color-scheme", "light", `${themeName}.json`);
4384
+ return readJsonFile(themePath);
4385
+ }
4386
+ async function readThemeConfig(tokensDir, themeName) {
4387
+ const themeConfigPath = path3.join(tokensDir, "themes", `${themeName}.json`);
4388
+ try {
4389
+ return await readJsonFile(themeConfigPath);
4390
+ } catch {
4391
+ return null;
4392
+ }
4393
+ }
4394
+ function extractBorderRadius(themeConfig) {
4395
+ if (!themeConfig || !("border-radius" in themeConfig)) {
4396
+ return void 0;
4397
+ }
4398
+ const borderRadius = themeConfig["border-radius"];
4399
+ if ("base" in borderRadius && typeof borderRadius.base === "object" && "$value" in borderRadius.base) {
4400
+ const token = borderRadius.base;
4401
+ return Number(token.$value);
4402
+ }
4403
+ return void 0;
4404
+ }
4405
+ function extractFontFamily(themeConfig) {
4406
+ if (!themeConfig || !("font-family" in themeConfig)) {
4407
+ return void 0;
4408
+ }
4409
+ const fontFamily = themeConfig["font-family"];
4410
+ if (typeof fontFamily === "object" && "$value" in fontFamily) {
4411
+ const token = fontFamily;
4412
+ const value = token.$value;
4413
+ if (value.startsWith("{") && value.endsWith("}")) {
4414
+ return void 0;
4415
+ }
4416
+ return value;
4417
+ }
4418
+ return void 0;
4419
+ }
4420
+ async function readTypographyConfig(tokensDir, themeName) {
4421
+ const typographyConfigPath = path3.join(
4422
+ tokensDir,
4423
+ "primitives",
4424
+ "modes",
4425
+ "typography",
4426
+ "primary",
4427
+ `${themeName}.json`
4428
+ );
4429
+ try {
4430
+ return await readJsonFile(typographyConfigPath);
4431
+ } catch {
4432
+ return null;
4433
+ }
4434
+ }
4435
+ function extractFontFamilyFromPrimitives(typographyConfig, themeName) {
4436
+ if (!typographyConfig) {
4437
+ return void 0;
4438
+ }
4439
+ const themeTypography = typographyConfig[themeName];
4440
+ if (!themeTypography || !("font-family" in themeTypography)) {
4441
+ return void 0;
4442
+ }
4443
+ const fontFamily = themeTypography["font-family"];
4444
+ if (typeof fontFamily === "object" && "$value" in fontFamily) {
4445
+ const token = fontFamily;
4446
+ return token.$value;
4447
+ }
4448
+ return void 0;
4449
+ }
4450
+ function categorizeColors(themeTokens, themeName) {
4451
+ const main = {};
4452
+ const support = {};
4453
+ let neutral = null;
4454
+ const builtInColors = ["neutral", "info", "success", "warning", "danger"];
4455
+ const specialKeys = ["link"];
4456
+ const themeColors = themeTokens[themeName];
4457
+ if (!themeColors) {
4458
+ return { main, support, neutral };
4459
+ }
4460
+ for (const [colorName, colorValue] of Object.entries(themeColors)) {
4461
+ if (specialKeys.includes(colorName)) {
4462
+ continue;
4463
+ }
4464
+ if (typeof colorValue === "object" && !("$value" in colorValue)) {
4465
+ const baseColor = extractBaseColor(colorValue);
4466
+ if (baseColor) {
4467
+ if (colorName === "neutral") {
4468
+ neutral = baseColor;
4469
+ } else if (builtInColors.includes(colorName)) {
4470
+ } else if (colorName === "accent") {
4471
+ main[colorName] = baseColor;
4472
+ } else {
4473
+ support[colorName] = baseColor;
4474
+ }
4475
+ }
4476
+ }
4477
+ }
4478
+ return { main, support, neutral };
4479
+ }
4480
+ async function generateConfigFromTokens(options) {
4481
+ const { tokensDir, dry = false } = options;
4482
+ console.log(`
4483
+ Reading tokens from ${pc9.blue(tokensDir)}`);
4484
+ const themes = await discoverThemes(tokensDir);
4485
+ if (themes.length === 0) {
4486
+ throw new Error(`
4487
+ No themes found in ${pc9.blue(tokensDir)}`);
4488
+ }
4489
+ console.log(`
4490
+ Found ${pc9.green(String(themes.length))} theme(s): ${themes.map((t) => pc9.cyan(t)).join(", ")}`);
4491
+ const config = {
4492
+ outDir: tokensDir,
4493
+ themes: {}
4494
+ };
4495
+ for (const themeName of themes) {
4496
+ console.log(`
4497
+ Processing theme ${pc9.cyan(themeName)}...`);
4498
+ const themeTokens = await readThemeTokens(tokensDir, themeName);
4499
+ const themeConfig = await readThemeConfig(tokensDir, themeName);
4500
+ const typographyConfig = await readTypographyConfig(tokensDir, themeName);
4501
+ const { main, support, neutral } = categorizeColors(themeTokens, themeName);
4502
+ if (Object.keys(main).length === 0) {
4503
+ console.warn(pc9.yellow(`
4504
+ Warning: No main colors found for theme ${themeName}`));
4505
+ }
4506
+ if (!neutral) {
4507
+ console.warn(pc9.yellow(`
4508
+ Warning: No neutral color found for theme ${themeName}`));
4509
+ continue;
4510
+ }
4511
+ const borderRadius = extractBorderRadius(themeConfig);
4512
+ const fontFamily = extractFontFamily(themeConfig) ?? extractFontFamilyFromPrimitives(typographyConfig, themeName);
4513
+ config.themes[themeName] = {
4514
+ colors: {
4515
+ main,
4516
+ support,
4517
+ neutral
4518
+ },
4519
+ borderRadius,
4520
+ typography: fontFamily ? { fontFamily } : void 0
4521
+ };
4522
+ console.log(
4523
+ `
4524
+ \u2705 Main colors: ${Object.keys(main).map((c) => pc9.cyan(c)).join(", ") || pc9.dim("none")}`
4525
+ );
4526
+ console.log(
4527
+ `
4528
+ \u2705 Support colors: ${Object.keys(support).map((c) => pc9.cyan(c)).join(", ") || pc9.dim("none")}`
4529
+ );
4530
+ console.log(`
4531
+ \u2705 Neutral: ${pc9.cyan(neutral)}`);
4532
+ if (borderRadius !== void 0) {
4533
+ console.log(`
4534
+ \u2705 Border radius: ${pc9.cyan(String(borderRadius))}`);
4535
+ }
4536
+ if (fontFamily) {
4537
+ console.log(`
4538
+ \u2705 Font family: ${pc9.cyan(fontFamily)}`);
4539
+ }
4540
+ }
4541
+ if (!dry && options.outFile) {
4542
+ const configJson = JSON.stringify(config, null, 2);
4543
+ await fs3.writeFile(options.outFile, configJson, "utf-8");
4544
+ console.log();
4545
+ console.log(`
4546
+ \u2705 Config file written to ${pc9.blue(options.outFile)}`);
4547
+ }
4548
+ return config;
4549
+ }
4550
+
4349
4551
  // bin/config.ts
4350
- import path4 from "path";
4351
- import pc10 from "picocolors";
4552
+ import path5 from "path";
4553
+ import pc11 from "picocolors";
4352
4554
  import * as R28 from "ramda";
4353
4555
 
4354
4556
  // src/config.ts
4355
- import pc9 from "picocolors";
4557
+ import pc10 from "picocolors";
4356
4558
  import * as R27 from "ramda";
4357
4559
  import { z } from "zod";
4358
4560
  import { fromError } from "zod-validation-error";
4359
- function mapPathToOptionName(path5) {
4360
- const normalisedPath = path5[0] === "themes" ? ["theme", ...R27.drop(2, path5)] : path5;
4561
+ function mapPathToOptionName(path6) {
4562
+ const normalisedPath = path6[0] === "themes" ? ["theme", ...R27.drop(2, path6)] : path6;
4361
4563
  const option = R27.path(normalisedPath, cliOptions);
4362
4564
  if (typeof option !== "string") {
4363
4565
  return;
@@ -4372,12 +4574,12 @@ function makeFriendlyError(err) {
4372
4574
  const optionName = mapPathToOptionName(issue.path);
4373
4575
  const errorCode = `(error code: ${issue.code})`;
4374
4576
  const optionMessage = optionName ? ` or CLI option --${optionName}` : "";
4375
- return ` - Error in JSON value ${pc9.red(issuePath)}${optionMessage}:
4376
- ${issue.message} ${pc9.dim(errorCode)}`;
4577
+ return ` - Error in JSON value ${pc10.red(issuePath)}${optionMessage}:
4578
+ ${issue.message} ${pc10.dim(errorCode)}`;
4377
4579
  }).join("\n")
4378
4580
  });
4379
4581
  } catch (_err2) {
4380
- console.error(pc9.red(err instanceof Error ? err.message : "Unknown error occurred while parsing config file"));
4582
+ console.error(pc10.red(err instanceof Error ? err.message : "Unknown error occurred while parsing config file"));
4381
4583
  console.error(err instanceof Error ? err.stack : "No stack trace available");
4382
4584
  }
4383
4585
  }
@@ -4385,7 +4587,7 @@ function validateConfig(schema, unvalidatedConfig, configPath) {
4385
4587
  try {
4386
4588
  return schema.parse(unvalidatedConfig);
4387
4589
  } catch (err) {
4388
- console.error(pc9.redBright(`Invalid config file at ${pc9.red(configPath)}`));
4590
+ console.error(pc10.redBright(`Invalid config file at ${pc10.red(configPath)}`));
4389
4591
  const validationError = makeFriendlyError(err);
4390
4592
  console.error(validationError?.toString());
4391
4593
  process.exit(1);
@@ -4398,7 +4600,7 @@ function parseConfig(configFile, configPath) {
4398
4600
  try {
4399
4601
  return JSON.parse(configFile);
4400
4602
  } catch (err) {
4401
- console.error(pc9.redBright(`Failed parsing config file at ${pc9.red(configPath)}`));
4603
+ console.error(pc10.redBright(`Failed parsing config file at ${pc10.red(configPath)}`));
4402
4604
  const validationError = makeFriendlyError(err);
4403
4605
  console.error(validationError?.toString());
4404
4606
  process.exit(1);
@@ -4428,21 +4630,26 @@ var colorCategorySchema = z.record(
4428
4630
  error: `Color names cannot include reserved names: ${RESERVED_COLORS.join(", ")}`
4429
4631
  }).describe("An object with one or more color definitions. The property name is used as the color name.");
4430
4632
  var colorModeOverrideSchema = z.object({
4431
- light: colorSchema.optional(),
4432
- dark: colorSchema.optional()
4633
+ light: colorSchema.optional().describe("A hex color that overrides light mode"),
4634
+ dark: colorSchema.optional().describe("A hex color that overrides dark mode")
4433
4635
  }).describe('Override values for semantic color tokens like "background-subtle", "border-default", etc.');
4434
4636
  var colorWeightOverrideSchema = z.partialRecord(z.enum([...colorNames]), colorModeOverrideSchema).describe('The name of the color to add overrides for, e.g. "accent"');
4435
4637
  var semanticColorOverrideSchema = z.record(z.string(), colorWeightOverrideSchema).describe("An object with color names as keys");
4436
4638
  var severityColorOverrideSchema = z.partialRecord(z.enum(baseColorNames), colorSchema.describe("A hex color, which is used for creating a color scale")).optional().describe("An object with severity color names as keys");
4639
+ var linkVisitedOverrideSchema = z.object({
4640
+ light: colorSchema.optional().describe("A hex color that overrides light mode"),
4641
+ dark: colorSchema.optional().describe("A hex color that overrides dark mode")
4642
+ }).describe('Overrides for the "link-visited" color');
4437
4643
  var overridesSchema = z.object({
4438
4644
  colors: semanticColorOverrideSchema.optional(),
4439
- severity: severityColorOverrideSchema.optional()
4645
+ severity: severityColorOverrideSchema.optional(),
4646
+ linkVisited: linkVisitedOverrideSchema.optional()
4440
4647
  }).describe("Overrides for generated design tokens. Currently only supports colors defined in your theme").optional();
4441
4648
  var themeSchema = z.object({
4442
4649
  colors: z.object({
4443
4650
  main: colorCategorySchema,
4444
4651
  support: colorCategorySchema.optional().default({}),
4445
- neutral: colorSchema
4652
+ neutral: colorSchema.describe("A hex color, which is used for creating a color scale.")
4446
4653
  }).meta({ description: "Defines the colors for this theme" }),
4447
4654
  typography: z.object({
4448
4655
  fontFamily: z.string().meta({ description: "Sets the font-family for this theme" })
@@ -4474,7 +4681,7 @@ var getCliOption = getOptionIfMatchingSource("cli", "default");
4474
4681
 
4475
4682
  // bin/config.ts
4476
4683
  async function readConfigFile(configPath, allowFileNotFound = true) {
4477
- const resolvedPath = path4.resolve(process.cwd(), configPath);
4684
+ const resolvedPath = path5.resolve(process.cwd(), configPath);
4478
4685
  let configFile;
4479
4686
  try {
4480
4687
  configFile = await readFile(resolvedPath, false, allowFileNotFound);
@@ -4482,11 +4689,11 @@ async function readConfigFile(configPath, allowFileNotFound = true) {
4482
4689
  if (allowFileNotFound) {
4483
4690
  return "";
4484
4691
  }
4485
- console.error(pc10.redBright(`Could not read config file at ${pc10.blue(resolvedPath)}`));
4692
+ console.error(pc11.redBright(`Could not read config file at ${pc11.blue(resolvedPath)}`));
4486
4693
  throw err;
4487
4694
  }
4488
4695
  if (configFile) {
4489
- console.log(`Found config file: ${pc10.green(resolvedPath)}`);
4696
+ console.log(`Found config file: ${pc11.green(resolvedPath)}`);
4490
4697
  }
4491
4698
  return configFile;
4492
4699
  }
@@ -4497,7 +4704,7 @@ async function parseCreateConfig(configFile, options) {
4497
4704
  (x) => /* @__PURE__ */ new Set([...R28.keys(x.colors.main), ...R28.keys(x.colors.support)])
4498
4705
  );
4499
4706
  if (!R28.all(R28.equals(R28.__, themeColors[0]), themeColors)) {
4500
- console.error(pc10.redBright(`In config, all themes must have the same custom color names, but we found:`));
4707
+ console.error(pc11.redBright(`In config, all themes must have the same custom color names, but we found:`));
4501
4708
  const themeNames = R28.keys(configParsed.themes ?? {});
4502
4709
  themeColors.forEach((colors2, index) => {
4503
4710
  const colorNames2 = Array.from(colors2);
@@ -4561,11 +4768,11 @@ var DEFAULT_THEME_NAME = "theme";
4561
4768
  var DEFAULT_CONFIG_FILE = "designsystemet.config.json";
4562
4769
  function makeTokenCommands() {
4563
4770
  const tokenCmd = createCommand("tokens");
4564
- tokenCmd.command("build").description("Build Designsystemet tokens").option("-t, --tokens <string>", `Path to ${pc11.blue("design-tokens")}`, DEFAULT_TOKENS_CREATE_DIR).option(
4771
+ tokenCmd.command("build").description("Build Designsystemet tokens").option("-t, --tokens <string>", `Path to ${pc12.blue("design-tokens")}`, DEFAULT_TOKENS_CREATE_DIR).option(
4565
4772
  "-o, --out-dir <string>",
4566
- `Output directory for built ${pc11.blue("design-tokens")}`,
4773
+ `Output directory for built ${pc12.blue("design-tokens")}`,
4567
4774
  DEFAULT_TOKENS_BUILD_DIR
4568
- ).option(`--${cliOptions.clean} [boolean]`, "Clean output directory before building tokens", parseBoolean, false).option("--dry [boolean]", `Dry run for built ${pc11.blue("design-tokens")}`, parseBoolean, false).option("--verbose", "Enable verbose output", false).option("--config <string>", `Path to config file (default: "${DEFAULT_CONFIG_FILE}")`).option("--experimental-tailwind", "Generate Tailwind CSS classes for tokens", false).action(async (opts) => {
4775
+ ).option(`--${cliOptions.clean} [boolean]`, "Clean output directory before building tokens", parseBoolean, false).option("--dry [boolean]", `Dry run for built ${pc12.blue("design-tokens")}`, parseBoolean, false).option("--verbose", "Enable verbose output", false).option("--config <string>", `Path to config file (default: "${DEFAULT_CONFIG_FILE}")`).option("--experimental-tailwind", "Generate Tailwind CSS classes for tokens", false).action(async (opts) => {
4569
4776
  console.log(figletAscii);
4570
4777
  const { verbose, clean, dry, experimentalTailwind } = opts;
4571
4778
  const tokensDir = typeof opts.tokens === "string" ? opts.tokens : DEFAULT_TOKENS_CREATE_DIR;
@@ -4583,9 +4790,9 @@ function makeTokenCommands() {
4583
4790
  });
4584
4791
  tokenCmd.command("create").description("Create Designsystemet tokens").option(`-m, --${cliOptions.theme.colors.main} <name:hex...>`, `Main colors`, parseColorValues).option(`-s, --${cliOptions.theme.colors.support} <name:hex...>`, `Support colors`, parseColorValues).option(`-n, --${cliOptions.theme.colors.neutral} <hex>`, `Neutral hex color`, convertToHex).option(
4585
4792
  `-o, --${cliOptions.outDir} <string>`,
4586
- `Output directory for created ${pc11.blue("design-tokens")}`,
4793
+ `Output directory for created ${pc12.blue("design-tokens")}`,
4587
4794
  DEFAULT_TOKENS_CREATE_DIR
4588
- ).option(`--${cliOptions.clean} [boolean]`, "Clean output directory before creating tokens", parseBoolean, false).option("--dry [boolean]", `Dry run for created ${pc11.blue("design-tokens")}`, parseBoolean, false).option(`-f, --${cliOptions.theme.typography.fontFamily} <string>`, `Font family (experimental)`, DEFAULT_FONT).option(
4795
+ ).option(`--${cliOptions.clean} [boolean]`, "Clean output directory before creating tokens", parseBoolean, false).option("--dry [boolean]", `Dry run for created ${pc12.blue("design-tokens")}`, parseBoolean, false).option(`-f, --${cliOptions.theme.typography.fontFamily} <string>`, `Font family (experimental)`, DEFAULT_FONT).option(
4589
4796
  `-b, --${cliOptions.theme.borderRadius} <number>`,
4590
4797
  `Unitless base border-radius in px`,
4591
4798
  (radiusAsString) => Number(radiusAsString),
@@ -4615,6 +4822,28 @@ function makeTokenCommands() {
4615
4822
  return tokenCmd;
4616
4823
  }
4617
4824
  program.addCommand(makeTokenCommands());
4825
+ program.command("generate-config-from-tokens").description("Generate a config file from existing design tokens. Will not include overrides.").option("-d, --dir <string>", "Path to design tokens directory", DEFAULT_TOKENS_CREATE_DIR).option("-o, --out <string>", "Output path for config file", DEFAULT_CONFIG_FILE).option("--dry [boolean]", "Dry run - show config without writing file", parseBoolean, false).action(async (opts) => {
4826
+ console.log(figletAscii);
4827
+ const { dry } = opts;
4828
+ const tokensDir = typeof opts.dir === "string" ? opts.dir : DEFAULT_TOKENS_CREATE_DIR;
4829
+ const outFile = typeof opts.out === "string" ? opts.out : DEFAULT_CONFIG_FILE;
4830
+ try {
4831
+ const config = await generateConfigFromTokens({
4832
+ tokensDir,
4833
+ outFile: dry ? void 0 : outFile,
4834
+ dry
4835
+ });
4836
+ if (dry) {
4837
+ console.log();
4838
+ console.log("Generated config (dry run):");
4839
+ console.log(JSON.stringify(config, null, 2));
4840
+ }
4841
+ } catch (error) {
4842
+ console.error(pc12.redBright("Error generating config:"));
4843
+ console.error(error instanceof Error ? error.message : String(error));
4844
+ process.exit(1);
4845
+ }
4846
+ });
4618
4847
  program.command("migrate").description("run a Designsystemet migration").addArgument(new Argument("[migration]", "Available migrations").choices(Object.keys(migrations_default))).option("-l --list", "List available migrations").option("-g --glob <glob>", "Glob for files upon which to apply the migration", "./**/*.(tsx|css)").action((migrationKey, opts) => {
4619
4848
  console.log(figletAscii);
4620
4849
  const { glob: glob2, list } = opts;
@@ -4628,8 +4857,8 @@ program.command("migrate").description("run a Designsystemet migration").addArgu
4628
4857
  console.error("Migration not found!");
4629
4858
  throw "Aborting";
4630
4859
  }
4631
- console.log(`Applying migration ${pc11.blue(migrationKey)} with glob: ${pc11.green(glob2)}`);
4632
- migration?.(glob2).then(() => console.log(`Migration ${pc11.blue(migrationKey)} finished`)).catch((error) => console.log(error));
4860
+ console.log(`Applying migration ${pc12.blue(migrationKey)} with glob: ${pc12.green(glob2)}`);
4861
+ migration?.(glob2).then(() => console.log(`Migration ${pc12.blue(migrationKey)} finished`)).catch((error) => console.log(error));
4633
4862
  } else {
4634
4863
  console.log("Migrate: please specify a migration name or --list");
4635
4864
  }
@@ -47,7 +47,7 @@
47
47
  }
48
48
  },
49
49
  "neutral": {
50
- "description": "A hex color, which is used for creating a color scale. Invalid color names: neutral, success, warning, danger, info"
50
+ "description": "A hex color, which is used for creating a color scale."
51
51
  }
52
52
  },
53
53
  "required": [
@@ -114,10 +114,10 @@
114
114
  "type": "object",
115
115
  "properties": {
116
116
  "light": {
117
- "description": "A hex color, which is used for creating a color scale. Invalid color names: neutral, success, warning, danger, info"
117
+ "description": "A hex color that overrides light mode"
118
118
  },
119
119
  "dark": {
120
- "description": "A hex color, which is used for creating a color scale. Invalid color names: neutral, success, warning, danger, info"
120
+ "description": "A hex color that overrides dark mode"
121
121
  }
122
122
  },
123
123
  "additionalProperties": false
@@ -139,6 +139,19 @@
139
139
  "additionalProperties": {
140
140
  "description": "A hex color, which is used for creating a color scale"
141
141
  }
142
+ },
143
+ "linkVisited": {
144
+ "description": "Overrides for the \"link-visited\" color",
145
+ "type": "object",
146
+ "properties": {
147
+ "light": {
148
+ "description": "A hex color that overrides light mode"
149
+ },
150
+ "dark": {
151
+ "description": "A hex color that overrides dark mode"
152
+ }
153
+ },
154
+ "additionalProperties": false
142
155
  }
143
156
  },
144
157
  "additionalProperties": false
@@ -39,6 +39,10 @@ declare const overridesSchema: z.ZodOptional<z.ZodObject<{
39
39
  success: "success";
40
40
  warning: "warning";
41
41
  }> & z.core.$partial, z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>>>;
42
+ linkVisited: z.ZodOptional<z.ZodObject<{
43
+ light: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>;
44
+ dark: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>;
45
+ }, z.core.$strip>>;
42
46
  }, z.core.$strip>>;
43
47
  declare const themeSchema: z.ZodObject<{
44
48
  colors: z.ZodObject<{
@@ -78,6 +82,10 @@ declare const themeSchema: z.ZodObject<{
78
82
  success: "success";
79
83
  warning: "warning";
80
84
  }> & z.core.$partial, z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>>>;
85
+ linkVisited: z.ZodOptional<z.ZodObject<{
86
+ light: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>;
87
+ dark: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>;
88
+ }, z.core.$strip>>;
81
89
  }, z.core.$strip>>;
82
90
  }, z.core.$strip>;
83
91
  export declare const commonConfig: z.ZodObject<{
@@ -126,6 +134,10 @@ export declare const configFileCreateSchema: z.ZodObject<{
126
134
  success: "success";
127
135
  warning: "warning";
128
136
  }> & z.core.$partial, z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>>>;
137
+ linkVisited: z.ZodOptional<z.ZodObject<{
138
+ light: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>;
139
+ dark: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<`#${string}`, string>>>;
140
+ }, z.core.$strip>>;
129
141
  }, z.core.$strip>>;
130
142
  }, z.core.$strip>>>;
131
143
  clean: z.ZodOptional<z.ZodBoolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuCxB;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EACpB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1C,UAAU,EAAE,MAAM,GACjB,CAAC,CAUH;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,CAexE;AAWD,eAAO,MAAM,UAAU,QAA2C,CAAC;AA6CnE,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAMR,CAAC;AAEd,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBmG,CAAC;AAErH,eAAO,MAAM,YAAY;;iBAEvB,CAAC;AAYH;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAqD,CAAC;AACzF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC9D,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC7D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACxE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAC5D,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAuCxB;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAC9B,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EACpB,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1C,UAAU,EAAE,MAAM,GACjB,CAAC,CAUH;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,CAexE;AAWD,eAAO,MAAM,UAAU,QAA2C,CAAC;AAoDnE,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAOR,CAAC;AAEd,QAAA,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBmG,CAAC;AAErH,eAAO,MAAM,YAAY;;iBAEvB,CAAC;AAYH;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAqD,CAAC;AACzF,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC9D,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC7D,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACxE,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAC5D,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}