@trojanbox-vcp-test/site-edit-engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +85 -0
  2. package/dist/execute-integration/execute-fixture-harness.d.ts +25 -0
  3. package/dist/execute-integration/execute-fixture-harness.js +37 -0
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.js +2 -0
  6. package/dist/internal/ast/diagnostics/index.d.ts +5 -0
  7. package/dist/internal/ast/diagnostics/index.js +25 -0
  8. package/dist/internal/ast/history/index.d.ts +15 -0
  9. package/dist/internal/ast/history/index.js +62 -0
  10. package/dist/internal/ast/index.d.ts +8 -0
  11. package/dist/internal/ast/index.js +5 -0
  12. package/dist/internal/ast/locators/index.d.ts +1 -0
  13. package/dist/internal/ast/locators/index.js +1 -0
  14. package/dist/internal/ast/locators/resolve-locator.d.ts +16 -0
  15. package/dist/internal/ast/locators/resolve-locator.js +920 -0
  16. package/dist/internal/ast/parser/SourceParser.d.ts +30 -0
  17. package/dist/internal/ast/parser/SourceParser.js +49 -0
  18. package/dist/internal/ast/parser/index.d.ts +21 -0
  19. package/dist/internal/ast/parser/index.js +64 -0
  20. package/dist/internal/ast/primitives/conditional/conditional-primitives.d.ts +18 -0
  21. package/dist/internal/ast/primitives/conditional/conditional-primitives.js +237 -0
  22. package/dist/internal/ast/primitives/conditional/index.d.ts +1 -0
  23. package/dist/internal/ast/primitives/conditional/index.js +1 -0
  24. package/dist/internal/ast/primitives/imports/add-import.d.ts +18 -0
  25. package/dist/internal/ast/primitives/imports/add-import.js +111 -0
  26. package/dist/internal/ast/primitives/imports/index.d.ts +2 -0
  27. package/dist/internal/ast/primitives/imports/index.js +2 -0
  28. package/dist/internal/ast/primitives/imports/remove-import.d.ts +15 -0
  29. package/dist/internal/ast/primitives/imports/remove-import.js +72 -0
  30. package/dist/internal/ast/primitives/index.d.ts +10 -0
  31. package/dist/internal/ast/primitives/index.js +10 -0
  32. package/dist/internal/ast/primitives/jsx/index.d.ts +4 -0
  33. package/dist/internal/ast/primitives/jsx/index.js +4 -0
  34. package/dist/internal/ast/primitives/jsx/insert-child.d.ts +11 -0
  35. package/dist/internal/ast/primitives/jsx/insert-child.js +69 -0
  36. package/dist/internal/ast/primitives/jsx/move-node.d.ts +9 -0
  37. package/dist/internal/ast/primitives/jsx/move-node.js +76 -0
  38. package/dist/internal/ast/primitives/jsx/remove-node.d.ts +7 -0
  39. package/dist/internal/ast/primitives/jsx/remove-node.js +36 -0
  40. package/dist/internal/ast/primitives/jsx/update-text.d.ts +8 -0
  41. package/dist/internal/ast/primitives/jsx/update-text.js +81 -0
  42. package/dist/internal/ast/primitives/next/index.d.ts +1 -0
  43. package/dist/internal/ast/primitives/next/index.js +1 -0
  44. package/dist/internal/ast/primitives/next/next-primitives.d.ts +43 -0
  45. package/dist/internal/ast/primitives/next/next-primitives.js +211 -0
  46. package/dist/internal/ast/primitives/shared.d.ts +60 -0
  47. package/dist/internal/ast/primitives/shared.js +176 -0
  48. package/dist/internal/ast/primitives/style/class-expression.d.ts +23 -0
  49. package/dist/internal/ast/primitives/style/class-expression.js +174 -0
  50. package/dist/internal/ast/primitives/style/index.d.ts +1 -0
  51. package/dist/internal/ast/primitives/style/index.js +1 -0
  52. package/dist/internal/ast/primitives/style/style-primitives.d.ts +49 -0
  53. package/dist/internal/ast/primitives/style/style-primitives.js +555 -0
  54. package/dist/internal/ast/primitives/values/index.d.ts +1 -0
  55. package/dist/internal/ast/primitives/values/index.js +1 -0
  56. package/dist/internal/ast/primitives/values/value-primitives.d.ts +42 -0
  57. package/dist/internal/ast/primitives/values/value-primitives.js +158 -0
  58. package/dist/internal/ast/printer/SourcePrinter.d.ts +21 -0
  59. package/dist/internal/ast/printer/SourcePrinter.js +76 -0
  60. package/dist/internal/ast/printer/index.d.ts +6 -0
  61. package/dist/internal/ast/printer/index.js +126 -0
  62. package/dist/internal/ast/types.d.ts +190 -0
  63. package/dist/internal/ast/types.js +1 -0
  64. package/dist/internal/capability/capability-resolver.d.ts +16 -0
  65. package/dist/internal/capability/capability-resolver.js +127 -0
  66. package/dist/internal/classname-source.d.ts +24 -0
  67. package/dist/internal/classname-source.js +220 -0
  68. package/dist/internal/contracts/IEditEngineRuntime.d.ts +18 -0
  69. package/dist/internal/contracts/IEditEngineRuntime.js +1 -0
  70. package/dist/internal/domain/EditDiagnostic.d.ts +38 -0
  71. package/dist/internal/domain/EditDiagnostic.js +43 -0
  72. package/dist/internal/events/event-bus.d.ts +14 -0
  73. package/dist/internal/events/event-bus.js +21 -0
  74. package/dist/internal/graph/graph-builder.d.ts +12 -0
  75. package/dist/internal/graph/graph-builder.js +1371 -0
  76. package/dist/internal/graph/import-resolver.d.ts +31 -0
  77. package/dist/internal/graph/import-resolver.js +109 -0
  78. package/dist/internal/graph/project-graph-builder.d.ts +32 -0
  79. package/dist/internal/graph/project-graph-builder.js +133 -0
  80. package/dist/internal/graph/types.d.ts +114 -0
  81. package/dist/internal/graph/types.js +6 -0
  82. package/dist/internal/history/undo-redo.d.ts +28 -0
  83. package/dist/internal/history/undo-redo.js +42 -0
  84. package/dist/internal/index.d.ts +2 -0
  85. package/dist/internal/index.js +1 -0
  86. package/dist/internal/planner/planner.d.ts +104 -0
  87. package/dist/internal/planner/planner.js +2533 -0
  88. package/dist/internal/planner/types.d.ts +275 -0
  89. package/dist/internal/planner/types.js +6 -0
  90. package/dist/internal/protocol/boundary.d.ts +10 -0
  91. package/dist/internal/protocol/boundary.js +3 -0
  92. package/dist/internal/protocol/capability.d.ts +47 -0
  93. package/dist/internal/protocol/capability.js +8 -0
  94. package/dist/internal/protocol/error.d.ts +43 -0
  95. package/dist/internal/protocol/error.js +38 -0
  96. package/dist/internal/protocol/event.d.ts +39 -0
  97. package/dist/internal/protocol/event.js +3 -0
  98. package/dist/internal/protocol/identity.d.ts +26 -0
  99. package/dist/internal/protocol/identity.js +30 -0
  100. package/dist/internal/protocol/operation.d.ts +224 -0
  101. package/dist/internal/protocol/operation.js +8 -0
  102. package/dist/internal/protocol/render.d.ts +212 -0
  103. package/dist/internal/protocol/render.js +3 -0
  104. package/dist/internal/protocol.d.ts +9 -0
  105. package/dist/internal/protocol.js +2 -0
  106. package/dist/internal/provenance/binding-graph.d.ts +39 -0
  107. package/dist/internal/provenance/binding-graph.js +184 -0
  108. package/dist/internal/provenance/capability-policy.d.ts +15 -0
  109. package/dist/internal/provenance/capability-policy.js +96 -0
  110. package/dist/internal/provenance/data-source-classifier.d.ts +14 -0
  111. package/dist/internal/provenance/data-source-classifier.js +281 -0
  112. package/dist/internal/provenance/resolve-text-provenance.d.ts +45 -0
  113. package/dist/internal/provenance/resolve-text-provenance.js +3090 -0
  114. package/dist/internal/provenance/types.d.ts +89 -0
  115. package/dist/internal/provenance/types.js +1 -0
  116. package/dist/internal/render/component-semantic.d.ts +11 -0
  117. package/dist/internal/render/component-semantic.js +141 -0
  118. package/dist/internal/render/content-model.d.ts +3 -0
  119. package/dist/internal/render/content-model.js +89 -0
  120. package/dist/internal/render/media-model.d.ts +3 -0
  121. package/dist/internal/render/media-model.js +45 -0
  122. package/dist/internal/render/provenance-types.d.ts +33 -0
  123. package/dist/internal/render/provenance-types.js +1 -0
  124. package/dist/internal/render/render-projection.d.ts +24 -0
  125. package/dist/internal/render/render-projection.js +281 -0
  126. package/dist/internal/render/tailwind-style-model.d.ts +19 -0
  127. package/dist/internal/render/tailwind-style-model.js +1187 -0
  128. package/dist/internal/runtime/EditEngineRuntime.d.ts +25 -0
  129. package/dist/internal/runtime/EditEngineRuntime.js +89 -0
  130. package/dist/internal/runtime/EditEngineRuntimeSnapshot.d.ts +31 -0
  131. package/dist/internal/runtime/EditEngineRuntimeSnapshot.js +15 -0
  132. package/dist/internal/runtime/InternalEditEngine.d.ts +44 -0
  133. package/dist/internal/runtime/InternalEditEngine.js +1391 -0
  134. package/dist/internal/runtime.d.ts +3 -0
  135. package/dist/internal/runtime.js +1 -0
  136. package/dist/internal/topology/topology.d.ts +6 -0
  137. package/dist/internal/topology/topology.js +98 -0
  138. package/dist/internal/topology/types.d.ts +35 -0
  139. package/dist/internal/topology/types.js +5 -0
  140. package/dist/internal/types.d.ts +1 -0
  141. package/dist/internal/types.js +1 -0
  142. package/dist/internal/writeback/in-memory-fs.d.ts +7 -0
  143. package/dist/internal/writeback/in-memory-fs.js +44 -0
  144. package/dist/internal/writeback/types.d.ts +45 -0
  145. package/dist/internal/writeback/types.js +7 -0
  146. package/dist/internal/writeback/writeback-service.d.ts +7 -0
  147. package/dist/internal/writeback/writeback-service.js +568 -0
  148. package/dist/internal-adapter.d.ts +18 -0
  149. package/dist/internal-adapter.js +350 -0
  150. package/dist/next-app-router-fs.d.ts +2 -0
  151. package/dist/next-app-router-fs.js +64 -0
  152. package/dist/next-app-router.d.ts +11 -0
  153. package/dist/next-app-router.js +140 -0
  154. package/dist/preview-runtime.d.ts +394 -0
  155. package/dist/preview-runtime.js +102 -0
  156. package/dist/public-file-system.d.ts +7 -0
  157. package/dist/public-file-system.js +1 -0
  158. package/dist/runtime-sync.d.ts +95 -0
  159. package/dist/runtime-sync.js +321 -0
  160. package/dist/runtime.d.ts +340 -0
  161. package/dist/runtime.js +134 -0
  162. package/dist/site-edit-instrumentation.d.ts +19 -0
  163. package/dist/site-edit-instrumentation.js +322 -0
  164. package/dist/snapshot-file-system.d.ts +19 -0
  165. package/dist/snapshot-file-system.js +49 -0
  166. package/dist/source-watcher.d.ts +20 -0
  167. package/dist/source-watcher.js +150 -0
  168. package/dist/source-writeback-test-harness.d.ts +244 -0
  169. package/dist/source-writeback-test-harness.js +119 -0
  170. package/dist/types.d.ts +68 -0
  171. package/dist/types.js +1 -0
  172. package/dist/webpack-loader.cjs +592 -0
  173. package/dist/webpack-loader.d.ts +27 -0
  174. package/package.json +66 -0
