mdx-artifacts 0.1.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/LICENSE +21 -0
- package/README.md +234 -0
- package/README.zh-CN.md +129 -0
- package/agents/AGENTS.snippet.md +13 -0
- package/artifact-docs/examples/commentable-feedback.mdx +126 -0
- package/artifact-docs/examples/decision-matrix.mdx +80 -0
- package/artifact-docs/examples/layout-composition.mdx +216 -0
- package/artifact-docs/examples/streamlit-style-mixed.mdx +183 -0
- package/dist/lib/cli/artifact-state.d.ts +27 -0
- package/dist/lib/cli/artifact-state.js +115 -0
- package/dist/lib/cli/build.d.ts +1 -0
- package/dist/lib/cli/build.js +25 -0
- package/dist/lib/cli/components.d.ts +3 -0
- package/dist/lib/cli/components.js +58 -0
- package/dist/lib/cli/config.d.ts +2 -0
- package/dist/lib/cli/config.js +36 -0
- package/dist/lib/cli/dev.d.ts +1 -0
- package/dist/lib/cli/dev.js +24 -0
- package/dist/lib/cli/index.d.ts +2 -0
- package/dist/lib/cli/index.js +69 -0
- package/dist/lib/cli/review.d.ts +33 -0
- package/dist/lib/cli/review.js +390 -0
- package/dist/lib/cli/scaffold.d.ts +1 -0
- package/dist/lib/cli/scaffold.js +56 -0
- package/dist/lib/cli/types.d.ts +7 -0
- package/dist/lib/cli/types.js +1 -0
- package/dist/lib/cli/validate.d.ts +6 -0
- package/dist/lib/cli/validate.js +79 -0
- package/dist/lib/cli/vite-artifact.d.ts +13 -0
- package/dist/lib/cli/vite-artifact.js +213 -0
- package/dist/lib/react/components/AnnotatedCode.d.ts +19 -0
- package/dist/lib/react/components/AnnotatedCode.js +30 -0
- package/dist/lib/react/components/ArtifactState.d.ts +68 -0
- package/dist/lib/react/components/ArtifactState.js +286 -0
- package/dist/lib/react/components/Callout.d.ts +9 -0
- package/dist/lib/react/components/Callout.js +21 -0
- package/dist/lib/react/components/CodeBlock.d.ts +10 -0
- package/dist/lib/react/components/CodeBlock.js +28 -0
- package/dist/lib/react/components/Comments.d.ts +53 -0
- package/dist/lib/react/components/Comments.js +613 -0
- package/dist/lib/react/components/ComparisonSet.d.ts +24 -0
- package/dist/lib/react/components/ComparisonSet.js +30 -0
- package/dist/lib/react/components/DecisionMatrix.d.ts +16 -0
- package/dist/lib/react/components/DecisionMatrix.js +27 -0
- package/dist/lib/react/components/DiffBlock.d.ts +15 -0
- package/dist/lib/react/components/DiffBlock.js +24 -0
- package/dist/lib/react/components/ExportPanel.d.ts +7 -0
- package/dist/lib/react/components/ExportPanel.js +84 -0
- package/dist/lib/react/components/InlineText.d.ts +9 -0
- package/dist/lib/react/components/InlineText.js +18 -0
- package/dist/lib/react/components/Layout.d.ts +44 -0
- package/dist/lib/react/components/Layout.js +28 -0
- package/dist/lib/react/components/MarkdownBody.d.ts +7 -0
- package/dist/lib/react/components/MarkdownBody.js +36 -0
- package/dist/lib/react/components/OptionGrid.d.ts +13 -0
- package/dist/lib/react/components/OptionGrid.js +21 -0
- package/dist/lib/react/components/Section.d.ts +7 -0
- package/dist/lib/react/components/Section.js +41 -0
- package/dist/lib/react/components/SeverityBadge.d.ts +7 -0
- package/dist/lib/react/components/SeverityBadge.js +14 -0
- package/dist/lib/react/index.d.ts +33 -0
- package/dist/lib/react/index.js +16 -0
- package/dist/lib/react/registry.d.ts +24 -0
- package/dist/lib/react/registry.js +1002 -0
- package/dist/lib/react/styles.css +1417 -0
- package/docs/component-protocol.md +273 -0
- package/docs/component-taxonomy.md +273 -0
- package/docs/design.md +239 -0
- package/docs/design.zh-CN.md +217 -0
- package/docs/naming.md +123 -0
- package/docs/testing.md +138 -0
- package/package.json +90 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Columns,
|
|
3
|
+
ComparisonSet,
|
|
4
|
+
DecisionMatrix,
|
|
5
|
+
ExportPanel,
|
|
6
|
+
Frame,
|
|
7
|
+
Grid,
|
|
8
|
+
InlineText,
|
|
9
|
+
MarkdownBody,
|
|
10
|
+
SplitPane,
|
|
11
|
+
Stack,
|
|
12
|
+
} from "mdx-artifacts/react";
|
|
13
|
+
|
|
14
|
+
# Layout Composition Example
|
|
15
|
+
|
|
16
|
+
<Stack gap="lg">
|
|
17
|
+
<Frame surface="plain" padding="lg">
|
|
18
|
+
<Stack gap="sm">
|
|
19
|
+
<InlineText
|
|
20
|
+
as="h2"
|
|
21
|
+
variant="title"
|
|
22
|
+
text="A composed artifact using **layout primitives** and text components"
|
|
23
|
+
/>
|
|
24
|
+
<MarkdownBody
|
|
25
|
+
body={`This example shows how \`Stack\`, \`Columns\`, \`Grid\`, \`SplitPane\`, and \`Frame\` can arrange existing semantic components without replacing them.
|
|
26
|
+
|
|
27
|
+
The layout components control placement and surface. Components such as \`DecisionMatrix\`, \`ComparisonSet\`, and \`ExportPanel\` still own the artifact meaning.`}
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
</Stack>
|
|
31
|
+
|
|
32
|
+
</Frame>
|
|
33
|
+
|
|
34
|
+
<SplitPane ratio="3:1" gap="lg">
|
|
35
|
+
<DecisionMatrix
|
|
36
|
+
id="decision.layout-authoring"
|
|
37
|
+
question="Should layout primitives become the default authoring path?"
|
|
38
|
+
options={[
|
|
39
|
+
{
|
|
40
|
+
id: "semantic-first",
|
|
41
|
+
name: "Semantic-first authoring",
|
|
42
|
+
summary: "Agents choose task-oriented components first, then use layout only when composition needs it.",
|
|
43
|
+
pros: ["Stable protocol", "Less ad hoc JSX", "Better validation path"],
|
|
44
|
+
cons: ["Less free-form than raw HTML"],
|
|
45
|
+
confidence: "high",
|
|
46
|
+
verdict: "Recommended"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "layout-first",
|
|
50
|
+
name: "Layout-first authoring",
|
|
51
|
+
summary: "Agents assemble artifacts from generic layout blocks and arbitrary children.",
|
|
52
|
+
pros: ["Maximum flexibility"],
|
|
53
|
+
cons: ["Easy to recreate raw HTML", "Harder to query through CLI", "Harder to keep consistent"],
|
|
54
|
+
confidence: "low",
|
|
55
|
+
verdict: "Keep as an advanced escape hatch"
|
|
56
|
+
}
|
|
57
|
+
]}
|
|
58
|
+
/>
|
|
59
|
+
|
|
60
|
+
<Frame surface="subtle">
|
|
61
|
+
<Stack gap="sm">
|
|
62
|
+
<InlineText as="h3" variant="subtitle" text="Reading note" />
|
|
63
|
+
<MarkdownBody
|
|
64
|
+
variant="compact"
|
|
65
|
+
body={`Use layout primitives when the artifact needs a custom reading shape:
|
|
66
|
+
|
|
67
|
+
- main content plus sidebar
|
|
68
|
+
- equal option previews
|
|
69
|
+
- framed snippets or mockups
|
|
70
|
+
|
|
71
|
+
Do not use them to rebuild existing semantic components.`}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
</Stack>
|
|
75
|
+
</Frame>
|
|
76
|
+
|
|
77
|
+
</SplitPane>
|
|
78
|
+
|
|
79
|
+
<Columns ratio="1:1:1" gap="md">
|
|
80
|
+
<Frame surface="outlined">
|
|
81
|
+
<InlineText as="h3" variant="subtitle" text="InlineText" />
|
|
82
|
+
<MarkdownBody
|
|
83
|
+
variant="compact"
|
|
84
|
+
body={`Short labels and headings can use **inline Markdown**, including *emphasis*, ~~removed text~~, and \`inline code\`.`}
|
|
85
|
+
/>
|
|
86
|
+
</Frame>
|
|
87
|
+
|
|
88
|
+
<Frame surface="outlined">
|
|
89
|
+
<InlineText as="h3" variant="subtitle" text="MarkdownBody" />
|
|
90
|
+
<MarkdownBody
|
|
91
|
+
variant="compact"
|
|
92
|
+
body={`Body copy supports controlled Markdown:
|
|
93
|
+
|
|
94
|
+
1. paragraphs
|
|
95
|
+
2. ordered lists
|
|
96
|
+
3. unordered lists
|
|
97
|
+
4. blockquotes`}
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
</Frame>
|
|
101
|
+
|
|
102
|
+
<Frame surface="outlined">
|
|
103
|
+
|
|
104
|
+
<InlineText as="h3" variant="subtitle" text="Frame" />
|
|
105
|
+
|
|
106
|
+
<MarkdownBody
|
|
107
|
+
|
|
108
|
+
variant="compact"
|
|
109
|
+
body={`Frame provides surface treatment:
|
|
110
|
+
|
|
111
|
+
- \`none\`
|
|
112
|
+
- \`plain\`
|
|
113
|
+
- \`subtle\`
|
|
114
|
+
- \`outlined\``}
|
|
115
|
+
/>
|
|
116
|
+
|
|
117
|
+
</Frame>
|
|
118
|
+
|
|
119
|
+
</Columns>
|
|
120
|
+
|
|
121
|
+
<ComparisonSet id="comparison.content-shapes" title="Compare content shapes" columns={3}>
|
|
122
|
+
|
|
123
|
+
<ComparisonSet.Item id="markdown-body" title="Markdown body" value="markdown-body">
|
|
124
|
+
|
|
125
|
+
<MarkdownBody
|
|
126
|
+
|
|
127
|
+
variant="compact"
|
|
128
|
+
body={`# This **item** is ~~plain Markdown content~~.
|
|
129
|
+
|
|
130
|
+
#### **item** is ~~plain Markdown content~~.
|
|
131
|
+
|
|
132
|
+
- Good for notes
|
|
133
|
+
- Easy for agents to write
|
|
134
|
+
- Easy to validate
|
|
135
|
+
|
|
136
|
+
1. Ordered item
|
|
137
|
+
2. Nested content check
|
|
138
|
+
|
|
139
|
+
`}
|
|
140
|
+
/>
|
|
141
|
+
|
|
142
|
+
</ComparisonSet.Item>
|
|
143
|
+
|
|
144
|
+
<ComparisonSet.Item id="framed-snippet" title="Framed snippet" value="framed-snippet">
|
|
145
|
+
<Frame surface="subtle">
|
|
146
|
+
<MarkdownBody
|
|
147
|
+
variant="compact"
|
|
148
|
+
body={`This slot can hold a future \`CodeBlock\`, \`MermaidBlock\`, image renderer, or project-local component.`}
|
|
149
|
+
/>
|
|
150
|
+
</Frame>
|
|
151
|
+
</ComparisonSet.Item>
|
|
152
|
+
|
|
153
|
+
<ComparisonSet.Item id="custom-composition" title="Custom composition" value="custom-composition">
|
|
154
|
+
<Stack gap="sm">
|
|
155
|
+
<InlineText text="Nested layout is allowed when the item needs it." variant="label" />
|
|
156
|
+
<MarkdownBody body="The comparison item owns the title. Its body remains open to child components." variant="compact" />
|
|
157
|
+
</Stack>
|
|
158
|
+
|
|
159
|
+
</ComparisonSet.Item>
|
|
160
|
+
|
|
161
|
+
</ComparisonSet>
|
|
162
|
+
|
|
163
|
+
<Grid columns={2} gap="md">
|
|
164
|
+
|
|
165
|
+
<Frame surface="outlined">
|
|
166
|
+
|
|
167
|
+
<InlineText as="h3" variant="subtitle" text="Grid is only layout" />
|
|
168
|
+
|
|
169
|
+
<MarkdownBody
|
|
170
|
+
|
|
171
|
+
body={`This card is built manually from \`Frame\`, \`InlineText\`, and \`MarkdownBody\`.
|
|
172
|
+
|
|
173
|
+
Use this when there is no reusable artifact meaning beyond placement.`}
|
|
174
|
+
/>
|
|
175
|
+
|
|
176
|
+
</Frame>
|
|
177
|
+
|
|
178
|
+
<Frame surface="plain" padding="lg">
|
|
179
|
+
<Stack gap="md">
|
|
180
|
+
<InlineText as="h3" variant="subtitle" text="Surface check" />
|
|
181
|
+
<MarkdownBody
|
|
182
|
+
body={`This frame uses \`surface=\"plain\"\`. In light mode it should read as a white surface on a very light neutral page background.
|
|
183
|
+
|
|
184
|
+
In dark mode it should read as a quiet elevated surface, not as a blue-tinted panel.`}
|
|
185
|
+
/>
|
|
186
|
+
|
|
187
|
+
</Stack>
|
|
188
|
+
</Frame>
|
|
189
|
+
|
|
190
|
+
</Grid>
|
|
191
|
+
|
|
192
|
+
<ExportPanel
|
|
193
|
+
title="Export layout review"
|
|
194
|
+
formats={["markdown", "json"]}
|
|
195
|
+
value={{
|
|
196
|
+
recommendation: "Keep layout primitives as advanced composition tools and use ComparisonSet for comparable mixed-content candidates.",
|
|
197
|
+
componentsShown: [
|
|
198
|
+
"Stack",
|
|
199
|
+
"Columns",
|
|
200
|
+
"Grid",
|
|
201
|
+
"SplitPane",
|
|
202
|
+
"Frame",
|
|
203
|
+
"ComparisonSet",
|
|
204
|
+
"InlineText",
|
|
205
|
+
"MarkdownBody"
|
|
206
|
+
],
|
|
207
|
+
reviewChecklist: [
|
|
208
|
+
"Light mode surface contrast is readable.",
|
|
209
|
+
"Dark mode surface contrast is readable.",
|
|
210
|
+
"Semantic components remain the main authoring path.",
|
|
211
|
+
"ComparisonSet items can hold mixed child components.",
|
|
212
|
+
"Text components render controlled Markdown correctly."
|
|
213
|
+
]
|
|
214
|
+
}}
|
|
215
|
+
/>
|
|
216
|
+
</Stack>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Callout,
|
|
3
|
+
CodeBlock,
|
|
4
|
+
ComparisonSet,
|
|
5
|
+
DecisionMatrix,
|
|
6
|
+
ExportPanel,
|
|
7
|
+
Frame,
|
|
8
|
+
MarkdownBody,
|
|
9
|
+
OptionGrid,
|
|
10
|
+
} from "mdx-artifacts/react";
|
|
11
|
+
|
|
12
|
+
# Streamlit-Style Linear Artifact
|
|
13
|
+
|
|
14
|
+
This example tests the simplest authoring path: write native MDX prose, place a component, continue writing prose, then place the next component.
|
|
15
|
+
|
|
16
|
+
The goal is not to create a complex layout. The goal is to check whether the artifact already feels natural when the author writes in a linear flow.
|
|
17
|
+
|
|
18
|
+
## Context
|
|
19
|
+
|
|
20
|
+
Streamlit works well because authors can write a small amount of text, drop in a widget or chart, then keep going. MDX already gives this project a similar baseline:
|
|
21
|
+
|
|
22
|
+
- markdown headings define the reading structure
|
|
23
|
+
- paragraphs carry narrative context
|
|
24
|
+
- components own reusable workflow UI
|
|
25
|
+
- the artifact shell controls page width and vertical rhythm
|
|
26
|
+
|
|
27
|
+
<Callout
|
|
28
|
+
id="callout.linear-check"
|
|
29
|
+
tone="info"
|
|
30
|
+
title="What this example is checking"
|
|
31
|
+
body="The artifact should read as one continuous document, not as disconnected component demos."
|
|
32
|
+
/>
|
|
33
|
+
|
|
34
|
+
## Decision point
|
|
35
|
+
|
|
36
|
+
The first question is whether the core authoring experience should stay close to native MDX or move toward more explicit text components.
|
|
37
|
+
|
|
38
|
+
<DecisionMatrix
|
|
39
|
+
id="decision.native-mdx"
|
|
40
|
+
question="Should top-level prose use native MDX by default?"
|
|
41
|
+
options={[
|
|
42
|
+
{
|
|
43
|
+
id: "native-mdx",
|
|
44
|
+
name: "Native MDX prose",
|
|
45
|
+
summary:
|
|
46
|
+
"Use regular headings, paragraphs, lists, links, and blockquotes between workflow components.",
|
|
47
|
+
pros: [
|
|
48
|
+
"Shortest authoring path",
|
|
49
|
+
"Agent-friendly",
|
|
50
|
+
"No extra text API to learn",
|
|
51
|
+
],
|
|
52
|
+
cons: [
|
|
53
|
+
"Needs strong default prose styling",
|
|
54
|
+
"Harder to constrain every edge case",
|
|
55
|
+
],
|
|
56
|
+
confidence: "high",
|
|
57
|
+
verdict: "Recommended baseline",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: "text-components",
|
|
61
|
+
name: "Text components everywhere",
|
|
62
|
+
summary:
|
|
63
|
+
"Require components such as InlineText and MarkdownBody for most visible copy.",
|
|
64
|
+
pros: ["More controlled rendering", "Clear metadata boundary"],
|
|
65
|
+
cons: ["Verbose for simple documents", "Less natural than MDX"],
|
|
66
|
+
confidence: "medium",
|
|
67
|
+
verdict: "Use inside reusable components",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "raw-html",
|
|
71
|
+
name: "Raw HTML escape hatch",
|
|
72
|
+
summary:
|
|
73
|
+
"Let authors write arbitrary HTML when layout or text rendering gets specific.",
|
|
74
|
+
pros: ["Maximum flexibility"],
|
|
75
|
+
cons: ["Style drift", "Weak validation", "Harder for agents to reuse"],
|
|
76
|
+
confidence: "low",
|
|
77
|
+
verdict: "Avoid unless necessary",
|
|
78
|
+
},
|
|
79
|
+
]}
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
After the decision block, the document should continue naturally. This paragraph is intentionally plain MDX so the spacing between prose and the component above is easy to judge.
|
|
83
|
+
|
|
84
|
+
> A good default artifact should let the author think in reading order first, then reach for layout primitives only when the content needs a custom arrangement.
|
|
85
|
+
|
|
86
|
+
## Component menu
|
|
87
|
+
|
|
88
|
+
The next block lists the current component roles. It should feel like a continuation of the same document, not a separate app screen.
|
|
89
|
+
|
|
90
|
+
<OptionGrid
|
|
91
|
+
id="option.linear-building-blocks"
|
|
92
|
+
title="Linear authoring building blocks"
|
|
93
|
+
options={[
|
|
94
|
+
{
|
|
95
|
+
id: "native-mdx",
|
|
96
|
+
name: "Native MDX",
|
|
97
|
+
intent: "Own top-level narrative flow.",
|
|
98
|
+
tradeoffs: [
|
|
99
|
+
"Best for headings and explanatory paragraphs",
|
|
100
|
+
"Needs polished document-level CSS",
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "markdown-body",
|
|
105
|
+
name: "MarkdownBody",
|
|
106
|
+
intent: "Own controlled body copy inside a component slot.",
|
|
107
|
+
tradeoffs: [
|
|
108
|
+
"Good for reusable components",
|
|
109
|
+
"Less convenient for top-level prose",
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
id: "workflow-components",
|
|
114
|
+
name: "Workflow components",
|
|
115
|
+
intent:
|
|
116
|
+
"Own structured decisions, comparisons, code notes, and export surfaces.",
|
|
117
|
+
tradeoffs: [
|
|
118
|
+
"Agent-queryable through the registry",
|
|
119
|
+
"Should stay higher-level than raw cards",
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
]}
|
|
123
|
+
/>
|
|
124
|
+
|
|
125
|
+
## Mixed content
|
|
126
|
+
|
|
127
|
+
Here is a small comparison set with component-local Markdown inside each item. This shows the boundary between native MDX prose and reusable component content.
|
|
128
|
+
|
|
129
|
+
<ComparisonSet id="comparison.text-forms" title="Where each text form belongs" columns={3}>
|
|
130
|
+
<ComparisonSet.Item id="top-level-mdx" title="Top-level MDX" value="top-level-mdx">
|
|
131
|
+
<Frame surface="subtle">
|
|
132
|
+
<MarkdownBody
|
|
133
|
+
variant="compact"
|
|
134
|
+
body="Use this for the document's main reading order: headings, paragraphs, lists, and short notes between components."
|
|
135
|
+
/>
|
|
136
|
+
</Frame>
|
|
137
|
+
</ComparisonSet.Item>
|
|
138
|
+
|
|
139
|
+
<ComparisonSet.Item id="markdown-body" title="MarkdownBody" value="markdown-body">
|
|
140
|
+
<Frame surface="subtle">
|
|
141
|
+
<MarkdownBody
|
|
142
|
+
variant="compact"
|
|
143
|
+
body="Use this when a component needs a controlled body field with safe Markdown rendering."
|
|
144
|
+
/>
|
|
145
|
+
</Frame>
|
|
146
|
+
</ComparisonSet.Item>
|
|
147
|
+
|
|
148
|
+
<ComparisonSet.Item id="code-block" title="CodeBlock" value="code-block">
|
|
149
|
+
<CodeBlock
|
|
150
|
+
id="code.linear-example"
|
|
151
|
+
filename="artifact-docs/examples/streamlit-style-mixed.mdx"
|
|
152
|
+
language="mdx"
|
|
153
|
+
code={`# Title
|
|
154
|
+
|
|
155
|
+
Plain MDX prose.
|
|
156
|
+
|
|
157
|
+
<DecisionMatrix ... />
|
|
158
|
+
|
|
159
|
+
More prose.`}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
</ComparisonSet.Item>
|
|
163
|
+
</ComparisonSet>
|
|
164
|
+
|
|
165
|
+
## Export
|
|
166
|
+
|
|
167
|
+
The export block closes the artifact with a structured summary. In a real artifact, this is where the reader or agent can copy the decision back into the workflow.
|
|
168
|
+
|
|
169
|
+
<ExportPanel
|
|
170
|
+
title="Export linear authoring verdict"
|
|
171
|
+
formats={["markdown", "json"]}
|
|
172
|
+
value={{
|
|
173
|
+
pattern: "Native MDX prose plus ordered workflow components",
|
|
174
|
+
recommendation:
|
|
175
|
+
"Keep native MDX as the top-level write path and use components for reusable structured UI.",
|
|
176
|
+
checks: [
|
|
177
|
+
"Top-level headings and paragraphs remain readable.",
|
|
178
|
+
"Components appear in the same order they are written.",
|
|
179
|
+
"Document spacing makes the artifact feel continuous.",
|
|
180
|
+
"MarkdownBody stays useful inside component slots.",
|
|
181
|
+
],
|
|
182
|
+
}}
|
|
183
|
+
/>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type ArtifactRoute = {
|
|
2
|
+
routePath: string;
|
|
3
|
+
slug: string;
|
|
4
|
+
sourcePath: string;
|
|
5
|
+
sourceRelativePath: string;
|
|
6
|
+
statePath: string;
|
|
7
|
+
stateRelativePath: string;
|
|
8
|
+
};
|
|
9
|
+
export type ArtifactState = {
|
|
10
|
+
version: number;
|
|
11
|
+
source: string;
|
|
12
|
+
threads: unknown[];
|
|
13
|
+
interactions: Record<string, unknown>;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
export declare function createArtifactRoute(projectRoot: string, mdxPath: string, docsDir: string): ArtifactRoute;
|
|
17
|
+
export declare function createArtifactMeta(projectRoot: string, artifact: ArtifactRoute): Promise<{
|
|
18
|
+
routePath: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
sourcePath: string;
|
|
21
|
+
statePath: string;
|
|
22
|
+
stateExists: boolean;
|
|
23
|
+
writable: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
export declare function readArtifactState(artifact: ArtifactRoute): Promise<ArtifactState>;
|
|
26
|
+
export declare function writeArtifactState(projectRoot: string, artifact: ArtifactRoute, value: unknown): Promise<ArtifactState>;
|
|
27
|
+
export declare function createDefaultArtifactState(artifact: ArtifactRoute): ArtifactState;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function createArtifactRoute(projectRoot, mdxPath, docsDir) {
|
|
4
|
+
const sourcePath = path.resolve(mdxPath);
|
|
5
|
+
const statePath = sourcePath.replace(/\.mdx$/i, ".state.json");
|
|
6
|
+
const slug = createArtifactSlug(projectRoot, sourcePath, docsDir);
|
|
7
|
+
return {
|
|
8
|
+
routePath: `/artifacts/${slug}`,
|
|
9
|
+
slug,
|
|
10
|
+
sourcePath,
|
|
11
|
+
sourceRelativePath: toProjectRelativePath(projectRoot, sourcePath),
|
|
12
|
+
statePath,
|
|
13
|
+
stateRelativePath: toProjectRelativePath(projectRoot, statePath)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function createArtifactMeta(projectRoot, artifact) {
|
|
17
|
+
return {
|
|
18
|
+
routePath: artifact.routePath,
|
|
19
|
+
slug: artifact.slug,
|
|
20
|
+
sourcePath: artifact.sourceRelativePath,
|
|
21
|
+
statePath: artifact.stateRelativePath,
|
|
22
|
+
stateExists: await fileExists(artifact.statePath),
|
|
23
|
+
writable: isSafeStatePath(projectRoot, artifact)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export async function readArtifactState(artifact) {
|
|
27
|
+
try {
|
|
28
|
+
const source = await readFile(artifact.statePath, "utf8");
|
|
29
|
+
return normalizeArtifactState(JSON.parse(source), artifact);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error.code === "ENOENT") {
|
|
33
|
+
return createDefaultArtifactState(artifact);
|
|
34
|
+
}
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function writeArtifactState(projectRoot, artifact, value) {
|
|
39
|
+
if (!isSafeStatePath(projectRoot, artifact)) {
|
|
40
|
+
throw new Error("State file must be next to its MDX source inside the project workspace.");
|
|
41
|
+
}
|
|
42
|
+
const state = normalizeArtifactState(value, artifact);
|
|
43
|
+
await mkdir(path.dirname(artifact.statePath), { recursive: true });
|
|
44
|
+
await writeFile(artifact.statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
45
|
+
return state;
|
|
46
|
+
}
|
|
47
|
+
export function createDefaultArtifactState(artifact) {
|
|
48
|
+
return {
|
|
49
|
+
version: 1,
|
|
50
|
+
source: artifact.sourceRelativePath,
|
|
51
|
+
threads: [],
|
|
52
|
+
interactions: {}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function normalizeArtifactState(value, artifact) {
|
|
56
|
+
if (!isRecord(value)) {
|
|
57
|
+
throw new Error("Artifact state must be a JSON object.");
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...value,
|
|
61
|
+
version: typeof value.version === "number" ? value.version : 1,
|
|
62
|
+
source: artifact.sourceRelativePath,
|
|
63
|
+
threads: Array.isArray(value.threads) ? value.threads : [],
|
|
64
|
+
interactions: isRecord(value.interactions) ? value.interactions : {}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function createArtifactSlug(projectRoot, mdxPath, docsDir) {
|
|
68
|
+
const docsRoot = path.resolve(projectRoot, docsDir);
|
|
69
|
+
const relativeToDocs = path.relative(docsRoot, mdxPath);
|
|
70
|
+
const pathForSlug = relativeToDocs.startsWith("..") || path.isAbsolute(relativeToDocs)
|
|
71
|
+
? path.basename(mdxPath, path.extname(mdxPath))
|
|
72
|
+
: relativeToDocs.replace(/\.mdx$/i, "");
|
|
73
|
+
return pathForSlug
|
|
74
|
+
.split(path.sep)
|
|
75
|
+
.map((segment) => slugify(segment))
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.join("/");
|
|
78
|
+
}
|
|
79
|
+
function slugify(value) {
|
|
80
|
+
const slug = value
|
|
81
|
+
.toLowerCase()
|
|
82
|
+
.replace(/[`*_~[\]()]/g, "")
|
|
83
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
84
|
+
.replace(/^-+|-+$/g, "")
|
|
85
|
+
.slice(0, 64);
|
|
86
|
+
return slug || "artifact";
|
|
87
|
+
}
|
|
88
|
+
function isSafeStatePath(projectRoot, artifact) {
|
|
89
|
+
return (isInsidePath(projectRoot, artifact.sourcePath) &&
|
|
90
|
+
isInsidePath(projectRoot, artifact.statePath) &&
|
|
91
|
+
isSiblingStatePath(artifact));
|
|
92
|
+
}
|
|
93
|
+
function isInsidePath(root, target) {
|
|
94
|
+
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
95
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
96
|
+
}
|
|
97
|
+
function isSiblingStatePath(artifact) {
|
|
98
|
+
const expected = artifact.sourcePath.replace(/\.mdx$/i, ".state.json");
|
|
99
|
+
return path.resolve(expected) === path.resolve(artifact.statePath);
|
|
100
|
+
}
|
|
101
|
+
function toProjectRelativePath(projectRoot, target) {
|
|
102
|
+
return path.relative(projectRoot, target).replaceAll(path.sep, "/");
|
|
103
|
+
}
|
|
104
|
+
async function fileExists(filePath) {
|
|
105
|
+
try {
|
|
106
|
+
await access(filePath);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function isRecord(value) {
|
|
114
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
115
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildCommand(projectRoot: string, input: string): Promise<void>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadConfig } from "./config.js";
|
|
4
|
+
import { buildArtifact, createArtifactProject } from "./vite-artifact.js";
|
|
5
|
+
export async function buildCommand(projectRoot, input) {
|
|
6
|
+
const config = await loadConfig(projectRoot);
|
|
7
|
+
const mdxPath = path.resolve(projectRoot, input);
|
|
8
|
+
const project = await createArtifactProject(projectRoot, mdxPath, config);
|
|
9
|
+
try {
|
|
10
|
+
const html = await buildArtifact(project);
|
|
11
|
+
const outputPath = outputFileFor(projectRoot, mdxPath, config.docsDir, config.outDir);
|
|
12
|
+
await mkdir(path.dirname(outputPath), { recursive: true });
|
|
13
|
+
await writeFile(outputPath, html);
|
|
14
|
+
console.log(`built ${path.relative(projectRoot, outputPath)}`);
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
await project.cleanup();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function outputFileFor(projectRoot, mdxPath, docsDir, outDir) {
|
|
21
|
+
const docsRoot = path.resolve(projectRoot, docsDir);
|
|
22
|
+
const relative = path.relative(docsRoot, mdxPath);
|
|
23
|
+
const safeRelative = relative.startsWith("..") ? path.basename(mdxPath) : relative;
|
|
24
|
+
return path.resolve(projectRoot, outDir, safeRelative.replace(/\.mdx$/, ".html"));
|
|
25
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { componentRegistry, findComponentMeta } from "../react/registry.js";
|
|
2
|
+
export function componentsCommand(input, options = {}) {
|
|
3
|
+
if (input) {
|
|
4
|
+
const component = findComponentMeta(input);
|
|
5
|
+
if (!component) {
|
|
6
|
+
throw new Error(`Unknown component: ${input}`);
|
|
7
|
+
}
|
|
8
|
+
if (options.json) {
|
|
9
|
+
console.log(JSON.stringify(component, null, 2));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
printComponent(component);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (options.json) {
|
|
16
|
+
console.log(JSON.stringify(componentRegistry, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log("Available artifact components:\n");
|
|
20
|
+
for (const component of componentRegistry) {
|
|
21
|
+
console.log(`- ${component.name}: ${component.description}`);
|
|
22
|
+
}
|
|
23
|
+
console.log("\nUse `artifact-kit components <ComponentName>` to inspect props and examples.");
|
|
24
|
+
console.log("Use `artifact-kit components --json` for machine-readable metadata.");
|
|
25
|
+
}
|
|
26
|
+
function printComponent(component) {
|
|
27
|
+
console.log(`${component.name}\n`);
|
|
28
|
+
console.log(component.description);
|
|
29
|
+
if (component.category || component.stability) {
|
|
30
|
+
console.log(`\nMetadata: ${[component.category ? `category=${component.category}` : undefined, component.stability ? `stability=${component.stability}` : undefined].filter(Boolean).join(", ")}`);
|
|
31
|
+
}
|
|
32
|
+
console.log("\nUse when:");
|
|
33
|
+
for (const item of component.useWhen) {
|
|
34
|
+
console.log(`- ${item}`);
|
|
35
|
+
}
|
|
36
|
+
console.log("\nProps:");
|
|
37
|
+
for (const prop of component.props) {
|
|
38
|
+
printProp(prop);
|
|
39
|
+
}
|
|
40
|
+
if (component.types?.length) {
|
|
41
|
+
console.log("\nNested types:");
|
|
42
|
+
for (const type of component.types) {
|
|
43
|
+
console.log(`\n${type.name}${type.description ? `: ${type.description}` : ""}`);
|
|
44
|
+
for (const field of type.fields) {
|
|
45
|
+
printProp(field);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log("\nExample:");
|
|
50
|
+
console.log(component.example);
|
|
51
|
+
}
|
|
52
|
+
function printProp(prop) {
|
|
53
|
+
console.log(`- ${prop.name}${prop.required ? " (required)" : ""}: ${prop.type}`);
|
|
54
|
+
if (prop.contentType) {
|
|
55
|
+
console.log(` content type: ${prop.contentType}`);
|
|
56
|
+
}
|
|
57
|
+
console.log(` ${prop.description}`);
|
|
58
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
const defaultConfig = {
|
|
5
|
+
docsDir: "artifact-docs",
|
|
6
|
+
includeDefaultStyles: true,
|
|
7
|
+
outDir: "dist/artifacts",
|
|
8
|
+
port: 4321,
|
|
9
|
+
styles: []
|
|
10
|
+
};
|
|
11
|
+
export async function loadConfig(projectRoot) {
|
|
12
|
+
const configPath = await findConfigPath(projectRoot);
|
|
13
|
+
if (!configPath) {
|
|
14
|
+
return defaultConfig;
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const imported = (await import(pathToFileURL(configPath).href));
|
|
18
|
+
return { ...defaultConfig, ...(imported.default ?? {}) };
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new Error(`Failed to read ${path.basename(configPath)}: ${String(error)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function findConfigPath(projectRoot) {
|
|
25
|
+
for (const filename of ["artifact-kit.config.mjs", "artifact-kit.config.js", "artifact-kit.config.ts"]) {
|
|
26
|
+
const configPath = path.join(projectRoot, filename);
|
|
27
|
+
try {
|
|
28
|
+
await access(configPath);
|
|
29
|
+
return configPath;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Try the next supported config filename.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function devCommand(projectRoot: string, input: string): Promise<void>;
|