mdx-artifacts 0.1.2 → 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.
Files changed (141) hide show
  1. package/README.md +211 -59
  2. package/README.zh-CN.md +299 -42
  3. package/agents/AGENTS.snippet.md +21 -10
  4. package/artifact-docs/examples/commentable-feedback.mdx +76 -70
  5. package/artifact-docs/examples/decision-matrix.mdx +122 -50
  6. package/artifact-docs/examples/layout-composition.mdx +106 -128
  7. package/artifact-docs/examples/streamlit-style-mixed.mdx +100 -85
  8. package/dist/lib/cli/{build.js → commands/build.js} +2 -2
  9. package/dist/lib/cli/{components.js → commands/components.js} +19 -3
  10. package/dist/lib/cli/{dev.js → commands/dev.js} +2 -2
  11. package/dist/lib/cli/commands/interactions.d.ts +2 -0
  12. package/dist/lib/cli/commands/interactions.js +280 -0
  13. package/dist/lib/cli/commands/review.d.ts +1 -0
  14. package/dist/lib/cli/commands/review.js +171 -0
  15. package/dist/lib/cli/commands/scaffold.d.ts +16 -0
  16. package/dist/lib/cli/commands/scaffold.js +440 -0
  17. package/dist/lib/cli/commands/validate.d.ts +18 -0
  18. package/dist/lib/cli/commands/validate.js +311 -0
  19. package/dist/lib/cli/config/config.d.ts +2 -0
  20. package/dist/lib/cli/{config.js → config/config.js} +3 -2
  21. package/dist/lib/cli/{types.d.ts → config/types.d.ts} +2 -1
  22. package/dist/lib/cli/{vite-artifact.d.ts → dev-server/vite-artifact.d.ts} +3 -3
  23. package/dist/lib/cli/{vite-artifact.js → dev-server/vite-artifact.js} +170 -10
  24. package/dist/lib/cli/diagnostics/diagnostics.d.ts +11 -0
  25. package/dist/lib/cli/diagnostics/diagnostics.js +1 -0
  26. package/dist/lib/cli/index.js +39 -18
  27. package/dist/lib/cli/mdx/sortable-list.d.ts +14 -0
  28. package/dist/lib/cli/mdx/sortable-list.js +520 -0
  29. package/dist/lib/cli/resources/resource-policy.d.ts +15 -0
  30. package/dist/lib/cli/resources/resource-policy.js +46 -0
  31. package/dist/lib/cli/resources/safe-path.d.ts +13 -0
  32. package/dist/lib/cli/resources/safe-path.js +55 -0
  33. package/dist/lib/cli/services/interaction-service.d.ts +40 -0
  34. package/dist/lib/cli/services/interaction-service.js +226 -0
  35. package/dist/lib/cli/services/review.d.ts +43 -0
  36. package/dist/lib/cli/{review.js → services/review.js} +34 -172
  37. package/dist/lib/react/{components → composites/comparison-set}/ComparisonSet.d.ts +1 -1
  38. package/dist/lib/react/{components → composites/comparison-set}/ComparisonSet.js +3 -3
  39. package/dist/lib/react/composites/comparison-set/index.d.ts +2 -0
  40. package/dist/lib/react/composites/comparison-set/index.js +1 -0
  41. package/dist/lib/react/composites/content-set/ContentItem.d.ts +37 -0
  42. package/dist/lib/react/composites/content-set/ContentItem.js +49 -0
  43. package/dist/lib/react/composites/content-set/index.d.ts +2 -0
  44. package/dist/lib/react/composites/content-set/index.js +1 -0
  45. package/dist/lib/react/{components → composites/export-panel}/ExportPanel.js +2 -2
  46. package/dist/lib/react/composites/export-panel/index.d.ts +2 -0
  47. package/dist/lib/react/composites/export-panel/index.js +1 -0
  48. package/dist/lib/react/{components → composites/section}/Section.js +1 -1
  49. package/dist/lib/react/composites/section/index.d.ts +2 -0
  50. package/dist/lib/react/composites/section/index.js +1 -0
  51. package/dist/lib/react/index.d.ts +36 -31
  52. package/dist/lib/react/index.js +18 -15
  53. package/dist/lib/react/interactions/artifact-state/index.d.ts +1 -0
  54. package/dist/lib/react/interactions/artifact-state/index.js +1 -0
  55. package/dist/lib/react/{components → interactions/comments}/Comments.d.ts +2 -2
  56. package/dist/lib/react/{components → interactions/comments}/Comments.js +3 -3
  57. package/dist/lib/react/interactions/comments/index.d.ts +1 -0
  58. package/dist/lib/react/interactions/comments/index.js +1 -0
  59. package/dist/lib/react/interactions/sortable-list/SortableList.d.ts +29 -0
  60. package/dist/lib/react/interactions/sortable-list/SortableList.js +282 -0
  61. package/dist/lib/react/interactions/sortable-list/index.d.ts +1 -0
  62. package/dist/lib/react/interactions/sortable-list/index.js +1 -0
  63. package/dist/lib/react/layout/layout-primitives/index.d.ts +2 -0
  64. package/dist/lib/react/layout/layout-primitives/index.js +1 -0
  65. package/dist/lib/react/legacy/LegacyContentComponents.d.ts +65 -0
  66. package/dist/lib/react/legacy/LegacyContentComponents.js +26 -0
  67. package/dist/lib/react/mdx-components.d.ts +5 -0
  68. package/dist/lib/react/mdx-components.js +38 -0
  69. package/dist/lib/react/{components → primitives/annotated-code}/AnnotatedCode.d.ts +1 -1
  70. package/dist/lib/react/{components → primitives/annotated-code}/AnnotatedCode.js +5 -5
  71. package/dist/lib/react/primitives/annotated-code/index.d.ts +2 -0
  72. package/dist/lib/react/primitives/annotated-code/index.js +1 -0
  73. package/dist/lib/react/primitives/callout/Callout.d.ts +11 -0
  74. package/dist/lib/react/{components → primitives/callout}/Callout.js +9 -6
  75. package/dist/lib/react/primitives/callout/index.d.ts +2 -0
  76. package/dist/lib/react/primitives/callout/index.js +1 -0
  77. package/dist/lib/react/primitives/code-block/CodeBlock.d.ts +20 -0
  78. package/dist/lib/react/primitives/code-block/CodeBlock.js +32 -0
  79. package/dist/lib/react/primitives/code-block/index.d.ts +2 -0
  80. package/dist/lib/react/primitives/code-block/index.js +1 -0
  81. package/dist/lib/react/primitives/code-surface/CodeSurface.d.ts +11 -0
  82. package/dist/lib/react/primitives/code-surface/CodeSurface.js +34 -0
  83. package/dist/lib/react/primitives/code-surface/index.d.ts +2 -0
  84. package/dist/lib/react/primitives/code-surface/index.js +1 -0
  85. package/dist/lib/react/primitives/diff-block/DiffBlock.js +25 -0
  86. package/dist/lib/react/primitives/diff-block/index.d.ts +2 -0
  87. package/dist/lib/react/primitives/diff-block/index.js +1 -0
  88. package/dist/lib/react/{components → primitives/inline-text}/InlineText.d.ts +4 -2
  89. package/dist/lib/react/primitives/inline-text/InlineText.js +28 -0
  90. package/dist/lib/react/primitives/inline-text/index.d.ts +2 -0
  91. package/dist/lib/react/primitives/inline-text/index.js +1 -0
  92. package/dist/lib/react/primitives/markdown-body/MarkdownBody.d.ts +9 -0
  93. package/dist/lib/react/primitives/markdown-body/MarkdownBody.js +49 -0
  94. package/dist/lib/react/primitives/markdown-body/index.d.ts +2 -0
  95. package/dist/lib/react/primitives/markdown-body/index.js +1 -0
  96. package/dist/lib/react/primitives/severity-badge/index.d.ts +2 -0
  97. package/dist/lib/react/primitives/severity-badge/index.js +1 -0
  98. package/dist/lib/react/registry.d.ts +10 -0
  99. package/dist/lib/react/registry.js +505 -210
  100. package/dist/lib/react/styles.css +490 -38
  101. package/docs/cli-structure.md +141 -0
  102. package/docs/component-protocol.md +199 -33
  103. package/docs/component-taxonomy.md +40 -4
  104. package/docs/design.md +42 -21
  105. package/docs/design.zh-CN.md +41 -21
  106. package/docs/naming.md +17 -7
  107. package/docs/releasing.md +132 -0
  108. package/docs/testing.md +35 -10
  109. package/package.json +9 -7
  110. package/dist/lib/cli/config.d.ts +0 -2
  111. package/dist/lib/cli/review.d.ts +0 -33
  112. package/dist/lib/cli/scaffold.d.ts +0 -1
  113. package/dist/lib/cli/scaffold.js +0 -56
  114. package/dist/lib/cli/validate.d.ts +0 -6
  115. package/dist/lib/cli/validate.js +0 -79
  116. package/dist/lib/react/components/Callout.d.ts +0 -9
  117. package/dist/lib/react/components/CodeBlock.d.ts +0 -10
  118. package/dist/lib/react/components/CodeBlock.js +0 -28
  119. package/dist/lib/react/components/DecisionMatrix.d.ts +0 -16
  120. package/dist/lib/react/components/DecisionMatrix.js +0 -27
  121. package/dist/lib/react/components/DiffBlock.js +0 -24
  122. package/dist/lib/react/components/InlineText.js +0 -18
  123. package/dist/lib/react/components/MarkdownBody.d.ts +0 -7
  124. package/dist/lib/react/components/MarkdownBody.js +0 -36
  125. package/dist/lib/react/components/OptionGrid.d.ts +0 -13
  126. package/dist/lib/react/components/OptionGrid.js +0 -21
  127. /package/dist/lib/cli/{build.d.ts → commands/build.d.ts} +0 -0
  128. /package/dist/lib/cli/{components.d.ts → commands/components.d.ts} +0 -0
  129. /package/dist/lib/cli/{dev.d.ts → commands/dev.d.ts} +0 -0
  130. /package/dist/lib/cli/{types.js → config/types.js} +0 -0
  131. /package/dist/lib/cli/{artifact-state.d.ts → state/artifact-state.d.ts} +0 -0
  132. /package/dist/lib/cli/{artifact-state.js → state/artifact-state.js} +0 -0
  133. /package/dist/lib/react/{components → composites/export-panel}/ExportPanel.d.ts +0 -0
  134. /package/dist/lib/react/{components → composites/section}/Section.d.ts +0 -0
  135. /package/dist/lib/react/{components → interactions/artifact-state}/ArtifactState.d.ts +0 -0
  136. /package/dist/lib/react/{components → interactions/artifact-state}/ArtifactState.js +0 -0
  137. /package/dist/lib/react/{components → layout/layout-primitives}/Layout.d.ts +0 -0
  138. /package/dist/lib/react/{components → layout/layout-primitives}/Layout.js +0 -0
  139. /package/dist/lib/react/{components → primitives/diff-block}/DiffBlock.d.ts +0 -0
  140. /package/dist/lib/react/{components → primitives/severity-badge}/SeverityBadge.d.ts +0 -0
  141. /package/dist/lib/react/{components → primitives/severity-badge}/SeverityBadge.js +0 -0
