@master/css-cli 2.0.0-rc.68 → 2.0.0-rc.70

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
@@ -20,11 +20,11 @@
20
20
  <img alt="NPM Version" src="https://img.shields.io/github/v/release/master-co/css?include_prereleases&color=f6f7f8&label=&style=for-the-badge&logo=github">
21
21
  </picture>
22
22
  </a>
23
- <a aria-label="NPM Package" href="https://www.npmjs.com/package/@master/create-css">
23
+ <a aria-label="NPM Package" href="https://www.npmjs.com/package/@master/css-cli">
24
24
  <picture>
25
- <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/dm/@master/create-css?color=212022&label=%20&logo=npm&style=for-the-badge">
26
- <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/npm/dm/@master/create-css?color=f6f7f8&label=%20&logo=npm&style=for-the-badge">
27
- <img alt="NPM package ( download / month )" src="https://img.shields.io/npm/dm/@master/create-css?color=f6f7f8&label=%20&logo=npm&style=for-the-badge">
25
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/npm/dm/@master/css-cli?color=212022&label=%20&logo=npm&style=for-the-badge">
26
+ <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/npm/dm/@master/css-cli?color=f6f7f8&label=%20&logo=npm&style=for-the-badge">
27
+ <img alt="NPM package ( download / month )" src="https://img.shields.io/npm/dm/@master/css-cli?color=f6f7f8&label=%20&logo=npm&style=for-the-badge">
28
28
  </picture>
29
29
  </a>
30
30
  <a aria-label="Discord Community" href="https://discord.gg/sZNKpAAAw6">
@@ -41,16 +41,50 @@
41
41
  <img alt="Follow @mastercorg" src="https://img.shields.io/static/v1?label=%20&message=twitter&color=f6f7f8&logo=twitter&style=for-the-badge">
42
42
  </picture>
43
43
  </a>
44
- <a aria-label="Github Actions" href="https://github.com/master-co/css/actions/workflows/release.yml">
44
+ <a aria-label="Github Actions" href="https://github.com/master-co/css/actions/workflows/ci-release.yml">
45
45
  <picture>
46
- <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=rc&label=%20&message=twitter&color=212022&logo=githubactions&style=for-the-badge">
47
- <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=rc&label=%20&message=twitter&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
48
- <img alt="Github release actions" src="https://img.shields.io/github/actions/workflow/status/master-co/css/release.yml?branch=rc&label=%20&message=twitter&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
46
+ <source media="(prefers-color-scheme: dark)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/ci-release.yml?branch=rc&label=%20&message=twitter&color=212022&logo=githubactions&style=for-the-badge">
47
+ <source media="(prefers-color-scheme: light)" srcset="https://img.shields.io/github/actions/workflow/status/master-co/css/ci-release.yml?branch=rc&label=%20&message=twitter&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
48
+ <img alt="Github release actions" src="https://img.shields.io/github/actions/workflow/status/master-co/css/ci-release.yml?branch=rc&label=%20&message=twitter&color=f6f7f8&logo=githubactions&style=for-the-badge&logoColor=%23000">
49
49
  </picture>
50
50
  </a>
51
51
  </p>
52
52
 
53
53
  </div>
54
54
 
