mdx-artifacts 0.1.1 → 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 (142) hide show
  1. package/README.md +224 -60
  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/dev-server/vite-artifact.js +415 -0
  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/cli/vite-artifact.js +0 -237
  117. package/dist/lib/react/components/Callout.d.ts +0 -9
  118. package/dist/lib/react/components/CodeBlock.d.ts +0 -10
  119. package/dist/lib/react/components/CodeBlock.js +0 -28
  120. package/dist/lib/react/components/DecisionMatrix.d.ts +0 -16
  121. package/dist/lib/react/components/DecisionMatrix.js +0 -27
  122. package/dist/lib/react/components/DiffBlock.js +0 -24
  123. package/dist/lib/react/components/InlineText.js +0 -18
  124. package/dist/lib/react/components/MarkdownBody.d.ts +0 -7
  125. package/dist/lib/react/components/MarkdownBody.js +0 -36
  126. package/dist/lib/react/components/OptionGrid.d.ts +0 -13
  127. package/dist/lib/react/components/OptionGrid.js +0 -21
  128. /package/dist/lib/cli/{build.d.ts → commands/build.d.ts} +0 -0
  129. /package/dist/lib/cli/{components.d.ts → commands/components.d.ts} +0 -0
  130. /package/dist/lib/cli/{dev.d.ts → commands/dev.d.ts} +0 -0
  131. /package/dist/lib/cli/{types.js → config/types.js} +0 -0
  132. /package/dist/lib/cli/{artifact-state.d.ts → state/artifact-state.d.ts} +0 -0
  133. /package/dist/lib/cli/{artifact-state.js → state/artifact-state.js} +0 -0
  134. /package/dist/lib/react/{components → composites/export-panel}/ExportPanel.d.ts +0 -0
  135. /package/dist/lib/react/{components → composites/section}/Section.d.ts +0 -0
  136. /package/dist/lib/react/{components → interactions/artifact-state}/ArtifactState.d.ts +0 -0
  137. /package/dist/lib/react/{components → interactions/artifact-state}/ArtifactState.js +0 -0
  138. /package/dist/lib/react/{components → layout/layout-primitives}/Layout.d.ts +0 -0
  139. /package/dist/lib/react/{components → layout/layout-primitives}/Layout.js +0 -0
  140. /package/dist/lib/react/{components → primitives/diff-block}/DiffBlock.d.ts +0 -0
  141. /package/dist/lib/react/{components → primitives/severity-badge}/SeverityBadge.d.ts +0 -0
  142. /package/dist/lib/react/{components → primitives/severity-badge}/SeverityBadge.js +0 -0
