@lowdefy/server-dev 5.6.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{pages/_app.js → client/App.jsx} +21 -33
- package/client/DevStreamContext.js +30 -0
- package/client/Inspector.jsx +226 -0
- package/{lib/client/Page.js → client/Page.jsx} +4 -8
- package/client/Reload.jsx +85 -0
- package/client/Routing.jsx +117 -0
- package/client/feedback/FeedbackMount.jsx +83 -0
- package/client/feedback/FeedbackOverlay.jsx +901 -0
- package/client/feedback/captureTabScreenshot.js +84 -0
- package/client/feedback/copyToClipboard.js +45 -0
- package/client/feedback/drawAnnotationsSvg.js +119 -0
- package/client/feedback/elementInspect.js +99 -0
- package/client/feedback/feedbackStyles.js +301 -0
- package/client/feedback/pinResponsiveImages.js +59 -0
- package/client/feedback/sendFeedback.js +39 -0
- package/client/feedback/useFeedbackToggle.js +70 -0
- package/client/main.jsx +42 -0
- package/client/openInEditor/OpenInEditorListener.jsx +268 -0
- package/client/openInEditor/findBlockLocation.js +51 -0
- package/lib/build/app.js +8 -1
- package/lib/build/appMeta.js +8 -1
- package/lib/build/auth.js +8 -1
- package/lib/build/config.js +8 -1
- package/lib/build/i18n.js +8 -1
- package/lib/build/logger.js +8 -1
- package/lib/build/theme.js +8 -1
- package/lib/client/auth/{Auth.js → Auth.jsx} +11 -2
- package/lib/client/auth/{AuthConfigured.js → AuthConfigured.jsx} +23 -10
- package/lib/client/setPageId.js +6 -5
- package/lib/client/utils/useMutateCache.js +14 -1
- package/lib/client/utils/usePageConfig.js +45 -43
- package/lib/client/utils/usePageConfig.test.mjs +62 -0
- package/lib/docs/captureAnnotatedScreenshot.js +88 -0
- package/lib/docs/checkpointPaths.js +91 -0
- package/lib/docs/checkpointStore.js +240 -0
- package/lib/docs/checkpointToMocks.js +65 -0
- package/lib/docs/clientErrorStore.js +36 -0
- package/lib/docs/configCheckpoints.test.mjs +156 -0
- package/lib/docs/createConfigCheckpoint.js +92 -0
- package/lib/docs/createDocsMcpServer.js +531 -0
- package/lib/docs/createDocsMcpServer.test.mjs +141 -0
- package/lib/docs/devMockRegistry.js +68 -0
- package/lib/docs/docs.test.mjs +448 -0
- package/lib/docs/enrichFeedback.js +71 -0
- package/lib/docs/evalOperator.js +47 -0
- package/lib/docs/evalOperator.test.mjs +157 -0
- package/lib/docs/evalOperatorHeadless.js +87 -0
- package/lib/docs/evalOperatorHeadless.test.mjs +72 -0
- package/lib/docs/evalOperatorInTab.js +48 -0
- package/lib/docs/findConfig.js +260 -0
- package/lib/docs/formatFeedback.js +121 -0
- package/lib/docs/formatFeedback.test.mjs +204 -0
- package/lib/docs/getAppMap.js +180 -0
- package/lib/docs/getBrowser.js +127 -0
- package/lib/docs/getBrowser.test.mjs +122 -0
- package/lib/docs/getBuildStatus.js +37 -0
- package/lib/docs/getCoreDoc.js +63 -0
- package/lib/docs/getDocsManifest.js +40 -0
- package/lib/docs/getExamples.js +58 -0
- package/lib/docs/getOverview.js +115 -0
- package/lib/docs/getPageConfig.js +53 -0
- package/lib/docs/getPluginDoc.js +55 -0
- package/lib/docs/getSchema.js +65 -0
- package/lib/docs/inspectState.js +47 -0
- package/lib/docs/inspectState.test.mjs +140 -0
- package/lib/docs/inspectStateFromTab.js +40 -0
- package/lib/docs/inspectStateHeadless.js +84 -0
- package/lib/docs/inspectStateHeadless.test.mjs +60 -0
- package/lib/docs/isWriteRequestsAllowed.js +50 -0
- package/lib/docs/listConfigCheckpoints.js +55 -0
- package/lib/docs/listPlugins.js +80 -0
- package/lib/docs/listTypes.js +105 -0
- package/lib/docs/loadState.js +152 -0
- package/lib/docs/mapPageBuildErrors.js +28 -0
- package/lib/docs/normalizeTypeKind.js +40 -0
- package/{pages/api/endpoints/[...endpointId].js → lib/docs/readBuildArtifact.js} +15 -12
- package/lib/docs/readPageArtifact.js +26 -0
- package/lib/docs/resolvePluginDir.js +49 -0
- package/lib/docs/resolveSource.js +48 -0
- package/lib/docs/resolveSource.test.mjs +100 -0
- package/lib/docs/revertConfigCheckpoint.js +73 -0
- package/lib/docs/runRequest.js +141 -0
- package/lib/docs/saveAnnotatedScreenshot.js +43 -0
- package/lib/docs/scaffoldPage.js +75 -0
- package/lib/docs/scaffoldPage.test.mjs +97 -0
- package/lib/docs/screenshotPage.js +110 -0
- package/lib/docs/screenshotPage.test.mjs +59 -0
- package/lib/docs/screenshotPageClip.test.mjs +102 -0
- package/lib/docs/searchDocs.js +67 -0
- package/lib/docs/setupTestFixtures.mjs +335 -0
- package/lib/docs/snapshotState.js +59 -0
- package/lib/docs/stateCheckpoints.test.mjs +366 -0
- package/lib/docs/tabAvailable.js +28 -0
- package/lib/docs/tabChannel.js +119 -0
- package/lib/docs/tabChannel.test.mjs +130 -0
- package/lib/server/auth/{getAuthOptions.js → getAuthConfig.js} +7 -4
- package/lib/server/auth/getDevSession.js +70 -0
- package/lib/server/auth/getDevSession.test.mjs +138 -0
- package/lib/server/auth/getHeadlessUser.js +40 -0
- package/lib/server/auth/{getMockSession.js → getMockUser.js} +8 -21
- package/lib/server/auth/headlessUser.js +29 -0
- package/lib/server/auth/resolveHeadlessUser.js +38 -0
- package/lib/server/auth/resolveHeadlessUser.test.mjs +78 -0
- package/lib/server/auth/session.js +39 -0
- package/lib/server/auth/strategies.js +47 -0
- package/lib/server/createLowdefyContext.js +122 -0
- package/lib/server/getPageJitEnrichment.test.mjs +105 -0
- package/lib/server/jitPageBuilder.js +125 -12
- package/lib/server/log/createHandleError.test.mjs +126 -0
- package/manager/getContext.mjs +3 -7
- package/manager/processes/initialBuild.mjs +0 -2
- package/manager/processes/installPlugins.mjs +19 -11
- package/manager/processes/lowdefyBuild.mjs +29 -7
- package/manager/processes/lowdefyBuild.test.mjs +102 -0
- package/manager/processes/readDotEnv.mjs +13 -1
- package/manager/processes/shutdownServer.mjs +6 -6
- package/manager/processes/startProxy.mjs +160 -0
- package/manager/processes/startProxy.test.mjs +102 -0
- package/manager/processes/startServer.mjs +33 -16
- package/manager/processes/startWatchers.mjs +2 -2
- package/manager/processes/warnAuthUrlPortMismatch.mjs +47 -0
- package/manager/processes/warnAuthUrlPortMismatch.test.mjs +93 -0
- package/manager/run.mjs +50 -0
- package/manager/utils/createCustomPluginMessagesMap.mjs +8 -1
- package/manager/utils/createCustomPluginTypesMap.mjs +1 -0
- package/manager/utils/formatNoticeBox.mjs +30 -0
- package/manager/utils/{getNextBin.mjs → getViteBin.mjs} +7 -10
- package/manager/utils/updatePageTailwindCss.mjs +7 -1
- package/manager/utils/writeBuildStatus.mjs +39 -0
- package/manager/utils/writeBuildStatus.test.mjs +76 -0
- package/manager/watchers/lowdefyBuildWatcher.mjs +0 -1
- package/manager/watchers/moduleBuildWatcher.mjs +0 -1
- package/manager/watchers/{nextBuildWatcher.mjs → serverArtifactWatcher.mjs} +18 -20
- package/package.json +61 -47
- package/package.original.json +64 -50
- package/postcss.config.cjs +1 -0
- package/src/app.js +175 -0
- package/src/html/renderDevPage.js +86 -0
- package/src/lib/getPathSegments.js +25 -0
- package/src/lib/safeScriptJson.js +38 -0
- package/src/middleware/apiContext.js +32 -0
- package/src/middleware/errorHandler.js +48 -0
- package/src/middleware/errorHandler.test.mjs +235 -0
- package/src/routes/agent.js +64 -0
- package/src/routes/apiPage.js +37 -0
- package/src/routes/auth.js +49 -0
- package/src/routes/auth.test.mjs +84 -0
- package/src/routes/clientError.js +57 -0
- package/src/routes/cron.js +47 -0
- package/src/routes/cronForward.js +47 -0
- package/src/routes/detached.js +47 -0
- package/src/routes/devInspect.js +111 -0
- package/src/routes/devInspect.test.mjs +119 -0
- package/{pages/[[...pageId]].js → src/routes/devTools.js} +4 -2
- package/{pages/404.js → src/routes/docs/appMap.js} +6 -2
- package/{pages/api/dev-tools.js → src/routes/docs/buildStatus.js} +5 -4
- package/src/routes/docs/checkpointsCreate.js +28 -0
- package/src/routes/docs/checkpointsList.js +23 -0
- package/{pages/api/icons/dynamic.js → src/routes/docs/checkpointsRevert.js} +11 -12
- package/src/routes/docs/content.js +31 -0
- package/src/routes/docs/evalOperator.js +56 -0
- package/src/routes/docs/examples.js +33 -0
- package/src/routes/docs/find.js +30 -0
- package/src/routes/docs/index.js +23 -0
- package/src/routes/docs/inspectState.js +44 -0
- package/src/routes/docs/loadState.js +49 -0
- package/src/routes/docs/mcp.js +30 -0
- package/src/routes/docs/pageConfig.js +36 -0
- package/src/routes/docs/parseUserParam.js +52 -0
- package/src/routes/docs/parseUserParam.test.mjs +45 -0
- package/src/routes/docs/pluginDoc.js +33 -0
- package/src/routes/docs/plugins.js +23 -0
- package/src/routes/docs/runRequest.js +36 -0
- package/{lib/server/auth/getServerSession.js → src/routes/docs/schema.js} +17 -15
- package/src/routes/docs/screenshot.js +72 -0
- package/src/routes/docs/search.js +27 -0
- package/src/routes/docs/snapshotState.js +36 -0
- package/src/routes/docs/stateCheckpointsList.js +26 -0
- package/src/routes/docs/types.js +33 -0
- package/src/routes/endpoints.js +71 -0
- package/src/routes/feedback.js +110 -0
- package/src/routes/feedback.test.mjs +204 -0
- package/src/routes/jitPage.js +86 -0
- package/src/routes/mcp.js +34 -0
- package/{pages/api → src/routes}/ping.js +4 -2
- package/src/routes/reload.js +79 -0
- package/src/routes/request.js +74 -0
- package/src/routes/request.test.mjs +124 -0
- package/{pages/api → src/routes}/root.js +4 -4
- package/src/routes/usage.js +46 -0
- package/src/routes/websocket.js +33 -0
- package/src/websocket/devWebSocket.js +78 -0
- package/vite.config.js +111 -0
- package/.eslintrc.yaml +0 -1
- package/lib/client/App.js +0 -80
- package/lib/client/Reload.js +0 -60
- package/lib/server/apiWrapper.js +0 -109
- package/lib/server/compileCss.js +0 -39
- package/manager/processes/compileCss.mjs +0 -54
- package/manager/processes/nextBuild.mjs +0 -50
- package/next.config.js +0 -25
- package/pages/_document.js +0 -109
- package/pages/api/agent/[...path].js +0 -86
- package/pages/api/auth/[...nextauth].js +0 -48
- package/pages/api/client-error.js +0 -45
- package/pages/api/js/[env].js +0 -42
- package/pages/api/page/[...pageId].js +0 -74
- package/pages/api/reload.js +0 -53
- package/pages/api/request/[...path].js +0 -41
- /package/lib/client/{BuildErrorPage.js → BuildErrorPage.jsx} +0 -0
- /package/lib/client/{BuildingPage.js → BuildingPage.jsx} +0 -0
- /package/lib/client/{ErrorBar.js → ErrorBar.jsx} +0 -0
- /package/lib/client/{InstallingPluginsPage.js → InstallingPluginsPage.jsx} +0 -0
- /package/lib/client/{RestartingPage.js → RestartingPage.jsx} +0 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { type } from '@lowdefy/helpers';
|
|
20
|
+
|
|
21
|
+
import getDocsManifest from './getDocsManifest.js';
|
|
22
|
+
|
|
23
|
+
const MAX_RESULTS = 20;
|
|
24
|
+
const SNIPPET_RADIUS = 120;
|
|
25
|
+
|
|
26
|
+
let contentCache = null;
|
|
27
|
+
|
|
28
|
+
function getContents({ manifest }) {
|
|
29
|
+
if (contentCache !== null) {
|
|
30
|
+
return contentCache;
|
|
31
|
+
}
|
|
32
|
+
contentCache = manifest.docs.map((doc) => ({
|
|
33
|
+
doc,
|
|
34
|
+
content: fs.readFileSync(path.join(manifest.contentDir, doc.path), 'utf8'),
|
|
35
|
+
}));
|
|
36
|
+
return contentCache;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function searchDocs({ query }) {
|
|
40
|
+
if (!type.isString(query) || query.trim() === '') {
|
|
41
|
+
throw new Error('searchDocs requires a "query" string.');
|
|
42
|
+
}
|
|
43
|
+
const manifest = getDocsManifest();
|
|
44
|
+
if (type.isNone(manifest)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const lowerQuery = query.trim().toLowerCase();
|
|
48
|
+
const titleMatches = [];
|
|
49
|
+
const contentMatches = [];
|
|
50
|
+
for (const { doc, content } of getContents({ manifest })) {
|
|
51
|
+
if (doc.title.toLowerCase().includes(lowerQuery) || doc.slug.includes(lowerQuery)) {
|
|
52
|
+
titleMatches.push({ ...doc, snippet: content.slice(0, SNIPPET_RADIUS * 2) });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const index = content.toLowerCase().indexOf(lowerQuery);
|
|
56
|
+
if (index !== -1) {
|
|
57
|
+
const start = Math.max(0, index - SNIPPET_RADIUS);
|
|
58
|
+
contentMatches.push({
|
|
59
|
+
...doc,
|
|
60
|
+
snippet: content.slice(start, index + lowerQuery.length + SNIPPET_RADIUS),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return [...titleMatches, ...contentMatches].slice(0, MAX_RESULTS);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default searchDocs;
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import path from 'path';
|
|
20
|
+
|
|
21
|
+
// Writes a throwaway server directory (build artifacts + a fake installed
|
|
22
|
+
// plugin in node_modules) for the docs service tests. Generated because a
|
|
23
|
+
// committed node_modules fixture would be gitignored.
|
|
24
|
+
function setupTestFixtures() {
|
|
25
|
+
const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-docs-test-'));
|
|
26
|
+
const write = (relativePath, data) => {
|
|
27
|
+
const filePath = path.join(fixtureDir, relativePath);
|
|
28
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
29
|
+
fs.writeFileSync(filePath, typeof data === 'string' ? data : JSON.stringify(data));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
write('package.json', { name: 'test-fixture-server', version: '1.0.0', type: 'module' });
|
|
33
|
+
|
|
34
|
+
// lib/build/config.js reads this at import time (screenshotPage → basePath).
|
|
35
|
+
write('build/config.json', {});
|
|
36
|
+
|
|
37
|
+
write('build/plugins/availableTypes.json', {
|
|
38
|
+
actions: {
|
|
39
|
+
SetState: { package: '@lowdefy/actions-core', originalTypeName: 'SetState', version: '5.0.0' },
|
|
40
|
+
},
|
|
41
|
+
agents: {},
|
|
42
|
+
auth: { adapters: {}, callbacks: {}, events: {}, providers: {} },
|
|
43
|
+
blocks: {
|
|
44
|
+
Button: { package: '@lowdefy/blocks-antd', originalTypeName: 'Button', version: '5.0.0' },
|
|
45
|
+
TestBlock: { package: 'test-plugin', originalTypeName: 'TestBlock', version: '1.0.0' },
|
|
46
|
+
},
|
|
47
|
+
connections: {
|
|
48
|
+
AxiosHttp: {
|
|
49
|
+
package: '@lowdefy/connection-axios-http',
|
|
50
|
+
originalTypeName: 'AxiosHttp',
|
|
51
|
+
version: '5.0.0',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
notifications: {},
|
|
55
|
+
operators: {
|
|
56
|
+
client: {
|
|
57
|
+
_get: { package: '@lowdefy/operators-js', originalTypeName: '_get', version: '5.0.0' },
|
|
58
|
+
},
|
|
59
|
+
server: {
|
|
60
|
+
_get: { package: '@lowdefy/operators-js', originalTypeName: '_get', version: '5.0.0' },
|
|
61
|
+
_secret: { package: '@lowdefy/operators-js', originalTypeName: '_secret', version: '5.0.0' },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
requests: {
|
|
65
|
+
AxiosHttp: {
|
|
66
|
+
package: '@lowdefy/connection-axios-http',
|
|
67
|
+
originalTypeName: 'AxiosHttp',
|
|
68
|
+
version: '5.0.0',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
websockets: {},
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
write('build/types.json', {
|
|
75
|
+
actions: {},
|
|
76
|
+
agents: {},
|
|
77
|
+
auth: { adapters: {}, callbacks: {}, events: {}, providers: {} },
|
|
78
|
+
blocks: {
|
|
79
|
+
Button: {
|
|
80
|
+
package: '@lowdefy/blocks-antd',
|
|
81
|
+
originalTypeName: 'Button',
|
|
82
|
+
version: '5.0.0',
|
|
83
|
+
count: 3,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
connections: {},
|
|
87
|
+
notifications: {},
|
|
88
|
+
operators: {
|
|
89
|
+
client: {
|
|
90
|
+
_get: {
|
|
91
|
+
package: '@lowdefy/operators-js',
|
|
92
|
+
originalTypeName: '_get',
|
|
93
|
+
version: '5.0.0',
|
|
94
|
+
count: 1,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
server: {},
|
|
98
|
+
},
|
|
99
|
+
requests: {},
|
|
100
|
+
websockets: {},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
write('build/plugins/blockSchemas.json', {
|
|
104
|
+
Button: { type: 'object', properties: { title: { type: 'string' } } },
|
|
105
|
+
TestBlock: { type: 'object', properties: {} },
|
|
106
|
+
});
|
|
107
|
+
write('build/plugins/blockMetas.json', {
|
|
108
|
+
Button: { category: 'display' },
|
|
109
|
+
TestBlock: { category: 'input', valueType: 'string' },
|
|
110
|
+
});
|
|
111
|
+
write('build/plugins/actionSchemas.json', { SetState: { type: 'object' } });
|
|
112
|
+
write('build/plugins/operatorSchemas.json', { _get: { params: { type: 'object' } } });
|
|
113
|
+
write('build/plugins/connectionSchemas.json', {
|
|
114
|
+
AxiosHttp: { schema: { type: 'object' }, requests: ['AxiosHttp'] },
|
|
115
|
+
});
|
|
116
|
+
write('build/plugins/requestSchemas.json', {
|
|
117
|
+
AxiosHttp: { schema: { type: 'object' }, meta: { checkRead: false, checkWrite: false } },
|
|
118
|
+
ReadOnlyRequest: { schema: { type: 'object' }, meta: { checkRead: true, checkWrite: false } },
|
|
119
|
+
WriteRequest: { schema: { type: 'object' }, meta: { checkRead: true, checkWrite: true } },
|
|
120
|
+
// No "meta" at all — some custom plugins don't declare it; treated as unknown.
|
|
121
|
+
UnknownMetaRequest: { schema: { type: 'object' } },
|
|
122
|
+
});
|
|
123
|
+
write('build/customTypesMap.json', {
|
|
124
|
+
blocks: { TestBlock: { package: 'test-plugin', originalTypeName: 'TestBlock', version: '1.0.0' } },
|
|
125
|
+
});
|
|
126
|
+
write('build/installedPluginPackages.json', [
|
|
127
|
+
'@lowdefy/blocks-antd',
|
|
128
|
+
'@lowdefy/actions-core',
|
|
129
|
+
'@lowdefy/operators-js',
|
|
130
|
+
'@lowdefy/connection-axios-http',
|
|
131
|
+
'test-plugin',
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
write('node_modules/test-plugin/package.json', { name: 'test-plugin', version: '1.0.0' });
|
|
135
|
+
write('node_modules/test-plugin/README.md', '# test-plugin\n\nA local test plugin.\n');
|
|
136
|
+
write(
|
|
137
|
+
'node_modules/test-plugin/dist/blocks/TestBlock/gallery.yaml',
|
|
138
|
+
'- id: example\n type: TestBlock\n properties:\n title: Hello\n'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Feedback-loop fixtures — build status, and a JIT-built page's registry,
|
|
142
|
+
// keyMap and refMap entries (see packages/build/src/build/addKeys.js for
|
|
143
|
+
// the key path format: `[index:id]` / `[index:id:Type]`).
|
|
144
|
+
write('build/buildStatus.json', {
|
|
145
|
+
status: 'ok',
|
|
146
|
+
timestamp: '2026-01-01T00:00:00.000Z',
|
|
147
|
+
errors: [],
|
|
148
|
+
warnings: [],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
write('build/pageRegistry.json', {
|
|
152
|
+
home: { pageId: 'home', auth: null, refId: 'ref-home', refPath: 'pages/home.yaml' },
|
|
153
|
+
other: { pageId: 'other', auth: null, refId: 'ref-other', refPath: 'pages/other.yaml' },
|
|
154
|
+
legal: { pageId: 'legal', auth: null, refId: 'ref-legal', refPath: 'pages/legal.yaml' },
|
|
155
|
+
unbuilt: { pageId: 'unbuilt', auth: null, refId: 'ref-unbuilt', refPath: 'pages/unbuilt.yaml' },
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Mirrors the real dev keyMap shape: the skeleton build keys the whole
|
|
159
|
+
// config (`root.pages[N:id]` page stubs only), while each JIT page build
|
|
160
|
+
// runs addKeys on the page object itself — so page CONTENT keys are
|
|
161
|
+
// `root.blocks[...]` with no page segment, one `root`-keyed subtree per
|
|
162
|
+
// built page, distinguishable only via the ~k_parent chain. Tree roots
|
|
163
|
+
// reference a parent id that is never written to keyMap.
|
|
164
|
+
write('build/keyMap.json', {
|
|
165
|
+
'key-root': { key: 'root', '~k_parent': 'key-root-parent' },
|
|
166
|
+
'key-pages': { key: 'root.pages', '~k_parent': 'key-root', '~r': 'ref-home' },
|
|
167
|
+
'key-home-stub': {
|
|
168
|
+
key: 'root.pages[0:home]',
|
|
169
|
+
'~k_parent': 'key-pages',
|
|
170
|
+
'~r': 'ref-home',
|
|
171
|
+
'~l': 1,
|
|
172
|
+
},
|
|
173
|
+
'key-other-stub': {
|
|
174
|
+
key: 'root.pages[1:other]',
|
|
175
|
+
'~k_parent': 'key-pages',
|
|
176
|
+
'~r': 'ref-other',
|
|
177
|
+
'~l': 1,
|
|
178
|
+
},
|
|
179
|
+
// JIT subtree for page "home".
|
|
180
|
+
'key-home': { key: 'root', '~k_parent': 'key-home-jit-parent', '~r': 'ref-home', '~l': 1 },
|
|
181
|
+
'key-button': {
|
|
182
|
+
key: 'root.blocks[2:my_button:Button]',
|
|
183
|
+
'~k_parent': 'key-home',
|
|
184
|
+
'~r': 'ref-home',
|
|
185
|
+
'~l': 5,
|
|
186
|
+
},
|
|
187
|
+
// List item block — config ids inside lists carry the `$` placeholder;
|
|
188
|
+
// runtime block ids have array indices applied (`my_list.0.item_title`).
|
|
189
|
+
'key-list-item': {
|
|
190
|
+
key: 'root.blocks[3:my_list:List].blocks[0:my_list.$.item_title:Title]',
|
|
191
|
+
'~k_parent': 'key-home',
|
|
192
|
+
'~r': 'ref-home',
|
|
193
|
+
'~l': 9,
|
|
194
|
+
},
|
|
195
|
+
// JIT subtree for page "other" — holds a block id that also exists on
|
|
196
|
+
// home, with an IDENTICAL key shape. findConfig must scope pageId scans
|
|
197
|
+
// via the ~k_parent chain, not the key path.
|
|
198
|
+
'key-other': { key: 'root', '~k_parent': 'key-other-jit-parent', '~r': 'ref-other', '~l': 1 },
|
|
199
|
+
'key-other-button': {
|
|
200
|
+
key: 'root.blocks[0:my_button:Button]',
|
|
201
|
+
'~k_parent': 'key-other',
|
|
202
|
+
'~r': 'ref-other',
|
|
203
|
+
'~l': 4,
|
|
204
|
+
},
|
|
205
|
+
// Skeleton-built page (like the default 404) — content is keyed inside
|
|
206
|
+
// the global config tree with the page segment inline, and chains to the
|
|
207
|
+
// shared config root rather than a page-specific one.
|
|
208
|
+
'key-legal': {
|
|
209
|
+
key: 'root.pages[2:legal]',
|
|
210
|
+
'~k_parent': 'key-pages',
|
|
211
|
+
'~r': 'ref-legal',
|
|
212
|
+
'~l': 1,
|
|
213
|
+
},
|
|
214
|
+
'key-legal-button': {
|
|
215
|
+
key: 'root.pages[2:legal].blocks[0:legal_button:Button]',
|
|
216
|
+
'~k_parent': 'key-legal',
|
|
217
|
+
'~r': 'ref-legal',
|
|
218
|
+
'~l': 3,
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
write('build/refMap.json', {
|
|
223
|
+
'ref-home': { parent: null, path: 'pages/home.yaml' },
|
|
224
|
+
'ref-other': { parent: null, path: 'pages/other.yaml' },
|
|
225
|
+
'ref-legal': { parent: null, path: 'pages/legal.yaml' },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
write('build/pages/home.json', {
|
|
229
|
+
pageId: 'home',
|
|
230
|
+
blocks: [
|
|
231
|
+
{ id: 'my_button', type: 'Button', '~k': 'key-button' },
|
|
232
|
+
{
|
|
233
|
+
id: 'card',
|
|
234
|
+
type: 'Card',
|
|
235
|
+
slots: { content: { blocks: [{ id: 'nested_button', type: 'Button' }] } },
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
requests: [
|
|
239
|
+
{ id: 'request:home:req-read', requestId: 'req-read', payload: {} },
|
|
240
|
+
{ id: 'request:home:req-write', requestId: 'req-write', payload: {} },
|
|
241
|
+
{ id: 'request:home:req-unknown', requestId: 'req-unknown', payload: {} },
|
|
242
|
+
],
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
write('build/pages/other.json', {
|
|
246
|
+
pageId: 'other',
|
|
247
|
+
blocks: [{ id: 'my_button', type: 'Button', '~k': 'key-other-button' }],
|
|
248
|
+
requests: [],
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
write('build/pages/legal.json', {
|
|
252
|
+
pageId: 'legal',
|
|
253
|
+
blocks: [{ id: 'legal_button', type: 'Button', '~k': 'key-legal-button' }],
|
|
254
|
+
requests: [],
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Per-request artifacts (packages/build/src/build/full/writeRequests.js
|
|
258
|
+
// strips type/connectionId/properties/auth off the page's `requests` array
|
|
259
|
+
// after writing these, so a request's type is only readable here).
|
|
260
|
+
write('build/pages/home/requests/req-read.json', {
|
|
261
|
+
id: 'request:home:req-read',
|
|
262
|
+
requestId: 'req-read',
|
|
263
|
+
pageId: 'home',
|
|
264
|
+
connectionId: 'axios',
|
|
265
|
+
type: 'ReadOnlyRequest',
|
|
266
|
+
payload: {},
|
|
267
|
+
properties: {},
|
|
268
|
+
});
|
|
269
|
+
write('build/pages/home/requests/req-write.json', {
|
|
270
|
+
id: 'request:home:req-write',
|
|
271
|
+
requestId: 'req-write',
|
|
272
|
+
pageId: 'home',
|
|
273
|
+
connectionId: 'axios',
|
|
274
|
+
type: 'WriteRequest',
|
|
275
|
+
payload: {},
|
|
276
|
+
properties: {},
|
|
277
|
+
});
|
|
278
|
+
write('build/pages/home/requests/req-unknown.json', {
|
|
279
|
+
id: 'request:home:req-unknown',
|
|
280
|
+
requestId: 'req-unknown',
|
|
281
|
+
pageId: 'home',
|
|
282
|
+
connectionId: 'axios',
|
|
283
|
+
type: 'UnknownMetaRequest',
|
|
284
|
+
payload: {},
|
|
285
|
+
properties: {},
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
write('build/connectionIds.json', ['axios']);
|
|
289
|
+
write('build/connections/axios.json', {
|
|
290
|
+
id: 'connection:axios',
|
|
291
|
+
connectionId: 'axios',
|
|
292
|
+
type: 'AxiosHttp',
|
|
293
|
+
properties: {},
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
write('build/menus.json', [
|
|
297
|
+
{
|
|
298
|
+
id: 'menu:default',
|
|
299
|
+
menuId: 'default',
|
|
300
|
+
links: [
|
|
301
|
+
{
|
|
302
|
+
id: 'menuitem:default:home',
|
|
303
|
+
menuItemId: 'home',
|
|
304
|
+
type: 'MenuLink',
|
|
305
|
+
pageId: 'home',
|
|
306
|
+
properties: { title: 'Home' },
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
]);
|
|
311
|
+
|
|
312
|
+
write('build/api/resolve_greeting.json', {
|
|
313
|
+
id: 'endpoint:resolve_greeting',
|
|
314
|
+
type: 'InternalApi',
|
|
315
|
+
routine: [],
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
write('build/agents/assistant.json', {
|
|
319
|
+
id: 'agent:assistant',
|
|
320
|
+
agentId: 'assistant',
|
|
321
|
+
type: 'OpenAiAgent',
|
|
322
|
+
connectionId: 'axios',
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
write('build/websocketIds.json', []);
|
|
326
|
+
|
|
327
|
+
// No cli.agentTools.allowWriteRequests — tests toggle this file directly
|
|
328
|
+
// (isWriteRequestsAllowed reads it fresh, no caching) to exercise the
|
|
329
|
+
// opt-in gate.
|
|
330
|
+
write('lowdefy.yaml', 'lowdefy: 5.0.0\n');
|
|
331
|
+
|
|
332
|
+
return fixtureDir;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export default setupTestFixtures;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import { buildPageUrl } from './getBrowser.js';
|
|
20
|
+
import inspectState from './inspectState.js';
|
|
21
|
+
import { writeCheckpoint } from './checkpointStore.js';
|
|
22
|
+
|
|
23
|
+
// Snapshots the running app (page state + recorded request/api responses)
|
|
24
|
+
// into a checkpoint folder under .lowdefy — see checkpointStore.js for the
|
|
25
|
+
// on-disk layout. Reuses inspectState.js's tab-first/headless-fallback
|
|
26
|
+
// snapshot rather than driving a browser directly, so a snapshot always
|
|
27
|
+
// reflects whatever a developer is actually looking at when one is
|
|
28
|
+
// available.
|
|
29
|
+
async function snapshotState({ origin, pageId, name, notes, source, overwrite }) {
|
|
30
|
+
if (type.isNone(pageId) || !type.isString(pageId)) {
|
|
31
|
+
return {
|
|
32
|
+
error: `snapshotState requires a "pageId" string. Received ${JSON.stringify(pageId)}.`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (type.isNone(name) || !type.isString(name)) {
|
|
36
|
+
return { error: `snapshotState requires a "name" string. Received ${JSON.stringify(name)}.` };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const snapshot = await inspectState({ origin, pageId, source });
|
|
40
|
+
if (snapshot?.error) {
|
|
41
|
+
return { error: snapshot.error };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const url = `${buildPageUrl({ origin, pageId })}${snapshot.urlQuery ?? ''}`;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const { dir, parts } = writeCheckpoint({
|
|
48
|
+
name,
|
|
49
|
+
snapshot: { ...snapshot, url },
|
|
50
|
+
notes,
|
|
51
|
+
overwrite,
|
|
52
|
+
});
|
|
53
|
+
return { name, dir, parts, source: snapshot.source };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return { error: error.message };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default snapshotState;
|