55
- ## Documentation
56
- Check out the official [documentation](https://rc.css.master.co/reference/cli).
55
+ ## Installation
56
+
57
+ ```bash
58
+ npm install -D @master/css-cli
59
+ ```
60
+
61
+ Run the package binary directly with `npx @master/css-cli`. The package does not expose `mcss` or `mastercss` aliases.
62
+
63
+ ## Commands
64
+
65
+ ### `npx @master/css-cli [source paths]`
66
+
67
+ Scan source files, detect Master CSS classes, generate CSS rules, and write the generated CSS file.
68
+
69
+ ```bash
70
+ npx @master/css-cli index.html main.js
71
+ ```
72
+
73
+ When no source paths are passed, the CLI scans the current project with the default scanner `include` and `exclude` options.
74
+
75
+ ```bash
76
+ npx @master/css-cli
77
+ ```
78
+
79
+ Options:
80
+
81
+ | Option | Type | Default | Description |
82
+ | --- | --- | --- | --- |
83
+ | `-o, --output <path>` | `string` | `master.css` | Generated CSS output path. |
84
+ | `-v, --verbose <level>` | `number` | `1` | Logging level. Use `0` to silence terminal output. |
85
+ | `-w, --watch` | `boolean` | `false` | Watch files and rebuild continuously. |
86
+
87
+ ## Related docs
88
+
89
+ - [Static rendering guide](https://rc.css.master.co/guide/installation)
90
+ - [Scanner package](../scanner)
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import runProgram from '../core.mjs';
3
3
 
4
- runProgram();
4
+ await runProgram();
package/dist/core.mjs CHANGED
@@ -3,18 +3,33 @@ import { Command } from 'commander';
3
3
  import { readJSONFileSync } from '@techor/fs';
4
4
  import { dirname } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
- import commandExtract from './commands/extract.mjs';
7
- import commandRender from './commands/render.mjs';
6
+ import runScan, { DEFAULT_SCAN_OUTPUT } from './scan.mjs';
8
7
 
9
- const __filename = fileURLToPath(import.meta.url);
10
- const __dirname = dirname(__filename);
11
- const pkg = readJSONFileSync(resolve(__dirname, '../package.json'));
12
- function runProgram(argv = process.argv) {
8
+ const __filename$1 = fileURLToPath(import.meta.url);
9
+ const __dirname$1 = dirname(__filename$1);
10
+ const pkg = readJSONFileSync(resolve(__dirname$1, '../package.json'));
11
+ const removedCommands = new Set([
12
+ 'extract',
13
+ 'render',
14
+ 'scan'
15
+ ]);
16
+ function errorRemovedCommand(program, command) {
17
+ program.error(`The "${command}" command was removed. Use "${pkg.name}" directly, for example "${pkg.name} -w".`);
18
+ }
19
+ async function runProgram(argv = process.argv) {
13
20
  const program = new Command();
14
- program.name(pkg.name).description(pkg.description).version(pkg.version || '0.0.0');
15
- commandExtract(program);
16
- commandRender(program);
17
- program.parse(argv);
21
+ program.name(pkg.name).description(pkg.description).version(pkg.version || '0.0.0').argument('[source paths...]', 'The glob pattern paths to scan sources').option('-w, --watch', 'Watch file changes and generate CSS rules.').option('-o, --output <path>', 'Specify your CSS file output path', DEFAULT_SCAN_OUTPUT).option('-v, --verbose <level>', 'Verbose logging 0~N', '1').option('--no-export', 'Print only CSS results.').action((sourcePaths, options)=>{
22
+ const removedCommand = sourcePaths[0];
23
+ if (removedCommand && removedCommands.has(removedCommand)) {
24
+ errorRemovedCommand(program, removedCommand);
25
+ }
26
+ return runScan(sourcePaths, options);
27
+ });
28
+ const firstArgument = argv[2];
29
+ if (firstArgument && removedCommands.has(firstArgument)) {
30
+ errorRemovedCommand(program, firstArgument);
31
+ }
32
+ await program.parseAsync(argv);
18
33
  return program;
19
34
  }
20
35
 
package/dist/scan.mjs ADDED
@@ -0,0 +1,181 @@
1
+ import { createExtractedCSS, registerStyleCSSSource } from '@master/css-stylesheet';
2
+ import { findCSSManifestEntryFiles } from '@master/css-project/entries';
3
+ import log from '@techor/log';
4
+ import { explorePathsSync } from '@techor/glob';
5
+ import bytes from 'bytes';
6
+ import chokidar from 'chokidar';
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+
10
+ const DEFAULT_SCAN_OUTPUT = 'master.css';
11
+ const DEFAULT_SOURCE_PATTERNS = [
12
+ '**/*.{html,htm,js,jsx,mjs,cjs,ts,tsx,mts,cts,svelte,astro,vue,md,mdx,pug,php}'
13
+ ];
14
+ async function registerManagedCSSEntries(scanner, styleCSSSources) {
15
+ styleCSSSources.clear();
16
+ for (const entry of (await findCSSManifestEntryFiles(scanner.cwd))){
17
+ await registerStyleCSSSource(scanner, styleCSSSources, entry, fs.readFileSync(entry, 'utf8'), {
18
+ projectDir: scanner.cwd
19
+ });
20
+ }
21
+ scanner.resetDependencies = [
22
+ ...new Set(Array.from(styleCSSSources.values()).flatMap((source)=>source.dependencies))
23
+ ];
24
+ }
25
+ function normalizeSourcePatterns(specifiedSourcePaths) {
26
+ return specifiedSourcePaths?.length ? specifiedSourcePaths : DEFAULT_SOURCE_PATTERNS;
27
+ }
28
+ function resolveSourcePaths(scanner, sourcePatterns, ignore = []) {
29
+ return explorePathsSync(sourcePatterns, {
30
+ cwd: scanner.cwd,
31
+ ignore
32
+ }).filter(Boolean);
33
+ }
34
+ async function scanSourceFile(scanner, source) {
35
+ const filepath = path.resolve(scanner.cwd, source);
36
+ await scanner.scan(source, fs.readFileSync(filepath, 'utf8'));
37
+ }
38
+ async function scanSourceFiles(scanner, sourcePaths) {
39
+ await Promise.all(sourcePaths.map((source)=>scanSourceFile(scanner, source)));
40
+ }
41
+ async function prepareScanner(scanner, styleCSSSources, sourcePaths) {
42
+ await registerManagedCSSEntries(scanner, styleCSSSources);
43
+ await scanSourceFiles(scanner, sourcePaths);
44
+ }
45
+ function exportCSS(scanner, css, filename = DEFAULT_SCAN_OUTPUT) {
46
+ const filepath = path.resolve(scanner.cwd, filename);
47
+ const dir = path.dirname(filepath);
48
+ if (!fs.existsSync(dir)) {
49
+ fs.mkdirSync(dir, {
50
+ recursive: true
51
+ });
52
+ }
53
+ fs.writeFileSync(filepath, css);
54
+ if (scanner.options.verbose) {
55
+ log.ok`**${filename}** exported ${bytes(css.length)}`;
56
+ }
57
+ scanner.emit('export', filename, filepath);
58
+ }
59
+ function formatWatchedPath(cwd, file) {
60
+ return path.isAbsolute(file) ? path.relative(cwd, file) : file;
61
+ }
62
+ async function waitForWatcherReady(watcher) {
63
+ await new Promise((resolve)=>watcher.once('ready', resolve));
64
+ // Let chokidar finish registering native watchers before callers mutate files.
65
+ await new Promise((resolve)=>setTimeout(resolve, 0));
66
+ }
67
+ async function runScan(specifiedSourcePaths = [], options = {}) {
68
+ const CSSScanner = (await import('@master/css-scanner')).default;
69
+ const { watch, output, verbose, cwd } = options;
70
+ const scanner = new CSSScanner({}, cwd);
71
+ const styleCSSSources = new Map();
72
+ const sourcePatterns = normalizeSourcePatterns(specifiedSourcePaths);
73
+ const writeOutput = async ()=>{
74
+ const css = await createExtractedCSS({
75
+ scanner,
76
+ styleCSSSources,
77
+ projectDir: scanner.cwd
78
+ });
79
+ if (options.export) {
80
+ exportCSS(scanner, css, output);
81
+ } else {
82
+ console.log(css);
83
+ }
84
+ };
85
+ scanner.on('init', (options)=>{
86
+ if (!specifiedSourcePaths.length) {
87
+ if (!options.exclude?.includes('**/node_modules/**')) {
88
+ options.exclude?.push('**/node_modules/**');
89
+ }
90
+ if (!options.exclude?.includes('node_modules')) {
91
+ options.exclude?.push('node_modules');
92
+ }
93
+ }
94
+ options.verbose = verbose ? +verbose : options.verbose;
95
+ });
96
+ await scanner.init();
97
+ const scanPaths = ()=>resolveSourcePaths(scanner, sourcePatterns, specifiedSourcePaths.length ? [] : scanner.options.exclude);
98
+ if (watch) {
99
+ const watchers = [];
100
+ let restarting = false;
101
+ let writing = Promise.resolve();
102
+ const queueWrite = ()=>{
103
+ writing = writing.then(writeOutput);
104
+ return writing;
105
+ };
106
+ const closeWatchers = async ()=>{
107
+ await Promise.all(watchers.splice(0).map((watcher)=>watcher.close()));
108
+ };
109
+ const startWatchers = async ()=>{
110
+ const sourcePaths = scanPaths();
111
+ if (sourcePaths.length) {
112
+ const sourceWatcher = chokidar.watch(sourcePaths, {
113
+ cwd: scanner.cwd,
114
+ ignoreInitial: true
115
+ });
116
+ sourceWatcher.on('add', (source)=>{
117
+ void scanSourceFile(scanner, source).then(queueWrite);
118
+ });
119
+ sourceWatcher.on('change', (source)=>{
120
+ void scanSourceFile(scanner, source).then(queueWrite);
121
+ });
122
+ watchers.push(sourceWatcher);
123
+ await waitForWatcherReady(sourceWatcher);
124
+ }
125
+ if (scanner.resetDependencies.length) {
126
+ const planWatcher = chokidar.watch(scanner.resetDependencies, {
127
+ ignoreInitial: true
128
+ });
129
+ const handlePlanChange = async (resetDependency)=>{
130
+ if (restarting) return;
131
+ restarting = true;
132
+ try {
133
+ if (scanner.options.verbose) {
134
+ log``;
135
+ log`[change] **${formatWatchedPath(scanner.cwd, resetDependency)}**`;
136
+ }
137
+ await closeWatchers();
138
+ await scanner.reset(scanner.customOptions, {
139
+ emit: false
140
+ });
141
+ await prepareScanner(scanner, styleCSSSources, scanPaths());
142
+ await queueWrite();
143
+ await startWatchers();
144
+ log``;
145
+ log.t`Restart watching source changes`;
146
+ scanner.emit('resetDependencyChange');
147
+ } finally{
148
+ restarting = false;
149
+ }
150
+ };
151
+ planWatcher.on('add', (resetDependency)=>{
152
+ void handlePlanChange(resetDependency);
153
+ });
154
+ planWatcher.on('change', (resetDependency)=>{
155
+ void handlePlanChange(resetDependency);
156
+ });
157
+ planWatcher.on('unlink', (resetDependency)=>{
158
+ void handlePlanChange(resetDependency);
159
+ });
160
+ watchers.push(planWatcher);
161
+ await waitForWatcherReady(planWatcher);
162
+ }
163
+ };
164
+ process.once('SIGTERM', ()=>{
165
+ void closeWatchers().finally(()=>process.exit(0));
166
+ });
167
+ process.once('SIGINT', ()=>{
168
+ void closeWatchers().finally(()=>process.exit(0));
169
+ });
170
+ await prepareScanner(scanner, styleCSSSources, scanPaths());
171
+ await queueWrite();
172
+ await startWatchers();
173
+ log``;
174
+ log.t`Start watching source changes`;
175
+ } else {
176
+ await prepareScanner(scanner, styleCSSSources, scanPaths());
177
+ await writeOutput();
178
+ }
179
+ }
180
+
181
+ export { DEFAULT_SCAN_OUTPUT, runScan as default };
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@master/css-cli","scripts":{"build":"techor build \"src/**/*.ts\" --formats esm","dev":"pnpm build --watch","lint":"eslint","type-check":"tsc --noEmit","test":"vitest"},"type":"module","license":"MIT","description":"Master CSS CLI","author":"Aoyue Design LLC.","funding":"https://rc.css.master.co/sponsor","homepage":"https://css.master.co","bugs":{"url":"https://github.com/master-co/css/issues"},"repository":{"type":"git","url":"https://github.com/master-co/css.git","directory":"packages/cli"},"keywords":["cli","css","mastercss"],"bin":{"mcss":"./dist/bin/index.mjs","mastercss":"./dist/bin/index.mjs"},"files":["dist"],"publishConfig":{"access":"public","provenance":true},"dependencies":{"@master/css":"^2.0.0-rc.68","@master/css-explore-config":"^2.0.0-rc.68","@master/css-extractor":"^2.0.0-rc.68","@master/css-server":"^2.0.0-rc.68","@techor/extend":"^3.2.0","@techor/fs":"^3.2.0","@techor/glob":"^3.2.0","@techor/log":"^3.2.0","bytes":"^3.1.2","commander":"^12.0.0","minimatch":"^9.0.3","pretty-hrtime":"^1.0.3"},"devDependencies":{"@types/pretty-hrtime":"^1.0.3","fast-glob":"^3.3.2","shared":"^2.0.0-rc.68"},"version":"2.0.0-rc.68"}
1
+ {"name":"@master/css-cli","scripts":{"build":"techor build \"src/**/*.ts\" --formats esm","dev":"pnpm build --watch","lint":"eslint","type-check":"tsc --noEmit","test":"vitest"},"type":"module","license":"MIT","description":"Master CSS CLI","author":"Aoyue Design LLC.","funding":"https://rc.css.master.co/sponsor","homepage":"https://css.master.co","bugs":{"url":"https://github.com/master-co/css/issues"},"repository":{"type":"git","url":"https://github.com/master-co/css.git","directory":"packages/cli"},"keywords":["cli","css","mastercss"],"bin":"./dist/bin/index.mjs","files":["dist"],"publishConfig":{"access":"public","provenance":true},"dependencies":{"@master/css":"^2.0.0-rc.70","@master/css-project":"^2.0.0-rc.70","@master/css-scanner":"^2.0.0-rc.70","@master/css-stylesheet":"^2.0.0-rc.70","@techor/fs":"^3.2.5","@techor/glob":"^3.2.5","@techor/log":"^3.2.5","bytes":"^3.1.2","chokidar":"^5.0.0","commander":"^14.0.3"},"devDependencies":{"@master/css-lexer":"^2.0.0-rc.70","@types/bytes":"^3.1.5","upath":"^2.0.1"},"version":"2.0.0-rc.70"}
@@ -1,54 +0,0 @@
1
- import { options } from '@master/css-extractor';
2
- import log from '@techor/log';
3
-
4
- var commandExtract = ((program)=>program.command('extract').argument('[source paths]', 'The glob pattern path to extract sources').option('-w, --watch', 'Watch file changed and generate CSS rules.').option('-o, --output <path>', 'Specify your CSS file output path', options.output).option('-v, --verbose <level>', 'Verbose logging 0~N', '1').option('--no-export', 'Print only CSS results.').option('--options <path>', 'Specify your extractor options sources', 'master.css-extractor').action(async function(specifiedSourcePaths, options) {
5
- const CSSExtractor = (await import('@master/css-extractor')).default;
6
- const { watch, output, verbose, cwd, options: customOptions } = options || {};
7
- const extractor = new CSSExtractor(customOptions, cwd);
8
- extractor.on('init', (options)=>{
9
- if (specifiedSourcePaths?.length) {
10
- options.include = specifiedSourcePaths;
11
- options.exclude = [];
12
- } else {
13
- if (!options.exclude?.includes('**/node_modules/**')) {
14
- options.exclude?.push('**/node_modules/**');
15
- }
16
- if (!options.exclude?.includes('node_modules')) {
17
- options.exclude?.push('node_modules');
18
- }
19
- }
20
- options.output = output;
21
- options.verbose = verbose ? +verbose : options.verbose;
22
- });
23
- extractor.init();
24
- if (watch) {
25
- extractor.on('watchStart', async ()=>{
26
- await extractor.prepare();
27
- log``;
28
- log.t`Start watching source changes`;
29
- }).on('reset', async ()=>{
30
- await extractor.reset();
31
- log``;
32
- log.t`Restart watching source changes`;
33
- }).on('change', ()=>{
34
- if (options?.export) {
35
- extractor.export();
36
- } else {
37
- console.log(extractor.css.text);
38
- }
39
- }).on('watchClose', ()=>{
40
- log``;
41
- log.t`Stop watching source changes`;
42
- });
43
- await extractor.startWatch();
44
- } else {
45
- await extractor.prepare();
46
- if (options?.export) {
47
- extractor.export();
48
- } else {
49
- console.log(extractor.css.text);
50
- }
51
- }
52
- }));
53
-
54
- export { commandExtract as default };
@@ -1,64 +0,0 @@
1
- import log from '@techor/log';
2
- import prettyHartime from 'pretty-hrtime';
3
- import { explorePathsSync } from '@techor/glob';
4
- import exploreConfig from '@master/css-explore-config';
5
- import { readFile, writeFileSync } from 'fs';
6
- import { brotliCompressSync } from 'zlib';
7
- import bytes from 'bytes';
8
-
9
- var commandRender = ((program)=>program.command('render').description('Scans HTML and injects generated CSS rules').argument('<source paths>', 'The path in glob patterns of the source of the HTML file').option('-c --config <path>', 'The source path of the Master CSS configuration', 'master.css').option('-a --analyze', 'Analyze injected CSS and HTML size ( brotli ) without writing to file').action(async function(filePatterns, options = {
10
- config: 'master.css'
11
- }) {
12
- const { render } = await import('@master/css-server');
13
- const sourcePaths = explorePathsSync(filePatterns);
14
- if (sourcePaths.length) {
15
- const renderStart = process.hrtime();
16
- const col1Width = Math.max(sourcePaths.reduce((max, current)=>{
17
- if (current.length > max.length) {
18
- return current;
19
- } else {
20
- return max;
21
- }
22
- }).length + 10, 20);
23
- const col2Width = 8;
24
- const config = typeof options.config === 'string' ? exploreConfig({
25
- name: options.config
26
- }) : undefined;
27
- log``;
28
- log`${' Source Files'.padEnd(col1Width)}${'CSS Size'.padStart(col2Width)}`;
29
- await Promise.all(sourcePaths.map(async (filename)=>new Promise((resolve, reject)=>{
30
- readFile(filename, {
31
- encoding: 'utf-8'
32
- }, (err, content)=>{
33
- if (err) reject(err);
34
- const { html, css } = render(content, config);
35
- const renderedCSSText = css?.text;
36
- const renderedCSSSize = renderedCSSText ? options.analyze ? brotliCompressSync(renderedCSSText).length : renderedCSSText.length : 0;
37
- if (!options.analyze) {
38
- if (content !== html) {
39
- writeFileSync(filename, html);
40
- }
41
- }
42
- const prettifiedCSSSize = bytes(renderedCSSSize);
43
- log.ok`**${filename}**${' '.repeat(col1Width - filename.length - 2)}${prettifiedCSSSize?.padStart(col2Width)}`;
44
- resolve();
45
- });
46
- })));
47
- const renderTime = process.hrtime(renderStart);
48
- if (options.analyze) {
49
- log`${' '.repeat(col1Width)}${'(Brotli)'.padStart(col2Width)}`;
50
- log``;
51
- log.success`**${sourcePaths.length}** files analyzed in ${prettyHartime(renderTime).replace(' ', '')}`;
52
- log``;
53
- log.i`The CSS output will be smaller because it doesn't yet consider the bytes shared with HTML.`;
54
- } else {
55
- log`${' '.repeat(col1Width)}${'(Raw)'.padStart(col2Width)}`;
56
- log``;
57
- log.success`**${sourcePaths.length}** files rendered in ${prettyHartime(renderTime).replace(' ', '')}`;
58
- }
59
- } else {
60
- log.i`No **${filePatterns}** files found`;
61
- }
62
- }));
63
-
64
- export { commandRender as default };