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
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import path from "node:path";
3
- import { buildCommand } from "./build.js";
4
- import { componentsCommand } from "./components.js";
5
- import { devCommand } from "./dev.js";
6
- import { reviewCommand } from "./review.js";
7
- import { initProject } from "./scaffold.js";
8
- import { printValidationResult, validateMdx } from "./validate.js";
3
+ import { buildCommand } from "./commands/build.js";
4
+ import { componentsCommand } from "./commands/components.js";
5
+ import { devCommand } from "./commands/dev.js";
6
+ import { interactionsCommand } from "./commands/interactions.js";
7
+ import { reviewCommand } from "./commands/review.js";
8
+ import { initProject, parseInitOptions } from "./commands/scaffold.js";
9
+ import { formatValidationJson, printValidationResult, validateMdx } from "./commands/validate.js";
10
+ import { loadConfig } from "./config/config.js";
9
11
  const projectRoot = process.cwd();
10
12
  const [command, input] = process.argv.slice(2);
11
13
  async function main() {
@@ -14,7 +16,7 @@ async function main() {
14
16
  return;
15
17
  }
16
18
  if (command === "init") {
17
- await initProject(projectRoot);
19
+ await initProject(projectRoot, await parseInitOptions(process.argv.slice(3)));
18
20
  return;
19
21
  }
20
22
  if (command === "components") {
@@ -28,12 +30,24 @@ async function main() {
28
30
  await reviewCommand(projectRoot, process.argv.slice(3));
29
31
  return;
30
32
  }
33
+ if (command === "interactions") {
34
+ await interactionsCommand(projectRoot, process.argv.slice(3));
35
+ return;
36
+ }
31
37
  if (!input) {
32
38
  throw new Error(`${command} requires a .mdx file path.`);
33
39
  }
34
40
  if (command === "validate") {
35
- const result = await validateMdx(path.resolve(projectRoot, input));
36
- printValidationResult(result);
41
+ const args = process.argv.slice(3);
42
+ const json = args.includes("--json");
43
+ const config = await loadConfig(projectRoot);
44
+ const result = await validateMdx(path.resolve(projectRoot, input), { projectRoot, config });
45
+ if (json) {
46
+ console.log(JSON.stringify(formatValidationJson(result), null, 2));
47
+ }
48
+ else {
49
+ printValidationResult(result);
50
+ }
37
51
  if (result.errors.length > 0) {
38
52
  process.exitCode = 1;
39
53
  }
@@ -50,17 +64,24 @@ async function main() {
50
64
  throw new Error(`Unknown command: ${command}`);
51
65
  }
52
66
  function printHelp() {
53
- console.log(`artifact-kit
67
+ console.log(`mdx-artifacts
54
68
 
55
69
  Usage:
56
- artifact-kit init
57
- artifact-kit components [ComponentName] [--json]
58
- artifact-kit validate <file.mdx>
59
- artifact-kit review add <file.mdx> --anchor <anchorId> --body <message> [--title <title>]
60
- artifact-kit review reply <file.mdx> --thread <threadId> --body <message> [...repeat] [--status <status>]
61
- artifact-kit review validate <file.mdx>
62
- artifact-kit dev <file.mdx>
63
- artifact-kit build <file.mdx>
70
+ mdx-artifacts init [--yes] [--docs-dir <dir>] [--components-dir <dir>] [--agent <generic|codex|claude-code|cursor|all>]
71
+ mdx-artifacts components [ComponentName] [--json]
72
+ mdx-artifacts validate <file.mdx> [--json]
73
+ mdx-artifacts interactions inspect <file.mdx> <id> [--json]
74
+ mdx-artifacts interactions set-order <file.mdx> <id> --ordered-ids <id> [...id]
75
+ mdx-artifacts interactions reset <file.mdx> <id>
76
+ mdx-artifacts interactions promote <file.mdx> <id>
77
+ mdx-artifacts interactions add-item <file.mdx> <id> --item-id <id> --title <title> [--summary <text>] [--badge <text>] [--tags <tag,tag>] [--disabled true|false] [--after <itemId>]
78
+ mdx-artifacts interactions remove-item <file.mdx> <id> --item-id <id>
79
+ mdx-artifacts interactions update-item <file.mdx> <id> --item-id <id> [--title <title>] [--summary <text>] [--badge <text>] [--tags <tag,tag>] [--disabled true|false]
80
+ mdx-artifacts review add <file.mdx> --anchor <anchorId> --body <message> [--title <title>]
81
+ mdx-artifacts review reply <file.mdx> --thread <threadId> --body <message> [...repeat] [--status <status>]
82
+ mdx-artifacts review validate <file.mdx>
83
+ mdx-artifacts dev <file.mdx>
84
+ mdx-artifacts build <file.mdx>
64
85
  `);
65
86
  }
66
87
  main().catch((error) => {
@@ -0,0 +1,14 @@
1
+ import type { SortableListItem } from "../../react";
2
+ export type SortableListSeed = {
3
+ id: string;
4
+ title: string;
5
+ summary?: string;
6
+ items: SortableListItem[];
7
+ };
8
+ export declare function extractSortableListSeeds(source: string): SortableListSeed[];
9
+ export declare function promoteSortableListOrder(source: string, listId: string, orderedIds: string[]): string;
10
+ export declare function addSortableListItem(source: string, listId: string, item: SortableListItem, options?: {
11
+ afterId?: string;
12
+ }): string;
13
+ export declare function removeSortableListItem(source: string, listId: string, itemId: string): string;
14
+ export declare function updateSortableListItem(source: string, listId: string, itemId: string, patch: Partial<Omit<SortableListItem, "id">>): string;
@@ -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
+ }