Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ npx cspec build
Create a source document:

```mdx
import { S } from "cspec"
import { S } from "@modularcloud/cspec"

<S id="writing">
Writing requirements.
Expand Down Expand Up @@ -105,7 +105,7 @@ Prints IDs as a tree or JSON.
Add `cspec.config.ts`:

```ts
import { defineConfig } from "cspec"
import { defineConfig } from "@modularcloud/cspec"

export default defineConfig({
specs: {
Expand Down Expand Up @@ -168,7 +168,7 @@ import RULES from "./RULES.cspec"
Embed a local block with `ref("id").$`:

```mdx
import { ref } from "cspec"
import { ref } from "@modularcloud/cspec"

{ref("writing.beSpecific").$}
```
Expand Down
4 changes: 2 additions & 2 deletions packages/cspec-app/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ const demoFiles: AppFile[] = [
{
file: "docs/RULES.mdx",
dirty: false,
source: `import { S } from "cspec"
source: `import { S } from "@modularcloud/cspec"

<S id="writing">
Writing rules.
Expand All @@ -379,7 +379,7 @@ Ask for clarification when behavior is ambiguous.
{
file: "prompts/PROMPT.mdx",
dirty: false,
source: `import { S, ref } from "cspec"
source: `import { S, ref } from "@modularcloud/cspec"
import RULES from "../docs/RULES.cspec"

# Instructions
Expand Down
4 changes: 3 additions & 1 deletion packages/cspec-core/src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,16 @@ function expandStandaloneRange(source: string, start: number, end: number): [num
return [start, end];
}

const CSPEC_RUNTIME_IMPORT = "@modularcloud/cspec";

function parseImports(source: string): CspecImport[] {
const imports: CspecImport[] = [];
for (const match of source.matchAll(IMPORT_RE)) {
const specifier = match[2] ?? "";
const clause = (match[1] ?? "").trim();
const start = match.index ?? 0;
const end = start + match[0].length;
if (specifier === "cspec") {
if (specifier === CSPEC_RUNTIME_IMPORT) {
imports.push({ kind: "cspec", clause, specifier, start, end });
continue;
}
Expand Down
25 changes: 15 additions & 10 deletions packages/cspec-core/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import path from "node:path";
import { test } from "node:test";
import { loadWorkspace, writeOutputs, assertFresh } from "./dist/workspace.js";
import { matchGlob } from "./dist/glob.js";
import { resolveImportPath } from "./dist/parser.js";
import { parseSource, resolveImportPath } from "./dist/parser.js";

test("builds generated modules and markdown with external references", async () => {
const dir = tmp();
fs.writeFileSync(path.join(dir, "RULES.mdx"), `import { S } from "cspec"
fs.writeFileSync(path.join(dir, "RULES.mdx"), `import { S } from "@modularcloud/cspec"

<S id="writing">
Writing rules.
Expand Down Expand Up @@ -51,7 +51,7 @@ Ask for clarification when behavior is ambiguous.

test("supports local refs and non-identifier-safe segments", async () => {
const dir = tmp();
fs.writeFileSync(path.join(dir, "GUIDE.mdx"), `import { S, ref } from "cspec"
fs.writeFileSync(path.join(dir, "GUIDE.mdx"), `import { S, ref } from "@modularcloud/cspec"

<S id="writing">
Rules:
Expand Down Expand Up @@ -99,13 +99,13 @@ test("reports validation errors", async () => {

test("reports unknown references and cycles", async () => {
const unknown = tmp();
fs.writeFileSync(path.join(unknown, "BAD.mdx"), `import { ref } from "cspec"
fs.writeFileSync(path.join(unknown, "BAD.mdx"), `import { ref } from "@modularcloud/cspec"
{ref("missing").$}
`);
await assert.rejects(() => loadWorkspace(unknown), /Unknown local block reference "missing"/);

const cycle = tmp();
fs.writeFileSync(path.join(cycle, "BAD.mdx"), `import { S, ref } from "cspec"
fs.writeFileSync(path.join(cycle, "BAD.mdx"), `import { S, ref } from "@modularcloud/cspec"
<S id="a">{ref("b").$}</S>
<S id="b">{ref("a").$}</S>
`);
Expand Down Expand Up @@ -133,25 +133,25 @@ Text.

test("validates dynamic refs while preserving static strings containing parens", async () => {
const dynamic = tmp();
fs.writeFileSync(path.join(dynamic, "BAD.mdx"), `import { ref } from "cspec"
fs.writeFileSync(path.join(dynamic, "BAD.mdx"), `import { ref } from "@modularcloud/cspec"
{ref(getId()).$}
`);
await assert.rejects(() => loadWorkspace(dynamic), /ref\(\.\.\.\) requires a static string literal/);

const template = tmp();
fs.writeFileSync(path.join(template, "BAD.mdx"), "import { ref } from \"cspec\"\n{ref(`a)`).$}\n");
fs.writeFileSync(path.join(template, "BAD.mdx"), "import { ref } from \"@modularcloud/cspec\"\n{ref(`a)`).$}\n");
await assert.rejects(() => loadWorkspace(template), /ref\(\.\.\.\) requires a static string literal/);

const paren = tmp();
fs.writeFileSync(path.join(paren, "OK.mdx"), `import { S, ref } from "cspec"
fs.writeFileSync(path.join(paren, "OK.mdx"), `import { S, ref } from "@modularcloud/cspec"
<S id="a)">Paren id.</S>
{ref("a)").$}
`);
const workspace = await loadWorkspace(paren);
assert.equal(workspace.documents[0].root.compiled, "Paren id.\nParen id.");

const singleQuoted = tmp();
fs.writeFileSync(path.join(singleQuoted, "OK.mdx"), `import { S, ref } from "cspec"
fs.writeFileSync(path.join(singleQuoted, "OK.mdx"), `import { S, ref } from "@modularcloud/cspec"
<S id="a'b">Quoted id.</S>
{ref('a\\'b').$}
`);
Expand All @@ -178,7 +178,7 @@ test("loads cspec.config.ts and detects stale outputs", async () => {
const dir = tmp();
fs.mkdirSync(path.join(dir, "docs"));
fs.mkdirSync(path.join(dir, "ignored"));
fs.writeFileSync(path.join(dir, "cspec.config.ts"), `import { defineConfig } from "cspec"
fs.writeFileSync(path.join(dir, "cspec.config.ts"), `import { defineConfig } from "@modularcloud/cspec"

export default defineConfig({
specs: { docs: ["docs/**/*.mdx"] },
Expand All @@ -205,6 +205,11 @@ test("matches common cspec globs", () => {
assert.equal(matchGlob("docs/*.mdx", "docs/nested/A.mdx"), false);
});

test("recognizes only the scoped runtime package import", () => {
assert.equal(parseSource("A.mdx", 'import { S } from "@modularcloud/cspec"\n').imports.length, 1);
assert.equal(parseSource("A.mdx", 'import { S } from "cspec"\n').imports.length, 0);
});

test("resolves imports across posix and windows-style paths", () => {
assert.equal(resolveImportPath("/repo/prompts/PROMPT.mdx", "../docs/RULES.cspec"), "/repo/docs/RULES.mdx");
assert.equal(resolveImportPath("prompts/PROMPT.mdx", "../docs/RULES.cspec"), "docs/RULES.mdx");
Expand Down
Loading
Loading