Skip to content

feat(cli): new way to run πŸƒπŸ»β€β™‚οΈ - #5416

Open
mimarz wants to merge 44 commits into
mainfrom
feat-config-new-output-field
Open

mimarz wants to merge 44 commits into
mainfrom
feat-config-new-output-field

Conversation

@mimarz

@mimarz mimarz commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

resolves #5191

Summary

Current way to run CLI

When we started with the CLI we didn't have experience or much of a plan on how it would be used. This resulted in a patchwork of features, implemented as the need came up, which is cumbersome to use and not very thought out.

  • You need to run two commands to generate css file
    • designsystemet tokens create && designsystemet tokens build -t ./design-tokens -o ./design-tokens-build
  • Users need to look up what command to run or forget to run the latter command for CSS file.
  • tokens create uses designsystemet.config.json for creating tokens with fields like outDir and clean tied only to create.
  • tokens build does not use designsystemet.config.json, instead you need to pass terminal arguments such as --out-dir, --clean, --tokens to configure how it runs.
  • no option for turning off types.d.ts generation, or in general confusing configuration
  • continuing in the pattern of tokens create and tokens build is not very scalable, as we can generate CSS without design-tokens.

New way to run CLI

Declarative approach with everything that needs to be run defined in a config schema, designsystemet.config.json.

New output field which lets users decide which output they want to create/generate for designsystemet. Just executing the bin/command, designsystemet, creates everything defined in output, so users can pick and choose which outputs they want created for their designsystem.

This is inspired with how CLI for bundlers, tsdown, rolldown, rollup etc. so should be more familiar.

This feature is also part of stabilising the config schema so we can remove the experimental we have today under config readme and page.

tokens create and tokens build still work, but are deprecated. outDir and clean are deprecated in favour of output, and the CLI offers to migrate existing config files automatically.

Stack

Preview

Before

{
  "outDir": "./design-tokens", // only used by `tokens create`
  "clean": true,
  "themes": {
    "my-theme": {
      "colors": {
        "accent": "#0062BA",
        "neutral": "#1E2B3C"
      }
    }
  }
}
npx @digdir/designsystemet tokens create --config designsystemet.config.json
npx @digdir/designsystemet tokens build -t ./design-tokens -o ./design-tokens-build --experimental-tailwind

After

{
  "themes": {
    "my-theme": {
      "colors": {
        "accent": "#0062BA",
        "neutral": "#1E2B3C"
      }
    }
  }
}
npx @digdir/designsystemet

