@superblocksteam/vite-plugin-file-sync 2.0.6-next.68 → 2.0.6-next.7
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 +12 -25
- 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 +39 -52
- package/dist/file-system-manager.d.ts.map +1 -1
- package/dist/file-system-manager.js +514 -633
- 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/routing.d.ts +2 -2
- package/dist/routing.d.ts.map +1 -1
- package/dist/routing.js +1 -21
- package/dist/routing.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 +5 -7
- 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/dist/util/tracing.d.ts +4 -0
- package/dist/util/tracing.d.ts.map +1 -0
- package/dist/util/tracing.js +56 -0
- package/dist/util/tracing.js.map +1 -0
- package/dist/util.d.ts +0 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +0 -8
- package/dist/util.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";
|
|
@@ -25,7 +24,6 @@ import { RenameManager } from "./rename-manager.js";
|
|
|
25
24
|
import { SourceTracker } from "./source-tracker.js";
|
|
26
25
|
import { traverse } from "./traverse.js";
|
|
27
26
|
import { getErrorMeta, getLogger } from "./util/logger.js";
|
|
28
|
-
import { getPageName } from "./util.js";
|
|
29
27
|
const SUPPORTED_FILETYPES = [
|
|
30
28
|
{
|
|
31
29
|
type: "tsx",
|
|
@@ -47,41 +45,26 @@ const SUPPORTED_FILETYPES = [
|
|
|
47
45
|
type: "js-api-step",
|
|
48
46
|
extension: ".js",
|
|
49
47
|
},
|
|
50
|
-
{
|
|
51
|
-
type: "json",
|
|
52
|
-
extension: ".json",
|
|
53
|
-
},
|
|
54
48
|
];
|
|
55
49
|
const APP_THEME_FILE_NAME = "appTheme.ts";
|
|
56
|
-
export class
|
|
50
|
+
export class FileSyncManager extends TracedEventEmitter {
|
|
57
51
|
rootDir;
|
|
58
52
|
tsFiles = {};
|
|
59
53
|
apiFiles = {};
|
|
60
54
|
sourceTracker;
|
|
61
55
|
fsOperationQueue;
|
|
62
|
-
operationProcessor;
|
|
63
56
|
generationNumberSequence;
|
|
64
57
|
routes = {};
|
|
65
|
-
routeChangesQueue = [];
|
|
66
58
|
watcher;
|
|
67
59
|
registeredComponentPaths = {};
|
|
68
60
|
renameManager = new RenameManager();
|
|
69
61
|
_tracer;
|
|
70
|
-
transactionNonce = Date.now();
|
|
71
|
-
pendingTransactions = new Set();
|
|
72
|
-
processedTransactions = [];
|
|
73
62
|
constructor(fsOperationQueue, generationNumberSequence, tracer) {
|
|
74
63
|
super(tracer, { captureRejections: true });
|
|
75
64
|
this.rootDir = "/";
|
|
76
65
|
this.fsOperationQueue = fsOperationQueue;
|
|
77
66
|
this.generationNumberSequence = generationNumberSequence;
|
|
78
67
|
this._tracer = tracer;
|
|
79
|
-
// intentionally a new queue here, we don't want to share the queue with the fsOperationQueue
|
|
80
|
-
this.operationProcessor = new OperationProcessor({
|
|
81
|
-
batchWindowMs: 50,
|
|
82
|
-
maxBatchSize: 100,
|
|
83
|
-
});
|
|
84
|
-
this.operationProcessor.disable();
|
|
85
68
|
applyErrorHandling(this, {
|
|
86
69
|
watch: { operation: "editing from code" },
|
|
87
70
|
handleCreatePage: { operation: "creating a page" },
|
|
@@ -127,7 +110,27 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
127
110
|
}
|
|
128
111
|
return path.join(this.rootDir, "App.tsx");
|
|
129
112
|
}
|
|
130
|
-
|
|
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
|
+
};
|
|
131
134
|
async watch(watcher, rootPath) {
|
|
132
135
|
const logger = getLogger();
|
|
133
136
|
this.rootDir = rootPath;
|
|
@@ -213,10 +216,178 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
213
216
|
return;
|
|
214
217
|
const { type, path } = file;
|
|
215
218
|
if (type === "api") {
|
|
216
|
-
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
|
+
});
|
|
217
349
|
}
|
|
218
350
|
});
|
|
219
|
-
|
|
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];
|
|
220
391
|
}
|
|
221
392
|
initializeSourceTracker() {
|
|
222
393
|
this.sourceTracker = new SourceTracker(this._tracer);
|
|
@@ -325,246 +496,6 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
325
496
|
return null;
|
|
326
497
|
}
|
|
327
498
|
}
|
|
328
|
-
enableOperationsQueue() {
|
|
329
|
-
this.operationProcessor.enable();
|
|
330
|
-
}
|
|
331
|
-
disableOperationsQueue() {
|
|
332
|
-
this.operationProcessor.disable();
|
|
333
|
-
}
|
|
334
|
-
async flushOperations() {
|
|
335
|
-
await this.operationProcessor.flush();
|
|
336
|
-
}
|
|
337
|
-
// MARK: file change handling
|
|
338
|
-
handleFileChange = async (event, filePath) => {
|
|
339
|
-
const logger = getLogger();
|
|
340
|
-
logger.info(`File changed: ${filePath}, event: ${event}`);
|
|
341
|
-
const rootPath = this.rootDir;
|
|
342
|
-
if (!rootPath) {
|
|
343
|
-
throw new Error("Root directory not set");
|
|
344
|
-
}
|
|
345
|
-
// Skip directory events
|
|
346
|
-
if (event === "addDir" || event === "unlinkDir") {
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
const routePath = path.join(rootPath, ROUTES_FILE);
|
|
350
|
-
const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
|
|
351
|
-
// Only handle files we care about and that are in our root path
|
|
352
|
-
if (!fileType || !filePath.startsWith(rootPath)) {
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
// Queue the operation based on the event type
|
|
356
|
-
switch (event) {
|
|
357
|
-
case "add": {
|
|
358
|
-
const data = await readFile(filePath);
|
|
359
|
-
if (typeof data !== "string")
|
|
360
|
-
return;
|
|
361
|
-
const isPage = isPageFilePath(filePath);
|
|
362
|
-
if (isPage) {
|
|
363
|
-
void this.operationProcessor.addOperation({
|
|
364
|
-
metadata: {
|
|
365
|
-
filePath,
|
|
366
|
-
},
|
|
367
|
-
execute: async () => {
|
|
368
|
-
const file = await readFile(filePath);
|
|
369
|
-
if (!file) {
|
|
370
|
-
logger.error(`Failed to read file: ${filePath}`);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
if (!(filePath in this.tsFiles)) {
|
|
374
|
-
this.tsFiles[filePath] = file;
|
|
375
|
-
this.handleNonVisualChangeByDeletingIds(filePath, file);
|
|
376
|
-
this.emit("addPage", filePath);
|
|
377
|
-
}
|
|
378
|
-
},
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
void this.operationProcessor.addOperation({
|
|
383
|
-
metadata: {
|
|
384
|
-
filePath,
|
|
385
|
-
},
|
|
386
|
-
execute: async () => {
|
|
387
|
-
switch (fileType.type) {
|
|
388
|
-
case "api":
|
|
389
|
-
case "python-api-step":
|
|
390
|
-
case "js-api-step": {
|
|
391
|
-
await this.processApiFileUpdates(filePath, fileType);
|
|
392
|
-
break;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
break;
|
|
399
|
-
}
|
|
400
|
-
case "change": {
|
|
401
|
-
if (filePath === routePath) {
|
|
402
|
-
void this.operationProcessor.addOperation({
|
|
403
|
-
metadata: {
|
|
404
|
-
filePath,
|
|
405
|
-
},
|
|
406
|
-
priority: true,
|
|
407
|
-
execute: async () => {
|
|
408
|
-
try {
|
|
409
|
-
const data = JSON.parse((await readFile(filePath)) ?? "{}");
|
|
410
|
-
if (!isEqual(this.routes, data))
|
|
411
|
-
this.routes = data;
|
|
412
|
-
// this.addRoute assigns this.routes itself, causing this.routes === data
|
|
413
|
-
// but we still want to emit the event to HMR root.tsx
|
|
414
|
-
this.emit("routesChanged", this.routes);
|
|
415
|
-
}
|
|
416
|
-
catch (e) {
|
|
417
|
-
logger.error("Error parsing routes file", getErrorMeta(e));
|
|
418
|
-
}
|
|
419
|
-
},
|
|
420
|
-
});
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
void this.operationProcessor.addOperation({
|
|
425
|
-
metadata: {
|
|
426
|
-
filePath,
|
|
427
|
-
},
|
|
428
|
-
execute: async () => {
|
|
429
|
-
const fileType = SUPPORTED_FILETYPES.find((f) => filePath.endsWith(f.extension));
|
|
430
|
-
if (!fileType || !filePath.startsWith(rootPath)) {
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
const data = await readFile(filePath);
|
|
434
|
-
if (typeof data !== "string")
|
|
435
|
-
return;
|
|
436
|
-
switch (fileType.type) {
|
|
437
|
-
case "tsx":
|
|
438
|
-
case "scope":
|
|
439
|
-
{
|
|
440
|
-
if (!(filePath in this.tsFiles &&
|
|
441
|
-
this.tsFiles[filePath] === data)) {
|
|
442
|
-
logger.info(`File changed: ${filePath} updating AST tracker`);
|
|
443
|
-
this.tsFiles[filePath] = data;
|
|
444
|
-
// only update the source tracker if the file is different
|
|
445
|
-
this.handleNonVisualChangeByDeletingIds(filePath, data);
|
|
446
|
-
this.emit("fileChanged", filePath, data, true);
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
logger.info(`File unchanged from last tracked state: ${filePath}`);
|
|
450
|
-
this.emit("fileChanged", filePath, data, false);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
break;
|
|
454
|
-
case "api":
|
|
455
|
-
case "python-api-step":
|
|
456
|
-
case "js-api-step":
|
|
457
|
-
{
|
|
458
|
-
await this.processApiFileUpdates(filePath, fileType);
|
|
459
|
-
}
|
|
460
|
-
break;
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
467
|
-
case "unlink": {
|
|
468
|
-
if (filePath in this.tsFiles) {
|
|
469
|
-
void this.operationProcessor.addOperation({
|
|
470
|
-
metadata: {
|
|
471
|
-
filePath,
|
|
472
|
-
},
|
|
473
|
-
execute: async () => {
|
|
474
|
-
await this.deleteTsFile(filePath);
|
|
475
|
-
},
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
else if (filePath in this.apiFiles) {
|
|
479
|
-
void this.operationProcessor.addOperation({
|
|
480
|
-
metadata: {
|
|
481
|
-
filePath,
|
|
482
|
-
},
|
|
483
|
-
execute: async () => {
|
|
484
|
-
await this.removeApiData(filePath);
|
|
485
|
-
},
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
};
|
|
492
|
-
async deleteTsFile(filePath) {
|
|
493
|
-
delete this.tsFiles[filePath];
|
|
494
|
-
this.sourceTracker?.removeFile(filePath);
|
|
495
|
-
if (isPageFilePath(filePath)) {
|
|
496
|
-
this.emit("deletePage", filePath);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
getTsFilePaths() {
|
|
500
|
-
return Object.keys(this.tsFiles);
|
|
501
|
-
}
|
|
502
|
-
getSourceTracker() {
|
|
503
|
-
return this.sourceTracker;
|
|
504
|
-
}
|
|
505
|
-
// MARK: fs read/write
|
|
506
|
-
async writeFile(path, content, kind) {
|
|
507
|
-
switch (kind) {
|
|
508
|
-
case "ts": {
|
|
509
|
-
// happens eagerly regardless of error - possible desync
|
|
510
|
-
this.tsFiles[path] = content;
|
|
511
|
-
// TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
|
|
512
|
-
await this.fsOperationQueue.enqueue(async () => {
|
|
513
|
-
await fs.writeFile(path, content);
|
|
514
|
-
});
|
|
515
|
-
break;
|
|
516
|
-
}
|
|
517
|
-
case "api": {
|
|
518
|
-
const currentApiFile = this.apiFiles[path];
|
|
519
|
-
const apiPb = yaml.parse(content);
|
|
520
|
-
// Client APIs have id, but server APIs don't
|
|
521
|
-
if (apiPb.metadata.id) {
|
|
522
|
-
delete apiPb.metadata.id;
|
|
523
|
-
}
|
|
524
|
-
const stepPathMap = currentApiFile?.stepPathMap ?? {};
|
|
525
|
-
this.updateInternalApiData({ api: apiPb, stepPathMap }, path);
|
|
526
|
-
// TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
|
|
527
|
-
await this.fsOperationQueue.enqueue(async () => {
|
|
528
|
-
await writeApiFiles({ apiPb }, "api", path.split("/").slice(0, -1).join("/"), false, [], [], { extractLargeSourceFiles: true, minLinesForExtraction: 1 }, new Set(Object.keys(this.apiFiles)), stepPathMap);
|
|
529
|
-
// stepPathMap will be generated after the write when the file doesn't exist
|
|
530
|
-
if (this.apiFiles[path]) {
|
|
531
|
-
this.apiFiles[path].stepPathMap = stepPathMap;
|
|
532
|
-
}
|
|
533
|
-
});
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
default: {
|
|
537
|
-
// TODO(george): as an optimization, we could update the memory snapshot of the file that SyncService is holding
|
|
538
|
-
await this.fsOperationQueue.enqueue(async () => {
|
|
539
|
-
await fs.writeFile(path, content);
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
readFile(path) {
|
|
545
|
-
return this.tsFiles[path];
|
|
546
|
-
}
|
|
547
|
-
async addRoute(route, filePath) {
|
|
548
|
-
if (!this.rootDir) {
|
|
549
|
-
throw new Error("Root directory not set");
|
|
550
|
-
}
|
|
551
|
-
this.routes[route] = { file: this.getRelativeRoutePath(filePath) };
|
|
552
|
-
await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
|
|
553
|
-
}
|
|
554
|
-
async removeRoute(filePath) {
|
|
555
|
-
if (!this.rootDir) {
|
|
556
|
-
throw new Error("Root directory not set");
|
|
557
|
-
}
|
|
558
|
-
const relativeFilePath = this.getRelativeRoutePath(filePath);
|
|
559
|
-
this.routes = Object.fromEntries(Object.entries(this.routes).filter(([_, value]) => value.file !== relativeFilePath));
|
|
560
|
-
await this.writeFile(path.join(this.rootDir, ROUTES_FILE), JSON.stringify(this.routes, null, 2));
|
|
561
|
-
}
|
|
562
|
-
async writeChanges(changes, callback) {
|
|
563
|
-
return Promise.all(changes.map(async ({ fileName, source, kind }) => {
|
|
564
|
-
await this.writeFile(fileName, source, kind);
|
|
565
|
-
return callback?.(fileName, source);
|
|
566
|
-
}));
|
|
567
|
-
}
|
|
568
499
|
getLocalBindingEntities(path) {
|
|
569
500
|
const logger = getLogger();
|
|
570
501
|
const files = this.sourceTracker?.getCurrentFiles();
|
|
@@ -592,36 +523,8 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
592
523
|
});
|
|
593
524
|
return Array.from(localBindingEntities);
|
|
594
525
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
|
|
598
|
-
if (!currentFile) {
|
|
599
|
-
return null;
|
|
600
|
-
}
|
|
601
|
-
return getPageRoots(filePath, currentFile);
|
|
602
|
-
}
|
|
603
|
-
getScope(filePath) {
|
|
604
|
-
// get the scope.ts file in the same directory as path
|
|
605
|
-
const scopeFilePath = path.join(path.dirname(filePath), SCOPE_FILE);
|
|
606
|
-
const currentFile = this.sourceTracker?.getCurrentFiles()[scopeFilePath];
|
|
607
|
-
if (!currentFile) {
|
|
608
|
-
console.log("File not found", scopeFilePath);
|
|
609
|
-
return null;
|
|
610
|
-
}
|
|
611
|
-
const scope = getScope(scopeFilePath, currentFile);
|
|
612
|
-
return scope;
|
|
613
|
-
}
|
|
614
|
-
getRoutes() {
|
|
615
|
-
const routes = [];
|
|
616
|
-
for (const [path, { file }] of Object.entries(this.routes)) {
|
|
617
|
-
routes.push({
|
|
618
|
-
path,
|
|
619
|
-
component: file,
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
return routes;
|
|
623
|
-
}
|
|
624
|
-
// MARK: transaction handling
|
|
526
|
+
pendingTransactions = new Set();
|
|
527
|
+
processedTransactions = [];
|
|
625
528
|
flushTransactions = () => {
|
|
626
529
|
// TODO do something more sophisticated than this.
|
|
627
530
|
this.processedTransactions = this.processedTransactions.slice(-20);
|
|
@@ -634,92 +537,42 @@ export class FileSystemManager extends TracedEventEmitter {
|
|
|
634
537
|
}
|
|
635
538
|
this.pendingTransactions.add(transactionId);
|
|
636
539
|
};
|
|
540
|
+
transactionNonce = Date.now();
|
|
637
541
|
// Until we have a sophisticated way to track ALL operations, including non-UI initiated ops, we will use a nonce
|
|
638
542
|
// TODO https://github.com/superblocksteam/superblocks/pull/11788
|
|
639
543
|
getProcessedTransactionsWithNonce() {
|
|
640
544
|
const nonce = `t-${this.transactionNonce++}`;
|
|
641
545
|
return this.processedTransactions.concat(nonce);
|
|
642
546
|
}
|
|
643
|
-
// MARK: editor operations
|
|
644
547
|
handleCreatePage = async (payload) => {
|
|
645
|
-
|
|
646
|
-
const { name, route, navigateToRoute, routeTestParams } = payload;
|
|
548
|
+
const { name } = payload;
|
|
647
549
|
if (!this.rootDir) {
|
|
648
550
|
throw new Error("Root directory not set");
|
|
649
551
|
}
|
|
650
|
-
if (!route) {
|
|
651
|
-
throw new Error("Route is required when creating a page");
|
|
652
|
-
}
|
|
653
552
|
const pagePath = getPageFolder(this.rootDir, name);
|
|
654
553
|
const pageIndexPath = path.join(pagePath, "index.tsx");
|
|
655
|
-
const
|
|
656
|
-
const pageContent = /*js*/ `import {
|
|
657
|
-
SbPage,
|
|
658
|
-
Dim,
|
|
659
|
-
SbSection,
|
|
660
|
-
SbColumn,
|
|
661
|
-
registerPage,
|
|
662
|
-
} from "@superblocksteam/library";
|
|
663
|
-
import { ${name}, ${name}Scope } from "./scope";
|
|
664
|
-
|
|
554
|
+
const pageContent = /*js*/ `import { SbPage, SbContainer, registerPage } from "@superblocksteam/library";
|
|
665
555
|
function Page() {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
<SbSection height={Dim.fill()}>
|
|
670
|
-
<SbColumn width={Dim.fill()}></SbColumn>
|
|
671
|
-
</SbSection>
|
|
672
|
-
</SbPage>
|
|
673
|
-
);
|
|
556
|
+
return <SbPage name="${name}">
|
|
557
|
+
<SbContainer width={Dim.fill()} />
|
|
558
|
+
</SbPage>;
|
|
674
559
|
}
|
|
675
560
|
|
|
676
|
-
export default registerPage(Page, ${name}
|
|
561
|
+
export default registerPage(Page, { name: "${name}" });
|
|
677
562
|
`;
|
|
678
|
-
const scopeContent = /*js*/ `import { createSbScope } from "@superblocksteam/library";
|
|
679
|
-
|
|
680
|
-
export const ${name}Scope = createSbScope<{}>(({ entities }) => ({}), {
|
|
681
|
-
name: "${name}",
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
export const ${name} = ${name}Scope.entities;
|
|
685
|
-
`;
|
|
686
|
-
if (navigateToRoute) {
|
|
687
|
-
this.routeChangesQueue.push({
|
|
688
|
-
route: route,
|
|
689
|
-
routeTestParams: routeTestParams,
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
this.watcher?.unwatch(pagePath);
|
|
693
563
|
await fs.mkdir(pagePath, { recursive: true });
|
|
694
564
|
await this.writeFile(pageIndexPath, pageContent, "ts");
|
|
695
|
-
await this.writeFile(scopePath, scopeContent, "ts");
|
|
696
565
|
await this.handleNonVisualChangeByDeletingIds(pageIndexPath, pageContent);
|
|
697
|
-
await this.handleNonVisualChangeByDeletingIds(scopePath, scopeContent);
|
|
698
|
-
this.watcher?.add(pagePath);
|
|
699
|
-
await this.addRoute(route, pageIndexPath);
|
|
700
566
|
this.emit("addPage", pageIndexPath);
|
|
701
567
|
};
|
|
702
|
-
handleDeletePage = async (payload) => {
|
|
703
|
-
const { name } = payload;
|
|
704
|
-
if (!this.rootDir) {
|
|
705
|
-
throw new Error("Root directory not set");
|
|
706
|
-
}
|
|
707
|
-
const pagePath = getPageFolder(this.rootDir, name);
|
|
708
|
-
await fs.rm(pagePath, { recursive: true, force: true });
|
|
709
|
-
await this.removeRoute(name);
|
|
710
|
-
this.emit("deletePage", pagePath);
|
|
711
|
-
};
|
|
712
|
-
get routeChange() {
|
|
713
|
-
return this.routeChangesQueue.shift();
|
|
714
|
-
}
|
|
715
568
|
handleReparent = async (payload, writeFile = true) => {
|
|
716
569
|
const { from, to, changedProps, transaction } = payload;
|
|
717
570
|
this.trackTransaction(transaction?.id);
|
|
718
|
-
this.sourceTracker?.setProperties({
|
|
571
|
+
await this.sourceTracker?.setProperties({
|
|
719
572
|
source: from.source,
|
|
720
573
|
changes: changedProps ?? {},
|
|
721
574
|
});
|
|
722
|
-
this.sourceTracker?.moveElement({
|
|
575
|
+
await this.sourceTracker?.moveElement({
|
|
723
576
|
from,
|
|
724
577
|
to,
|
|
725
578
|
});
|
|
@@ -733,7 +586,7 @@ export const ${name} = ${name}Scope.entities;
|
|
|
733
586
|
};
|
|
734
587
|
handleCreateComponent = async (payload, writeFile = true) => {
|
|
735
588
|
this.trackTransaction(payload.transaction?.id);
|
|
736
|
-
const sourceId = this.sourceTracker?.addElement(payload);
|
|
589
|
+
const sourceId = await this.sourceTracker?.addElement(payload);
|
|
737
590
|
if (writeFile) {
|
|
738
591
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
739
592
|
await this.writeChanges(changes ?? [], (fileName) => {
|
|
@@ -747,7 +600,7 @@ export const ${name} = ${name}Scope.entities;
|
|
|
747
600
|
const { elements, transaction } = payload;
|
|
748
601
|
this.trackTransaction(transaction?.id);
|
|
749
602
|
for (const element of elements) {
|
|
750
|
-
this.sourceTracker?.deleteElement({
|
|
603
|
+
await this.sourceTracker?.deleteElement({
|
|
751
604
|
source: element.source,
|
|
752
605
|
scopeName: element.scopeName,
|
|
753
606
|
});
|
|
@@ -763,7 +616,7 @@ export const ${name} = ${name}Scope.entities;
|
|
|
763
616
|
handleSetProperty = async (payload, writeFile = true) => {
|
|
764
617
|
const { element: { source }, property, value, transaction, } = payload;
|
|
765
618
|
this.trackTransaction(transaction?.id);
|
|
766
|
-
this.sourceTracker?.setProperty({
|
|
619
|
+
await this.sourceTracker?.setProperty({
|
|
767
620
|
source,
|
|
768
621
|
property,
|
|
769
622
|
info: value,
|
|
@@ -778,7 +631,7 @@ export const ${name} = ${name}Scope.entities;
|
|
|
778
631
|
handleSetProperties = async (payload, writeFile = true) => {
|
|
779
632
|
const { element: { source }, properties, transaction, } = payload;
|
|
780
633
|
this.trackTransaction(transaction?.id);
|
|
781
|
-
this.sourceTracker?.setProperties({
|
|
634
|
+
await this.sourceTracker?.setProperties({
|
|
782
635
|
source,
|
|
783
636
|
changes: properties,
|
|
784
637
|
});
|
|
@@ -834,180 +687,39 @@ export const ${name} = ${name}Scope.entities;
|
|
|
834
687
|
this.flushTransactions();
|
|
835
688
|
return returnValues;
|
|
836
689
|
};
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
this.sourceTracker
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
type: payload.type,
|
|
843
|
-
name: payload.name,
|
|
844
|
-
attributes: payload.attributes,
|
|
845
|
-
},
|
|
846
|
-
});
|
|
847
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
848
|
-
await this.writeChanges(changes, (fileName) => {
|
|
849
|
-
this.emit("addEntity", fileName, payload);
|
|
850
|
-
});
|
|
851
|
-
};
|
|
852
|
-
handleUpdateEntity = async (payload) => {
|
|
853
|
-
this.sourceTracker?.updateEntity({
|
|
854
|
-
entityId: payload.entityId,
|
|
855
|
-
updates: payload.updates,
|
|
856
|
-
});
|
|
857
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
858
|
-
await this.writeChanges(changes, (fileName) => {
|
|
859
|
-
this.emit("updateEntity", fileName, payload);
|
|
860
|
-
});
|
|
861
|
-
};
|
|
862
|
-
handleDeleteEntity = async (payload) => {
|
|
863
|
-
const deletedEntityName = this.sourceTracker?.deleteEntity({
|
|
864
|
-
entityId: payload.entityId,
|
|
865
|
-
});
|
|
866
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
867
|
-
await this.writeChanges(changes, (fileName) => {
|
|
868
|
-
if (deletedEntityName) {
|
|
869
|
-
this.emit("deleteEntity", fileName, deletedEntityName);
|
|
870
|
-
}
|
|
871
|
-
});
|
|
872
|
-
};
|
|
873
|
-
handleUpdateTheme = async (payload) => {
|
|
874
|
-
const { theme } = payload;
|
|
690
|
+
handleUpdateApi = async (payload) => {
|
|
691
|
+
const { api } = payload;
|
|
692
|
+
if (!this.sourceTracker) {
|
|
693
|
+
throw new Error("Source tracker not initialized");
|
|
694
|
+
}
|
|
875
695
|
if (!this.rootDir) {
|
|
876
696
|
throw new Error("Root directory not set");
|
|
877
697
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
themeFilePath: filePath,
|
|
881
|
-
theme,
|
|
882
|
-
});
|
|
883
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
884
|
-
await this.writeChanges(changes);
|
|
885
|
-
};
|
|
886
|
-
// MARK: rename operations
|
|
887
|
-
handleRenameElement = async (payload) => {
|
|
888
|
-
if (payload.kind === "component") {
|
|
889
|
-
return this.handleRenameComponent(payload);
|
|
698
|
+
if (!api.pageName) {
|
|
699
|
+
throw new Error("API page name is not set");
|
|
890
700
|
}
|
|
891
|
-
|
|
892
|
-
|
|
701
|
+
const apiName = api.apiPb.metadata.name;
|
|
702
|
+
if (!apiName) {
|
|
703
|
+
throw new Error("API name is not set");
|
|
893
704
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
};
|
|
898
|
-
handleRenameComponent = async (payload) => {
|
|
899
|
-
const { elementId, newName, oldName, scopeName } = payload;
|
|
900
|
-
await this.sourceTracker?.renameComponent({
|
|
901
|
-
widgetSourceId: elementId,
|
|
902
|
-
oldName,
|
|
903
|
-
newName,
|
|
904
|
-
scopeName,
|
|
905
|
-
});
|
|
906
|
-
await this.renameIdentifierInApis({
|
|
907
|
-
elementId,
|
|
908
|
-
oldName,
|
|
909
|
-
newName,
|
|
910
|
-
});
|
|
911
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
912
|
-
await this.writeChanges(changes, (fileName) => {
|
|
913
|
-
this.emit("renameComponent", fileName);
|
|
914
|
-
});
|
|
915
|
-
};
|
|
916
|
-
handleRenameEntity = async (payload) => {
|
|
917
|
-
const { elementId, newName, oldName, scopeName } = payload;
|
|
918
|
-
this.sourceTracker?.renameEntity({
|
|
919
|
-
entityId: elementId,
|
|
920
|
-
oldName,
|
|
921
|
-
newName,
|
|
922
|
-
scopeName,
|
|
923
|
-
});
|
|
924
|
-
await this.renameIdentifierInApis(payload);
|
|
925
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
926
|
-
await this.writeChanges(changes, (fileName) => {
|
|
927
|
-
this.emit("renameEntity", fileName);
|
|
928
|
-
});
|
|
929
|
-
};
|
|
930
|
-
handleRenamePage = async (payload) => {
|
|
931
|
-
const { newName, oldName } = payload;
|
|
932
|
-
if (!this.rootDir) {
|
|
933
|
-
throw new Error("Root directory not set");
|
|
934
|
-
}
|
|
935
|
-
const newPageFolder = getPageFolder(this.rootDir, newName);
|
|
936
|
-
const newIndexFilePath = path.join(newPageFolder, "index.tsx");
|
|
937
|
-
const oldIndexFilePath = path.join(this.rootDir, "pages", oldName, "index.tsx");
|
|
938
|
-
const oldPageFolder = getPageFolder(this.rootDir, oldName);
|
|
939
|
-
this.watcher?.unwatch(newPageFolder);
|
|
940
|
-
this.watcher?.unwatch(oldPageFolder);
|
|
941
|
-
const existingRoute = Object.keys(this.routes).find((route) => this.routes[route]?.file ===
|
|
942
|
-
this.getRelativeRoutePath(oldIndexFilePath));
|
|
943
|
-
if (!existingRoute) {
|
|
944
|
-
throw new Error(`Route for ${oldName} not found`);
|
|
945
|
-
}
|
|
946
|
-
// Write the name attribute to the page file
|
|
947
|
-
await this.sourceTracker?.renamePage({
|
|
948
|
-
newName,
|
|
949
|
-
filePath: oldIndexFilePath,
|
|
950
|
-
});
|
|
951
|
-
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
952
|
-
await this.writeChanges(changes);
|
|
953
|
-
// A rename of a folder is an unlink followed by an add, so the file watcher will take over initializing this
|
|
954
|
-
// "new" page
|
|
955
|
-
await fs.rename(oldPageFolder, newPageFolder);
|
|
956
|
-
// Now we just clean up the routes
|
|
957
|
-
await this.removeRoute(oldIndexFilePath);
|
|
958
|
-
await this.addRoute(existingRoute, newIndexFilePath);
|
|
959
|
-
const newIndexFile = await readFile(newIndexFilePath);
|
|
960
|
-
if (!newIndexFile) {
|
|
961
|
-
throw new Error(`New index file ${newIndexFilePath} not found`);
|
|
962
|
-
}
|
|
963
|
-
this.tsFiles[newIndexFilePath] = newIndexFile;
|
|
964
|
-
await this.handleNonVisualChangeByDeletingIds(newIndexFilePath, newIndexFile);
|
|
965
|
-
this.emit("renamePage", newIndexFilePath);
|
|
966
|
-
// Re-add the watcher
|
|
967
|
-
this.watcher?.add(newPageFolder);
|
|
968
|
-
this.watcher?.add(oldPageFolder);
|
|
969
|
-
};
|
|
970
|
-
getRelativeRoutePath(filePath) {
|
|
971
|
-
if (!this.rootDir) {
|
|
972
|
-
throw new Error("Root directory not set");
|
|
973
|
-
}
|
|
974
|
-
// no leading slash
|
|
975
|
-
return path.relative(path.join(this.rootDir, PAGES_DIRECTORY), filePath);
|
|
976
|
-
}
|
|
977
|
-
// MARK: API operations
|
|
978
|
-
handleUpdateApi = async (payload) => {
|
|
979
|
-
const { api } = payload;
|
|
980
|
-
if (!this.sourceTracker) {
|
|
981
|
-
throw new Error("Source tracker not initialized");
|
|
982
|
-
}
|
|
983
|
-
if (!this.rootDir) {
|
|
984
|
-
throw new Error("Root directory not set");
|
|
985
|
-
}
|
|
986
|
-
if (!api.pageName) {
|
|
987
|
-
throw new Error("API page name is not set");
|
|
988
|
-
}
|
|
989
|
-
const apiName = api.apiPb.metadata.name;
|
|
990
|
-
if (!apiName) {
|
|
991
|
-
throw new Error("API name is not set");
|
|
992
|
-
}
|
|
993
|
-
const apiFilePath = getApiFilePath(this.rootDir, api.pageName, apiName);
|
|
994
|
-
const apiDir = path.dirname(apiFilePath);
|
|
995
|
-
const isNewApi = !this.getApiFiles()[apiFilePath];
|
|
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];
|
|
996
708
|
try {
|
|
997
709
|
const stats = await fs.stat(apiDir);
|
|
998
710
|
if (!stats.isDirectory()) {
|
|
999
|
-
await
|
|
711
|
+
await fs.mkdir(apiDir, { recursive: true });
|
|
1000
712
|
}
|
|
1001
713
|
}
|
|
1002
714
|
catch {
|
|
1003
|
-
await
|
|
715
|
+
await fs.mkdir(apiDir, { recursive: true });
|
|
1004
716
|
}
|
|
1005
|
-
await this.writeFile(
|
|
717
|
+
await this.writeFile(apiPath, yaml.stringify(api.apiPb), "api");
|
|
1006
718
|
const generationNumber = this.generationNumberSequence.next();
|
|
1007
719
|
const apiDef = this.createClientApi(api);
|
|
1008
720
|
let scopeId = "";
|
|
1009
721
|
if (isNewApi) {
|
|
1010
|
-
scopeId = await this.
|
|
722
|
+
scopeId = await this.createScopedApi(api);
|
|
1011
723
|
}
|
|
1012
724
|
else {
|
|
1013
725
|
const scopeDef = this.sourceTracker.getScopeDefinitionForPage(api.pageName);
|
|
@@ -1016,6 +728,20 @@ export const ${name} = ${name}Scope.entities;
|
|
|
1016
728
|
this.emit("apiUpdate", { api: apiDef, scopeId });
|
|
1017
729
|
return { api: apiDef, scopeId, generationNumber };
|
|
1018
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
|
+
};
|
|
1019
745
|
handleDeleteApi = async (payload) => {
|
|
1020
746
|
const logger = getLogger();
|
|
1021
747
|
const { apis } = payload;
|
|
@@ -1023,34 +749,40 @@ export const ${name} = ${name}Scope.entities;
|
|
|
1023
749
|
throw new Error("Root directory not set");
|
|
1024
750
|
}
|
|
1025
751
|
const rootDir = this.rootDir;
|
|
1026
|
-
const
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
try {
|
|
1035
|
-
const stats = await fs.stat(apiDir);
|
|
1036
|
-
if (stats.isDirectory()) {
|
|
1037
|
-
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);
|
|
1038
760
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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);
|
|
1054
786
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
1055
787
|
await this.writeChanges(changes);
|
|
1056
788
|
this.emit("apiDelete", {
|
|
@@ -1131,40 +863,229 @@ export const ${name} = ${name}Scope.entities;
|
|
|
1131
863
|
// TODO: Should I delete here?
|
|
1132
864
|
}));
|
|
1133
865
|
};
|
|
1134
|
-
async
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
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");
|
|
1138
905
|
}
|
|
1139
|
-
|
|
1140
|
-
this.sourceTracker?.
|
|
1141
|
-
|
|
1142
|
-
|
|
906
|
+
const filePath = path.join(this.rootDir, APP_THEME_FILE_NAME);
|
|
907
|
+
await this.sourceTracker?.updateTheme({
|
|
908
|
+
themeFilePath: filePath,
|
|
909
|
+
theme,
|
|
1143
910
|
});
|
|
1144
911
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
1145
912
|
await this.writeChanges(changes);
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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,
|
|
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,
|
|
1155
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);
|
|
1156
1004
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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();
|
|
1161
1069
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
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);
|
|
1168
1089
|
}
|
|
1169
1090
|
// Utilities for converting server API format to Client API format
|
|
1170
1091
|
// We internally save the API as the server does, but we return should always return it
|
|
@@ -1181,21 +1102,19 @@ export const ${name} = ${name}Scope.entities;
|
|
|
1181
1102
|
},
|
|
1182
1103
|
};
|
|
1183
1104
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
};
|
|
1198
|
-
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) {
|
|
1199
1118
|
let yamlPath = filePath;
|
|
1200
1119
|
if (fileType.type === "python-api-step" ||
|
|
1201
1120
|
fileType.type === "js-api-step") {
|
|
@@ -1213,7 +1132,7 @@ export const ${name} = ${name}Scope.entities;
|
|
|
1213
1132
|
const parsedData = { apiPb: apiContent?.api };
|
|
1214
1133
|
if (!(yamlPath in this.apiFiles &&
|
|
1215
1134
|
isEqual(this.apiFiles[yamlPath]?.apiPb, parsedData.apiPb))) {
|
|
1216
|
-
const { updatedApi, pageName, isNewApi } = this.
|
|
1135
|
+
const { updatedApi, pageName, isNewApi } = this.updateApi({
|
|
1217
1136
|
api: parsedData.apiPb,
|
|
1218
1137
|
stepPathMap: apiContent.stepPathMap,
|
|
1219
1138
|
}, yamlPath);
|
|
@@ -1224,7 +1143,7 @@ export const ${name} = ${name}Scope.entities;
|
|
|
1224
1143
|
return;
|
|
1225
1144
|
}
|
|
1226
1145
|
if (isNewApi) {
|
|
1227
|
-
await this.
|
|
1146
|
+
await this.createScopedApi(updatedApi);
|
|
1228
1147
|
}
|
|
1229
1148
|
this.emit("apiManualUpdate", {
|
|
1230
1149
|
api: this.createClientApi(updatedApi),
|
|
@@ -1240,56 +1159,9 @@ export const ${name} = ${name}Scope.entities;
|
|
|
1240
1159
|
logger.error(`Error updating API: ${yamlPath}, error: ${error}`);
|
|
1241
1160
|
}
|
|
1242
1161
|
}
|
|
1243
|
-
updateInternalApiData = (content, path) => {
|
|
1244
|
-
if (!this.rootDir) {
|
|
1245
|
-
throw new Error("Root directory not set");
|
|
1246
|
-
}
|
|
1247
|
-
const { api: apiContents, stepPathMap } = content;
|
|
1248
|
-
const pageName = getPageName(path);
|
|
1249
|
-
let scopeId = this.sourceTracker?.getScopeDefinitionForPage(pageName)?.id;
|
|
1250
|
-
if (!scopeId) {
|
|
1251
|
-
console.warn("Scope ID not found for API", apiContents.metadata.name);
|
|
1252
|
-
scopeId = "";
|
|
1253
|
-
}
|
|
1254
|
-
const updatedApi = {
|
|
1255
|
-
apiPb: yaml.parse(JSON.stringify(apiContents)),
|
|
1256
|
-
pageName,
|
|
1257
|
-
stepPathMap,
|
|
1258
|
-
scopeId,
|
|
1259
|
-
};
|
|
1260
|
-
const isNewApi = !this.apiFiles[path];
|
|
1261
|
-
this.apiFiles[path] = updatedApi;
|
|
1262
|
-
return { updatedApi, pageName, isNewApi };
|
|
1263
|
-
};
|
|
1264
|
-
renameIdentifierInApis = async ({ elementId, oldName, newName, parentBinding, }) => {
|
|
1265
|
-
const apisInScope = structuredClone(this.getApisInScope(elementId));
|
|
1266
|
-
await this.renameManager.renameEntityInApis({
|
|
1267
|
-
oldName,
|
|
1268
|
-
newName,
|
|
1269
|
-
parentBinding,
|
|
1270
|
-
apis: apisInScope,
|
|
1271
|
-
});
|
|
1272
|
-
// only save the APIs that have changed
|
|
1273
|
-
await Promise.all(apisInScope.map(({ api, filePath }) => {
|
|
1274
|
-
if (isEqual(api?.apiPb, this.apiFiles[filePath]?.apiPb)) {
|
|
1275
|
-
return Promise.resolve();
|
|
1276
|
-
}
|
|
1277
|
-
return this.writeFile(filePath, yaml.stringify(api?.apiPb), "api");
|
|
1278
|
-
}));
|
|
1279
|
-
};
|
|
1280
|
-
getApisInScope = (elementId) => {
|
|
1281
|
-
const filePath = this.sourceTracker?.getElementToFilePath(elementId);
|
|
1282
|
-
if (!filePath) {
|
|
1283
|
-
return [];
|
|
1284
|
-
}
|
|
1285
|
-
const pagePath = path.dirname(filePath);
|
|
1286
|
-
return Object.entries(this.apiFiles)
|
|
1287
|
-
.filter(([filePath]) => filePath.includes(pagePath))
|
|
1288
|
-
.map(([filePath, api]) => ({ api, filePath }));
|
|
1289
|
-
};
|
|
1290
1162
|
}
|
|
1291
1163
|
// Add new mock implementation
|
|
1292
|
-
export class MockFileSyncManager extends
|
|
1164
|
+
export class MockFileSyncManager extends FileSyncManager {
|
|
1293
1165
|
tsFiles = {};
|
|
1294
1166
|
apiFiles = {};
|
|
1295
1167
|
async watch(_watcher, _path) {
|
|
@@ -1343,7 +1215,8 @@ async function readFile(path) {
|
|
|
1343
1215
|
// in order for try-catch to work, we need to intentionally await the readFile call,
|
|
1344
1216
|
// otherwise the error won't be be caught
|
|
1345
1217
|
// see: https://github.com/nodejs/node/issues/51894#issuecomment-1974017737
|
|
1346
|
-
|
|
1218
|
+
const content = await fs.readFile(path, "utf-8");
|
|
1219
|
+
return content;
|
|
1347
1220
|
}
|
|
1348
1221
|
catch (e) {
|
|
1349
1222
|
getLogger().error(`error reading file: ${path}`, getErrorMeta(e));
|
|
@@ -1364,4 +1237,12 @@ async function readFiles(dir) {
|
|
|
1364
1237
|
const getMergedApiContent = async (path) => {
|
|
1365
1238
|
return readAppApiYamlFile(path.split("/").slice(0, -1).join("/"));
|
|
1366
1239
|
};
|
|
1240
|
+
const getPageName = (path) => {
|
|
1241
|
+
const parts = path.split("/");
|
|
1242
|
+
const pagesIndex = parts.findIndex((part) => part === "pages");
|
|
1243
|
+
if (pagesIndex !== -1 && parts[pagesIndex + 1]) {
|
|
1244
|
+
return parts[pagesIndex + 1] ?? "";
|
|
1245
|
+
}
|
|
1246
|
+
return "";
|
|
1247
|
+
};
|
|
1367
1248
|
//# sourceMappingURL=file-system-manager.js.map
|