kennel 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/cli/src/plugins/external.d.ts +3 -0
- package/dist/cli/src/plugins/external.js +24 -12
- package/dist/cli/src/site/hot-viewer.js +45 -38
- package/dist/cli/src/site/server-plugins.d.ts +2 -2
- package/dist/cli/src/site/viewer.js +13 -8
- package/dist/core/api-client.js +576 -10317
- package/dist/core/core-folders.js +2313 -12088
- package/dist/core/runtime.js +7899 -17525
- package/dist/package/preview.js +9838 -19692
- package/dist/plugins/external.d.ts +3 -0
- package/dist/plugins/external.js +24 -12
- package/dist/site/hot-viewer.js +45 -38
- package/dist/site/server-plugins.d.ts +2 -2
- package/dist/site/server-plugins.js +9 -9
- package/dist/site/viewer.js +13 -8
- package/kernel/apps/web/package.json +1 -1
- package/kernel/apps/web/scripts/dev.mjs +4 -0
- package/kernel/apps/web/src/api/events.ts +60 -24
- package/kernel/apps/web/src/external-plugins.ts +1 -1
- package/kernel/apps/web/src/folder-filter-icons.tsx +1 -1
- package/kernel/apps/web/src/folder-plugin-view.tsx +1 -1
- package/kernel/apps/web/src/plugins.ts +1 -1
- package/kernel/apps/web/vite.config.ts +3 -1
- package/kernel/packages/agents/package.json +1 -1
- package/kernel/packages/agents/src/agent-card.tsx +1 -1
- package/kernel/packages/agents/src/client.tsx +1 -1
- package/kernel/packages/agents/src/manifest.ts +1 -1
- package/kernel/packages/agents/src/server.ts +1 -1
- package/kernel/packages/agents/src/viewer.tsx +1 -1
- package/kernel/packages/api/package.json +1 -1
- package/kernel/packages/api/src/server/acp-live-plugin.ts +1 -1
- package/kernel/packages/api/src/server/api/create-agent-file.ts +2 -2
- package/kernel/packages/api/src/server/api/document-edit.ts +2 -2
- package/kernel/packages/api/src/server/api/open-path.ts +2 -2
- package/kernel/packages/api/src/server/folder-metadata-plugin.ts +4 -4
- package/kernel/packages/api/src/server/plugins.ts +3 -3
- package/kernel/packages/api/src/shared/viewer-route.ts +1 -1
- package/kernel/packages/api/test/core.test.ts +1 -1
- package/kernel/packages/notes/package.json +1 -1
- package/kernel/packages/notes/src/client.tsx +1 -1
- package/kernel/packages/notes/src/manifest.ts +1 -1
- package/kernel/packages/notes/src/pages/notes-page.tsx +34 -1
- package/kernel/packages/notes/src/server.ts +1 -1
- package/kernel/packages/notes/src/ui/folder-notes-list.tsx +10 -74
- package/kernel/packages/plugins/SKILL.md +39 -0
- package/kernel/packages/{folder-plugin → plugins}/package.json +65 -11
- package/kernel/packages/plugins/src/index.ts +52 -0
- package/kernel/packages/plugins/src/page.ts +17 -0
- package/kernel/packages/{folder-plugin → plugins}/tsconfig.json +5 -1
- package/kernel/packages/plugins/tsup.config.ts +25 -0
- package/kernel/packages/ui/src/components/content-issues.tsx +1 -1
- package/kernel/packages/ui/tsconfig.json +1 -1
- package/package.json +2 -3
- package/web/assets/{abnfDiagram-VRR7QNED-xd_g1uK3.js → abnfDiagram-VRR7QNED-CDr-U31F.js} +1 -1
- package/web/assets/app-DHGorUqZ.js +2 -0
- package/web/assets/app-options-CtkiC8GR.js +25 -0
- package/web/assets/{app-view-header-CNk5KnA-.js → app-view-header-DbQuD6_F.js} +1 -1
- package/web/assets/{arc-B0u5yBJA.js → arc-Cwz5OeLQ.js} +1 -1
- package/web/assets/{architectureDiagram-ZJ3FMSHR-DfxOAo8P.js → architectureDiagram-ZJ3FMSHR-igGC0b-J.js} +1 -1
- package/web/assets/{ask-tray-SscJecDY.js → ask-tray-y8nkSKNG.js} +1 -1
- package/web/assets/{blockDiagram-677ZJIJ3-Ci3Usxh6.js → blockDiagram-677ZJIJ3-D4SDltQ_.js} +1 -1
- package/web/assets/{c4Diagram-LMCZKHZV-BqgwIVgh.js → c4Diagram-LMCZKHZV-Br15uN-s.js} +1 -1
- package/web/assets/channel-DRnAhH31.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-CfX69l2U.js → chunk-2Q5K7J3B-B0RqrvJA.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-CuFlzZkl.js → chunk-32BRIVSS-De9VBEyE.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-DidCMlJa.js → chunk-5VM5RSS4-YIMlZtW3.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-B5CDm1dS.js → chunk-EX3LRPZG-y9kGcrYi.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-D5fGV_sH.js → chunk-JWPE2WC7-D0A13HAu.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-ayhH5Q-H.js → chunk-MOJQB5TN-8v4jL8iu.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-C4BfOOZn.js → chunk-RYQCIY6F-Ri_iNmIp.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-DpUEIM9V.js → chunk-V7JOEXUC-b0mIbG_X.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-DoQuFFRh.js → chunk-VR4S4FIN-BkTZWt1n.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-CI9eiLwn.js → chunk-XXDRQBXY-D7muOXcd.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-BPkIpFlc.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-BPkIpFlc.js +1 -0
- package/web/assets/{client-Dk-NBvJR.js → client-BMso7lHb.js} +2 -2
- package/web/assets/client-Bkl3gwZ5.js +17 -0
- package/web/assets/client-soK_bLWT.js +5 -0
- package/web/assets/{command-zkzH4hVz.js → command-CsN8TfJg.js} +1 -1
- package/web/assets/{copy-context-D5oiWrYC.js → copy-context-DmBNpffM.js} +1 -1
- package/web/assets/{cose-bilkent-JH36ORCC-BHRk1mO3.js → cose-bilkent-JH36ORCC-7hnQZylB.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-C1B09TRE.js → cynefin-VYW2F7L2-CTbqXuvk.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-B27qXQbv.js → cynefinDiagram-TSTJHNR4-CmIJtocX.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-B1qEi32g.js → dagre-VKFMJZFB-BLPHNs2Y.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-BMkkgADD.js → diagram-FQU43EPY-L6FVWAuj.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-Cb8_g1A5.js → diagram-G47NLZAW-Bb26dbIM.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-B51wuc5m.js → diagram-NH7WQ7WH-BBauwdU-.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-xnsecbbI.js → diagram-OA4YK3LP-Dj_9FE7R.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-DSZdyaAV.js → diagram-WEI45ONY-Co1MS5BO.js} +1 -1
- package/web/assets/{document-Dc2dftTW.js → document-_ETc5epn.js} +3 -3
- package/web/assets/{ebnfDiagram-CCIWWBDH-aTHmcUGy.js → ebnfDiagram-CCIWWBDH-COGAgW5z.js} +1 -1
- package/web/assets/{entities-CCUtpskY.js → entities-ByGFSWnQ.js} +1 -1
- package/web/assets/{erDiagram-Q63AITRT-CUUPBcvo.js → erDiagram-Q63AITRT-CRMKCr0m.js} +1 -1
- package/web/assets/{file-viewer-DJ3rsmef.js → file-viewer-Dqx7DbTw.js} +1 -1
- package/web/assets/{flowDiagram-23GEKE2U-aTQXx2Mh.js → flowDiagram-23GEKE2U-Drv72N5g.js} +1 -1
- package/web/assets/folder-filter-bar-Bs_uyU0g.js +4 -0
- package/web/assets/folder-image-BBNRcu1j.js +1 -0
- package/web/assets/{folder-landing-DCnzYXXY.js → folder-landing-0EXAzEpD.js} +3 -3
- package/web/assets/{folder-list-DvVkVjiR.js → folder-list-Do8Kl8uk.js} +1 -1
- package/web/assets/{folder-metadata-C9hMMzIH.js → folder-metadata-DncP3Vx2.js} +1 -1
- package/web/assets/folder-mobile-bar-D-XXKrip.js +1 -0
- package/web/assets/folder-plugin-view-DoRQa8Dz.js +3 -0
- package/web/assets/{ganttDiagram-NO4QXBWP-DiwdUp7u.js → ganttDiagram-NO4QXBWP-DHEt2_9K.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-BL2gG_IS.js → gitGraphDiagram-IHSO6WYX-Bt6XsYTq.js} +1 -1
- package/web/assets/{index-CUTtMSyJ.js → index-7JCizcu7.js} +2 -2
- package/web/assets/index-DY0VGBht.js +1 -0
- package/web/assets/{infoDiagram-FWYZ7A6U-B3n0zZpt.js → infoDiagram-FWYZ7A6U-961DCJAH.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-D7YytbEv.js → ishikawaDiagram-FXEZZL3T-ibSYK50l.js} +1 -1
- package/web/assets/{journeyDiagram-5HDEW3XC-BYwqEl3C.js → journeyDiagram-5HDEW3XC-Cd0ZPqw2.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-BQHNJgXZ.js → kanban-definition-HUTT4EX6-Bu5skY8G.js} +1 -1
- package/web/assets/{linear-BPiQCLNk.js → linear-Cf8Xflqn.js} +1 -1
- package/web/assets/{mermaid.core-CD-KvBjV.js → mermaid.core-C9iHkrxr.js} +4 -4
- package/web/assets/{metadata-COJ7fZul.js → metadata-CLq-ZGmh.js} +1 -1
- package/web/assets/{mindmap-definition-LN4V7U3C-C1L_gyoj.js → mindmap-definition-LN4V7U3C-C5bK3O7k.js} +1 -1
- package/web/assets/{pegDiagram-2B236MQR-BDAfItqS.js → pegDiagram-2B236MQR-5rN2nkjz.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-CkZYH3Vz.js → pieDiagram-ENE6RG2P-DnSBFuIb.js} +1 -1
- package/web/assets/popover-D7MTK-sz.js +9 -0
- package/web/assets/{project-search-dialog-B2oqMT6h.js → project-search-dialog-Bo1JWdqR.js} +1 -1
- package/web/assets/{quadrantDiagram-ABIIQ3AL-Dy8Al3YZ.js → quadrantDiagram-ABIIQ3AL-xinqVpVM.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-Dd9_yo7B.js → railroadDiagram-RFXS5EU6-GsASFEVa.js} +1 -1
- package/web/assets/{requirementDiagram-TGXJPOKE-DPhIUsbs.js → requirementDiagram-TGXJPOKE-DS5CqNNo.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-B2QkYtub.js → sankeyDiagram-HTMAVEWB-DsnvjCXC.js} +1 -1
- package/web/assets/{search-C1J0-xiB.js → search-BA4r_Yoa.js} +1 -1
- package/web/assets/select-D4TpCgnU.js +30 -0
- package/web/assets/{selection-popup-qhlUFzGq.js → selection-popup-DIME-60e.js} +3 -3
- package/web/assets/{sequenceDiagram-DBY2YBRQ-Bx7fcZDx.js → sequenceDiagram-DBY2YBRQ-PI0WgJrf.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-CIXIhl_A.js → sizeCapture-X5ZJPWSS-Bpul3MSt.js} +1 -1
- package/web/assets/{stateDiagram-2N3HPSRC-DCrdAflB.js → stateDiagram-2N3HPSRC-DZEEP9Z2.js} +1 -1
- package/web/assets/stateDiagram-v2-6OUMAXLB-WAp7-BWX.js +1 -0
- package/web/assets/{swimlanes-5IMT3BWC-DA2vc7iZ.js → swimlanes-5IMT3BWC-_qVjNiCh.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-Dy4wBDSi.js +8 -0
- package/web/assets/{timeline-definition-FHXFAJF6-lKfJiV9V.js → timeline-definition-FHXFAJF6-CJev7rIB.js} +1 -1
- package/web/assets/use-folder-filter-hA6fa7nm.js +1 -0
- package/web/assets/{vennDiagram-L72KCM5P-DO1q2KzG.js → vennDiagram-L72KCM5P-BrTVvREy.js} +1 -1
- package/web/assets/{view-context-DXKM3-l1.js → view-context-Co01ON81.js} +35 -35
- package/web/assets/{viewer-capabilities-Cig_xM9u.js → viewer-capabilities-C_kjcYcl.js} +1 -1
- package/web/assets/{wardleyDiagram-EHGQE667-Dal9LkuZ.js → wardleyDiagram-EHGQE667-XwMSgrT5.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-B3dPLPII.js → xychartDiagram-FW5EYKEG-Be1kFxeC.js} +1 -1
- package/web/index.html +1 -1
- package/kernel/packages/folder-plugin/src/index.ts +0 -15
- package/web/assets/app-CX20nNIa.js +0 -2
- package/web/assets/app-options-CkPJE5ep.js +0 -25
- package/web/assets/channel-X7WHyxxT.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-BjsLQ3dz.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-BjsLQ3dz.js +0 -1
- package/web/assets/client-7OWw5kW2.js +0 -6
- package/web/assets/client-DYj_td4Q.js +0 -17
- package/web/assets/folder-image-Bnv43tzP.js +0 -1
- package/web/assets/folder-mobile-bar-VoTL1GS0.js +0 -1
- package/web/assets/folder-plugin-view-Cvd804ZU.js +0 -6
- package/web/assets/popover-0oA2lLUV.js +0 -9
- package/web/assets/select-CtXC_PLC.js +0 -30
- package/web/assets/stateDiagram-v2-6OUMAXLB-0KCFLYeQ.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-CAw3AV84.js +0 -8
- /package/kernel/packages/{folder-plugin → plugins}/eslint.config.js +0 -0
- /package/kernel/packages/{folder-plugin → plugins}/src/client.ts +0 -0
- /package/kernel/packages/{folder-plugin → plugins}/src/endpoint.ts +0 -0
- /package/kernel/packages/{folder-plugin → plugins}/src/server.ts +0 -0
- /package/kernel/packages/{folder-plugin → plugins}/src/subpath.ts +0 -0
- /package/kernel/packages/{folder-plugin → plugins}/src/types.ts +0 -0
- /package/kernel/packages/{folder-plugin → plugins}/src/validation.ts +0 -0
- /package/kernel/packages/{folder-plugin → plugins}/test/contract.test.mjs +0 -0
package/README.md
CHANGED
|
@@ -116,14 +116,17 @@ diagnostics come from the declared schema and Markdown frontmatter. Pass
|
|
|
116
116
|
list. See [Core folders and metadata indexing](../docs/core-folders.md) for
|
|
117
117
|
patterns, parser support, navigation labels and refresh semantics.
|
|
118
118
|
|
|
119
|
-
`kennel view` starts the source Vite viewer from a Kennel checkout when
|
|
119
|
+
`kennel view` starts the source Vite viewer from a Kennel checkout, also when
|
|
120
120
|
external plugins are installed. React and CSS changes use HMR. Changes to
|
|
121
121
|
schema and content files cause a metadata refetch without a full page reload.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
uses the current directory when no folder is given. It
|
|
126
|
-
server that
|
|
122
|
+
The Vite viewer loads the installed external plugins and reports a
|
|
123
|
+
runtime-independent plugin-set key from `/api/health`. `kennel serve` starts
|
|
124
|
+
the packaged static viewer with its own runtime-specific cache key. The hot
|
|
125
|
+
viewer uses the current directory when no folder is given. It reuses a Kennel Vite
|
|
126
|
+
server on the selected port only when that server has the same kernel key. If
|
|
127
|
+
the plugin set changed, stop the running viewer and run the command again. A
|
|
128
|
+
local Vite viewer can open any local folder; the static viewer is reused only
|
|
129
|
+
for its own root or a folder below it.
|
|
127
130
|
|
|
128
131
|
`kennel publish init cloudflare` is the exception for deploy wiring: it scaffolds
|
|
129
132
|
a Cloudflare Worker email-code gate for an existing docs workspace by writing
|
|
@@ -26,6 +26,9 @@ export declare function readExternalPlugins(): ExternalPluginEntry[];
|
|
|
26
26
|
export declare function installExternalPlugin(url: string, options?: {
|
|
27
27
|
directory?: string;
|
|
28
28
|
}): Promise<ExternalPluginEntry>;
|
|
29
|
+
/** Identifies the installed plugin set, independent of the Node runtime. */
|
|
30
|
+
export declare function hotKernelKey(plugins?: ExternalPluginEntry[]): string;
|
|
31
|
+
/** Identifies a runtime-specific static kernel cache entry. */
|
|
29
32
|
export declare function kernelKey(plugins?: ExternalPluginEntry[]): string;
|
|
30
33
|
export type ActiveKernel = {
|
|
31
34
|
buildIdentity: string;
|
|
@@ -163,7 +163,7 @@ function internalSourceAliases(root = kernelSourceRoot()) {
|
|
|
163
163
|
const aliases = {};
|
|
164
164
|
for (const [name, path] of Object.entries({
|
|
165
165
|
"@raggle-ai/agents": "packages/agents/src",
|
|
166
|
-
"
|
|
166
|
+
"kennel-plugins": "packages/plugins/src",
|
|
167
167
|
"@raggle-ai/kennel-api": "packages/api/src",
|
|
168
168
|
"@raggle-ai/markdown": "packages/markdown/src",
|
|
169
169
|
"@raggle-ai/notes": "packages/notes/src",
|
|
@@ -373,21 +373,33 @@ export async function installExternalPlugin(url, options = {}) {
|
|
|
373
373
|
function cliRoot() {
|
|
374
374
|
return resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
375
375
|
}
|
|
376
|
+
function kernelPlugins(plugins) {
|
|
377
|
+
return plugins.map((plugin) => ({
|
|
378
|
+
url: plugin.url,
|
|
379
|
+
requestedRef: plugin.requestedRef ?? null,
|
|
380
|
+
packageName: plugin.packageName,
|
|
381
|
+
id: plugin.id,
|
|
382
|
+
directory: plugin.directory ?? null,
|
|
383
|
+
commit: plugin.commit,
|
|
384
|
+
trigger: plugin.trigger,
|
|
385
|
+
entries: plugin.entries,
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
/** Identifies the installed plugin set, independent of the Node runtime. */
|
|
389
|
+
export function hotKernelKey(plugins = readExternalPlugins()) {
|
|
390
|
+
return hash(JSON.stringify({
|
|
391
|
+
buildIdentity: BUILD_IDENTITY,
|
|
392
|
+
schema: KENNEL_KERNEL_SCHEMA,
|
|
393
|
+
plugins: kernelPlugins(plugins),
|
|
394
|
+
}));
|
|
395
|
+
}
|
|
396
|
+
/** Identifies a runtime-specific static kernel cache entry. */
|
|
376
397
|
export function kernelKey(plugins = readExternalPlugins()) {
|
|
377
398
|
return hash(JSON.stringify({
|
|
378
399
|
buildIdentity: BUILD_IDENTITY,
|
|
379
400
|
schema: KENNEL_KERNEL_SCHEMA,
|
|
380
401
|
runtime: BUILD_RUNTIME,
|
|
381
|
-
plugins: plugins
|
|
382
|
-
url: plugin.url,
|
|
383
|
-
requestedRef: plugin.requestedRef ?? null,
|
|
384
|
-
packageName: plugin.packageName,
|
|
385
|
-
id: plugin.id,
|
|
386
|
-
directory: plugin.directory ?? null,
|
|
387
|
-
commit: plugin.commit,
|
|
388
|
-
trigger: plugin.trigger,
|
|
389
|
-
entries: plugin.entries,
|
|
390
|
-
})),
|
|
402
|
+
plugins: kernelPlugins(plugins),
|
|
391
403
|
}));
|
|
392
404
|
}
|
|
393
405
|
function kernelRoot(key) {
|
|
@@ -448,7 +460,7 @@ export async function prepareExternalPluginModules(temp, target = temp, plugins
|
|
|
448
460
|
const cssImports = plugins.map((plugin) => plugin.entries.css ? `import ${JSON.stringify(plugin.entries.css)};` : "").filter(Boolean).join("\n");
|
|
449
461
|
const clientImports = plugins.map((plugin, index) => `import { ${plugin.entries.manifestSymbol} as manifest${index} } from ${JSON.stringify(plugin.entries.manifest)};`).join("\n");
|
|
450
462
|
const clientItems = plugins.map((plugin, index) => ` { ...manifest${index}, component: lazy(async () => ({ default: (await import(${JSON.stringify(plugin.entries.client)}))[${JSON.stringify(plugin.entries.clientSymbol)}].component })) }`).join(",\n");
|
|
451
|
-
await writeFile(clientModule, `import { lazy } from "react";\nimport type { FolderPluginClient } from "
|
|
463
|
+
await writeFile(clientModule, `import { lazy } from "react";\nimport type { FolderPluginClient } from "kennel-plugins/client";\n${cssImports}\n${clientImports}\nexport const externalFolderPlugins: FolderPluginClient[] = [\n${clientItems}\n];\n`);
|
|
452
464
|
const serverEntries = [];
|
|
453
465
|
const apiEntries = [];
|
|
454
466
|
for (const plugin of plugins) {
|
|
@@ -7,12 +7,15 @@ import { tmpdir } from 'node:os';
|
|
|
7
7
|
import { dirname, relative, resolve } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
9
|
import { createKennelClient } from '../core/api-client.js';
|
|
10
|
+
import { hotKernelKey, readExternalPlugins } from '../plugins/external.js';
|
|
10
11
|
function sourceRoot() {
|
|
11
12
|
return resolve(dirname(fileURLToPath(import.meta.url)), '../../..');
|
|
12
13
|
}
|
|
13
14
|
async function viewerStatus(url) {
|
|
14
15
|
return createKennelClient({ baseUrl: url }).health({ signal: AbortSignal.timeout(1_000) })
|
|
15
|
-
.then((body) => body.viewer === 'kennel-vite' || body.viewer === 'kennel-core'
|
|
16
|
+
.then((body) => body.viewer === 'kennel-vite' || body.viewer === 'kennel-core'
|
|
17
|
+
? { viewer: body.viewer, kernelKey: body.kernelKey }
|
|
18
|
+
: 'other')
|
|
16
19
|
.catch((error) => {
|
|
17
20
|
// HTTP rejection means a different service owns the port; connection and
|
|
18
21
|
// timeout failures mean the viewer is unavailable, so startup may retry.
|
|
@@ -20,17 +23,17 @@ async function viewerStatus(url) {
|
|
|
20
23
|
? 'other' : 'unavailable';
|
|
21
24
|
});
|
|
22
25
|
}
|
|
23
|
-
async function
|
|
26
|
+
async function runningViewer(url, attempts = 1, waitOnOther = false) {
|
|
24
27
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
25
28
|
const status = await viewerStatus(url);
|
|
26
|
-
if (status === '
|
|
27
|
-
return
|
|
29
|
+
if (typeof status === 'object')
|
|
30
|
+
return status;
|
|
28
31
|
if (status === 'other' && !waitOnOther)
|
|
29
|
-
return
|
|
32
|
+
return undefined;
|
|
30
33
|
if (attempt + 1 < attempts)
|
|
31
34
|
await new Promise((done) => setTimeout(done, 250));
|
|
32
35
|
}
|
|
33
|
-
return
|
|
36
|
+
return undefined;
|
|
34
37
|
}
|
|
35
38
|
async function portOpen(host, port) {
|
|
36
39
|
return new Promise((done) => {
|
|
@@ -132,8 +135,20 @@ export async function hotViewer(options) {
|
|
|
132
135
|
console.log(`bun ${[...args, ...(options.open ? ['--open', openPath] : [])].join(' ')}`);
|
|
133
136
|
return;
|
|
134
137
|
}
|
|
135
|
-
const
|
|
136
|
-
|
|
138
|
+
const plugins = readExternalPlugins();
|
|
139
|
+
// Must match the key apps/web/scripts/dev.mjs gives Vite as KENNEL_KERNEL_KEY.
|
|
140
|
+
const expectedKey = plugins.length > 0 ? hotKernelKey(plugins) : 'core';
|
|
141
|
+
// Reuse only a Vite viewer that serves the same plugin set. Anything else on
|
|
142
|
+
// the port is stale for this kernel and must be restarted, never reused.
|
|
143
|
+
const reuse = (running) => {
|
|
144
|
+
const restart = 'Stop that viewer (Ctrl+C in its terminal) and run this command again, or select another port.';
|
|
145
|
+
if (running.viewer !== 'kennel-vite') {
|
|
146
|
+
throw new Error(`Port ${options.port} runs the static Kennel viewer, not the hot viewer. ${restart}`);
|
|
147
|
+
}
|
|
148
|
+
if (running.kernelKey !== expectedKey) {
|
|
149
|
+
throw new Error(`The Kennel hot viewer on port ${options.port} uses kernel ${running.kernelKey}, `
|
|
150
|
+
+ `but the installed plugins need kernel ${expectedKey}. ${restart}`);
|
|
151
|
+
}
|
|
137
152
|
if (options.open) {
|
|
138
153
|
// Do not stack another tab: the viewer is already open. Print the URL
|
|
139
154
|
// so the caller can still get to it deliberately.
|
|
@@ -142,21 +157,19 @@ export async function hotViewer(options) {
|
|
|
142
157
|
else {
|
|
143
158
|
console.log('Kennel hot viewer is already running. Reusing it.');
|
|
144
159
|
}
|
|
160
|
+
};
|
|
161
|
+
const status = await viewerStatus(url);
|
|
162
|
+
if (typeof status === 'object') {
|
|
163
|
+
reuse(status);
|
|
145
164
|
return;
|
|
146
165
|
}
|
|
147
166
|
if (status === 'other') {
|
|
148
167
|
throw new Error(`Port ${options.port} is used by another service. Stop it or select another port.`);
|
|
149
168
|
}
|
|
150
169
|
if (await portOpen(options.host, options.port)) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// so the caller can still get to it deliberately.
|
|
155
|
-
console.log(`Kennel hot viewer is already running at ${viewerUrl}`);
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
console.log('Kennel hot viewer is already running. Reusing it.');
|
|
159
|
-
}
|
|
170
|
+
const running = await runningViewer(url, 20, true);
|
|
171
|
+
if (running) {
|
|
172
|
+
reuse(running);
|
|
160
173
|
return;
|
|
161
174
|
}
|
|
162
175
|
throw new Error(`Port ${options.port} is used by another service. Stop it or select another port.`);
|
|
@@ -164,15 +177,9 @@ export async function hotViewer(options) {
|
|
|
164
177
|
const lock = startupLock(options.host, options.port);
|
|
165
178
|
let token = await acquireLock(lock);
|
|
166
179
|
if (!token) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
// so the caller can still get to it deliberately.
|
|
171
|
-
console.log(`Kennel hot viewer is already running at ${viewerUrl}`);
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
console.log('Kennel hot viewer is already running. Reusing it.');
|
|
175
|
-
}
|
|
180
|
+
const running = await runningViewer(url, 40, true);
|
|
181
|
+
if (running) {
|
|
182
|
+
reuse(running);
|
|
176
183
|
return;
|
|
177
184
|
}
|
|
178
185
|
await rm(lock, { force: true, recursive: true });
|
|
@@ -180,16 +187,10 @@ export async function hotViewer(options) {
|
|
|
180
187
|
}
|
|
181
188
|
if (!token)
|
|
182
189
|
throw new Error(`Could not acquire the hot viewer startup lock for port ${options.port}.`);
|
|
183
|
-
|
|
190
|
+
const running = await runningViewer(url);
|
|
191
|
+
if (running) {
|
|
184
192
|
await releaseLock(lock, token);
|
|
185
|
-
|
|
186
|
-
// Do not stack another tab: the viewer is already open. Print the URL
|
|
187
|
-
// so the caller can still get to it deliberately.
|
|
188
|
-
console.log(`Kennel hot viewer is already running at ${viewerUrl}`);
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
console.log('Kennel hot viewer is already running. Reusing it.');
|
|
192
|
-
}
|
|
193
|
+
reuse(running);
|
|
193
194
|
return;
|
|
194
195
|
}
|
|
195
196
|
console.log(`bun ${args.join(' ')}`);
|
|
@@ -220,9 +221,15 @@ export async function hotViewer(options) {
|
|
|
220
221
|
done();
|
|
221
222
|
return;
|
|
222
223
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
const existing = await runningViewer(url, 20, true);
|
|
225
|
+
if (existing) {
|
|
226
|
+
try {
|
|
227
|
+
reuse(existing);
|
|
228
|
+
done();
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
fail(error);
|
|
232
|
+
}
|
|
226
233
|
return;
|
|
227
234
|
}
|
|
228
235
|
fail(new Error(`Kennel hot viewer stopped with exit code ${code ?? 'unknown'}`));
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* plain-`node` CLI never imports the plugin packages' runtime sources, while
|
|
9
9
|
* staying type-checked against the one source of truth.
|
|
10
10
|
*/
|
|
11
|
-
import type { FolderPluginServer } from "
|
|
12
|
-
export type { FolderPluginContext, FolderPluginData, FolderPluginDetection, FolderPluginServer, } from "
|
|
11
|
+
import type { FolderPluginServer } from "kennel-plugins/server";
|
|
12
|
+
export type { FolderPluginContext, FolderPluginData, FolderPluginDetection, FolderPluginServer, } from "kennel-plugins/server";
|
|
13
13
|
/**
|
|
14
14
|
* Signal a server-side plugin failure (like a mail preview read error) so the
|
|
15
15
|
* host maps it to an HTTP 5xx instead of a not-found. All other thrown errors
|
|
@@ -259,17 +259,15 @@ function tailscaleHost() {
|
|
|
259
259
|
export async function viewer(input) {
|
|
260
260
|
const portExplicit = viewerPortWasExplicit(input);
|
|
261
261
|
const options = splitViewerArgs(input);
|
|
262
|
-
|
|
263
|
-
const externalPlugins = readExternalPlugins();
|
|
264
|
-
if (options.hot && externalPlugins.length === 0) {
|
|
262
|
+
if (options.hot) {
|
|
265
263
|
const { hotViewer } = await import("./hot-viewer.js");
|
|
266
264
|
await hotViewer(options);
|
|
267
265
|
return;
|
|
268
266
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
267
|
+
const { ensureKernel, hotKernelKey, readExternalPlugins } = await import("../plugins/external.js");
|
|
268
|
+
const externalPlugins = readExternalPlugins();
|
|
272
269
|
const kernel = await ensureKernel(externalPlugins);
|
|
270
|
+
const viteKernelKey = externalPlugins.length > 0 ? hotKernelKey(externalPlugins) : "core";
|
|
273
271
|
const web = kernel.webRoot;
|
|
274
272
|
if (!existsSync(resolve(web, "index.html"))) {
|
|
275
273
|
throw new Error(`Kennel web app not found. Run the CLI build first.`);
|
|
@@ -298,9 +296,16 @@ export async function viewer(input) {
|
|
|
298
296
|
.join(",");
|
|
299
297
|
const initialViewerUrl = `http://${options.host}:${options.port}`;
|
|
300
298
|
const existing = await runningViewer(initialViewerUrl);
|
|
299
|
+
// A local Vite viewer is not root-restricted, so it can open any local
|
|
300
|
+
// folder. The static kennel-core viewer is root-scoped: reuse it only for
|
|
301
|
+
// its own root or a descendant.
|
|
301
302
|
if (existing &&
|
|
302
|
-
existing.
|
|
303
|
-
|
|
303
|
+
((existing.viewer === "kennel-vite" &&
|
|
304
|
+
!remote &&
|
|
305
|
+
existing.kernelKey === viteKernelKey) ||
|
|
306
|
+
(existing.viewer !== "kennel-vite" &&
|
|
307
|
+
existing.kernelKey === kernel.key &&
|
|
308
|
+
isSameOrDescendant(existing.root, options.root)))) {
|
|
304
309
|
const viewerUrl = popUrl(displayHost, options.port, options);
|
|
305
310
|
console.log(viewerUrl);
|
|
306
311
|
console.log(`Kennel is already running at ${viewerUrl}`);
|