@navecss/core 0.2.0 → 0.4.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,42 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import url from 'url';
5
+
6
+ // src/postcss-extend-module.ts
7
+ function resolveExtendSpecifier(specifier, dir = process.cwd()) {
8
+ const file = path.resolve(dir, specifier);
9
+ const stats = fs.existsSync(file) ? fs.statSync(file) : void 0;
10
+ if (!stats?.isFile()) {
11
+ throw new Error(`@nave: cannot find extend module "${specifier}" from "${dir}"`);
12
+ }
13
+ return file;
14
+ }
15
+ function isJsonPath(file) {
16
+ return path.extname(file).toLowerCase() === ".json";
17
+ }
18
+ async function importExtendMap(moduleUrl, file) {
19
+ const value = isJsonPath(file) ? JSON.parse(fs.readFileSync(file, "utf8")) : (await import(moduleUrl)).default;
20
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
21
+ throw new Error(
22
+ `@nave: extend module "${file}" must have a default export that is a plain object`
23
+ );
24
+ }
25
+ return value;
26
+ }
27
+ function loadExtendModule(file, cache) {
28
+ const digest = crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
29
+ const moduleUrl = `${url.pathToFileURL(file).href}?v=${digest}`;
30
+ const cached = cache.get(moduleUrl);
31
+ if (cached) return cached;
32
+ const filePrefix = `${url.pathToFileURL(file).href}?v=`;
33
+ for (const key of cache.keys()) if (key.startsWith(filePrefix)) cache.delete(key);
34
+ const pending = importExtendMap(moduleUrl, file);
35
+ cache.set(moduleUrl, pending);
36
+ return pending;
37
+ }
38
+ async function applyExtendModule(file, cache, setExtend) {
39
+ setExtend(await loadExtendModule(file, cache));
40
+ }
41
+
42
+ export { applyExtendModule, resolveExtendSpecifier };
@@ -0,0 +1,302 @@
1
+ import { atoms } from './chunk-CUL5PLPI.js';
2
+ import { atomClassMap } from './chunk-GZL2VW7C.js';
3
+
4
+ // src/directive/diagnostics-format.ts
5
+ function vocabulary(extend) {
6
+ const extendKeys = new Set(Object.keys(extend));
7
+ const names = [];
8
+ for (const [name, definition] of Object.entries(atoms)) {
9
+ if (definition && !extendKeys.has(name)) names.push(name);
10
+ }
11
+ for (const [name, definition] of Object.entries(extend)) {
12
+ if (definition) names.push(name);
13
+ }
14
+ return names;
15
+ }
16
+ function distance(a, b) {
17
+ const d = [];
18
+ for (let i = 0; i <= a.length; i++) d.push([i]);
19
+ for (let j = 0; j <= b.length; j++) d[0][j] = j;
20
+ for (let i = 1; i <= a.length; i++) {
21
+ for (let j = 1; j <= b.length; j++) {
22
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
23
+ let best = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
24
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
25
+ best = Math.min(best, d[i - 2][j - 2] + 1);
26
+ }
27
+ d[i][j] = best;
28
+ }
29
+ }
30
+ return d[a.length][b.length];
31
+ }
32
+ function normalize(name) {
33
+ const lower = name.toLowerCase();
34
+ const withoutPrefix = lower.startsWith("nave-") ? lower.slice(5) : lower;
35
+ return withoutPrefix.replaceAll("-", "");
36
+ }
37
+ function groupByDistance(normalizedTyped, names) {
38
+ let minDistance = Infinity;
39
+ const byDistance = /* @__PURE__ */ new Map();
40
+ for (const name of names) {
41
+ const d = distance(normalizedTyped, normalize(name));
42
+ if (!byDistance.has(d)) byDistance.set(d, []);
43
+ byDistance.get(d).push(name);
44
+ if (d < minDistance) minDistance = d;
45
+ }
46
+ return { byDistance, minDistance };
47
+ }
48
+ function hasCamelCaseNote(typed, candidates, minDistance, extend) {
49
+ if (minDistance !== 0 || candidates.length !== 1) return false;
50
+ if (!typed.includes("-")) return false;
51
+ const candidate = candidates[0];
52
+ const shadow = extend[candidate];
53
+ if (shadow) return false;
54
+ return Object.hasOwn(atomClassMap, candidate);
55
+ }
56
+ function findHint(typed, extend) {
57
+ const normalizedTyped = normalize(typed);
58
+ const { byDistance, minDistance } = groupByDistance(normalizedTyped, vocabulary(extend));
59
+ if (minDistance > 2 || minDistance >= normalizedTyped.length / 2) return void 0;
60
+ const candidates = byDistance.get(minDistance) ?? [];
61
+ if (candidates.length === 0) return void 0;
62
+ return { candidates, camelCaseNote: hasCamelCaseNote(typed, candidates, minDistance, extend) };
63
+ }
64
+ function quoteList(items) {
65
+ if (items.length === 1) return `"${items[0]}"`;
66
+ return `${items.slice(0, -1).map((i) => `"${i}"`).join(", ")} or "${items.at(-1)}"`;
67
+ }
68
+ function formatUnknownAtom(name, extend) {
69
+ const first = `@nave: unknown atom "${name}".`;
70
+ const hint = findHint(name, extend);
71
+ if (hint) {
72
+ const camelCase = hint.camelCaseNote ? ` Atom names are camelCase; "${atomClassMap[hint.candidates[0]]}" is its class.` : "";
73
+ return `${first} Did you mean ${quoteList(hint.candidates)}?${camelCase}`;
74
+ }
75
+ const available = vocabulary(extend).join(", ");
76
+ return `${first} If it is an atom of your own, pass it in the extend option.
77
+ Available: ${available}`;
78
+ }
79
+ var ANSWERED_BY_STANDALONE = /* @__PURE__ */ new Set([
80
+ "@navecss/core",
81
+ "@navecss/core/atomic",
82
+ "@navecss/core/layers",
83
+ "@navecss/core/reset",
84
+ "@navecss/core/standalone",
85
+ "@navecss/tokens/css"
86
+ ]);
87
+ var STANDALONE_PATH = "node_modules/@navecss/core/dist/standalone.css";
88
+ var STANDALONE_CDN_URL = "https://cdn.jsdelivr.net/npm/@navecss/core@<version>/dist/standalone.css";
89
+ var NO_TOKENS_PATH = "node_modules/@navecss/core/dist/no-tokens.css";
90
+ var NO_TOKENS_CDN_URL = "https://cdn.jsdelivr.net/npm/@navecss/core@<version>/dist/no-tokens.css";
91
+ var BAD_PARENT_WORKAROUND = ". A directive here can be written `& { @nave ...; }` inside the group rule instead.";
92
+ function formatBadParent(diagnostic) {
93
+ const workaround = diagnostic.detail === "nested-group" ? BAD_PARENT_WORKAROUND : "";
94
+ return `@nave must be the direct child of a CSS rule selector block${workaround}`;
95
+ }
96
+ var FIXED_TEXTS = {
97
+ "bad-token": (d) => `@nave: unexpected "${d.text ?? ""}"; a directive takes only atom names (separate atom names with spaces)`,
98
+ "bare-import": (d) => {
99
+ const specifier = d.text ?? "";
100
+ const quoted = JSON.stringify(specifier);
101
+ if (ANSWERED_BY_STANDALONE.has(specifier)) {
102
+ return `@import ${quoted} names a package, so a browser cannot load it. Link Nave's self-contained stylesheet from the page instead, ahead of your own stylesheets: ${STANDALONE_PATH}, or ${STANDALONE_CDN_URL} with <version> replaced by the version you installed.`;
103
+ }
104
+ if (specifier === "@navecss/core/no-tokens") {
105
+ return `@import ${quoted} names a package, so a browser cannot load it. Link that entry's own file from the page instead, ahead of your own stylesheets: ${NO_TOKENS_PATH}, or ${NO_TOKENS_CDN_URL} with <version> replaced by the version you installed. Not the self-contained stylesheet: it holds Nave's token layer, which this entry leaves out.`;
106
+ }
107
+ return `@import ${quoted} names no file relative to the output, so a browser cannot load it. Point it at a file the page can reach, by a path that works from the output or by a URL, or bundle the stylesheet.`;
108
+ },
109
+ "has-block": () => "@nave: a directive with a {} block is not supported",
110
+ "in-keyframes": () => "@nave cannot be used inside @keyframes",
111
+ "missing-declarations": (d) => `@nave: atom "${d.name ?? ""}" is registered without a declarations object`,
112
+ "no-atom": () => "@nave: directive names no atom"
113
+ };
114
+ function formatDiagnostic(diagnostic, options = {}) {
115
+ if (diagnostic.code === "unknown-atom")
116
+ return formatUnknownAtom(diagnostic.name, options.extend ?? {});
117
+ if (diagnostic.code === "bad-parent") return formatBadParent(diagnostic);
118
+ return FIXED_TEXTS[diagnostic.code](diagnostic);
119
+ }
120
+
121
+ // src/directive/source-map.ts
122
+ var BASE64_DIGITS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
123
+ var NEWLINE_CHARS = /* @__PURE__ */ new Set(["\n", "\r", "\f"]);
124
+ function encodeVLQ(n) {
125
+ let value = n < 0 ? (-n << 1) + 1 : n << 1;
126
+ let result = "";
127
+ do {
128
+ let digit = value & 31;
129
+ value >>>= 5;
130
+ if (value > 0) digit |= 32;
131
+ result += BASE64_DIGITS[digit];
132
+ } while (value > 0);
133
+ return result;
134
+ }
135
+ var BASE64_VALUE = new Map([...BASE64_DIGITS].map((c, i) => [c, i]));
136
+ function decodeVLQSegment(mappings, start) {
137
+ let value = 0;
138
+ let shift = 0;
139
+ let i = start;
140
+ for (; ; ) {
141
+ const digit = BASE64_VALUE.get(mappings[i]);
142
+ i++;
143
+ value += (digit & 31) << shift;
144
+ if ((digit & 32) === 0) break;
145
+ shift += 5;
146
+ }
147
+ const isNegative = (value & 1) === 1;
148
+ const magnitude = value >>> 1;
149
+ return { value: isNegative ? -magnitude : magnitude, next: i };
150
+ }
151
+ var MappingsBuilder = class {
152
+ hasSegmentOnLine = false;
153
+ lines = [[]];
154
+ outputColumn = 0;
155
+ outputLine = 0;
156
+ // A lone trailing `\r`, possibly half of a CRLF pair split across two
157
+ // `advance()` calls by a removed directive: consumed by the next call's
158
+ // leading `\n`, if any, instead of double-counted.
159
+ pendingCR = false;
160
+ prevGeneratedColumn = 0;
161
+ prevSourceColumn = 0;
162
+ prevSourceIndex = 0;
163
+ prevSourceLine = 0;
164
+ /**
165
+ * A plain indexed loop, not `for...of` (which iterates by code point, so
166
+ * a surrogate pair would advance the column by one instead of two — every
167
+ * position here is in UTF-16 code units). A line break is CR, FF, LF or a
168
+ * CRLF pair, CSS Syntax 3's own "newline" (§4.2).
169
+ */
170
+ advance(text) {
171
+ let i = this.consumePendingCR(text);
172
+ while (i < text.length) {
173
+ const c = text[i];
174
+ if (c !== void 0 && NEWLINE_CHARS.has(c)) {
175
+ i = this.advancePastNewline(text, i, c);
176
+ continue;
177
+ }
178
+ this.outputColumn++;
179
+ i++;
180
+ }
181
+ }
182
+ /**
183
+ * `i` past the line break `c` (at `i`): past the whole pair for a `\r`
184
+ * immediately followed by `\n`, and `pendingCR` set instead when `\r` is
185
+ * the last character `text` has to offer.
186
+ */
187
+ advancePastNewline(text, i, c) {
188
+ let next = i + 1;
189
+ if (c === "\r" && text[next] === "\n") {
190
+ next++;
191
+ } else if (c === "\r" && next === text.length) {
192
+ this.pendingCR = true;
193
+ }
194
+ this.outputLine++;
195
+ this.outputColumn = 0;
196
+ this.lines.push([]);
197
+ this.prevGeneratedColumn = 0;
198
+ this.hasSegmentOnLine = false;
199
+ return next;
200
+ }
201
+ /**
202
+ * How far into `text` `advance` should start: `1` for a lone `\n` that is
203
+ * really the other half of a CRLF pair split across two calls; `0`
204
+ * otherwise.
205
+ */
206
+ consumePendingCR(text) {
207
+ if (text.length === 0) return 0;
208
+ const skip = this.pendingCR && text.startsWith("\n") ? 1 : 0;
209
+ this.pendingCR = false;
210
+ return skip;
211
+ }
212
+ mark(position) {
213
+ const sourceIndex = position.sourceIndex ?? 0;
214
+ const sourceLine = position.line - 1;
215
+ const generatedColumnDelta = this.hasSegmentOnLine ? this.outputColumn - this.prevGeneratedColumn : this.outputColumn;
216
+ const sourceIndexDelta = sourceIndex - this.prevSourceIndex;
217
+ const sourceLineDelta = sourceLine - this.prevSourceLine;
218
+ const sourceColumnDelta = position.column - this.prevSourceColumn;
219
+ this.lines[this.outputLine].push(
220
+ encodeVLQ(generatedColumnDelta) + encodeVLQ(sourceIndexDelta) + encodeVLQ(sourceLineDelta) + encodeVLQ(sourceColumnDelta)
221
+ );
222
+ this.prevGeneratedColumn = this.outputColumn;
223
+ this.prevSourceIndex = sourceIndex;
224
+ this.prevSourceLine = sourceLine;
225
+ this.prevSourceColumn = position.column;
226
+ this.hasSegmentOnLine = true;
227
+ }
228
+ toMappings() {
229
+ return this.lines.map((segments) => segments.join(",")).join(";");
230
+ }
231
+ };
232
+ function findCandidate(segments, position) {
233
+ const generatedLine = position.line - 1;
234
+ let low = 0;
235
+ let high = segments.length - 1;
236
+ let candidate;
237
+ while (low <= high) {
238
+ const mid = low + high >> 1;
239
+ const segment = segments[mid];
240
+ const isAtOrBefore = segment.generatedLine < generatedLine || segment.generatedLine === generatedLine && segment.generatedColumn <= position.column;
241
+ if (isAtOrBefore) {
242
+ candidate = segment;
243
+ low = mid + 1;
244
+ } else {
245
+ high = mid - 1;
246
+ }
247
+ }
248
+ return candidate?.generatedLine === generatedLine ? candidate : void 0;
249
+ }
250
+ function decodeIncomingMap(map) {
251
+ const sources = map.sources;
252
+ const segments = decodeSegments(map.mappings);
253
+ return {
254
+ sources,
255
+ sourceRoot: map.sourceRoot,
256
+ sourcesContent: map.sourcesContent,
257
+ originalPositionFor(position) {
258
+ const candidate = findCandidate(segments, position);
259
+ if (!candidate) return;
260
+ return {
261
+ source: sources[candidate.sourceIndex],
262
+ sourceIndex: candidate.sourceIndex,
263
+ line: candidate.sourceLine + 1,
264
+ column: candidate.sourceColumn
265
+ };
266
+ }
267
+ };
268
+ }
269
+ function decodeOneSegment(state, raw, generatedLine) {
270
+ const col = decodeVLQSegment(raw, 0);
271
+ state.generatedColumn += col.value;
272
+ if (col.next >= raw.length) return void 0;
273
+ const srcIdx = decodeVLQSegment(raw, col.next);
274
+ state.sourceIndex += srcIdx.value;
275
+ const srcLine = decodeVLQSegment(raw, srcIdx.next);
276
+ state.sourceLine += srcLine.value;
277
+ const srcCol = decodeVLQSegment(raw, srcLine.next);
278
+ state.sourceColumn += srcCol.value;
279
+ return {
280
+ generatedLine,
281
+ generatedColumn: state.generatedColumn,
282
+ sourceIndex: state.sourceIndex,
283
+ sourceLine: state.sourceLine,
284
+ sourceColumn: state.sourceColumn
285
+ };
286
+ }
287
+ function decodeSegments(mappings) {
288
+ const segments = [];
289
+ const state = { generatedColumn: 0, sourceIndex: 0, sourceLine: 0, sourceColumn: 0 };
290
+ const lines = mappings.split(";");
291
+ for (const [generatedLine, line] of lines.entries()) {
292
+ state.generatedColumn = 0;
293
+ if (line === "") continue;
294
+ for (const raw of line.split(",")) {
295
+ const segment = decodeOneSegment(state, raw, generatedLine);
296
+ if (segment) segments.push(segment);
297
+ }
298
+ }
299
+ return segments;
300
+ }
301
+
302
+ export { MappingsBuilder, decodeIncomingMap, formatDiagnostic };
@@ -0,0 +1,115 @@
1
+ import { createPositionFinder, tokenize, matchBrackets, readItem, isNaveAtKeyword } from './chunk-NFS2REPQ.js';
2
+
3
+ // src/directive/find-survivors.ts
4
+ var NATURAL_STOP_CHARS = /* @__PURE__ */ new Set(["\n", ";", "}"]);
5
+ function naturalTextAfter(css, offset) {
6
+ let end = css.length;
7
+ for (let i = offset; i < css.length; i++) {
8
+ if (NATURAL_STOP_CHARS.has(css[i])) {
9
+ end = i;
10
+ break;
11
+ }
12
+ }
13
+ return css.slice(offset, end).trim();
14
+ }
15
+ function isStructural(token) {
16
+ return token.type !== "whitespace-token" && token.type !== "comment";
17
+ }
18
+ function skipInert(tokens, i, limit) {
19
+ let j = i;
20
+ while (j < limit && !isStructural(tokens[j])) j++;
21
+ return j;
22
+ }
23
+ var Scan = class {
24
+ closerFor;
25
+ css;
26
+ survivors = [];
27
+ tokens;
28
+ constructor(css) {
29
+ this.css = css;
30
+ this.tokens = tokenize(css);
31
+ this.closerFor = matchBrackets(this.tokens);
32
+ }
33
+ };
34
+ function scanForNaveTokens(scan, start, end, selector) {
35
+ for (let i = start; i < end; i++) {
36
+ const token = scan.tokens[i];
37
+ if (isNaveAtKeyword(token))
38
+ scan.survivors.push({
39
+ offset: token.startIndex,
40
+ text: naturalTextAfter(scan.css, token.startIndex),
41
+ selector
42
+ });
43
+ }
44
+ }
45
+ function ruleSelector(scan, item) {
46
+ const braceIndex = item.blockStart - 1;
47
+ return scan.css.slice(scan.tokens[item.start].startIndex, scan.tokens[braceIndex].startIndex).trim();
48
+ }
49
+ function childFrameFor(scan, item, selector) {
50
+ if (item.blockStart === void 0 || item.blockEnd === void 0) return void 0;
51
+ const childSelector = item.kind === "rule" ? ruleSelector(scan, item) : selector;
52
+ return {
53
+ i: skipInert(scan.tokens, item.blockStart, item.blockEnd),
54
+ limit: item.blockEnd,
55
+ selector: childSelector
56
+ };
57
+ }
58
+ function preludeEnd(item) {
59
+ return item.blockStart === void 0 ? item.end : item.blockStart - 1;
60
+ }
61
+ function visitItem(scan, item, selector) {
62
+ switch (item.kind) {
63
+ case "at-rule":
64
+ case "rule": {
65
+ scanForNaveTokens(scan, item.start, preludeEnd(item), selector);
66
+ return childFrameFor(scan, item, selector);
67
+ }
68
+ case "declaration":
69
+ case "invalid": {
70
+ scanForNaveTokens(scan, item.start, item.end, selector);
71
+ return void 0;
72
+ }
73
+ }
74
+ }
75
+ function walkBlock(scan, start, limit, selector) {
76
+ const stack = [{ i: skipInert(scan.tokens, start, limit), limit, selector }];
77
+ while (stack.length > 0) {
78
+ const frame = stack.at(-1);
79
+ if (frame.i >= frame.limit) {
80
+ stack.pop();
81
+ continue;
82
+ }
83
+ const item = readItem(scan.tokens, frame.i, frame.limit, scan.closerFor);
84
+ frame.i = skipInert(scan.tokens, item.end, frame.limit);
85
+ const child = visitItem(scan, item, frame.selector);
86
+ if (child) stack.push(child);
87
+ }
88
+ }
89
+ function findSurvivors(css) {
90
+ const scan = new Scan(css);
91
+ walkBlock(scan, 0, scan.tokens.length, void 0);
92
+ return scan.survivors;
93
+ }
94
+
95
+ // src/directive/findings.ts
96
+ function findingsInText(file, raw) {
97
+ const css = raw.startsWith("\uFEFF") ? raw.slice(1) : raw;
98
+ const positionAt = createPositionFinder(css);
99
+ return findSurvivors(css).map((survivor) => {
100
+ const position = positionAt(survivor.offset);
101
+ return {
102
+ file,
103
+ line: position.line,
104
+ column: position.column + 1,
105
+ text: survivor.text,
106
+ ...survivor.selector !== void 0 && { selector: survivor.selector }
107
+ };
108
+ });
109
+ }
110
+ function formatFinding(finding) {
111
+ const where = finding.selector === void 0 ? "" : ` (in ${finding.selector.replaceAll(/\s+/g, " ")})`;
112
+ return `${finding.file}:${finding.line}:${finding.column}: ${finding.text}${where}`;
113
+ }
114
+
115
+ export { findingsInText, formatFinding };
@@ -0,0 +1,152 @@
1
+ import { findingsInText } from './chunk-UANF3FGO.js';
2
+ import { readFile, stat, realpath, readdir } from 'fs/promises';
3
+ import path from 'path';
4
+
5
+ var NotAStylesheetPathError = class extends Error {
6
+ path;
7
+ constructor(path2) {
8
+ super(`${path2} is not a stylesheet file`);
9
+ this.path = path2;
10
+ this.name = "NotAStylesheetPathError";
11
+ }
12
+ };
13
+ function hasCssExtension(name) {
14
+ return path.extname(name).toLowerCase() === ".css";
15
+ }
16
+ async function entryKind(entry, entryPath, state) {
17
+ if (entry.isDirectory()) return "directory";
18
+ if (entry.isFile()) return hasCssExtension(entry.name) ? "css-file" : "skip";
19
+ if (!entry.isSymbolicLink()) return "skip";
20
+ let targetStats;
21
+ try {
22
+ targetStats = await stat(entryPath);
23
+ } catch {
24
+ if (hasCssExtension(entry.name)) state.unreadablePaths.push(entryPath);
25
+ return "skip";
26
+ }
27
+ if (targetStats.isDirectory()) return "directory";
28
+ if (targetStats.isFile()) return hasCssExtension(entry.name) ? "css-file" : "skip";
29
+ return "skip";
30
+ }
31
+ async function listSubdirectory(entryPath, state) {
32
+ let canonicalPath;
33
+ try {
34
+ canonicalPath = await realpath(entryPath);
35
+ } catch {
36
+ state.unreadablePaths.push(entryPath);
37
+ return [];
38
+ }
39
+ if (state.visitedDirs.has(canonicalPath)) return [];
40
+ state.visitedDirs.add(canonicalPath);
41
+ return listDirectory(entryPath, state);
42
+ }
43
+ async function cssFileIfNew(entryPath, state) {
44
+ let canonicalFile;
45
+ try {
46
+ canonicalFile = await realpath(entryPath);
47
+ } catch {
48
+ state.unreadablePaths.push(entryPath);
49
+ return void 0;
50
+ }
51
+ if (state.reportedRealFiles.has(canonicalFile)) return void 0;
52
+ state.reportedRealFiles.add(canonicalFile);
53
+ return entryPath;
54
+ }
55
+ async function listDirectory(dir, state) {
56
+ let entries;
57
+ try {
58
+ entries = await readdir(dir, { withFileTypes: true });
59
+ } catch {
60
+ state.unreadablePaths.push(dir);
61
+ return [];
62
+ }
63
+ const files = [];
64
+ for (const entry of entries) {
65
+ const entryPath = path.join(dir, entry.name);
66
+ const kind = await entryKind(entry, entryPath, state);
67
+ if (kind === "directory") {
68
+ files.push(...await listSubdirectory(entryPath, state));
69
+ } else if (kind === "css-file") {
70
+ const file = await cssFileIfNew(entryPath, state);
71
+ if (file) files.push(file);
72
+ }
73
+ }
74
+ return files;
75
+ }
76
+ async function listCssFiles(source) {
77
+ const stats = await stat(source);
78
+ if (!stats.isDirectory()) {
79
+ if (!stats.isFile()) throw new NotAStylesheetPathError(source);
80
+ return { files: [source], unreadablePaths: [] };
81
+ }
82
+ const canonicalRoot = await realpath(source);
83
+ const state = {
84
+ reportedRealFiles: /* @__PURE__ */ new Set(),
85
+ unreadablePaths: [],
86
+ visitedDirs: /* @__PURE__ */ new Set([canonicalRoot])
87
+ };
88
+ const files = await listDirectory(source, state);
89
+ return { files, unreadablePaths: state.unreadablePaths };
90
+ }
91
+
92
+ // src/directive/check-run.ts
93
+ async function wasFileRead(file, findings) {
94
+ let raw;
95
+ try {
96
+ raw = await readFile(file, "utf8");
97
+ } catch {
98
+ return false;
99
+ }
100
+ for (const finding of findingsInText(file, raw)) findings.push(finding);
101
+ return true;
102
+ }
103
+ async function sourceOutcome(source) {
104
+ try {
105
+ return { kind: "listing", listing: await listCssFiles(source) };
106
+ } catch (error) {
107
+ if (error instanceof NotAStylesheetPathError) return { kind: "not-a-stylesheet" };
108
+ return { kind: "unreadable" };
109
+ }
110
+ }
111
+ function statusFor(hasUnreadableSource, stylesheetsRead, findingCount) {
112
+ if (hasUnreadableSource || stylesheetsRead === 0) return 2;
113
+ return findingCount > 0 ? 1 : 0;
114
+ }
115
+ async function runCheck(source) {
116
+ const findings = [];
117
+ const unreadablePaths = [];
118
+ const notStylesheetPaths = [];
119
+ let stylesheetsRead = 0;
120
+ let hasUnreadableSource = false;
121
+ for (const one of source) {
122
+ const outcome = await sourceOutcome(one);
123
+ if (outcome.kind === "unreadable") {
124
+ hasUnreadableSource = true;
125
+ unreadablePaths.push(one);
126
+ continue;
127
+ }
128
+ if (outcome.kind === "not-a-stylesheet") {
129
+ hasUnreadableSource = true;
130
+ notStylesheetPaths.push(one);
131
+ continue;
132
+ }
133
+ const listing = outcome.listing;
134
+ if (listing.unreadablePaths.length > 0) {
135
+ hasUnreadableSource = true;
136
+ unreadablePaths.push(...listing.unreadablePaths);
137
+ }
138
+ for (const file of listing.files) {
139
+ const wasReadable = await wasFileRead(file, findings);
140
+ if (wasReadable) {
141
+ stylesheetsRead++;
142
+ } else {
143
+ hasUnreadableSource = true;
144
+ unreadablePaths.push(file);
145
+ }
146
+ }
147
+ }
148
+ const status = statusFor(hasUnreadableSource, stylesheetsRead, findings.length);
149
+ return { result: { status, findings, stylesheetsRead }, unreadablePaths, notStylesheetPaths };
150
+ }
151
+
152
+ export { runCheck };