Tighten plugin dev file watching

This commit is contained in:
Dotta
2026-03-14 12:07:04 -05:00
parent 22b8e90ba6
commit 0605c9f229
5 changed files with 464 additions and 76 deletions

352
pnpm-lock.yaml generated
View File

@@ -246,6 +246,180 @@ importers:
specifier: ^3.0.5
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
packages/plugins/create-paperclip-plugin:
dependencies:
'@paperclipai/plugin-sdk':
specifier: workspace:*
version: link:../sdk
devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.12.0
typescript:
specifier: ^5.7.3
version: 5.9.3
packages/plugins/examples/plugin-authoring-smoke-example:
dependencies:
'@paperclipai/plugin-sdk':
specifier: workspace:*
version: link:../../sdk
react:
specifier: '>=18'
version: 19.2.4
devDependencies:
'@rollup/plugin-node-resolve':
specifier: ^16.0.1
version: 16.0.3(rollup@4.57.1)
'@rollup/plugin-typescript':
specifier: ^12.1.2
version: 12.3.0(rollup@4.57.1)(tslib@2.8.1)(typescript@5.9.3)
'@types/node':
specifier: ^24.6.0
version: 24.12.0
'@types/react':
specifier: ^19.0.8
version: 19.2.14
esbuild:
specifier: ^0.27.3
version: 0.27.3
rollup:
specifier: ^4.38.0
version: 4.57.1
tslib:
specifier: ^2.8.1
version: 2.8.1
typescript:
specifier: ^5.7.3
version: 5.9.3
vitest:
specifier: ^3.0.5
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)
packages/plugins/examples/plugin-file-browser-example:
dependencies:
'@codemirror/lang-javascript':
specifier: ^6.2.2
version: 6.2.4
'@codemirror/language':
specifier: ^6.11.0
version: 6.12.1
'@codemirror/state':
specifier: ^6.4.0
version: 6.5.4
'@codemirror/view':
specifier: ^6.28.0
version: 6.39.15
'@lezer/highlight':
specifier: ^1.2.1
version: 1.2.3
'@paperclipai/plugin-sdk':
specifier: workspace:*
version: link:../../sdk
codemirror:
specifier: ^6.0.1
version: 6.0.2
devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.12.0
'@types/react':
specifier: ^19.0.8
version: 19.2.14
'@types/react-dom':
specifier: ^19.0.3
version: 19.2.3(@types/react@19.2.14)
esbuild:
specifier: ^0.27.3
version: 0.27.3
react:
specifier: ^19.0.0
version: 19.2.4
react-dom:
specifier: ^19.0.0
version: 19.2.4(react@19.2.4)
typescript:
specifier: ^5.7.3
version: 5.9.3
packages/plugins/examples/plugin-hello-world-example:
dependencies:
'@paperclipai/plugin-sdk':
specifier: workspace:*
version: link:../../sdk
devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.12.0
'@types/react':
specifier: ^19.0.8
version: 19.2.14
'@types/react-dom':
specifier: ^19.0.3
version: 19.2.3(@types/react@19.2.14)
react:
specifier: ^19.0.0
version: 19.2.4
react-dom:
specifier: ^19.0.0
version: 19.2.4(react@19.2.4)
typescript:
specifier: ^5.7.3
version: 5.9.3
packages/plugins/examples/plugin-kitchen-sink-example:
dependencies:
'@paperclipai/plugin-sdk':
specifier: workspace:*
version: link:../../sdk
'@paperclipai/shared':
specifier: workspace:*
version: link:../../../shared
devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.12.0
'@types/react':
specifier: ^19.0.8
version: 19.2.14
'@types/react-dom':
specifier: ^19.0.3
version: 19.2.3(@types/react@19.2.14)
esbuild:
specifier: ^0.27.3
version: 0.27.3
react:
specifier: ^19.0.0
version: 19.2.4
react-dom:
specifier: ^19.0.0
version: 19.2.4(react@19.2.4)
typescript:
specifier: ^5.7.3
version: 5.9.3
packages/plugins/sdk:
dependencies:
'@paperclipai/shared':
specifier: workspace:*
version: link:../../shared
react:
specifier: '>=18'
version: 19.2.4
zod:
specifier: ^3.24.2
version: 3.25.76
devDependencies:
'@types/node':
specifier: ^24.6.0
version: 24.12.0
'@types/react':
specifier: ^19.0.8
version: 19.2.14
typescript:
specifier: ^5.7.3
version: 5.9.3
packages/shared:
dependencies:
zod:
@@ -288,12 +462,24 @@ importers:
'@paperclipai/db':
specifier: workspace:*
version: link:../packages/db
'@paperclipai/plugin-sdk':
specifier: workspace:*
version: link:../packages/plugins/sdk
'@paperclipai/shared':
specifier: workspace:*
version: link:../packages/shared
ajv:
specifier: ^8.18.0
version: 8.18.0
ajv-formats:
specifier: ^3.0.1
version: 3.0.1(ajv@8.18.0)
better-auth:
specifier: 1.4.18
version: 1.4.18(drizzle-kit@0.31.9)(drizzle-orm@0.38.4(@electric-sql/pglite@0.3.15)(@types/react@19.2.14)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8)(react@19.2.4))(pg@8.18.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0))
chokidar:
specifier: ^4.0.3
version: 4.0.3
detect-port:
specifier: ^2.1.0
version: 2.1.0
@@ -2436,6 +2622,37 @@ packages:
'@rolldown/pluginutils@1.0.0-beta.27':
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
'@rollup/plugin-node-resolve@16.0.3':
resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.78.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/plugin-typescript@12.3.0':
resolution: {integrity: sha512-7DP0/p7y3t67+NabT9f8oTBFE6gGkto4SA6Np2oudYmZE/m1dt8RB0SjL1msMxFpLo631qjRCcBlAbq1ml/Big==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.14.0||^3.0.0||^4.0.0
tslib: '*'
typescript: '>=3.7.0'
peerDependenciesMeta:
rollup:
optional: true
tslib:
optional: true
'@rollup/pluginutils@5.3.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
'@rollup/rollup-android-arm-eabi@4.57.1':
resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==}
cpu: [arm]
@@ -3068,6 +3285,9 @@ packages:
'@types/react@19.2.14':
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
'@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
'@types/send@1.2.1':
resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==}
@@ -3148,6 +3368,17 @@ packages:
resolution: {integrity: sha512-XNAb/a6TCqou+TufU8/u11HCu9x1gYvOoxLwtlXgIqmkrYQADVv6ljyW2zwiPhHz9R1gItAWpuDrdJMmrOBFEA==}
engines: {node: '>= 16.0.0'}
ajv-formats@3.0.1:
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
peerDependencies:
ajv: ^8.0.0
peerDependenciesMeta:
ajv:
optional: true
ajv@8.18.0:
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
anser@2.3.5:
resolution: {integrity: sha512-vcZjxvvVoxTeR5XBNJB38oTu/7eDCZlwdz32N1eNgpyPF7j/Z7Idf+CUwQOkKKpJ7RJyjxgLHCM7vdIK0iCNMQ==}
@@ -3361,6 +3592,10 @@ packages:
chevrotain@11.1.2:
resolution: {integrity: sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==}
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@@ -3660,6 +3895,10 @@ packages:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
default-browser-id@5.0.1:
resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
engines: {node: '>=18'}
@@ -3941,6 +4180,9 @@ packages:
estree-util-visit@2.0.0:
resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==}
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
@@ -3971,6 +4213,9 @@ packages:
fast-copy@4.0.2:
resolution: {integrity: sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
@@ -3978,6 +4223,9 @@ packages:
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
fast-xml-parser@5.3.6:
resolution: {integrity: sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==}
hasBin: true
@@ -4165,6 +4413,10 @@ packages:
is-alphanumerical@2.0.1:
resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==}
is-core-module@2.16.1:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
is-decimal@2.0.1:
resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==}
@@ -4193,6 +4445,9 @@ packages:
engines: {node: '>=14.16'}
hasBin: true
is-module@1.0.0:
resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==}
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
@@ -4252,6 +4507,9 @@ packages:
engines: {node: '>=6'}
hasBin: true
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -4742,6 +5000,9 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-to-regexp@8.3.0:
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
@@ -5030,6 +5291,10 @@ packages:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
@@ -5046,6 +5311,10 @@ packages:
remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
@@ -5053,6 +5322,11 @@ packages:
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
resolve@1.22.11:
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
engines: {node: '>= 0.4'}
hasBin: true
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -5257,6 +5531,10 @@ packages:
resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==}
engines: {node: '>=14.18.0'}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
tabbable@6.4.0:
resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==}
@@ -8212,6 +8490,33 @@ snapshots:
'@rolldown/pluginutils@1.0.0-beta.27': {}
'@rollup/plugin-node-resolve@16.0.3(rollup@4.57.1)':
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.57.1)
'@types/resolve': 1.20.2
deepmerge: 4.3.1
is-module: 1.0.0
resolve: 1.22.11
optionalDependencies:
rollup: 4.57.1
'@rollup/plugin-typescript@12.3.0(rollup@4.57.1)(tslib@2.8.1)(typescript@5.9.3)':
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.57.1)
resolve: 1.22.11
typescript: 5.9.3
optionalDependencies:
rollup: 4.57.1
tslib: 2.8.1
'@rollup/pluginutils@5.3.0(rollup@4.57.1)':
dependencies:
'@types/estree': 1.0.8
estree-walker: 2.0.2
picomatch: 4.0.3
optionalDependencies:
rollup: 4.57.1
'@rollup/rollup-android-arm-eabi@4.57.1':
optional: true
@@ -8934,6 +9239,8 @@ snapshots:
dependencies:
csstype: 3.2.3
'@types/resolve@1.20.2': {}
'@types/send@1.2.1':
dependencies:
'@types/node': 25.2.3
@@ -9043,6 +9350,17 @@ snapshots:
address@2.0.3: {}
ajv-formats@3.0.1(ajv@8.18.0):
optionalDependencies:
ajv: 8.18.0
ajv@8.18.0:
dependencies:
fast-deep-equal: 3.1.3
fast-uri: 3.1.0
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
anser@2.3.5: {}
ansi-colors@4.1.3: {}
@@ -9209,6 +9527,10 @@ snapshots:
'@chevrotain/utils': 11.1.2
lodash-es: 4.17.23
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
@@ -9517,6 +9839,8 @@ snapshots:
deep-eql@5.0.2: {}
deepmerge@4.3.1: {}
default-browser-id@5.0.1: {}
default-browser@5.5.0:
@@ -9789,6 +10113,8 @@ snapshots:
'@types/estree-jsx': 1.0.5
'@types/unist': 3.0.3
estree-walker@2.0.2: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
@@ -9845,6 +10171,8 @@ snapshots:
fast-copy@4.0.2: {}
fast-deep-equal@3.1.3: {}
fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -9855,6 +10183,8 @@ snapshots:
fast-safe-stringify@2.1.1: {}
fast-uri@3.1.0: {}
fast-xml-parser@5.3.6:
dependencies:
strnum: 2.1.2
@@ -10057,6 +10387,10 @@ snapshots:
is-alphabetical: 2.0.1
is-decimal: 2.0.1
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
is-decimal@2.0.1: {}
is-docker@3.0.0: {}
@@ -10075,6 +10409,8 @@ snapshots:
dependencies:
is-docker: 3.0.0
is-module@1.0.0: {}
is-number@7.0.0: {}
is-plain-obj@4.1.0: {}
@@ -10116,6 +10452,8 @@ snapshots:
jsesc@3.1.0: {}
json-schema-traverse@1.0.0: {}
json5@2.2.3: {}
jsonfile@4.0.0:
@@ -10873,6 +11211,8 @@ snapshots:
path-key@3.1.1: {}
path-parse@1.0.7: {}
path-to-regexp@8.3.0: {}
path-type@4.0.0: {}
@@ -11221,6 +11561,8 @@ snapshots:
string_decoder: 1.3.0
util-deprecate: 1.0.2
readdirp@4.1.2: {}
real-require@0.2.0: {}
remark-gfm@4.0.1:
@@ -11257,10 +11599,18 @@ snapshots:
mdast-util-to-markdown: 2.1.2
unified: 11.0.5
require-from-string@2.0.2: {}
resolve-from@5.0.0: {}
resolve-pkg-maps@1.0.0: {}
resolve@1.22.11:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
reusify@1.1.0: {}
robust-predicates@3.0.2: {}
@@ -11510,6 +11860,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
supports-preserve-symlinks-flag@1.0.0: {}
tabbable@6.4.0: {}
tailwind-merge@3.4.1: {}

