@modularcloud/cspec 0.1.0 → 0.2.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.
- package/README.md +226 -0
- package/dist/bin/cspec.d.ts +3 -0
- package/dist/bin/cspec.d.ts.map +1 -0
- package/dist/bin/cspec.js +6 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +60 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +47 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +20 -0
- package/dist/generate.d.ts +11 -0
- package/dist/generate.d.ts.map +1 -0
- package/dist/generate.js +57 -0
- package/dist/glob.d.ts +3 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +49 -0
- package/dist/ids.d.ts +6 -0
- package/dist/ids.d.ts.map +1 -0
- package/dist/ids.js +38 -0
- package/dist/parser.d.ts +55 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +210 -0
- package/dist/render.d.ts +9 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +91 -0
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.d.ts.map +1 -0
- package/{src → dist}/runtime.js +3 -6
- package/dist/workspace.d.ts +13 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +31 -0
- package/package.json +24 -9
- package/bin/cspec.js +0 -7
- package/src/cli.js +0 -62
- package/src/config.js +0 -47
- package/src/errors.js +0 -15
- package/src/generate.js +0 -53
- package/src/glob.js +0 -42
- package/src/ids.js +0 -45
- package/src/parser.js +0 -221
- package/src/render.js +0 -89
- package/src/workspace.js +0 -37
package/src/glob.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export function discoverFiles(cwd, specs) {
|
|
5
|
-
const patterns = Object.values(specs).flat();
|
|
6
|
-
const files = new Set();
|
|
7
|
-
for (const pattern of patterns) {
|
|
8
|
-
for (const file of walk(cwd)) {
|
|
9
|
-
const rel = path.relative(cwd, file).split(path.sep).join("/");
|
|
10
|
-
if (isGenerated(rel)) continue;
|
|
11
|
-
if (matchGlob(pattern, rel)) files.add(file);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
return [...files].sort();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function walk(dir) {
|
|
18
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
19
|
-
const files = [];
|
|
20
|
-
for (const entry of entries) {
|
|
21
|
-
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
22
|
-
const full = path.join(dir, entry.name);
|
|
23
|
-
if (entry.isDirectory()) files.push(...walk(full));
|
|
24
|
-
else if (entry.isFile()) files.push(full);
|
|
25
|
-
}
|
|
26
|
-
return files;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function isGenerated(rel) {
|
|
30
|
-
return rel.endsWith(".cspec.ts") || rel.endsWith(".md");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function matchGlob(pattern, rel) {
|
|
34
|
-
if (pattern.startsWith("**/") && matchGlob(pattern.slice(3), rel)) return true;
|
|
35
|
-
if (pattern.includes("/**/") && matchGlob(pattern.replace("/**/", "/"), rel)) return true;
|
|
36
|
-
const escaped = pattern.split(/(\*\*|\*)/g).map((part) => {
|
|
37
|
-
if (part === "**") return ".*";
|
|
38
|
-
if (part === "*") return "[^/]*";
|
|
39
|
-
return part.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
40
|
-
}).join("");
|
|
41
|
-
return new RegExp(`^${escaped}$`).test(rel);
|
|
42
|
-
}
|
package/src/ids.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { CspecError } from "./errors.js";
|
|
2
|
-
|
|
3
|
-
const RESERVED = new Set(["$", "__proto__", "prototype", "constructor"]);
|
|
4
|
-
|
|
5
|
-
export function splitId(id, details = {}) {
|
|
6
|
-
if (typeof id !== "string") {
|
|
7
|
-
throw new CspecError("Block id must be a string.", details);
|
|
8
|
-
}
|
|
9
|
-
if (id.trim() !== id) {
|
|
10
|
-
throw new CspecError(`Invalid block id "${id}"; ids must not have leading or trailing whitespace.`, details);
|
|
11
|
-
}
|
|
12
|
-
const segments = id.split(".");
|
|
13
|
-
for (const segment of segments) validateSegment(segment, details);
|
|
14
|
-
return segments;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function validateSegment(segment, details = {}) {
|
|
18
|
-
if (!segment) {
|
|
19
|
-
throw new CspecError("Invalid empty id segment.", details);
|
|
20
|
-
}
|
|
21
|
-
if (segment.trim() !== segment) {
|
|
22
|
-
throw new CspecError(`Invalid id segment "${segment}"; segments must not have leading or trailing whitespace.`, details);
|
|
23
|
-
}
|
|
24
|
-
if (/[\u0000-\u001f\u007f]/u.test(segment)) {
|
|
25
|
-
throw new CspecError(`Invalid id segment "${segment}"; control characters are not allowed.`, details);
|
|
26
|
-
}
|
|
27
|
-
if (RESERVED.has(segment)) {
|
|
28
|
-
const reason = segment === "$" ? '"$" is reserved for block values.' : `"${segment}" is reserved.`;
|
|
29
|
-
throw new CspecError(`Invalid id segment "${segment}"; ${reason}`, details);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function assertStructuralId(id, parentId, details = {}) {
|
|
34
|
-
splitId(id, details);
|
|
35
|
-
if (parentId && !id.startsWith(`${parentId}.`)) {
|
|
36
|
-
throw new CspecError(
|
|
37
|
-
`Child block id must begin with parent id plus ".".\nExpected id matching "${parentId}.<segment>".\nReceived "${id}".`,
|
|
38
|
-
details
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function idToSegments(id) {
|
|
44
|
-
return id ? id.split(".") : [];
|
|
45
|
-
}
|
package/src/parser.js
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { CspecError } from "./errors.js";
|
|
3
|
-
import { assertStructuralId, idToSegments } from "./ids.js";
|
|
4
|
-
|
|
5
|
-
const TAG_RE = /<\/?(S|Spec)\b[^>]*>/g;
|
|
6
|
-
const IMPORT_RE = /^\s*import\s+([^'";]+?)\s+from\s+["']([^"']+)["']\s*;?\s*$/gm;
|
|
7
|
-
const EXTERNAL_REF_RE = /\{([A-Za-z_$][\w$]*(?:(?:\.[A-Za-z_$][\w$]*)|(?:\["(?:[^"\\]|\\.)*"\])|(?:\['(?:[^'\\]|\\.)*'\]))*)\.\$\}/g;
|
|
8
|
-
const LOCAL_REF_RE = /\{\s*ref\s*\(([^)]*)\)\s*\.\$\s*\}/g;
|
|
9
|
-
|
|
10
|
-
export function parseSource(file, source) {
|
|
11
|
-
const imports = parseImports(source);
|
|
12
|
-
const root = makeBlock("", null, 0, source.length, 0, source.length, "root");
|
|
13
|
-
const stack = [root];
|
|
14
|
-
const blocks = new Map();
|
|
15
|
-
const rangesToRemove = imports.map((item) => expandStandaloneRange(source, item.start, item.end));
|
|
16
|
-
|
|
17
|
-
for (const match of source.matchAll(TAG_RE)) {
|
|
18
|
-
const raw = match[0];
|
|
19
|
-
const index = match.index;
|
|
20
|
-
const closing = raw.startsWith("</");
|
|
21
|
-
if (closing) {
|
|
22
|
-
if (stack.length === 1) {
|
|
23
|
-
throw new CspecError(`Unexpected closing ${raw}.`, { file, line: lineAt(source, index) });
|
|
24
|
-
}
|
|
25
|
-
const block = stack.pop();
|
|
26
|
-
const expected = `</${block.tag}>`;
|
|
27
|
-
if (raw !== expected) {
|
|
28
|
-
throw new CspecError(`Mismatched block tag. Expected ${expected}, received ${raw}.`, {
|
|
29
|
-
file,
|
|
30
|
-
line: lineAt(source, index)
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
block.closeStart = index;
|
|
34
|
-
block.closeEnd = index + raw.length;
|
|
35
|
-
block.contentEnd = index;
|
|
36
|
-
rangesToRemove.push(
|
|
37
|
-
expandStandaloneRange(source, block.openStart, block.openEnd),
|
|
38
|
-
expandStandaloneRange(source, block.closeStart, block.closeEnd)
|
|
39
|
-
);
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const tag = match[1];
|
|
44
|
-
const parent = stack[stack.length - 1];
|
|
45
|
-
const id = parseIdAttribute(raw);
|
|
46
|
-
if (!id) {
|
|
47
|
-
throw new CspecError(`Non-root <${tag}> block is missing required id.`, { file, line: lineAt(source, index) });
|
|
48
|
-
}
|
|
49
|
-
assertStructuralId(id, parent.id, { file, line: lineAt(source, index) });
|
|
50
|
-
if (blocks.has(id)) {
|
|
51
|
-
throw new CspecError(`Duplicate block id "${id}".`, { file, line: lineAt(source, index) });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const block = makeBlock(id, parent.id || null, index, index + raw.length, index + raw.length, null, tag);
|
|
55
|
-
block.segments = idToSegments(id);
|
|
56
|
-
block.line = lineAt(source, index);
|
|
57
|
-
parent.children.push(block);
|
|
58
|
-
blocks.set(id, block);
|
|
59
|
-
stack.push(block);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (stack.length > 1) {
|
|
63
|
-
const block = stack[stack.length - 1];
|
|
64
|
-
throw new CspecError(`Unclosed <${block.tag}> block "${block.id}".`, { file, line: block.line });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
root.children = root.children.sort((a, b) => a.openStart - b.openStart);
|
|
68
|
-
return {
|
|
69
|
-
file,
|
|
70
|
-
source,
|
|
71
|
-
root,
|
|
72
|
-
blocks,
|
|
73
|
-
imports,
|
|
74
|
-
rangesToRemove,
|
|
75
|
-
references: parseReferences(source, imports)
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function makeBlock(id, parentId, openStart, openEnd, contentStart, contentEnd, tag) {
|
|
80
|
-
return {
|
|
81
|
-
id,
|
|
82
|
-
parentId,
|
|
83
|
-
openStart,
|
|
84
|
-
openEnd,
|
|
85
|
-
contentStart,
|
|
86
|
-
contentEnd,
|
|
87
|
-
closeStart: null,
|
|
88
|
-
closeEnd: null,
|
|
89
|
-
tag,
|
|
90
|
-
children: [],
|
|
91
|
-
segments: [],
|
|
92
|
-
line: 1,
|
|
93
|
-
compiled: null
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function parseIdAttribute(raw) {
|
|
98
|
-
const doubleMatch = raw.match(/\bid\s*=\s*"([^"]*)"/);
|
|
99
|
-
if (doubleMatch) return doubleMatch[1];
|
|
100
|
-
const singleMatch = raw.match(/\bid\s*=\s*'([^']*)'/);
|
|
101
|
-
if (singleMatch) return singleMatch[1];
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function expandStandaloneRange(source, start, end) {
|
|
106
|
-
const lineStart = source.lastIndexOf("\n", start - 1) + 1;
|
|
107
|
-
const nextNewline = source.indexOf("\n", end);
|
|
108
|
-
const lineEnd = nextNewline === -1 ? source.length : nextNewline + 1;
|
|
109
|
-
const before = source.slice(lineStart, start);
|
|
110
|
-
const after = source.slice(end, nextNewline === -1 ? source.length : nextNewline);
|
|
111
|
-
if (/^[ \t]*$/.test(before) && /^[ \t]*$/.test(after)) {
|
|
112
|
-
return [lineStart, lineEnd];
|
|
113
|
-
}
|
|
114
|
-
return [start, end];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function parseImports(source) {
|
|
118
|
-
const imports = [];
|
|
119
|
-
for (const match of source.matchAll(IMPORT_RE)) {
|
|
120
|
-
const specifier = match[2];
|
|
121
|
-
const clause = match[1].trim();
|
|
122
|
-
const start = match.index;
|
|
123
|
-
const end = start + match[0].length;
|
|
124
|
-
if (specifier === "cspec") {
|
|
125
|
-
imports.push({ kind: "cspec", clause, specifier, start, end });
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
if (specifier.endsWith(".cspec")) {
|
|
129
|
-
const local = clause.match(/^([A-Za-z_$][\w$]*)$/)?.[1];
|
|
130
|
-
if (local) imports.push({ kind: "module", local, specifier, start, end });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return imports;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function parseReferences(source, imports) {
|
|
137
|
-
const importedNames = new Set(imports.filter((item) => item.kind === "module").map((item) => item.local));
|
|
138
|
-
const references = [];
|
|
139
|
-
|
|
140
|
-
for (const match of source.matchAll(LOCAL_REF_RE)) {
|
|
141
|
-
const arg = match[1].trim();
|
|
142
|
-
const literal = parseStringLiteral(arg);
|
|
143
|
-
references.push({
|
|
144
|
-
kind: "local",
|
|
145
|
-
raw: match[0],
|
|
146
|
-
start: match.index,
|
|
147
|
-
end: match.index + match[0].length,
|
|
148
|
-
id: literal,
|
|
149
|
-
validStatic: literal !== null,
|
|
150
|
-
expression: arg
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
for (const match of source.matchAll(EXTERNAL_REF_RE)) {
|
|
155
|
-
const expression = match[1];
|
|
156
|
-
const [name] = expression.split(/[.\[]/, 1);
|
|
157
|
-
if (!importedNames.has(name)) continue;
|
|
158
|
-
references.push({
|
|
159
|
-
kind: "external",
|
|
160
|
-
raw: match[0],
|
|
161
|
-
start: match.index,
|
|
162
|
-
end: match.index + match[0].length,
|
|
163
|
-
local: name,
|
|
164
|
-
path: memberExpressionToPath(expression.slice(name.length)),
|
|
165
|
-
expression
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return references.sort((a, b) => a.start - b.start);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function parseStringLiteral(value) {
|
|
173
|
-
if (!/^(['"])(?:\\.|(?!\1).)*\1$/s.test(value)) return null;
|
|
174
|
-
try {
|
|
175
|
-
return JSON.parse(value[0] === "'" ? `"${value.slice(1, -1).replace(/"/g, '\\"')}"` : value);
|
|
176
|
-
} catch {
|
|
177
|
-
return value.slice(1, -1);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function memberExpressionToPath(expression) {
|
|
182
|
-
const segments = [];
|
|
183
|
-
let rest = expression;
|
|
184
|
-
while (rest) {
|
|
185
|
-
const dot = rest.match(/^\.(?!\$)([A-Za-z_$][\w$]*)/);
|
|
186
|
-
if (dot) {
|
|
187
|
-
segments.push(dot[1]);
|
|
188
|
-
rest = rest.slice(dot[0].length);
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
const bracket = rest.match(/^\[(["'])((?:\\.|(?!\1).)*)\]/s);
|
|
192
|
-
if (bracket) {
|
|
193
|
-
segments.push(unescapeQuoted(bracket[2]));
|
|
194
|
-
rest = rest.slice(bracket[0].length);
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
break;
|
|
198
|
-
}
|
|
199
|
-
return segments.join(".");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function unescapeQuoted(value) {
|
|
203
|
-
try {
|
|
204
|
-
return JSON.parse(`"${value.replace(/"/g, '\\"')}"`);
|
|
205
|
-
} catch {
|
|
206
|
-
return value.replace(/\\(['"\\])/g, "$1");
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function resolveImportPath(fromFile, specifier) {
|
|
211
|
-
const base = path.resolve(path.dirname(fromFile), specifier.replace(/\.cspec$/, ".mdx"));
|
|
212
|
-
return base;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export function lineAt(source, index) {
|
|
216
|
-
let line = 1;
|
|
217
|
-
for (let i = 0; i < index; i += 1) {
|
|
218
|
-
if (source.charCodeAt(i) === 10) line += 1;
|
|
219
|
-
}
|
|
220
|
-
return line;
|
|
221
|
-
}
|
package/src/render.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { CspecError } from "./errors.js";
|
|
2
|
-
import { lineAt, resolveImportPath } from "./parser.js";
|
|
3
|
-
|
|
4
|
-
export function compileWorkspace(documents) {
|
|
5
|
-
const byFile = new Map(documents.map((doc) => [doc.file, doc]));
|
|
6
|
-
const context = { byFile, stack: [] };
|
|
7
|
-
for (const doc of documents) {
|
|
8
|
-
compileBlock(doc, doc.root, context);
|
|
9
|
-
for (const block of doc.blocks.values()) compileBlock(doc, block, context);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function compileBlock(doc, block, context) {
|
|
14
|
-
const key = `${doc.file}#${block.id || "$"}`;
|
|
15
|
-
if (block.compiled !== null) return block.compiled;
|
|
16
|
-
const cycleAt = context.stack.indexOf(key);
|
|
17
|
-
if (cycleAt !== -1) {
|
|
18
|
-
const cycle = context.stack.slice(cycleAt).concat(key).map((item) => item.split("#")[1]).join(" -> ");
|
|
19
|
-
throw new CspecError(`Embedding cycle detected:\n${cycle}`, { file: doc.file, line: block.line });
|
|
20
|
-
}
|
|
21
|
-
context.stack.push(key);
|
|
22
|
-
const start = block.id ? block.contentStart : 0;
|
|
23
|
-
const end = block.id ? block.contentEnd : doc.source.length;
|
|
24
|
-
block.compiled = renderRange(doc, start, end, context);
|
|
25
|
-
context.stack.pop();
|
|
26
|
-
return block.compiled;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function renderRange(doc, start, end, context) {
|
|
30
|
-
const removals = doc.rangesToRemove
|
|
31
|
-
.filter(([a, b]) => b > start && a < end)
|
|
32
|
-
.map(([a, b]) => [Math.max(a, start), Math.min(b, end), ""]);
|
|
33
|
-
const replacements = doc.references
|
|
34
|
-
.filter((ref) => ref.start >= start && ref.end <= end)
|
|
35
|
-
.map((ref) => [ref.start, ref.end, resolveReference(doc, ref, context)]);
|
|
36
|
-
const edits = removals.concat(replacements).sort((a, b) => a[0] - b[0] || b[1] - a[1]);
|
|
37
|
-
|
|
38
|
-
let out = "";
|
|
39
|
-
let cursor = start;
|
|
40
|
-
for (const [a, b, value] of edits) {
|
|
41
|
-
if (a < cursor) continue;
|
|
42
|
-
out += doc.source.slice(cursor, a);
|
|
43
|
-
out += value;
|
|
44
|
-
cursor = b;
|
|
45
|
-
}
|
|
46
|
-
out += doc.source.slice(cursor, end);
|
|
47
|
-
return trimOuterBlankLines(out);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function resolveReference(doc, ref, context) {
|
|
51
|
-
if (ref.kind === "local") {
|
|
52
|
-
if (!ref.validStatic) {
|
|
53
|
-
throw new CspecError("ref(...) requires a static string literal.", {
|
|
54
|
-
file: doc.file,
|
|
55
|
-
line: lineAt(doc.source, ref.start)
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
const target = doc.blocks.get(ref.id);
|
|
59
|
-
if (!target) {
|
|
60
|
-
throw new CspecError(`Unknown local block reference "${ref.id}".`, {
|
|
61
|
-
file: doc.file,
|
|
62
|
-
line: lineAt(doc.source, ref.start)
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
return compileBlock(doc, target, context);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const imported = doc.imports.find((item) => item.kind === "module" && item.local === ref.local);
|
|
69
|
-
const targetFile = resolveImportPath(doc.file, imported.specifier);
|
|
70
|
-
const targetDoc = context.byFile.get(targetFile);
|
|
71
|
-
if (!targetDoc) {
|
|
72
|
-
throw new CspecError(`Unresolved import ${imported.specifier}.`, {
|
|
73
|
-
file: doc.file,
|
|
74
|
-
line: lineAt(doc.source, ref.start)
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
const target = ref.path ? targetDoc.blocks.get(ref.path) : targetDoc.root;
|
|
78
|
-
if (!target) {
|
|
79
|
-
throw new CspecError(`Unknown block reference ${ref.expression}.`, {
|
|
80
|
-
file: doc.file,
|
|
81
|
-
line: lineAt(doc.source, ref.start)
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
return compileBlock(targetDoc, target, context);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function trimOuterBlankLines(value) {
|
|
88
|
-
return value.replace(/^\s*\n/, "").replace(/\n\s*$/, "");
|
|
89
|
-
}
|
package/src/workspace.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { loadConfig } from "./config.js";
|
|
4
|
-
import { discoverFiles } from "./glob.js";
|
|
5
|
-
import { parseSource } from "./parser.js";
|
|
6
|
-
import { compileWorkspace } from "./render.js";
|
|
7
|
-
import { generatedFilesFor } from "./generate.js";
|
|
8
|
-
import { CspecError } from "./errors.js";
|
|
9
|
-
|
|
10
|
-
export async function loadWorkspace(cwd) {
|
|
11
|
-
const config = await loadConfig(cwd);
|
|
12
|
-
const files = discoverFiles(cwd, config.specs);
|
|
13
|
-
const documents = files.map((file) => parseSource(file, fs.readFileSync(file, "utf8")));
|
|
14
|
-
compileWorkspace(documents);
|
|
15
|
-
return { cwd, config, documents };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function expectedOutputs(workspace) {
|
|
19
|
-
return workspace.documents.flatMap((doc) =>
|
|
20
|
-
generatedFilesFor(doc, { markdownEmit: workspace.config.markdown.emit })
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function writeOutputs(workspace) {
|
|
25
|
-
for (const output of expectedOutputs(workspace)) {
|
|
26
|
-
fs.writeFileSync(output.file, output.content);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function assertFresh(workspace) {
|
|
31
|
-
for (const output of expectedOutputs(workspace)) {
|
|
32
|
-
const rel = path.relative(workspace.cwd, output.file);
|
|
33
|
-
if (!fs.existsSync(output.file) || fs.readFileSync(output.file, "utf8") !== output.content) {
|
|
34
|
-
throw new CspecError(`Generated file ${rel} is stale.\nRun cspec build.`, { file: output.file });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|