@@ -0,0 +1,127 @@
1
+ // ─── Capability Resolver ─────────────────────────────
2
+ // 来源: docs/06-development-checklist.md §5
3
+ //
4
+ // 关键约束:
5
+ // canMove 不能被误算成 same-file only
6
+ // canInsertChild 不能被误算成 native-tag only
7
+ // repeat / boundary / opaque 限制必须显式通过 constraint 返回
8
+ const VOID_ELEMENT_TAGS = new Set([
9
+ "area",
10
+ "base",
11
+ "br",
12
+ "col",
13
+ "embed",
14
+ "hr",
15
+ "img",
16
+ "input",
17
+ "link",
18
+ "meta",
19
+ "param",
20
+ "source",
21
+ "track",
22
+ "wbr",
23
+ ]);
24
+ const CONTAINER_ELEMENT_TAGS = new Set([
25
+ "article",
26
+ "aside",
27
+ "div",
28
+ "footer",
29
+ "form",
30
+ "header",
31
+ "li",
32
+ "main",
33
+ "nav",
34
+ "ol",
35
+ "table",
36
+ "tbody",
37
+ "tfoot",
38
+ "thead",
39
+ "tr",
40
+ "select",
41
+ "section",
42
+ "ul",
43
+ ]);
44
+ /**
45
+ * 为指定节点计算能力快照。
46
+ *
47
+ * 规则:
48
+ * - canMove = true 意味着支持全部 MoveTarget 位置(含跨文件)
49
+ * - canInsertChild = true 意味着支持 native-tag + component-call
50
+ * - 限制通过 constraints 显式返回
51
+ */
52
+ export function resolveCapability(key, topology, graph) {
53
+ const topologyNode = topology.nodes.get(key);
54
+ const element = graph.elements.get(key);
55
+ if (!topologyNode || !element) {
56
+ return {
57
+ key,
58
+ canUpdateText: false,
59
+ canInsertChild: false,
60
+ canMove: false,
61
+ canRemove: false,
62
+ constraints: [
63
+ { kind: "opaque-region", message: "Node not found in graph" },
64
+ ],
65
+ };
66
+ }
67
+ // Collect constraints from topology
68
+ const constraints = topologyNode.constraints.map((c) => ({
69
+ kind: c.kind,
70
+ message: c.reason,
71
+ }));
72
+ // Determine capabilities based on element properties and constraints
73
+ const isOpaque = element.isOpaque;
74
+ const isRoot = topologyNode.parentKey === null;
75
+ const isRouteComponentRoot = isRoot && element.componentName === "Page";
76
+ const tagName = element.tag.toLowerCase();
77
+ const isComponentElement = element.tag !== tagName;
78
+ const isVoidElement = !isComponentElement && VOID_ELEMENT_TAGS.has(tagName);
79
+ const isContainerElement = isComponentElement || CONTAINER_ELEMENT_TAGS.has(tagName);
80
+ const isComponentSlotContainer = isComponentElement && element.childKeys.length > 0;
81
+ const hasText = element.textSegments.length > 0 &&
82
+ element.textSegments.some((s) => s.editable);
83
+ if (isVoidElement &&
84
+ !constraints.some((constraint) => constraint.kind === "void-element")) {
85
+ constraints.push({
86
+ kind: "void-element",
87
+ message: `Element '${element.tag}' is void: children cannot be inserted`,
88
+ });
89
+ }
90
+ if (!isVoidElement &&
91
+ !isContainerElement &&
92
+ !constraints.some((constraint) => constraint.kind === "void-element")) {
93
+ constraints.push({
94
+ kind: "void-element",
95
+ message: `Element '${element.tag}' cannot contain structured children`,
96
+ });
97
+ }
98
+ // canUpdateText: has editable text segments and not opaque
99
+ const canUpdateText = hasText && !isOpaque;
100
+ // canInsertChild edits the call-site children for component slots; it does
101
+ // not imply access to the component implementation internals.
102
+ const canInsertChild = !isVoidElement &&
103
+ isContainerElement &&
104
+ (!isOpaque || isComponentSlotContainer);
105
+ // canMove: root nodes cannot be reparented, other nodes keep full move semantics
106
+ const canMove = !isRouteComponentRoot;
107
+ // canRemove: root nodes are structural entry points and cannot be removed
108
+ const canRemove = !isRouteComponentRoot;
109
+ return {
110
+ key,
111
+ canUpdateText,
112
+ canInsertChild,
113
+ canMove,
114
+ canRemove,
115
+ constraints,
116
+ };
117
+ }
118
+ /**
119
+ * 批量计算所有节点的能力快照。
120
+ */
121
+ export function resolveAllCapabilities(topology, graph) {
122
+ const result = new Map();
123
+ for (const key of topology.nodes.keys()) {
124
+ result.set(key, resolveCapability(key, topology, graph));
125
+ }
126
+ return result;
127
+ }
@@ -0,0 +1,24 @@
1
+ import type { SiteEditClassNameSource, SiteEditClassNameSourceRange, SiteEditSetClassNameSourceRequest, SiteEditSetClassNameSourceResult } from "@trojanbox-vcp-test/contracts";
2
+ import type { RenderSourceGraph } from "./graph/types.js";
3
+ export declare const classNameSourceParserVersion = "site-edit-classname-source@1";
4
+ export type ClassNameSourceRouteContext = {
5
+ routeId: string;
6
+ graph: RenderSourceGraph;
7
+ source: string;
8
+ };
9
+ export declare function extractClassNameSource(input: {
10
+ key: string;
11
+ route: ClassNameSourceRouteContext;
12
+ }): SiteEditClassNameSource;
13
+ export declare function applyClassNameSourceWrite(input: {
14
+ request: SiteEditSetClassNameSourceRequest;
15
+ current: SiteEditClassNameSource;
16
+ source: string;
17
+ writeSource: (nextSource: string) => Promise<void>;
18
+ }): Promise<SiteEditSetClassNameSourceResult>;
19
+ export declare function replaceClassNameAttributeSource(input: {
20
+ source: string;
21
+ oldAttrRange: SiteEditClassNameSourceRange | null;
22
+ openingElementRange: SiteEditClassNameSourceRange;
23
+ newAttrRaw: string | null;
24
+ }): string;
@@ -0,0 +1,220 @@
1
+ import { resolveLocatorPath } from "./ast/locators/resolve-locator.js";
2
+ import { detectParserProfile, parseWithProfile } from "./ast/parser/index.js";
3
+ import { sha256ContentHash } from "../snapshot-file-system.js";
4
+ export const classNameSourceParserVersion = "site-edit-classname-source@1";
5
+ export function extractClassNameSource(input) {
6
+ const element = input.route.graph.elements.get(input.key);
7
+ if (!element) {
8
+ throw new Error(`Key not found: ${input.key}`);
9
+ }
10
+ const match = locateElementInSource(input.route.source, element);
11
+ const openingElement = match.openingElement;
12
+ const classNameAttr = openingElement.attributes.find((attribute) => attribute.type === "JSXAttribute" &&
13
+ attribute.name.type === "JSXIdentifier" &&
14
+ attribute.name.name === "className");
15
+ const base = {
16
+ key: input.key,
17
+ routeId: input.route.routeId,
18
+ filePath: element.sourceFile,
19
+ componentName: element.componentName,
20
+ openingElementRange: toContractRange(openingElement),
21
+ version: {
22
+ documentVersion: input.route.graph.version,
23
+ contentHash: sha256ContentHash(input.route.source),
24
+ parserVersion: classNameSourceParserVersion,
25
+ },
26
+ };
27
+ if (!classNameAttr) {
28
+ return {
29
+ ...base,
30
+ sourceKind: "missing",
31
+ attrRaw: null,
32
+ valueRaw: null,
33
+ attrRange: null,
34
+ valueRange: null,
35
+ diagnostics: [
36
+ {
37
+ code: "missing-class-name",
38
+ message: "No className attribute is present on this element.",
39
+ },
40
+ ],
41
+ };
42
+ }
43
+ const value = classNameAttr.value;
44
+ const sourceKind = value?.type === "StringLiteral" ? "stringLiteral" : "expression";
45
+ return {
46
+ ...base,
47
+ sourceKind,
48
+ attrRaw: sliceNode(input.route.source, classNameAttr),
49
+ valueRaw: value ? sliceNode(input.route.source, value) : null,
50
+ attrRange: toContractRange(classNameAttr),
51
+ valueRange: value ? toContractRange(value) : null,
52
+ diagnostics: [],
53
+ };
54
+ }
55
+ export async function applyClassNameSourceWrite(input) {
56
+ const { request, current, source } = input;
57
+ if (request.expectedVersion.documentVersion !== current.version.documentVersion) {
58
+ return failure("document_version_stale", current, {
59
+ code: "version-conflict",
60
+ message: "The document version changed before className save.",
61
+ });
62
+ }
63
+ if (request.expectedVersion.contentHash !== current.version.contentHash) {
64
+ return failure("file_hash_mismatch", current, {
65
+ code: "version-conflict",
66
+ message: "The source file changed before className save.",
67
+ });
68
+ }
69
+ if (!sameRange(request.openingElementRange, current.openingElementRange)) {
70
+ return failure("range_mismatch", current, {
71
+ code: "version-conflict",
72
+ message: "The target opening element range no longer matches.",
73
+ });
74
+ }
75
+ if (!sameNullableRange(request.oldAttrRange, current.attrRange)) {
76
+ return failure("range_mismatch", current, {
77
+ code: "version-conflict",
78
+ message: "The className attribute range no longer matches.",
79
+ });
80
+ }
81
+ if (request.oldAttrRaw !== current.attrRaw) {
82
+ return failure("old_attr_mismatch", current, {
83
+ code: "version-conflict",
84
+ message: "The className attribute changed before save.",
85
+ });
86
+ }
87
+ if (request.newAttrRaw !== null &&
88
+ !isValidClassNameAttribute(request.newAttrRaw)) {
89
+ return failure("invalid_new_attr", current, {
90
+ code: "invalid-new-attr",
91
+ message: "newAttrRaw must be a valid JSX className attribute.",
92
+ });
93
+ }
94
+ const nextSource = replaceClassNameAttributeSource({
95
+ source,
96
+ oldAttrRange: request.oldAttrRange,
97
+ openingElementRange: request.openingElementRange,
98
+ newAttrRaw: request.newAttrRaw,
99
+ });
100
+ try {
101
+ await input.writeSource(nextSource);
102
+ }
103
+ catch {
104
+ return failure("write_failed", current, {
105
+ code: "write-failed",
106
+ message: "Failed to write className source.",
107
+ });
108
+ }
109
+ return { ok: true, source: current };
110
+ }
111
+ export function replaceClassNameAttributeSource(input) {
112
+ const { source, oldAttrRange, openingElementRange, newAttrRaw } = input;
113
+ if (oldAttrRange) {
114
+ const start = oldAttrRange.start > 0 && /\s/.test(source[oldAttrRange.start - 1] ?? "")
115
+ ? oldAttrRange.start - 1
116
+ : oldAttrRange.start;
117
+ const replacement = newAttrRaw
118
+ ? `${start < oldAttrRange.start ? " " : ""}${newAttrRaw}`
119
+ : "";
120
+ return (source.slice(0, start) + replacement + source.slice(oldAttrRange.end));
121
+ }
122
+ if (!newAttrRaw) {
123
+ return source;
124
+ }
125
+ const openingEnd = openingElementRange.end;
126
+ const insertAt = source.slice(Math.max(0, openingEnd - 2), openingEnd) === "/>"
127
+ ? openingEnd - 2
128
+ : openingEnd - 1;
129
+ return source.slice(0, insertAt) + ` ${newAttrRaw}` + source.slice(insertAt);
130
+ }
131
+ function locateElementInSource(source, element) {
132
+ const parsed = parseWithProfile({
133
+ source,
134
+ profile: detectParserProfile({
135
+ filePath: element.sourceFile,
136
+ source,
137
+ }),
138
+ });
139
+ if (!parsed.ok) {
140
+ throw new Error(parsed.diagnostic.message);
141
+ }
142
+ const result = resolveLocatorPath(parsed.ast, {
143
+ kind: "jsx-node",
144
+ strategy: "structural-path",
145
+ file: element.identity.file,
146
+ component: element.identity.component,
147
+ structuralPath: element.identity.structuralPath,
148
+ expectedRange: toInternalRange(element.sourceRange),
149
+ });
150
+ if (!result.ok || result.match.node.type !== "JSXElement") {
151
+ throw new Error(`Unable to locate JSX element for key ${element.identity.key}`);
152
+ }
153
+ return result.match.node;
154
+ }
155
+ function isValidClassNameAttribute(attrRaw) {
156
+ const parsed = parseWithProfile({
157
+ source: `const __v = <div ${attrRaw} />;`,
158
+ profile: "tsx-module",
159
+ });
160
+ if (!parsed.ok) {
161
+ return false;
162
+ }
163
+ const statement = parsed.ast.program.body[0];
164
+ const declaration = statement?.type === "VariableDeclaration"
165
+ ? statement.declarations[0]
166
+ : null;
167
+ const init = declaration?.init;
168
+ if (init?.type !== "JSXElement") {
169
+ return false;
170
+ }
171
+ const attrs = init.openingElement.attributes;
172
+ return (attrs.length === 1 &&
173
+ attrs[0]?.type === "JSXAttribute" &&
174
+ attrs[0].name.type === "JSXIdentifier" &&
175
+ attrs[0].name.name === "className");
176
+ }
177
+ function failure(reason, current, diagnostic) {
178
+ return {
179
+ ok: false,
180
+ reason,
181
+ current,
182
+ diagnostics: [diagnostic],
183
+ };
184
+ }
185
+ function sliceNode(source, node) {
186
+ if (node.start == null || node.end == null) {
187
+ return "";
188
+ }
189
+ return source.slice(node.start, node.end);
190
+ }
191
+ function toContractRange(node) {
192
+ if (node.start == null || node.end == null) {
193
+ throw new Error("AST node does not include offsets.");
194
+ }
195
+ return {
196
+ start: node.start,
197
+ end: node.end,
198
+ startLine: node.loc?.start.line,
199
+ startColumn: node.loc?.start.column,
200
+ endLine: node.loc?.end.line,
201
+ endColumn: node.loc?.end.column,
202
+ };
203
+ }
204
+ function toInternalRange(range) {
205
+ return {
206
+ startLine: range.startLine,
207
+ startColumn: range.startColumn,
208
+ endLine: range.endLine,
209
+ endColumn: range.endColumn,
210
+ };
211
+ }
212
+ function sameNullableRange(left, right) {
213
+ if (left === null || right === null) {
214
+ return left === right;
215
+ }
216
+ return sameRange(left, right);
217
+ }
218
+ function sameRange(left, right) {
219
+ return left.start === right.start && left.end === right.end;
220
+ }
@@ -0,0 +1,18 @@
1
+ import type { SiteEditGetClassNameSourceResult, SiteEditObjectCapabilities, SiteEditSetClassNameSourceRequest, SiteEditSetClassNameSourceResult } from "@trojanbox-vcp-test/contracts";
2
+ import type { DesignerEvent, OperationRequest, OperationResult, RenderDocument, RenderObjectDetail } from "../protocol.js";
3
+ export interface IEditEngineRuntime {
4
+ readonly status: "idle" | "building" | "ready" | "error" | "disposed";
5
+ registerRoute(routeId: string, entryFile: string): void;
6
+ setActiveRoute(routeId: string): Promise<void>;
7
+ getDocument(routeId: string): RenderDocument;
8
+ getObjectCapabilities(key: string, routeId?: string): SiteEditObjectCapabilities;
9
+ getRenderDetail(key: string, routeId?: string): RenderObjectDetail;
10
+ getClassNameSource(key: string, routeId?: string): SiteEditGetClassNameSourceResult;
11
+ setClassNameSource(request: SiteEditSetClassNameSourceRequest): Promise<SiteEditSetClassNameSourceResult>;
12
+ execute(request: OperationRequest): Promise<OperationResult>;
13
+ undo(): Promise<OperationResult>;
14
+ redo(): Promise<OperationResult>;
15
+ notifyFileChanged(file: string): void;
16
+ subscribe(listener: (event: DesignerEvent) => void): () => void;
17
+ dispose(): void;
18
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ /**
2
+ * EditDiagnostic - 结构化诊断信息创建工具。
3
+ *
4
+ * 提供统一的诊断对象创建函数,用于表示解析失败、定位失败、
5
+ * 不支持的语法等错误场景。
6
+ */
7
+ export type EditDiagnosticCode = "parse-failed" | "locator-miss" | "unsupported-syntax" | "write-failed" | "printer-failed";
8
+ export interface EditDiagnostic {
9
+ code: EditDiagnosticCode;
10
+ message: string;
11
+ details?: Record<string, unknown>;
12
+ }
13
+ export interface PrimitiveSuccessResult {
14
+ ok: true;
15
+ source: string;
16
+ }
17
+ export interface PrimitiveFailureResult {
18
+ ok: false;
19
+ source: string;
20
+ diagnostic: EditDiagnostic;
21
+ }
22
+ export type PrimitiveResult = PrimitiveSuccessResult | PrimitiveFailureResult;
23
+ /**
24
+ * 创建诊断对象。
25
+ */
26
+ export declare function createDiagnostic(code: EditDiagnosticCode, message: string, details?: Record<string, unknown>): EditDiagnostic;
27
+ /**
28
+ * 创建 primitive 失败结果(返回原始 source + 诊断)。
29
+ */
30
+ export declare function primitiveFailure(source: string, code: EditDiagnosticCode, message: string, details?: Record<string, unknown>): PrimitiveFailureResult;
31
+ /**
32
+ * 创建定位失败结果。
33
+ */
34
+ export declare function locatorFailure(source: string, message: string, details?: Record<string, unknown>): PrimitiveFailureResult;
35
+ /**
36
+ * 创建解析失败结果。
37
+ */
38
+ export declare function parseFailure(source: string, message: string): PrimitiveFailureResult;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * EditDiagnostic - 结构化诊断信息创建工具。
3
+ *
4
+ * 提供统一的诊断对象创建函数,用于表示解析失败、定位失败、
5
+ * 不支持的语法等错误场景。
6
+ */
7
+ /**
8
+ * 创建诊断对象。
9
+ */
10
+ export function createDiagnostic(code, message, details) {
11
+ return details === undefined
12
+ ? {
13
+ code,
14
+ message,
15
+ }
16
+ : {
17
+ code,
18
+ message,
19
+ details,
20
+ };
21
+ }
22
+ /**
23
+ * 创建 primitive 失败结果(返回原始 source + 诊断)。
24
+ */
25
+ export function primitiveFailure(source, code, message, details) {
26
+ return {
27
+ ok: false,
28
+ source,
29
+ diagnostic: createDiagnostic(code, message, details),
30
+ };
31
+ }
32
+ /**
33
+ * 创建定位失败结果。
34
+ */
35
+ export function locatorFailure(source, message, details) {
36
+ return primitiveFailure(source, "locator-miss", message, details);
37
+ }
38
+ /**
39
+ * 创建解析失败结果。
40
+ */
41
+ export function parseFailure(source, message) {
42
+ return primitiveFailure(source, "parse-failed", message);
43
+ }
@@ -0,0 +1,14 @@
1
+ import type { DesignerEvent } from "../protocol.js";
2
+ /**
3
+ * 事件总线 —— engine 内部事件分发。
4
+ */
5
+ export interface EventBus {
6
+ /** 发射事件 */
7
+ emit(event: DesignerEvent): void;
8
+ /** 订阅事件,返回取消订阅函数 */
9
+ subscribe(listener: (event: DesignerEvent) => void): () => void;
10
+ }
11
+ /**
12
+ * 创建事件总线。
13
+ */
14
+ export declare function createEventBus(): EventBus;
@@ -0,0 +1,21 @@
1
+ // ─── Event Bus ───────────────────────────────────────
2
+ // 来源: docs/06-development-checklist.md §7
3
+ /**
4
+ * 创建事件总线。
5
+ */
6
+ export function createEventBus() {
7
+ const listeners = new Set();
8
+ return {
9
+ emit(event) {
10
+ for (const listener of listeners) {
11
+ listener(event);
12
+ }
13
+ },
14
+ subscribe(listener) {
15
+ listeners.add(listener);
16
+ return () => {
17
+ listeners.delete(listener);
18
+ };
19
+ },
20
+ };
21
+ }
@@ -0,0 +1,12 @@
1
+ import type { RenderSourceGraph } from "./types.js";
2
+ export declare function buildGraphFromSource(file: string, source: string): RenderSourceGraph;
3
+ export declare function buildGraphWithLocalImportsFromSource(file: string, source: string): {
4
+ graph: RenderSourceGraph;
5
+ localImports: string[];
6
+ };
7
+ export { buildGraphFromSource as buildProjectSourceGraph };
8
+ /**
9
+ * 从项目源图中提取单路由的渲染源图。
10
+ * 当前阶段直接返回单文件构建结果。
11
+ */
12
+ export declare function extractRenderSourceGraph(graph: RenderSourceGraph, _routeId: string, _entryFile: string): RenderSourceGraph;