@libria/plugin-loader 0.1.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.
@@ -0,0 +1 @@
1
+ 2c5f1c5099692fedf676cdc07ff278087d4924891f85b79c5b08647b51e7ed06
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CinemaCove
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # @libria/plugin-loader
2
+
3
+ A simple, type-safe plugin loader for Node.js applications. Supports both ESM and CommonJS plugins with glob pattern discovery.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @libria/plugin-loader
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ### 1. Create a Plugin
14
+
15
+ Create a `plugin.json` manifest in your plugin directory:
16
+
17
+ ```json
18
+ {
19
+ "name": "my-plugin",
20
+ "pluginType": "greeting",
21
+ "module": "./dist/index.mjs"
22
+ }
23
+ ```
24
+
25
+ Create the plugin module:
26
+
27
+ ```typescript
28
+ // src/index.ts
29
+ import { definePlugin } from '@libria/plugin-loader';
30
+
31
+ export default definePlugin('greeting', {
32
+ sayHello(name: string) {
33
+ return `Hello, ${name}!`;
34
+ }
35
+ });
36
+ ```
37
+
38
+ ### 2. Load Plugins
39
+
40
+ ```typescript
41
+ import { findPlugins, loadPlugin, loadAllPlugins } from '@libria/plugin-loader';
42
+
43
+ // Load all plugins from a directory
44
+ const plugins = await loadAllPlugins('./plugins', 'greeting');
45
+
46
+ for (const plugin of plugins) {
47
+ console.log(plugin.api.sayHello('World'));
48
+ }
49
+ ```
50
+
51
+ ## API
52
+
53
+ ### `definePlugin<T>(pluginType, api, name?)`
54
+
55
+ Helper function to create a type-safe plugin export.
56
+
57
+ ```typescript
58
+ import { definePlugin } from '@libria/plugin-loader';
59
+
60
+ export default definePlugin('my-type', {
61
+ myMethod() {
62
+ return 'Hello!';
63
+ }
64
+ });
65
+ ```
66
+
67
+ ### `findPlugins(pattern, pluginType?)`
68
+
69
+ Discovers plugins by scanning directories for `plugin.json` manifests.
70
+
71
+ ```typescript
72
+ import { findPlugins } from '@libria/plugin-loader';
73
+
74
+ // Simple directory path (scans one level deep)
75
+ const manifests = await findPlugins('./plugins');
76
+
77
+ // Glob pattern
78
+ const manifests = await findPlugins('./plugins/*-plugin');
79
+
80
+ // Recursive glob
81
+ const manifests = await findPlugins('./plugins/**/dist');
82
+
83
+ // Filter by plugin type
84
+ const manifests = await findPlugins('./plugins', 'greeting');
85
+ ```
86
+
87
+ ### `loadPlugin<T>(manifest)`
88
+
89
+ Loads a single plugin from its manifest. Supports both ESM (`module`) and CommonJS (`main`) entry points.
90
+
91
+ ```typescript
92
+ import { findPlugins, loadPlugin } from '@libria/plugin-loader';
93
+
94
+ const [manifest] = await findPlugins('./plugins/my-plugin');
95
+ const plugin = await loadPlugin<{ sayHello: (name: string) => string }>(manifest);
96
+
97
+ console.log(plugin.api.sayHello('World'));
98
+ ```
99
+
100
+ ### `loadAllPlugins<T>(pattern, pluginType?)`
101
+
102
+ Convenience function that combines `findPlugins` and `loadPlugin`. Discovers and loads all matching plugins.
103
+
104
+ ```typescript
105
+ import { loadAllPlugins } from '@libria/plugin-loader';
106
+
107
+ const plugins = await loadAllPlugins<{ greet: () => string }>(
108
+ './plugins/*-plugin',
109
+ 'greeting'
110
+ );
111
+
112
+ for (const plugin of plugins) {
113
+ console.log(plugin.api.greet());
114
+ }
115
+ ```
116
+
117
+ ## Plugin Manifest
118
+
119
+ The `plugin.json` file defines plugin metadata:
120
+
121
+ | Field | Type | Required | Description |
122
+ |--------------|--------|----------|--------------------------------------|
123
+ | `name` | string | Yes | Unique plugin identifier |
124
+ | `pluginType` | string | Yes | Plugin category/type for filtering |
125
+ | `module` | string | No | ESM entry point (relative path) |
126
+ | `main` | string | No | CommonJS entry point (relative path) |
127
+ | `types` | string | No | TypeScript declaration file |
128
+
129
+ At least one of `module` or `main` must be specified.
130
+
131
+ ### ESM Plugin Example
132
+
133
+ ```
134
+ my-plugin/
135
+ plugin.json
136
+ dist/
137
+ index.mjs
138
+ ```
139
+
140
+ ```json
141
+ {
142
+ "name": "my-plugin",
143
+ "pluginType": "feature",
144
+ "module": "./dist/index.mjs"
145
+ }
146
+ ```
147
+
148
+ ### CommonJS Plugin Example
149
+
150
+ ```
151
+ my-plugin/
152
+ plugin.json
153
+ dist/
154
+ index.cjs
155
+ ```
156
+
157
+ ```json
158
+ {
159
+ "name": "my-plugin",
160
+ "pluginType": "feature",
161
+ "main": "./dist/index.cjs"
162
+ }
163
+ ```
164
+
165
+ ### Nested Manifest (dist folder)
166
+
167
+ You can place `plugin.json` inside the `dist` folder and use glob patterns to discover it:
168
+
169
+ ```
170
+ my-plugin/
171
+ dist/
172
+ plugin.json
173
+ index.mjs
174
+ ```
175
+
176
+ ```typescript
177
+ // Discover plugins with manifest in dist/
178
+ const plugins = await loadAllPlugins('./plugins/*/dist');
179
+ ```
180
+
181
+ ## Types
182
+
183
+ ### `LibriaPlugin<T>`
184
+
185
+ ```typescript
186
+ interface LibriaPlugin<T = unknown> {
187
+ readonly pluginType: string;
188
+ readonly name?: string;
189
+ readonly api: T;
190
+ }
191
+ ```
192
+
193
+ ### `PluginManifest`
194
+
195
+ ```typescript
196
+ interface PluginManifest {
197
+ readonly name: string;
198
+ readonly pluginType: string;
199
+ readonly main?: string;
200
+ readonly module?: string;
201
+ readonly types?: string;
202
+ readonly __dir: string; // Resolved absolute path
203
+ }
204
+ ```
205
+
206
+ ## Error Handling
207
+
208
+ The library throws specific errors for common issues:
209
+
210
+ - **`PluginLoadError`** - Failed to load the plugin module
211
+ - **`PluginInvalidExportError`** - Plugin export is not a valid object
212
+ - **`PluginTypeMismatchError`** - Plugin type doesn't match manifest
213
+
214
+ ```typescript
215
+ import { loadPlugin, PluginTypeMismatchError } from '@libria/plugin-loader';
216
+
217
+ try {
218
+ const plugin = await loadPlugin(manifest);
219
+ } catch (error) {
220
+ if (error instanceof PluginTypeMismatchError) {
221
+ console.error(`Type mismatch: expected ${error.expected}, got ${error.actual}`);
222
+ }
223
+ }
224
+ ```
225
+
226
+ ## License
227
+
228
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`fs-extra`);c=s(c);let l=require(`fast-glob`);l=s(l);let u=require(`path`);u=s(u);let d=require(`url`),f=require(`node:module`);function p(e,t,n){return{pluginType:e,name:n,api:t}}async function m(e,t){let n=[];if(e.includes(`*`)||e.includes(`{`)||e.includes(`?`)){let r=await(0,l.default)(e.replace(/\\/g,`/`),{onlyDirectories:!0,absolute:!0});for(let e of r){let r=u.default.join(e,`plugin.json`);if(!await c.default.pathExists(r))continue;let i=await c.default.readJson(r);t&&i.pluginType!==t||n.push({...i,__dir:e})}}else{if(!await c.default.pathExists(e))return[];let r=await c.default.readdir(e);for(let i of r){let r=u.default.join(e,i);if(!(await c.default.stat(r)).isDirectory())continue;let a=u.default.join(r,`plugin.json`);if(!await c.default.pathExists(a))continue;let o=await c.default.readJson(a);t&&o.pluginType!==t||n.push({...o,__dir:r})}}return n}var h=class extends Error{constructor(e,t){super(`Failed to load plugin "${e}".\n${String(t)}`),this.name=`PluginLoadError`,this.pluginName=e,this.cause=t}},g=class extends Error{constructor(e){super(`Plugin "${e}" has invalid export`),this.name=`PluginInvalidExportError`,this.pluginName=e}},_=class extends Error{constructor(e,t,n){super(`Plugin type mismatch for "${e}": "${n}" !== "${t}"`),this.name=`PluginTypeMismatchError`,this.pluginName=e,this.expected=t,this.actual=n}};const v=(0,f.createRequire)(require(`url`).pathToFileURL(__filename).href);async function y(e){let t,n;if(e.module)try{t=await import((0,d.pathToFileURL)(u.default.resolve(e.__dir,e.module)).href)}catch(e){n=e}if(!t&&e.main)try{t=v(u.default.resolve(e.__dir,e.main))}catch(e){n=e}if(!t)throw new h(e.name,n);let r=t.default??t;if(!r||typeof r!=`object`)throw new g(e.name);if(r.pluginType!==e.pluginType)throw new _(e.name,e.pluginType,r.pluginType);return r}async function b(e,t){let n=await m(e,t),r=[];for(let e of n){let t=await y(e);r.push(t)}return r}exports.PluginInvalidExportError=g,exports.PluginLoadError=h,exports.PluginTypeMismatchError=_,exports.definePlugin=p,exports.findPlugins=m,exports.loadAllPlugins=b,exports.loadPlugin=y;
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["fs","require"],"sources":["../src/define-plugin.ts","../src/find-plugins.ts","../src/types.ts","../src/load-plugin.ts","../src/load-all-plugins.ts"],"sourcesContent":["import {LibriaPlugin} from \"./types\";\r\n\r\nexport function definePlugin<T>(\r\n pluginType: string,\r\n api: T,\r\n name: string\r\n): LibriaPlugin<T> {\r\n return {\r\n pluginType: pluginType,\r\n name,\r\n api\r\n };\r\n}","import {PluginManifest} from \"./types\";\r\nimport fs from \"fs-extra\";\r\nimport fg from \"fast-glob\";\r\nimport path from \"path\";\r\n\r\nexport async function findPlugins(\r\n pattern: string,\r\n pluginType?: string\r\n): Promise<PluginManifest[]> {\r\n const manifests: PluginManifest[] = [];\r\n\r\n // Check if pattern is a glob pattern or a simple path\r\n const isGlob = pattern.includes('*') || pattern.includes('{') || pattern.includes('?');\r\n\r\n if (isGlob) {\r\n // Normalize path separators for fast-glob (it expects forward slashes)\r\n const normalizedPattern = pattern.replace(/\\\\/g, '/');\r\n\r\n // Use fast-glob to find all matching directories\r\n const pluginDirs = await fg(normalizedPattern, {\r\n onlyDirectories: true,\r\n absolute: true,\r\n });\r\n\r\n for (const dir of pluginDirs) {\r\n const manifestPath = path.join(dir, 'plugin.json');\r\n if (!(await fs.pathExists(manifestPath))) continue;\r\n\r\n const raw = await fs.readJson(manifestPath);\r\n\r\n if (pluginType && raw.pluginType !== pluginType) continue;\r\n\r\n manifests.push({\r\n ...raw,\r\n __dir: dir\r\n });\r\n }\r\n } else {\r\n // Original behavior for simple directory paths\r\n if (!(await fs.pathExists(pattern))) return [];\r\n\r\n const entries = await fs.readdir(pattern);\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(pattern, entry);\r\n const stat = await fs.stat(fullPath);\r\n\r\n if (!stat.isDirectory()) continue;\r\n\r\n const manifestPath = path.join(fullPath, 'plugin.json');\r\n if (!(await fs.pathExists(manifestPath))) continue;\r\n\r\n const raw = await fs.readJson(manifestPath);\r\n\r\n if (pluginType && raw.pluginType !== pluginType) continue;\r\n\r\n manifests.push({\r\n ...raw,\r\n __dir: fullPath\r\n });\r\n }\r\n }\r\n\r\n return manifests;\r\n}","export interface LibriaPlugin<T = unknown> {\r\n readonly pluginType: string;\r\n readonly name?: string;\r\n readonly api: T;\r\n}\r\n\r\nexport interface PluginManifest {\r\n readonly name: string;\r\n readonly pluginType: string;\r\n\r\n readonly main?: string; // cjs\r\n readonly module?: string; // esm\r\n readonly types?: string;\r\n\r\n // resolved absolute path to the plugin folder\r\n readonly __dir: string;\r\n}\r\n\r\n// Plugin Errors\r\n\r\nexport class PluginLoadError extends Error {\r\n readonly pluginName: string;\r\n readonly cause: unknown;\r\n\r\n constructor(pluginName: string, cause: unknown) {\r\n super(`Failed to load plugin \"${pluginName}\".\\n${String(cause)}`);\r\n this.name = 'PluginLoadError';\r\n this.pluginName = pluginName;\r\n this.cause = cause;\r\n }\r\n}\r\n\r\nexport class PluginInvalidExportError extends Error {\r\n readonly pluginName: string;\r\n\r\n constructor(pluginName: string) {\r\n super(`Plugin \"${pluginName}\" has invalid export`);\r\n this.name = 'PluginInvalidExportError';\r\n this.pluginName = pluginName;\r\n }\r\n}\r\n\r\nexport class PluginTypeMismatchError extends Error {\r\n readonly pluginName: string;\r\n readonly expected: string;\r\n readonly actual: string;\r\n\r\n constructor(pluginName: string, expected: string, actual: string) {\r\n super(\r\n `Plugin type mismatch for \"${pluginName}\": ` +\r\n `\"${actual}\" !== \"${expected}\"`\r\n );\r\n this.name = 'PluginTypeMismatchError';\r\n this.pluginName = pluginName;\r\n this.expected = expected;\r\n this.actual = actual;\r\n }\r\n}","import {\r\n LibriaPlugin,\r\n PluginManifest,\r\n PluginLoadError,\r\n PluginInvalidExportError,\r\n PluginTypeMismatchError\r\n} from \"./types\";\r\nimport path from \"path\";\r\nimport {pathToFileURL} from \"url\";\r\nimport {createRequire} from \"node:module\";\r\n\r\nconst require = createRequire(import.meta.url);\r\n\r\nexport async function loadPlugin<T = unknown>(\r\n manifest: PluginManifest\r\n): Promise<LibriaPlugin<T>> {\r\n let mod: any;\r\n let lastError: unknown;\r\n\r\n // 1 Try ESM first\r\n if (manifest.module) {\r\n try {\r\n const esmPath = path.resolve(manifest.__dir, manifest.module);\r\n mod = await import(pathToFileURL(esmPath).href);\r\n } catch (err) {\r\n lastError = err;\r\n }\r\n }\r\n\r\n // 2 Fallback to CJS\r\n if (!mod && manifest.main) {\r\n try {\r\n const cjsPath = path.resolve(manifest.__dir, manifest.main);\r\n mod = require(cjsPath);\r\n } catch (err) {\r\n lastError = err;\r\n }\r\n }\r\n\r\n if (!mod) {\r\n throw new PluginLoadError(manifest.name, lastError);\r\n }\r\n\r\n // 3️⃣ Normalize export\r\n const plugin = (mod.default ?? mod) as LibriaPlugin<T>;\r\n\r\n if (!plugin || typeof plugin !== 'object') {\r\n throw new PluginInvalidExportError(manifest.name);\r\n }\r\n\r\n if (plugin.pluginType !== manifest.pluginType) {\r\n throw new PluginTypeMismatchError(\r\n manifest.name,\r\n manifest.pluginType,\r\n plugin.pluginType\r\n );\r\n }\r\n\r\n return plugin;\r\n}","import {LibriaPlugin} from \"./types\";\r\nimport {findPlugins} from \"./find-plugins\";\r\nimport {loadPlugin} from \"./load-plugin\";\r\n\r\nexport async function loadAllPlugins<T = unknown>(\r\n pattern: string,\r\n pluginType?: string\r\n): Promise<LibriaPlugin<T>[]> {\r\n const manifests = await findPlugins(pattern, pluginType);\r\n const plugins: LibriaPlugin<T>[] = [];\r\n\r\n for (const manifest of manifests) {\r\n const plugin = await loadPlugin<T>(manifest);\r\n plugins.push(plugin);\r\n }\r\n\r\n return plugins;\r\n}\r\n"],"mappings":"4mBAEA,SAAgB,EACZ,EACA,EACA,EACe,CACf,MAAO,CACS,aACZ,OACA,MACH,CCNL,eAAsB,EAClB,EACA,EACyB,CACzB,IAAM,EAA8B,EAAE,CAKtC,GAFe,EAAQ,SAAS,IAAI,EAAI,EAAQ,SAAS,IAAI,EAAI,EAAQ,SAAS,IAAI,CAE1E,CAKR,IAAM,EAAa,MAAA,EAAA,EAAA,SAHO,EAAQ,QAAQ,MAAO,IAAI,CAGN,CAC3C,gBAAiB,GACjB,SAAU,GACb,CAAC,CAEF,IAAK,IAAM,KAAO,EAAY,CAC1B,IAAM,EAAe,EAAA,QAAK,KAAK,EAAK,cAAc,CAClD,GAAI,CAAE,MAAMA,EAAAA,QAAG,WAAW,EAAa,CAAG,SAE1C,IAAM,EAAM,MAAMA,EAAAA,QAAG,SAAS,EAAa,CAEvC,GAAc,EAAI,aAAe,GAErC,EAAU,KAAK,CACX,GAAG,EACH,MAAO,EACV,CAAC,MAEH,CAEH,GAAI,CAAE,MAAMA,EAAAA,QAAG,WAAW,EAAQ,CAAG,MAAO,EAAE,CAE9C,IAAM,EAAU,MAAMA,EAAAA,QAAG,QAAQ,EAAQ,CAEzC,IAAK,IAAM,KAAS,EAAS,CACzB,IAAM,EAAW,EAAA,QAAK,KAAK,EAAS,EAAM,CAG1C,GAAI,EAFS,MAAMA,EAAAA,QAAG,KAAK,EAAS,EAE1B,aAAa,CAAE,SAEzB,IAAM,EAAe,EAAA,QAAK,KAAK,EAAU,cAAc,CACvD,GAAI,CAAE,MAAMA,EAAAA,QAAG,WAAW,EAAa,CAAG,SAE1C,IAAM,EAAM,MAAMA,EAAAA,QAAG,SAAS,EAAa,CAEvC,GAAc,EAAI,aAAe,GAErC,EAAU,KAAK,CACX,GAAG,EACH,MAAO,EACV,CAAC,EAIV,OAAO,EC3CX,IAAa,EAAb,cAAqC,KAAM,CAIvC,YAAY,EAAoB,EAAgB,CAC5C,MAAM,0BAA0B,EAAW,MAAM,OAAO,EAAM,GAAG,CACjE,KAAK,KAAO,kBACZ,KAAK,WAAa,EAClB,KAAK,MAAQ,IAIR,EAAb,cAA8C,KAAM,CAGhD,YAAY,EAAoB,CAC5B,MAAM,WAAW,EAAW,sBAAsB,CAClD,KAAK,KAAO,2BACZ,KAAK,WAAa,IAIb,EAAb,cAA6C,KAAM,CAK/C,YAAY,EAAoB,EAAkB,EAAgB,CAC9D,MACI,6BAA6B,EAAW,MACpC,EAAO,SAAS,EAAS,GAChC,CACD,KAAK,KAAO,0BACZ,KAAK,WAAa,EAClB,KAAK,SAAW,EAChB,KAAK,OAAS,IC5CtB,MAAMC,GAAAA,EAAAA,EAAAA,eAAAA,QAAAA,MAAAA,CAAAA,cAAAA,WAAAA,CAAAA,KAAwC,CAE9C,eAAsB,EAClB,EACwB,CACxB,IAAI,EACA,EAGJ,GAAI,EAAS,OACT,GAAI,CAEA,EAAM,MAAM,QAAA,EAAA,EAAA,eADI,EAAA,QAAK,QAAQ,EAAS,MAAO,EAAS,OAAO,CACpB,CAAC,YACrC,EAAK,CACV,EAAY,EAKpB,GAAI,CAAC,GAAO,EAAS,KACjB,GAAI,CAEA,EAAMA,EADU,EAAA,QAAK,QAAQ,EAAS,MAAO,EAAS,KAAK,CACrC,OACjB,EAAK,CACV,EAAY,EAIpB,GAAI,CAAC,EACD,MAAM,IAAI,EAAgB,EAAS,KAAM,EAAU,CAIvD,IAAM,EAAU,EAAI,SAAW,EAE/B,GAAI,CAAC,GAAU,OAAO,GAAW,SAC7B,MAAM,IAAI,EAAyB,EAAS,KAAK,CAGrD,GAAI,EAAO,aAAe,EAAS,WAC/B,MAAM,IAAI,EACN,EAAS,KACT,EAAS,WACT,EAAO,WACV,CAGL,OAAO,ECtDX,eAAsB,EAClB,EACA,EAC0B,CAC1B,IAAM,EAAY,MAAM,EAAY,EAAS,EAAW,CAClD,EAA6B,EAAE,CAErC,IAAK,IAAM,KAAY,EAAW,CAC9B,IAAM,EAAS,MAAM,EAAc,EAAS,CAC5C,EAAQ,KAAK,EAAO,CAGxB,OAAO"}
@@ -0,0 +1,44 @@
1
+ //#region src/types.d.ts
2
+ interface LibriaPlugin<T = unknown> {
3
+ readonly pluginType: string;
4
+ readonly name?: string;
5
+ readonly api: T;
6
+ }
7
+ interface PluginManifest {
8
+ readonly name: string;
9
+ readonly pluginType: string;
10
+ readonly main?: string;
11
+ readonly module?: string;
12
+ readonly types?: string;
13
+ readonly __dir: string;
14
+ }
15
+ declare class PluginLoadError extends Error {
16
+ readonly pluginName: string;
17
+ readonly cause: unknown;
18
+ constructor(pluginName: string, cause: unknown);
19
+ }
20
+ declare class PluginInvalidExportError extends Error {
21
+ readonly pluginName: string;
22
+ constructor(pluginName: string);
23
+ }
24
+ declare class PluginTypeMismatchError extends Error {
25
+ readonly pluginName: string;
26
+ readonly expected: string;
27
+ readonly actual: string;
28
+ constructor(pluginName: string, expected: string, actual: string);
29
+ }
30
+ //#endregion
31
+ //#region src/define-plugin.d.ts
32
+ declare function definePlugin<T>(pluginType: string, api: T, name: string): LibriaPlugin<T>;
33
+ //#endregion
34
+ //#region src/find-plugins.d.ts
35
+ declare function findPlugins(pattern: string, pluginType?: string): Promise<PluginManifest[]>;
36
+ //#endregion
37
+ //#region src/load-plugin.d.ts
38
+ declare function loadPlugin<T = unknown>(manifest: PluginManifest): Promise<LibriaPlugin<T>>;
39
+ //#endregion
40
+ //#region src/load-all-plugins.d.ts
41
+ declare function loadAllPlugins<T = unknown>(pattern: string, pluginType?: string): Promise<LibriaPlugin<T>[]>;
42
+ //#endregion
43
+ export { LibriaPlugin, PluginInvalidExportError, PluginLoadError, PluginManifest, PluginTypeMismatchError, definePlugin, findPlugins, loadAllPlugins, loadPlugin };
44
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,44 @@
1
+ //#region src/types.d.ts
2
+ interface LibriaPlugin<T = unknown> {
3
+ readonly pluginType: string;
4
+ readonly name?: string;
5
+ readonly api: T;
6
+ }
7
+ interface PluginManifest {
8
+ readonly name: string;
9
+ readonly pluginType: string;
10
+ readonly main?: string;
11
+ readonly module?: string;
12
+ readonly types?: string;
13
+ readonly __dir: string;
14
+ }
15
+ declare class PluginLoadError extends Error {
16
+ readonly pluginName: string;
17
+ readonly cause: unknown;
18
+ constructor(pluginName: string, cause: unknown);
19
+ }
20
+ declare class PluginInvalidExportError extends Error {
21
+ readonly pluginName: string;
22
+ constructor(pluginName: string);
23
+ }
24
+ declare class PluginTypeMismatchError extends Error {
25
+ readonly pluginName: string;
26
+ readonly expected: string;
27
+ readonly actual: string;
28
+ constructor(pluginName: string, expected: string, actual: string);
29
+ }
30
+ //#endregion
31
+ //#region src/define-plugin.d.ts
32
+ declare function definePlugin<T>(pluginType: string, api: T, name: string): LibriaPlugin<T>;
33
+ //#endregion
34
+ //#region src/find-plugins.d.ts
35
+ declare function findPlugins(pattern: string, pluginType?: string): Promise<PluginManifest[]>;
36
+ //#endregion
37
+ //#region src/load-plugin.d.ts
38
+ declare function loadPlugin<T = unknown>(manifest: PluginManifest): Promise<LibriaPlugin<T>>;
39
+ //#endregion
40
+ //#region src/load-all-plugins.d.ts
41
+ declare function loadAllPlugins<T = unknown>(pattern: string, pluginType?: string): Promise<LibriaPlugin<T>[]>;
42
+ //#endregion
43
+ export { LibriaPlugin, PluginInvalidExportError, PluginLoadError, PluginManifest, PluginTypeMismatchError, definePlugin, findPlugins, loadAllPlugins, loadPlugin };
44
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{createRequire as e}from"node:module";import t from"fs-extra";import n from"fast-glob";import r from"path";import{pathToFileURL as i}from"url";function a(e,t,n){return{pluginType:e,name:n,api:t}}async function o(e,i){let a=[];if(e.includes(`*`)||e.includes(`{`)||e.includes(`?`)){let o=await n(e.replace(/\\/g,`/`),{onlyDirectories:!0,absolute:!0});for(let e of o){let n=r.join(e,`plugin.json`);if(!await t.pathExists(n))continue;let o=await t.readJson(n);i&&o.pluginType!==i||a.push({...o,__dir:e})}}else{if(!await t.pathExists(e))return[];let n=await t.readdir(e);for(let o of n){let n=r.join(e,o);if(!(await t.stat(n)).isDirectory())continue;let s=r.join(n,`plugin.json`);if(!await t.pathExists(s))continue;let c=await t.readJson(s);i&&c.pluginType!==i||a.push({...c,__dir:n})}}return a}var s=class extends Error{constructor(e,t){super(`Failed to load plugin "${e}".\n${String(t)}`),this.name=`PluginLoadError`,this.pluginName=e,this.cause=t}},c=class extends Error{constructor(e){super(`Plugin "${e}" has invalid export`),this.name=`PluginInvalidExportError`,this.pluginName=e}},l=class extends Error{constructor(e,t,n){super(`Plugin type mismatch for "${e}": "${n}" !== "${t}"`),this.name=`PluginTypeMismatchError`,this.pluginName=e,this.expected=t,this.actual=n}};const u=e(import.meta.url);async function d(e){let t,n;if(e.module)try{t=await import(i(r.resolve(e.__dir,e.module)).href)}catch(e){n=e}if(!t&&e.main)try{t=u(r.resolve(e.__dir,e.main))}catch(e){n=e}if(!t)throw new s(e.name,n);let a=t.default??t;if(!a||typeof a!=`object`)throw new c(e.name);if(a.pluginType!==e.pluginType)throw new l(e.name,e.pluginType,a.pluginType);return a}async function f(e,t){let n=await o(e,t),r=[];for(let e of n){let t=await d(e);r.push(t)}return r}export{c as PluginInvalidExportError,s as PluginLoadError,l as PluginTypeMismatchError,a as definePlugin,o as findPlugins,f as loadAllPlugins,d as loadPlugin};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/define-plugin.ts","../src/find-plugins.ts","../src/types.ts","../src/load-plugin.ts","../src/load-all-plugins.ts"],"sourcesContent":["import {LibriaPlugin} from \"./types\";\r\n\r\nexport function definePlugin<T>(\r\n pluginType: string,\r\n api: T,\r\n name: string\r\n): LibriaPlugin<T> {\r\n return {\r\n pluginType: pluginType,\r\n name,\r\n api\r\n };\r\n}","import {PluginManifest} from \"./types\";\r\nimport fs from \"fs-extra\";\r\nimport fg from \"fast-glob\";\r\nimport path from \"path\";\r\n\r\nexport async function findPlugins(\r\n pattern: string,\r\n pluginType?: string\r\n): Promise<PluginManifest[]> {\r\n const manifests: PluginManifest[] = [];\r\n\r\n // Check if pattern is a glob pattern or a simple path\r\n const isGlob = pattern.includes('*') || pattern.includes('{') || pattern.includes('?');\r\n\r\n if (isGlob) {\r\n // Normalize path separators for fast-glob (it expects forward slashes)\r\n const normalizedPattern = pattern.replace(/\\\\/g, '/');\r\n\r\n // Use fast-glob to find all matching directories\r\n const pluginDirs = await fg(normalizedPattern, {\r\n onlyDirectories: true,\r\n absolute: true,\r\n });\r\n\r\n for (const dir of pluginDirs) {\r\n const manifestPath = path.join(dir, 'plugin.json');\r\n if (!(await fs.pathExists(manifestPath))) continue;\r\n\r\n const raw = await fs.readJson(manifestPath);\r\n\r\n if (pluginType && raw.pluginType !== pluginType) continue;\r\n\r\n manifests.push({\r\n ...raw,\r\n __dir: dir\r\n });\r\n }\r\n } else {\r\n // Original behavior for simple directory paths\r\n if (!(await fs.pathExists(pattern))) return [];\r\n\r\n const entries = await fs.readdir(pattern);\r\n\r\n for (const entry of entries) {\r\n const fullPath = path.join(pattern, entry);\r\n const stat = await fs.stat(fullPath);\r\n\r\n if (!stat.isDirectory()) continue;\r\n\r\n const manifestPath = path.join(fullPath, 'plugin.json');\r\n if (!(await fs.pathExists(manifestPath))) continue;\r\n\r\n const raw = await fs.readJson(manifestPath);\r\n\r\n if (pluginType && raw.pluginType !== pluginType) continue;\r\n\r\n manifests.push({\r\n ...raw,\r\n __dir: fullPath\r\n });\r\n }\r\n }\r\n\r\n return manifests;\r\n}","export interface LibriaPlugin<T = unknown> {\r\n readonly pluginType: string;\r\n readonly name?: string;\r\n readonly api: T;\r\n}\r\n\r\nexport interface PluginManifest {\r\n readonly name: string;\r\n readonly pluginType: string;\r\n\r\n readonly main?: string; // cjs\r\n readonly module?: string; // esm\r\n readonly types?: string;\r\n\r\n // resolved absolute path to the plugin folder\r\n readonly __dir: string;\r\n}\r\n\r\n// Plugin Errors\r\n\r\nexport class PluginLoadError extends Error {\r\n readonly pluginName: string;\r\n readonly cause: unknown;\r\n\r\n constructor(pluginName: string, cause: unknown) {\r\n super(`Failed to load plugin \"${pluginName}\".\\n${String(cause)}`);\r\n this.name = 'PluginLoadError';\r\n this.pluginName = pluginName;\r\n this.cause = cause;\r\n }\r\n}\r\n\r\nexport class PluginInvalidExportError extends Error {\r\n readonly pluginName: string;\r\n\r\n constructor(pluginName: string) {\r\n super(`Plugin \"${pluginName}\" has invalid export`);\r\n this.name = 'PluginInvalidExportError';\r\n this.pluginName = pluginName;\r\n }\r\n}\r\n\r\nexport class PluginTypeMismatchError extends Error {\r\n readonly pluginName: string;\r\n readonly expected: string;\r\n readonly actual: string;\r\n\r\n constructor(pluginName: string, expected: string, actual: string) {\r\n super(\r\n `Plugin type mismatch for \"${pluginName}\": ` +\r\n `\"${actual}\" !== \"${expected}\"`\r\n );\r\n this.name = 'PluginTypeMismatchError';\r\n this.pluginName = pluginName;\r\n this.expected = expected;\r\n this.actual = actual;\r\n }\r\n}","import {\r\n LibriaPlugin,\r\n PluginManifest,\r\n PluginLoadError,\r\n PluginInvalidExportError,\r\n PluginTypeMismatchError\r\n} from \"./types\";\r\nimport path from \"path\";\r\nimport {pathToFileURL} from \"url\";\r\nimport {createRequire} from \"node:module\";\r\n\r\nconst require = createRequire(import.meta.url);\r\n\r\nexport async function loadPlugin<T = unknown>(\r\n manifest: PluginManifest\r\n): Promise<LibriaPlugin<T>> {\r\n let mod: any;\r\n let lastError: unknown;\r\n\r\n // 1 Try ESM first\r\n if (manifest.module) {\r\n try {\r\n const esmPath = path.resolve(manifest.__dir, manifest.module);\r\n mod = await import(pathToFileURL(esmPath).href);\r\n } catch (err) {\r\n lastError = err;\r\n }\r\n }\r\n\r\n // 2 Fallback to CJS\r\n if (!mod && manifest.main) {\r\n try {\r\n const cjsPath = path.resolve(manifest.__dir, manifest.main);\r\n mod = require(cjsPath);\r\n } catch (err) {\r\n lastError = err;\r\n }\r\n }\r\n\r\n if (!mod) {\r\n throw new PluginLoadError(manifest.name, lastError);\r\n }\r\n\r\n // 3️⃣ Normalize export\r\n const plugin = (mod.default ?? mod) as LibriaPlugin<T>;\r\n\r\n if (!plugin || typeof plugin !== 'object') {\r\n throw new PluginInvalidExportError(manifest.name);\r\n }\r\n\r\n if (plugin.pluginType !== manifest.pluginType) {\r\n throw new PluginTypeMismatchError(\r\n manifest.name,\r\n manifest.pluginType,\r\n plugin.pluginType\r\n );\r\n }\r\n\r\n return plugin;\r\n}","import {LibriaPlugin} from \"./types\";\r\nimport {findPlugins} from \"./find-plugins\";\r\nimport {loadPlugin} from \"./load-plugin\";\r\n\r\nexport async function loadAllPlugins<T = unknown>(\r\n pattern: string,\r\n pluginType?: string\r\n): Promise<LibriaPlugin<T>[]> {\r\n const manifests = await findPlugins(pattern, pluginType);\r\n const plugins: LibriaPlugin<T>[] = [];\r\n\r\n for (const manifest of manifests) {\r\n const plugin = await loadPlugin<T>(manifest);\r\n plugins.push(plugin);\r\n }\r\n\r\n return plugins;\r\n}\r\n"],"mappings":"qJAEA,SAAgB,EACZ,EACA,EACA,EACe,CACf,MAAO,CACS,aACZ,OACA,MACH,CCNL,eAAsB,EAClB,EACA,EACyB,CACzB,IAAM,EAA8B,EAAE,CAKtC,GAFe,EAAQ,SAAS,IAAI,EAAI,EAAQ,SAAS,IAAI,EAAI,EAAQ,SAAS,IAAI,CAE1E,CAKR,IAAM,EAAa,MAAM,EAHC,EAAQ,QAAQ,MAAO,IAAI,CAGN,CAC3C,gBAAiB,GACjB,SAAU,GACb,CAAC,CAEF,IAAK,IAAM,KAAO,EAAY,CAC1B,IAAM,EAAe,EAAK,KAAK,EAAK,cAAc,CAClD,GAAI,CAAE,MAAM,EAAG,WAAW,EAAa,CAAG,SAE1C,IAAM,EAAM,MAAM,EAAG,SAAS,EAAa,CAEvC,GAAc,EAAI,aAAe,GAErC,EAAU,KAAK,CACX,GAAG,EACH,MAAO,EACV,CAAC,MAEH,CAEH,GAAI,CAAE,MAAM,EAAG,WAAW,EAAQ,CAAG,MAAO,EAAE,CAE9C,IAAM,EAAU,MAAM,EAAG,QAAQ,EAAQ,CAEzC,IAAK,IAAM,KAAS,EAAS,CACzB,IAAM,EAAW,EAAK,KAAK,EAAS,EAAM,CAG1C,GAAI,EAFS,MAAM,EAAG,KAAK,EAAS,EAE1B,aAAa,CAAE,SAEzB,IAAM,EAAe,EAAK,KAAK,EAAU,cAAc,CACvD,GAAI,CAAE,MAAM,EAAG,WAAW,EAAa,CAAG,SAE1C,IAAM,EAAM,MAAM,EAAG,SAAS,EAAa,CAEvC,GAAc,EAAI,aAAe,GAErC,EAAU,KAAK,CACX,GAAG,EACH,MAAO,EACV,CAAC,EAIV,OAAO,EC3CX,IAAa,EAAb,cAAqC,KAAM,CAIvC,YAAY,EAAoB,EAAgB,CAC5C,MAAM,0BAA0B,EAAW,MAAM,OAAO,EAAM,GAAG,CACjE,KAAK,KAAO,kBACZ,KAAK,WAAa,EAClB,KAAK,MAAQ,IAIR,EAAb,cAA8C,KAAM,CAGhD,YAAY,EAAoB,CAC5B,MAAM,WAAW,EAAW,sBAAsB,CAClD,KAAK,KAAO,2BACZ,KAAK,WAAa,IAIb,EAAb,cAA6C,KAAM,CAK/C,YAAY,EAAoB,EAAkB,EAAgB,CAC9D,MACI,6BAA6B,EAAW,MACpC,EAAO,SAAS,EAAS,GAChC,CACD,KAAK,KAAO,0BACZ,KAAK,WAAa,EAClB,KAAK,SAAW,EAChB,KAAK,OAAS,IC5CtB,MAAM,EAAU,EAAc,OAAO,KAAK,IAAI,CAE9C,eAAsB,EAClB,EACwB,CACxB,IAAI,EACA,EAGJ,GAAI,EAAS,OACT,GAAI,CAEA,EAAM,MAAM,OAAO,EADH,EAAK,QAAQ,EAAS,MAAO,EAAS,OAAO,CACpB,CAAC,YACrC,EAAK,CACV,EAAY,EAKpB,GAAI,CAAC,GAAO,EAAS,KACjB,GAAI,CAEA,EAAM,EADU,EAAK,QAAQ,EAAS,MAAO,EAAS,KAAK,CACrC,OACjB,EAAK,CACV,EAAY,EAIpB,GAAI,CAAC,EACD,MAAM,IAAI,EAAgB,EAAS,KAAM,EAAU,CAIvD,IAAM,EAAU,EAAI,SAAW,EAE/B,GAAI,CAAC,GAAU,OAAO,GAAW,SAC7B,MAAM,IAAI,EAAyB,EAAS,KAAK,CAGrD,GAAI,EAAO,aAAe,EAAS,WAC/B,MAAM,IAAI,EACN,EAAS,KACT,EAAS,WACT,EAAO,WACV,CAGL,OAAO,ECtDX,eAAsB,EAClB,EACA,EAC0B,CAC1B,IAAM,EAAY,MAAM,EAAY,EAAS,EAAW,CAClD,EAA6B,EAAE,CAErC,IAAK,IAAM,KAAY,EAAW,CAC9B,IAAM,EAAS,MAAM,EAAc,EAAS,CAC5C,EAAQ,KAAK,EAAO,CAGxB,OAAO"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@libria/plugin-loader",
3
+ "version": "0.1.0",
4
+ "description": "Simple plugin loader for Node.js applications",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.mjs",
7
+ "exports": {
8
+ ".": {
9
+ "require": {
10
+ "types": "./dist/index.d.cts",
11
+ "default": "./dist/index.cjs"
12
+ },
13
+ "import": {
14
+ "types": "./dist/index.d.mts",
15
+ "default": "./dist/index.mjs"
16
+ }
17
+ }
18
+ },
19
+ "type": "module",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/LibriaForge/plugin-loader.git"
23
+ },
24
+ "keywords": [
25
+ "plugin",
26
+ "loader",
27
+ "plugin loader",
28
+ "typescript"
29
+ ],
30
+ "author": "LibriaForge",
31
+ "license": "MIT",
32
+ "bugs": {
33
+ "url": "https://github.com/LibriaForge/plugin-loader/issues"
34
+ },
35
+ "homepage": "https://github.com/LibriaForge/plugin-loader#readme",
36
+ "dependencies": {
37
+ "fast-glob": "^3.3.3",
38
+ "fs-extra": "^11.3.3"
39
+ }
40
+ }