@superblocksteam/vite-plugin-file-sync 2.0.6-next.59 → 2.0.6-next.6
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.
- package/dist/ai-service/app-interface/linter.d.ts +2 -7
- package/dist/ai-service/app-interface/linter.d.ts.map +1 -1
- package/dist/ai-service/app-interface/linter.js +41 -52
- package/dist/ai-service/app-interface/linter.js.map +1 -1
- package/dist/ai-service/app-interface/shell.d.ts +0 -2
- package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
- package/dist/ai-service/app-interface/shell.js +4 -13
- package/dist/ai-service/app-interface/shell.js.map +1 -1
- package/dist/ai-service/const.d.ts +0 -2
- package/dist/ai-service/const.d.ts.map +1 -1
- package/dist/ai-service/const.js +0 -2
- package/dist/ai-service/const.js.map +1 -1
- package/dist/ai-service/eval/template-renderer.d.ts.map +1 -1
- package/dist/ai-service/eval/template-renderer.js +1 -1
- package/dist/ai-service/eval/template-renderer.js.map +1 -1
- package/dist/ai-service/index.d.ts.map +1 -1
- package/dist/ai-service/index.js +12 -4
- package/dist/ai-service/index.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbButtonPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbButtonPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbButtonPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbButtonPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbCheckboxPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbCheckboxPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbCheckboxPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbCheckboxPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbColumnPropsDocs.js +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.js +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbDatePickerPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbDatePickerPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbDatePickerPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbDatePickerPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbDropdownPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbDropdownPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbDropdownPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbDropdownPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbIconPropsDocs.js +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.js +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbInputPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbInputPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbInputPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbInputPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbModalPropsDocs.js +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbPagePropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbPagePropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbPagePropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbPagePropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSectionPropsDocs.js +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.js +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSwitchPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSwitchPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSwitchPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbSwitchPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbTablePropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbTablePropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbTablePropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbTablePropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbTextPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbTextPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbTextPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbTextPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-typedefs/Dim.js +1 -1
- package/dist/ai-service/prompts/generated/library-typedefs/SbEventFlow.js +1 -1
- package/dist/ai-service/prompts/generated/library-typedefs/index.d.ts +0 -1
- package/dist/ai-service/prompts/generated/library-typedefs/index.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-typedefs/index.js +0 -1
- package/dist/ai-service/prompts/generated/library-typedefs/index.js.map +1 -1
- package/dist/ai-service/prompts/generated/subprompts/full-examples.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-api.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-components-rules.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-custom-components.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-data-filtering.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-event-flow.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-forms.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-layouts.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-page.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-rbac.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-routes.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-state.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-theming.js +1 -1
- package/dist/ai-service/prompts/generated/subprompts/system.js +1 -1
- package/dist/ai-service/prompts/system.d.ts.map +1 -1
- package/dist/ai-service/prompts/system.js +0 -4
- package/dist/ai-service/prompts/system.js.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +0 -2
- package/dist/ai-service/state-machine/clark-fsm.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/agent-planning.js +0 -10
- package/dist/ai-service/state-machine/handlers/agent-planning.js.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.d.ts +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.d.ts.map +1 -1
- package/dist/ai-service/state-machine/handlers/llm-generating.js +11 -49
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/transform/add-metadata-to-api-yaml/transformer.d.ts.map +1 -1
- package/dist/ai-service/transform/add-metadata-to-api-yaml/transformer.js +2 -3
- package/dist/ai-service/transform/add-metadata-to-api-yaml/transformer.js.map +1 -1
- package/dist/ai-service/transform/shared.d.ts.map +1 -1
- package/dist/ai-service/transform/shared.js +8 -9
- package/dist/ai-service/transform/shared.js.map +1 -1
- package/dist/ai-service/types.d.ts +1 -1
- package/dist/ai-service/types.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +6 -15
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/file-system-helpers.d.ts +0 -4
- package/dist/file-system-helpers.d.ts.map +1 -1
- package/dist/file-system-helpers.js +0 -10
- package/dist/file-system-helpers.js.map +1 -1
- package/dist/file-system-manager.d.ts +35 -40
- package/dist/file-system-manager.d.ts.map +1 -1
- package/dist/file-system-manager.js +490 -553
- package/dist/file-system-manager.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/inject-index-vite-plugin.d.ts +2 -0
- package/dist/inject-index-vite-plugin.d.ts.map +1 -1
- package/dist/inject-index-vite-plugin.js +2 -2
- package/dist/inject-index-vite-plugin.js.map +1 -1
- package/dist/injected-index.d.ts +2 -2
- package/dist/injected-index.d.ts.map +1 -1
- package/dist/injected-index.js.map +1 -1
- package/dist/lock-service/index.d.ts.map +1 -1
- package/dist/lock-service/index.js +2 -13
- package/dist/lock-service/index.js.map +1 -1
- package/dist/parsing/computed/to-code-computed.d.ts.map +1 -1
- package/dist/parsing/computed/to-code-computed.js +3 -3
- package/dist/parsing/computed/to-code-computed.js.map +1 -1
- package/dist/parsing/events/to-code-events.d.ts.map +1 -1
- package/dist/parsing/events/to-code-events.js +0 -11
- package/dist/parsing/events/to-code-events.js.map +1 -1
- package/dist/parsing/events/to-value-events.d.ts.map +1 -1
- package/dist/parsing/events/to-value-events.js +0 -47
- package/dist/parsing/events/to-value-events.js.map +1 -1
- package/dist/parsing/template/index.js +1 -1
- package/dist/parsing/template/index.js.map +1 -1
- package/dist/parsing/template/to-code-template.d.ts +1 -2
- package/dist/parsing/template/to-code-template.d.ts.map +1 -1
- package/dist/parsing/template/to-code-template.js +2 -2
- package/dist/parsing/template/to-code-template.js.map +1 -1
- package/dist/plugin-options.d.ts +2 -0
- package/dist/plugin-options.d.ts.map +1 -1
- package/dist/plugin-options.js.map +1 -1
- package/dist/socket-manager.d.ts +2 -2
- package/dist/socket-manager.d.ts.map +1 -1
- package/dist/socket-manager.js +2 -2
- package/dist/socket-manager.js.map +1 -1
- package/dist/source-tracker.d.ts +20 -20
- package/dist/source-tracker.d.ts.map +1 -1
- package/dist/source-tracker.js +13 -31
- package/dist/source-tracker.js.map +1 -1
- package/dist/sync-service/index.d.ts.map +1 -1
- package/dist/sync-service/index.js +1 -2
- package/dist/sync-service/index.js.map +1 -1
- package/dist/sync-service/list-dir.js +1 -1
- package/dist/sync-service/list-dir.js.map +1 -1
- package/dist/util/logger.d.ts +17 -13
- package/dist/util/logger.d.ts.map +1 -1
- package/dist/util/logger.js +44 -34
- package/dist/util/logger.js.map +1 -1
- package/package.json +6 -15
- package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.d.ts +0 -2
- package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.d.ts.map +0 -1
- package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.js +0 -6
- package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.js.map +0 -1
- package/dist/binding-extraction/index.d.ts +0 -2
- package/dist/binding-extraction/index.d.ts.map +0 -1
- package/dist/binding-extraction/index.js +0 -2
- package/dist/binding-extraction/index.js.map +0 -1
- package/dist/operations/operation-processor.d.ts +0 -24
- package/dist/operations/operation-processor.d.ts.map +0 -1
- package/dist/operations/operation-processor.js +0 -80
- package/dist/operations/operation-processor.js.map +0 -1
- package/dist/operations/types.d.ts +0 -8
- package/dist/operations/types.d.ts.map +0 -1
- package/dist/operations/types.js +0 -2
- package/dist/operations/types.js.map +0 -1
- package/dist/parsing/index.d.ts +0 -3
- package/dist/parsing/index.d.ts.map +0 -1
- package/dist/parsing/index.js +0 -3
- package/dist/parsing/index.js.map +0 -1
|
@@ -12,9 +12,8 @@ import { generateJSXAttribute } from "./codegen.js";
|
|
|
12
12
|
import { ComponentsManager } from "./components-manager.js";
|
|
13
13
|
import { addLegacyCustomComponentVariables, modifyLegacyCustomComponentElements, modifyLegacyCustomComponentImports, } from "./custom-components.js";
|
|
14
14
|
import { applyErrorHandling, } from "./errors/error-handler.js";
|
|
15
|
-
import {
|
|
15
|
+
import { getPageFolder, PAGES_DIRECTORY, ROUTES_FILE, SCOPE_FILE, } from "./file-system-helpers.js";
|
|
16
16
|
import { generate } from "./generate.js";
|
|
17
|
-
import { OperationProcessor } from "./operations/operation-processor.js";
|
|
18
17
|
import { doesElementHaveBinding } from "./parsing/bindings.js";
|
|
19
18
|
import { getSbElementId } from "./parsing/ids.js";
|
|
20
19
|
import { makeJSXAttribute } from "./parsing/jsx.js";
|
|
@@ -48,34 +47,24 @@ const SUPPORTED_FILETYPES = [
|
|
|
48
47
|
},
|
|
49
48
|
];
|
|
50
49
|
const APP_THEME_FILE_NAME = "appTheme.ts";
|
|
51
|
-
export class
|
|
50
|
+
export class FileSyncManager extends TracedEventEmitter {
|
|
52
51
|
rootDir;
|
|
53
52
|
tsFiles = {};
|
|
54
53
|
apiFiles = {};
|
|
55
54
|
sourceTracker;
|
|
56
55
|
fsOperationQueue;
|
|
57
|
-
operationProcessor;
|
|
58
56
|
generationNumberSequence;
|
|
59
57
|
routes = {};
|
|
60
58
|
watcher;
|
|
61
59
|
registeredComponentPaths = {};
|
|
62
60
|
renameManager = new RenameManager();
|
|
63
61
|
_tracer;
|
|
64
|
-
transactionNonce = Date.now();
|
|
65
|
-
pendingTransactions = new Set();
|
|
66
|
-
processedTransactions = [];
|
|
67
62
|
constructor(fsOperationQueue, generationNumberSequence, tracer) {
|
|
68
63
|
super(tracer, { captureRejections: true });
|
|
69
64
|
this.rootDir = "/";
|
|
70
65
|
this.fsOperationQueue = fsOperationQueue;
|
|
71
66
|
this.generationNumberSequence = generationNumberSequence;
|
|
72
67
|
this._tracer = tracer;
|
|
73
|
-
// intentionally a new queue here, we don't want to share the queue with the fsOperationQueue
|
|
74
|
-
this.operationProcessor = new OperationProcessor({
|
|
75
|
-
batchWindowMs: 50,
|
|
76
|
-
maxBatchSize: 100,
|
|
77
|
-
});
|
|
78
|
-
this.operationProcessor.disable();
|
|
79
68
|
applyErrorHandling(this, {
|
|
80
69
|
watch: { operation: "editing from code" },
|
|
81
70
|
handleCreatePage: { operation: "creating a page" },
|
|
@@ -121,7 +110,27 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
121
110
|
}
|
|
122
111
|
return path.join(this.rootDir, "App.tsx");
|
|
123
112
|
}
|
|
124
|
-
|
|
113
|
+
updateApi = (content, path) => {
|
|
114
|
+
if (!this.rootDir) {
|
|
115
|
+
throw new Error("Root directory not set");
|
|
116
|
+
}
|
|
117
|
+
const { api: apiContents, stepPathMap } = content;
|
|
118
|
+
const pageName = getPageName(path);
|
|
119
|
+
let scopeId = this.sourceTracker?.getScopeDefinitionForPage(pageName)?.id;
|
|
120
|
+
if (!scopeId) {
|
|
121
|
+
console.warn("Scope ID not found for API", apiContents.metadata.name);
|
|
122
|
+
scopeId = "";
|
|
123
|
+
}
|
|
124
|
+
const updatedApi = {
|
|
125
|
+
apiPb: yaml.parse(JSON.stringify(apiContents)),
|
|
126
|
+
pageName,
|
|
127
|
+
stepPathMap,
|
|
128
|
+
scopeId,
|
|
129
|
+
};
|
|
130
|
+
const isNewApi = !this.apiFiles[path];
|
|
131
|
+
this.apiFiles[path] = updatedApi;
|
|
132
|
+
return { updatedApi, pageName, isNewApi };
|
|
133
|
+
};
|
|
125
134
|
async watch(watcher, rootPath) {
|
|
126
135
|
const logger = getLogger();
|
|
127
136
|
this.rootDir = rootPath;
|
|
@@ -207,10 +216,178 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
207
216
|
return;
|
|
208
217
|
const { type, path } = file;
|
|
209
218
|
if (type === "api") {
|
|
210
|
-
this.
|
|
219
|
+
this.updateApi(content, path);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
const handleFileChange = async (event, filePath) => {
|
|
223
|
+
logger.info(`File changed: ${filePath}, event: ${event}`);
|
|
224
|
+
switch (event) {
|
|
225
|
+
case "add": {
|
|
226
|
+
const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
|
|
227
|
+
if (!fileType || !filePath.startsWith(rootPath)) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const data = await readFile(filePath);
|
|
231
|
+
if (typeof data !== "string")
|
|
232
|
+
return;
|
|
233
|
+
// if we match pages/*/index.tsx, we want to emit an addPage event
|
|
234
|
+
if (/pages\/.*\/index\.tsx/.test(filePath)) {
|
|
235
|
+
const file = await readFile(filePath);
|
|
236
|
+
if (!file) {
|
|
237
|
+
logger.error(`Failed to read file: ${filePath}`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!(filePath in this.tsFiles)) {
|
|
241
|
+
this.tsFiles[filePath] = file;
|
|
242
|
+
this.handleNonVisualChangeByDeletingIds(filePath, file);
|
|
243
|
+
this.emit("addPage", filePath);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
switch (fileType.type) {
|
|
247
|
+
case "api":
|
|
248
|
+
case "python-api-step":
|
|
249
|
+
case "js-api-step": {
|
|
250
|
+
await this.processApiUpdates(filePath, fileType);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case "change": {
|
|
257
|
+
// Special routes file handling
|
|
258
|
+
if (filePath === routePath) {
|
|
259
|
+
const data = await readFile(filePath);
|
|
260
|
+
try {
|
|
261
|
+
this.routes = JSON.parse(data);
|
|
262
|
+
this.emit("routesChanged", this.routes);
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
logger.error("Error parsing routes file", getErrorMeta(e));
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
|
|
270
|
+
if (!fileType || !filePath.startsWith(rootPath)) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const data = await readFile(filePath);
|
|
274
|
+
if (typeof data !== "string")
|
|
275
|
+
return;
|
|
276
|
+
switch (fileType.type) {
|
|
277
|
+
case "tsx":
|
|
278
|
+
case "scope":
|
|
279
|
+
{
|
|
280
|
+
if (!(filePath in this.tsFiles && this.tsFiles[filePath] === data)) {
|
|
281
|
+
logger.info(`File changed: ${filePath} updating AST tracker`);
|
|
282
|
+
this.tsFiles[filePath] = data;
|
|
283
|
+
// only update the source tracker if the file is different
|
|
284
|
+
this.handleNonVisualChangeByDeletingIds(filePath, data);
|
|
285
|
+
this.emit("fileChanged", filePath, data, true);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
logger.info(`File unchanged from last tracked state: ${filePath}`);
|
|
289
|
+
this.emit("fileChanged", filePath, data, false);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
case "api":
|
|
294
|
+
case "python-api-step":
|
|
295
|
+
case "js-api-step":
|
|
296
|
+
{
|
|
297
|
+
await this.processApiUpdates(filePath, fileType);
|
|
298
|
+
}
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
case "unlink": {
|
|
304
|
+
if (filePath in this.apiFiles) {
|
|
305
|
+
const api = this.apiFiles[filePath];
|
|
306
|
+
delete this.apiFiles[filePath];
|
|
307
|
+
if (!api || !this.sourceTracker)
|
|
308
|
+
break;
|
|
309
|
+
const scopeId = await this.sourceTracker.deleteApi({
|
|
310
|
+
pageName: api.pageName,
|
|
311
|
+
apiName: api.apiPb.metadata.name,
|
|
312
|
+
});
|
|
313
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
314
|
+
await this.writeChanges(changes);
|
|
315
|
+
this.emit("apiManualDelete", {
|
|
316
|
+
api: {
|
|
317
|
+
id: getClientApiId(api.apiPb.metadata.name, api.pageName),
|
|
318
|
+
apiName: api.apiPb.metadata.name,
|
|
319
|
+
// TODO(saksham): get pagename more defensively
|
|
320
|
+
pageName: getPageName(filePath),
|
|
321
|
+
scopeId,
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (filePath in this.tsFiles) {
|
|
326
|
+
delete this.tsFiles[filePath];
|
|
327
|
+
this.sourceTracker?.removeFile(filePath);
|
|
328
|
+
this.emit("deletePage", filePath);
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
watcher.on("all", async (event, filePath) => {
|
|
335
|
+
const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
|
|
336
|
+
switch (fileType?.type) {
|
|
337
|
+
case "tsx":
|
|
338
|
+
case "scope":
|
|
339
|
+
// these can be awaited to ensure sequential execution
|
|
340
|
+
this.fsOperationQueue.enqueue(async () => {
|
|
341
|
+
return await handleFileChange(event, filePath);
|
|
342
|
+
});
|
|
343
|
+
break;
|
|
344
|
+
default:
|
|
345
|
+
// some files including APIs cannot be awaited currently
|
|
346
|
+
this.fsOperationQueue.enqueue(async () => {
|
|
347
|
+
void handleFileChange(event, filePath);
|
|
348
|
+
});
|
|
211
349
|
}
|
|
212
350
|
});
|
|
213
|
-
|
|
351
|
+
}
|
|
352
|
+
getApiFiles() {
|
|
353
|
+
return this.formatApisToClientApis(this.apiFiles);
|
|
354
|
+
}
|
|
355
|
+
getTsFilePaths() {
|
|
356
|
+
return Object.keys(this.tsFiles);
|
|
357
|
+
}
|
|
358
|
+
getSourceTracker() {
|
|
359
|
+
return this.sourceTracker;
|
|
360
|
+
}
|
|
361
|
+
async writeFile(path, content, kind) {
|
|
362
|
+
if (kind === "ts") {
|
|
363
|
+
// happens eagerly regardless of error - possible desync
|
|
364
|
+
this.tsFiles[path] = content;
|
|
365
|
+
// TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
|
|
366
|
+
await this.fsOperationQueue.enqueue(async () => {
|
|
367
|
+
await fs.writeFile(path, content);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
else if (kind === "api") {
|
|
371
|
+
const currentApiFile = this.apiFiles[path];
|
|
372
|
+
const apiPb = yaml.parse(content);
|
|
373
|
+
// Client APIs have id, but server APIs don't
|
|
374
|
+
if (apiPb.metadata.id) {
|
|
375
|
+
delete apiPb.metadata.id;
|
|
376
|
+
}
|
|
377
|
+
const stepPathMap = currentApiFile?.stepPathMap ?? {};
|
|
378
|
+
this.updateApi({ api: apiPb, stepPathMap }, path);
|
|
379
|
+
// TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
|
|
380
|
+
await this.fsOperationQueue.enqueue(async () => {
|
|
381
|
+
await writeApiFiles({ apiPb }, "api", path.split("/").slice(0, -1).join("/"), false, [], [], { extractLargeSourceFiles: true, minLinesForExtraction: 1 }, new Set(Object.keys(this.apiFiles)), stepPathMap);
|
|
382
|
+
// stepPathMap will be generated after the write when the file doesn't exist
|
|
383
|
+
if (this.apiFiles[path]) {
|
|
384
|
+
this.apiFiles[path].stepPathMap = stepPathMap;
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
readFile(path) {
|
|
390
|
+
return this.tsFiles[path];
|
|
214
391
|
}
|
|
215
392
|
initializeSourceTracker() {
|
|
216
393
|
this.sourceTracker = new SourceTracker(this._tracer);
|
|
@@ -319,235 +496,6 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
319
496
|
return null;
|
|
320
497
|
}
|
|
321
498
|
}
|
|
322
|
-
enableOperationsQueue() {
|
|
323
|
-
this.operationProcessor.enable();
|
|
324
|
-
}
|
|
325
|
-
disableOperationsQueue() {
|
|
326
|
-
this.operationProcessor.disable();
|
|
327
|
-
}
|
|
328
|
-
async flushOperations() {
|
|
329
|
-
await this.operationProcessor.flush();
|
|
330
|
-
}
|
|
331
|
-
// MARK: file change handling
|
|
332
|
-
handleFileChange = async (event, filePath) => {
|
|
333
|
-
const logger = getLogger();
|
|
334
|
-
logger.info(`File changed: ${filePath}, event: ${event}`);
|
|
335
|
-
const rootPath = this.rootDir;
|
|
336
|
-
if (!rootPath) {
|
|
337
|
-
throw new Error("Root directory not set");
|
|
338
|
-
}
|
|
339
|
-
// Skip directory events
|
|
340
|
-
if (event === "addDir" || event === "unlinkDir") {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
const routePath = path.join(rootPath, ROUTES_FILE);
|
|
344
|
-
const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
|
|
345
|
-
// Only handle files we care about and that are in our root path
|
|
346
|
-
if (!fileType || !filePath.startsWith(rootPath)) {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
// Queue the operation based on the event type
|
|
350
|
-
switch (event) {
|
|
351
|
-
case "add": {
|
|
352
|
-
const data = await readFile(filePath);
|
|
353
|
-
if (typeof data !== "string")
|
|
354
|
-
return;
|
|
355
|
-
const isPage = isPageFilePath(filePath);
|
|
356
|
-
if (isPage) {
|
|
357
|
-
void this.operationProcessor.addOperation({
|
|
358
|
-
metadata: {
|
|
359
|
-
filePath,
|
|
360
|
-
},
|
|
361
|
-
execute: async () => {
|
|
362
|
-
const file = await readFile(filePath);
|
|
363
|
-
if (!file) {
|
|
364
|
-
logger.error(`Failed to read file: ${filePath}`);
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
if (!(filePath in this.tsFiles)) {
|
|
368
|
-
this.tsFiles[filePath] = file;
|
|
369
|
-
this.handleNonVisualChangeByDeletingIds(filePath, file);
|
|
370
|
-
this.emit("addPage", filePath);
|
|
371
|
-
}
|
|
372
|
-
},
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
else {
|
|
376
|
-
void this.operationProcessor.addOperation({
|
|
377
|
-
metadata: {
|
|
378
|
-
filePath,
|
|
379
|
-
},
|
|
380
|
-
execute: async () => {
|
|
381
|
-
switch (fileType.type) {
|
|
382
|
-
case "api":
|
|
383
|
-
case "python-api-step":
|
|
384
|
-
case "js-api-step": {
|
|
385
|
-
await this.processApiFileUpdates(filePath, fileType);
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
394
|
-
case "change": {
|
|
395
|
-
if (filePath === routePath) {
|
|
396
|
-
void this.operationProcessor.addOperation({
|
|
397
|
-
metadata: {
|
|
398
|
-
filePath,
|
|
399
|
-
},
|
|
400
|
-
priority: true,
|
|
401
|
-
execute: async () => {
|
|
402
|
-
try {
|
|
403
|
-
const data = JSON.parse((await readFile(filePath)) ?? "{}");
|
|
404
|
-
if (!isEqual(this.routes, data)) {
|
|
405
|
-
this.routes = data;
|
|
406
|
-
this.emit("routesChanged", this.routes);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
catch (e) {
|
|
410
|
-
logger.error("Error parsing routes file", getErrorMeta(e));
|
|
411
|
-
}
|
|
412
|
-
},
|
|
413
|
-
});
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
void this.operationProcessor.addOperation({
|
|
418
|
-
metadata: {
|
|
419
|
-
filePath,
|
|
420
|
-
},
|
|
421
|
-
execute: async () => {
|
|
422
|
-
const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
|
|
423
|
-
if (!fileType || !filePath.startsWith(rootPath)) {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
const data = await readFile(filePath);
|
|
427
|
-
if (typeof data !== "string")
|
|
428
|
-
return;
|
|
429
|
-
switch (fileType.type) {
|
|
430
|
-
case "tsx":
|
|
431
|
-
case "scope":
|
|
432
|
-
{
|
|
433
|
-
if (!(filePath in this.tsFiles &&
|
|
434
|
-
this.tsFiles[filePath] === data)) {
|
|
435
|
-
logger.info(`File changed: ${filePath} updating AST tracker`);
|
|
436
|
-
this.tsFiles[filePath] = data;
|
|
437
|
-
// only update the source tracker if the file is different
|
|
438
|
-
this.handleNonVisualChangeByDeletingIds(filePath, data);
|
|
439
|
-
this.emit("fileChanged", filePath, data, true);
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
logger.info(`File unchanged from last tracked state: ${filePath}`);
|
|
443
|
-
this.emit("fileChanged", filePath, data, false);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
break;
|
|
447
|
-
case "api":
|
|
448
|
-
case "python-api-step":
|
|
449
|
-
case "js-api-step":
|
|
450
|
-
{
|
|
451
|
-
await this.processApiFileUpdates(filePath, fileType);
|
|
452
|
-
}
|
|
453
|
-
break;
|
|
454
|
-
}
|
|
455
|
-
},
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
break;
|
|
459
|
-
}
|
|
460
|
-
case "unlink": {
|
|
461
|
-
if (filePath in this.tsFiles) {
|
|
462
|
-
void this.operationProcessor.addOperation({
|
|
463
|
-
metadata: {
|
|
464
|
-
filePath,
|
|
465
|
-
},
|
|
466
|
-
execute: async () => {
|
|
467
|
-
await this.deleteTsFile(filePath);
|
|
468
|
-
},
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
else if (filePath in this.apiFiles) {
|
|
472
|
-
void this.operationProcessor.addOperation({
|
|
473
|
-
metadata: {
|
|
474
|
-
filePath,
|
|
475
|
-
},
|
|
476
|
-
execute: async () => {
|
|
477
|
-
await this.removeApiData(filePath);
|
|
478
|
-
},
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
break;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
};
|
|
485
|
-
async deleteTsFile(filePath) {
|
|
486
|
-
delete this.tsFiles[filePath];
|
|
487
|
-
this.sourceTracker?.removeFile(filePath);
|
|
488
|
-
if (isPageFilePath(filePath)) {
|
|
489
|
-
this.emit("deletePage", filePath);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
getTsFilePaths() {
|
|
493
|
-
return Object.keys(this.tsFiles);
|
|
494
|
-
}
|
|
495
|
-
getSourceTracker() {
|
|
496
|
-
return this.sourceTracker;
|
|
497
|
-
}
|
|
498
|
-
// MARK: fs read/write
|
|
499
|
-
async writeFile(path, content, kind) {
|
|
500
|
-
if (kind === "ts") {
|
|
501
|
-
// happens eagerly regardless of error - possible desync
|
|
502
|
-
this.tsFiles[path] = content;
|
|
503
|
-
// TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
|
|
504
|
-
await this.fsOperationQueue.enqueue(async () => {
|
|
505
|
-
await fs.writeFile(path, content);
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
else if (kind === "api") {
|
|
509
|
-
const currentApiFile = this.apiFiles[path];
|
|
510
|
-
const apiPb = yaml.parse(content);
|
|
511
|
-
// Client APIs have id, but server APIs don't
|
|
512
|
-
if (apiPb.metadata.id) {
|
|
513
|
-
delete apiPb.metadata.id;
|
|
514
|
-
}
|
|
515
|
-
const stepPathMap = currentApiFile?.stepPathMap ?? {};
|
|
516
|
-
this.updateInternalApiData({ api: apiPb, stepPathMap }, path);
|
|
517
|
-
// TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
|
|
518
|
-
await this.fsOperationQueue.enqueue(async () => {
|
|
519
|
-
await writeApiFiles({ apiPb }, "api", path.split("/").slice(0, -1).join("/"), false, [], [], { extractLargeSourceFiles: true, minLinesForExtraction: 1 }, new Set(Object.keys(this.apiFiles)), stepPathMap);
|
|
520
|
-
// stepPathMap will be generated after the write when the file doesn't exist
|
|
521
|
-
if (this.apiFiles[path]) {
|
|
522
|
-
this.apiFiles[path].stepPathMap = stepPathMap;
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
readFile(path) {
|
|
528
|
-
return this.tsFiles[path];
|
|
529
|
-
}
|
|
530
|
-
async addRoute(route, filePath) {
|
|
531
|
-
if (!this.rootDir) {
|
|
532
|
-
throw new Error("Root directory not set");
|
|
533
|
-
}
|
|
534
|
-
this.routes[route] = { file: this.getRelativeRoutePath(filePath) };
|
|
535
|
-
await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
|
|
536
|
-
}
|
|
537
|
-
async removeRoute(filePath) {
|
|
538
|
-
if (!this.rootDir) {
|
|
539
|
-
throw new Error("Root directory not set");
|
|
540
|
-
}
|
|
541
|
-
const relativeFilePath = this.getRelativeRoutePath(filePath);
|
|
542
|
-
this.routes = Object.fromEntries(Object.entries(this.routes).filter(([_, value]) => value.file !== relativeFilePath));
|
|
543
|
-
await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
|
|
544
|
-
}
|
|
545
|
-
async writeChanges(changes, callback) {
|
|
546
|
-
return Promise.all(changes.map(async ({ fileName, source, kind }) => {
|
|
547
|
-
await this.writeFile(fileName, source, kind);
|
|
548
|
-
return callback?.(fileName, source);
|
|
549
|
-
}));
|
|
550
|
-
}
|
|
551
499
|
getLocalBindingEntities(path) {
|
|
552
500
|
const logger = getLogger();
|
|
553
501
|
const files = this.sourceTracker?.getCurrentFiles();
|
|
@@ -575,36 +523,8 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
575
523
|
});
|
|
576
524
|
return Array.from(localBindingEntities);
|
|
577
525
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
|
|
581
|
-
if (!currentFile) {
|
|
582
|
-
return null;
|
|
583
|
-
}
|
|
584
|
-
return getPageRoots(filePath, currentFile);
|
|
585
|
-
}
|
|
586
|
-
getScope(filePath) {
|
|
587
|
-
// get the scope.ts file in the same directory as path
|
|
588
|
-
const scopeFilePath = path.join(path.dirname(filePath), SCOPE_FILE);
|
|
589
|
-
const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
|
|
590
|
-
if (!currentFile) {
|
|
591
|
-
console.log("File not found", scopeFilePath);
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
const scope = getScope(scopeFilePath, currentFile);
|
|
595
|
-
return scope;
|
|
596
|
-
}
|
|
597
|
-
getRoutes() {
|
|
598
|
-
const routes = [];
|
|
599
|
-
for (const [path, { file }] of Object.entries(this.routes)) {
|
|
600
|
-
routes.push({
|
|
601
|
-
path,
|
|
602
|
-
component: file,
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
return routes;
|
|
606
|
-
}
|
|
607
|
-
// MARK: transaction handling
|
|
526
|
+
pendingTransactions = new Set();
|
|
527
|
+
processedTransactions = [];
|
|
608
528
|
flushTransactions = () => {
|
|
609
529
|
// TODO do something more sophisticated than this.
|
|
610
530
|
this.processedTransactions = this.processedTransactions.slice(-20);
|
|
@@ -617,13 +537,13 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
617
537
|
}
|
|
618
538
|
this.pendingTransactions.add(transactionId);
|
|
619
539
|
};
|
|
540
|
+
transactionNonce = Date.now();
|
|
620
541
|
// Until we have a sophisticated way to track ALL operations, including non-UI initiated ops, we will use a nonce
|
|
621
542
|
// TODO https://github.com/superblocksteam/superblocks/pull/11788
|
|
622
543
|
getProcessedTransactionsWithNonce() {
|
|
623
544
|
const nonce = `t-${this.transactionNonce++}`;
|
|
624
545
|
return this.processedTransactions.concat(nonce);
|
|
625
546
|
}
|
|
626
|
-
// MARK: editor operations
|
|
627
547
|
handleCreatePage = async (payload) => {
|
|
628
548
|
const { name } = payload;
|
|
629
549
|
if (!this.rootDir) {
|
|
@@ -640,9 +560,7 @@ function Page() {
|
|
|
640
560
|
|
|
641
561
|
export default registerPage(Page, { name: "${name}" });
|
|
642
562
|
`;
|
|
643
|
-
await
|
|
644
|
-
await fs.mkdir(pagePath, { recursive: true });
|
|
645
|
-
});
|
|
563
|
+
await fs.mkdir(pagePath, { recursive: true });
|
|
646
564
|
await this.writeFile(pageIndexPath, pageContent, "ts");
|
|
647
565
|
await this.handleNonVisualChangeByDeletingIds(pageIndexPath, pageContent);
|
|
648
566
|
this.emit("addPage", pageIndexPath);
|
|
@@ -650,11 +568,11 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
650
568
|
handleReparent = async (payload, writeFile = true) => {
|
|
651
569
|
const { from, to, changedProps, transaction } = payload;
|
|
652
570
|
this.trackTransaction(transaction?.id);
|
|
653
|
-
this.sourceTracker?.setProperties({
|
|
571
|
+
await this.sourceTracker?.setProperties({
|
|
654
572
|
source: from.source,
|
|
655
573
|
changes: changedProps ?? {},
|
|
656
574
|
});
|
|
657
|
-
this.sourceTracker?.moveElement({
|
|
575
|
+
await this.sourceTracker?.moveElement({
|
|
658
576
|
from,
|
|
659
577
|
to,
|
|
660
578
|
});
|
|
@@ -668,7 +586,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
668
586
|
};
|
|
669
587
|
handleCreateComponent = async (payload, writeFile = true) => {
|
|
670
588
|
this.trackTransaction(payload.transaction?.id);
|
|
671
|
-
const sourceId = this.sourceTracker?.addElement(payload);
|
|
589
|
+
const sourceId = await this.sourceTracker?.addElement(payload);
|
|
672
590
|
if (writeFile) {
|
|
673
591
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
674
592
|
await this.writeChanges(changes ?? [], (fileName) => {
|
|
@@ -682,7 +600,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
682
600
|
const { elements, transaction } = payload;
|
|
683
601
|
this.trackTransaction(transaction?.id);
|
|
684
602
|
for (const element of elements) {
|
|
685
|
-
this.sourceTracker?.deleteElement({
|
|
603
|
+
await this.sourceTracker?.deleteElement({
|
|
686
604
|
source: element.source,
|
|
687
605
|
scopeName: element.scopeName,
|
|
688
606
|
});
|
|
@@ -698,7 +616,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
698
616
|
handleSetProperty = async (payload, writeFile = true) => {
|
|
699
617
|
const { element: { source }, property, value, transaction, } = payload;
|
|
700
618
|
this.trackTransaction(transaction?.id);
|
|
701
|
-
this.sourceTracker?.setProperty({
|
|
619
|
+
await this.sourceTracker?.setProperty({
|
|
702
620
|
source,
|
|
703
621
|
property,
|
|
704
622
|
info: value,
|
|
@@ -713,7 +631,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
713
631
|
handleSetProperties = async (payload, writeFile = true) => {
|
|
714
632
|
const { element: { source }, properties, transaction, } = payload;
|
|
715
633
|
this.trackTransaction(transaction?.id);
|
|
716
|
-
this.sourceTracker?.setProperties({
|
|
634
|
+
await this.sourceTracker?.setProperties({
|
|
717
635
|
source,
|
|
718
636
|
changes: properties,
|
|
719
637
|
});
|
|
@@ -769,147 +687,6 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
769
687
|
this.flushTransactions();
|
|
770
688
|
return returnValues;
|
|
771
689
|
};
|
|
772
|
-
// MARK: entity operations
|
|
773
|
-
handleAddEntity = async (payload) => {
|
|
774
|
-
this.sourceTracker?.addEntity({
|
|
775
|
-
scopeId: payload.scopeId,
|
|
776
|
-
entity: {
|
|
777
|
-
type: payload.type,
|
|
778
|
-
name: payload.name,
|
|
779
|
-
attributes: payload.attributes,
|
|
780
|
-
},
|
|
781
|
-
});
|
|
782
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
783
|
-
await this.writeChanges(changes, (fileName) => {
|
|
784
|
-
this.emit("addEntity", fileName, payload);
|
|
785
|
-
});
|
|
786
|
-
};
|
|
787
|
-
handleUpdateEntity = async (payload) => {
|
|
788
|
-
this.sourceTracker?.updateEntity({
|
|
789
|
-
entityId: payload.entityId,
|
|
790
|
-
updates: payload.updates,
|
|
791
|
-
});
|
|
792
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
793
|
-
await this.writeChanges(changes, (fileName) => {
|
|
794
|
-
this.emit("updateEntity", fileName, payload);
|
|
795
|
-
});
|
|
796
|
-
};
|
|
797
|
-
handleDeleteEntity = async (payload) => {
|
|
798
|
-
const deletedEntityName = this.sourceTracker?.deleteEntity({
|
|
799
|
-
entityId: payload.entityId,
|
|
800
|
-
});
|
|
801
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
802
|
-
await this.writeChanges(changes, (fileName) => {
|
|
803
|
-
if (deletedEntityName) {
|
|
804
|
-
this.emit("deleteEntity", fileName, deletedEntityName);
|
|
805
|
-
}
|
|
806
|
-
});
|
|
807
|
-
};
|
|
808
|
-
handleUpdateTheme = async (payload) => {
|
|
809
|
-
const { theme } = payload;
|
|
810
|
-
if (!this.rootDir) {
|
|
811
|
-
throw new Error("Root directory not set");
|
|
812
|
-
}
|
|
813
|
-
const filePath = path.join(this.rootDir, APP_THEME_FILE_NAME);
|
|
814
|
-
this.sourceTracker?.updateTheme({
|
|
815
|
-
themeFilePath: filePath,
|
|
816
|
-
theme,
|
|
817
|
-
});
|
|
818
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
819
|
-
await this.writeChanges(changes);
|
|
820
|
-
};
|
|
821
|
-
// MARK: rename operations
|
|
822
|
-
handleRenameElement = async (payload) => {
|
|
823
|
-
if (payload.kind === "component") {
|
|
824
|
-
return this.handleRenameComponent(payload);
|
|
825
|
-
}
|
|
826
|
-
else if (payload.kind === "entity") {
|
|
827
|
-
return this.handleRenameEntity(payload);
|
|
828
|
-
}
|
|
829
|
-
else if (payload.kind === "page") {
|
|
830
|
-
return this.handleRenamePage(payload);
|
|
831
|
-
}
|
|
832
|
-
};
|
|
833
|
-
handleRenameComponent = async (payload) => {
|
|
834
|
-
const { elementId, newName, oldName, scopeName } = payload;
|
|
835
|
-
await this.sourceTracker?.renameComponent({
|
|
836
|
-
widgetSourceId: elementId,
|
|
837
|
-
oldName,
|
|
838
|
-
newName,
|
|
839
|
-
scopeName,
|
|
840
|
-
});
|
|
841
|
-
await this.renameIdentifierInApis({
|
|
842
|
-
elementId,
|
|
843
|
-
oldName,
|
|
844
|
-
newName,
|
|
845
|
-
});
|
|
846
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
847
|
-
await this.writeChanges(changes, (fileName) => {
|
|
848
|
-
this.emit("renameComponent", fileName);
|
|
849
|
-
});
|
|
850
|
-
};
|
|
851
|
-
handleRenameEntity = async (payload) => {
|
|
852
|
-
const { elementId, newName, oldName, scopeName } = payload;
|
|
853
|
-
this.sourceTracker?.renameEntity({
|
|
854
|
-
entityId: elementId,
|
|
855
|
-
oldName,
|
|
856
|
-
newName,
|
|
857
|
-
scopeName,
|
|
858
|
-
});
|
|
859
|
-
await this.renameIdentifierInApis(payload);
|
|
860
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
861
|
-
await this.writeChanges(changes, (fileName) => {
|
|
862
|
-
this.emit("renameEntity", fileName);
|
|
863
|
-
});
|
|
864
|
-
};
|
|
865
|
-
handleRenamePage = async (payload) => {
|
|
866
|
-
const { newName, oldName } = payload;
|
|
867
|
-
if (!this.rootDir) {
|
|
868
|
-
throw new Error("Root directory not set");
|
|
869
|
-
}
|
|
870
|
-
const newPageFolder = getPageFolder(this.rootDir, newName);
|
|
871
|
-
const newIndexFilePath = path.join(newPageFolder, "index.tsx");
|
|
872
|
-
const oldIndexFilePath = path.join(this.rootDir, "pages", oldName, "index.tsx");
|
|
873
|
-
const oldPageFolder = getPageFolder(this.rootDir, oldName);
|
|
874
|
-
this.watcher?.unwatch(newPageFolder);
|
|
875
|
-
this.watcher?.unwatch(oldPageFolder);
|
|
876
|
-
const existingRoute = Object.keys(this.routes).find((route) => this.routes[route]?.file ===
|
|
877
|
-
this.getRelativeRoutePath(oldIndexFilePath));
|
|
878
|
-
if (!existingRoute) {
|
|
879
|
-
throw new Error(`Route for ${oldName} not found`);
|
|
880
|
-
}
|
|
881
|
-
// Write the name attribute to the page file
|
|
882
|
-
await this.sourceTracker?.renamePage({
|
|
883
|
-
newName,
|
|
884
|
-
filePath: oldIndexFilePath,
|
|
885
|
-
});
|
|
886
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
887
|
-
await this.writeChanges(changes);
|
|
888
|
-
// A rename of a folder is an unlink followed by an add, so the file watcher will take over initializing this
|
|
889
|
-
// "new" page
|
|
890
|
-
await fs.rename(oldPageFolder, newPageFolder);
|
|
891
|
-
// Now we just clean up the routes
|
|
892
|
-
await this.removeRoute(oldIndexFilePath);
|
|
893
|
-
await this.addRoute(existingRoute, newIndexFilePath);
|
|
894
|
-
const newIndexFile = await readFile(newIndexFilePath);
|
|
895
|
-
if (!newIndexFile) {
|
|
896
|
-
throw new Error(`New index file ${newIndexFilePath} not found`);
|
|
897
|
-
}
|
|
898
|
-
this.tsFiles[newIndexFilePath] = newIndexFile;
|
|
899
|
-
await this.handleNonVisualChangeByDeletingIds(newIndexFilePath, newIndexFile);
|
|
900
|
-
this.emit("renamePage", newIndexFilePath);
|
|
901
|
-
// Re-add the watcher
|
|
902
|
-
this.watcher?.add(newPageFolder);
|
|
903
|
-
this.watcher?.add(oldPageFolder);
|
|
904
|
-
};
|
|
905
|
-
getRelativeRoutePath(filePath) {
|
|
906
|
-
if (!this.rootDir) {
|
|
907
|
-
throw new Error("Root directory not set");
|
|
908
|
-
}
|
|
909
|
-
// no leading slash
|
|
910
|
-
return path.relative(path.join(this.rootDir, PAGES_DIRECTORY), filePath);
|
|
911
|
-
}
|
|
912
|
-
// MARK: API operations
|
|
913
690
|
handleUpdateApi = async (payload) => {
|
|
914
691
|
const { api } = payload;
|
|
915
692
|
if (!this.sourceTracker) {
|
|
@@ -925,24 +702,24 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
925
702
|
if (!apiName) {
|
|
926
703
|
throw new Error("API name is not set");
|
|
927
704
|
}
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
const isNewApi = !this.getApiFiles()[
|
|
705
|
+
const apiDir = path.join(this.rootDir, "pages", api.pageName, "apis", apiName);
|
|
706
|
+
const apiPath = path.join(apiDir, "api.yaml");
|
|
707
|
+
const isNewApi = !this.getApiFiles()[apiPath];
|
|
931
708
|
try {
|
|
932
709
|
const stats = await fs.stat(apiDir);
|
|
933
710
|
if (!stats.isDirectory()) {
|
|
934
|
-
await
|
|
711
|
+
await fs.mkdir(apiDir, { recursive: true });
|
|
935
712
|
}
|
|
936
713
|
}
|
|
937
714
|
catch {
|
|
938
|
-
await
|
|
715
|
+
await fs.mkdir(apiDir, { recursive: true });
|
|
939
716
|
}
|
|
940
|
-
await this.writeFile(
|
|
717
|
+
await this.writeFile(apiPath, yaml.stringify(api.apiPb), "api");
|
|
941
718
|
const generationNumber = this.generationNumberSequence.next();
|
|
942
719
|
const apiDef = this.createClientApi(api);
|
|
943
720
|
let scopeId = "";
|
|
944
721
|
if (isNewApi) {
|
|
945
|
-
scopeId = await this.
|
|
722
|
+
scopeId = await this.createScopedApi(api);
|
|
946
723
|
}
|
|
947
724
|
else {
|
|
948
725
|
const scopeDef = this.sourceTracker.getScopeDefinitionForPage(api.pageName);
|
|
@@ -951,6 +728,20 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
951
728
|
this.emit("apiUpdate", { api: apiDef, scopeId });
|
|
952
729
|
return { api: apiDef, scopeId, generationNumber };
|
|
953
730
|
};
|
|
731
|
+
createScopedApi = async (api) => {
|
|
732
|
+
if (!this.sourceTracker) {
|
|
733
|
+
throw new Error("Source tracker not initialized");
|
|
734
|
+
}
|
|
735
|
+
// We want to add the API entity to our scope, but we do not want to emit entity events, because
|
|
736
|
+
// the API update event handles this particular side effect.
|
|
737
|
+
const scopeId = await this.sourceTracker.addApi({
|
|
738
|
+
pageName: api.pageName,
|
|
739
|
+
apiName: api.apiPb.metadata.name,
|
|
740
|
+
});
|
|
741
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
742
|
+
await this.writeChanges(changes);
|
|
743
|
+
return scopeId;
|
|
744
|
+
};
|
|
954
745
|
handleDeleteApi = async (payload) => {
|
|
955
746
|
const logger = getLogger();
|
|
956
747
|
const { apis } = payload;
|
|
@@ -958,34 +749,40 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
958
749
|
throw new Error("Root directory not set");
|
|
959
750
|
}
|
|
960
751
|
const rootDir = this.rootDir;
|
|
961
|
-
const
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
try {
|
|
970
|
-
const stats = await fs.stat(apiDir);
|
|
971
|
-
if (stats.isDirectory()) {
|
|
972
|
-
await fs.rmdir(apiDir, { recursive: true });
|
|
752
|
+
const executeDeleteApis = Promise.all(apis.map(({ apiName, pageName }) => {
|
|
753
|
+
return new Promise(
|
|
754
|
+
// eslint-disable-next-line no-async-promise-executor
|
|
755
|
+
async (resolve) => {
|
|
756
|
+
const apiFilePath = path.join(rootDir, "pages", pageName, "apis", apiName, "api.yaml");
|
|
757
|
+
const api = this.apiFiles[apiFilePath];
|
|
758
|
+
if (!api || !this.sourceTracker) {
|
|
759
|
+
return resolve(undefined);
|
|
973
760
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
761
|
+
const apiDir = path.join(rootDir, "pages", pageName, "apis", apiName);
|
|
762
|
+
try {
|
|
763
|
+
const stats = await fs.stat(apiDir);
|
|
764
|
+
if (stats.isDirectory()) {
|
|
765
|
+
await fs.rmdir(apiDir, { recursive: true });
|
|
766
|
+
}
|
|
767
|
+
delete this.apiFiles[apiFilePath];
|
|
768
|
+
const scopeId = await this.sourceTracker.deleteApi({
|
|
769
|
+
pageName,
|
|
770
|
+
apiName,
|
|
771
|
+
});
|
|
772
|
+
resolve({
|
|
773
|
+
apiName,
|
|
774
|
+
pageName,
|
|
775
|
+
scopeId,
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
catch (e) {
|
|
779
|
+
logger.warn(`Could not delete api ${apiFilePath} ${JSON.stringify(e)}`);
|
|
780
|
+
resolve(undefined);
|
|
781
|
+
}
|
|
782
|
+
resolve(undefined);
|
|
783
|
+
});
|
|
784
|
+
}));
|
|
785
|
+
const deletedApis = (await executeDeleteApis).filter((api) => api !== undefined);
|
|
989
786
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
990
787
|
await this.writeChanges(changes);
|
|
991
788
|
this.emit("apiDelete", {
|
|
@@ -1066,40 +863,229 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1066
863
|
// TODO: Should I delete here?
|
|
1067
864
|
}));
|
|
1068
865
|
};
|
|
1069
|
-
async
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
866
|
+
handleAddEntity = async (payload) => {
|
|
867
|
+
await this.sourceTracker?.addEntity({
|
|
868
|
+
scopeId: payload.scopeId,
|
|
869
|
+
entity: {
|
|
870
|
+
type: payload.type,
|
|
871
|
+
name: payload.name,
|
|
872
|
+
attributes: payload.attributes,
|
|
873
|
+
},
|
|
874
|
+
});
|
|
875
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
876
|
+
await this.writeChanges(changes, (fileName) => {
|
|
877
|
+
this.emit("addEntity", fileName, payload);
|
|
878
|
+
});
|
|
879
|
+
};
|
|
880
|
+
handleUpdateEntity = async (payload) => {
|
|
881
|
+
await this.sourceTracker?.updateEntity({
|
|
882
|
+
entityId: payload.entityId,
|
|
883
|
+
updates: payload.updates,
|
|
884
|
+
});
|
|
885
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
886
|
+
await this.writeChanges(changes, (fileName) => {
|
|
887
|
+
this.emit("updateEntity", fileName, payload);
|
|
888
|
+
});
|
|
889
|
+
};
|
|
890
|
+
handleDeleteEntity = async (payload) => {
|
|
891
|
+
const deletedEntityName = await this.sourceTracker?.deleteEntity({
|
|
892
|
+
entityId: payload.entityId,
|
|
893
|
+
});
|
|
894
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
895
|
+
await this.writeChanges(changes, (fileName) => {
|
|
896
|
+
if (deletedEntityName) {
|
|
897
|
+
this.emit("deleteEntity", fileName, deletedEntityName);
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
};
|
|
901
|
+
handleUpdateTheme = async (payload) => {
|
|
902
|
+
const { theme } = payload;
|
|
903
|
+
if (!this.rootDir) {
|
|
904
|
+
throw new Error("Root directory not set");
|
|
1073
905
|
}
|
|
1074
|
-
|
|
1075
|
-
this.sourceTracker?.
|
|
1076
|
-
|
|
1077
|
-
|
|
906
|
+
const filePath = path.join(this.rootDir, APP_THEME_FILE_NAME);
|
|
907
|
+
await this.sourceTracker?.updateTheme({
|
|
908
|
+
themeFilePath: filePath,
|
|
909
|
+
theme,
|
|
1078
910
|
});
|
|
1079
911
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
1080
912
|
await this.writeChanges(changes);
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
913
|
+
};
|
|
914
|
+
handleRenameElement = async (payload) => {
|
|
915
|
+
if (payload.kind === "component") {
|
|
916
|
+
return this.handleRenameComponent(payload);
|
|
917
|
+
}
|
|
918
|
+
else if (payload.kind === "entity") {
|
|
919
|
+
return this.handleRenameEntity(payload);
|
|
920
|
+
}
|
|
921
|
+
else if (payload.kind === "page") {
|
|
922
|
+
return this.handleRenamePage(payload);
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
handleRenameComponent = async (payload) => {
|
|
926
|
+
const { elementId, newName, oldName, scopeName } = payload;
|
|
927
|
+
await this.sourceTracker?.renameComponent({
|
|
928
|
+
widgetSourceId: elementId,
|
|
929
|
+
oldName,
|
|
930
|
+
newName,
|
|
931
|
+
scopeName,
|
|
1090
932
|
});
|
|
933
|
+
await this.renameIdentifierInApis({
|
|
934
|
+
elementId,
|
|
935
|
+
oldName,
|
|
936
|
+
newName,
|
|
937
|
+
});
|
|
938
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
939
|
+
await this.writeChanges(changes, (fileName) => {
|
|
940
|
+
this.emit("renameComponent", fileName);
|
|
941
|
+
});
|
|
942
|
+
};
|
|
943
|
+
handleRenameEntity = async (payload) => {
|
|
944
|
+
const { elementId, newName, oldName, scopeName } = payload;
|
|
945
|
+
await this.sourceTracker?.renameEntity({
|
|
946
|
+
entityId: elementId,
|
|
947
|
+
oldName,
|
|
948
|
+
newName,
|
|
949
|
+
scopeName,
|
|
950
|
+
});
|
|
951
|
+
await this.renameIdentifierInApis(payload);
|
|
952
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
953
|
+
await this.writeChanges(changes, (fileName) => {
|
|
954
|
+
this.emit("renameEntity", fileName);
|
|
955
|
+
});
|
|
956
|
+
};
|
|
957
|
+
handleRenamePage = async (payload) => {
|
|
958
|
+
const { newName, oldName } = payload;
|
|
959
|
+
if (!this.rootDir) {
|
|
960
|
+
throw new Error("Root directory not set");
|
|
961
|
+
}
|
|
962
|
+
const newPageFolder = getPageFolder(this.rootDir, newName);
|
|
963
|
+
const newIndexFilePath = path.join(newPageFolder, "index.tsx");
|
|
964
|
+
const oldIndexFilePath = path.join(this.rootDir, "pages", oldName, "index.tsx");
|
|
965
|
+
const oldPageFolder = getPageFolder(this.rootDir, oldName);
|
|
966
|
+
this.watcher?.unwatch(newPageFolder);
|
|
967
|
+
this.watcher?.unwatch(oldPageFolder);
|
|
968
|
+
const existingRoute = Object.keys(this.routes).find((route) => this.routes[route]?.file ===
|
|
969
|
+
this.getRelativeRoutePath(oldIndexFilePath));
|
|
970
|
+
if (!existingRoute) {
|
|
971
|
+
throw new Error(`Route for ${oldName} not found`);
|
|
972
|
+
}
|
|
973
|
+
// Write the name attribute to the page file
|
|
974
|
+
await this.sourceTracker?.renamePage({
|
|
975
|
+
newName,
|
|
976
|
+
filePath: oldIndexFilePath,
|
|
977
|
+
});
|
|
978
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
979
|
+
await this.writeChanges(changes);
|
|
980
|
+
// A rename of a folder is an unlink followed by an add, so the file watcher will take over initializing this
|
|
981
|
+
// "new" page
|
|
982
|
+
await fs.rename(oldPageFolder, newPageFolder);
|
|
983
|
+
// Now we just clean up the routes
|
|
984
|
+
await this.removeRoute(oldIndexFilePath);
|
|
985
|
+
await this.addRoute(existingRoute, newIndexFilePath);
|
|
986
|
+
const newIndexFile = await readFile(newIndexFilePath);
|
|
987
|
+
if (!newIndexFile) {
|
|
988
|
+
throw new Error(`New index file ${newIndexFilePath} not found`);
|
|
989
|
+
}
|
|
990
|
+
this.tsFiles[newIndexFilePath] = newIndexFile;
|
|
991
|
+
await this.handleNonVisualChangeByDeletingIds(newIndexFilePath, newIndexFile);
|
|
992
|
+
this.emit("renamePage", newIndexFilePath);
|
|
993
|
+
// Re-add the watcher
|
|
994
|
+
this.watcher?.add(newPageFolder);
|
|
995
|
+
this.watcher?.add(oldPageFolder);
|
|
996
|
+
};
|
|
997
|
+
getPageRoots(filePath) {
|
|
998
|
+
const scopeFilePath = path.join(path.dirname(filePath), "index.tsx");
|
|
999
|
+
const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
|
|
1000
|
+
if (!currentFile) {
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
return getPageRoots(filePath, currentFile);
|
|
1091
1004
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1005
|
+
getScope(filePath) {
|
|
1006
|
+
// get the scope.ts file in the same directory as path
|
|
1007
|
+
const scopeFilePath = path.join(path.dirname(filePath), SCOPE_FILE);
|
|
1008
|
+
const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
|
|
1009
|
+
if (!currentFile) {
|
|
1010
|
+
console.log("File not found", scopeFilePath);
|
|
1011
|
+
return null;
|
|
1012
|
+
}
|
|
1013
|
+
const scope = getScope(scopeFilePath, currentFile);
|
|
1014
|
+
return scope;
|
|
1015
|
+
}
|
|
1016
|
+
getRoutes() {
|
|
1017
|
+
const routes = [];
|
|
1018
|
+
for (const [path, { file }] of Object.entries(this.routes)) {
|
|
1019
|
+
routes.push({
|
|
1020
|
+
path,
|
|
1021
|
+
component: file,
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
return routes;
|
|
1025
|
+
}
|
|
1026
|
+
async addRoute(route, filePath) {
|
|
1027
|
+
if (!this.rootDir) {
|
|
1028
|
+
throw new Error("Root directory not set");
|
|
1029
|
+
}
|
|
1030
|
+
this.routes[route] = { file: this.getRelativeRoutePath(filePath) };
|
|
1031
|
+
await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
|
|
1032
|
+
}
|
|
1033
|
+
async removeRoute(filePath) {
|
|
1034
|
+
if (!this.rootDir) {
|
|
1035
|
+
throw new Error("Root directory not set");
|
|
1036
|
+
}
|
|
1037
|
+
const relativeFilePath = this.getRelativeRoutePath(filePath);
|
|
1038
|
+
this.routes = Object.fromEntries(Object.entries(this.routes).filter(([_, value]) => value.file !== relativeFilePath));
|
|
1039
|
+
await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
|
|
1040
|
+
}
|
|
1041
|
+
async writeChanges(changes, callback) {
|
|
1042
|
+
return Promise.all(changes.map(async ({ fileName, source, kind }) => {
|
|
1043
|
+
await this.writeFile(fileName, source, kind);
|
|
1044
|
+
return callback?.(fileName, source);
|
|
1045
|
+
}));
|
|
1046
|
+
}
|
|
1047
|
+
getNodeForWidgetSourceId(id) {
|
|
1048
|
+
return this.sourceTracker?.getElementToLocation(id);
|
|
1049
|
+
}
|
|
1050
|
+
getAstForWidgetSourceId(id) {
|
|
1051
|
+
const filePath = this.sourceTracker?.getElementToFilePath(id);
|
|
1052
|
+
if (!filePath) {
|
|
1053
|
+
return null;
|
|
1054
|
+
}
|
|
1055
|
+
return this.sourceTracker?.getCurrentFiles()[filePath]?.ast;
|
|
1056
|
+
}
|
|
1057
|
+
renameIdentifierInApis = async ({ elementId, oldName, newName, parentBinding, }) => {
|
|
1058
|
+
const apisInScope = structuredClone(this.getApisInScope(elementId));
|
|
1059
|
+
await this.renameManager.renameEntityInApis({
|
|
1060
|
+
oldName,
|
|
1061
|
+
newName,
|
|
1062
|
+
parentBinding,
|
|
1063
|
+
apis: apisInScope,
|
|
1064
|
+
});
|
|
1065
|
+
// only save the APIs that have changed
|
|
1066
|
+
await Promise.all(apisInScope.map(({ api, filePath }) => {
|
|
1067
|
+
if (isEqual(api?.apiPb, this.apiFiles[filePath]?.apiPb)) {
|
|
1068
|
+
return Promise.resolve();
|
|
1096
1069
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1070
|
+
return this.writeFile(filePath, yaml.stringify(api?.apiPb), "api");
|
|
1071
|
+
}));
|
|
1072
|
+
};
|
|
1073
|
+
getApisInScope = (elementId) => {
|
|
1074
|
+
const filePath = this.sourceTracker?.getElementToFilePath(elementId);
|
|
1075
|
+
if (!filePath) {
|
|
1076
|
+
return [];
|
|
1077
|
+
}
|
|
1078
|
+
const pagePath = path.dirname(filePath);
|
|
1079
|
+
return Object.entries(this.apiFiles)
|
|
1080
|
+
.filter(([filePath]) => filePath.includes(pagePath))
|
|
1081
|
+
.map(([filePath, api]) => ({ api, filePath }));
|
|
1082
|
+
};
|
|
1083
|
+
getRelativeRoutePath(filePath) {
|
|
1084
|
+
if (!this.rootDir) {
|
|
1085
|
+
throw new Error("Root directory not set");
|
|
1086
|
+
}
|
|
1087
|
+
// no leading slash
|
|
1088
|
+
return path.relative(path.join(this.rootDir, PAGES_DIRECTORY), filePath);
|
|
1103
1089
|
}
|
|
1104
1090
|
// Utilities for converting server API format to Client API format
|
|
1105
1091
|
// We internally save the API as the server does, but we return should always return it
|
|
@@ -1116,21 +1102,19 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1116
1102
|
},
|
|
1117
1103
|
};
|
|
1118
1104
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
};
|
|
1133
|
-
async processApiFileUpdates(filePath, fileType) {
|
|
1105
|
+
formatApisToClientApis(serverApis) {
|
|
1106
|
+
return Object.keys(serverApis).reduce((acc, key) => {
|
|
1107
|
+
if (!serverApis[key]) {
|
|
1108
|
+
return acc;
|
|
1109
|
+
}
|
|
1110
|
+
acc[key] = {
|
|
1111
|
+
api: this.createClientApi(serverApis[key]),
|
|
1112
|
+
scopeId: serverApis[key].scopeId,
|
|
1113
|
+
};
|
|
1114
|
+
return acc;
|
|
1115
|
+
}, {});
|
|
1116
|
+
}
|
|
1117
|
+
async processApiUpdates(filePath, fileType) {
|
|
1134
1118
|
let yamlPath = filePath;
|
|
1135
1119
|
if (fileType.type === "python-api-step" ||
|
|
1136
1120
|
fileType.type === "js-api-step") {
|
|
@@ -1148,7 +1132,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1148
1132
|
const parsedData = { apiPb: apiContent?.api };
|
|
1149
1133
|
if (!(yamlPath in this.apiFiles &&
|
|
1150
1134
|
isEqual(this.apiFiles[yamlPath]?.apiPb, parsedData.apiPb))) {
|
|
1151
|
-
const { updatedApi, pageName, isNewApi } = this.
|
|
1135
|
+
const { updatedApi, pageName, isNewApi } = this.updateApi({
|
|
1152
1136
|
api: parsedData.apiPb,
|
|
1153
1137
|
stepPathMap: apiContent.stepPathMap,
|
|
1154
1138
|
}, yamlPath);
|
|
@@ -1159,7 +1143,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1159
1143
|
return;
|
|
1160
1144
|
}
|
|
1161
1145
|
if (isNewApi) {
|
|
1162
|
-
await this.
|
|
1146
|
+
await this.createScopedApi(updatedApi);
|
|
1163
1147
|
}
|
|
1164
1148
|
this.emit("apiManualUpdate", {
|
|
1165
1149
|
api: this.createClientApi(updatedApi),
|
|
@@ -1175,56 +1159,9 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1175
1159
|
logger.error(`Error updating API: ${yamlPath}, error: ${error}`);
|
|
1176
1160
|
}
|
|
1177
1161
|
}
|
|
1178
|
-
updateInternalApiData = (content, path) => {
|
|
1179
|
-
if (!this.rootDir) {
|
|
1180
|
-
throw new Error("Root directory not set");
|
|
1181
|
-
}
|
|
1182
|
-
const { api: apiContents, stepPathMap } = content;
|
|
1183
|
-
const pageName = getPageName(path);
|
|
1184
|
-
let scopeId = this.sourceTracker?.getScopeDefinitionForPage(pageName)?.id;
|
|
1185
|
-
if (!scopeId) {
|
|
1186
|
-
console.warn("Scope ID not found for API", apiContents.metadata.name);
|
|
1187
|
-
scopeId = "";
|
|
1188
|
-
}
|
|
1189
|
-
const updatedApi = {
|
|
1190
|
-
apiPb: yaml.parse(JSON.stringify(apiContents)),
|
|
1191
|
-
pageName,
|
|
1192
|
-
stepPathMap,
|
|
1193
|
-
scopeId,
|
|
1194
|
-
};
|
|
1195
|
-
const isNewApi = !this.apiFiles[path];
|
|
1196
|
-
this.apiFiles[path] = updatedApi;
|
|
1197
|
-
return { updatedApi, pageName, isNewApi };
|
|
1198
|
-
};
|
|
1199
|
-
renameIdentifierInApis = async ({ elementId, oldName, newName, parentBinding, }) => {
|
|
1200
|
-
const apisInScope = structuredClone(this.getApisInScope(elementId));
|
|
1201
|
-
await this.renameManager.renameEntityInApis({
|
|
1202
|
-
oldName,
|
|
1203
|
-
newName,
|
|
1204
|
-
parentBinding,
|
|
1205
|
-
apis: apisInScope,
|
|
1206
|
-
});
|
|
1207
|
-
// only save the APIs that have changed
|
|
1208
|
-
await Promise.all(apisInScope.map(({ api, filePath }) => {
|
|
1209
|
-
if (isEqual(api?.apiPb, this.apiFiles[filePath]?.apiPb)) {
|
|
1210
|
-
return Promise.resolve();
|
|
1211
|
-
}
|
|
1212
|
-
return this.writeFile(filePath, yaml.stringify(api?.apiPb), "api");
|
|
1213
|
-
}));
|
|
1214
|
-
};
|
|
1215
|
-
getApisInScope = (elementId) => {
|
|
1216
|
-
const filePath = this.sourceTracker?.getElementToFilePath(elementId);
|
|
1217
|
-
if (!filePath) {
|
|
1218
|
-
return [];
|
|
1219
|
-
}
|
|
1220
|
-
const pagePath = path.dirname(filePath);
|
|
1221
|
-
return Object.entries(this.apiFiles)
|
|
1222
|
-
.filter(([filePath]) => filePath.includes(pagePath))
|
|
1223
|
-
.map(([filePath, api]) => ({ api, filePath }));
|
|
1224
|
-
};
|
|
1225
1162
|
}
|
|
1226
1163
|
// Add new mock implementation
|
|
1227
|
-
export class MockFileSyncManager extends
|
|
1164
|
+
export class MockFileSyncManager extends FileSyncManager {
|
|
1228
1165
|
tsFiles = {};
|
|
1229
1166
|
apiFiles = {};
|
|
1230
1167
|
async watch(_watcher, _path) {
|
|
@@ -1300,7 +1237,7 @@ async function readFiles(dir) {
|
|
|
1300
1237
|
const getMergedApiContent = async (path) => {
|
|
1301
1238
|
return readAppApiYamlFile(path.split("/").slice(0, -1).join("/"));
|
|
1302
1239
|
};
|
|
1303
|
-
|
|
1240
|
+
const getPageName = (path) => {
|
|
1304
1241
|
const parts = path.split("/");
|
|
1305
1242
|
const pagesIndex = parts.findIndex((part) => part === "pages");
|
|
1306
1243
|
if (pagesIndex !== -1 && parts[pagesIndex + 1]) {
|