#!/usr/bin/env node /* ========================================================================= * dssl - the command line for the DSSL style language * * dssl check styles.dssl [more.dssl …] [--json] [--strict] [--quiet] * Compile files with the REAL parser + resolver (the same engine the * browser runs). Prints file:line diagnostics with did-you-mean hints. * Exit 1 on errors, 2 on usage problems. --strict makes warnings fatal. * --json emits machine-readable diagnostics for CI and editors. * * dssl import styles.css [-o out.dssl] [--stdout] [--json] * Convert CSS to DSSL (semicolons, px, transition order, font:, @keyframes, * !important, raw blocks) and report what could not be carried over. * * dssl dev [dir] [--port 3000] [--inspect] * A tiny static server that never caches and adds DSSL.watch() to every page it * serves, so saving a .dssl file restyles the open page in place. Pages that load * /dssl/boot.js get it from the engine folder if the served folder has none. * * dssl build styles.dssl [-o out.css] [--json] [--strict] [--no-banner] * The exit lane: compile the static subset to a real .css file. * Tokens become custom properties, states become :hover, layers become * @layer, motions become @keyframes, raw { } passes through verbatim. * @signal rules and animate:/engine-native properties stay in the * runtime - the report says exactly what was left behind. * * Files are compiled in the order given; every bundle's tokens fold into the * next (name a tokens.dssl first, exactly like order in the browser). * ========================================================================= */ import { readFileSync, writeFileSync, statSync, existsSync } from 'node:fs'; import { dirname, resolve as pathResolve, basename, join, relative as pathRelative } from 'node:path'; import { fileURLToPath } from 'node:url'; // the engine is plain ESM, import it directly, no build step. In a repo checkout // it lives in public/dssl/. In the downloaded package (dssl-engine.zip) it sits // next to cli/ as dssl/. 0.8.0 only knew the first layout, so the packaged CLI // crashed with "module not found" for everyone who installed it. const here = pathResolve(dirname(fileURLToPath(import.meta.url)), '..'); // 0.15.0: `--engine ./dssl` (or DSSL_ENGINE=./dssl) points at the engine folder wherever it is, so the CLI file can live anywhere let engineDir = existsSync(join(here, 'public/dssl/parser.js')) ? join(here, 'public/dssl') : join(here, 'dssl'); { const i = process.argv.indexOf('--engine'); let want = process.env.DSSL_ENGINE || null; if (i !== -1) { want = process.argv[i + 1] || null; process.argv.splice(i, want === null ? 1 : 2); if (want === null) { console.error('dssl: --engine needs a folder, e.g. --engine ./dssl'); process.exit(2); } } if (want) { const dir = pathResolve(want); if (!existsSync(join(dir, 'parser.js'))) { console.error(`dssl: ${dir} has no parser.js, so it is not the engine folder (the folder that holds parser.js, resolver.js, index.js...)`); process.exit(2); } engineDir = dir; } } const { parse } = await import('file://' + join(engineDir, 'parser.js')); const { resolve } = await import('file://' + join(engineDir, 'resolver.js')); const { toCSS } = await import('file://' + join(engineDir, 'static.js')); const { VERSION } = await import('file://' + join(engineDir, 'index.js')); const { importCss } = await import('file://' + join(engineDir, 'import.js')); const { shadowWarnings } = await import('file://' + join(engineDir, 'lint.js')); const USAGE = `dssl v${VERSION} - the DSSL style language dssl check compile with the real engine, print diagnostics. A page (index.html) works too: it takes the stylesheets in the order the page loads them --json machine-readable output --strict warnings are errors too (exit 1) --quiet only print diagnostics, no summary dssl build (or a page, index.html) compile the static subset to a real .css file (several files: in load order, tokens first) -o output path (default: .css) --sourcemap write .map so devtools show the .dssl file and line of each rule --map-name file name for the map when your host will not serve *.map (default .map) --reset include the small reset, as a layer that loses to everything --inline-tokens write each token's value in place of var(--dssl-x) where the token is defined once and never changed: the fastest CSS, but a token changed at run time will not reach it --native-specificity keep selectors as written. By default every selector is wrapped in :where(), so source order wins exactly like in the engine, not CSS specificity --json machine-readable report --strict warnings are errors too (exit 1) --no-banner omit the generated-file header dssl import convert existing CSS to DSSL and report what could not carry over -o output path (default: .dssl) --stdout print the DSSL instead of writing a file --json machine-readable report dssl dev [dir] serve a folder with hot reload: edit a .dssl file and the page restyles with no reload --port port (default 3000) --inspect also open the in-page inspector (hover an element to see its rules) dssl --version print the version every command: --engine uses the engine in that folder (default: the one next to this file; or set DSSL_ENGINE)`; const readArg = (args, flag) => { const i = args.indexOf(flag); if (i === -1) return null; args.splice(i, 1); return args.splice(i, 1)[0] || null; }; const readSource = (file) => { try { statSync(file); return readFileSync(file, 'utf8'); } catch { console.error(`dssl: cannot read ${file}`); process.exit(2); } }; /** * 0.14.0: `dssl check index.html` and `dssl build index.html` read the page and take its stylesheets in the order the browser * loads them: every and every inline