With no output field, you get the defaults, ["design-tokens", "css", "types"] once the whole stack is merged (["design-tokens", "css"] in this PR alone, as types arrives in #5444).

This is primarily to match today's expected output when running tokens create and tokens build. I expect us to adjust what default output will be in the future.

The config file is auto-detected (designsystemet.config.json, then designsystemet.config.jsonc), or passed with -c, --config <path>. All paths in output are relative to the config file.

How output works

output is a list of what to create. Each item is either an output type using its defaults, or an object with custom settings:

{
  "output": [
    "design-tokens", // output type with default values

    // object with configured output options for css file
    {
      "type": "css",
      "dir": "./css",
      "tokensDir": "./design-tokens",
      "tailwind": "v4"
    },
  ]
}
  • Cleaning is on by default: cleanDir defaults to true, so files that are no longer generated are removed. This was opt-in before (clean: false). Every output directory is cleaned once, before any output runs, so outputs can share a directory without deleting each other's files. The CLI refuses to clean a directory that contains the config file, existing design tokens that css or types build from (tokensDir), or another output with cleanDir: false, and stops before deleting anything.

  • Order: design-tokens always runs first, whatever order the outputs are listed in, since CSS and types can be built from the tokens.

  • tokensDir: css and types build from tokensDir, or else from the dir of the design-tokens output. This is decided per output, so one css output can build from existing tokens while another is created from the themes.

  • Without design tokens: "output": ["css", "types"] with no tokensDir creates CSS and types directly from the themes, without writing any design tokens to disk.

  • Without themes: themes is only needed by outputs that are created from themes. A config with only a css output and a tokensDir builds CSS from existing design tokens and can leave themes out:

    {
      "output": [{ "type": "css", "tokensDir": "./design-tokens" }]
    }

If an output needs themes and there are none, the CLI stops with an error saying so, before cleaning anything.

  • Dry run: --dry writes nothing. CSS and types that would be built from the design tokens created in the same run are created from the themes instead, so the dry run shows the real result.

Inject design-tokens

We know some users today have scripts to manipulate design-tokens before building. Usually adding colors or adjusting size scale. This is still possible with output by using two configs and running designsystemet twice with each config.

create-tokens.json

{
  "output": ["design-tokens"],
  "themes": {} // your theme configuration
}

build-tokens.json

{
  // only output is needed as we build css from design-tokens
  "output": [{
      "type": "css",
      "tokensDir": "./design-tokens",
   }]
}

run

designsystemet --config create-tokens.json &&
node inject-script.js && 
designsystemet --config build-tokens.json

Tailwind (#5438)

The css output has a tailwind option that replaces --experimental-tailwind. By default (false) no Tailwind file is generated. Set it to the Tailwind version you use to also get a <theme>.tailwind.css:

  • "v4" uses @theme inline, so Tailwind utilities reference the --ds-* variables directly. data-color, data-color-scheme and data-size then also apply to utilities, at any depth in the DOM.
  • "v3" generates the same file as before. The deprecated tokens build --experimental-tailwind keeps generating v3.

Both versions also get --text-sm, --text-md and --text-lg mapped to the body font sizes in #5451. These were never generated because of a typo in the token name matching.

Types (#5444)

Type declarations (types.d.ts) now have their own types output instead of always being written with the CSS. These augment @digdir/designsystemet-types with the theme's color names.

Note

The css output no longer writes type declarations. If you set output yourself, add "types" to keep getting them. The default output, and configs migrated from outDir, already include it.

Migrating existing configs

When designsystemet finds outDir or clean, it offers to migrate the config file:

 βœ‹ Automigration detected
Config file designsystemet.config.json is eligible for migration: New output field
Your config file uses the deprecated outDir and clean fields.
This migration will replace them with a new output field if necessary.
? Do you want to migrate? (Y/n)

"outDir": "./tokens", "clean": true becomes:

{
  "output": [
    { "type": "design-tokens", "dir": "./tokens" },
    { "type": "css", "tokensDir": "./tokens" },
    { "type": "types", "tokensDir": "./tokens" }
  ]
}
  • Default values: if outDir already has its default value and clean isn't false, the migration just removes the old fields and doesn't add an output.
  • clean: false: an explicit "clean": false is carried over as "cleanDir": false on every output that is cleaned, so folders the user opted out of cleaning are not deleted. This adds an output even when outDir has its default value.
  • Paths: outDir was resolved from the directory the CLI ran in, but output paths are resolved from the config file. When the config isn't in that directory, the migration rewrites outDir so it still points to the same place.
  • Formatting: the migration edits the file in place, so comments and formatting are kept. output is placed right after $schema.
  • Declining: designsystemet stops without cleaning or writing anything, since it only reads output. tokens create keeps using outDir and clean.
  • tokens create: doesn't offer this migration, since it only reads outDir and clean and would ignore the migrated output. It prints a hint to run designsystemet instead.

generate-config-from-tokens uses the same mapping, so a generated config gets the same output as a migrated one.

Deprecations

  • tokens create / tokens build: these now print a deprecation warning and are marked [deprecated] in --help. They're kept for backwards compatibility, keep using outDir and clean, and will be removed in a future release.
  • outDir / clean: marked deprecated in the Config schema, and replaced by output[].dir / output[].cleanDir.
  • experimental_tailwind: replaced by tailwind on the css output.

Other changes

  • generate-config-from-tokens: the generated config now uses output instead of the deprecated outDir, with the tokens directory written relative to the config file (--out) rather than as an absolute path. If the tokens are in the default design-tokens directory next to the config, no output is written and the defaults apply. It refuses an --out inside the tokens directory, since running that config would clean the directory it's in.
  • themes is optional: themes is no longer required by the config schema or the published JSON schema. An empty "themes": {} is still rejected.
  • Severity color defaults (chore: better handling of severity default color namesΒ #5443): the default info, success, warning and danger colors are now added during config validation, instead of in several places in the token generation. User-defined severity colors keep their value, and all severity colors are placed last. neutral is still required.
  • Root config: the repo's designsystemet.config.json sets "tailwind": "v3" on its css output, so running the new command keeps the published packages/css/theme/designsystemet.tailwind.css identical to what build:theme generates.
  • Theme builder: the "use theme" modal now shows npx @digdir/designsystemet as the build command. The config snippet no longer includes outDir, the hard-coded typography, or borderRadius when it's the default.
  • Removed banner: the banner option on the css output was never used by the CLI, so it's gone from the schema.
  • Docs: the cli-config, own-theme, multiple-themes and css pages (en and no), the CLI README and the CSS README are updated to use the new command, output and the tailwind option.

@changeset-bot

changeset-bot Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

πŸ¦‹ Changeset detected

Latest commit: fc4f52e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@digdir/designsystemet Minor
@plugin/designsystemet Minor
@digdir/designsystemet-css Minor
@digdir/designsystemet-react Minor
@digdir/designsystemet-types Minor
@digdir/designsystemet-web Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployments for this pull request:

storybook - 1. Oct 2026 - 15:28

themebuilder - 1. Oct 2026 - 15:29

www - 1. Oct 2026 - 15:30

@mimarz
mimarz force-pushed the feat-config-new-output-field branch from 7556eb0 to 24c1fe6 Compare September 28, 2026 06:40
@mimarz
mimarz added this pull request to stack #5439 September 28, 2026 12:30
@mimarz
mimarz force-pushed the feat-config-new-output-field branch 3 times, most recently from 387736d to 66ef2dd Compare September 29, 2026 13:27
@mimarz
mimarz requested a balanced review from Copilot September 30, 2026 06:51
@mimarz
mimarz marked this pull request as ready for review September 30, 2026 06:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟑 Changes recommended

Migration and output-cleaning paths contain failure cases, while several public documentation contracts remain inconsistent.

Review effort: Balanced
Findings: 1 Medium severity Β· 5 Low severity

Open (6)
What changed in this PR

Introduces a config-driven root CLI command for generating design tokens and CSS while deprecating legacy token commands and configuration fields.

Changes:

  • Adds configurable outputs, execution ordering, and optional themes.
  • Adds automatic migration from outDir and clean.
  • Updates integrations, documentation, tests, and generated fixtures.
File Description
plugins/​designsystemet/​src/​ui/​preview-view.tsx Handles optional themes.
plugins/​designsystemet/​src/​ui/​app.tsx Safely initializes theme selection.
plugins/​designsystemet/​src/​plugin/​code.ts Requires themes for plugin generation.
packages/​cli/​tests/​config/​design-tokens/​$designsystemet.jsonc Updates fixture version.
packages/​cli/​tests/​config/​build/​types.d.ts Updates generated version.
packages/​cli/​tests/​config/​build/​some-org.css Updates CSS fixture version.
packages/​cli/​tests/​config/​build/​other-org.css Updates CSS fixture version.
packages/​cli/​tests/​config/​build/​colors.d.ts Updates declaration fixture version.
packages/​cli/​src/​tokens/​generate-config.ts Generates relative output configuration.
packages/​cli/​src/​tokens/​css-variables.test.ts Adapts tests to optional themes.
packages/​cli/​src/​scripts/​update-preview-tokens.ts Validates preview themes exist.
packages/​cli/​src/​schemas/​schema.ts Makes themes optional publicly.
packages/​cli/​src/​schemas/​schema-output.ts Defines outputs and deprecations.
packages/​cli/​src/​schemas/​__snapshots__/​config.schema.json Updates published schema snapshot.
packages/​cli/​src/​migrations/​new-output-field.ts Adds output-field migration.
packages/​cli/​src/​migrations/​new-output-field.test.ts Tests migration behavior.
packages/​cli/​src/​migrations/​index.ts Registers the migration.
packages/​cli/​src/​internal.ts Exports the border-radius default.
packages/​cli/​src/​figma/​scopes.test.ts Adapts tests to optional themes.
packages/​cli/​src/​automigrate.ts Returns config text consistently.
packages/​cli/​README.md Documents the root command and outputs.
packages/​cli/​package.json Skips migration prompts in scripts.
packages/​cli/​docs/​designsystemet.config.defaults.json Removes unused banner default.
packages/​cli/​bin/​options.ts Extracts shared CLI options.
packages/​cli/​bin/​designsystemet.ts Implements root output execution.
packages/​cli/​bin/​deprecated.ts Extracts deprecated token commands.
packages/​cli/​bin/​config.ts Adds config discovery and theme checks.
designsystemet.config.json Uses the public configuration schema.
apps/​www/​app/​content/​fundamentals/​no/​theme/​multiple-themes.mdx Updates Norwegian multi-theme examples.
apps/​www/​app/​content/​fundamentals/​no/​start-here/​own-theme.mdx Updates Norwegian setup instructions.
apps/​www/​app/​content/​fundamentals/​no/​code/​cli-config.mdx Documents outputs in Norwegian.
apps/​www/​app/​content/​fundamentals/​en/​theme/​multiple-themes.mdx Updates English multi-theme examples.
apps/​www/​app/​content/​fundamentals/​en/​start-here/​own-theme.mdx Updates English setup instructions.
apps/​www/​app/​content/​fundamentals/​en/​code/​cli-config.mdx Documents outputs in English.
apps/​themebuilder/​app/​_utils/​config-to-url.ts Adapts theme typing.
apps/​themebuilder/​app/​_components/​token-modal/​use-token-modal.ts Simplifies generated configuration.
.changeset/​sour-turtles-begin.md Records default cleaning behavior.
.changeset/​solid-turkeys-buy.md Records field deprecations.
.changeset/​honest-pans-lose.md Records the output feature.
.changeset/​clear-buckets-shout.md Records command deprecations.

πŸ’‘ Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/cli/src/migrations/new-output-field.ts Outdated
Comment thread .changeset/clear-buckets-shout.md
Comment thread apps/www/app/content/fundamentals/en/code/cli-config.mdx Outdated
Comment thread apps/www/app/content/fundamentals/no/code/cli-config.mdx Outdated
Comment thread packages/cli/src/schemas/schema-output.ts
Comment thread packages/cli/src/schemas/schema.ts
@mimarz
mimarz force-pushed the feat-config-new-output-field branch from eb00c8f to 93e6183 Compare September 30, 2026 07:22
@mimarz
mimarz requested a balanced review from Copilot September 30, 2026 07:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread apps/www/app/content/fundamentals/en/code/cli-config.mdx Outdated
Comment thread apps/www/app/content/fundamentals/no/code/cli-config.mdx Outdated
Comment thread packages/cli/README.md Outdated
@mimarz
mimarz force-pushed the feat-config-new-output-field branch from 28f27a3 to 6e6102d Compare September 30, 2026 07:45
@mimarz
mimarz requested a balanced review from Copilot September 30, 2026 07:46

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

πŸ”΅ Needs a closer look

JSONC migration can crash, deprecated command migration changes the active output settings, and mixed CSS outputs are handled incorrectly.

Review effort: Balanced
Findings: None

Resolved since last review (3)

@mimarz
mimarz force-pushed the feat-config-new-output-field branch from 6e6102d to 25774df Compare September 30, 2026 10:31
@mimarz
mimarz removed this pull request from stack #5439 September 30, 2026 14:00
@mimarz
mimarz added this pull request to stack #5452 September 30, 2026 14:01
@eirikbacker
eirikbacker requested a balanced review from Copilot October 1, 2026 06:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

πŸ”΅ Needs a closer look

JSONC migration can crash, legacy migration can redirect output, and mixed CSS outputs are routed incorrectly.

Review effort: Balanced
Findings: None

@mimarz
mimarz requested a balanced review from Copilot October 1, 2026 08:10
@mimarz
mimarz force-pushed the feat-config-new-output-field branch from fb64422 to 07c0cce Compare October 1, 2026 11:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

πŸ”΅ Needs a closer look

Theme validation currently occurs after destructive cleaning, and the deprecated create command can fail before normal config handling.

Review effort: Balanced
Findings: None

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

πŸ”΅ Needs a closer look

Declining or skipping migration causes legacy output paths and cleaning settings to be silently ignored.

Review effort: Balanced
Findings: None

Previously missed (1)

In code that hasn't changed since last review

Low severity Correct release note to reference output directories

.changeset/​sour-turtles-begin.md:5

This release note refers to deprecated outDir folders, but the new API cleans each output[].dir; saying outDir implies the legacy field controls the behavior.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

πŸ”΅ Needs a closer look

Directory-overlap validation can delete token inputs, and valid JSONC can be corrupted during migration.

Review effort: Balanced
Findings: None

Previously missed (2)

In code that hasn't changed since last review

Medium severity Make deprecated property removal aware of JSONC comments

packages/​cli/​src/​migrations/​new-output-field.ts:48

A JSONC comment between a deprecated property's value and its comma is not whitespace, so this regex misses the comma. Removing { "outDir": "tokens" /* note */, "themes": {} } then leaves { /* note */, "themes": {} }, which is invalid and makes the automatic migration corrupt an otherwise valid config. Make removal comment-aware and cover inline block/line comments around the comma.

Low severity Update docblock to include the public output property

packages/​cli/​src/​schemas/​schema.ts:275

This docblock still says the public schema excludes output, but the changed declaration now extends the complete config schema and intentionally publishes output. Update the comment to reflect the new public contract.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

πŸ”΅ Needs a closer look

Dry runs can fail or build CSS from stale tokens because generated design tokens are never written before the CSS build reads them.

Review effort: Balanced
Findings: None

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟒 Approval recommended

The implementation is coherent and well-tested; remaining findings are minor documentation corrections.

Review effort: Balanced
Findings: None

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow configuration of designsystemet build output directory in the config file

2 participants