@vibesdotdev/runtime-environment-bun 0.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/dist/bun-environment.impl.d.ts +71 -0
- package/dist/bun-environment.impl.d.ts.map +1 -0
- package/dist/bun-environment.impl.js +130 -0
- package/dist/bun-environment.impl.js.map +1 -0
- package/dist/bun-loader.impl.d.ts +12 -0
- package/dist/bun-loader.impl.d.ts.map +1 -0
- package/dist/bun-loader.impl.js +96 -0
- package/dist/bun-loader.impl.js.map +1 -0
- package/dist/discovery/discovery.assets.consumer.d.ts +13 -0
- package/dist/discovery/discovery.assets.consumer.d.ts.map +1 -0
- package/dist/discovery/discovery.assets.consumer.js +116 -0
- package/dist/discovery/discovery.assets.consumer.js.map +1 -0
- package/dist/host/host.consumer.d.ts +12 -0
- package/dist/host/host.consumer.d.ts.map +1 -0
- package/dist/host/host.consumer.js +13 -0
- package/dist/host/host.consumer.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/fallback-loader.impl.consumer.d.ts +7 -0
- package/dist/loaders/fallback-loader.impl.consumer.d.ts.map +1 -0
- package/dist/loaders/fallback-loader.impl.consumer.js +63 -0
- package/dist/loaders/fallback-loader.impl.consumer.js.map +1 -0
- package/dist/package-plugins/candidates.consumer.d.ts +8 -0
- package/dist/package-plugins/candidates.consumer.d.ts.map +1 -0
- package/dist/package-plugins/candidates.consumer.js +133 -0
- package/dist/package-plugins/candidates.consumer.js.map +1 -0
- package/dist/package-plugins/import-module.consumer.d.ts +14 -0
- package/dist/package-plugins/import-module.consumer.d.ts.map +1 -0
- package/dist/package-plugins/import-module.consumer.js +28 -0
- package/dist/package-plugins/import-module.consumer.js.map +1 -0
- package/dist/package-plugins/index.d.ts +8 -0
- package/dist/package-plugins/index.d.ts.map +1 -0
- package/dist/package-plugins/index.js +8 -0
- package/dist/package-plugins/index.js.map +1 -0
- package/dist/package-plugins/installer.consumer.d.ts +14 -0
- package/dist/package-plugins/installer.consumer.d.ts.map +1 -0
- package/dist/package-plugins/installer.consumer.js +29 -0
- package/dist/package-plugins/installer.consumer.js.map +1 -0
- package/dist/package-plugins/loader.consumer.d.ts +12 -0
- package/dist/package-plugins/loader.consumer.d.ts.map +1 -0
- package/dist/package-plugins/loader.consumer.js +59 -0
- package/dist/package-plugins/loader.consumer.js.map +1 -0
- package/dist/package-plugins/reporting.d.ts +5 -0
- package/dist/package-plugins/reporting.d.ts.map +1 -0
- package/dist/package-plugins/reporting.js +10 -0
- package/dist/package-plugins/reporting.js.map +1 -0
- package/dist/package-plugins/resolve.consumer.d.ts +22 -0
- package/dist/package-plugins/resolve.consumer.d.ts.map +1 -0
- package/dist/package-plugins/resolve.consumer.js +265 -0
- package/dist/package-plugins/resolve.consumer.js.map +1 -0
- package/dist/process-spawn.impl.d.ts +15 -0
- package/dist/process-spawn.impl.d.ts.map +1 -0
- package/dist/process-spawn.impl.js +41 -0
- package/dist/process-spawn.impl.js.map +1 -0
- package/dist/services/runtime-path.impl.d.ts +20 -0
- package/dist/services/runtime-path.impl.d.ts.map +1 -0
- package/dist/services/runtime-path.impl.js +20 -0
- package/dist/services/runtime-path.impl.js.map +1 -0
- package/dist/services/self-spawn.consumer.d.ts +55 -0
- package/dist/services/self-spawn.consumer.d.ts.map +1 -0
- package/dist/services/self-spawn.consumer.js +85 -0
- package/dist/services/self-spawn.consumer.js.map +1 -0
- package/dist/services/workspace-resolve.d.ts +84 -0
- package/dist/services/workspace-resolve.d.ts.map +1 -0
- package/dist/services/workspace-resolve.js +107 -0
- package/dist/services/workspace-resolve.js.map +1 -0
- package/package.json +66 -0
- package/src/bun-environment.impl.ts +179 -0
- package/src/bun-loader.impl.ts +102 -0
- package/src/discovery/discovery.assets.consumer.ts +135 -0
- package/src/host/host.consumer.ts +23 -0
- package/src/index.ts +92 -0
- package/src/loaders/fallback-loader.impl.consumer.ts +57 -0
- package/src/package-plugins/candidates.consumer.ts +133 -0
- package/src/package-plugins/import-module.consumer.ts +27 -0
- package/src/package-plugins/index.ts +8 -0
- package/src/package-plugins/installer.consumer.ts +38 -0
- package/src/package-plugins/loader.consumer.ts +80 -0
- package/src/package-plugins/reporting.ts +13 -0
- package/src/package-plugins/resolve.consumer.ts +292 -0
- package/src/process-spawn.impl.ts +52 -0
- package/src/services/runtime-path.impl.ts +20 -0
- package/src/services/self-spawn.consumer.ts +91 -0
- package/src/services/workspace-resolve.ts +146 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun Runtime Environment Package
|
|
3
|
+
*
|
|
4
|
+
* Provides Bun-specific implementations for:
|
|
5
|
+
* - runtime/environment: BunEnvironment
|
|
6
|
+
* - runtime/loader: BunLoader
|
|
7
|
+
* - runtime/scope: Built from captured env
|
|
8
|
+
* - runtime/env: Env + secrets access
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { VibesRuntime } from '@vibesdotdev/runtime';
|
|
12
|
+
import { BunEnvironment, type BunEnvironmentOptions } from './bun-environment.impl';
|
|
13
|
+
import { BunLoader } from './bun-loader.impl';
|
|
14
|
+
import { bunRuntimePath } from './services/runtime-path.impl';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Register the Bun environment with the runtime.
|
|
18
|
+
* Call this at bootstrap before any plugins are loaded.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* // apps/my-app/src/hooks.server.ts
|
|
23
|
+
* import { registerBunEnvironment } from '@vibesdotdev/runtime-environment-bun';
|
|
24
|
+
* import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
25
|
+
*
|
|
26
|
+
* export async function handle({ event, resolve }) {
|
|
27
|
+
* registerBunEnvironment(getVibesRuntime(), {
|
|
28
|
+
* tenancy: {
|
|
29
|
+
* ownerType: 'organization',
|
|
30
|
+
* ownerId: event.locals.activeOrgId
|
|
31
|
+
* }
|
|
32
|
+
* });
|
|
33
|
+
* // ... rest of handle
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function registerBunEnvironment(
|
|
38
|
+
runtime: VibesRuntime,
|
|
39
|
+
options: BunEnvironmentOptions = {}
|
|
40
|
+
): BunEnvironment {
|
|
41
|
+
const environment = new BunEnvironment(options);
|
|
42
|
+
runtime.registerEnvironment(environment);
|
|
43
|
+
|
|
44
|
+
// Constitutional registration: the low-level path operations (owned by the host)
|
|
45
|
+
// are provided under the kind id defined by @vibesdotdev/paths.
|
|
46
|
+
// packages/paths queries this kind and stays completely agnostic to which
|
|
47
|
+
// runtime-environment-* package is active or what its classes look like.
|
|
48
|
+
//
|
|
49
|
+
// Register both the descriptor (so `query.withId('bun').resolve()` works)
|
|
50
|
+
// and the loader (so the impl is lazily produced when resolved).
|
|
51
|
+
runtime.registerDescriptor('paths/runtime-path', {
|
|
52
|
+
id: 'bun',
|
|
53
|
+
kind: 'paths/runtime-path',
|
|
54
|
+
name: 'Bun Runtime Path',
|
|
55
|
+
description: 'Bun host implementation of low-level path operations'
|
|
56
|
+
});
|
|
57
|
+
runtime.registerLoader('paths/runtime-path', 'bun', () => Promise.resolve({ impl: bunRuntimePath }));
|
|
58
|
+
|
|
59
|
+
return environment;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the Bun environment implementation.
|
|
64
|
+
* Throws if Bun environment wasn't registered.
|
|
65
|
+
*/
|
|
66
|
+
export function getBunEnvironment(runtime: VibesRuntime): BunEnvironment {
|
|
67
|
+
const env = runtime.environment;
|
|
68
|
+
if (!(env instanceof BunEnvironment)) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Expected BunEnvironment but got ${env.constructor.name}. ` +
|
|
71
|
+
'Did you call registerBunEnvironment() at bootstrap?'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return env;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { BunEnvironment, BunLoader };
|
|
78
|
+
export { BunProcessSpawn } from './process-spawn.impl';
|
|
79
|
+
export { bunRuntimePath } from './services/runtime-path.impl';
|
|
80
|
+
export type { BunEnvironmentOptions };
|
|
81
|
+
|
|
82
|
+
// Package import policy / dynamic plugin loading from the filesystem
|
|
83
|
+
// (owned by the environment per RUNTIME-ENVIRONMENT-SURFACE-SPEC.md)
|
|
84
|
+
export { loadPlugins } from './package-plugins/loader.consumer';
|
|
85
|
+
export { reportMissingPlugins } from './package-plugins/installer.consumer';
|
|
86
|
+
export { resolvePluginModule, getPackageName } from './package-plugins/resolve.consumer';
|
|
87
|
+
|
|
88
|
+
// Fallback loader for test/dev when no surface loader is provided
|
|
89
|
+
export { fallbackLoader } from './loaders/fallback-loader.impl.consumer';
|
|
90
|
+
|
|
91
|
+
// Consumer asset discovery helper (filesystem scanning for .descriptor / .impl / .plugin files)
|
|
92
|
+
export { discoverAssets } from './discovery/discovery.assets.consumer';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import type {
|
|
4
|
+
RuntimeDiscoveryConfig,
|
|
5
|
+
RuntimeLoaderImplementation,
|
|
6
|
+
RuntimeLoaderModuleMaps
|
|
7
|
+
} from '@vibesdotdev/runtime/schemas/registry';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Test/dev fallback loader.
|
|
11
|
+
* Recursively scans basePaths for .ts/.js files and returns lazy importers.
|
|
12
|
+
*/
|
|
13
|
+
export const fallbackLoader: RuntimeLoaderImplementation = {
|
|
14
|
+
async loadModules({ discovery }: { discovery?: RuntimeDiscoveryConfig }) {
|
|
15
|
+
const descriptors: Record<string, () => Promise<{ default?: object }>> = {};
|
|
16
|
+
const implementations: Record<string, () => Promise<{ default?: object }>> = {};
|
|
17
|
+
const plugins: Record<string, () => Promise<{ default?: object }>> = {};
|
|
18
|
+
|
|
19
|
+
async function scanDir(dir: string) {
|
|
20
|
+
try {
|
|
21
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
const fullPath = join(dir, entry.name);
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
await scanDir(fullPath);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (!entry.isFile()) continue;
|
|
29
|
+
const name = entry.name;
|
|
30
|
+
if (!name.endsWith('.ts') && !name.endsWith('.js')) continue;
|
|
31
|
+
const importer = async () => {
|
|
32
|
+
try {
|
|
33
|
+
return await import(fullPath);
|
|
34
|
+
} catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
if (name.includes('.descriptor')) {
|
|
39
|
+
descriptors[fullPath] = importer;
|
|
40
|
+
} else if (name.includes('.impl')) {
|
|
41
|
+
implementations[fullPath] = importer;
|
|
42
|
+
} else if (name.includes('.plugin')) {
|
|
43
|
+
plugins[fullPath] = importer;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Skip unreadable directories
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const basePath of discovery?.basePaths ?? []) {
|
|
52
|
+
await scanDir(basePath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return { descriptors, implementations, plugins } as RuntimeLoaderModuleMaps;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { basename, extname, join, sep } from 'path';
|
|
3
|
+
import type { ExportTarget } from '@vibesdotdev/runtime/schemas/package-plugins';
|
|
4
|
+
|
|
5
|
+
const EXPORT_CONDITION_PRIORITY = [
|
|
6
|
+
'bun',
|
|
7
|
+
'import',
|
|
8
|
+
'module',
|
|
9
|
+
'default',
|
|
10
|
+
'compiled',
|
|
11
|
+
'node',
|
|
12
|
+
'require'
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
function readPackageJson(packageRoot: string): { exports?: Record<string, unknown> | string } {
|
|
16
|
+
return JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')) as {
|
|
17
|
+
exports?: Record<string, unknown> | string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getPackageName(specifier: string): string {
|
|
22
|
+
if (specifier.startsWith('@')) {
|
|
23
|
+
const parts = specifier.split('/');
|
|
24
|
+
return `${parts[0]}/${parts[1]}`;
|
|
25
|
+
}
|
|
26
|
+
return specifier.split('/')[0]!;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isBarePackageSpecifier(specifier: string): boolean {
|
|
30
|
+
return specifier === getPackageName(specifier);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function packageRootFor(baseDir: string, packageName: string): string | null {
|
|
34
|
+
const root = join(baseDir, 'node_modules', ...packageName.split('/'));
|
|
35
|
+
return existsSync(join(root, 'package.json')) ? root : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function exportPluginSpecifiers(packageName: string, packageRoot: string): string[] {
|
|
39
|
+
const pkg = readPackageJson(packageRoot) as { exports?: Record<string, unknown> };
|
|
40
|
+
if (!pkg.exports || typeof pkg.exports !== 'object' || Array.isArray(pkg.exports)) return [];
|
|
41
|
+
|
|
42
|
+
const exactName = `./${packageName.split('/').pop()!}.plugin`;
|
|
43
|
+
const keys = Object.keys(pkg.exports).filter((key) => key === './plugin' || key.endsWith('.plugin'));
|
|
44
|
+
keys.sort((left, right) => {
|
|
45
|
+
if (left === './plugin') return -1;
|
|
46
|
+
if (right === './plugin') return 1;
|
|
47
|
+
if (left === exactName) return -1;
|
|
48
|
+
if (right === exactName) return 1;
|
|
49
|
+
return left.localeCompare(right);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return keys.map((key) => `${packageName}/${key.slice(2)}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveExportTarget(packageRoot: string, target: ExportTarget): string | null {
|
|
56
|
+
if (!target) return null;
|
|
57
|
+
if (typeof target === 'string') return join(packageRoot, target);
|
|
58
|
+
if (Array.isArray(target)) {
|
|
59
|
+
for (const option of target) {
|
|
60
|
+
const resolved = resolveExportTarget(packageRoot, option);
|
|
61
|
+
if (resolved && existsSync(resolved)) return resolved;
|
|
62
|
+
if (resolved) return resolved;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const record = target as Record<string, unknown>;
|
|
68
|
+
for (const key of EXPORT_CONDITION_PRIORITY) {
|
|
69
|
+
if (!(key in record)) continue;
|
|
70
|
+
const resolved = resolveExportTarget(packageRoot, record[key] as ExportTarget);
|
|
71
|
+
if (resolved && existsSync(resolved)) return resolved;
|
|
72
|
+
if (resolved) return resolved;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const value of Object.values(record)) {
|
|
76
|
+
const resolved = resolveExportTarget(packageRoot, value as ExportTarget);
|
|
77
|
+
if (resolved && existsSync(resolved)) return resolved;
|
|
78
|
+
if (resolved) return resolved;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function resolvePackageExportPath(packageRoot: string, subpath: string): string | null {
|
|
85
|
+
const pkg = readPackageJson(packageRoot);
|
|
86
|
+
if (!pkg.exports) return null;
|
|
87
|
+
|
|
88
|
+
if (typeof pkg.exports === 'string') {
|
|
89
|
+
return subpath === '.' ? join(packageRoot, pkg.exports) : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const exportsMap = pkg.exports as Record<string, unknown>;
|
|
93
|
+
const exportValue = subpath === '.' ? (exportsMap['.'] ?? exportsMap) : exportsMap[subpath];
|
|
94
|
+
return resolveExportTarget(packageRoot, exportValue as ExportTarget);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function resolvePackageSpecifierPath(baseDir: string, specifier: string): string | null {
|
|
98
|
+
const packageName = getPackageName(specifier);
|
|
99
|
+
const packageRoot = packageRootFor(baseDir, packageName);
|
|
100
|
+
if (!packageRoot) return null;
|
|
101
|
+
|
|
102
|
+
const subpath = specifier === packageName ? '.' : `./${specifier.slice(packageName.length + 1)}`;
|
|
103
|
+
return resolvePackageExportPath(packageRoot, subpath);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function collectPluginFiles(dir: string, files: string[] = [], depth = 0): string[] {
|
|
107
|
+
if (depth > 6) return files;
|
|
108
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
109
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
110
|
+
const fullPath = join(dir, entry.name);
|
|
111
|
+
if (entry.isDirectory()) {
|
|
112
|
+
collectPluginFiles(fullPath, files, depth + 1);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (!entry.isFile()) continue;
|
|
116
|
+
if (!entry.name.includes('.plugin.')) continue;
|
|
117
|
+
const extension = extname(entry.name);
|
|
118
|
+
if (!['.ts', '.js', '.mjs', '.cjs'].includes(extension)) continue;
|
|
119
|
+
files.push(fullPath);
|
|
120
|
+
}
|
|
121
|
+
return files;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function pluginFileCandidates(packageRoot: string, packageName: string): string[] {
|
|
125
|
+
const expectedBasename = `${packageName.split('/').pop()!}.plugin`;
|
|
126
|
+
return collectPluginFiles(packageRoot).sort((left, right) => {
|
|
127
|
+
const leftName = basename(left).replace(extname(left), '');
|
|
128
|
+
const rightName = basename(right).replace(extname(right), '');
|
|
129
|
+
const leftScore = Number(leftName !== expectedBasename) * 100 + left.split(sep).length;
|
|
130
|
+
const rightScore = Number(rightName !== expectedBasename) * 100 + right.split(sep).length;
|
|
131
|
+
return leftScore - rightScore || left.localeCompare(right);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard dynamic import for plugin modules.
|
|
3
|
+
*
|
|
4
|
+
* Resolves a path on disk to a `file://` URL and imports it. No bundle-shim
|
|
5
|
+
* fallback: a self-contained binary already has its own first-party plugins
|
|
6
|
+
* bundled, and project-local third-party plugins resolve normally through
|
|
7
|
+
* Bun's standard module resolution.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { pathToFileURL } from 'url';
|
|
11
|
+
|
|
12
|
+
export function isNotFoundError(message: string): boolean {
|
|
13
|
+
return (
|
|
14
|
+
message.includes('Cannot find') ||
|
|
15
|
+
message.includes('Could not resolve') ||
|
|
16
|
+
message.includes('Failed to resolve')
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function importPluginModule(path: string): Promise<{
|
|
21
|
+
loadedPath: string;
|
|
22
|
+
module: Record<string, unknown>;
|
|
23
|
+
}> {
|
|
24
|
+
const url = path.startsWith('file://') ? path : pathToFileURL(path).href;
|
|
25
|
+
const module = (await import(url)) as Record<string, unknown>;
|
|
26
|
+
return { loadedPath: path, module };
|
|
27
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin install advisor.
|
|
3
|
+
*
|
|
4
|
+
* When `loadPlugins` returns `missing: [...]` (specifiers that didn't resolve
|
|
5
|
+
* from the project), this surface emits a clear, actionable instruction
|
|
6
|
+
* telling the user exactly how to install them and from which directory.
|
|
7
|
+
*
|
|
8
|
+
* Auto-installing was removed: it was the source of opaque failures and
|
|
9
|
+
* surprising network calls. The CLI now refuses to mutate the project's
|
|
10
|
+
* dependencies on the user's behalf — they run `bun add` themselves.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { PackagePluginRequest, PluginLoadOptions } from '@vibesdotdev/runtime/schemas/package-plugins';
|
|
14
|
+
import { getPackageName } from './resolve.consumer';
|
|
15
|
+
import { reportError } from './reporting';
|
|
16
|
+
|
|
17
|
+
export function reportMissingPlugins(
|
|
18
|
+
requests: PackagePluginRequest[],
|
|
19
|
+
options?: PluginLoadOptions
|
|
20
|
+
): void {
|
|
21
|
+
if (requests.length === 0) return;
|
|
22
|
+
|
|
23
|
+
const byProject = new Map<string, Set<string>>();
|
|
24
|
+
for (const request of requests) {
|
|
25
|
+
const packages = byProject.get(request.projectDir) ?? new Set<string>();
|
|
26
|
+
packages.add(getPackageName(request.id));
|
|
27
|
+
byProject.set(request.projectDir, packages);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
for (const [projectDir, packages] of byProject) {
|
|
31
|
+
const pkgList = [...packages].sort().join(' ');
|
|
32
|
+
reportError(
|
|
33
|
+
options,
|
|
34
|
+
`[vibes] ${packages.size} plugin(s) configured but not installed in ${projectDir}.\n` +
|
|
35
|
+
` Run: cd ${projectDir} && bun add ${pkgList}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin loader.
|
|
3
|
+
*
|
|
4
|
+
* Resolves each `PackagePluginRequest` via Bun's standard module resolution
|
|
5
|
+
* from `request.projectDir` and registers any returned `RuntimePlugin` with
|
|
6
|
+
* the runtime. Plugins that don't resolve are reported as missing; the caller
|
|
7
|
+
* surfaces install instructions to the user.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { VibesRuntime } from '@vibesdotdev/runtime';
|
|
11
|
+
import type {
|
|
12
|
+
LoadPluginsResult,
|
|
13
|
+
PackagePluginRequest,
|
|
14
|
+
PluginLoadOptions,
|
|
15
|
+
PluginLoadResult
|
|
16
|
+
} from '@vibesdotdev/runtime/schemas/package-plugins';
|
|
17
|
+
import { reportInfo, reportWarn } from './reporting';
|
|
18
|
+
import { resolvePluginModule } from './resolve.consumer';
|
|
19
|
+
|
|
20
|
+
function validatePluginRequest(request: PackagePluginRequest): void {
|
|
21
|
+
if (typeof request.id !== 'string') {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`[vibes] Plugin request id must be a string, got ${typeof request.id}. ` +
|
|
24
|
+
`Check your vibes.config.ts plugins array — all entries must be strings. ` +
|
|
25
|
+
`Received: ${JSON.stringify(request)}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function loadPlugins(
|
|
31
|
+
runtime: VibesRuntime,
|
|
32
|
+
requests: PackagePluginRequest[],
|
|
33
|
+
options?: PluginLoadOptions
|
|
34
|
+
): Promise<LoadPluginsResult> {
|
|
35
|
+
const loaded: PluginLoadResult[] = [];
|
|
36
|
+
const missing: PackagePluginRequest[] = [];
|
|
37
|
+
const verbose = options?.verbose ?? false;
|
|
38
|
+
|
|
39
|
+
for (const request of requests) {
|
|
40
|
+
validatePluginRequest(request);
|
|
41
|
+
const resolved = await resolvePluginModule(request);
|
|
42
|
+
if ('plugins' in resolved) {
|
|
43
|
+
for (const entry of resolved.plugins) {
|
|
44
|
+
try {
|
|
45
|
+
await runtime.registerPlugin(entry.plugin);
|
|
46
|
+
loaded.push({
|
|
47
|
+
id: `${request.id} → ${entry.plugin.id}`,
|
|
48
|
+
success: true,
|
|
49
|
+
resolvedPath: entry.resolvedPath
|
|
50
|
+
});
|
|
51
|
+
if (verbose) {
|
|
52
|
+
reportInfo(options, `[vibes] Loaded plugin: ${entry.plugin.id} (${entry.specifier})`);
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
loaded.push({
|
|
57
|
+
id: `${request.id} → ${entry.plugin.id}`,
|
|
58
|
+
success: false,
|
|
59
|
+
error: message
|
|
60
|
+
});
|
|
61
|
+
reportWarn(
|
|
62
|
+
options,
|
|
63
|
+
`[vibes] Failed to register ${entry.plugin.id} from ${entry.specifier}: ${message}`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (resolved.missing) {
|
|
71
|
+
missing.push(request);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
loaded.push({ id: request.id, success: false, error: resolved.error });
|
|
76
|
+
reportWarn(options, `[vibes] Failed to load ${request.id}: ${resolved.error}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { loaded, missing };
|
|
80
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PluginLoadOptions } from '@vibesdotdev/runtime/schemas/package-plugins';
|
|
2
|
+
|
|
3
|
+
export function reportInfo(options: PluginLoadOptions | undefined, message: string): void {
|
|
4
|
+
options?.report?.info?.(message);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function reportWarn(options: PluginLoadOptions | undefined, message: string): void {
|
|
8
|
+
options?.report?.warn?.(message);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function reportError(options: PluginLoadOptions | undefined, message: string): void {
|
|
12
|
+
options?.report?.error?.(message);
|
|
13
|
+
}
|