@dexto/image-bundler 1.5.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,331 @@
1
+ /**
2
+ * Code generator for base images
3
+ *
4
+ * Transforms image definitions into importable packages with:
5
+ * - Side-effect provider registration
6
+ * - createAgent() factory
7
+ * - Utility exports
8
+ * - Metadata exports
9
+ */
10
+
11
+ import type { ImageDefinition } from '@dexto/core';
12
+ import type { GeneratedCode } from './types.js';
13
+ import type { DiscoveredProviders } from './bundler.js';
14
+
15
+ /**
16
+ * Generate JavaScript entry point for an image
17
+ */
18
+ export function generateEntryPoint(
19
+ definition: ImageDefinition,
20
+ coreVersion: string,
21
+ discoveredProviders?: DiscoveredProviders
22
+ ): GeneratedCode {
23
+ // Generate imports section
24
+ const imports = generateImports(definition, discoveredProviders);
25
+
26
+ // Generate provider registration section
27
+ const registrations = generateProviderRegistrations(definition, discoveredProviders);
28
+
29
+ // Generate factory function
30
+ const factory = generateFactory();
31
+
32
+ // Generate utility exports
33
+ const utilityExports = generateUtilityExports(definition);
34
+
35
+ // Generate metadata export
36
+ const metadata = generateMetadata(definition, coreVersion);
37
+
38
+ // Combine all sections
39
+ const js = `// AUTO-GENERATED by @dexto/bundler
40
+ // Do not edit this file directly. Edit dexto.image.ts instead.
41
+
42
+ ${imports}
43
+
44
+ ${registrations}
45
+
46
+ ${factory}
47
+
48
+ ${utilityExports}
49
+
50
+ ${metadata}
51
+ `;
52
+
53
+ // Generate TypeScript definitions
54
+ const dts = generateTypeDefinitions(definition);
55
+
56
+ return { js, dts };
57
+ }
58
+
59
+ function generateImports(
60
+ definition: ImageDefinition,
61
+ discoveredProviders?: DiscoveredProviders
62
+ ): string {
63
+ const imports: string[] = [];
64
+
65
+ // Import base image first (if extending) - triggers side-effect provider registration
66
+ if (definition.extends) {
67
+ imports.push(`// Import base image for provider registration (side effect)`);
68
+ imports.push(`import '${definition.extends}';`);
69
+ imports.push(``);
70
+ }
71
+
72
+ // Core imports
73
+ imports.push(`import { DextoAgent } from '@dexto/core';`);
74
+
75
+ // Always import all registries since we re-export them in generateFactory()
76
+ // This ensures the re-exports don't reference unimported identifiers
77
+ imports.push(
78
+ `import { customToolRegistry, pluginRegistry, compactionRegistry, blobStoreRegistry } from '@dexto/core';`
79
+ );
80
+
81
+ // Import discovered providers
82
+ if (discoveredProviders) {
83
+ const categories = [
84
+ { key: 'blobStore', label: 'Blob Storage' },
85
+ { key: 'customTools', label: 'Custom Tools' },
86
+ { key: 'compaction', label: 'Compaction' },
87
+ { key: 'plugins', label: 'Plugins' },
88
+ ] as const;
89
+
90
+ for (const { key, label } of categories) {
91
+ const providers = discoveredProviders[key];
92
+ if (providers.length > 0) {
93
+ imports.push(``);
94
+ imports.push(`// ${label} providers (auto-discovered)`);
95
+ providers.forEach((path, index) => {
96
+ const varName = `${key}Provider${index}`;
97
+ imports.push(`import * as ${varName} from '${path}';`);
98
+ });
99
+ }
100
+ }
101
+ }
102
+
103
+ return imports.join('\n');
104
+ }
105
+
106
+ function generateProviderRegistrations(
107
+ definition: ImageDefinition,
108
+ discoveredProviders?: DiscoveredProviders
109
+ ): string {
110
+ const registrations: string[] = [];
111
+
112
+ if (definition.extends) {
113
+ registrations.push(
114
+ `// Base image providers already registered via import of '${definition.extends}'`
115
+ );
116
+ registrations.push('');
117
+ }
118
+
119
+ registrations.push('// SIDE EFFECT: Register providers on import');
120
+ registrations.push('');
121
+
122
+ // Auto-register discovered providers
123
+ if (discoveredProviders) {
124
+ const categoryMap = [
125
+ { key: 'blobStore', registry: 'blobStoreRegistry', label: 'Blob Storage' },
126
+ { key: 'customTools', registry: 'customToolRegistry', label: 'Custom Tools' },
127
+ { key: 'compaction', registry: 'compactionRegistry', label: 'Compaction' },
128
+ { key: 'plugins', registry: 'pluginRegistry', label: 'Plugins' },
129
+ ] as const;
130
+
131
+ for (const { key, registry, label } of categoryMap) {
132
+ const providers = discoveredProviders[key];
133
+ if (providers.length === 0) continue;
134
+
135
+ registrations.push(`// Auto-register ${label} providers`);
136
+ providers.forEach((path, index) => {
137
+ const varName = `${key}Provider${index}`;
138
+ registrations.push(`// From ${path}`);
139
+ registrations.push(`for (const exported of Object.values(${varName})) {`);
140
+ registrations.push(
141
+ ` if (exported && typeof exported === 'object' && 'type' in exported && 'create' in exported) {`
142
+ );
143
+ registrations.push(` try {`);
144
+ registrations.push(` ${registry}.register(exported);`);
145
+ registrations.push(
146
+ ` console.log(\`✓ Registered ${key}: \${exported.type}\`);`
147
+ );
148
+ registrations.push(` } catch (err) {`);
149
+ registrations.push(` // Ignore duplicate registration errors`);
150
+ registrations.push(
151
+ ` if (!err.message?.includes('already registered')) throw err;`
152
+ );
153
+ registrations.push(` }`);
154
+ registrations.push(` }`);
155
+ registrations.push(`}`);
156
+ });
157
+ registrations.push('');
158
+ }
159
+ }
160
+
161
+ // Handle manual registration functions (backwards compatibility)
162
+ for (const [category, config] of Object.entries(definition.providers)) {
163
+ if (!config) continue;
164
+
165
+ if (config.register) {
166
+ // Async registration function with duplicate prevention
167
+ registrations.push(`// Register ${category} via custom function (from dexto.image.ts)`);
168
+ registrations.push(`await (async () => {`);
169
+ registrations.push(` try {`);
170
+ registrations.push(
171
+ ` ${config.register
172
+ .toString()
173
+ .replace(/^async\s*\(\)\s*=>\s*{/, '')
174
+ .replace(/}$/, '')}`
175
+ );
176
+ registrations.push(` } catch (err) {`);
177
+ registrations.push(` // Ignore duplicate registration errors`);
178
+ registrations.push(` if (!err.message?.includes('already registered')) {`);
179
+ registrations.push(` throw err;`);
180
+ registrations.push(` }`);
181
+ registrations.push(` }`);
182
+ registrations.push(`})();`);
183
+ registrations.push('');
184
+ }
185
+ }
186
+
187
+ return registrations.join('\n');
188
+ }
189
+
190
+ function generateFactory(): string {
191
+ return `/**
192
+ * Create a Dexto agent using this image's registered providers.
193
+ *
194
+ * @param config - Agent configuration
195
+ * @param configPath - Optional path to config file
196
+ * @returns DextoAgent instance with providers already registered
197
+ */
198
+ export function createAgent(config, configPath) {
199
+ return new DextoAgent(config, configPath);
200
+ }
201
+
202
+ /**
203
+ * Re-export registries for runtime customization.
204
+ * This allows apps to add custom providers without depending on @dexto/core directly.
205
+ */
206
+ export {
207
+ customToolRegistry,
208
+ pluginRegistry,
209
+ compactionRegistry,
210
+ blobStoreRegistry,
211
+ } from '@dexto/core';`;
212
+ }
213
+
214
+ function generateUtilityExports(definition: ImageDefinition): string {
215
+ const sections: string[] = [];
216
+
217
+ // Generate wildcard utility exports
218
+ if (definition.utils && Object.keys(definition.utils).length > 0) {
219
+ sections.push('// Utility exports');
220
+ for (const [name, path] of Object.entries(definition.utils)) {
221
+ sections.push(`export * from '${path}';`);
222
+ }
223
+ }
224
+
225
+ // Generate selective named exports (filter out type-only exports for runtime JS)
226
+ if (definition.exports && Object.keys(definition.exports).length > 0) {
227
+ if (sections.length > 0) sections.push('');
228
+ sections.push('// Selective package re-exports');
229
+ for (const [packageName, exports] of Object.entries(definition.exports)) {
230
+ // Check for wildcard re-export
231
+ if (exports.length === 1 && exports[0] === '*') {
232
+ sections.push(`export * from '${packageName}';`);
233
+ continue;
234
+ }
235
+
236
+ // Filter out type-only exports (those starting with 'type ')
237
+ const runtimeExports = exports.filter((exp) => !exp.startsWith('type '));
238
+ if (runtimeExports.length > 0) {
239
+ sections.push(`export {`);
240
+ sections.push(` ${runtimeExports.join(',\n ')}`);
241
+ sections.push(`} from '${packageName}';`);
242
+ }
243
+ }
244
+ }
245
+
246
+ if (sections.length === 0) {
247
+ return '// No utilities or exports defined for this image';
248
+ }
249
+
250
+ return sections.join('\n');
251
+ }
252
+
253
+ function generateMetadata(definition: ImageDefinition, coreVersion: string): string {
254
+ const metadata: Record<string, any> = {
255
+ name: definition.name,
256
+ version: definition.version,
257
+ description: definition.description,
258
+ target: definition.target || 'custom',
259
+ constraints: definition.constraints || [],
260
+ builtAt: new Date().toISOString(),
261
+ coreVersion: coreVersion,
262
+ };
263
+
264
+ // Include extends information if present
265
+ if (definition.extends) {
266
+ metadata.extends = definition.extends;
267
+ }
268
+
269
+ return `/**
270
+ * Image metadata
271
+ * Generated at build time
272
+ */
273
+ export const imageMetadata = ${JSON.stringify(metadata, null, 4)};`;
274
+ }
275
+
276
+ function generateTypeDefinitions(definition: ImageDefinition): string {
277
+ const sections: string[] = [];
278
+
279
+ // Wildcard utility exports
280
+ if (definition.utils && Object.keys(definition.utils).length > 0) {
281
+ sections.push('// Utility re-exports');
282
+ for (const path of Object.values(definition.utils)) {
283
+ sections.push(`export * from '${path}';`);
284
+ }
285
+ }
286
+
287
+ // Selective named exports
288
+ if (definition.exports && Object.keys(definition.exports).length > 0) {
289
+ if (sections.length > 0) sections.push('');
290
+ sections.push('// Selective package re-exports');
291
+ for (const [packageName, exports] of Object.entries(definition.exports)) {
292
+ // Check for wildcard re-export
293
+ if (exports.length === 1 && exports[0] === '*') {
294
+ sections.push(`export * from '${packageName}';`);
295
+ continue;
296
+ }
297
+
298
+ sections.push(`export {`);
299
+ sections.push(` ${exports.join(',\n ')}`);
300
+ sections.push(`} from '${packageName}';`);
301
+ }
302
+ }
303
+
304
+ const utilityExports = sections.length > 0 ? '\n\n' + sections.join('\n') : '';
305
+
306
+ return `// AUTO-GENERATED TypeScript definitions
307
+ // Do not edit this file directly
308
+
309
+ import type { DextoAgent, AgentConfig, ImageMetadata } from '@dexto/core';
310
+
311
+ /**
312
+ * Create a Dexto agent using this image's registered providers.
313
+ */
314
+ export declare function createAgent(config: AgentConfig, configPath?: string): DextoAgent;
315
+
316
+ /**
317
+ * Image metadata
318
+ */
319
+ export declare const imageMetadata: ImageMetadata;
320
+
321
+ /**
322
+ * Re-exported registries for runtime customization
323
+ */
324
+ export {
325
+ customToolRegistry,
326
+ pluginRegistry,
327
+ compactionRegistry,
328
+ blobStoreRegistry,
329
+ } from '@dexto/core';${utilityExports}
330
+ `;
331
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @dexto/bundler
3
+ *
4
+ * Bundles Dexto base images from dexto.image.ts definitions
5
+ * into importable packages with side-effect provider registration.
6
+ */
7
+
8
+ export { bundle } from './bundler.js';
9
+ export type { BundleOptions, BundleResult, GeneratedCode } from './types.js';
package/src/types.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { ImageDefinition, ImageMetadata } from '@dexto/core';
2
+
3
+ export interface BundleOptions {
4
+ /** Path to dexto.image.ts file */
5
+ imagePath: string;
6
+ /** Output directory for built image */
7
+ outDir: string;
8
+ /** Whether to generate source maps */
9
+ sourcemap?: boolean;
10
+ /** Whether to minify output */
11
+ minify?: boolean;
12
+ }
13
+
14
+ export interface BundleResult {
15
+ /** Path to generated entry file */
16
+ entryFile: string;
17
+ /** Path to generated types file */
18
+ typesFile: string;
19
+ /** Image metadata */
20
+ metadata: ImageMetadata;
21
+ /** Warnings encountered during build */
22
+ warnings: string[];
23
+ }
24
+
25
+ export interface GeneratedCode {
26
+ /** Generated JavaScript code */
27
+ js: string;
28
+ /** Generated TypeScript definitions */
29
+ dts: string;
30
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "allowImportingTsExtensions": true,
15
+ "noEmit": true
16
+ },
17
+ "include": ["src/**/*.ts"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts', 'src/cli.ts'],
5
+ format: ['esm'],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ splitting: false,
10
+ shims: true,
11
+ external: ['typescript', '@dexto/core', 'picocolors', 'commander'],
12
+ noExternal: [],
13
+ });