#!/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