@dexto/image-bundler 1.5.8 → 1.6.1
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/.turbo/turbo-build.log +9 -10
- package/CHANGELOG.md +66 -0
- package/README.md +45 -0
- package/dist/cli.js +411 -221
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +66 -2
- package/dist/index.js +407 -210
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/bundler.ts +278 -92
- package/src/cli.ts +4 -12
- package/src/generator.ts +223 -204
- package/src/image-definition/types.ts +72 -0
- package/src/image-definition/validate-image-definition.ts +70 -0
- package/src/index.ts +2 -1
- package/src/types.ts +1 -1
- package/test/bundle.integration.test.ts +253 -0
- package/tsconfig.json +5 -1
- package/tsconfig.tsbuildinfo +1 -0
- package/tsup.config.ts +7 -1
- package/dist/cli.d.ts +0 -1
package/src/generator.ts
CHANGED
|
@@ -1,53 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Code generator for
|
|
2
|
+
* Code generator for images
|
|
3
3
|
*
|
|
4
|
-
* Transforms image definitions into importable packages with:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* - Utility exports
|
|
8
|
-
* - Metadata exports
|
|
4
|
+
* Transforms image definitions + convention folders into importable packages with:
|
|
5
|
+
* - A typed `DextoImage` default export (no side effects)
|
|
6
|
+
* - Optional utility re-exports
|
|
9
7
|
*/
|
|
10
8
|
|
|
11
|
-
import type { ImageDefinition } from '
|
|
9
|
+
import type { ImageDefinition } from './image-definition/types.js';
|
|
12
10
|
import type { GeneratedCode } from './types.js';
|
|
13
|
-
import type {
|
|
11
|
+
import type { DiscoveredFactories } from './bundler.js';
|
|
14
12
|
|
|
15
13
|
/**
|
|
16
14
|
* Generate JavaScript entry point for an image
|
|
17
15
|
*/
|
|
18
16
|
export function generateEntryPoint(
|
|
19
17
|
definition: ImageDefinition,
|
|
20
|
-
|
|
21
|
-
discoveredProviders?: DiscoveredProviders
|
|
18
|
+
discoveredFactories: DiscoveredFactories
|
|
22
19
|
): GeneratedCode {
|
|
23
|
-
|
|
24
|
-
const
|
|
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
|
|
20
|
+
const imports = generateImports(definition, discoveredFactories);
|
|
21
|
+
const helpers = definition.extends ? generateHelpers() : '';
|
|
22
|
+
const imageModule = generateImageModule(definition, discoveredFactories);
|
|
33
23
|
const utilityExports = generateUtilityExports(definition);
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
const metadata = generateMetadata(definition, coreVersion);
|
|
37
|
-
|
|
38
|
-
// Combine all sections
|
|
39
|
-
const js = `// AUTO-GENERATED by @dexto/bundler
|
|
25
|
+
const js = `// AUTO-GENERATED by @dexto/image-bundler
|
|
40
26
|
// Do not edit this file directly. Edit dexto.image.ts instead.
|
|
41
27
|
|
|
42
28
|
${imports}
|
|
43
29
|
|
|
44
|
-
${
|
|
30
|
+
${helpers}
|
|
45
31
|
|
|
46
|
-
${
|
|
32
|
+
${imageModule}
|
|
47
33
|
|
|
48
34
|
${utilityExports}
|
|
49
|
-
|
|
50
|
-
${metadata}
|
|
51
35
|
`;
|
|
52
36
|
|
|
53
37
|
// Generate TypeScript definitions
|
|
@@ -56,159 +40,111 @@ ${metadata}
|
|
|
56
40
|
return { js, dts };
|
|
57
41
|
}
|
|
58
42
|
|
|
43
|
+
function sanitizeIdentifier(value: string): string {
|
|
44
|
+
const sanitized = value.replace(/[^a-zA-Z0-9_$]/g, '_');
|
|
45
|
+
if (/^[a-zA-Z_$]/.test(sanitized)) {
|
|
46
|
+
return sanitized;
|
|
47
|
+
}
|
|
48
|
+
return `_${sanitized}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function toFactoryImportSymbol(prefix: string, type: string): string {
|
|
52
|
+
return sanitizeIdentifier(`${prefix}_${type}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
59
55
|
function generateImports(
|
|
60
56
|
definition: ImageDefinition,
|
|
61
|
-
|
|
57
|
+
discoveredFactories: DiscoveredFactories
|
|
62
58
|
): string {
|
|
63
59
|
const imports: string[] = [];
|
|
64
60
|
|
|
65
|
-
// Import base image first (if extending) - triggers side-effect provider registration
|
|
66
61
|
if (definition.extends) {
|
|
67
|
-
imports.push(
|
|
68
|
-
imports.push(`import '${definition.extends}';`);
|
|
69
|
-
imports.push(``);
|
|
62
|
+
imports.push(`import baseImage from '${definition.extends}';`);
|
|
70
63
|
}
|
|
71
64
|
|
|
72
|
-
|
|
73
|
-
imports.push(`import { DextoAgent } from '@dexto/core';`);
|
|
65
|
+
imports.push(`import { defaultLoggerFactory } from '@dexto/core';`);
|
|
74
66
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
const toolProviders = [...discoveredFactories.tools].sort((a, b) =>
|
|
68
|
+
a.type.localeCompare(b.type)
|
|
69
|
+
);
|
|
70
|
+
const hookProviders = [...discoveredFactories.hooks].sort((a, b) =>
|
|
71
|
+
a.type.localeCompare(b.type)
|
|
72
|
+
);
|
|
73
|
+
const compactionProviders = [...discoveredFactories.compaction].sort((a, b) =>
|
|
74
|
+
a.type.localeCompare(b.type)
|
|
75
|
+
);
|
|
76
|
+
const blobProviders = [...discoveredFactories.storage.blob].sort((a, b) =>
|
|
77
|
+
a.type.localeCompare(b.type)
|
|
78
|
+
);
|
|
79
|
+
const databaseProviders = [...discoveredFactories.storage.database].sort((a, b) =>
|
|
80
|
+
a.type.localeCompare(b.type)
|
|
81
|
+
);
|
|
82
|
+
const cacheProviders = [...discoveredFactories.storage.cache].sort((a, b) =>
|
|
83
|
+
a.type.localeCompare(b.type)
|
|
79
84
|
);
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
}
|
|
86
|
+
if (
|
|
87
|
+
toolProviders.length > 0 ||
|
|
88
|
+
hookProviders.length > 0 ||
|
|
89
|
+
compactionProviders.length > 0 ||
|
|
90
|
+
blobProviders.length > 0 ||
|
|
91
|
+
databaseProviders.length > 0 ||
|
|
92
|
+
cacheProviders.length > 0
|
|
93
|
+
) {
|
|
94
|
+
imports.push('');
|
|
95
|
+
imports.push('// Factories (convention folders; each must `export const factory = ...`)');
|
|
101
96
|
}
|
|
102
97
|
|
|
103
|
-
|
|
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('');
|
|
98
|
+
for (const entry of toolProviders) {
|
|
99
|
+
const symbol = toFactoryImportSymbol('tools', entry.type);
|
|
100
|
+
imports.push(`import { factory as ${symbol} } from '${entry.importPath}';`);
|
|
117
101
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
}
|
|
102
|
+
for (const entry of hookProviders) {
|
|
103
|
+
const symbol = toFactoryImportSymbol('hooks', entry.type);
|
|
104
|
+
imports.push(`import { factory as ${symbol} } from '${entry.importPath}';`);
|
|
159
105
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
}
|
|
106
|
+
for (const entry of compactionProviders) {
|
|
107
|
+
const symbol = toFactoryImportSymbol('compaction', entry.type);
|
|
108
|
+
imports.push(`import { factory as ${symbol} } from '${entry.importPath}';`);
|
|
109
|
+
}
|
|
110
|
+
for (const entry of blobProviders) {
|
|
111
|
+
const symbol = toFactoryImportSymbol('storage_blob', entry.type);
|
|
112
|
+
imports.push(`import { factory as ${symbol} } from '${entry.importPath}';`);
|
|
113
|
+
}
|
|
114
|
+
for (const entry of databaseProviders) {
|
|
115
|
+
const symbol = toFactoryImportSymbol('storage_database', entry.type);
|
|
116
|
+
imports.push(`import { factory as ${symbol} } from '${entry.importPath}';`);
|
|
117
|
+
}
|
|
118
|
+
for (const entry of cacheProviders) {
|
|
119
|
+
const symbol = toFactoryImportSymbol('storage_cache', entry.type);
|
|
120
|
+
imports.push(`import { factory as ${symbol} } from '${entry.importPath}';`);
|
|
185
121
|
}
|
|
186
122
|
|
|
187
|
-
return
|
|
123
|
+
return imports.join('\n');
|
|
188
124
|
}
|
|
189
125
|
|
|
190
|
-
function
|
|
191
|
-
return
|
|
192
|
-
|
|
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);
|
|
126
|
+
function generateHelpers(): string {
|
|
127
|
+
return `function isPlainObject(value) {
|
|
128
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
200
129
|
}
|
|
201
130
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
131
|
+
function mergeImageDefaults(baseDefaults, overrideDefaults) {
|
|
132
|
+
if (!baseDefaults) return overrideDefaults;
|
|
133
|
+
if (!overrideDefaults) return baseDefaults;
|
|
134
|
+
|
|
135
|
+
const merged = { ...baseDefaults, ...overrideDefaults };
|
|
136
|
+
for (const [key, baseValue] of Object.entries(baseDefaults)) {
|
|
137
|
+
const overrideValue = overrideDefaults[key];
|
|
138
|
+
if (!isPlainObject(baseValue) || !isPlainObject(overrideValue)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
merged[key] = {
|
|
142
|
+
...baseValue,
|
|
143
|
+
...overrideValue,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return merged;
|
|
147
|
+
}`;
|
|
212
148
|
}
|
|
213
149
|
|
|
214
150
|
function generateUtilityExports(definition: ImageDefinition): string {
|
|
@@ -250,32 +186,128 @@ function generateUtilityExports(definition: ImageDefinition): string {
|
|
|
250
186
|
return sections.join('\n');
|
|
251
187
|
}
|
|
252
188
|
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
189
|
+
function generateImageModule(
|
|
190
|
+
definition: ImageDefinition,
|
|
191
|
+
discoveredFactories: DiscoveredFactories
|
|
192
|
+
): string {
|
|
193
|
+
const derivedDefaults =
|
|
194
|
+
definition.defaults !== undefined
|
|
195
|
+
? JSON.stringify(definition.defaults, null, 4)
|
|
196
|
+
: 'undefined';
|
|
197
|
+
|
|
198
|
+
const toolsEntries = discoveredFactories.tools
|
|
199
|
+
.slice()
|
|
200
|
+
.sort((a, b) => a.type.localeCompare(b.type))
|
|
201
|
+
.map((entry) => {
|
|
202
|
+
const symbol = toFactoryImportSymbol('tools', entry.type);
|
|
203
|
+
return ` ${JSON.stringify(entry.type)}: ${symbol},`;
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const hookEntries = discoveredFactories.hooks
|
|
207
|
+
.slice()
|
|
208
|
+
.sort((a, b) => a.type.localeCompare(b.type))
|
|
209
|
+
.map((entry) => {
|
|
210
|
+
const symbol = toFactoryImportSymbol('hooks', entry.type);
|
|
211
|
+
return ` ${JSON.stringify(entry.type)}: ${symbol},`;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const compactionEntries = discoveredFactories.compaction
|
|
215
|
+
.slice()
|
|
216
|
+
.sort((a, b) => a.type.localeCompare(b.type))
|
|
217
|
+
.map((entry) => {
|
|
218
|
+
const symbol = toFactoryImportSymbol('compaction', entry.type);
|
|
219
|
+
return ` ${JSON.stringify(entry.type)}: ${symbol},`;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const blobEntries = discoveredFactories.storage.blob
|
|
223
|
+
.slice()
|
|
224
|
+
.sort((a, b) => a.type.localeCompare(b.type))
|
|
225
|
+
.map((entry) => {
|
|
226
|
+
const symbol = toFactoryImportSymbol('storage_blob', entry.type);
|
|
227
|
+
return ` ${JSON.stringify(entry.type)}: ${symbol},`;
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const databaseEntries = discoveredFactories.storage.database
|
|
231
|
+
.slice()
|
|
232
|
+
.sort((a, b) => a.type.localeCompare(b.type))
|
|
233
|
+
.map((entry) => {
|
|
234
|
+
const symbol = toFactoryImportSymbol('storage_database', entry.type);
|
|
235
|
+
return ` ${JSON.stringify(entry.type)}: ${symbol},`;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const cacheEntries = discoveredFactories.storage.cache
|
|
239
|
+
.slice()
|
|
240
|
+
.sort((a, b) => a.type.localeCompare(b.type))
|
|
241
|
+
.map((entry) => {
|
|
242
|
+
const symbol = toFactoryImportSymbol('storage_cache', entry.type);
|
|
243
|
+
return ` ${JSON.stringify(entry.type)}: ${symbol},`;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const metadataLines: string[] = [];
|
|
247
|
+
metadataLines.push(` name: ${JSON.stringify(definition.name)},`);
|
|
248
|
+
metadataLines.push(` version: ${JSON.stringify(definition.version)},`);
|
|
249
|
+
metadataLines.push(` description: ${JSON.stringify(definition.description)},`);
|
|
250
|
+
|
|
251
|
+
if (definition.target !== undefined) {
|
|
252
|
+
metadataLines.push(` target: ${JSON.stringify(definition.target)},`);
|
|
253
|
+
} else if (definition.extends) {
|
|
254
|
+
metadataLines.push(` target: baseImage.metadata.target,`);
|
|
267
255
|
}
|
|
268
256
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
257
|
+
if (definition.extends) {
|
|
258
|
+
const derivedConstraints = JSON.stringify(definition.constraints ?? []);
|
|
259
|
+
metadataLines.push(
|
|
260
|
+
` constraints: Array.from(new Set([...(baseImage.metadata.constraints ?? []), ...${derivedConstraints}])),`
|
|
261
|
+
);
|
|
262
|
+
} else if (definition.constraints !== undefined) {
|
|
263
|
+
metadataLines.push(` constraints: ${JSON.stringify(definition.constraints)},`);
|
|
272
264
|
}
|
|
273
265
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
266
|
+
const defaultsExpression = definition.extends
|
|
267
|
+
? `mergeImageDefaults(baseImage.defaults, ${derivedDefaults})`
|
|
268
|
+
: derivedDefaults;
|
|
269
|
+
|
|
270
|
+
const toolsSpread = definition.extends ? ` ...baseImage.tools,\n` : '';
|
|
271
|
+
const hooksSpread = definition.extends ? ` ...baseImage.hooks,\n` : '';
|
|
272
|
+
const compactionSpread = definition.extends ? ` ...baseImage.compaction,\n` : '';
|
|
273
|
+
|
|
274
|
+
const blobSpread = definition.extends ? ` ...baseImage.storage.blob,\n` : '';
|
|
275
|
+
const databaseSpread = definition.extends ? ` ...baseImage.storage.database,\n` : '';
|
|
276
|
+
const cacheSpread = definition.extends ? ` ...baseImage.storage.cache,\n` : '';
|
|
277
|
+
|
|
278
|
+
const loggerExpression = definition.extends
|
|
279
|
+
? `baseImage.logger ?? defaultLoggerFactory`
|
|
280
|
+
: `defaultLoggerFactory`;
|
|
281
|
+
|
|
282
|
+
return `const image = {
|
|
283
|
+
metadata: {
|
|
284
|
+
${metadataLines.join('\n')}
|
|
285
|
+
},
|
|
286
|
+
defaults: ${defaultsExpression},
|
|
287
|
+
tools: {
|
|
288
|
+
${toolsSpread}${toolsEntries.join('\n')}
|
|
289
|
+
},
|
|
290
|
+
storage: {
|
|
291
|
+
blob: {
|
|
292
|
+
${blobSpread}${blobEntries.join('\n')}
|
|
293
|
+
},
|
|
294
|
+
database: {
|
|
295
|
+
${databaseSpread}${databaseEntries.join('\n')}
|
|
296
|
+
},
|
|
297
|
+
cache: {
|
|
298
|
+
${cacheSpread}${cacheEntries.join('\n')}
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
hooks: {
|
|
302
|
+
${hooksSpread}${hookEntries.join('\n')}
|
|
303
|
+
},
|
|
304
|
+
compaction: {
|
|
305
|
+
${compactionSpread}${compactionEntries.join('\n')}
|
|
306
|
+
},
|
|
307
|
+
logger: ${loggerExpression},
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export default image;`;
|
|
279
311
|
}
|
|
280
312
|
|
|
281
313
|
function generateTypeDefinitions(definition: ImageDefinition): string {
|
|
@@ -311,26 +343,13 @@ function generateTypeDefinitions(definition: ImageDefinition): string {
|
|
|
311
343
|
return `// AUTO-GENERATED TypeScript definitions
|
|
312
344
|
// Do not edit this file directly
|
|
313
345
|
|
|
314
|
-
import type {
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Create a Dexto agent using this image's registered providers.
|
|
318
|
-
*/
|
|
319
|
-
export declare function createAgent(config: AgentConfig, configPath?: string): DextoAgent;
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Image metadata
|
|
323
|
-
*/
|
|
324
|
-
export declare const imageMetadata: ImageMetadata;
|
|
346
|
+
import type { DextoImage } from '@dexto/agent-config';
|
|
325
347
|
|
|
326
|
-
/**
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
blobStoreRegistry,
|
|
334
|
-
} from '@dexto/core';${utilityExports}
|
|
335
|
-
`;
|
|
348
|
+
/**
|
|
349
|
+
* Typed image module (no side effects)
|
|
350
|
+
*/
|
|
351
|
+
declare const image: DextoImage;
|
|
352
|
+
export default image;
|
|
353
|
+
${utilityExports}
|
|
354
|
+
`;
|
|
336
355
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Definition Types (bundler-only)
|
|
3
|
+
*
|
|
4
|
+
* The bundler consumes a `dexto.image.ts` file that declares metadata and defaults.
|
|
5
|
+
* Concrete tools/storage/hooks/compaction factories are discovered from convention folders
|
|
6
|
+
* and must `export const factory = ...` from their `index.ts`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ImageDefaults } from '@dexto/agent-config';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Image definition structure consumed by `@dexto/image-bundler`.
|
|
13
|
+
*
|
|
14
|
+
* Note: Provider factories are discovered from folders; this file is metadata + defaults only.
|
|
15
|
+
*/
|
|
16
|
+
export interface ImageDefinition {
|
|
17
|
+
/** Unique name for this image (e.g., 'image-local') */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Semantic version of this image */
|
|
20
|
+
version: string;
|
|
21
|
+
/** Brief description of this image's purpose */
|
|
22
|
+
description: string;
|
|
23
|
+
|
|
24
|
+
/** Target deployment environment (for documentation and validation) */
|
|
25
|
+
target?: string;
|
|
26
|
+
|
|
27
|
+
/** Runtime constraints this image requires (for validation and error messages) */
|
|
28
|
+
constraints?: string[];
|
|
29
|
+
|
|
30
|
+
/** Parent image package name to extend (optional) */
|
|
31
|
+
extends?: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default configuration values (merged into agent config; config wins).
|
|
35
|
+
*
|
|
36
|
+
* This must match the `AgentConfig` shape. Unknown fields will be rejected by schema validation.
|
|
37
|
+
*/
|
|
38
|
+
defaults?: ImageDefaults;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Utility exports (optional).
|
|
42
|
+
* Maps export name to file path (relative to image root).
|
|
43
|
+
*/
|
|
44
|
+
utils?: Record<string, string>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Selective named exports from packages (optional).
|
|
48
|
+
*/
|
|
49
|
+
exports?: Record<string, string[]>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Metadata about a built image (generated by bundler).
|
|
54
|
+
*/
|
|
55
|
+
export interface ImageMetadata {
|
|
56
|
+
/** Image name */
|
|
57
|
+
name: string;
|
|
58
|
+
/** Image version */
|
|
59
|
+
version: string;
|
|
60
|
+
/** Description */
|
|
61
|
+
description: string;
|
|
62
|
+
/** Target environment */
|
|
63
|
+
target?: string;
|
|
64
|
+
/** Runtime constraints */
|
|
65
|
+
constraints: string[];
|
|
66
|
+
/** Build timestamp */
|
|
67
|
+
builtAt: string;
|
|
68
|
+
/** Core version this image was built for */
|
|
69
|
+
coreVersion: string;
|
|
70
|
+
/** Base image this extends (if any) */
|
|
71
|
+
extends?: string;
|
|
72
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ImageDefinition } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate an image definition.
|
|
5
|
+
* Throws if the definition is invalid.
|
|
6
|
+
*
|
|
7
|
+
* Used by bundler to validate images before building.
|
|
8
|
+
*/
|
|
9
|
+
export function validateImageDefinition(definition: ImageDefinition): void {
|
|
10
|
+
// Basic validation
|
|
11
|
+
if (!definition.name || typeof definition.name !== 'string') {
|
|
12
|
+
throw new Error('Image name must be a non-empty string');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!definition.version || typeof definition.version !== 'string') {
|
|
16
|
+
throw new Error('Image version must be a non-empty string');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!definition.description || typeof definition.description !== 'string') {
|
|
20
|
+
throw new Error('Image description must be a non-empty string');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Validate version format (basic semver check)
|
|
24
|
+
const versionRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/;
|
|
25
|
+
if (!versionRegex.test(definition.version)) {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Image version '${definition.version}' is not valid semver. Expected format: x.y.z`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Validate target if provided
|
|
32
|
+
if (definition.target !== undefined) {
|
|
33
|
+
if (typeof definition.target !== 'string' || definition.target.trim().length === 0) {
|
|
34
|
+
throw new Error(`Image target must be a non-empty string when provided`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (definition.constraints) {
|
|
39
|
+
if (!Array.isArray(definition.constraints)) {
|
|
40
|
+
throw new Error('Image constraints must be an array');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const constraint of definition.constraints) {
|
|
44
|
+
if (typeof constraint !== 'string' || constraint.trim().length === 0) {
|
|
45
|
+
throw new Error(`Image constraint must be a non-empty string`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate utils if provided
|
|
51
|
+
if (definition.utils) {
|
|
52
|
+
for (const [name, path] of Object.entries(definition.utils)) {
|
|
53
|
+
if (typeof path !== 'string') {
|
|
54
|
+
throw new Error(`Utility '${name}' path must be a string`);
|
|
55
|
+
}
|
|
56
|
+
if (!path.startsWith('./')) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Utility '${name}' path must be relative (start with './'). Got: ${path}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate extends if provided
|
|
65
|
+
if (definition.extends) {
|
|
66
|
+
if (typeof definition.extends !== 'string') {
|
|
67
|
+
throw new Error('Image extends must be a string (parent image name)');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* @dexto/bundler
|
|
3
3
|
*
|
|
4
4
|
* Bundles Dexto base images from dexto.image.ts definitions
|
|
5
|
-
* into importable packages
|
|
5
|
+
* into importable packages exporting a typed `DextoImage` (no side effects).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export { bundle } from './bundler.js';
|
|
9
9
|
export type { BundleOptions, BundleResult, GeneratedCode } from './types.js';
|
|
10
|
+
export type { ImageDefinition, ImageMetadata } from './image-definition/types.js';
|
package/src/types.ts
CHANGED