@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,150 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ const defaultWatchDirs = [
4
+ "app",
5
+ "src/app",
6
+ "components",
7
+ "src/components",
8
+ "lib",
9
+ "src/lib",
10
+ ];
11
+ const sourceFilePattern = /\.(?:tsx|ts|jsx|js|mjs|cjs|css|scss|sass)$/;
12
+ const ignoredPathPattern = /^(?:\.next|node_modules|\.git)(?:\/|$)/;
13
+ export class SiteEditSourceWatcher {
14
+ constructor(options) {
15
+ this.options = options;
16
+ this.watchers = [];
17
+ this.fileMtimes = new Map();
18
+ this.pollingTimer = null;
19
+ this.isPolling = false;
20
+ }
21
+ start() {
22
+ for (const relativeDirectory of this.getWatchDirs()) {
23
+ const baseDir = path.join(this.options.projectRoot, relativeDirectory);
24
+ if (!fs.existsSync(baseDir)) {
25
+ continue;
26
+ }
27
+ if (this.registerWatcher(baseDir, true)) {
28
+ continue;
29
+ }
30
+ for (const nestedDir of collectSubdirectories(baseDir)) {
31
+ this.registerWatcher(nestedDir, false);
32
+ }
33
+ }
34
+ this.scanFiles();
35
+ this.pollingTimer = setInterval(() => this.poll(), this.options.interval_ms ?? 1_000);
36
+ return () => this.stop();
37
+ }
38
+ stop() {
39
+ if (this.pollingTimer) {
40
+ clearInterval(this.pollingTimer);
41
+ this.pollingTimer = null;
42
+ }
43
+ for (const watcher of this.watchers) {
44
+ watcher.close();
45
+ }
46
+ this.watchers.length = 0;
47
+ }
48
+ getWatchDirs() {
49
+ return this.options.watch_dirs?.length
50
+ ? this.options.watch_dirs
51
+ : defaultWatchDirs;
52
+ }
53
+ registerWatcher(baseDir, recursive) {
54
+ try {
55
+ const watcher = fs.watch(baseDir, { recursive }, (_eventType, filename) => {
56
+ const changedFile = normalizeChangedFile(this.options.projectRoot, baseDir, filename);
57
+ if (changedFile) {
58
+ this.options.on_change(changedFile);
59
+ }
60
+ });
61
+ this.watchers.push(watcher);
62
+ return true;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ poll() {
69
+ if (this.isPolling) {
70
+ return;
71
+ }
72
+ this.isPolling = true;
73
+ try {
74
+ this.scanFiles();
75
+ }
76
+ finally {
77
+ this.isPolling = false;
78
+ }
79
+ }
80
+ scanFiles() {
81
+ for (const relativeDirectory of this.getWatchDirs()) {
82
+ const baseDir = path.join(this.options.projectRoot, relativeDirectory);
83
+ if (!fs.existsSync(baseDir)) {
84
+ continue;
85
+ }
86
+ for (const absoluteFile of collectWatchedFiles(this.options.projectRoot, baseDir)) {
87
+ try {
88
+ const stat = fs.statSync(absoluteFile);
89
+ const relativeFile = toProjectRelativePath(this.options.projectRoot, absoluteFile);
90
+ const previousMtime = this.fileMtimes.get(relativeFile);
91
+ if (previousMtime !== undefined && previousMtime !== stat.mtimeMs) {
92
+ this.options.on_change(relativeFile);
93
+ }
94
+ this.fileMtimes.set(relativeFile, stat.mtimeMs);
95
+ }
96
+ catch {
97
+ // Files can disappear while Next is recompiling; the next scan settles state.
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ function collectSubdirectories(baseDir) {
104
+ const directories = [baseDir];
105
+ for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
106
+ if (!entry.isDirectory() ||
107
+ entry.name.startsWith(".") ||
108
+ entry.name === "node_modules") {
109
+ continue;
110
+ }
111
+ directories.push(...collectSubdirectories(path.join(baseDir, entry.name)));
112
+ }
113
+ return directories;
114
+ }
115
+ function collectWatchedFiles(projectRoot, baseDir) {
116
+ const files = [];
117
+ for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
118
+ const absolutePath = path.join(baseDir, entry.name);
119
+ if (entry.isDirectory()) {
120
+ if (entry.name.startsWith(".") || entry.name === "node_modules") {
121
+ continue;
122
+ }
123
+ files.push(...collectWatchedFiles(projectRoot, absolutePath));
124
+ continue;
125
+ }
126
+ const relativeFile = toProjectRelativePath(projectRoot, absolutePath);
127
+ if (!ignoredPathPattern.test(relativeFile) &&
128
+ sourceFilePattern.test(relativeFile)) {
129
+ files.push(absolutePath);
130
+ }
131
+ }
132
+ return files;
133
+ }
134
+ function normalizeChangedFile(projectRoot, baseDir, filename) {
135
+ if (!filename) {
136
+ return null;
137
+ }
138
+ const absoluteFile = path.join(baseDir, filename.toString());
139
+ const relativeFile = toProjectRelativePath(projectRoot, absoluteFile);
140
+ if (!relativeFile ||
141
+ relativeFile.startsWith("..") ||
142
+ ignoredPathPattern.test(relativeFile) ||
143
+ !sourceFilePattern.test(relativeFile)) {
144
+ return null;
145
+ }
146
+ return relativeFile;
147
+ }
148
+ function toProjectRelativePath(projectRoot, absoluteFile) {
149
+ return path.relative(projectRoot, absoluteFile).replace(/\\/g, "/");
150
+ }
@@ -0,0 +1,244 @@
1
+ import type { SiteEditSetClassNameSourceRequest, SiteEditOperationParams, SiteEditRenderDocument } from "@trojanbox-vcp-test/contracts";
2
+ export type SourceFixtureFile = {
3
+ path: string;
4
+ content: string;
5
+ };
6
+ export declare function createSourceWritebackHarness(input: {
7
+ files: SourceFixtureFile[];
8
+ routeId?: string;
9
+ entryFile?: string;
10
+ }): {
11
+ getSource(path: string): string;
12
+ setActiveRoute(nextRouteId?: string): Promise<void>;
13
+ getDocument(nextRouteId?: string): SiteEditRenderDocument;
14
+ getClassNameSource(key: string, nextRouteId?: string): {
15
+ key: string;
16
+ routeId: string;
17
+ filePath: string;
18
+ componentName: string;
19
+ sourceKind: "missing" | "expression" | "stringLiteral";
20
+ attrRaw: string | null;
21
+ valueRaw: string | null;
22
+ attrRange: {
23
+ start: number;
24
+ end: number;
25
+ startLine?: number | undefined;
26
+ startColumn?: number | undefined;
27
+ endLine?: number | undefined;
28
+ endColumn?: number | undefined;
29
+ } | null;
30
+ valueRange: {
31
+ start: number;
32
+ end: number;
33
+ startLine?: number | undefined;
34
+ startColumn?: number | undefined;
35
+ endLine?: number | undefined;
36
+ endColumn?: number | undefined;
37
+ } | null;
38
+ openingElementRange: {
39
+ start: number;
40
+ end: number;
41
+ startLine?: number | undefined;
42
+ startColumn?: number | undefined;
43
+ endLine?: number | undefined;
44
+ endColumn?: number | undefined;
45
+ };
46
+ version: {
47
+ documentVersion: number;
48
+ contentHash: string;
49
+ parserVersion: string;
50
+ };
51
+ diagnostics: {
52
+ code: "missing-class-name" | "unsupported-attribute-name" | "source-locator-warning" | "invalid-new-attr" | "version-conflict" | "write-failed";
53
+ message: string;
54
+ }[];
55
+ };
56
+ setClassNameSource(request: SiteEditSetClassNameSourceRequest): Promise<{
57
+ ok: true;
58
+ source: {
59
+ key: string;
60
+ routeId: string;
61
+ filePath: string;
62
+ componentName: string;
63
+ sourceKind: "missing" | "expression" | "stringLiteral";
64
+ attrRaw: string | null;
65
+ valueRaw: string | null;
66
+ attrRange: {
67
+ start: number;
68
+ end: number;
69
+ startLine?: number | undefined;
70
+ startColumn?: number | undefined;
71
+ endLine?: number | undefined;
72
+ endColumn?: number | undefined;
73
+ } | null;
74
+ valueRange: {
75
+ start: number;
76
+ end: number;
77
+ startLine?: number | undefined;
78
+ startColumn?: number | undefined;
79
+ endLine?: number | undefined;
80
+ endColumn?: number | undefined;
81
+ } | null;
82
+ openingElementRange: {
83
+ start: number;
84
+ end: number;
85
+ startLine?: number | undefined;
86
+ startColumn?: number | undefined;
87
+ endLine?: number | undefined;
88
+ endColumn?: number | undefined;
89
+ };
90
+ version: {
91
+ documentVersion: number;
92
+ contentHash: string;
93
+ parserVersion: string;
94
+ };
95
+ diagnostics: {
96
+ code: "missing-class-name" | "unsupported-attribute-name" | "source-locator-warning" | "invalid-new-attr" | "version-conflict" | "write-failed";
97
+ message: string;
98
+ }[];
99
+ };
100
+ } | {
101
+ ok: false;
102
+ reason: "node_not_found" | "document_version_stale" | "file_hash_mismatch" | "range_mismatch" | "old_attr_mismatch" | "invalid_new_attr" | "unsupported_target" | "write_failed";
103
+ diagnostics: {
104
+ code: "missing-class-name" | "unsupported-attribute-name" | "source-locator-warning" | "invalid-new-attr" | "version-conflict" | "write-failed";
105
+ message: string;
106
+ }[];
107
+ current?: {
108
+ key: string;
109
+ routeId: string;
110
+ filePath: string;
111
+ componentName: string;
112
+ sourceKind: "missing" | "expression" | "stringLiteral";
113
+ attrRaw: string | null;
114
+ valueRaw: string | null;
115
+ attrRange: {
116
+ start: number;
117
+ end: number;
118
+ startLine?: number | undefined;
119
+ startColumn?: number | undefined;
120
+ endLine?: number | undefined;
121
+ endColumn?: number | undefined;
122
+ } | null;
123
+ valueRange: {
124
+ start: number;
125
+ end: number;
126
+ startLine?: number | undefined;
127
+ startColumn?: number | undefined;
128
+ endLine?: number | undefined;
129
+ endColumn?: number | undefined;
130
+ } | null;
131
+ openingElementRange: {
132
+ start: number;
133
+ end: number;
134
+ startLine?: number | undefined;
135
+ startColumn?: number | undefined;
136
+ endLine?: number | undefined;
137
+ endColumn?: number | undefined;
138
+ };
139
+ version: {
140
+ documentVersion: number;
141
+ contentHash: string;
142
+ parserVersion: string;
143
+ };
144
+ diagnostics: {
145
+ code: "missing-class-name" | "unsupported-attribute-name" | "source-locator-warning" | "invalid-new-attr" | "version-conflict" | "write-failed";
146
+ message: string;
147
+ }[];
148
+ } | undefined;
149
+ }>;
150
+ findEntryByTag(tag: string, index?: number): {
151
+ key: string;
152
+ tag: string;
153
+ label: string;
154
+ parentKey: string | null;
155
+ childKeys: string[];
156
+ operationSummary: {
157
+ canUpdateText: boolean;
158
+ canInsertChild: boolean;
159
+ canMove: boolean;
160
+ canRemove: boolean;
161
+ };
162
+ };
163
+ executeNodeOperation(input: {
164
+ key: string;
165
+ params: SiteEditOperationParams;
166
+ documentVersion?: number;
167
+ }): Promise<{
168
+ requestId: string;
169
+ kind: "update-text" | "remove-node" | "insert-child" | "move-node" | "replace-rich-text-content" | "insert-rich-text-block" | "remove-rich-text-block" | "set-media-field" | "set-component-slot-content" | "set-object-field" | "insert-object-field" | "remove-object-field" | "update-array-item" | "insert-array-item" | "remove-array-item" | "move-array-item" | "replace-conditional-expression" | "set-conditional-branch-content" | "set-jsx-prop" | "remove-jsx-prop" | "set-class-name" | "add-class-token" | "remove-class-token" | "set-style-property" | "set-style-properties" | "set-css-module-class" | "set-directive" | "remove-directive" | "set-route-export" | "set-metadata-field" | "set-generate-metadata";
170
+ target: {
171
+ kind: "node";
172
+ key: string;
173
+ } | {
174
+ kind: "route";
175
+ routeId: string;
176
+ };
177
+ ok: boolean;
178
+ resultVersion: number;
179
+ error?: {
180
+ code: "NODE_NOT_FOUND" | "ROUTE_NOT_FOUND" | "CAPABILITY_REJECTED" | "CONSTRAINT_VIOLATED" | "INVALID_PARAMS" | "PLAN_FAILED" | "VERSION_STALE" | "AST_PARSE_ERROR" | "UNSUPPORTED_OPERATION" | "LOCATOR_FAILED" | "INVALID_TARGET" | "WRITE_IO_ERROR" | "CONCURRENT_MODIFIED" | "ROLLBACK_FAILED" | "INTERNAL_ERROR";
181
+ message: string;
182
+ details?: Record<string, unknown> | undefined;
183
+ } | undefined;
184
+ }>;
185
+ executeRouteOperation(input: {
186
+ routeId?: string;
187
+ params: SiteEditOperationParams;
188
+ documentVersion?: number;
189
+ }): Promise<{
190
+ requestId: string;
191
+ kind: "update-text" | "remove-node" | "insert-child" | "move-node" | "replace-rich-text-content" | "insert-rich-text-block" | "remove-rich-text-block" | "set-media-field" | "set-component-slot-content" | "set-object-field" | "insert-object-field" | "remove-object-field" | "update-array-item" | "insert-array-item" | "remove-array-item" | "move-array-item" | "replace-conditional-expression" | "set-conditional-branch-content" | "set-jsx-prop" | "remove-jsx-prop" | "set-class-name" | "add-class-token" | "remove-class-token" | "set-style-property" | "set-style-properties" | "set-css-module-class" | "set-directive" | "remove-directive" | "set-route-export" | "set-metadata-field" | "set-generate-metadata";
192
+ target: {
193
+ kind: "node";
194
+ key: string;
195
+ } | {
196
+ kind: "route";
197
+ routeId: string;
198
+ };
199
+ ok: boolean;
200
+ resultVersion: number;
201
+ error?: {
202
+ code: "NODE_NOT_FOUND" | "ROUTE_NOT_FOUND" | "CAPABILITY_REJECTED" | "CONSTRAINT_VIOLATED" | "INVALID_PARAMS" | "PLAN_FAILED" | "VERSION_STALE" | "AST_PARSE_ERROR" | "UNSUPPORTED_OPERATION" | "LOCATOR_FAILED" | "INVALID_TARGET" | "WRITE_IO_ERROR" | "CONCURRENT_MODIFIED" | "ROLLBACK_FAILED" | "INTERNAL_ERROR";
203
+ message: string;
204
+ details?: Record<string, unknown> | undefined;
205
+ } | undefined;
206
+ }>;
207
+ undo(): Promise<{
208
+ requestId: string;
209
+ kind: "update-text" | "remove-node" | "insert-child" | "move-node" | "replace-rich-text-content" | "insert-rich-text-block" | "remove-rich-text-block" | "set-media-field" | "set-component-slot-content" | "set-object-field" | "insert-object-field" | "remove-object-field" | "update-array-item" | "insert-array-item" | "remove-array-item" | "move-array-item" | "replace-conditional-expression" | "set-conditional-branch-content" | "set-jsx-prop" | "remove-jsx-prop" | "set-class-name" | "add-class-token" | "remove-class-token" | "set-style-property" | "set-style-properties" | "set-css-module-class" | "set-directive" | "remove-directive" | "set-route-export" | "set-metadata-field" | "set-generate-metadata";
210
+ target: {
211
+ kind: "node";
212
+ key: string;
213
+ } | {
214
+ kind: "route";
215
+ routeId: string;
216
+ };
217
+ ok: boolean;
218
+ resultVersion: number;
219
+ error?: {
220
+ code: "NODE_NOT_FOUND" | "ROUTE_NOT_FOUND" | "CAPABILITY_REJECTED" | "CONSTRAINT_VIOLATED" | "INVALID_PARAMS" | "PLAN_FAILED" | "VERSION_STALE" | "AST_PARSE_ERROR" | "UNSUPPORTED_OPERATION" | "LOCATOR_FAILED" | "INVALID_TARGET" | "WRITE_IO_ERROR" | "CONCURRENT_MODIFIED" | "ROLLBACK_FAILED" | "INTERNAL_ERROR";
221
+ message: string;
222
+ details?: Record<string, unknown> | undefined;
223
+ } | undefined;
224
+ }>;
225
+ redo(): Promise<{
226
+ requestId: string;
227
+ kind: "update-text" | "remove-node" | "insert-child" | "move-node" | "replace-rich-text-content" | "insert-rich-text-block" | "remove-rich-text-block" | "set-media-field" | "set-component-slot-content" | "set-object-field" | "insert-object-field" | "remove-object-field" | "update-array-item" | "insert-array-item" | "remove-array-item" | "move-array-item" | "replace-conditional-expression" | "set-conditional-branch-content" | "set-jsx-prop" | "remove-jsx-prop" | "set-class-name" | "add-class-token" | "remove-class-token" | "set-style-property" | "set-style-properties" | "set-css-module-class" | "set-directive" | "remove-directive" | "set-route-export" | "set-metadata-field" | "set-generate-metadata";
228
+ target: {
229
+ kind: "node";
230
+ key: string;
231
+ } | {
232
+ kind: "route";
233
+ routeId: string;
234
+ };
235
+ ok: boolean;
236
+ resultVersion: number;
237
+ error?: {
238
+ code: "NODE_NOT_FOUND" | "ROUTE_NOT_FOUND" | "CAPABILITY_REJECTED" | "CONSTRAINT_VIOLATED" | "INVALID_PARAMS" | "PLAN_FAILED" | "VERSION_STALE" | "AST_PARSE_ERROR" | "UNSUPPORTED_OPERATION" | "LOCATOR_FAILED" | "INVALID_TARGET" | "WRITE_IO_ERROR" | "CONCURRENT_MODIFIED" | "ROLLBACK_FAILED" | "INTERNAL_ERROR";
239
+ message: string;
240
+ details?: Record<string, unknown> | undefined;
241
+ } | undefined;
242
+ }>;
243
+ subscribe(listener: Parameters<(listener: (event: import("@trojanbox-vcp-test/contracts").SiteEditEvent) => void) => () => void>[0]): () => void;
244
+ };
@@ -0,0 +1,119 @@
1
+ import { createPreviewSiteEditEngineRuntime } from "./preview-runtime.js";
2
+ export function createSourceWritebackHarness(input) {
3
+ const routeId = input.routeId ?? "/";
4
+ const entryFile = input.entryFile ?? "app/page.tsx";
5
+ const fileSystem = new MemoryProjectFileSystem(input.files);
6
+ const runtime = createPreviewSiteEditEngineRuntime({
7
+ siteId: "site_123",
8
+ snapshotId: "snapshot_123",
9
+ projectRoot: "/virtual",
10
+ routes: [{ routeId: routeId, entryFile: entryFile }],
11
+ dev: true,
12
+ fileSystem: fileSystem,
13
+ });
14
+ return {
15
+ getSource(path) {
16
+ return fileSystem.getSource(path);
17
+ },
18
+ async setActiveRoute(nextRouteId = routeId) {
19
+ await runtime.setActiveRoute(nextRouteId);
20
+ },
21
+ getDocument(nextRouteId = routeId) {
22
+ return runtime.getDocument(nextRouteId);
23
+ },
24
+ getClassNameSource(key, nextRouteId = routeId) {
25
+ return runtime.getClassNameSource(key, nextRouteId);
26
+ },
27
+ setClassNameSource(request) {
28
+ return runtime.setClassNameSource(request);
29
+ },
30
+ findEntryByTag(tag, index = 0) {
31
+ const matches = runtime
32
+ .getDocument(routeId)
33
+ .entries.filter((entry) => entry.tag === tag);
34
+ const entry = matches[index];
35
+ if (!entry) {
36
+ throw new Error(`Missing ${tag} at index ${index}`);
37
+ }
38
+ return entry;
39
+ },
40
+ executeNodeOperation(input) {
41
+ const document = runtime.getDocument(routeId);
42
+ const request = {
43
+ id: `op_${input.params.kind}_${input.key}`,
44
+ siteId: "site_123",
45
+ baseSnapshotId: "snapshot_123",
46
+ kind: input.params.kind,
47
+ target: { kind: "node", key: input.key },
48
+ documentVersion: input.documentVersion ?? document.version,
49
+ params: input.params,
50
+ };
51
+ return runtime.executeOperation(request);
52
+ },
53
+ executeRouteOperation(input) {
54
+ const targetRouteId = input.routeId ?? routeId;
55
+ let documentVersion = input.documentVersion;
56
+ if (documentVersion === undefined) {
57
+ try {
58
+ documentVersion = runtime.getDocument(targetRouteId).version;
59
+ }
60
+ catch {
61
+ documentVersion = 0;
62
+ }
63
+ }
64
+ const request = {
65
+ id: `op_${input.params.kind}_${targetRouteId}`,
66
+ siteId: "site_123",
67
+ baseSnapshotId: "snapshot_123",
68
+ kind: input.params.kind,
69
+ target: { kind: "route", routeId: targetRouteId },
70
+ documentVersion: documentVersion,
71
+ params: input.params,
72
+ };
73
+ return runtime.executeOperation(request);
74
+ },
75
+ undo() {
76
+ return runtime.undo();
77
+ },
78
+ redo() {
79
+ return runtime.redo();
80
+ },
81
+ subscribe(listener) {
82
+ return runtime.subscribe(listener);
83
+ },
84
+ };
85
+ }
86
+ class MemoryProjectFileSystem {
87
+ constructor(files) {
88
+ this.versions = new Map();
89
+ this.files = new Map(files.map((file) => [file.path, file.content]));
90
+ for (const file of files) {
91
+ this.versions.set(file.path, 1);
92
+ }
93
+ }
94
+ async readFile(file) {
95
+ return this.getSource(file);
96
+ }
97
+ async writeFile(file, content) {
98
+ this.files.set(file, content);
99
+ this.bumpVersion(file);
100
+ }
101
+ async exists(file) {
102
+ return this.files.has(file);
103
+ }
104
+ getVersion(file) {
105
+ return this.versions.get(file) ?? 0;
106
+ }
107
+ bumpVersion(file) {
108
+ const nextVersion = this.getVersion(file) + 1;
109
+ this.versions.set(file, nextVersion);
110
+ return nextVersion;
111
+ }
112
+ getSource(path) {
113
+ const content = this.files.get(path);
114
+ if (content === undefined) {
115
+ throw new Error(`Missing file ${path}`);
116
+ }
117
+ return content;
118
+ }
119
+ }
@@ -0,0 +1,68 @@
1
+ import type { SiteEditEvent, SiteEditGetClassNameSourceResult, SiteEditObjectCapabilities, SiteEditObjectContentDetail, SiteEditObjectEditContext, SiteEditObjectMediaDetail, SiteEditObjectStyleDetail, SiteEditObjectSummary, SiteEditOperationRequest, SiteEditOperationResult, SiteEditPatchPlan, SiteEditRenderDocument, SiteEditSetClassNameSourceRequest, SiteEditSetClassNameSourceResult } from "@trojanbox-vcp-test/contracts";
2
+ import type { SiteEditProjectFileSystem } from "./public-file-system.js";
3
+ export type { SiteEditProjectFileSystem } from "./public-file-system.js";
4
+ export interface SiteEditSnapshotFile {
5
+ path: string;
6
+ content: string;
7
+ version?: number;
8
+ }
9
+ export interface SiteEditEngineRoute {
10
+ routeId: string;
11
+ entryFile: string;
12
+ }
13
+ export interface SiteEditEngineRuntimeOptions {
14
+ siteId: string;
15
+ snapshotId: string;
16
+ files: SiteEditSnapshotFile[];
17
+ routes: SiteEditEngineRoute[];
18
+ onEvent?: (event: SiteEditEvent) => void;
19
+ dev?: boolean;
20
+ }
21
+ export interface SiteEditPlanOperationResult {
22
+ result: SiteEditOperationResult;
23
+ patchPlan?: SiteEditPatchPlan;
24
+ }
25
+ export interface SiteEditEngineRuntime {
26
+ readonly status: "idle" | "building" | "ready" | "disposed";
27
+ setActiveRoute(routeId: string): Promise<void>;
28
+ getDocument(routeId: string): SiteEditRenderDocument;
29
+ getObjectCapabilities(key: string, routeId?: string): SiteEditObjectCapabilities;
30
+ getObjectSummary(key: string, routeId?: string): SiteEditObjectSummary;
31
+ getObjectEditContext(key: string, routeId?: string): SiteEditObjectEditContext;
32
+ getObjectStyleDetail(key: string, routeId?: string): SiteEditObjectStyleDetail;
33
+ getObjectContentDetail(key: string, routeId?: string): SiteEditObjectContentDetail;
34
+ getObjectMediaDetail(key: string, routeId?: string): SiteEditObjectMediaDetail;
35
+ getClassNameSource(key: string, routeId?: string): SiteEditGetClassNameSourceResult;
36
+ setClassNameSource(request: SiteEditSetClassNameSourceRequest): Promise<SiteEditSetClassNameSourceResult>;
37
+ planOperation(request: SiteEditOperationRequest): Promise<SiteEditPlanOperationResult>;
38
+ dispose(): void;
39
+ }
40
+ export interface PreviewSiteEditEngineRuntimeOptions {
41
+ siteId: string;
42
+ snapshotId: string;
43
+ projectRoot: string;
44
+ routes: SiteEditEngineRoute[];
45
+ onEvent?: (event: SiteEditEvent) => void;
46
+ dev?: boolean;
47
+ fileSystem?: SiteEditProjectFileSystem;
48
+ }
49
+ export interface PreviewSiteEditEngineRuntime {
50
+ readonly status: "idle" | "building" | "ready" | "error" | "disposed";
51
+ registerRoute(routeId: string, entryFile: string): void;
52
+ setActiveRoute(routeId: string): Promise<void>;
53
+ getDocument(routeId: string): SiteEditRenderDocument;
54
+ getObjectCapabilities(key: string, routeId?: string): SiteEditObjectCapabilities;
55
+ getObjectSummary(key: string, routeId?: string): SiteEditObjectSummary;
56
+ getObjectEditContext(key: string, routeId?: string): SiteEditObjectEditContext;
57
+ getObjectStyleDetail(key: string, routeId?: string): SiteEditObjectStyleDetail;
58
+ getObjectContentDetail(key: string, routeId?: string): SiteEditObjectContentDetail;
59
+ getObjectMediaDetail(key: string, routeId?: string): SiteEditObjectMediaDetail;
60
+ getClassNameSource(key: string, routeId?: string): SiteEditGetClassNameSourceResult;
61
+ setClassNameSource(request: SiteEditSetClassNameSourceRequest): Promise<SiteEditSetClassNameSourceResult>;
62
+ executeOperation(request: SiteEditOperationRequest): Promise<SiteEditOperationResult>;
63
+ undo(): Promise<SiteEditOperationResult>;
64
+ redo(): Promise<SiteEditOperationResult>;
65
+ notifyFileChanged(file: string): void;
66
+ subscribe(listener: (event: SiteEditEvent) => void): () => void;
67
+ dispose(): void;
68
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};