@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,122 @@
|
|
|
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
|
+
import { jest } from '@jest/globals';
|
|
21
|
+
|
|
22
|
+
// openPage takes its browser as a parameter, so a fake one covers the cookie
|
|
23
|
+
// injection without a real Chromium. lib/build/config.js reads build/config.json
|
|
24
|
+
// from process.cwd() at import time — chdir into a fixture that has one before
|
|
25
|
+
// getBrowser.js is loaded. Mirrors the pattern in screenshotPage.test.mjs.
|
|
26
|
+
const originalCwd = process.cwd();
|
|
27
|
+
const fixtureDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lowdefy-get-browser-test-'));
|
|
28
|
+
fs.mkdirSync(path.join(fixtureDir, 'build'), { recursive: true });
|
|
29
|
+
fs.writeFileSync(path.join(fixtureDir, 'build', 'config.json'), JSON.stringify({ basePath: '' }));
|
|
30
|
+
process.chdir(fixtureDir);
|
|
31
|
+
|
|
32
|
+
const { openPage } = await import('./getBrowser.js');
|
|
33
|
+
|
|
34
|
+
afterAll(() => {
|
|
35
|
+
process.chdir(originalCwd);
|
|
36
|
+
fs.rmSync(fixtureDir, { recursive: true, force: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function createBrowser() {
|
|
40
|
+
const addCookies = jest.fn();
|
|
41
|
+
const page = {
|
|
42
|
+
goto: jest.fn().mockResolvedValue(undefined),
|
|
43
|
+
waitForFunction: jest.fn().mockResolvedValue(undefined),
|
|
44
|
+
};
|
|
45
|
+
const context = {
|
|
46
|
+
addCookies,
|
|
47
|
+
newPage: jest.fn().mockResolvedValue(page),
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
browser: { newContext: jest.fn().mockResolvedValue(context) },
|
|
51
|
+
addCookies,
|
|
52
|
+
context,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function decodeUserCookie(addCookies) {
|
|
57
|
+
const [[[cookie]]] = addCookies.mock.calls;
|
|
58
|
+
return JSON.parse(Buffer.from(cookie.value, 'base64').toString());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test('openPage injects the default roleless user when no user is given', async () => {
|
|
62
|
+
const { browser, addCookies } = createBrowser();
|
|
63
|
+
|
|
64
|
+
await openPage({ browser, origin: 'http://localhost:3001', pageId: 'home' });
|
|
65
|
+
|
|
66
|
+
expect(decodeUserCookie(addCookies)).toEqual({
|
|
67
|
+
sub: 'lowdefy-headless',
|
|
68
|
+
id: 'lowdefy-headless',
|
|
69
|
+
name: 'Lowdefy Headless',
|
|
70
|
+
roles: [],
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('openPage injects a per-call user with roles', async () => {
|
|
75
|
+
const { browser, addCookies } = createBrowser();
|
|
76
|
+
|
|
77
|
+
await openPage({
|
|
78
|
+
browser,
|
|
79
|
+
origin: 'http://localhost:3001',
|
|
80
|
+
pageId: 'home',
|
|
81
|
+
user: { id: 'agent', roles: ['user-admin'] },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
expect(decodeUserCookie(addCookies)).toEqual({
|
|
85
|
+
sub: 'lowdefy-headless',
|
|
86
|
+
id: 'agent',
|
|
87
|
+
name: 'Lowdefy Headless',
|
|
88
|
+
roles: ['user-admin'],
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('openPage gives concurrent calls their own identity', async () => {
|
|
93
|
+
const admin = createBrowser();
|
|
94
|
+
const member = createBrowser();
|
|
95
|
+
|
|
96
|
+
await Promise.all([
|
|
97
|
+
openPage({
|
|
98
|
+
browser: admin.browser,
|
|
99
|
+
origin: 'http://localhost:3001',
|
|
100
|
+
pageId: 'home',
|
|
101
|
+
user: { roles: ['user-admin'] },
|
|
102
|
+
}),
|
|
103
|
+
openPage({
|
|
104
|
+
browser: member.browser,
|
|
105
|
+
origin: 'http://localhost:3001',
|
|
106
|
+
pageId: 'home',
|
|
107
|
+
user: { roles: ['member'] },
|
|
108
|
+
}),
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
expect(decodeUserCookie(admin.addCookies).roles).toEqual(['user-admin']);
|
|
112
|
+
expect(decodeUserCookie(member.addCookies).roles).toEqual(['member']);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('openPage rejects an invalid user before opening a browser context', async () => {
|
|
116
|
+
const { browser } = createBrowser();
|
|
117
|
+
|
|
118
|
+
await expect(
|
|
119
|
+
openPage({ browser, origin: 'http://localhost:3001', pageId: 'home', user: 'admin' })
|
|
120
|
+
).rejects.toThrow('Headless "user" must be an object. Received "admin".');
|
|
121
|
+
expect(browser.newContext).not.toHaveBeenCalled();
|
|
122
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
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 clientErrorStore from './clientErrorStore.js';
|
|
18
|
+
import readBuildArtifact from './readBuildArtifact.js';
|
|
19
|
+
|
|
20
|
+
// Feedback loop for agents: build status (written by the build manager to
|
|
21
|
+
// build/buildStatus.json) plus recent browser errors reported via
|
|
22
|
+
// POST /api/client-error. Lets an agent check "did my last edit work?"
|
|
23
|
+
// without tailing terminal logs.
|
|
24
|
+
function getBuildStatus() {
|
|
25
|
+
const build = readBuildArtifact({ name: 'buildStatus.json' }) ?? {
|
|
26
|
+
status: 'unknown',
|
|
27
|
+
message:
|
|
28
|
+
'No build status yet — the build manager has not written build/buildStatus.json. ' +
|
|
29
|
+
'This is expected before the first build completes.',
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
build,
|
|
33
|
+
clientErrors: clientErrorStore.list(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default getBuildStatus;
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
function findDoc({ manifest, slug, kind, typeName }) {
|
|
24
|
+
if (!type.isNone(slug)) {
|
|
25
|
+
return manifest.docs.find((doc) => doc.slug === slug) ?? null;
|
|
26
|
+
}
|
|
27
|
+
if (type.isNone(typeName)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
let singularKind = type.isNone(kind) ? null : String(kind).toLowerCase().replace(/s$/, '');
|
|
31
|
+
// Requests are documented on their connection's page (e.g. MongoDBFind → MongoDB).
|
|
32
|
+
if (singularKind === 'request') {
|
|
33
|
+
singularKind = 'connection';
|
|
34
|
+
}
|
|
35
|
+
const candidates = manifest.docs.filter(
|
|
36
|
+
(doc) => doc.typeName && (type.isNone(singularKind) || doc.kind === singularKind)
|
|
37
|
+
);
|
|
38
|
+
const exact = candidates.find((doc) => doc.typeName === typeName);
|
|
39
|
+
if (exact) {
|
|
40
|
+
return exact;
|
|
41
|
+
}
|
|
42
|
+
// Connection docs are titled per package (e.g. "MongoDB" covers
|
|
43
|
+
// MongoDBCollection and all MongoDB requests) — fall back to prefix match.
|
|
44
|
+
const prefixed = candidates
|
|
45
|
+
.filter((doc) => typeName.startsWith(doc.typeName))
|
|
46
|
+
.sort((a, b) => b.typeName.length - a.typeName.length);
|
|
47
|
+
return prefixed[0] ?? null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getCoreDoc({ slug, kind, type: typeName }) {
|
|
51
|
+
const manifest = getDocsManifest();
|
|
52
|
+
if (type.isNone(manifest)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const doc = findDoc({ manifest, slug, kind, typeName });
|
|
56
|
+
if (type.isNone(doc)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const markdown = fs.readFileSync(path.join(manifest.contentDir, doc.path), 'utf8');
|
|
60
|
+
return { slug: doc.slug, title: doc.title, section: doc.section, markdown };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default getCoreDoc;
|
|
@@ -0,0 +1,40 @@
|
|
|
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 { createRequire } from 'node:module';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
|
|
21
|
+
const require = createRequire(import.meta.url);
|
|
22
|
+
|
|
23
|
+
let manifest;
|
|
24
|
+
|
|
25
|
+
function getDocsManifest() {
|
|
26
|
+
if (manifest !== undefined) {
|
|
27
|
+
return manifest;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const manifestPath = require.resolve('@lowdefy/docs-content/index.json');
|
|
31
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
32
|
+
manifest.contentDir = path.dirname(manifestPath);
|
|
33
|
+
} catch {
|
|
34
|
+
// docs-content not installed — core docs unavailable, plugin data still served.
|
|
35
|
+
manifest = null;
|
|
36
|
+
}
|
|
37
|
+
return manifest;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default getDocsManifest;
|
|
@@ -0,0 +1,58 @@
|
|
|
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 readBuildArtifact from './readBuildArtifact.js';
|
|
22
|
+
import resolvePluginDir from './resolvePluginDir.js';
|
|
23
|
+
|
|
24
|
+
const EXAMPLE_FILES = ['gallery.yaml', 'examples.yaml', 'tests.yaml'];
|
|
25
|
+
|
|
26
|
+
// Block example yaml ships in plugin dist by convention
|
|
27
|
+
// (dist/blocks/<Block>/gallery.yaml). Local plugins may only ship some of
|
|
28
|
+
// these files, or none — return whatever exists, never throw.
|
|
29
|
+
function getExamples({ type: typeName }) {
|
|
30
|
+
const availableTypes = readBuildArtifact({ name: 'plugins/availableTypes.json' }) ?? {};
|
|
31
|
+
const definition = availableTypes.blocks?.[typeName];
|
|
32
|
+
if (type.isNone(definition)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const pluginDir = resolvePluginDir({ packageName: definition.package });
|
|
36
|
+
if (type.isNone(pluginDir)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const files = {};
|
|
40
|
+
for (const baseDir of ['dist/blocks', 'src/blocks', 'blocks']) {
|
|
41
|
+
const blockDir = path.join(pluginDir, baseDir, definition.originalTypeName);
|
|
42
|
+
for (const fileName of EXAMPLE_FILES) {
|
|
43
|
+
const filePath = path.join(blockDir, fileName);
|
|
44
|
+
if (!files[fileName] && fs.existsSync(filePath)) {
|
|
45
|
+
files[fileName] = fs.readFileSync(filePath, 'utf8');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (Object.keys(files).length > 0) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (Object.keys(files).length === 0) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return { type: typeName, package: definition.package, files };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default getExamples;
|
|
@@ -0,0 +1,115 @@
|
|
|
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 getDocsManifest from './getDocsManifest.js';
|
|
18
|
+
import listPlugins from './listPlugins.js';
|
|
19
|
+
import readBuildArtifact from './readBuildArtifact.js';
|
|
20
|
+
|
|
21
|
+
function countKinds({ availableTypes }) {
|
|
22
|
+
return {
|
|
23
|
+
blocks: Object.keys(availableTypes.blocks ?? {}).length,
|
|
24
|
+
operators: new Set([
|
|
25
|
+
...Object.keys(availableTypes.operators?.client ?? {}),
|
|
26
|
+
...Object.keys(availableTypes.operators?.server ?? {}),
|
|
27
|
+
]).size,
|
|
28
|
+
actions: Object.keys(availableTypes.actions ?? {}).length,
|
|
29
|
+
connections: Object.keys(availableTypes.connections ?? {}).length,
|
|
30
|
+
requests: Object.keys(availableTypes.requests ?? {}).length,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getOverview() {
|
|
35
|
+
const availableTypes = readBuildArtifact({ name: 'plugins/availableTypes.json' }) ?? {};
|
|
36
|
+
const counts = countKinds({ availableTypes });
|
|
37
|
+
const plugins = listPlugins();
|
|
38
|
+
const manifest = getDocsManifest();
|
|
39
|
+
const sections = [...new Set((manifest?.docs ?? []).map((doc) => doc.section))];
|
|
40
|
+
|
|
41
|
+
const lines = [
|
|
42
|
+
'# Lowdefy Docs for AI Coding Agents',
|
|
43
|
+
'',
|
|
44
|
+
'Lowdefy is a config-driven web framework: apps are YAML files composing **blocks** (UI components), **operators** (logic like `_if`, `_get`, `_state`), **actions** (event handlers), and **connections/requests** (databases and APIs).',
|
|
45
|
+
'',
|
|
46
|
+
'This server describes everything installed in THIS project — never guess type names or properties, look them up here first.',
|
|
47
|
+
'',
|
|
48
|
+
'## What is available',
|
|
49
|
+
'',
|
|
50
|
+
`- ${counts.blocks} block types, ${counts.operators} operators, ${counts.actions} actions, ${counts.connections} connections, ${counts.requests} request types (from ${plugins.length} plugin packages, including this project's local plugins).`,
|
|
51
|
+
manifest
|
|
52
|
+
? `- ${manifest.docs.length} core documentation pages (sections: ${sections.join(', ')}).`
|
|
53
|
+
: '- Core documentation package (@lowdefy/docs-content) is not installed.',
|
|
54
|
+
'',
|
|
55
|
+
'## How to use this (recommended order)',
|
|
56
|
+
'',
|
|
57
|
+
'1. **List what exists**: `GET /lowdefy-docs/blocks`, `/lowdefy-docs/operators`, `/lowdefy-docs/actions`, `/lowdefy-docs/connections`, `/lowdefy-docs/requests` — or the `lowdefy_list_types` MCP tool. Each entry links to its schema, examples, and doc.',
|
|
58
|
+
'2. **Get the exact contract**: `GET /lowdefy-docs/schema/{kind}/{type}` (e.g. `/lowdefy-docs/schema/blocks/Button`) — or `lowdefy_get_schema`. JSON Schema of all properties.',
|
|
59
|
+
'3. **See real usage**: `GET /lowdefy-docs/examples/{type}` — or `lowdefy_get_examples`. YAML examples for block types.',
|
|
60
|
+
'4. **Read concept docs**: `GET /lowdefy-docs/content/{slug}` (e.g. `/lowdefy-docs/content/operators/_get`) — or `lowdefy_get_doc`. Start with `concepts/lowdefy-schema`, `concepts/blocks`, `concepts/events-and-actions`, `concepts/connections-and-requests`, `concepts/operators`.',
|
|
61
|
+
'5. **Search**: `GET /lowdefy-docs/search?q=...` — or `lowdefy_search_docs`.',
|
|
62
|
+
'6. **Plugin packages**: `GET /lowdefy-docs/plugins` lists every installed plugin; `GET /lowdefy-docs/plugin-doc/{package}` returns markdown a plugin ships itself (READMEs, guides).',
|
|
63
|
+
'',
|
|
64
|
+
'## The feedback loop (after every edit)',
|
|
65
|
+
'',
|
|
66
|
+
'The dev server rebuilds automatically when config files change. After EVERY edit:',
|
|
67
|
+
'',
|
|
68
|
+
'1. **Check what broke**: `GET /lowdefy-docs/build-status` — or `lowdefy_build_status`. Current build errors and warnings with source file locations, plus recent browser runtime errors.',
|
|
69
|
+
'2. **Verify the page**: `GET /lowdefy-docs/page-config/{pageId}` — or `lowdefy_get_page_config`. The fully built page config, or its structured build errors.',
|
|
70
|
+
'3. **See it rendered**: `GET /lowdefy-docs/screenshot/{pageId}` — or `lowdefy_screenshot_page`. PNG of the page in headless Chromium.',
|
|
71
|
+
'4. **Locate config**: `GET /lowdefy-docs/find/{id}?pageId=...` — or `lowdefy_find_config`. Which yaml file defines a page/block/request id.',
|
|
72
|
+
'5. **Scaffold**: `lowdefy_scaffold_page` creates a canonical new page file (then register it in lowdefy.yaml).',
|
|
73
|
+
'',
|
|
74
|
+
'## Live state and testing',
|
|
75
|
+
'',
|
|
76
|
+
"1. **Inspect live state**: `GET /lowdefy-docs/inspect-state/{pageId}` — or `lowdefy_inspect_state`. The actual state, request results, and event log of a running page. Reads the developer's open browser tab when connected (ask them to interact, then inspect), else runs the page headless.",
|
|
77
|
+
'2. **Evaluate operators**: `POST /lowdefy-docs/eval-operator` `{pageId, expression}` — or `lowdefy_eval_operator`. A REPL for config: run any operator expression against live page state. The operator goes in the `expression` body key (`operator` is accepted as an alias).',
|
|
78
|
+
'3. **Run a request**: `POST /lowdefy-docs/run-request` `{pageId, requestId, payload}` — or `lowdefy_run_request`. Verify the data shape a request returns. Read-only types always run; writes need `cli.agentTools.allowWriteRequests: true` in lowdefy.yaml.',
|
|
79
|
+
'4. **Understand the app**: `GET /lowdefy-docs/app-map` — or `lowdefy_app_map`. Every page, menu, connection, endpoint, agent, and websocket in one call.',
|
|
80
|
+
'5. **Config checkpoints**: `POST /lowdefy-docs/checkpoints` `{label}` / `POST /lowdefy-docs/checkpoints/revert` `{id}` — or `lowdefy_checkpoint` / `lowdefy_revert_checkpoint`. Snapshot config files before risky changes; revert instantly.',
|
|
81
|
+
"6. **State checkpoints**: `POST /lowdefy-docs/state-checkpoints/snapshot` `{pageId, name}` captures live page state + request responses into .lowdefy/state-checkpoints/<name>/ (gitignored — checkpoints contain user/session data); `POST /lowdefy-docs/state-checkpoints/load` `{name, mode}` restores it — 'registry-only' mode returns a ?_checkpoint URL a human can open to test the app in that exact state. `lowdefy_checkpoint_to_mocks` converts one into e2e mocks.yaml fixtures.",
|
|
82
|
+
'',
|
|
83
|
+
'## Visual feedback (annotation helper)',
|
|
84
|
+
'',
|
|
85
|
+
'The developer can press Cmd/Ctrl+/ in the running app to point at elements, draw, and comment; the helper copies an agent-readable feedback block to their clipboard which they paste into their agent session. Pasted blocks start with "Feedback:" and carry the blockId, resolved config file:line, drawn shapes (usable as screenshot clip geometry), and recent console entries — treat them as precise UI feedback.',
|
|
86
|
+
'',
|
|
87
|
+
'## Routes',
|
|
88
|
+
'',
|
|
89
|
+
'| Route | Purpose |',
|
|
90
|
+
'| --- | --- |',
|
|
91
|
+
'| `GET /lowdefy-docs` | This overview |',
|
|
92
|
+
'| `GET /lowdefy-docs/{kind}` | List all available types of a kind (blocks, operators, actions, connections, requests, agents, notifications, websockets) |',
|
|
93
|
+
'| `GET /lowdefy-docs/plugins` | Installed plugin packages and the types each provides |',
|
|
94
|
+
'| `GET /lowdefy-docs/schema/{kind}/{type}` | JSON schema for a type |',
|
|
95
|
+
'| `GET /lowdefy-docs/examples/{type}` | Example yaml for a block type |',
|
|
96
|
+
'| `GET /lowdefy-docs/content/{slug}` | Core doc page as markdown |',
|
|
97
|
+
'| `GET /lowdefy-docs/search?q={query}` | Search core docs |',
|
|
98
|
+
'| `GET /lowdefy-docs/plugin-doc/{package}` | Markdown shipped inside a plugin package |',
|
|
99
|
+
'| `GET /lowdefy-docs/build-status` | Current build errors/warnings + recent browser errors |',
|
|
100
|
+
'| `GET /lowdefy-docs/page-config/{pageId}` | Fully built page config, or its build errors |',
|
|
101
|
+
'| `GET /lowdefy-docs/screenshot/{pageId}` | PNG screenshot of the rendered page |',
|
|
102
|
+
'| `GET /lowdefy-docs/find/{id}?pageId=` | Locate where a page/block/request id is defined |',
|
|
103
|
+
'| `GET /lowdefy-docs/inspect-state/{pageId}?source=` | Live state/requests/eventLog of a running page (tab or headless) |',
|
|
104
|
+
'| `POST /lowdefy-docs/eval-operator` | Evaluate an operator expression against live page state |',
|
|
105
|
+
'| `POST /lowdefy-docs/run-request` | Execute a request with a test payload (read-only unless opted in) |',
|
|
106
|
+
'| `GET /lowdefy-docs/app-map` | Whole-app graph: pages, menus, connections, endpoints, agents |',
|
|
107
|
+
'| `GET/POST /lowdefy-docs/checkpoints` + `/revert` | Config-file checkpoints: list, create, revert |',
|
|
108
|
+
'| `GET/POST /lowdefy-docs/state-checkpoints` + `/snapshot`, `/load` | State & data checkpoints: capture/restore live app state |',
|
|
109
|
+
'| `POST /lowdefy-feedback` | Annotation helper: enrich + format a feedback batch (overlay → clipboard) |',
|
|
110
|
+
'| `ALL /lowdefy-docs/mcp` | MCP endpoint (streamable HTTP) exposing all of the above as tools |',
|
|
111
|
+
];
|
|
112
|
+
return lines.join('\n');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default getOverview;
|
|
@@ -0,0 +1,53 @@
|
|
|
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 path from 'node:path';
|
|
18
|
+
|
|
19
|
+
import buildPageIfNeeded from '../server/jitPageBuilder.js';
|
|
20
|
+
import mapPageBuildErrors from './mapPageBuildErrors.js';
|
|
21
|
+
import readBuildArtifact from './readBuildArtifact.js';
|
|
22
|
+
import readPageArtifact from './readPageArtifact.js';
|
|
23
|
+
|
|
24
|
+
// JIT-builds a page (same pipeline as GET /api/page/*, see src/routes/jitPage.js)
|
|
25
|
+
// and returns its built config so agents can inspect a page's blocks/requests
|
|
26
|
+
// without a browser. Returns null when the pageId isn't registered at all.
|
|
27
|
+
async function getPageConfig({ pageId }) {
|
|
28
|
+
const registry = readBuildArtifact({ name: 'pageRegistry.json' }) ?? {};
|
|
29
|
+
if (!registry[pageId]) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const buildDirectory = path.join(process.cwd(), 'build');
|
|
34
|
+
const configDirectory = process.env.LOWDEFY_DIRECTORY_CONFIG || process.cwd();
|
|
35
|
+
|
|
36
|
+
let buildResult;
|
|
37
|
+
try {
|
|
38
|
+
buildResult = await buildPageIfNeeded({ pageId, buildDirectory, configDirectory });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return { buildError: true, errors: mapPageBuildErrors(error) };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (buildResult === false) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (buildResult && buildResult.installing) {
|
|
47
|
+
return { installing: true, packages: buildResult.packages };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return readPageArtifact({ pageId }) ?? {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default getPageConfig;
|
|
@@ -0,0 +1,55 @@
|
|
|
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 resolvePluginDir from './resolvePluginDir.js';
|
|
22
|
+
|
|
23
|
+
// Local plugins have no doc convention — probe the common locations and
|
|
24
|
+
// return whatever markdown ships with the package.
|
|
25
|
+
function getPluginDoc({ packageName }) {
|
|
26
|
+
const pluginDir = resolvePluginDir({ packageName });
|
|
27
|
+
if (type.isNone(pluginDir)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const sections = [];
|
|
31
|
+
for (const readme of ['README.md', 'readme.md']) {
|
|
32
|
+
const readmePath = path.join(pluginDir, readme);
|
|
33
|
+
if (fs.existsSync(readmePath)) {
|
|
34
|
+
sections.push(fs.readFileSync(readmePath, 'utf8'));
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for (const docsDir of ['docs', 'dist/docs']) {
|
|
39
|
+
const dirPath = path.join(pluginDir, docsDir);
|
|
40
|
+
if (!fs.existsSync(dirPath)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
for (const fileName of fs.readdirSync(dirPath).sort()) {
|
|
44
|
+
if (fileName.endsWith('.md')) {
|
|
45
|
+
sections.push(fs.readFileSync(path.join(dirPath, fileName), 'utf8'));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (sections.length === 0) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return { package: packageName, markdown: sections.join('\n\n---\n\n') };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default getPluginDoc;
|
|
@@ -0,0 +1,65 @@
|
|
|
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 normalizeTypeKind from './normalizeTypeKind.js';
|
|
20
|
+
import readBuildArtifact from './readBuildArtifact.js';
|
|
21
|
+
|
|
22
|
+
const SCHEMA_ARTIFACTS = {
|
|
23
|
+
actions: 'plugins/actionSchemas.json',
|
|
24
|
+
blocks: 'plugins/blockSchemas.json',
|
|
25
|
+
connections: 'plugins/connectionSchemas.json',
|
|
26
|
+
operators: 'plugins/operatorSchemas.json',
|
|
27
|
+
requests: 'plugins/requestSchemas.json',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function getSchema({ kind, type: typeName }) {
|
|
31
|
+
const normalizedKind = normalizeTypeKind({ kind });
|
|
32
|
+
if (type.isNone(SCHEMA_ARTIFACTS[normalizedKind])) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`No schemas available for type kind. Received ${JSON.stringify(kind)}. Use one of: blocks, operators, actions, connections, requests.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const schemas = readBuildArtifact({ name: SCHEMA_ARTIFACTS[normalizedKind] }) ?? {};
|
|
38
|
+
const entry = schemas[typeName];
|
|
39
|
+
if (type.isNone(entry)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const result = { kind: normalizedKind, type: typeName };
|
|
43
|
+
if (normalizedKind === 'connections') {
|
|
44
|
+
result.schema = entry.schema ?? entry;
|
|
45
|
+
if (entry.requests) {
|
|
46
|
+
result.requests = entry.requests;
|
|
47
|
+
}
|
|
48
|
+
} else if (normalizedKind === 'requests') {
|
|
49
|
+
result.schema = entry.schema ?? entry;
|
|
50
|
+
if (entry.meta) {
|
|
51
|
+
result.meta = entry.meta;
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
result.schema = entry;
|
|
55
|
+
}
|
|
56
|
+
if (normalizedKind === 'blocks') {
|
|
57
|
+
const blockMetas = readBuildArtifact({ name: 'plugins/blockMetas.json' }) ?? {};
|
|
58
|
+
if (blockMetas[typeName]) {
|
|
59
|
+
result.meta = blockMetas[typeName];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export default getSchema;
|
|
@@ -0,0 +1,47 @@
|
|
|
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 inspectStateFromTab from './inspectStateFromTab.js';
|
|
18
|
+
import inspectStateHeadless from './inspectStateHeadless.js';
|
|
19
|
+
import resolveSource from './resolveSource.js';
|
|
20
|
+
|
|
21
|
+
// resolveSource picks the tab or headless — including why `user` is
|
|
22
|
+
// headless-only. Falls back to headless whenever the tab path isn't usable
|
|
23
|
+
// (no tab connected, or `source: 'tab'` was requested but it errored) so
|
|
24
|
+
// agents always get an answer.
|
|
25
|
+
async function inspectState({ origin, pageId, source, user }) {
|
|
26
|
+
const { tryTab, error, invalidInput } = resolveSource({
|
|
27
|
+
name: 'inspectState',
|
|
28
|
+
pageId,
|
|
29
|
+
source,
|
|
30
|
+
user,
|
|
31
|
+
});
|
|
32
|
+
if (error) {
|
|
33
|
+
return { error, invalidInput };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (tryTab) {
|
|
37
|
+
const result = await inspectStateFromTab({ pageId });
|
|
38
|
+
if (!result?.error) {
|
|
39
|
+
return { ...result, source: 'tab' };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = await inspectStateHeadless({ origin, pageId, user });
|
|
44
|
+
return { ...result, source: 'headless' };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default inspectState;
|