@@ -0,0 +1,311 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { componentRegistry } from "../../react/registry.js";
4
+ import { validateResourceReferences } from "../resources/resource-policy.js";
5
+ const componentsRequiringStableId = [
6
+ "Section",
7
+ "ComparisonSet",
8
+ "ComparisonSet.Item",
9
+ "AnnotatedCode",
10
+ "CodeBlock",
11
+ "DiffBlock",
12
+ "Callout",
13
+ "ContentItem",
14
+ "ContentSet",
15
+ "ContentSet.Item",
16
+ "SortableList"
17
+ ];
18
+ const deprecatedAuthoringComponents = [
19
+ {
20
+ componentName: "DecisionMatrix",
21
+ warning: "DecisionMatrix is a deprecated compatibility component. Use ContentSet with ContentSet.Item children.",
22
+ suggestion: "Replace DecisionMatrix with ContentSet and ContentSet.Item children."
23
+ },
24
+ {
25
+ componentName: "DecisionMatrix.Option",
26
+ warning: "DecisionMatrix.Option is a deprecated compatibility component. Use ContentSet.Item.",
27
+ suggestion: "Replace DecisionMatrix.Option with ContentSet.Item."
28
+ },
29
+ {
30
+ componentName: "OptionGrid",
31
+ warning: "OptionGrid is a deprecated compatibility component. Use ContentSet with ContentSet.Item children.",
32
+ suggestion: "Replace OptionGrid with ContentSet and ContentSet.Item children."
33
+ },
34
+ {
35
+ componentName: "OptionGrid.Item",
36
+ warning: "OptionGrid.Item is a deprecated compatibility component. Use ContentSet.Item.",
37
+ suggestion: "Replace OptionGrid.Item with ContentSet.Item."
38
+ }
39
+ ];
40
+ const deprecatedAuthoringProps = [
41
+ {
42
+ componentName: "DecisionMatrix",
43
+ propName: "question",
44
+ warning: 'DecisionMatrix prop "question" is deprecated. Use ContentSet prop "title".',
45
+ suggestion: 'Rename "question" to "title" when migrating to ContentSet.'
46
+ },
47
+ {
48
+ componentName: "DecisionMatrix",
49
+ propName: "options",
50
+ warning: 'DecisionMatrix prop "options" is deprecated. Use ContentSet.Item children.',
51
+ suggestion: "Move each option into an explicit ContentSet.Item child."
52
+ },
53
+ {
54
+ componentName: "DecisionMatrix.Option",
55
+ propName: "name",
56
+ warning: 'DecisionMatrix.Option prop "name" is deprecated. Use "title".',
57
+ suggestion: 'Rename "name" to "title".'
58
+ },
59
+ {
60
+ componentName: "DecisionMatrix.Option",
61
+ propName: "pros",
62
+ warning: 'DecisionMatrix.Option prop "pros" is deprecated. Move long lists into MDX children.',
63
+ suggestion: "Move pros into the ContentSet.Item MDX body."
64
+ },
65
+ {
66
+ componentName: "DecisionMatrix.Option",
67
+ propName: "cons",
68
+ warning: 'DecisionMatrix.Option prop "cons" is deprecated. Move long lists into MDX children.',
69
+ suggestion: "Move cons into the ContentSet.Item MDX body."
70
+ },
71
+ {
72
+ componentName: "DecisionMatrix.Option",
73
+ propName: "risks",
74
+ warning: 'DecisionMatrix.Option prop "risks" is deprecated. Move risks into MDX children.',
75
+ suggestion: "Move risks into the ContentSet.Item MDX body."
76
+ },
77
+ {
78
+ componentName: "DecisionMatrix.Option",
79
+ propName: "confidence",
80
+ warning: 'DecisionMatrix.Option prop "confidence" is deprecated. Use "badge" for short display labels.',
81
+ suggestion: 'Use "badge" for short display labels.'
82
+ },
83
+ {
84
+ componentName: "DecisionMatrix.Option",
85
+ propName: "verdict",
86
+ warning: 'DecisionMatrix.Option prop "verdict" is deprecated. Use "summary" or MDX children.',
87
+ suggestion: 'Use "summary" for short verdict text or move longer verdicts into children.'
88
+ },
89
+ {
90
+ componentName: "OptionGrid.Item",
91
+ propName: "name",
92
+ warning: 'OptionGrid.Item prop "name" is deprecated. Use "title".',
93
+ suggestion: 'Rename "name" to "title".'
94
+ },
95
+ {
96
+ componentName: "OptionGrid",
97
+ propName: "options",
98
+ warning: 'OptionGrid prop "options" is deprecated. Use ContentSet.Item children.',
99
+ suggestion: "Move each option into an explicit ContentSet.Item child."
100
+ },
101
+ {
102
+ componentName: "OptionGrid.Item",
103
+ propName: "intent",
104
+ warning: 'OptionGrid.Item prop "intent" is deprecated. Use "summary" for short intent text.',
105
+ suggestion: 'Rename "intent" to "summary" when the text is short.'
106
+ },
107
+ {
108
+ componentName: "OptionGrid.Item",
109
+ propName: "description",
110
+ warning: 'OptionGrid.Item prop "description" is deprecated. Move longer descriptions into MDX children.',
111
+ suggestion: "Move longer descriptions into the ContentSet.Item MDX body."
112
+ },
113
+ {
114
+ componentName: "OptionGrid.Item",
115
+ propName: "tradeoffs",
116
+ warning: 'OptionGrid.Item prop "tradeoffs" is deprecated. Move tradeoff lists into MDX children.',
117
+ suggestion: "Move tradeoff lists into the ContentSet.Item MDX body."
118
+ }
119
+ ];
120
+ export async function validateMdx(filePath, options = {}) {
121
+ const result = createValidationResult();
122
+ if (path.extname(filePath) !== ".mdx") {
123
+ addDiagnostic(result, {
124
+ severity: "error",
125
+ code: "invalid_file_extension",
126
+ message: "Input file must be .mdx.",
127
+ sourcePath: filePath,
128
+ suggestion: "Pass a .mdx artifact source file to mdx-artifacts validate.",
129
+ example: "mdx-artifacts validate artifact-docs/examples/hello.mdx"
130
+ });
131
+ return result;
132
+ }
133
+ let source = "";
134
+ try {
135
+ source = await readFile(filePath, "utf8");
136
+ }
137
+ catch {
138
+ addDiagnostic(result, {
139
+ severity: "error",
140
+ code: "file_read_failed",
141
+ message: `Failed to read file: ${filePath}`,
142
+ sourcePath: filePath,
143
+ suggestion: "Confirm the file exists and is readable."
144
+ });
145
+ return result;
146
+ }
147
+ if (source.includes("<script")) {
148
+ addDiagnostic(result, {
149
+ severity: "error",
150
+ code: "raw_script_blocked",
151
+ message: "Do not write <script> directly in MDX. Wrap behavior in a controlled component.",
152
+ sourcePath: filePath,
153
+ suggestion: "Move browser behavior into a controlled React component instead of inline script tags."
154
+ });
155
+ }
156
+ if (options.config) {
157
+ const resourceDiagnostics = await validateResourceReferences({
158
+ projectRoot: options.projectRoot ?? process.cwd(),
159
+ references: options.config.styles.map((stylePath) => ({
160
+ type: "style",
161
+ path: stylePath,
162
+ sourcePath: filePath,
163
+ fieldName: "styles"
164
+ }))
165
+ });
166
+ for (const diagnostic of resourceDiagnostics) {
167
+ addDiagnostic(result, diagnostic);
168
+ }
169
+ }
170
+ if (!source.includes("ExportPanel") && !source.includes("CommentExport")) {
171
+ addDiagnostic(result, {
172
+ severity: "warning",
173
+ code: "export_component_missing",
174
+ message: "ExportPanel or equivalent export component not found. Interactive artifacts should provide an export path.",
175
+ sourcePath: filePath,
176
+ suggestion: "Add ExportPanel for result handoff, or CommentExport for comments-only artifacts.",
177
+ example: '<ExportPanel value={{ status: "ready" }} />'
178
+ });
179
+ }
180
+ if (source.length > 40_000) {
181
+ addDiagnostic(result, {
182
+ severity: "warning",
183
+ code: "mdx_file_large",
184
+ message: "MDX file is large. Move bulky data to adjacent JSON files.",
185
+ sourcePath: filePath,
186
+ suggestion: "Keep authored prose in MDX and move large structured data into adjacent files."
187
+ });
188
+ }
189
+ const knownComponents = componentRegistry.map((component) => component.name);
190
+ const usedArtifactComponent = knownComponents.some((name) => source.includes(`<${name}`));
191
+ if (!usedArtifactComponent) {
192
+ addDiagnostic(result, {
193
+ severity: "warning",
194
+ code: "high_level_component_missing",
195
+ message: "No first-stage high-level artifact component found. Confirm plain MDX is intentional.",
196
+ sourcePath: filePath,
197
+ suggestion: "Use high-level artifact components when the document needs structured workflow UI."
198
+ });
199
+ }
200
+ const sourceWithoutStringLiterals = stripStringLiterals(source);
201
+ for (const componentName of componentsRequiringStableId) {
202
+ if (hasOpeningTagWithoutProp(sourceWithoutStringLiterals, componentName, "id")) {
203
+ addDiagnostic(result, {
204
+ severity: "warning",
205
+ code: "stable_id_missing",
206
+ message: `${componentName} should include a stable id prop so comments and state can use a durable anchorId.`,
207
+ sourcePath: filePath,
208
+ componentName,
209
+ propName: "id",
210
+ suggestion: `Add a stable id prop to ${componentName}.`,
211
+ example: stableIdExample(componentName)
212
+ });
213
+ }
214
+ }
215
+ for (const deprecatedComponent of deprecatedAuthoringComponents) {
216
+ if (hasOpeningTag(sourceWithoutStringLiterals, deprecatedComponent.componentName)) {
217
+ addDiagnostic(result, {
218
+ severity: "warning",
219
+ code: "deprecated_component",
220
+ message: deprecatedComponent.warning,
221
+ sourcePath: filePath,
222
+ componentName: deprecatedComponent.componentName,
223
+ suggestion: deprecatedComponent.suggestion
224
+ });
225
+ }
226
+ }
227
+ for (const deprecatedProp of deprecatedAuthoringProps) {
228
+ if (hasOpeningTagWithProp(sourceWithoutStringLiterals, deprecatedProp.componentName, deprecatedProp.propName)) {
229
+ addDiagnostic(result, {
230
+ severity: "warning",
231
+ code: "deprecated_prop",
232
+ message: deprecatedProp.warning,
233
+ sourcePath: filePath,
234
+ componentName: deprecatedProp.componentName,
235
+ propName: deprecatedProp.propName,
236
+ suggestion: deprecatedProp.suggestion
237
+ });
238
+ }
239
+ }
240
+ return result;
241
+ }
242
+ function createValidationResult() {
243
+ return { diagnostics: [], errors: [], warnings: [] };
244
+ }
245
+ function addDiagnostic(result, diagnostic) {
246
+ result.diagnostics.push(diagnostic);
247
+ if (diagnostic.severity === "error") {
248
+ result.errors.push(diagnostic.message);
249
+ return;
250
+ }
251
+ if (diagnostic.severity === "warning") {
252
+ result.warnings.push(diagnostic.message);
253
+ }
254
+ }
255
+ export function formatValidationJson(result) {
256
+ return {
257
+ ok: result.errors.length === 0,
258
+ diagnostics: result.diagnostics
259
+ };
260
+ }
261
+ export function printValidationResult(result) {
262
+ for (const error of result.errors) {
263
+ console.error(`error: ${error}`);
264
+ }
265
+ for (const warning of result.warnings) {
266
+ console.warn(`warn: ${warning}`);
267
+ }
268
+ if (result.errors.length === 0 && result.warnings.length === 0) {
269
+ console.log("validate ok");
270
+ }
271
+ }
272
+ function stableIdExample(componentName) {
273
+ if (componentName.includes(".")) {
274
+ return `<${componentName} id="item.example" title="Example">Readable body.</${componentName}>`;
275
+ }
276
+ return `<${componentName} id="${componentName.toLowerCase()}.example" />`;
277
+ }
278
+ function stripStringLiterals(source) {
279
+ return source
280
+ .replace(/`(?:\\[\s\S]|[^`\\])*`/g, "``")
281
+ .replace(/"(?:\\.|[^"\\])*"/g, '""')
282
+ .replace(/'(?:\\.|[^'\\])*'/g, "''");
283
+ }
284
+ function hasOpeningTagWithoutProp(source, componentName, propName) {
285
+ const escapedName = componentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
286
+ const tagPattern = new RegExp(`<${escapedName}(?=[\\s>/])[^>]*>`, "g");
287
+ const propPattern = new RegExp(`\\s${propName}\\s*=`);
288
+ for (const match of source.matchAll(tagPattern)) {
289
+ const openingTag = match[0];
290
+ if (!propPattern.test(openingTag)) {
291
+ return true;
292
+ }
293
+ }
294
+ return false;
295
+ }
296
+ function hasOpeningTag(source, componentName) {
297
+ const escapedName = componentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
298
+ const tagPattern = new RegExp(`<${escapedName}(?=[\\s>/])`);
299
+ return tagPattern.test(source);
300
+ }
301
+ function hasOpeningTagWithProp(source, componentName, propName) {
302
+ const escapedName = componentName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
303
+ const tagPattern = new RegExp(`<${escapedName}(?=[\\s>/])[^>]*>`, "g");
304
+ const propPattern = new RegExp(`\\s${propName}\\s*=`);
305
+ for (const match of source.matchAll(tagPattern)) {
306
+ if (propPattern.test(match[0])) {
307
+ return true;
308
+ }
309
+ }
310
+ return false;
311
+ }
@@ -0,0 +1,2 @@
1
+ import type { MdxArtifactsConfig } from "./types";
2
+ export declare function loadConfig(projectRoot: string): Promise<Required<MdxArtifactsConfig>>;
@@ -6,7 +6,8 @@ const defaultConfig = {
6
6
  includeDefaultStyles: true,
7
7
  outDir: "dist/artifacts",
8
8
  port: 4321,
9
- styles: []
9
+ styles: [],
10
+ tailwindSources: []
10
11
  };
11
12
  export async function loadConfig(projectRoot) {
12
13
  const configPath = await findConfigPath(projectRoot);
@@ -22,7 +23,7 @@ export async function loadConfig(projectRoot) {
22
23
  }
23
24
  }
24
25
  async function findConfigPath(projectRoot) {
25
- for (const filename of ["artifact-kit.config.mjs", "artifact-kit.config.js", "artifact-kit.config.ts"]) {
26
+ for (const filename of ["mdx-artifacts.config.mjs", "mdx-artifacts.config.js", "mdx-artifacts.config.ts"]) {
26
27
  const configPath = path.join(projectRoot, filename);
27
28
  try {
28
29
  await access(configPath);
@@ -1,7 +1,8 @@
1
- export type ArtifactKitConfig = {
1
+ export type MdxArtifactsConfig = {
2
2
  docsDir?: string;
3
3
  includeDefaultStyles?: boolean;
4
4
  outDir?: string;
5
5
  port?: number;
6
6
  styles?: string[];
7
+ tailwindSources?: string[];
7
8
  };
@@ -1,6 +1,6 @@
1
1
  import type { InlineConfig, ViteDevServer } from "vite";
2
- import { type ArtifactRoute } from "./artifact-state";
3
- import type { ArtifactKitConfig } from "./types";
2
+ import { type ArtifactRoute } from "../state/artifact-state";
3
+ import type { MdxArtifactsConfig } from "../config/types";
4
4
  export type ArtifactProject = {
5
5
  artifact: ArtifactRoute;
6
6
  tmpDir: string;
@@ -8,6 +8,6 @@ export type ArtifactProject = {
8
8
  config: InlineConfig;
9
9
  cleanup: () => Promise<void>;
10
10
  };
11
- export declare function createArtifactProject(projectRoot: string, mdxPath: string, config: Required<ArtifactKitConfig>): Promise<ArtifactProject>;
11
+ export declare function createArtifactProject(projectRoot: string, mdxPath: string, config: Required<MdxArtifactsConfig>): Promise<ArtifactProject>;
12
12
  export declare function startDevServer(project: ArtifactProject): Promise<ViteDevServer>;
13
13
  export declare function buildArtifact(project: ArtifactProject): Promise<string>;
@@ -7,21 +7,24 @@ import { createRequire } from "node:module";
7
7
  import path from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
  import { createServer, build as viteBuild } from "vite";
10
- import { createArtifactMeta, createArtifactRoute, readArtifactState, writeArtifactState } from "./artifact-state.js";
10
+ import { createArtifactMeta, createArtifactRoute, readArtifactState, writeArtifactState } from "../state/artifact-state.js";
11
+ import { addInteractionItemService, promoteInteractionService, removeInteractionItemService, resetInteractionService, setInteractionOrderService, updateInteractionItemService } from "../services/interaction-service.js";
11
12
  const packageCliDir = path.dirname(fileURLToPath(import.meta.url));
12
- const defaultStylesPath = path.resolve(packageCliDir, "../react/styles.css");
13
+ const defaultStylesPath = path.resolve(packageCliDir, "../../react/styles.css");
13
14
  export async function createArtifactProject(projectRoot, mdxPath, config) {
14
- const tmpDir = path.join(projectRoot, ".artifact-kit", "tmp", randomUUID());
15
+ const tmpDir = path.join(projectRoot, ".mdx-artifacts", "tmp", randomUUID());
15
16
  const artifact = createArtifactRoute(projectRoot, mdxPath, config.docsDir);
16
17
  const srcDir = path.join(tmpDir, "src");
17
18
  const distDir = path.join(tmpDir, "dist");
18
19
  const entryPath = path.join(srcDir, "entry.tsx");
20
+ const tailwindSourcePath = path.join(srcDir, "artifact-tailwind-sources.css");
19
21
  const mdxImport = toRelativeImport(entryPath, mdxPath);
20
- const styleImports = createStyleImports(projectRoot, entryPath, config);
22
+ const styleImports = createStyleImports(projectRoot, entryPath, config, tailwindSourcePath);
21
23
  const reactEntryPath = await resolveReactEntryPath();
22
24
  const reactEntryImport = toRelativeImport(entryPath, reactEntryPath);
23
25
  const reactAliases = resolveReactAliases(projectRoot);
24
26
  await mkdir(srcDir, { recursive: true });
27
+ await writeFile(tailwindSourcePath, createTailwindSourceCss(projectRoot, tailwindSourcePath, mdxPath, config));
25
28
  await writeFile(path.join(tmpDir, "index.html"), `<!doctype html>