View File

@@ -48,6 +48,7 @@
"ajv": "^8.18.0",
"ajv-formats": "^3.0.1",
"better-auth": "1.4.18",
"chokidar": "^4.0.3",
"detect-port": "^2.1.0",
"dotenv": "^17.0.1",
"drizzle-orm": "^0.38.4",

View File

@@ -20,7 +20,7 @@ function makeTempPluginDir(): string {
}
describe("resolvePluginWatchTargets", () => {
it("watches the package root plus declared build output directories", () => {
it("watches package metadata plus concrete declared runtime files", () => {
const pluginDir = makeTempPluginDir();
mkdirSync(path.join(pluginDir, "dist", "ui"), { recursive: true });
writeFileSync(
@@ -37,26 +37,32 @@ describe("resolvePluginWatchTargets", () => {
writeFileSync(path.join(pluginDir, "dist", "manifest.js"), "export default {};\n");
writeFileSync(path.join(pluginDir, "dist", "worker.js"), "export default {};\n");
writeFileSync(path.join(pluginDir, "dist", "ui", "index.js"), "export default {};\n");
writeFileSync(path.join(pluginDir, "dist", "ui", "index.css"), "body {}\n");
const targets = resolvePluginWatchTargets(pluginDir);
expect(targets).toEqual([
{ path: pluginDir, recursive: false },
{ path: path.join(pluginDir, "dist"), recursive: true },
{ path: path.join(pluginDir, "dist", "ui"), recursive: true },
{ path: path.join(pluginDir, "dist", "manifest.js"), recursive: false, kind: "file" },
{ path: path.join(pluginDir, "dist", "ui", "index.css"), recursive: false, kind: "file" },
{ path: path.join(pluginDir, "dist", "ui", "index.js"), recursive: false, kind: "file" },
{ path: path.join(pluginDir, "dist", "worker.js"), recursive: false, kind: "file" },
{ path: path.join(pluginDir, "package.json"), recursive: false, kind: "file" },
]);
});
it("falls back to dist when package metadata does not declare entrypoints", () => {
const pluginDir = makeTempPluginDir();
mkdirSync(path.join(pluginDir, "dist"), { recursive: true });
mkdirSync(path.join(pluginDir, "dist", "nested"), { recursive: true });
writeFileSync(path.join(pluginDir, "package.json"), JSON.stringify({ name: "@acme/example" }));
writeFileSync(path.join(pluginDir, "dist", "manifest.js"), "export default {};\n");
writeFileSync(path.join(pluginDir, "dist", "nested", "chunk.js"), "export default {};\n");
const targets = resolvePluginWatchTargets(pluginDir);
expect(targets).toEqual([
{ path: pluginDir, recursive: false },
{ path: path.join(pluginDir, "dist"), recursive: true },
{ path: path.join(pluginDir, "package.json"), recursive: false, kind: "file" },
{ path: path.join(pluginDir, "dist", "manifest.js"), recursive: false, kind: "file" },
{ path: path.join(pluginDir, "dist", "nested", "chunk.js"), recursive: false, kind: "file" },
]);
});
});

View File

@@ -272,14 +272,16 @@ export async function createApp(
void toolDispatcher.initialize().catch((err) => {
logger.error({ err }, "Failed to initialize plugin tool dispatcher");
});
const devWatcher = createPluginDevWatcher(
lifecycle,
async (pluginId) => (await pluginRegistry.getById(pluginId))?.packagePath ?? null,
);
const devWatcher = opts.uiMode === "vite-dev"
? createPluginDevWatcher(
lifecycle,
async (pluginId) => (await pluginRegistry.getById(pluginId))?.packagePath ?? null,
)
: null;
void loader.loadAll().then((result) => {
if (!result) return;
for (const loaded of result.results) {
if (loaded.success && loaded.plugin.packagePath) {
if (devWatcher && loaded.success && loaded.plugin.packagePath) {
devWatcher.watch(loaded.plugin.id, loaded.plugin.packagePath);
}
}
@@ -287,7 +289,7 @@ export async function createApp(
logger.error({ err }, "Failed to load ready plugins on startup");
});
process.once("exit", () => {
devWatcher.close();
devWatcher?.close();
hostServiceCleanup.disposeAll();
hostServiceCleanup.teardown();
});

View File

@@ -7,10 +7,14 @@
* `packagePath` in the DB) are watched. File changes in the plugin's package
* directory trigger a debounced worker restart via the lifecycle manager.
*
* Uses chokidar rather than raw fs.watch so we get a production-grade watcher
* backend across platforms and avoid exhausting file descriptors as quickly in
* large dev workspaces.
*
* @see PLUGIN_SPEC.md §27.2 — Local Development Workflow
*/
import { watch, type FSWatcher } from "node:fs";
import { existsSync, readFileSync, statSync } from "node:fs";
import chokidar, { type FSWatcher } from "chokidar";
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import path from "node:path";
import { logger } from "../middleware/logger.js";
import type { PluginLifecycleManager } from "./plugin-lifecycle.js";
@@ -35,14 +39,15 @@ export type ResolvePluginPackagePath = (
export interface PluginDevWatcherFsDeps {
existsSync?: typeof existsSync;
watch?: typeof watch;
readFileSync?: typeof readFileSync;
readdirSync?: typeof readdirSync;
statSync?: typeof statSync;
}
type PluginWatchTarget = {
path: string;
recursive: boolean;
kind: "file" | "dir";
};
type PluginPackageJson = {
@@ -69,17 +74,19 @@ function shouldIgnorePath(filename: string | null | undefined): boolean {
export function resolvePluginWatchTargets(
packagePath: string,
fsDeps?: Pick<PluginDevWatcherFsDeps, "existsSync" | "readFileSync" | "statSync">,
fsDeps?: Pick<PluginDevWatcherFsDeps, "existsSync" | "readFileSync" | "readdirSync" | "statSync">,
): PluginWatchTarget[] {
const fileExists = fsDeps?.existsSync ?? existsSync;
const readFile = fsDeps?.readFileSync ?? readFileSync;
const readDir = fsDeps?.readdirSync ?? readdirSync;
const statFile = fsDeps?.statSync ?? statSync;
const absPath = path.resolve(packagePath);
const targets = new Map<string, PluginWatchTarget>();
function addWatchTarget(targetPath: string, recursive: boolean): void {
function addWatchTarget(targetPath: string, recursive: boolean, kind?: "file" | "dir"): void {
const resolved = path.resolve(targetPath);
if (!fileExists(resolved)) return;
const inferredKind = kind ?? (statFile(resolved).isDirectory() ? "dir" : "file");
const existing = targets.get(resolved);
if (existing) {
@@ -87,14 +94,27 @@ export function resolvePluginWatchTargets(
return;
}
targets.set(resolved, { path: resolved, recursive });
targets.set(resolved, { path: resolved, recursive, kind: inferredKind });
}
// Watch the package root non-recursively so top-level files like package.json
// can trigger reloads without traversing node_modules or other deep trees.
addWatchTarget(absPath, false);
function addRuntimeFilesFromDir(dirPath: string): void {
if (!fileExists(dirPath)) return;
for (const entry of readDir(dirPath, { withFileTypes: true })) {
const entryPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
addRuntimeFilesFromDir(entryPath);
continue;
}
if (!entry.isFile()) continue;
if (!entry.name.endsWith(".js") && !entry.name.endsWith(".css")) continue;
addWatchTarget(entryPath, false, "file");
}
}
const packageJsonPath = path.join(absPath, "package.json");
addWatchTarget(packageJsonPath, false, "file");
if (!fileExists(packageJsonPath)) {
return [...targets.values()];
}
@@ -113,7 +133,7 @@ export function resolvePluginWatchTargets(
].filter((value): value is string => typeof value === "string" && value.length > 0);
if (entrypointPaths.length === 0) {
addWatchTarget(path.join(absPath, "dist"), true);
addRuntimeFilesFromDir(path.join(absPath, "dist"));
return [...targets.values()];
}
@@ -123,13 +143,13 @@ export function resolvePluginWatchTargets(
const stat = statFile(resolvedEntrypoint);
if (stat.isDirectory()) {
addWatchTarget(resolvedEntrypoint, true);
addRuntimeFilesFromDir(resolvedEntrypoint);
} else {
addWatchTarget(path.dirname(resolvedEntrypoint), true);
addWatchTarget(resolvedEntrypoint, false, "file");
}
}
return [...targets.values()];
return [...targets.values()].sort((a, b) => a.path.localeCompare(b.path));
}
/**
@@ -141,10 +161,9 @@ export function createPluginDevWatcher(
resolvePluginPackagePath?: ResolvePluginPackagePath,
fsDeps?: PluginDevWatcherFsDeps,
): PluginDevWatcher {
const watchers = new Map<string, FSWatcher[]>();
const watchers = new Map<string, FSWatcher>();
const debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
const fileExists = fsDeps?.existsSync ?? existsSync;
const watchFs = fsDeps?.watch ?? watch;
function watchPlugin(pluginId: string, packagePath: string): void {
// Don't double-watch
@@ -169,60 +188,70 @@ export function createPluginDevWatcher(
return;
}
const activeWatchers = watcherTargets.map((target) => {
const watcher = watchFs(target.path, { recursive: target.recursive }, (_event, filename) => {
if (shouldIgnorePath(filename)) return;
const watcher = chokidar.watch(
watcherTargets.map((target) => target.path),
{
ignoreInitial: true,
awaitWriteFinish: {
stabilityThreshold: 200,
pollInterval: 100,
},
ignored: (watchedPath) => {
const relativePath = path.relative(absPath, watchedPath);
return shouldIgnorePath(relativePath);
},
},
);
// Debounce: multiple rapid file changes collapse into one restart
const existing = debounceTimers.get(pluginId);
if (existing) clearTimeout(existing);
watcher.on("all", (_eventName, changedPath) => {
const relativePath = path.relative(absPath, changedPath);
if (shouldIgnorePath(relativePath)) return;
debounceTimers.set(
pluginId,
setTimeout(() => {
debounceTimers.delete(pluginId);
log.info(
{ pluginId, changedFile: filename, watchTarget: target.path },
"plugin-dev-watcher: file change detected, restarting worker",
const existing = debounceTimers.get(pluginId);
if (existing) clearTimeout(existing);
debounceTimers.set(
pluginId,
setTimeout(() => {
debounceTimers.delete(pluginId);
log.info(
{ pluginId, changedFile: relativePath || path.basename(changedPath) },
"plugin-dev-watcher: file change detected, restarting worker",
);
lifecycle.restartWorker(pluginId).catch((err) => {
log.warn(
{
pluginId,
err: err instanceof Error ? err.message : String(err),
},
"plugin-dev-watcher: failed to restart worker after file change",
);
lifecycle.restartWorker(pluginId).catch((err) => {
log.warn(
{
pluginId,
err: err instanceof Error ? err.message : String(err),
},
"plugin-dev-watcher: failed to restart worker after file change",
);
});
}, DEBOUNCE_MS),
);
});
watcher.on("error", (err) => {
log.warn(
{
pluginId,
packagePath: absPath,
watchTarget: target.path,
err: err instanceof Error ? err.message : String(err),
},
"plugin-dev-watcher: watcher error, stopping watch for this plugin",
);
unwatchPlugin(pluginId);
});
return watcher;
});
}, DEBOUNCE_MS),
);
});
watchers.set(pluginId, activeWatchers);
watcher.on("error", (err) => {
log.warn(
{
pluginId,
packagePath: absPath,
err: err instanceof Error ? err.message : String(err),
},
"plugin-dev-watcher: watcher error, stopping watch for this plugin",
);
unwatchPlugin(pluginId);
});
watchers.set(pluginId, watcher);
log.info(
{
pluginId,
packagePath: absPath,
watchTargets: watcherTargets.map((target) => ({
path: target.path,
recursive: target.recursive,
kind: target.kind,
})),
},
"plugin-dev-watcher: watching local plugin for changes",
@@ -240,11 +269,9 @@ export function createPluginDevWatcher(
}
function unwatchPlugin(pluginId: string): void {
const pluginWatchers = watchers.get(pluginId);
if (pluginWatchers) {
for (const watcher of pluginWatchers) {
watcher.close();
}
const pluginWatcher = watchers.get(pluginId);
if (pluginWatcher) {
void pluginWatcher.close();
watchers.delete(pluginId);
}
const timer = debounceTimers.get(pluginId);