@master/css-cli 2.0.0-rc.88 → 2.0.0

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.
@@ -0,0 +1,103 @@
1
+ import { join } from "node:path";
2
+ import { randomUUID } from "node:crypto";
3
+ import { platform, userInfo } from "node:os";
4
+ import { mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
5
+ import { setTimeout } from "node:timers/promises";
6
+ // src/publication-lock.ts
7
+ const NAME = /^(\d+)-[a-f0-9-]+\.json$/;
8
+ function missing(error) {
9
+ return error?.code === "ENOENT";
10
+ }
11
+ async function remove(file) {
12
+ try {
13
+ await withSharingRetry(() => unlink(file));
14
+ } catch (error) {
15
+ if (!missing(error)) throw error;
16
+ }
17
+ }
18
+ async function withSharingRetry(operation, deadline = performance.now() + 1e3) {
19
+ while (true) try {
20
+ return await operation();
21
+ } catch (error) {
22
+ const code = error.code;
23
+ if (platform() !== "win32" || ![
24
+ "EPERM",
25
+ "EACCES",
26
+ "EBUSY"
27
+ ].includes(code ?? "") || performance.now() >= deadline) throw error;
28
+ await setTimeout(10);
29
+ }
30
+ }
31
+ function alive(pid) {
32
+ try {
33
+ process.kill(pid, 0);
34
+ return true;
35
+ } catch (error) {
36
+ return error.code !== "ESRCH";
37
+ }
38
+ }
39
+ async function tickets(directory) {
40
+ const entries = [];
41
+ for (const id of await readdir(directory)) {
42
+ const register = id.endsWith(".next") ? id.slice(0, -5) : id;
43
+ const match = NAME.exec(register);
44
+ if (!match) continue;
45
+ const file = join(directory, register);
46
+ if (!alive(Number(match[1]))) {
47
+ await remove(file);
48
+ await remove(file + ".next");
49
+ continue;
50
+ }
51
+ if (id !== register) continue;
52
+ let text;
53
+ try {
54
+ text = await readFile(file, "utf8");
55
+ } catch (error) {
56
+ if (missing(error)) continue;
57
+ throw error;
58
+ }
59
+ const value = JSON.parse(text);
60
+ if (typeof value.choosing !== "boolean" || typeof value.number !== "string" || !/^\d+$/.test(value.number)) throw new Error(`Invalid CLI publication lock register: ${file}`);
61
+ entries.push({
62
+ id,
63
+ choosing: value.choosing,
64
+ number: BigInt(value.number)
65
+ });
66
+ }
67
+ return entries;
68
+ }
69
+ /** Serialize ownership, publication and cleanup across cooperating local CLI processes. */
70
+ async function withStylesheetPublicationLock(work, options = {}) {
71
+ const directory = options.directory ?? join(userInfo().homedir, ".cache", "mastercss", "cli-publication-lock-v1");
72
+ const deadline = performance.now() + (options.timeout ?? 3e4);
73
+ const id = `${process.pid}-${randomUUID()}.json`;
74
+ const file = join(directory, id);
75
+ await mkdir(directory, {
76
+ recursive: true,
77
+ mode: 448
78
+ });
79
+ const publish = async (value) => {
80
+ await writeFile(file + ".next", JSON.stringify(value), { mode: 384 });
81
+ await withSharingRetry(() => rename(file + ".next", file), Math.min(deadline, performance.now() + 1e3));
82
+ };
83
+ try {
84
+ await publish({
85
+ choosing: true,
86
+ number: "0"
87
+ });
88
+ const number = (await tickets(directory)).reduce((max, peer) => peer.number > max ? peer.number : max, 0n) + 1n;
89
+ await publish({
90
+ choosing: false,
91
+ number: String(number)
92
+ });
93
+ while (true) {
94
+ if (performance.now() >= deadline) throw new Error("Timed out waiting for another stylesheet publication.");
95
+ if (!(await tickets(directory)).some((peer) => peer.id !== id && (peer.choosing || peer.number < number || peer.number === number && peer.id < id))) return await work();
96
+ await setTimeout(10);
97
+ }
98
+ } finally {
99
+ await remove(file);
100
+ await remove(file + ".next");
101
+ }
102
+ }
103
+ export { withStylesheetPublicationLock };
@@ -0,0 +1,109 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { randomUUID } from "node:crypto";
4
+ import { platform } from "node:os";
5
+ // src/publication.ts
6
+ function renameStylesheetFile(from, to) {
7
+ const deadline = performance.now() + 1e3;
8
+ let waitBuffer;
9
+ while (true) try {
10
+ fs.renameSync(from, to);
11
+ return;
12
+ } catch (error) {
13
+ const code = error.code;
14
+ if (platform() !== "win32" || ![
15
+ "EPERM",
16
+ "EACCES",
17
+ "EBUSY"
18
+ ].includes(code ?? "") || performance.now() >= deadline) throw error;
19
+ waitBuffer ??= new Int32Array(new SharedArrayBuffer(4));
20
+ Atomics.wait(waitBuffer, 0, 0, Math.min(10, Math.max(0, deadline - performance.now())));
21
+ }
22
+ }
23
+ function matchesAsset(file, bytes) {
24
+ const stat = fs.lstatSync(file, { throwIfNoEntry: false });
25
+ if (!stat) return false;
26
+ if (!stat.isFile() || !fs.readFileSync(file).equals(bytes)) throw new Error(`Refusing to overwrite stylesheet asset: ${file}`);
27
+ return true;
28
+ }
29
+ function prepareTemporaryFile(target, bytes, outputFiles, mode) {
30
+ const temporary = path.join(path.dirname(target), `.master-css-${randomUUID()}.tmp`);
31
+ let descriptor;
32
+ let created = false;
33
+ outputFiles.add(temporary);
34
+ try {
35
+ descriptor = fs.openSync(temporary, "wx", mode ?? 438);
36
+ created = true;
37
+ if (mode !== void 0) fs.fchmodSync(descriptor, mode);
38
+ fs.writeFileSync(descriptor, bytes);
39
+ fs.closeSync(descriptor);
40
+ descriptor = void 0;
41
+ return temporary;
42
+ } catch (error) {
43
+ if (descriptor !== void 0) {
44
+ fs.closeSync(descriptor);
45
+ descriptor = void 0;
46
+ }
47
+ if (created) fs.rmSync(temporary, { force: true });
48
+ outputFiles.delete(temporary);
49
+ throw error;
50
+ } finally {
51
+ if (descriptor !== void 0) fs.closeSync(descriptor);
52
+ }
53
+ }
54
+ function removeTemporaryFile(file, outputFiles) {
55
+ try {
56
+ fs.rmSync(file, { force: true });
57
+ } finally {
58
+ outputFiles.delete(file);
59
+ }
60
+ }
61
+ function replaceStylesheetFile(file, bytes, outputFiles, mode) {
62
+ const temporary = prepareTemporaryFile(file, bytes, outputFiles, mode);
63
+ try {
64
+ renameStylesheetFile(temporary, file);
65
+ } finally {
66
+ removeTemporaryFile(temporary, outputFiles);
67
+ }
68
+ }
69
+ function resolveEntryTarget(file) {
70
+ for (let depth = 0; depth < 40; depth++) {
71
+ if (!fs.lstatSync(file, { throwIfNoEntry: false })?.isSymbolicLink()) return file;
72
+ file = path.resolve(path.dirname(file), fs.readlinkSync(file));
73
+ }
74
+ throw new Error("Too many symbolic links in stylesheet output path");
75
+ }
76
+ /** Publish immutable sidecars completely before atomically replacing the entry. */
77
+ function publishStylesheet(entry, css, assets, outputFiles, journal) {
78
+ fs.mkdirSync(path.dirname(entry), { recursive: true });
79
+ const target = resolveEntryTarget(entry);
80
+ const previous = fs.statSync(target, { throwIfNoEntry: false });
81
+ if (previous) {
82
+ if (!previous.isFile()) throw new Error(`Stylesheet output is not a file: ${target}`);
83
+ fs.accessSync(target, fs.constants.W_OK);
84
+ }
85
+ outputFiles.add(target);
86
+ for (const [file, bytes] of assets) {
87
+ outputFiles.add(file);
88
+ if (file === target || file === entry) throw new Error(`Stylesheet asset collides with output: ${file}`);
89
+ matchesAsset(file, bytes);
90
+ }
91
+ const prepared = /* @__PURE__ */ new Map();
92
+ let entryTemporary;
93
+ try {
94
+ for (const [file, bytes] of assets) if (!matchesAsset(file, bytes)) prepared.set(file, prepareTemporaryFile(file, bytes, outputFiles));
95
+ entryTemporary = prepareTemporaryFile(target, Buffer.from(css), outputFiles, previous ? previous.mode & 511 : void 0);
96
+ journal?.beforePublish(prepared, entryTemporary);
97
+ for (const [file, temporary] of prepared) try {
98
+ fs.linkSync(temporary, file);
99
+ } catch (error) {
100
+ if (error.code !== "EEXIST" || !matchesAsset(file, assets.get(file))) throw error;
101
+ }
102
+ renameStylesheetFile(entryTemporary, target);
103
+ journal?.afterPublish();
104
+ } finally {
105
+ for (const temporary of prepared.values()) removeTemporaryFile(temporary, outputFiles);
106
+ if (entryTemporary) removeTemporaryFile(entryTemporary, outputFiles);
107
+ }
108
+ }
109
+ export { publishStylesheet, replaceStylesheetFile, resolveEntryTarget };
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ // src/source-watch.ts
4
+ const normalize = (value) => value.replace(/\\/g, "/");
5
+ function createSourceWatchPlan(fg, cwd, patterns, ignore = []) {
6
+ const tasks = fg.generateTasks(patterns.map(normalize), {
7
+ cwd,
8
+ ignore: ignore.map(normalize)
9
+ });
10
+ const bases = tasks.map((task) => path.resolve(cwd, task.base));
11
+ const match = (file, pattern) => {
12
+ const absolute = normalize(path.resolve(cwd, file));
13
+ const candidate = /[/\\]$/.test(file) && !absolute.endsWith("/") ? absolute + "/" : absolute;
14
+ return path.matchesGlob(candidate, normalize(path.resolve(cwd, pattern)));
15
+ };
16
+ const excluded = (file) => tasks.length > 0 && tasks.every((task) => task.negative.some((pattern) => match(file, pattern)));
17
+ const matches = (file) => tasks.some((task) => task.positive.some((pattern) => match(file, pattern)) && !task.negative.some((pattern) => match(file, pattern)));
18
+ const contains = (parent, child) => {
19
+ const relative = path.relative(parent, child);
20
+ return relative === "" || !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
21
+ };
22
+ const roots = [...new Set(bases.map((base) => {
23
+ while (!fs.existsSync(base) || !fs.statSync(base).isDirectory()) {
24
+ const parent = path.dirname(base);
25
+ if (parent === base) break;
26
+ base = parent;
27
+ }
28
+ return base;
29
+ }))];
30
+ return {
31
+ roots: roots.filter((root) => !roots.some((other) => other !== root && contains(other, root))),
32
+ matches,
33
+ ignored(file, stats) {
34
+ const absolute = path.resolve(cwd, file);
35
+ if (excluded(absolute) || stats?.isDirectory() && excluded(absolute + path.sep)) return true;
36
+ if (stats?.isDirectory()) return !bases.some((base) => contains(base, absolute) || contains(absolute, base));
37
+ return stats?.isFile() ? !matches(absolute) : false;
38
+ }
39
+ };
40
+ }
41
+ export { createSourceWatchPlan };
@@ -0,0 +1,70 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { loadProjectManifest } from "@master/css-compiler/project";
4
+ import { createToolingSessionSync } from "@master/css-tooling/node";
5
+ import defaultManifestJSON from "@master/css-preset/default-manifest.json" with { type: "json" };
6
+ // src/suggest.ts
7
+ async function runSuggest(declarations, options = {}) {
8
+ const report = {
9
+ version: 1,
10
+ cwd: path.resolve(options.cwd || process.cwd()),
11
+ manifest: {
12
+ status: "not-loaded",
13
+ entries: []
14
+ },
15
+ result: null,
16
+ validation: [],
17
+ diagnostics: []
18
+ };
19
+ let failureCode = "SUGGEST_INPUT_ERROR";
20
+ try {
21
+ if (options.stdin ? declarations !== void 0 : declarations === void 0) throw new Error("Provide either a declaration argument or --stdin, but not both.");
22
+ const input = options.stdin ? fs.readFileSync(0, "utf8") : declarations;
23
+ if (!input.trim()) throw new Error("The declaration block must not be empty.");
24
+ if (!fs.statSync(report.cwd).isDirectory()) throw new Error("The project root must be a directory.");
25
+ failureCode = "PROJECT_MANIFEST_LOAD_ERROR";
26
+ const project = await loadProjectManifest({
27
+ root: report.cwd,
28
+ baseManifest: defaultManifestJSON
29
+ });
30
+ report.manifest = {
31
+ status: "loaded",
32
+ entries: project.entries
33
+ };
34
+ report.diagnostics.push(...project.diagnostics);
35
+ failureCode = "SUGGEST_EXECUTION_ERROR";
36
+ using session = createToolingSessionSync({ manifest: project.manifest });
37
+ report.result = session.suggestClasses(input);
38
+ for (const className of report.result.classes) {
39
+ const inspection = session.inspectClassName(className);
40
+ const { matchStatus, cssSyntaxStatus, cssValueStatus, browserSupport } = inspection;
41
+ report.validation.push({
42
+ className,
43
+ matchStatus,
44
+ cssSyntaxStatus,
45
+ cssValueStatus,
46
+ browserSupport
47
+ });
48
+ report.diagnostics.push(...(inspection.diagnostics || []).map((diagnostic) => ({
49
+ ...diagnostic,
50
+ className
51
+ })));
52
+ }
53
+ } catch (error) {
54
+ if (failureCode === "PROJECT_MANIFEST_LOAD_ERROR") report.manifest.status = "error";
55
+ report.result = null;
56
+ report.validation = [];
57
+ report.diagnostics.push({
58
+ code: failureCode,
59
+ severity: "error",
60
+ message: error instanceof Error ? error.message : String(error)
61
+ });
62
+ }
63
+ for (const diagnostic of report.diagnostics) if (diagnostic.severity === "error") {
64
+ process.stderr.write(`${diagnostic.code}: ${diagnostic.message}\n`);
65
+ process.exitCode = 1;
66
+ }
67
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
68
+ return report;
69
+ }
70
+ export { runSuggest as default };
package/package.json CHANGED
@@ -1,60 +1,56 @@
1
1
  {
2
- "name": "@master/css-cli",
3
- "type": "module",
4
- "license": "MIT",
5
- "description": "Master CSS CLI",
6
- "author": "Aoyue Design LLC.",
7
- "funding": "https://rc.css.master.co/sponsor",
8
- "homepage": "https://css.master.co",
9
- "bugs": {
10
- "url": "https://github.com/master-co/css/issues"
11
- },
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/master-co/css.git",
15
- "directory": "packages/cli"
16
- },
17
- "keywords": [
18
- "cli",
19
- "css",
20
- "mastercss"
21
- ],
22
- "files": [
23
- "dist"
24
- ],
25
- "publishConfig": {
26
- "access": "public",
27
- "provenance": true
28
- },
29
- "dependencies": {
30
- "bytes": "^3.1.2",
31
- "chokidar": "^5.0.0",
32
- "commander": "^14.0.3",
33
- "consola": "^3.4.2",
34
- "fast-glob": "^3.3.3",
35
- "@master/css": "^2.0.0-rc.88",
36
- "@master/css-diagnostics": "^2.0.0-rc.88",
37
- "@master/css-language": "^2.0.0-rc.88",
38
- "@master/css-lexer": "^2.0.0-rc.88",
39
- "@master/css-lint": "^2.0.0-rc.88",
40
- "@master/css-project": "^2.0.0-rc.88",
41
- "@master/css-scanner": "^2.0.0-rc.88",
42
- "@master/css-stylesheet": "^2.0.0-rc.88",
43
- "@master/css-validator": "^2.0.0-rc.88"
44
- },
45
- "devDependencies": {
46
- "@types/bytes": "^3.1.5",
47
- "upath": "^2.0.1"
48
- },
49
- "version": "2.0.0-rc.88",
50
- "scripts": {
51
- "build": "tsdown --config ../../shared/tsdown-package.config.ts",
52
- "dev": "tsdown --config ../../shared/tsdown-package.config.ts --watch",
53
- "lint": "eslint",
54
- "type-check": "tsc -b tsconfig.typecheck.json",
55
- "test": "vitest"
56
- },
57
- "bin": {
58
- "css-cli": "./dist/bin/index.js"
59
- }
60
- }
2
+ "name": "@master/css-cli",
3
+ "scripts": {
4
+ "build": "bun --bun tsdown --config ../../shared/tsdown-package.config.ts",
5
+ "dev": "bun --bun tsdown --config ../../shared/tsdown-package.config.ts --watch",
6
+ "lint": "bun --bun eslint",
7
+ "type-check": "bun --bun tsc -b tsconfig.typecheck.json",
8
+ "test": "node ../../node_modules/vitest/vitest.mjs run"
9
+ },
10
+ "type": "module",
11
+ "engines": {
12
+ "node": "^24"
13
+ },
14
+ "license": "MIT",
15
+ "description": "Master CSS CLI",
16
+ "author": "Aoyue Design LLC.",
17
+ "homepage": "https://css.master.co",
18
+ "bugs": {
19
+ "url": "https://github.com/master-co/css/issues"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/master-co/css.git",
24
+ "directory": "packages/cli"
25
+ },
26
+ "keywords": [
27
+ "cli",
28
+ "css",
29
+ "mastercss"
30
+ ],
31
+ "bin": {
32
+ "master-css": "./dist/bin/index.js"
33
+ },
34
+ "exports": {},
35
+ "sideEffects": false,
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "provenance": true
42
+ },
43
+ "dependencies": {
44
+ "@master/css": "2.0.0",
45
+ "@master/css-compiler": "2.0.0",
46
+ "@master/css-preset": "2.0.0",
47
+ "@master/css-schema": "2.0.0",
48
+ "@master/css-tooling": "2.0.0",
49
+ "bytes": "^3.1.2",
50
+ "chokidar": "^5.0.0",
51
+ "commander": "^15.0.0",
52
+ "fast-glob": "^3.3.3"
53
+ },
54
+ "version": "2.0.0",
55
+ "gitHead": "6bf9adc4432b67e62613caea59d3b554711b4d54"
56
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- The MIT License
2
-
3
- Copyright (c) Aoyue Design LLC.
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
package/dist/scan.js DELETED
@@ -1,199 +0,0 @@
1
- import { DEFAULT_SCAN_OUTPUT } from "./constants.js";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- //#region src/scan.ts
5
- const DEFAULT_SOURCE_PATTERNS = ["**/*.{html,htm,js,jsx,cjs,ts,tsx,mts,cts,svelte,astro,vue,md,mdx,pug,php}"];
6
- let scannerModulePromise;
7
- let stylesheetModulePromise;
8
- let projectEntriesModulePromise;
9
- let fastGlobModulePromise;
10
- let chokidarModulePromise;
11
- let loggerPromise;
12
- let bytesPromise;
13
- async function registerManagedCSSEntries(scanner, styleCSSSources) {
14
- const [{ registerStyleCSSSource }, { findCSSManifestEntryFiles }] = await Promise.all([loadStylesheetModule(), loadProjectEntriesModule()]);
15
- styleCSSSources.clear();
16
- for (const entry of await findCSSManifestEntryFiles(scanner.cwd)) await registerStyleCSSSource(scanner, styleCSSSources, entry, fs.readFileSync(entry, "utf8"), { projectDir: scanner.cwd });
17
- scanner.resetDependencies = [...new Set(Array.from(styleCSSSources.values()).flatMap((source) => source.dependencies))];
18
- }
19
- function normalizeSourcePatterns(specifiedSourcePaths) {
20
- return specifiedSourcePaths?.length ? specifiedSourcePaths : DEFAULT_SOURCE_PATTERNS;
21
- }
22
- function normalizeGlobPatterns(patterns) {
23
- return patterns.map((pattern) => pattern.replace(/\\/g, "/"));
24
- }
25
- function resolveSourcePaths(scanner, fg, sourcePatterns, ignore = []) {
26
- return fg.sync(normalizeGlobPatterns(sourcePatterns), {
27
- cwd: scanner.cwd,
28
- ignore: normalizeGlobPatterns(ignore)
29
- }).filter(Boolean);
30
- }
31
- async function scanSourceFile(scanner, source) {
32
- const filepath = path.resolve(scanner.cwd, source);
33
- await scanner.scan(source, fs.readFileSync(filepath, "utf8"));
34
- }
35
- async function scanSourceFiles(scanner, sourcePaths) {
36
- await Promise.all(sourcePaths.map((source) => scanSourceFile(scanner, source)));
37
- }
38
- async function prepareScanner(scanner, styleCSSSources, sourcePaths) {
39
- await registerManagedCSSEntries(scanner, styleCSSSources);
40
- await scanSourceFiles(scanner, sourcePaths);
41
- }
42
- async function exportCSS(scanner, css, filename = DEFAULT_SCAN_OUTPUT) {
43
- const filepath = path.resolve(scanner.cwd, filename);
44
- const dir = path.dirname(filepath);
45
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
46
- fs.writeFileSync(filepath, css);
47
- if (scanner.options.verbose) {
48
- const [logger, bytes] = await Promise.all([loadLogger(), loadBytes()]);
49
- logger.success(`${filename} exported ${bytes(css.length)}`);
50
- }
51
- scanner.emit("export", filename, filepath);
52
- }
53
- function formatWatchedPath(cwd, file) {
54
- return path.isAbsolute(file) ? path.relative(cwd, file) : file;
55
- }
56
- async function waitForWatcherReady(watcher) {
57
- await new Promise((resolve) => watcher.once("ready", resolve));
58
- await new Promise((resolve) => setTimeout(resolve, 0));
59
- }
60
- async function runScan(specifiedSourcePaths = [], options = {}) {
61
- const pipelineModules = Promise.all([loadStylesheetModule(), loadProjectEntriesModule()]);
62
- const [CSSScanner, fg] = await Promise.all([loadCSSScanner(), loadFastGlob()]);
63
- const { watch, output, verbose, cwd } = options;
64
- const scanner = new CSSScanner({}, cwd);
65
- const styleCSSSources = /* @__PURE__ */ new Map();
66
- const sourcePatterns = normalizeSourcePatterns(specifiedSourcePaths);
67
- const writeOutput = async () => {
68
- const { createExtractedCSS } = await loadStylesheetModule();
69
- const css = await createExtractedCSS({
70
- scanner,
71
- styleCSSSources,
72
- projectDir: scanner.cwd
73
- });
74
- if (options.export) await exportCSS(scanner, css, output);
75
- else console.log(css);
76
- };
77
- scanner.on("init", (options) => {
78
- if (!specifiedSourcePaths.length) {
79
- if (!options.exclude?.includes("**/node_modules/**")) options.exclude?.push("**/node_modules/**");
80
- if (!options.exclude?.includes("node_modules")) options.exclude?.push("node_modules");
81
- }
82
- options.verbose = verbose ? +verbose : options.verbose;
83
- });
84
- await scanner.init();
85
- const scanPaths = () => resolveSourcePaths(scanner, fg, sourcePatterns, specifiedSourcePaths.length ? [] : scanner.options.exclude);
86
- if (watch) {
87
- const chokidar = await loadChokidar();
88
- const watchers = [];
89
- let restarting = false;
90
- let writing = Promise.resolve();
91
- const queueWrite = () => {
92
- writing = writing.then(writeOutput);
93
- return writing;
94
- };
95
- const closeWatchers = async () => {
96
- await Promise.all(watchers.splice(0).map((watcher) => watcher.close()));
97
- };
98
- const startWatchers = async () => {
99
- const sourcePaths = scanPaths();
100
- if (sourcePaths.length) {
101
- const sourceWatcher = chokidar.watch(sourcePaths, {
102
- cwd: scanner.cwd,
103
- ignoreInitial: true
104
- });
105
- sourceWatcher.on("add", (source) => {
106
- scanSourceFile(scanner, source).then(queueWrite);
107
- });
108
- sourceWatcher.on("change", (source) => {
109
- scanSourceFile(scanner, source).then(queueWrite);
110
- });
111
- watchers.push(sourceWatcher);
112
- await waitForWatcherReady(sourceWatcher);
113
- }
114
- if (scanner.resetDependencies.length) {
115
- const planWatcher = chokidar.watch(scanner.resetDependencies, { ignoreInitial: true });
116
- const handlePlanChange = async (resetDependency) => {
117
- if (restarting) return;
118
- restarting = true;
119
- try {
120
- if (scanner.options.verbose) {
121
- const logger = await loadLogger();
122
- logger.log("");
123
- logger.info(`[change] ${formatWatchedPath(scanner.cwd, resetDependency)}`);
124
- }
125
- await closeWatchers();
126
- await scanner.reset(scanner.customOptions, { emit: false });
127
- await prepareScanner(scanner, styleCSSSources, scanPaths());
128
- await queueWrite();
129
- await startWatchers();
130
- const logger = await loadLogger();
131
- logger.log("");
132
- logger.info("Restart watching source changes");
133
- scanner.emit("resetDependencyChange");
134
- } finally {
135
- restarting = false;
136
- }
137
- };
138
- planWatcher.on("add", (resetDependency) => {
139
- handlePlanChange(resetDependency);
140
- });
141
- planWatcher.on("change", (resetDependency) => {
142
- handlePlanChange(resetDependency);
143
- });
144
- planWatcher.on("unlink", (resetDependency) => {
145
- handlePlanChange(resetDependency);
146
- });
147
- watchers.push(planWatcher);
148
- await waitForWatcherReady(planWatcher);
149
- }
150
- };
151
- process.once("SIGTERM", () => {
152
- closeWatchers().finally(() => process.exit(0));
153
- });
154
- process.once("SIGINT", () => {
155
- closeWatchers().finally(() => process.exit(0));
156
- });
157
- await pipelineModules;
158
- await prepareScanner(scanner, styleCSSSources, scanPaths());
159
- await queueWrite();
160
- await startWatchers();
161
- const logger = await loadLogger();
162
- logger.log("");
163
- logger.info("Start watching source changes");
164
- } else {
165
- await pipelineModules;
166
- await prepareScanner(scanner, styleCSSSources, scanPaths());
167
- await writeOutput();
168
- }
169
- }
170
- function loadCSSScanner() {
171
- scannerModulePromise ||= import("@master/css-scanner").then((mod) => mod.default);
172
- return scannerModulePromise;
173
- }
174
- function loadStylesheetModule() {
175
- stylesheetModulePromise ||= import("@master/css-stylesheet");
176
- return stylesheetModulePromise;
177
- }
178
- function loadProjectEntriesModule() {
179
- projectEntriesModulePromise ||= import("@master/css-project/entries");
180
- return projectEntriesModulePromise;
181
- }
182
- function loadFastGlob() {
183
- fastGlobModulePromise ||= import("fast-glob").then((mod) => mod.default || mod);
184
- return fastGlobModulePromise;
185
- }
186
- function loadChokidar() {
187
- chokidarModulePromise ||= import("chokidar").then((mod) => mod.default);
188
- return chokidarModulePromise;
189
- }
190
- function loadLogger() {
191
- loggerPromise ||= import("consola").then(({ createConsola }) => createConsola({ level: 3 }));
192
- return loggerPromise;
193
- }
194
- function loadBytes() {
195
- bytesPromise ||= import("bytes").then((mod) => mod.default || mod);
196
- return bytesPromise;
197
- }
198
- //#endregion
199
- export { runScan as default };