@superblocksteam/vite-plugin-file-sync 2.0.6-next.9 → 2.0.6-next.91
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 +7 -2
- package/dist/ai-service/app-interface/linter.d.ts.map +1 -1
- package/dist/ai-service/app-interface/linter.js +52 -41
- package/dist/ai-service/app-interface/linter.js.map +1 -1
- package/dist/ai-service/app-interface/shell.d.ts +2 -0
- package/dist/ai-service/app-interface/shell.d.ts.map +1 -1
- package/dist/ai-service/app-interface/shell.js +13 -4
- package/dist/ai-service/app-interface/shell.js.map +1 -1
- package/dist/ai-service/const.d.ts +2 -0
- package/dist/ai-service/const.d.ts.map +1 -1
- package/dist/ai-service/const.js +2 -0
- 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 +4 -12
- 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.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbColumnPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbColumnPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbColumnPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbContainerPropsDocs.js.map +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.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbIconPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbIconPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbIconPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbImagePropsDocs.js.map +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.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbModalPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbModalPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbModalPropsDocs.js.map +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.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSectionPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSectionPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbSectionPropsDocs.js.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.d.ts +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.js +2 -2
- package/dist/ai-service/prompts/generated/library-components/SbSlideoutPropsDocs.js.map +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/TextStyleWithVariant.d.ts +2 -0
- package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.d.ts.map +1 -0
- package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.js +6 -0
- package/dist/ai-service/prompts/generated/library-typedefs/TextStyleWithVariant.js.map +1 -0
- package/dist/ai-service/prompts/generated/library-typedefs/index.d.ts +1 -0
- package/dist/ai-service/prompts/generated/library-typedefs/index.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/library-typedefs/index.js +1 -0
- 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.d.ts +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-api.d.ts.map +1 -1
- package/dist/ai-service/prompts/generated/subprompts/superblocks-api.js +2 -2
- package/dist/ai-service/prompts/generated/subprompts/superblocks-api.js.map +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 +4 -0
- package/dist/ai-service/prompts/system.js.map +1 -1
- package/dist/ai-service/state-machine/clark-fsm.d.ts +2 -0
- 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 +10 -0
- 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 +70 -24
- package/dist/ai-service/state-machine/handlers/llm-generating.js.map +1 -1
- package/dist/ai-service/test-utils/app-generation-mocks/orders-app.d.ts +3 -0
- package/dist/ai-service/test-utils/app-generation-mocks/orders-app.d.ts.map +1 -0
- package/dist/ai-service/test-utils/app-generation-mocks/orders-app.js +851 -0
- package/dist/ai-service/test-utils/app-generation-mocks/orders-app.js.map +1 -0
- package/dist/ai-service/test-utils/app-generation-mocks/smoketest.d.ts +3 -0
- package/dist/ai-service/test-utils/app-generation-mocks/smoketest.d.ts.map +1 -0
- package/dist/ai-service/test-utils/app-generation-mocks/smoketest.js +111 -0
- package/dist/ai-service/test-utils/app-generation-mocks/smoketest.js.map +1 -0
- package/dist/ai-service/test-utils/mock-utils.d.ts +22 -0
- package/dist/ai-service/test-utils/mock-utils.d.ts.map +1 -0
- package/dist/ai-service/test-utils/mock-utils.js +46 -0
- package/dist/ai-service/test-utils/mock-utils.js.map +1 -0
- 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 +3 -2
- 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 +9 -8
- package/dist/ai-service/transform/shared.js.map +1 -1
- package/dist/ai-service/types.d.ts +23 -1
- package/dist/ai-service/types.d.ts.map +1 -1
- package/dist/ai-service/types.js.map +1 -1
- package/dist/binding-extraction/index.d.ts +2 -0
- package/dist/binding-extraction/index.d.ts.map +1 -0
- package/dist/binding-extraction/index.js +2 -0
- package/dist/binding-extraction/index.js.map +1 -0
- package/dist/codegen.js +1 -1
- package/dist/codegen.js.map +1 -1
- package/dist/file-sync-vite-plugin.d.ts.map +1 -1
- package/dist/file-sync-vite-plugin.js +34 -13
- package/dist/file-sync-vite-plugin.js.map +1 -1
- package/dist/file-system-helpers.d.ts +4 -0
- package/dist/file-system-helpers.d.ts.map +1 -1
- package/dist/file-system-helpers.js +10 -0
- package/dist/file-system-helpers.js.map +1 -1
- package/dist/file-system-manager.d.ts +52 -39
- package/dist/file-system-manager.d.ts.map +1 -1
- package/dist/file-system-manager.js +633 -514
- package/dist/file-system-manager.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/inject-index-vite-plugin.d.ts +0 -2
- 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 +13 -2
- package/dist/lock-service/index.js.map +1 -1
- package/dist/operations/operation-processor.d.ts +24 -0
- package/dist/operations/operation-processor.d.ts.map +1 -0
- package/dist/operations/operation-processor.js +80 -0
- package/dist/operations/operation-processor.js.map +1 -0
- package/dist/operations/types.d.ts +8 -0
- package/dist/operations/types.d.ts.map +1 -0
- package/dist/operations/types.js +2 -0
- package/dist/operations/types.js.map +1 -0
- package/dist/parsing/computed/to-code-computed.d.ts.map +1 -1
- package/dist/parsing/computed/to-code-computed.js +7 -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 +12 -1
- 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 +47 -0
- package/dist/parsing/events/to-value-events.js.map +1 -1
- package/dist/parsing/index.d.ts +3 -0
- package/dist/parsing/index.d.ts.map +1 -0
- package/dist/parsing/index.js +3 -0
- package/dist/parsing/index.js.map +1 -0
- 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 +2 -1
- 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 +0 -2
- package/dist/plugin-options.d.ts.map +1 -1
- package/dist/plugin-options.js.map +1 -1
- package/dist/refactor/entities.d.ts +6 -0
- package/dist/refactor/entities.d.ts.map +1 -0
- package/dist/refactor/entities.js +62 -0
- package/dist/refactor/entities.js.map +1 -0
- package/dist/refactor/javascript.d.ts +0 -4
- package/dist/refactor/javascript.d.ts.map +1 -1
- package/dist/refactor/javascript.js +0 -8
- package/dist/refactor/javascript.js.map +1 -1
- package/dist/rename-manager.d.ts +0 -5
- package/dist/rename-manager.d.ts.map +1 -1
- package/dist/rename-manager.js +1 -27
- package/dist/rename-manager.js.map +1 -1
- package/dist/routing.d.ts +2 -2
- package/dist/routing.d.ts.map +1 -1
- package/dist/routing.js +21 -1
- package/dist/routing.js.map +1 -1
- package/dist/sb-scope-manager.d.ts +1 -1
- package/dist/sb-scope-manager.d.ts.map +1 -1
- package/dist/sb-scope-manager.js +10 -0
- package/dist/sb-scope-manager.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 +7 -5
- 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 +33 -17
- package/dist/source-tracker.js.map +1 -1
- package/dist/sync-service/index.d.ts.map +1 -1
- package/dist/sync-service/index.js +2 -1
- 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 +13 -17
- package/dist/util/logger.d.ts.map +1 -1
- package/dist/util/logger.js +34 -44
- package/dist/util/logger.js.map +1 -1
- package/dist/util.d.ts +1 -0
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +8 -0
- package/dist/util.js.map +1 -1
- package/package.json +15 -6
- package/dist/util/tracing.d.ts +0 -4
- package/dist/util/tracing.d.ts.map +0 -1
- package/dist/util/tracing.js +0 -56
- package/dist/util/tracing.js.map +0 -1
|
@@ -12,8 +12,9 @@ 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 { getPageFolder, PAGES_DIRECTORY, ROUTES_FILE, SCOPE_FILE, } from "./file-system-helpers.js";
|
|
15
|
+
import { getApiFilePath, getPageFolder, isPageFilePath, 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";
|
|
17
18
|
import { doesElementHaveBinding } from "./parsing/bindings.js";
|
|
18
19
|
import { getSbElementId } from "./parsing/ids.js";
|
|
19
20
|
import { makeJSXAttribute } from "./parsing/jsx.js";
|
|
@@ -24,6 +25,7 @@ import { RenameManager } from "./rename-manager.js";
|
|
|
24
25
|
import { SourceTracker } from "./source-tracker.js";
|
|
25
26
|
import { traverse } from "./traverse.js";
|
|
26
27
|
import { getErrorMeta, getLogger } from "./util/logger.js";
|
|
28
|
+
import { getPageName } from "./util.js";
|
|
27
29
|
const SUPPORTED_FILETYPES = [
|
|
28
30
|
{
|
|
29
31
|
type: "tsx",
|
|
@@ -45,26 +47,41 @@ const SUPPORTED_FILETYPES = [
|
|
|
45
47
|
type: "js-api-step",
|
|
46
48
|
extension: ".js",
|
|
47
49
|
},
|
|
50
|
+
{
|
|
51
|
+
type: "json",
|
|
52
|
+
extension: ".json",
|
|
53
|
+
},
|
|
48
54
|
];
|
|
49
55
|
const APP_THEME_FILE_NAME = "appTheme.ts";
|
|
50
|
-
export class
|
|
56
|
+
export class FileSystemManager extends TracedEventEmitter {
|
|
51
57
|
rootDir;
|
|
52
58
|
tsFiles = {};
|
|
53
59
|
apiFiles = {};
|
|
54
60
|
sourceTracker;
|
|
55
61
|
fsOperationQueue;
|
|
62
|
+
operationProcessor;
|
|
56
63
|
generationNumberSequence;
|
|
57
64
|
routes = {};
|
|
65
|
+
routeChangesQueue = [];
|
|
58
66
|
watcher;
|
|
59
67
|
registeredComponentPaths = {};
|
|
60
68
|
renameManager = new RenameManager();
|
|
61
69
|
_tracer;
|
|
70
|
+
transactionNonce = Date.now();
|
|
71
|
+
pendingTransactions = new Set();
|
|
72
|
+
processedTransactions = [];
|
|
62
73
|
constructor(fsOperationQueue, generationNumberSequence, tracer) {
|
|
63
74
|
super(tracer, { captureRejections: true });
|
|
64
75
|
this.rootDir = "/";
|
|
65
76
|
this.fsOperationQueue = fsOperationQueue;
|
|
66
77
|
this.generationNumberSequence = generationNumberSequence;
|
|
67
78
|
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();
|
|
68
85
|
applyErrorHandling(this, {
|
|
69
86
|
watch: { operation: "editing from code" },
|
|
70
87
|
handleCreatePage: { operation: "creating a page" },
|
|
@@ -110,27 +127,7 @@ export class FileSyncManager extends TracedEventEmitter {
|
|
|
110
127
|
}
|
|
111
128
|
return path.join(this.rootDir, "App.tsx");
|
|
112
129
|
}
|
|
113
|
-
|
|
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
|
-
};
|
|
130
|
+
// MARK: core setup/init
|
|
134
131
|
async watch(watcher, rootPath) {
|
|
135
132
|
const logger = getLogger();
|
|
136
133
|
this.rootDir = rootPath;
|
|
@@ -216,178 +213,10 @@ export class FileSyncManager extends TracedEventEmitter {
|
|
|
216
213
|
return;
|
|
217
214
|
const { type, path } = file;
|
|
218
215
|
if (type === "api") {
|
|
219
|
-
this.
|
|
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
|
-
});
|
|
216
|
+
this.updateInternalApiData(content, path);
|
|
349
217
|
}
|
|
350
218
|
});
|
|
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];
|
|
219
|
+
watcher.on("all", this.handleFileChange);
|
|
391
220
|
}
|
|
392
221
|
initializeSourceTracker() {
|
|
393
222
|
this.sourceTracker = new SourceTracker(this._tracer);
|
|
@@ -496,6 +325,246 @@ export class FileSyncManager extends TracedEventEmitter {
|
|
|
496
325
|
return null;
|
|
497
326
|
}
|
|
498
327
|
}
|
|
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
|
+
}
|
|
499
568
|
getLocalBindingEntities(path) {
|
|
500
569
|
const logger = getLogger();
|
|
501
570
|
const files = this.sourceTracker?.getCurrentFiles();
|
|
@@ -523,8 +592,36 @@ export class FileSyncManager extends TracedEventEmitter {
|
|
|
523
592
|
});
|
|
524
593
|
return Array.from(localBindingEntities);
|
|
525
594
|
}
|
|
526
|
-
|
|
527
|
-
|
|
595
|
+
getPageRoots(filePath) {
|
|
596
|
+
const scopeFilePath = path.join(path.dirname(filePath), "index.tsx");
|
|
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
|
|
528
625
|
flushTransactions = () => {
|
|
529
626
|
// TODO do something more sophisticated than this.
|
|
530
627
|
this.processedTransactions = this.processedTransactions.slice(-20);
|
|
@@ -537,42 +634,92 @@ export class FileSyncManager extends TracedEventEmitter {
|
|
|
537
634
|
}
|
|
538
635
|
this.pendingTransactions.add(transactionId);
|
|
539
636
|
};
|
|
540
|
-
transactionNonce = Date.now();
|
|
541
637
|
// Until we have a sophisticated way to track ALL operations, including non-UI initiated ops, we will use a nonce
|
|
542
638
|
// TODO https://github.com/superblocksteam/superblocks/pull/11788
|
|
543
639
|
getProcessedTransactionsWithNonce() {
|
|
544
640
|
const nonce = `t-${this.transactionNonce++}`;
|
|
545
641
|
return this.processedTransactions.concat(nonce);
|
|
546
642
|
}
|
|
643
|
+
// MARK: editor operations
|
|
547
644
|
handleCreatePage = async (payload) => {
|
|
548
|
-
|
|
645
|
+
this.trackTransaction(payload.transaction?.id);
|
|
646
|
+
const { name, route, navigateToRoute, routeTestParams } = payload;
|
|
549
647
|
if (!this.rootDir) {
|
|
550
648
|
throw new Error("Root directory not set");
|
|
551
649
|
}
|
|
650
|
+
if (!route) {
|
|
651
|
+
throw new Error("Route is required when creating a page");
|
|
652
|
+
}
|
|
552
653
|
const pagePath = getPageFolder(this.rootDir, name);
|
|
553
654
|
const pageIndexPath = path.join(pagePath, "index.tsx");
|
|
554
|
-
const
|
|
655
|
+
const scopePath = path.join(pagePath, "scope.ts");
|
|
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
|
+
|
|
555
665
|
function Page() {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
666
|
+
const {} = ${name};
|
|
667
|
+
return (
|
|
668
|
+
<SbPage name="${name}" height={Dim.fill()} width={Dim.fill()}>
|
|
669
|
+
<SbSection height={Dim.fill()}>
|
|
670
|
+
<SbColumn width={Dim.fill()}></SbColumn>
|
|
671
|
+
</SbSection>
|
|
672
|
+
</SbPage>
|
|
673
|
+
);
|
|
559
674
|
}
|
|
560
675
|
|
|
561
|
-
export default registerPage(Page,
|
|
676
|
+
export default registerPage(Page, ${name}Scope);
|
|
562
677
|
`;
|
|
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);
|
|
563
693
|
await fs.mkdir(pagePath, { recursive: true });
|
|
564
694
|
await this.writeFile(pageIndexPath, pageContent, "ts");
|
|
695
|
+
await this.writeFile(scopePath, scopeContent, "ts");
|
|
565
696
|
await this.handleNonVisualChangeByDeletingIds(pageIndexPath, pageContent);
|
|
697
|
+
await this.handleNonVisualChangeByDeletingIds(scopePath, scopeContent);
|
|
698
|
+
this.watcher?.add(pagePath);
|
|
699
|
+
await this.addRoute(route, pageIndexPath);
|
|
566
700
|
this.emit("addPage", pageIndexPath);
|
|
567
701
|
};
|
|
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
|
+
}
|
|
568
715
|
handleReparent = async (payload, writeFile = true) => {
|
|
569
716
|
const { from, to, changedProps, transaction } = payload;
|
|
570
717
|
this.trackTransaction(transaction?.id);
|
|
571
|
-
|
|
718
|
+
this.sourceTracker?.setProperties({
|
|
572
719
|
source: from.source,
|
|
573
720
|
changes: changedProps ?? {},
|
|
574
721
|
});
|
|
575
|
-
|
|
722
|
+
this.sourceTracker?.moveElement({
|
|
576
723
|
from,
|
|
577
724
|
to,
|
|
578
725
|
});
|
|
@@ -586,7 +733,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
586
733
|
};
|
|
587
734
|
handleCreateComponent = async (payload, writeFile = true) => {
|
|
588
735
|
this.trackTransaction(payload.transaction?.id);
|
|
589
|
-
const sourceId =
|
|
736
|
+
const sourceId = this.sourceTracker?.addElement(payload);
|
|
590
737
|
if (writeFile) {
|
|
591
738
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
592
739
|
await this.writeChanges(changes ?? [], (fileName) => {
|
|
@@ -600,7 +747,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
600
747
|
const { elements, transaction } = payload;
|
|
601
748
|
this.trackTransaction(transaction?.id);
|
|
602
749
|
for (const element of elements) {
|
|
603
|
-
|
|
750
|
+
this.sourceTracker?.deleteElement({
|
|
604
751
|
source: element.source,
|
|
605
752
|
scopeName: element.scopeName,
|
|
606
753
|
});
|
|
@@ -616,7 +763,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
616
763
|
handleSetProperty = async (payload, writeFile = true) => {
|
|
617
764
|
const { element: { source }, property, value, transaction, } = payload;
|
|
618
765
|
this.trackTransaction(transaction?.id);
|
|
619
|
-
|
|
766
|
+
this.sourceTracker?.setProperty({
|
|
620
767
|
source,
|
|
621
768
|
property,
|
|
622
769
|
info: value,
|
|
@@ -631,7 +778,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
631
778
|
handleSetProperties = async (payload, writeFile = true) => {
|
|
632
779
|
const { element: { source }, properties, transaction, } = payload;
|
|
633
780
|
this.trackTransaction(transaction?.id);
|
|
634
|
-
|
|
781
|
+
this.sourceTracker?.setProperties({
|
|
635
782
|
source,
|
|
636
783
|
changes: properties,
|
|
637
784
|
});
|
|
@@ -687,39 +834,180 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
687
834
|
this.flushTransactions();
|
|
688
835
|
return returnValues;
|
|
689
836
|
};
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
837
|
+
// MARK: entity operations
|
|
838
|
+
handleAddEntity = async (payload) => {
|
|
839
|
+
this.sourceTracker?.addEntity({
|
|
840
|
+
scopeId: payload.scopeId,
|
|
841
|
+
entity: {
|
|
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;
|
|
695
875
|
if (!this.rootDir) {
|
|
696
876
|
throw new Error("Root directory not set");
|
|
697
877
|
}
|
|
698
|
-
|
|
699
|
-
|
|
878
|
+
const filePath = path.join(this.rootDir, APP_THEME_FILE_NAME);
|
|
879
|
+
this.sourceTracker?.updateTheme({
|
|
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);
|
|
700
890
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
throw new Error("API name is not set");
|
|
891
|
+
else if (payload.kind === "entity") {
|
|
892
|
+
return this.handleRenameEntity(payload);
|
|
704
893
|
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
894
|
+
else if (payload.kind === "page") {
|
|
895
|
+
return this.handleRenamePage(payload);
|
|
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];
|
|
708
996
|
try {
|
|
709
997
|
const stats = await fs.stat(apiDir);
|
|
710
998
|
if (!stats.isDirectory()) {
|
|
711
|
-
await fs.mkdir(apiDir, { recursive: true });
|
|
999
|
+
await this.fsOperationQueue.enqueue(async () => await fs.mkdir(apiDir, { recursive: true }));
|
|
712
1000
|
}
|
|
713
1001
|
}
|
|
714
1002
|
catch {
|
|
715
|
-
await fs.mkdir(apiDir, { recursive: true });
|
|
1003
|
+
await this.fsOperationQueue.enqueue(async () => await fs.mkdir(apiDir, { recursive: true }));
|
|
716
1004
|
}
|
|
717
|
-
await this.writeFile(
|
|
1005
|
+
await this.writeFile(apiFilePath, yaml.stringify(api.apiPb), "api");
|
|
718
1006
|
const generationNumber = this.generationNumberSequence.next();
|
|
719
1007
|
const apiDef = this.createClientApi(api);
|
|
720
1008
|
let scopeId = "";
|
|
721
1009
|
if (isNewApi) {
|
|
722
|
-
scopeId = await this.
|
|
1010
|
+
scopeId = await this.addApiToScope(api);
|
|
723
1011
|
}
|
|
724
1012
|
else {
|
|
725
1013
|
const scopeDef = this.sourceTracker.getScopeDefinitionForPage(api.pageName);
|
|
@@ -728,20 +1016,6 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
728
1016
|
this.emit("apiUpdate", { api: apiDef, scopeId });
|
|
729
1017
|
return { api: apiDef, scopeId, generationNumber };
|
|
730
1018
|
};
|
|
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
|
-
};
|
|
745
1019
|
handleDeleteApi = async (payload) => {
|
|
746
1020
|
const logger = getLogger();
|
|
747
1021
|
const { apis } = payload;
|
|
@@ -749,40 +1023,34 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
749
1023
|
throw new Error("Root directory not set");
|
|
750
1024
|
}
|
|
751
1025
|
const rootDir = this.rootDir;
|
|
752
|
-
const
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
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);
|
|
1026
|
+
const deletedApis = [];
|
|
1027
|
+
for (const { apiName, pageName } of apis) {
|
|
1028
|
+
const apiFilePath = getApiFilePath(rootDir, pageName, apiName);
|
|
1029
|
+
const api = this.apiFiles[apiFilePath];
|
|
1030
|
+
if (!api || !this.sourceTracker) {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
const apiDir = path.dirname(apiFilePath);
|
|
1034
|
+
try {
|
|
1035
|
+
const stats = await fs.stat(apiDir);
|
|
1036
|
+
if (stats.isDirectory()) {
|
|
1037
|
+
await fs.rmdir(apiDir, { recursive: true });
|
|
781
1038
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1039
|
+
delete this.apiFiles[apiFilePath];
|
|
1040
|
+
const scopeId = this.sourceTracker.deleteApi({
|
|
1041
|
+
pageName,
|
|
1042
|
+
apiName,
|
|
1043
|
+
});
|
|
1044
|
+
deletedApis.push({
|
|
1045
|
+
apiName,
|
|
1046
|
+
pageName,
|
|
1047
|
+
scopeId,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
catch (e) {
|
|
1051
|
+
logger.warn(`Could not delete api ${apiFilePath} ${JSON.stringify(e)}`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
786
1054
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
787
1055
|
await this.writeChanges(changes);
|
|
788
1056
|
this.emit("apiDelete", {
|
|
@@ -863,229 +1131,40 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
863
1131
|
// TODO: Should I delete here?
|
|
864
1132
|
}));
|
|
865
1133
|
};
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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");
|
|
1134
|
+
async removeApiData(filePath) {
|
|
1135
|
+
const api = this.apiFiles[filePath];
|
|
1136
|
+
if (!api) {
|
|
1137
|
+
return;
|
|
905
1138
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1139
|
+
delete this.apiFiles[filePath];
|
|
1140
|
+
this.sourceTracker?.deleteApi({
|
|
1141
|
+
pageName: api.pageName,
|
|
1142
|
+
apiName: api.apiPb.metadata.name,
|
|
910
1143
|
});
|
|
911
1144
|
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
912
1145
|
await this.writeChanges(changes);
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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,
|
|
1146
|
+
const scopeId = this.sourceTracker?.getScopeDefinitionForPage(api.pageName)?.id;
|
|
1147
|
+
this.emit("apiManualDelete", {
|
|
1148
|
+
api: {
|
|
1149
|
+
id: getClientApiId(api.apiPb.metadata.name, api.pageName),
|
|
1150
|
+
apiName: api.apiPb.metadata.name,
|
|
1151
|
+
// TODO(saksham): get pagename more defensively
|
|
1152
|
+
pageName: getPageName(filePath),
|
|
1153
|
+
scopeId,
|
|
1154
|
+
},
|
|
977
1155
|
});
|
|
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);
|
|
1004
|
-
}
|
|
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
1156
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
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();
|
|
1157
|
+
getApiFiles() {
|
|
1158
|
+
return Object.keys(this.apiFiles).reduce((acc, key) => {
|
|
1159
|
+
if (!this.apiFiles[key]) {
|
|
1160
|
+
return acc;
|
|
1069
1161
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
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);
|
|
1162
|
+
acc[key] = {
|
|
1163
|
+
api: this.createClientApi(this.apiFiles[key]),
|
|
1164
|
+
scopeId: this.apiFiles[key].scopeId,
|
|
1165
|
+
};
|
|
1166
|
+
return acc;
|
|
1167
|
+
}, {});
|
|
1089
1168
|
}
|
|
1090
1169
|
// Utilities for converting server API format to Client API format
|
|
1091
1170
|
// We internally save the API as the server does, but we return should always return it
|
|
@@ -1102,19 +1181,21 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1102
1181
|
},
|
|
1103
1182
|
};
|
|
1104
1183
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1184
|
+
addApiToScope = async (api) => {
|
|
1185
|
+
if (!this.sourceTracker) {
|
|
1186
|
+
throw new Error("Source tracker not initialized");
|
|
1187
|
+
}
|
|
1188
|
+
// We want to add the API entity to our scope, but we do not want to emit entity events, because
|
|
1189
|
+
// the API update event handles this particular side effect.
|
|
1190
|
+
const scopeId = await this.sourceTracker.addApi({
|
|
1191
|
+
pageName: api.pageName,
|
|
1192
|
+
apiName: api.apiPb.metadata.name,
|
|
1193
|
+
});
|
|
1194
|
+
const changes = (await this.sourceTracker?.getAndFlushChanges()) ?? [];
|
|
1195
|
+
await this.writeChanges(changes);
|
|
1196
|
+
return scopeId;
|
|
1197
|
+
};
|
|
1198
|
+
async processApiFileUpdates(filePath, fileType) {
|
|
1118
1199
|
let yamlPath = filePath;
|
|
1119
1200
|
if (fileType.type === "python-api-step" ||
|
|
1120
1201
|
fileType.type === "js-api-step") {
|
|
@@ -1132,7 +1213,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1132
1213
|
const parsedData = { apiPb: apiContent?.api };
|
|
1133
1214
|
if (!(yamlPath in this.apiFiles &&
|
|
1134
1215
|
isEqual(this.apiFiles[yamlPath]?.apiPb, parsedData.apiPb))) {
|
|
1135
|
-
const { updatedApi, pageName, isNewApi } = this.
|
|
1216
|
+
const { updatedApi, pageName, isNewApi } = this.updateInternalApiData({
|
|
1136
1217
|
api: parsedData.apiPb,
|
|
1137
1218
|
stepPathMap: apiContent.stepPathMap,
|
|
1138
1219
|
}, yamlPath);
|
|
@@ -1143,7 +1224,7 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1143
1224
|
return;
|
|
1144
1225
|
}
|
|
1145
1226
|
if (isNewApi) {
|
|
1146
|
-
await this.
|
|
1227
|
+
await this.addApiToScope(updatedApi);
|
|
1147
1228
|
}
|
|
1148
1229
|
this.emit("apiManualUpdate", {
|
|
1149
1230
|
api: this.createClientApi(updatedApi),
|
|
@@ -1159,9 +1240,56 @@ export default registerPage(Page, { name: "${name}" });
|
|
|
1159
1240
|
logger.error(`Error updating API: ${yamlPath}, error: ${error}`);
|
|
1160
1241
|
}
|
|
1161
1242
|
}
|
|
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
|
+
};
|
|
1162
1290
|
}
|
|
1163
1291
|
// Add new mock implementation
|
|
1164
|
-
export class MockFileSyncManager extends
|
|
1292
|
+
export class MockFileSyncManager extends FileSystemManager {
|
|
1165
1293
|
tsFiles = {};
|
|
1166
1294
|
apiFiles = {};
|
|
1167
1295
|
async watch(_watcher, _path) {
|
|
@@ -1215,8 +1343,7 @@ async function readFile(path) {
|
|
|
1215
1343
|
// in order for try-catch to work, we need to intentionally await the readFile call,
|
|
1216
1344
|
// otherwise the error won't be be caught
|
|
1217
1345
|
// see: https://github.com/nodejs/node/issues/51894#issuecomment-1974017737
|
|
1218
|
-
|
|
1219
|
-
return content;
|
|
1346
|
+
return await fs.readFile(path, "utf-8");
|
|
1220
1347
|
}
|
|
1221
1348
|
catch (e) {
|
|
1222
1349
|
getLogger().error(`error reading file: ${path}`, getErrorMeta(e));
|
|
@@ -1237,12 +1364,4 @@ async function readFiles(dir) {
|
|
|
1237
1364
|
const getMergedApiContent = async (path) => {
|
|
1238
1365
|
return readAppApiYamlFile(path.split("/").slice(0, -1).join("/"));
|
|
1239
1366
|
};
|
|
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
|
-
};
|
|
1248
1367
|
//# sourceMappingURL=file-system-manager.js.map
|