@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.
Files changed (86) hide show
  1. package/dist/bun-environment.impl.d.ts +71 -0
  2. package/dist/bun-environment.impl.d.ts.map +1 -0
  3. package/dist/bun-environment.impl.js +130 -0
  4. package/dist/bun-environment.impl.js.map +1 -0
  5. package/dist/bun-loader.impl.d.ts +12 -0
  6. package/dist/bun-loader.impl.d.ts.map +1 -0
  7. package/dist/bun-loader.impl.js +96 -0
  8. package/dist/bun-loader.impl.js.map +1 -0
  9. package/dist/discovery/discovery.assets.consumer.d.ts +13 -0
  10. package/dist/discovery/discovery.assets.consumer.d.ts.map +1 -0
  11. package/dist/discovery/discovery.assets.consumer.js +116 -0
  12. package/dist/discovery/discovery.assets.consumer.js.map +1 -0
  13. package/dist/host/host.consumer.d.ts +12 -0
  14. package/dist/host/host.consumer.d.ts.map +1 -0
  15. package/dist/host/host.consumer.js +13 -0
  16. package/dist/host/host.consumer.js.map +1 -0
  17. package/dist/index.d.ts +49 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +77 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/loaders/fallback-loader.impl.consumer.d.ts +7 -0
  22. package/dist/loaders/fallback-loader.impl.consumer.d.ts.map +1 -0
  23. package/dist/loaders/fallback-loader.impl.consumer.js +63 -0
  24. package/dist/loaders/fallback-loader.impl.consumer.js.map +1 -0
  25. package/dist/package-plugins/candidates.consumer.d.ts +8 -0
  26. package/dist/package-plugins/candidates.consumer.d.ts.map +1 -0
  27. package/dist/package-plugins/candidates.consumer.js +133 -0
  28. package/dist/package-plugins/candidates.consumer.js.map +1 -0
  29. package/dist/package-plugins/import-module.consumer.d.ts +14 -0
  30. package/dist/package-plugins/import-module.consumer.d.ts.map +1 -0
  31. package/dist/package-plugins/import-module.consumer.js +28 -0
  32. package/dist/package-plugins/import-module.consumer.js.map +1 -0
  33. package/dist/package-plugins/index.d.ts +8 -0
  34. package/dist/package-plugins/index.d.ts.map +1 -0
  35. package/dist/package-plugins/index.js +8 -0
  36. package/dist/package-plugins/index.js.map +1 -0
  37. package/dist/package-plugins/installer.consumer.d.ts +14 -0
  38. package/dist/package-plugins/installer.consumer.d.ts.map +1 -0
  39. package/dist/package-plugins/installer.consumer.js +29 -0
  40. package/dist/package-plugins/installer.consumer.js.map +1 -0
  41. package/dist/package-plugins/loader.consumer.d.ts +12 -0
  42. package/dist/package-plugins/loader.consumer.d.ts.map +1 -0
  43. package/dist/package-plugins/loader.consumer.js +59 -0
  44. package/dist/package-plugins/loader.consumer.js.map +1 -0
  45. package/dist/package-plugins/reporting.d.ts +5 -0
  46. package/dist/package-plugins/reporting.d.ts.map +1 -0
  47. package/dist/package-plugins/reporting.js +10 -0
  48. package/dist/package-plugins/reporting.js.map +1 -0
  49. package/dist/package-plugins/resolve.consumer.d.ts +22 -0
  50. package/dist/package-plugins/resolve.consumer.d.ts.map +1 -0
  51. package/dist/package-plugins/resolve.consumer.js +265 -0
  52. package/dist/package-plugins/resolve.consumer.js.map +1 -0
  53. package/dist/process-spawn.impl.d.ts +15 -0
  54. package/dist/process-spawn.impl.d.ts.map +1 -0
  55. package/dist/process-spawn.impl.js +41 -0
  56. package/dist/process-spawn.impl.js.map +1 -0
  57. package/dist/services/runtime-path.impl.d.ts +20 -0
  58. package/dist/services/runtime-path.impl.d.ts.map +1 -0
  59. package/dist/services/runtime-path.impl.js +20 -0
  60. package/dist/services/runtime-path.impl.js.map +1 -0
  61. package/dist/services/self-spawn.consumer.d.ts +55 -0
  62. package/dist/services/self-spawn.consumer.d.ts.map +1 -0
  63. package/dist/services/self-spawn.consumer.js +85 -0
  64. package/dist/services/self-spawn.consumer.js.map +1 -0
  65. package/dist/services/workspace-resolve.d.ts +84 -0
  66. package/dist/services/workspace-resolve.d.ts.map +1 -0
  67. package/dist/services/workspace-resolve.js +107 -0
  68. package/dist/services/workspace-resolve.js.map +1 -0
  69. package/package.json +66 -0
  70. package/src/bun-environment.impl.ts +179 -0
  71. package/src/bun-loader.impl.ts +102 -0
  72. package/src/discovery/discovery.assets.consumer.ts +135 -0
  73. package/src/host/host.consumer.ts +23 -0
  74. package/src/index.ts +92 -0
  75. package/src/loaders/fallback-loader.impl.consumer.ts +57 -0
  76. package/src/package-plugins/candidates.consumer.ts +133 -0
  77. package/src/package-plugins/import-module.consumer.ts +27 -0
  78. package/src/package-plugins/index.ts +8 -0
  79. package/src/package-plugins/installer.consumer.ts +38 -0
  80. package/src/package-plugins/loader.consumer.ts +80 -0
  81. package/src/package-plugins/reporting.ts +13 -0
  82. package/src/package-plugins/resolve.consumer.ts +292 -0
  83. package/src/process-spawn.impl.ts +52 -0
  84. package/src/services/runtime-path.impl.ts +20 -0
  85. package/src/services/self-spawn.consumer.ts +91 -0
  86. package/src/services/workspace-resolve.ts +146 -0
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Unified Workspace Root Resolution
3
+ *
4
+ * Single source of truth for resolving the workspace root path.
5
+ * This consolidates the various fallback patterns used across the codebase.
6
+ *
7
+ * Priority order (highest to lowest):
8
+ * 1. Explicit override (e.g., MCP request header)
9
+ * 2. Runtime context workspacePath
10
+ * 3. Environment config workspacePath
11
+ * 4. CLI invocation cwd
12
+ * 5. process.cwd() fallback
13
+ *
14
+ * NOTE: This module lives in @vibesdotdev/runtime (not workspace) to avoid
15
+ * a circular dependency between runtime and workspace. The workspace package
16
+ * re-exports from here for backward compatibility.
17
+ */
18
+ function normalizeCandidate(path) {
19
+ if (typeof path !== 'string')
20
+ return undefined;
21
+ const trimmed = path.trim();
22
+ return trimmed.length > 0 ? trimmed : undefined;
23
+ }
24
+ function readStringField(value, key) {
25
+ if (!value)
26
+ return undefined;
27
+ const candidate = value[key];
28
+ return typeof candidate === 'string' ? candidate : undefined;
29
+ }
30
+ const RESOLUTION_ORDER = [
31
+ 'override',
32
+ 'runtimeContext',
33
+ 'environment',
34
+ 'cliCwd',
35
+ 'cwd'
36
+ ];
37
+ /**
38
+ * Resolve workspace root path from multiple sources with priority fallback.
39
+ *
40
+ * @param sources - Object containing potential workspace path sources
41
+ * @returns The resolved workspace path (never undefined)
42
+ *
43
+ * @example
44
+ * ```ts
45
+ * const workspace = resolveWorkspaceRoot({
46
+ * override: request.workspacePath,
47
+ * runtimeContext: context.workspacePath,
48
+ * environment: context.environment?.workspacePath,
49
+ * cliCwd: context.cli?.cwd
50
+ * });
51
+ * ```
52
+ */
53
+ export function resolveWorkspaceRoot(sources = {}) {
54
+ const { path } = resolveWorkspaceRootWithSource(sources);
55
+ return path;
56
+ }
57
+ /**
58
+ * Resolve workspace root path with source information for debugging.
59
+ *
60
+ * @param sources - Object containing potential workspace path sources
61
+ * @returns Object with resolved path and source that provided it
62
+ */
63
+ export function resolveWorkspaceRootWithSource(sources = {}) {
64
+ for (const source of RESOLUTION_ORDER) {
65
+ const path = normalizeCandidate(sources[source]);
66
+ if (path) {
67
+ return { path, source };
68
+ }
69
+ }
70
+ const fallback = sources.cwd || process.cwd();
71
+ return { path: fallback, source: 'processCwd' };
72
+ }
73
+ // Context Extraction Helpers
74
+ /**
75
+ * Extract workspace sources from a RuntimeContext-like object.
76
+ * Handles the various shapes of context objects in the codebase.
77
+ *
78
+ * @param context - Runtime context object with optional workspace-related fields
79
+ * @param override - Optional explicit override (e.g., from MCP request)
80
+ * @returns WorkspaceSources for use with resolveWorkspaceRoot
81
+ */
82
+ export function extractWorkspaceSources(context, override) {
83
+ return {
84
+ override: normalizeCandidate(override),
85
+ runtimeContext: normalizeCandidate(context.workspacePath),
86
+ environment: normalizeCandidate(readStringField(context.environment, 'workspacePath')),
87
+ cliCwd: normalizeCandidate(readStringField(context.cli, 'cwd'))
88
+ };
89
+ }
90
+ /**
91
+ * Convenience function: Extract sources and resolve in one step.
92
+ *
93
+ * @param context - Runtime context object
94
+ * @param override - Optional explicit override
95
+ * @returns Resolved workspace path
96
+ */
97
+ export function resolveWorkspaceFromContext(context, override) {
98
+ return resolveWorkspaceRoot(extractWorkspaceSources(context, override));
99
+ }
100
+ /**
101
+ * Resolve the current workspace as an object with rootPath.
102
+ * Convenience wrapper for callers that expect `{ rootPath: string }`.
103
+ */
104
+ export function resolveCurrentWorkspace() {
105
+ return { rootPath: resolveWorkspaceRoot() };
106
+ }
107
+ //# sourceMappingURL=workspace-resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-resolve.js","sourceRoot":"","sources":["../../src/services/workspace-resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,SAAS,kBAAkB,CAAC,IAAwB;IACnD,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC;AAED,SAAS,eAAe,CACvB,KAAgC,EAChC,GAA4B;IAE5B,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,SAAS,GAAI,KAA6D,CAAC,GAAG,CAAC,CAAC;IACtF,OAAO,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9D,CAAC;AAqBD,MAAM,gBAAgB,GAAkC;IACvD,UAAU;IACV,gBAAgB;IAChB,aAAa;IACb,QAAQ;IACR,KAAK;CACL,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAA4B,EAAE;IAClE,MAAM,EAAE,IAAI,EAAE,GAAG,8BAA8B,CAAC,OAAO,CAAC,CAAC;IACzD,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B,CAC7C,UAA4B,EAAE;IAE9B,KAAK,MAAM,MAAM,IAAI,gBAAgB,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QACjD,IAAI,IAAI,EAAE,CAAC;YACV,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACzB,CAAC;IACF,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;AACjD,CAAC;AAED,6BAA6B;AAE7B;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CACtC,OAAmC,EACnC,QAAiB;IAEjB,OAAO;QACN,QAAQ,EAAE,kBAAkB,CAAC,QAAQ,CAAC;QACtC,cAAc,EAAE,kBAAkB,CAAC,OAAO,CAAC,aAAa,CAAC;QACzD,WAAW,EAAE,kBAAkB,CAAC,eAAe,CAAC,OAAO,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;QACtF,MAAM,EAAE,kBAAkB,CAAC,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;KAC/D,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CAC1C,OAAmC,EACnC,QAAiB;IAEjB,OAAO,oBAAoB,CAAC,uBAAuB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB;IACtC,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,EAAE,CAAC;AAC7C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@vibesdotdev/runtime-environment-bun",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "bun": "./src/index.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ },
14
+ "./loader": {
15
+ "types": "./dist/bun-loader.impl.d.ts",
16
+ "bun": "./src/bun-loader.impl.ts",
17
+ "import": "./dist/bun-loader.impl.js",
18
+ "default": "./dist/bun-loader.impl.js"
19
+ },
20
+ "./loaders/fallback-loader.impl.consumer": {
21
+ "types": "./dist/loaders/fallback-loader.impl.consumer.d.ts",
22
+ "bun": "./src/loaders/fallback-loader.impl.consumer.ts",
23
+ "import": "./dist/loaders/fallback-loader.impl.consumer.js",
24
+ "default": "./dist/loaders/fallback-loader.impl.consumer.js"
25
+ }
26
+ },
27
+ "dependencies": {
28
+ "@vibesdotdev/runtime": "0.0.1"
29
+ },
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "check": "bun --bun tsc -p tsconfig.json --noEmit"
33
+ },
34
+ "license": "MIT",
35
+ "files": [
36
+ "dist",
37
+ "src",
38
+ "bin",
39
+ "README.md",
40
+ "SPEC.md",
41
+ "LICENSE",
42
+ "!src/**/__tests__/**",
43
+ "!src/**/__stubs__/**",
44
+ "!src/**/*.test.ts",
45
+ "!src/**/*.test.tsx",
46
+ "!src/**/*.spec.ts",
47
+ "!src/**/*.spec.tsx",
48
+ "!dist/**/__tests__/**",
49
+ "!dist/**/__stubs__/**",
50
+ "!dist/**/*.test.js",
51
+ "!dist/**/*.test.js.map",
52
+ "!dist/**/*.test.d.ts",
53
+ "!dist/**/*.test.d.ts.map",
54
+ "!dist/**/*.spec.js",
55
+ "!dist/**/*.spec.js.map",
56
+ "!dist/**/*.spec.d.ts",
57
+ "!dist/**/*.spec.d.ts.map"
58
+ ],
59
+ "publishConfig": {
60
+ "registry": "https://registry.npmjs.org",
61
+ "access": "public"
62
+ },
63
+ "vibes": {
64
+ "visibility": "public-framework"
65
+ }
66
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Bun Runtime Environment Implementation
3
+ *
4
+ * Captures environment at construction and provides Bun-specific
5
+ * implementations for scope, env access, secrets, and module loading.
6
+ */
7
+
8
+ import type {
9
+ RuntimeDiscoveryConfig,
10
+ RuntimeDiscoveryProvider,
11
+ RuntimeEnvironmentImplementation,
12
+ RuntimeLoaderImplementation
13
+ } from '@vibesdotdev/runtime';
14
+ import type { RuntimeScope, TenancyConfig } from '@vibesdotdev/runtime';
15
+ import { BunLoader } from './bun-loader.impl';
16
+ import { bunRuntimePath } from './services/runtime-path.impl';
17
+
18
+ export interface BunEnvironmentOptions {
19
+ /** Surface override (default: detected from env) */
20
+ surface?: string;
21
+ /** Hardware override (default: detected from env) */
22
+ hardware?: string;
23
+ /** Purpose override (default: detected from env) */
24
+ purpose?: string;
25
+ /** Tenancy configuration */
26
+ tenancy?: TenancyConfig;
27
+ /** Dev mode flag */
28
+ dev?: boolean;
29
+ /** Whether daemon endpoints/process features should be enabled for this runtime */
30
+ daemon?: boolean;
31
+ /** Optional discovery defaults for this environment capability */
32
+ discoveryDefaults?: RuntimeDiscoveryConfig;
33
+ /** Optional discovery provider override for this environment capability */
34
+ discoveryProvider?: RuntimeDiscoveryProvider;
35
+ }
36
+
37
+ export class BunEnvironment implements RuntimeEnvironmentImplementation {
38
+ readonly id: string;
39
+ readonly description: string;
40
+ readonly capabilities: Record<string, boolean>;
41
+ readonly scope: RuntimeScope;
42
+ readonly loader: RuntimeLoaderImplementation;
43
+ readonly discovery: {
44
+ readonly defaults: RuntimeDiscoveryConfig;
45
+ readonly provider?: RuntimeDiscoveryProvider;
46
+ };
47
+
48
+ private _envSnapshot: Record<string, string> | undefined;
49
+
50
+ constructor(options: BunEnvironmentOptions = {}) {
51
+ this.id = 'bun';
52
+ this.description = 'Bun runtime environment';
53
+
54
+ // Capture env snapshot FIRST - required for any _detect* methods below
55
+ this._envSnapshot = this._captureEnv();
56
+
57
+ const daemon = this._detectDaemonCapability(options);
58
+ this.capabilities = {
59
+ esm: true,
60
+ typescript: true,
61
+ nativeLoader: true,
62
+ secrets: false,
63
+ daemon
64
+ };
65
+
66
+ // Build scope from captured env + options
67
+ this.scope = this._buildScope(options);
68
+
69
+ // Create Bun-specific loader
70
+ this.loader = new BunLoader();
71
+ this.discovery = {
72
+ defaults: options.discoveryDefaults ?? {
73
+ basePaths: [process.cwd()],
74
+ skipPlugins: false,
75
+ respectGitignore: true
76
+ },
77
+ ...(options.discoveryProvider ? { provider: options.discoveryProvider } : {})
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Capture environment variables at construction time.
83
+ * This is the ONLY place process.env is accessed.
84
+ */
85
+ private _captureEnv(): Record<string, string> {
86
+ const captured: Record<string, string> = {};
87
+ if (typeof process !== 'undefined' && process.env) {
88
+ for (const [key, value] of Object.entries(process.env)) {
89
+ if (typeof value === 'string') {
90
+ captured[key] = value;
91
+ }
92
+ }
93
+ }
94
+ return captured;
95
+ }
96
+
97
+ /**
98
+ * Return the env snapshot, healing it if a cross-module or partially
99
+ * initialized BunEnvironment instance was attached to the runtime.
100
+ * This makes `dev mcp serve` (and other heavy CLI commands) robust when
101
+ * reusing the runtime created by createVibesCliApp.
102
+ */
103
+ private _getSnapshot(): Record<string, string> {
104
+ if (!this._envSnapshot) {
105
+ this._envSnapshot = this._captureEnv();
106
+ }
107
+ return this._envSnapshot;
108
+ }
109
+
110
+ /**
111
+ * Build runtime scope from captured environment.
112
+ */
113
+ private _buildScope(options: BunEnvironmentOptions): RuntimeScope {
114
+ const surface = options.surface ?? this._detectSurface();
115
+ const hardware = options.hardware ?? this._detectHardware();
116
+ const purpose = options.purpose ?? this._detectPurpose();
117
+ const snap = this._getSnapshot();
118
+ const dev = options.dev ?? (snap.NODE_ENV !== 'production');
119
+ const daemon = this._detectDaemonCapability(options, surface, hardware);
120
+
121
+ return {
122
+ surface,
123
+ hardware,
124
+ purpose,
125
+ capabilities: {
126
+ daemon
127
+ },
128
+ qualifiers: {
129
+ dev,
130
+ ...(options.tenancy?.teamId && { teamId: options.tenancy.teamId })
131
+ },
132
+ tenancy: options.tenancy
133
+ };
134
+ }
135
+
136
+ private _detectDaemonCapability(
137
+ options: BunEnvironmentOptions,
138
+ surface?: string,
139
+ hardware?: string
140
+ ): boolean {
141
+ if (typeof options.daemon === 'boolean') return options.daemon;
142
+ const snap = this._getSnapshot();
143
+ if (snap.VIBES_RUNTIME_DAEMON === '1' || snap.VIBES_DAEMON === '1') {
144
+ return true;
145
+ }
146
+ const resolvedSurface = surface ?? options.surface ?? this._detectSurface();
147
+ const resolvedHardware = hardware ?? options.hardware ?? this._detectHardware();
148
+ return resolvedSurface === 'daemon' || resolvedHardware === 'consumer';
149
+ }
150
+
151
+ private _detectSurface(): string {
152
+ return this._getSnapshot().VIBES_SURFACE ?? 'ssr';
153
+ }
154
+
155
+ private _detectHardware(): string {
156
+ return this._getSnapshot().VIBES_HARDWARE ?? 'consumer';
157
+ }
158
+
159
+ private _detectPurpose(): string {
160
+ return this._getSnapshot().VIBES_PURPOSE ?? 'direct';
161
+ }
162
+
163
+ /**
164
+ * Get environment variable from captured snapshot.
165
+ * No direct process.env access.
166
+ */
167
+ getEnv(key: string): string | undefined {
168
+ return this._getSnapshot()[key];
169
+ }
170
+
171
+ /**
172
+ * Get secret value.
173
+ * Bun doesn't have a secrets API, so this falls back to env.
174
+ * Cloudflare environment will use CF Secrets Store.
175
+ */
176
+ async getSecret(key: string): Promise<string | undefined> {
177
+ return this._getSnapshot()[key];
178
+ }
179
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Bun Loader Implementation
3
+ *
4
+ * Uses Bun.import() for module loading with native ESM support.
5
+ */
6
+
7
+ import type {
8
+ RuntimeLoaderImplementation,
9
+ RuntimeLoaderModuleMaps,
10
+ RuntimeLoaderOptions,
11
+ RuntimeLoaderKindDiscoveryOptions
12
+ } from '@vibesdotdev/runtime';
13
+ import type { RuntimeDescriptor } from '@vibesdotdev/runtime';
14
+ import { Glob } from 'bun';
15
+ import { join, basename } from 'path';
16
+
17
+ export class BunLoader implements RuntimeLoaderImplementation {
18
+ async loadModules(options: RuntimeLoaderOptions): Promise<RuntimeLoaderModuleMaps> {
19
+ const { scope, discovery } = options;
20
+ const basePaths = discovery?.basePaths ?? [process.cwd()];
21
+ const patterns = discovery?.patterns ?? {
22
+ plugin: '**/*.plugin.ts',
23
+ descriptor: '**/*.descriptor*.ts',
24
+ implementation: '**/*.impl*.ts*'
25
+ };
26
+
27
+ const result: RuntimeLoaderModuleMaps = {
28
+ plugins: {},
29
+ descriptors: {},
30
+ implementations: {}
31
+ };
32
+
33
+ for (const basePath of basePaths) {
34
+ // Load plugins
35
+ const pluginGlob = new Glob(patterns.plugin ?? '**/*.plugin.ts');
36
+ const pluginFiles: string[] = [];
37
+ for await (const file of pluginGlob.scan({ cwd: basePath, absolute: true } as never)) {
38
+ if (file) pluginFiles.push(file);
39
+ }
40
+ for (const file of pluginFiles) {
41
+ const fullPath = join(basePath, file);
42
+ result.plugins[fullPath] = () => import(fullPath);
43
+ }
44
+
45
+ // Load descriptors
46
+ const descriptorGlob = new Glob(patterns.descriptor ?? '**/*.descriptor*.ts');
47
+ const descriptorFiles: string[] = [];
48
+ for await (const file of descriptorGlob.scan({ cwd: basePath, absolute: true } as never)) {
49
+ if (file) descriptorFiles.push(file);
50
+ }
51
+ for (const file of descriptorFiles) {
52
+ const fullPath = join(basePath, file);
53
+ result.descriptors[fullPath] = () => import(fullPath);
54
+ }
55
+
56
+ // Load implementations
57
+ const implGlob = new Glob(patterns.implementation ?? '**/*.impl*.ts*');
58
+ const implFiles: string[] = [];
59
+ for await (const file of implGlob.scan({ cwd: basePath, absolute: true } as never)) {
60
+ if (file) implFiles.push(file);
61
+ }
62
+ for (const file of implFiles) {
63
+ const fullPath = join(basePath, file);
64
+ result.implementations[fullPath] = () => import(fullPath);
65
+ }
66
+ }
67
+
68
+ return result;
69
+ }
70
+
71
+ async discoverKindDescriptors(
72
+ options: RuntimeLoaderKindDiscoveryOptions
73
+ ): Promise<RuntimeDescriptor[]> {
74
+ const { scope, discovery, kindPatterns } = options;
75
+ const basePaths = discovery?.basePaths ?? [process.cwd()];
76
+ const descriptors: RuntimeDescriptor[] = [];
77
+
78
+ for (const kindPattern of kindPatterns) {
79
+ const glob = new Glob(kindPattern.pattern);
80
+ for (const basePath of basePaths) {
81
+ for await (const file of glob.scan({ cwd: basePath, absolute: true } as never)) {
82
+ const fullPath = join(basePath, file!);
83
+ try {
84
+ const module = await import(fullPath);
85
+ if (module.default) {
86
+ const descriptor = module.default as RuntimeDescriptor;
87
+ if (descriptor.kind === kindPattern.kindId) {
88
+ descriptors.push(descriptor);
89
+ }
90
+ }
91
+ } catch (error) {
92
+ if (discovery?.debug) {
93
+ console.warn(`Failed to load descriptor ${fullPath}:`, error);
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ return descriptors;
101
+ }
102
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Plugin Asset Discovery
3
+ *
4
+ * Scans the filesystem for descriptor and implementation files,
5
+ * registers descriptors eagerly, and implementations as lazy loaders.
6
+ * Consumer/Bun-only helper for plugin-local asset discovery.
7
+ */
8
+
9
+ import { fileURLToPath } from 'node:url';
10
+ import { dirname, resolve } from 'node:path';
11
+ import type { RuntimeRef } from '@vibesdotdev/runtime/factory/plugin';
12
+ import { extractDescriptorStem, parseImplFilename } from '@vibesdotdev/runtime/discovery/scope-parser';
13
+
14
+ type FsGlobModule = {
15
+ globSync?: (pattern: string, options: { cwd: string }) => string[];
16
+ };
17
+
18
+ type ProcessWithBuiltinModule = typeof process & {
19
+ getBuiltinModule?: (id: 'node:fs') => FsGlobModule | undefined;
20
+ };
21
+
22
+ export async function discoverAssets(
23
+ runtime: RuntimeRef,
24
+ baseUrl: string | undefined,
25
+ kindPatterns: Record<string, string | string[]>
26
+ ): Promise<{ descriptors: number; loaders: number }> {
27
+ // Filesystem asset discovery requires a real `file://` base URL. In workerd
28
+ // and other non-filesystem runtimes `import.meta.url` is undefined, so
29
+ // the caller can't give us a baseUrl. Short-circuit instead of throwing.
30
+ if (typeof baseUrl !== 'string' || baseUrl.length === 0) {
31
+ return { descriptors: 0, loaders: 0 };
32
+ }
33
+ if (baseUrl.includes('/$bunfs/') || baseUrl.includes('/$bun/')) {
34
+ return { descriptors: 0, loaders: 0 };
35
+ }
36
+
37
+ const baseDir = dirname(fileURLToPath(baseUrl));
38
+ let descriptors = 0;
39
+ let loaders = 0;
40
+
41
+ for (const [kind, rawPatterns] of Object.entries(kindPatterns)) {
42
+ const patterns = Array.isArray(rawPatterns) ? rawPatterns : [rawPatterns];
43
+ const files = scanPatterns(patterns, baseDir);
44
+
45
+ const descriptorFiles = files.filter((f) => f.includes('.descriptor.'));
46
+ const implFiles = files.filter((f) => f.includes('.impl.'));
47
+
48
+ for (const file of descriptorFiles) {
49
+ try {
50
+ const absPath = resolve(baseDir, file);
51
+ const mod = await import(absPath);
52
+ const desc = mod.default;
53
+ if (!desc || !desc.id) continue;
54
+
55
+ const descKind = desc.kind ?? kind;
56
+ runtime.registerDescriptor(descKind, desc);
57
+ descriptors++;
58
+
59
+ const stem = extractDescriptorStem(file)?.stem;
60
+ if (!stem) continue;
61
+ const matchingImpl = implFiles.find((candidate) => {
62
+ try {
63
+ return parseImplFilename(candidate).stem === stem;
64
+ } catch {
65
+ return false;
66
+ }
67
+ });
68
+ if (!matchingImpl) continue;
69
+
70
+ const implPath = resolve(baseDir, matchingImpl);
71
+ runtime.registerLoader(descKind, desc.id, async () => {
72
+ const exported = (await import(implPath)).default;
73
+ if (
74
+ exported &&
75
+ typeof exported === 'object' &&
76
+ 'impl' in exported &&
77
+ 'meta' in exported
78
+ ) {
79
+ return exported;
80
+ }
81
+ return {
82
+ meta: {
83
+ id: desc.id,
84
+ kind: descKind,
85
+ priority: 0
86
+ },
87
+ impl: exported
88
+ };
89
+ });
90
+ loaders++;
91
+ } catch {
92
+ // Descriptor may have unresolvable imports in this environment — skip
93
+ }
94
+ }
95
+ }
96
+
97
+ return { descriptors, loaders };
98
+ }
99
+
100
+ function scanPatterns(patterns: string[], cwd: string): string[] {
101
+ const results: string[] = [];
102
+ for (const pattern of patterns) {
103
+ for (const expanded of expandScopedPattern(pattern)) {
104
+ results.push(...scanGlob(expanded, cwd));
105
+ }
106
+ }
107
+ return results;
108
+ }
109
+
110
+ function expandScopedPattern(pattern: string): string[] {
111
+ if (!pattern.includes('{descriptor,impl}')) return [pattern];
112
+ return [
113
+ pattern.replace('{descriptor,impl}', 'descriptor*'),
114
+ pattern.replace('{descriptor,impl}', 'impl*')
115
+ ];
116
+ }
117
+
118
+ function scanGlob(pattern: string, cwd: string): string[] {
119
+ if (typeof globalThis !== 'undefined' && 'Bun' in globalThis) {
120
+ const BunGlob = (globalThis as Record<string, unknown>).Bun as {
121
+ Glob: new (p: string) => {
122
+ scanSync: (o: { cwd: string; onlyFiles: boolean }) => Iterable<string>;
123
+ };
124
+ };
125
+ return [...new BunGlob.Glob(pattern).scanSync({ cwd, onlyFiles: true })];
126
+ }
127
+
128
+ const builtinProcess = process as ProcessWithBuiltinModule;
129
+ const fsModule = builtinProcess.getBuiltinModule?.('node:fs');
130
+ if (typeof fsModule?.globSync === 'function') {
131
+ return fsModule.globSync(pattern, { cwd });
132
+ }
133
+
134
+ return [];
135
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Minimal RuntimeHost interface (duplicated here to avoid export map changes in runtime core).
3
+ * Matches the contract in packages/runtime/src/env/host.contracts.ts.
4
+ */
5
+ interface RuntimeHost {
6
+ kind: 'browser' | 'node' | 'cloud';
7
+ cwd(): string | null;
8
+ readTextFile(path: string): Promise<string>;
9
+ }
10
+
11
+ import { readFile } from 'node:fs/promises';
12
+ import { resolve } from 'node:path';
13
+
14
+ export const host: RuntimeHost = {
15
+ kind: 'node',
16
+ cwd(): string {
17
+ return process.cwd();
18
+ },
19
+ async readTextFile(path: string): Promise<string> {
20
+ const absolute = resolve(process.cwd(), path);
21
+ return await readFile(absolute, 'utf8');
22
+ }
23
+ };