@@ -0,0 +1,520 @@
1
+ export function extractSortableListSeeds(source) {
2
+ return findJsxOpeningTags(source, "SortableList").map((tagSource) => parseSortableListTag(tagSource));
3
+ }
4
+ export function promoteSortableListOrder(source, listId, orderedIds) {
5
+ const target = findSortableListTags(source).find((tag) => extractStringProp(tag.source, "id") === listId);
6
+ if (!target) {
7
+ throw new Error(`SortableList not found: ${listId}.`);
8
+ }
9
+ const itemsExpression = extractExpressionPropRange(target.source, "items");
10
+ if (!itemsExpression) {
11
+ throw new Error(`SortableList ${listId} is missing required items prop.`);
12
+ }
13
+ const expression = target.source.slice(itemsExpression.start, itemsExpression.end);
14
+ const currentItems = parseSortableListItems(expression, listId);
15
+ const objectSpans = extractTopLevelObjectSpans(expression);
16
+ if (objectSpans.length !== currentItems.length) {
17
+ throw new Error(`SortableList ${listId} items must be a static object array.`);
18
+ }
19
+ const chunksById = new Map(currentItems.map((item, index) => [item.id, expression.slice(objectSpans[index].start, objectSpans[index].end)]));
20
+ const firstSpan = objectSpans[0];
21
+ const lastSpan = objectSpans[objectSpans.length - 1];
22
+ if (!firstSpan || !lastSpan) {
23
+ throw new Error(`SortableList ${listId} items must contain at least one item.`);
24
+ }
25
+ const separator = objectSpans.length > 1 ? expression.slice(objectSpans[0].end, objectSpans[1].start) : ",\n";
26
+ const nextExpression = [
27
+ expression.slice(0, firstSpan.start),
28
+ orderedIds.map((itemId) => {
29
+ const chunk = chunksById.get(itemId);
30
+ if (!chunk) {
31
+ throw new Error(`SortableList ${listId} item not found: ${itemId}.`);
32
+ }
33
+ return chunk;
34
+ }).join(separator),
35
+ expression.slice(lastSpan.end)
36
+ ].join("");
37
+ const absoluteStart = target.start + itemsExpression.start;
38
+ const absoluteEnd = target.start + itemsExpression.end;
39
+ return `${source.slice(0, absoluteStart)}${nextExpression}${source.slice(absoluteEnd)}`;
40
+ }
41
+ export function addSortableListItem(source, listId, item, options = {}) {
42
+ const context = createSortableListArrayContext(source, listId);
43
+ if (context.currentItems.some((currentItem) => currentItem.id === item.id)) {
44
+ throw new Error(`SortableList ${listId} already contains item: ${item.id}.`);
45
+ }
46
+ const objectChunk = formatSortableListItem(item);
47
+ const separator = createItemSeparator(context.expression, context.objectSpans);
48
+ const insertIndex = options.afterId
49
+ ? context.currentItems.findIndex((currentItem) => currentItem.id === options.afterId)
50
+ : context.currentItems.length - 1;
51
+ if (options.afterId && insertIndex < 0) {
52
+ throw new Error(`SortableList ${listId} item not found: ${options.afterId}.`);
53
+ }
54
+ const nextExpression = context.objectSpans.length === 0
55
+ ? insertFirstItem(context.expression, objectChunk)
56
+ : insertItemAfterSpan(context.expression, context.objectSpans[insertIndex], separator, objectChunk);
57
+ return replaceExpression(source, context, nextExpression);
58
+ }
59
+ export function removeSortableListItem(source, listId, itemId) {
60
+ const context = createSortableListArrayContext(source, listId);
61
+ const itemIndex = context.currentItems.findIndex((item) => item.id === itemId);
62
+ if (itemIndex < 0) {
63
+ throw new Error(`SortableList ${listId} item not found: ${itemId}.`);
64
+ }
65
+ const nextExpression = removeItemSpan(context.expression, context.objectSpans, itemIndex);
66
+ return replaceExpression(source, context, nextExpression);
67
+ }
68
+ export function updateSortableListItem(source, listId, itemId, patch) {
69
+ const context = createSortableListArrayContext(source, listId);
70
+ const itemIndex = context.currentItems.findIndex((item) => item.id === itemId);
71
+ if (itemIndex < 0) {
72
+ throw new Error(`SortableList ${listId} item not found: ${itemId}.`);
73
+ }
74
+ const nextItem = {
75
+ ...context.currentItems[itemIndex],
76
+ ...patch
77
+ };
78
+ const span = context.objectSpans[itemIndex];
79
+ const nextExpression = `${context.expression.slice(0, span.start)}${formatSortableListItem(nextItem)}${context.expression.slice(span.end)}`;
80
+ return replaceExpression(source, context, nextExpression);
81
+ }
82
+ function parseSortableListTag(tagSource) {
83
+ const id = extractStringProp(tagSource, "id");
84
+ const title = extractStringProp(tagSource, "title");
85
+ const summary = extractStringProp(tagSource, "summary");
86
+ const itemsExpression = extractExpressionProp(tagSource, "items");
87
+ if (!id) {
88
+ throw new Error("SortableList is missing required id prop.");
89
+ }
90
+ if (!title) {
91
+ throw new Error(`SortableList ${id} is missing required title prop.`);
92
+ }
93
+ if (!itemsExpression) {
94
+ throw new Error(`SortableList ${id} is missing required items prop.`);
95
+ }
96
+ return {
97
+ id,
98
+ title,
99
+ ...(summary ? { summary } : {}),
100
+ items: parseSortableListItems(itemsExpression, id)
101
+ };
102
+ }
103
+ function createSortableListArrayContext(source, listId) {
104
+ const target = findSortableListTags(source).find((tag) => extractStringProp(tag.source, "id") === listId);
105
+ if (!target) {
106
+ throw new Error(`SortableList not found: ${listId}.`);
107
+ }
108
+ const itemsExpression = extractExpressionPropRange(target.source, "items");
109
+ if (!itemsExpression) {
110
+ throw new Error(`SortableList ${listId} is missing required items prop.`);
111
+ }
112
+ const expression = target.source.slice(itemsExpression.start, itemsExpression.end);
113
+ const currentItems = parseSortableListItems(expression, listId);
114
+ const objectSpans = extractTopLevelObjectSpans(expression);
115
+ if (objectSpans.length !== currentItems.length) {
116
+ throw new Error(`SortableList ${listId} items must be a static object array.`);
117
+ }
118
+ return {
119
+ absoluteEnd: target.start + itemsExpression.end,
120
+ absoluteStart: target.start + itemsExpression.start,
121
+ currentItems,
122
+ expression,
123
+ objectSpans
124
+ };
125
+ }
126
+ function replaceExpression(source, context, nextExpression) {
127
+ return `${source.slice(0, context.absoluteStart)}${nextExpression}${source.slice(context.absoluteEnd)}`;
128
+ }
129
+ function formatSortableListItem(item) {
130
+ const fields = [
131
+ `id: ${quoteString(item.id)}`,
132
+ `title: ${quoteString(item.title)}`,
133
+ item.summary !== undefined ? `summary: ${quoteString(item.summary)}` : undefined,
134
+ item.badge !== undefined ? `badge: ${quoteString(item.badge)}` : undefined,
135
+ item.tags !== undefined ? `tags: [${item.tags.map(quoteString).join(", ")}]` : undefined,
136
+ item.disabled !== undefined ? `disabled: ${String(item.disabled)}` : undefined
137
+ ].filter(Boolean);
138
+ return `{ ${fields.join(", ")} }`;
139
+ }
140
+ function quoteString(value) {
141
+ return JSON.stringify(value);
142
+ }
143
+ function createItemSeparator(expression, objectSpans) {
144
+ if (objectSpans.length > 1) {
145
+ return expression.slice(objectSpans[0].end, objectSpans[1].start);
146
+ }
147
+ const leadingWhitespace = expression.slice(0, objectSpans[0]?.start ?? 0).match(/\n([ \t]*)$/)?.[1];
148
+ return `,\n${leadingWhitespace ?? " "}`;
149
+ }
150
+ function insertFirstItem(expression, objectChunk) {
151
+ const arrayStart = expression.indexOf("[");
152
+ const arrayEnd = expression.lastIndexOf("]");
153
+ if (arrayStart < 0 || arrayEnd < arrayStart) {
154
+ throw new Error("SortableList items must be a static object array.");
155
+ }
156
+ const indentation = expression.slice(0, arrayEnd).match(/\n([ \t]*)$/)?.[1] ?? " ";
157
+ return `${expression.slice(0, arrayStart + 1)}\n${indentation} ${objectChunk}\n${indentation}${expression.slice(arrayEnd)}`;
158
+ }
159
+ function insertItemAfterSpan(expression, span, separator, objectChunk) {
160
+ return `${expression.slice(0, span.end)}${separator}${objectChunk}${expression.slice(span.end)}`;
161
+ }
162
+ function removeItemSpan(expression, objectSpans, itemIndex) {
163
+ const currentSpan = objectSpans[itemIndex];
164
+ const previousSpan = objectSpans[itemIndex - 1];
165
+ const nextSpan = objectSpans[itemIndex + 1];
166
+ if (!previousSpan && !nextSpan) {
167
+ return `${expression.slice(0, currentSpan.start)}${expression.slice(currentSpan.end)}`;
168
+ }
169
+ if (nextSpan) {
170
+ return `${expression.slice(0, currentSpan.start)}${expression.slice(nextSpan.start)}`;
171
+ }
172
+ return `${expression.slice(0, previousSpan.end)}${expression.slice(currentSpan.end)}`;
173
+ }
174
+ function parseSortableListItems(expression, listId) {
175
+ const parser = new StaticExpressionParser(expression);
176
+ const value = parser.parse();
177
+ if (!Array.isArray(value)) {
178
+ throw new Error(`SortableList ${listId} items must be a static object array.`);
179
+ }
180
+ return value.map((item, index) => normalizeSortableListItem(item, listId, index));
181
+ }
182
+ function normalizeSortableListItem(value, listId, index) {
183
+ if (!isRecord(value)) {
184
+ throw new Error(`SortableList ${listId} item at index ${index} must be an object.`);
185
+ }
186
+ if (typeof value.id !== "string" || !value.id) {
187
+ throw new Error(`SortableList ${listId} item at index ${index} is missing string id.`);
188
+ }
189
+ if (typeof value.title !== "string" || !value.title) {
190
+ throw new Error(`SortableList ${listId} item ${value.id} is missing string title.`);
191
+ }
192
+ return {
193
+ id: value.id,
194
+ title: value.title,
195
+ ...(typeof value.summary === "string" ? { summary: value.summary } : {}),
196
+ ...(typeof value.badge === "string" ? { badge: value.badge } : {}),
197
+ ...(Array.isArray(value.tags) ? { tags: value.tags.filter((tag) => typeof tag === "string") } : {}),
198
+ ...(typeof value.disabled === "boolean" ? { disabled: value.disabled } : {})
199
+ };
200
+ }
201
+ function findJsxOpeningTags(source, componentName) {
202
+ return findJsxOpeningTagSources(source, componentName).map((tag) => tag.source);
203
+ }
204
+ function findSortableListTags(source) {
205
+ return findJsxOpeningTagSources(source, "SortableList");
206
+ }
207
+ function findJsxOpeningTagSources(source, componentName) {
208
+ const tags = [];
209
+ const locatedTags = [];
210
+ const startPattern = new RegExp(`<${componentName}(?=[\\s>/])`, "g");
211
+ for (const match of source.matchAll(startPattern)) {
212
+ const start = match.index ?? 0;
213
+ const end = findJsxOpeningTagEnd(source, start);
214
+ if (end >= 0) {
215
+ const tagSource = source.slice(start, end + 1);
216
+ tags.push(tagSource);
217
+ locatedTags.push({ source: tagSource, start, end: end + 1 });
218
+ }
219
+ }
220
+ return locatedTags;
221
+ }
222
+ function findJsxOpeningTagEnd(source, start) {
223
+ const scanner = new SourceScanner(source, start);
224
+ let braceDepth = 0;
225
+ while (!scanner.done()) {
226
+ const char = scanner.current();
227
+ if (scanner.consumeString()) {
228
+ continue;
229
+ }
230
+ if (char === "{") {
231
+ braceDepth += 1;
232
+ }
233
+ else if (char === "}") {
234
+ braceDepth -= 1;
235
+ }
236
+ else if (char === ">" && braceDepth === 0) {
237
+ return scanner.index;
238
+ }
239
+ scanner.advance();
240
+ }
241
+ return -1;
242
+ }
243
+ function extractStringProp(source, propName) {
244
+ const direct = source.match(new RegExp(`\\b${propName}\\s*=\\s*["']([^"']*)["']`))?.[1];
245
+ if (direct !== undefined) {
246
+ return direct;
247
+ }
248
+ return source.match(new RegExp(`\\b${propName}\\s*=\\s*\\{\\s*["']([^"']*)["']\\s*\\}`))?.[1];
249
+ }
250
+ function extractExpressionProp(source, propName) {
251
+ const range = extractExpressionPropRange(source, propName);
252
+ return range ? source.slice(range.start, range.end) : undefined;
253
+ }
254
+ function extractExpressionPropRange(source, propName) {
255
+ const propStart = source.search(new RegExp(`\\b${propName}\\s*=\\s*\\{`));
256
+ if (propStart < 0) {
257
+ return undefined;
258
+ }
259
+ const expressionStart = source.indexOf("{", propStart);
260
+ const expressionEnd = findMatchingBrace(source, expressionStart);
261
+ if (expressionEnd < 0) {
262
+ return undefined;
263
+ }
264
+ return {
265
+ start: expressionStart + 1,
266
+ end: expressionEnd
267
+ };
268
+ }
269
+ function findMatchingBrace(source, start) {
270
+ const scanner = new SourceScanner(source, start);
271
+ let depth = 0;
272
+ while (!scanner.done()) {
273
+ const char = scanner.current();
274
+ if (scanner.consumeString()) {
275
+ continue;
276
+ }
277
+ if (char === "{") {
278
+ depth += 1;
279
+ }
280
+ else if (char === "}") {
281
+ depth -= 1;
282
+ if (depth === 0) {
283
+ return scanner.index;
284
+ }
285
+ }
286
+ scanner.advance();
287
+ }
288
+ return -1;
289
+ }
290
+ function extractTopLevelObjectSpans(expression) {
291
+ const trimmedStart = expression.search(/\S/);
292
+ if (trimmedStart < 0 || expression[trimmedStart] !== "[") {
293
+ throw new Error("SortableList items must be a static object array.");
294
+ }
295
+ const spans = [];
296
+ const scanner = new SourceScanner(expression, trimmedStart + 1);
297
+ let arrayDepth = 1;
298
+ let braceDepth = 0;
299
+ while (!scanner.done() && arrayDepth > 0) {
300
+ const char = scanner.current();
301
+ if (scanner.consumeString()) {
302
+ continue;
303
+ }
304
+ if (char === "[" && braceDepth === 0) {
305
+ arrayDepth += 1;
306
+ scanner.advance();
307
+ continue;
308
+ }
309
+ if (char === "]" && braceDepth === 0) {
310
+ arrayDepth -= 1;
311
+ scanner.advance();
312
+ continue;
313
+ }
314
+ if (char === "{" && arrayDepth === 1 && braceDepth === 0) {
315
+ const start = scanner.index;
316
+ const end = findMatchingBrace(expression, scanner.index);
317
+ if (end < 0) {
318
+ throw new Error("SortableList items contain an unterminated object.");
319
+ }
320
+ spans.push({ start, end: end + 1 });
321
+ scanner.index = end + 1;
322
+ continue;
323
+ }
324
+ if (char === "{") {
325
+ braceDepth += 1;
326
+ }
327
+ else if (char === "}") {
328
+ braceDepth -= 1;
329
+ }
330
+ scanner.advance();
331
+ }
332
+ return spans;
333
+ }
334
+ class StaticExpressionParser {
335
+ source;
336
+ index = 0;
337
+ constructor(source) {
338
+ this.source = source;
339
+ }
340
+ parse() {
341
+ const value = this.parseValue();
342
+ this.skipWhitespace();
343
+ if (!this.done()) {
344
+ throw new Error("SortableList items must use a static object array.");
345
+ }
346
+ return value;
347
+ }
348
+ parseValue() {
349
+ this.skipWhitespace();
350
+ const char = this.current();
351
+ if (char === "[" || char === "{") {
352
+ return char === "[" ? this.parseArray() : this.parseObject();
353
+ }
354
+ if (char === '"' || char === "'" || char === "`") {
355
+ return this.parseString();
356
+ }
357
+ if (this.source.startsWith("true", this.index)) {
358
+ this.index += 4;
359
+ return true;
360
+ }
361
+ if (this.source.startsWith("false", this.index)) {
362
+ this.index += 5;
363
+ return false;
364
+ }
365
+ if (this.source.startsWith("null", this.index)) {
366
+ this.index += 4;
367
+ return null;
368
+ }
369
+ throw new Error("SortableList items must use only static objects, arrays, strings, booleans, or null.");
370
+ }
371
+ parseArray() {
372
+ const values = [];
373
+ this.expect("[");
374
+ this.skipWhitespace();
375
+ while (this.current() !== "]") {
376
+ values.push(this.parseValue());
377
+ this.skipWhitespace();
378
+ if (this.current() === ",") {
379
+ this.index += 1;
380
+ this.skipWhitespace();
381
+ }
382
+ else if (this.current() !== "]") {
383
+ throw new Error("SortableList items array contains unsupported syntax.");
384
+ }
385
+ }
386
+ this.expect("]");
387
+ return values;
388
+ }
389
+ parseObject() {
390
+ const value = {};
391
+ this.expect("{");
392
+ this.skipWhitespace();
393
+ while (this.current() !== "}") {
394
+ const key = this.parseObjectKey();
395
+ this.skipWhitespace();
396
+ this.expect(":");
397
+ value[key] = this.parseValue();
398
+ this.skipWhitespace();
399
+ if (this.current() === ",") {
400
+ this.index += 1;
401
+ this.skipWhitespace();
402
+ }
403
+ else if (this.current() !== "}") {
404
+ throw new Error("SortableList item object contains unsupported syntax.");
405
+ }
406
+ }
407
+ this.expect("}");
408
+ return value;
409
+ }
410
+ parseObjectKey() {
411
+ this.skipWhitespace();
412
+ const char = this.current();
413
+ if (char === '"' || char === "'") {
414
+ return this.parseString();
415
+ }
416
+ const match = this.source.slice(this.index).match(/^[A-Za-z_$][A-Za-z0-9_$-]*/);
417
+ if (!match) {
418
+ throw new Error("SortableList item object contains an unsupported key.");
419
+ }
420
+ this.index += match[0].length;
421
+ return match[0];
422
+ }
423
+ parseString() {
424
+ const quote = this.current();
425
+ let value = "";
426
+ this.index += 1;
427
+ while (!this.done()) {
428
+ const char = this.current();
429
+ if (char === quote) {
430
+ this.index += 1;
431
+ return value;
432
+ }
433
+ if (quote === "`" && char === "$" && this.source[this.index + 1] === "{") {
434
+ throw new Error("SortableList items do not support template interpolation.");
435
+ }
436
+ if (char === "\\") {
437
+ value += this.parseEscape();
438
+ }
439
+ else {
440
+ value += char;
441
+ this.index += 1;
442
+ }
443
+ }
444
+ throw new Error("SortableList items contain an unterminated string.");
445
+ }
446
+ parseEscape() {
447
+ const next = this.source[this.index + 1];
448
+ if (next === undefined) {
449
+ throw new Error("SortableList items contain an invalid string escape.");
450
+ }
451
+ this.index += 2;
452
+ if (next === "n")
453
+ return "\n";
454
+ if (next === "r")
455
+ return "\r";
456
+ if (next === "t")
457
+ return "\t";
458
+ return next;
459
+ }
460
+ expect(char) {
461
+ if (this.current() !== char) {
462
+ throw new Error(`Expected ${char} in SortableList items.`);
463
+ }
464
+ this.index += 1;
465
+ }
466
+ skipWhitespace() {
467
+ while (!this.done() && /\s/.test(this.current())) {
468
+ this.index += 1;
469
+ }
470
+ }
471
+ current() {
472
+ return this.source[this.index] ?? "";
473
+ }
474
+ done() {
475
+ return this.index >= this.source.length;
476
+ }
477
+ }
478
+ class SourceScanner {
479
+ source;
480
+ index;
481
+ constructor(source, index) {
482
+ this.source = source;
483
+ this.index = index;
484
+ }
485
+ consumeString() {
486
+ const quote = this.current();
487
+ if (quote !== '"' && quote !== "'" && quote !== "`") {
488
+ return false;
489
+ }
490
+ this.index += 1;
491
+ while (!this.done()) {
492
+ const char = this.current();
493
+ if (char === "\\") {
494
+ this.index += 2;
495
+ continue;
496
+ }
497
+ if (quote === "`" && char === "$" && this.source[this.index + 1] === "{") {
498
+ this.index += 2;
499
+ continue;
500
+ }
501
+ this.index += 1;
502
+ if (char === quote) {
503
+ break;
504
+ }
505
+ }
506
+ return true;
507
+ }
508
+ current() {
509
+ return this.source[this.index] ?? "";
510
+ }
511
+ advance() {
512
+ this.index += 1;
513
+ }
514
+ done() {
515
+ return this.index >= this.source.length;
516
+ }
517
+ }
518
+ function isRecord(value) {
519
+ return typeof value === "object" && value !== null && !Array.isArray(value);
520
+ }
@@ -0,0 +1,15 @@
1
+ import type { ArtifactDiagnostic } from "../diagnostics/diagnostics";
2
+ export declare const resourceTypes: readonly ["style", "data", "artifact", "component-module"];
3
+ export type ResourceType = (typeof resourceTypes)[number];
4
+ export type ResourceReference = {
5
+ type: ResourceType;
6
+ path: string;
7
+ sourcePath: string;
8
+ fieldName?: string;
9
+ mustExist?: boolean;
10
+ };
11
+ export type ResourcePolicyInput = {
12
+ projectRoot: string;
13
+ references: ResourceReference[];
14
+ };
15
+ export declare function validateResourceReferences(input: ResourcePolicyInput): Promise<ArtifactDiagnostic[]>;
@@ -0,0 +1,46 @@
1
+ import { access } from "node:fs/promises";
2
+ import { resolveSafeProjectPath } from "./safe-path.js";
3
+ export const resourceTypes = ["style", "data", "artifact", "component-module"];
4
+ export async function validateResourceReferences(input) {
5
+ const diagnostics = [];
6
+ for (const reference of input.references) {
7
+ const safePath = await resolveSafeProjectPath(input.projectRoot, reference.path, { realpath: true });
8
+ if (!safePath.ok) {
9
+ diagnostics.push({
10
+ severity: "error",
11
+ code: safePath.code,
12
+ message: resourceMessage(reference, safePath.message),
13
+ sourcePath: reference.sourcePath,
14
+ propName: reference.fieldName,
15
+ suggestion: safePath.suggestion
16
+ });
17
+ continue;
18
+ }
19
+ if (reference.mustExist === false) {
20
+ continue;
21
+ }
22
+ try {
23
+ await access(safePath.path);
24
+ }
25
+ catch {
26
+ diagnostics.push({
27
+ severity: "error",
28
+ code: "resource_not_found",
29
+ message: `${resourceLabel(reference.type)} not found: ${reference.path}`,
30
+ sourcePath: reference.sourcePath,
31
+ propName: reference.fieldName,
32
+ suggestion: "Confirm the resource path exists relative to the project root."
33
+ });
34
+ }
35
+ }
36
+ return diagnostics;
37
+ }
38
+ function resourceMessage(reference, message) {
39
+ return message.replace("resources", `${resourceLabel(reference.type)}s`).replace("Resource path", resourceLabel(reference.type));
40
+ }
41
+ function resourceLabel(type) {
42
+ if (type === "component-module") {
43
+ return "component module resource";
44
+ }
45
+ return `${type} resource`;
46
+ }
@@ -0,0 +1,13 @@
1
+ export type SafeProjectPathResult = {
2
+ ok: true;
3
+ path: string;
4
+ } | {
5
+ ok: false;
6
+ code: "remote_resource_blocked" | "home_resource_blocked" | "resource_path_escape";
7
+ message: string;
8
+ suggestion: string;
9
+ };
10
+ export type SafeProjectPathOptions = {
11
+ realpath?: boolean;
12
+ };
13
+ export declare function resolveSafeProjectPath(projectRoot: string, resourcePath: string, options?: SafeProjectPathOptions): Promise<SafeProjectPathResult>;
@@ -0,0 +1,55 @@
1
+ import { realpath } from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function resolveSafeProjectPath(projectRoot, resourcePath, options = {}) {
4
+ if (isUrlLike(resourcePath)) {
5
+ return {
6
+ ok: false,
7
+ code: "remote_resource_blocked",
8
+ message: `Remote resources are not allowed: ${resourcePath}`,
9
+ suggestion: "Use a local project-relative resource path."
10
+ };
11
+ }
12
+ if (resourcePath === "~" || resourcePath.startsWith("~/")) {
13
+ return {
14
+ ok: false,
15
+ code: "home_resource_blocked",
16
+ message: `Home-relative resources are not allowed: ${resourcePath}`,
17
+ suggestion: "Move the resource inside the project and reference it with a project-relative path."
18
+ };
19
+ }
20
+ const rootPath = path.resolve(projectRoot);
21
+ const resolvedPath = path.resolve(rootPath, resourcePath);
22
+ if (!isInsidePath(rootPath, resolvedPath)) {
23
+ return {
24
+ ok: false,
25
+ code: "resource_path_escape",
26
+ message: `Resource path must stay inside the project root: ${resourcePath}`,
27
+ suggestion: "Move the resource inside the project root before referencing it."
28
+ };
29
+ }
30
+ if (!options.realpath) {
31
+ return { ok: true, path: resolvedPath };
32
+ }
33
+ try {
34
+ const [realRootPath, realResolvedPath] = await Promise.all([realpath(rootPath), realpath(resolvedPath)]);
35
+ if (!isInsidePath(realRootPath, realResolvedPath)) {
36
+ return {
37
+ ok: false,
38
+ code: "resource_path_escape",
39
+ message: `Resource path must stay inside the project root: ${resourcePath}`,
40
+ suggestion: "Do not reference symlinks that resolve outside the project root."
41
+ };
42
+ }
43
+ return { ok: true, path: realResolvedPath };
44
+ }
45
+ catch {
46
+ return { ok: true, path: resolvedPath };
47
+ }
48
+ }
49
+ function isUrlLike(resourcePath) {
50
+ return /^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(resourcePath) || /^(data|blob):/.test(resourcePath) || resourcePath.startsWith("//");
51
+ }
52
+ function isInsidePath(rootPath, candidatePath) {
53
+ const relativePath = path.relative(rootPath, candidatePath);
54
+ return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
55
+ }
@@ -0,0 +1,40 @@
1
+ import { type ArtifactState } from "../state/artifact-state";
2
+ import { type SortableListSeed } from "../mdx/sortable-list";
3
+ import type { SortableListItem } from "../../react";
4
+ export type InteractionsInspectResult = {
5
+ component: SortableListSeed;
6
+ order: {
7
+ source: "mdx" | "state";
8
+ orderedIds: string[];
9
+ staleIds: string[];
10
+ appendedIds: string[];
11
+ };
12
+ state: {
13
+ path: string;
14
+ exists: boolean;
15
+ };
16
+ source: {
17
+ path: string;
18
+ };
19
+ warnings: string[];
20
+ };
21
+ export type InteractionMutationResult = {
22
+ action: string;
23
+ component: string;
24
+ sourcePath: string;
25
+ statePath: string;
26
+ state: ArtifactState;
27
+ itemId?: string;
28
+ orderedIds?: string[];
29
+ status?: "ok" | "noop";
30
+ reason?: string;
31
+ };
32
+ export declare function inspectInteractionService(projectRoot: string, input: string, id: string): Promise<InteractionsInspectResult>;
33
+ export declare function setInteractionOrderService(projectRoot: string, input: string, id: string, orderedIds: string[]): Promise<InteractionMutationResult>;
34
+ export declare function resetInteractionService(projectRoot: string, input: string, id: string): Promise<InteractionMutationResult>;
35
+ export declare function promoteInteractionService(projectRoot: string, input: string, id: string): Promise<InteractionMutationResult>;
36
+ export declare function addInteractionItemService(projectRoot: string, input: string, id: string, item: SortableListItem, options?: {
37
+ afterId?: string;
38
+ }): Promise<InteractionMutationResult>;
39
+ export declare function removeInteractionItemService(projectRoot: string, input: string, id: string, itemId: string): Promise<InteractionMutationResult>;
40
+ export declare function updateInteractionItemService(projectRoot: string, input: string, id: string, itemId: string, patch: Partial<Omit<SortableListItem, "id">>): Promise<InteractionMutationResult>;