26
29
  <html lang="en">
27
30
  <head>
@@ -37,7 +40,7 @@ export async function createArtifactProject(projectRoot, mdxPath, config) {
37
40
  `);
38
41
  await writeFile(entryPath, `import React from "react";
39
42
  import { createRoot } from "react-dom/client";
40
- import { ArtifactStateProvider, CommentLayer } from "${reactEntryImport}";
43
+ import { ArtifactStateProvider, CommentLayer, artifactMdxComponents } from "${reactEntryImport}";
41
44
  import Doc from "${mdxImport}";
42
45
  ${styleImports}
43
46
 
@@ -47,7 +50,7 @@ function App() {
47
50
  <article className="ak-document">
48
51
  <ArtifactStateProvider>
49
52
  <CommentLayer>
50
- <Doc />
53
+ <Doc components={artifactMdxComponents} />
51
54
  </CommentLayer>
52
55
  </ArtifactStateProvider>
53
56
  </article>
@@ -98,7 +101,7 @@ createRoot(document.getElementById("root")!).render(<App />);
98
101
  }
99
102
  function artifactStatePlugin(projectRoot, artifact) {
100
103
  return {
101
- name: "artifact-kit-state",
104
+ name: "mdx-artifacts-state",
102
105
  configureServer(server) {
103
106
  server.middlewares.use(async (request, response, next) => {
104
107
  const requestUrl = new URL(request.url ?? "/", "http://localhost");
@@ -111,6 +114,10 @@ function artifactStatePlugin(projectRoot, artifact) {
111
114
  await handleArtifactState(projectRoot, artifact, request, response);
112
115
  return;
113
116
  }
117
+ if (requestUrl.pathname.startsWith("/__artifact/interactions/")) {
118
+ await handleArtifactInteraction(projectRoot, artifact, requestUrl.pathname, request, response);
119
+ return;
120
+ }
114
121
  }
115
122
  catch (error) {
116
123
  sendJson(response, 500, {
@@ -150,6 +157,143 @@ async function handleArtifactState(projectRoot, artifact, request, response) {
150
157
  }
151
158
  sendJson(response, 405, { error: "Method not allowed." });
152
159
  }
160
+ async function handleArtifactInteraction(projectRoot, artifact, pathname, request, response) {
161
+ if (request.method !== "POST") {
162
+ sendJson(response, 405, { error: "Method not allowed." });
163
+ return;
164
+ }
165
+ let value;
166
+ try {
167
+ value = JSON.parse(await readRequestBody(request));
168
+ }
169
+ catch {
170
+ sendJson(response, 400, { error: "Request body must be valid JSON." });
171
+ return;
172
+ }
173
+ try {
174
+ if (pathname === "/__artifact/interactions/set-order") {
175
+ const body = parseSetOrderRequest(value);
176
+ const result = await setInteractionOrderService(projectRoot, artifact.sourceRelativePath, body.id, body.orderedIds);
177
+ sendJson(response, 200, { ok: true, result, state: result.state });
178
+ return;
179
+ }
180
+ if (pathname === "/__artifact/interactions/reset") {
181
+ const body = parseInteractionIdRequest(value, "reset");
182
+ const result = await resetInteractionService(projectRoot, artifact.sourceRelativePath, body.id);
183
+ sendJson(response, 200, { ok: true, result, state: result.state });
184
+ return;
185
+ }
186
+ if (pathname === "/__artifact/interactions/promote") {
187
+ const body = parseInteractionIdRequest(value, "promote");
188
+ const result = await promoteInteractionService(projectRoot, artifact.sourceRelativePath, body.id);
189
+ sendJson(response, 200, { ok: true, result, state: result.state });
190
+ return;
191
+ }
192
+ if (pathname === "/__artifact/interactions/add-item") {
193
+ const body = parseAddItemRequest(value);
194
+ const result = await addInteractionItemService(projectRoot, artifact.sourceRelativePath, body.id, body.item, {
195
+ afterId: body.afterId
196
+ });
197
+ sendJson(response, 200, { ok: true, result, state: result.state });
198
+ return;
199
+ }
200
+ if (pathname === "/__artifact/interactions/remove-item") {
201
+ const body = parseItemIdRequest(value, "remove-item");
202
+ const result = await removeInteractionItemService(projectRoot, artifact.sourceRelativePath, body.id, body.itemId);
203
+ sendJson(response, 200, { ok: true, result, state: result.state });
204
+ return;
205
+ }
206
+ if (pathname === "/__artifact/interactions/update-item") {
207
+ const body = parseUpdateItemRequest(value);
208
+ const result = await updateInteractionItemService(projectRoot, artifact.sourceRelativePath, body.id, body.itemId, body.patch);
209
+ sendJson(response, 200, { ok: true, result, state: result.state });
210
+ return;
211
+ }
212
+ }
213
+ catch (error) {
214
+ sendJson(response, 400, { error: error instanceof Error ? error.message : String(error) });
215
+ return;
216
+ }
217
+ sendJson(response, 404, { error: "Interaction endpoint not found." });
218
+ }
219
+ function parseAddItemRequest(value) {
220
+ const body = parseInteractionIdRequest(value, "add-item");
221
+ if (!isRecord(value) || !isRecord(value.item)) {
222
+ throw new Error("interactions add-item requires item.");
223
+ }
224
+ if (typeof value.item.id !== "string" || !value.item.id) {
225
+ throw new Error("interactions add-item requires item.id.");
226
+ }
227
+ if (typeof value.item.title !== "string" || !value.item.title) {
228
+ throw new Error("interactions add-item requires item.title.");
229
+ }
230
+ return {
231
+ id: body.id,
232
+ item: normalizeItem(value.item),
233
+ afterId: typeof value.afterId === "string" && value.afterId ? value.afterId : undefined
234
+ };
235
+ }
236
+ function parseUpdateItemRequest(value) {
237
+ const body = parseItemIdRequest(value, "update-item");
238
+ if (!isRecord(value) || !isRecord(value.patch)) {
239
+ throw new Error("interactions update-item requires patch.");
240
+ }
241
+ return {
242
+ id: body.id,
243
+ itemId: body.itemId,
244
+ patch: normalizeItemPatch(value.patch)
245
+ };
246
+ }
247
+ function parseItemIdRequest(value, action) {
248
+ const body = parseInteractionIdRequest(value, action);
249
+ if (!isRecord(value) || typeof value.itemId !== "string" || !value.itemId) {
250
+ throw new Error(`interactions ${action} requires itemId.`);
251
+ }
252
+ return {
253
+ id: body.id,
254
+ itemId: value.itemId
255
+ };
256
+ }
257
+ function parseSetOrderRequest(value) {
258
+ const body = parseInteractionIdRequest(value, "set-order");
259
+ if (!isRecord(value) || !Array.isArray(value.orderedIds)) {
260
+ throw new Error("interactions set-order requires orderedIds.");
261
+ }
262
+ const orderedIds = value.orderedIds.filter((itemId) => typeof itemId === "string");
263
+ if (orderedIds.length !== value.orderedIds.length || orderedIds.length === 0) {
264
+ throw new Error("interactions set-order requires non-empty string orderedIds.");
265
+ }
266
+ return {
267
+ id: body.id,
268
+ orderedIds
269
+ };
270
+ }
271
+ function normalizeItem(value) {
272
+ return {
273
+ id: value.id,
274
+ title: value.title,
275
+ ...normalizeItemPatch(value)
276
+ };
277
+ }
278
+ function normalizeItemPatch(value) {
279
+ return {
280
+ ...(typeof value.title === "string" ? { title: value.title } : {}),
281
+ ...("summary" in value ? { summary: typeof value.summary === "string" ? value.summary : undefined } : {}),
282
+ ...("badge" in value ? { badge: typeof value.badge === "string" ? value.badge : undefined } : {}),
283
+ ...("tags" in value
284
+ ? { tags: Array.isArray(value.tags) ? value.tags.filter((tag) => typeof tag === "string") : undefined }
285
+ : {}),
286
+ ...(typeof value.disabled === "boolean" ? { disabled: value.disabled } : {})
287
+ };
288
+ }
289
+ function parseInteractionIdRequest(value, action) {
290
+ if (!isRecord(value) || typeof value.id !== "string" || !value.id) {
291
+ throw new Error(`interactions ${action} requires id.`);
292
+ }
293
+ return {
294
+ id: value.id
295
+ };
296
+ }
153
297
  function readRequestBody(request) {
154
298
  return new Promise((resolve, reject) => {
155
299
  let body = "";
@@ -166,9 +310,12 @@ function sendJson(response, statusCode, value) {
166
310
  response.setHeader("content-type", "application/json; charset=utf-8");
167
311
  response.end(JSON.stringify(value, null, 2));
168
312
  }
313
+ function isRecord(value) {
314
+ return typeof value === "object" && value !== null && !Array.isArray(value);
315
+ }
169
316
  async function resolveReactEntryPath() {
170
- const builtEntryPath = path.resolve(packageCliDir, "../react/index.js");
171
- const sourceEntryPath = path.resolve(packageCliDir, "../react/index.ts");
317
+ const builtEntryPath = path.resolve(packageCliDir, "../../react/index.js");
318
+ const sourceEntryPath = path.resolve(packageCliDir, "../../react/index.ts");
172
319
  try {
173
320
  await access(builtEntryPath);
174
321
  return builtEntryPath;
@@ -234,13 +381,26 @@ function toRelativeImport(fromFile, targetFile) {
234
381
  const relative = path.relative(path.dirname(fromFile), targetFile).replaceAll(path.sep, "/");
235
382
  return relative.startsWith(".") ? relative : `./${relative}`;
236
383
  }
237
- function createStyleImports(projectRoot, entryPath, config) {
384
+ function createStyleImports(projectRoot, entryPath, config, tailwindSourcePath) {
238
385
  const styles = [
386
+ tailwindSourcePath,
239
387
  ...(config.includeDefaultStyles ? [defaultStylesPath] : []),
240
388
  ...config.styles.map((stylePath) => path.resolve(projectRoot, stylePath))
241
389
  ];
242
390
  return styles.map((stylePath) => `import "${toRelativeImport(entryPath, stylePath)}";`).join("\n");
243
391
  }
392
+ function createTailwindSourceCss(projectRoot, sourceStylesPath, mdxPath, config) {
393
+ const sources = [
394
+ mdxPath,
395
+ ...config.tailwindSources.map((sourcePath) => path.resolve(projectRoot, sourcePath))
396
+ ];
397
+ return `@import "tailwindcss";\n${sources
398
+ .map((sourcePath) => `@source "${escapeCssString(toRelativeImport(sourceStylesPath, sourcePath))}";`)
399
+ .join("\n")}\n`;
400
+ }
401
+ function escapeCssString(value) {
402
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
403
+ }
244
404
  async function replaceAsync(source, pattern, replacer) {
245
405
  const matches = Array.from(source.matchAll(pattern));
246
406
  let output = source;
@@ -0,0 +1,11 @@
1
+ export type ArtifactDiagnostic = {
2
+ severity: "error" | "warning" | "info";
3
+ code: string;
4
+ message: string;
5
+ sourcePath: string;
6
+ line?: number;
7
+ componentName?: string;
8
+ propName?: string;
9
+ suggestion?: string;
10
+ example?: string;
11
+ };
@@ -0,0 +1 @@
1
+ export {};