@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.
- package/.turbo/turbo-build.log +20 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +44 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +540 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +473 -0
- package/dist/index.js.map +1 -0
- package/package.json +33 -0
- package/src/bundler.ts +361 -0
- package/src/cli.ts +93 -0
- package/src/generator.ts +331 -0
- package/src/index.ts +9 -0
- package/src/types.ts +30 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +13 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bundler.ts","../src/generator.ts"],"sourcesContent":["/**\n * Main bundler logic\n */\n\nimport { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'node:fs';\nimport { dirname, join, resolve, relative, extname } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { validateImageDefinition } from '@dexto/core';\nimport type { ImageDefinition } from '@dexto/core';\nimport type { BundleOptions, BundleResult } from './types.js';\nimport { generateEntryPoint } from './generator.js';\nimport ts from 'typescript';\n\n/**\n * Bundle a Dexto base image\n */\nexport async function bundle(options: BundleOptions): Promise<BundleResult> {\n const warnings: string[] = [];\n\n // 1. Load and validate image definition\n console.log(`š¦ Loading image definition from ${options.imagePath}`);\n const definition = await loadImageDefinition(options.imagePath);\n\n console.log(`ā
Loaded image: ${definition.name} v${definition.version}`);\n\n // 2. Validate definition\n console.log(`š Validating image definition...`);\n try {\n validateImageDefinition(definition);\n console.log(`ā
Image definition is valid`);\n } catch (error) {\n throw new Error(`Image validation failed: ${error}`);\n }\n\n // 3. Get core version (from package.json)\n const coreVersion = getCoreVersion();\n\n // 3.5. Discover providers from convention-based folders\n console.log(`š Discovering providers from folders...`);\n const imageDir = dirname(options.imagePath);\n const discoveredProviders = discoverProviders(imageDir);\n console.log(`ā
Discovered ${discoveredProviders.totalCount} provider(s)`);\n\n // 4. Generate code\n console.log(`šØ Generating entry point...`);\n const generated = generateEntryPoint(definition, coreVersion, discoveredProviders);\n\n // 5. Ensure output directory exists\n const outDir = resolve(options.outDir);\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n\n // 5.5. Compile provider category folders\n console.log(`šØ Compiling provider source files...`);\n const categories = ['blob-store', 'tools', 'compaction', 'plugins'];\n let compiledCount = 0;\n\n for (const category of categories) {\n const categoryDir = join(imageDir, category);\n if (existsSync(categoryDir)) {\n compileSourceFiles(categoryDir, join(outDir, category));\n compiledCount++;\n }\n }\n\n if (compiledCount > 0) {\n console.log(\n `ā
Compiled ${compiledCount} provider categor${compiledCount === 1 ? 'y' : 'ies'}`\n );\n }\n\n // 6. Write generated files\n const entryFile = join(outDir, 'index.js');\n const typesFile = join(outDir, 'index.d.ts');\n\n console.log(`š Writing ${entryFile}...`);\n writeFileSync(entryFile, generated.js, 'utf-8');\n\n console.log(`š Writing ${typesFile}...`);\n writeFileSync(typesFile, generated.dts, 'utf-8');\n\n // 7. Generate package.json exports\n updatePackageJson(dirname(options.imagePath), outDir);\n\n console.log(`⨠Build complete!`);\n console.log(` Entry: ${entryFile}`);\n console.log(` Types: ${typesFile}`);\n\n const metadata = {\n name: definition.name,\n version: definition.version,\n description: definition.description,\n target: definition.target || 'custom',\n constraints: definition.constraints || [],\n builtAt: new Date().toISOString(),\n coreVersion,\n };\n\n return {\n entryFile,\n typesFile,\n metadata,\n warnings,\n };\n}\n\n/**\n * Load image definition from file\n */\nasync function loadImageDefinition(imagePath: string): Promise<ImageDefinition> {\n const absolutePath = resolve(imagePath);\n\n if (!existsSync(absolutePath)) {\n throw new Error(`Image file not found: ${absolutePath}`);\n }\n\n try {\n // Convert to file:// URL for ESM import\n const fileUrl = pathToFileURL(absolutePath).href;\n\n // Dynamic import\n const module = await import(fileUrl);\n\n // Get default export\n const definition = module.default as ImageDefinition;\n\n if (!definition) {\n throw new Error('Image file must have a default export');\n }\n\n return definition;\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Failed to load image definition: ${error.message}`);\n }\n throw error;\n }\n}\n\n/**\n * Get @dexto/core version\n */\nfunction getCoreVersion(): string {\n try {\n // Try to read from node_modules\n const corePackageJson = join(process.cwd(), 'node_modules/@dexto/core/package.json');\n if (existsSync(corePackageJson)) {\n const pkg = JSON.parse(readFileSync(corePackageJson, 'utf-8'));\n return pkg.version;\n }\n\n // Fallback to workspace version\n return '1.0.0';\n } catch {\n return '1.0.0';\n }\n}\n\n/**\n * Update or create package.json with proper exports\n */\nfunction updatePackageJson(imageDir: string, outDir: string): void {\n const packageJsonPath = join(imageDir, 'package.json');\n\n if (!existsSync(packageJsonPath)) {\n console.log(`ā ļø No package.json found, skipping exports update`);\n return;\n }\n\n try {\n const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\n // Update exports\n pkg.exports = {\n '.': {\n types: './dist/index.d.ts',\n import: './dist/index.js',\n },\n };\n\n // Update main and types fields\n pkg.main = './dist/index.js';\n pkg.types = './dist/index.d.ts';\n\n writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), 'utf-8');\n console.log(`ā
Updated package.json exports`);\n } catch (error) {\n console.warn(`ā ļø Failed to update package.json: ${error}`);\n }\n}\n\n/**\n * Compile TypeScript source files to JavaScript\n */\nfunction compileSourceFiles(srcDir: string, outDir: string): void {\n // Find all .ts files\n const tsFiles = findTypeScriptFiles(srcDir);\n\n if (tsFiles.length === 0) {\n console.log(` No TypeScript files found in ${srcDir}`);\n return;\n }\n\n console.log(` Found ${tsFiles.length} TypeScript file(s) to compile`);\n\n // TypeScript compiler options\n const compilerOptions: ts.CompilerOptions = {\n target: ts.ScriptTarget.ES2022,\n module: ts.ModuleKind.ESNext,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n outDir: outDir,\n rootDir: srcDir, // Use srcDir as root\n declaration: true,\n esModuleInterop: true,\n skipLibCheck: true,\n strict: true,\n resolveJsonModule: true,\n };\n\n // Create program\n const program = ts.createProgram(tsFiles, compilerOptions);\n\n // Emit compiled files\n const emitResult = program.emit();\n\n // Check for errors\n const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);\n\n if (allDiagnostics.length > 0) {\n allDiagnostics.forEach((diagnostic) => {\n if (diagnostic.file) {\n const { line, character } = ts.getLineAndCharacterOfPosition(\n diagnostic.file,\n diagnostic.start!\n );\n const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\\n');\n console.error(\n ` ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`\n );\n } else {\n console.error(\n ` ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\\n')}`\n );\n }\n });\n\n if (emitResult.emitSkipped) {\n throw new Error('TypeScript compilation failed');\n }\n }\n}\n\n/**\n * Recursively find all TypeScript files in a directory\n */\nfunction findTypeScriptFiles(dir: string): string[] {\n const files: string[] = [];\n\n function walk(currentDir: string) {\n const entries = readdirSync(currentDir);\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n walk(fullPath);\n } else if (stat.isFile() && extname(entry) === '.ts') {\n files.push(fullPath);\n }\n }\n }\n\n walk(dir);\n return files;\n}\n\n/**\n * Provider discovery result for a single category\n */\nexport interface DiscoveredProviders {\n blobStore: string[];\n customTools: string[];\n compaction: string[];\n plugins: string[];\n totalCount: number;\n}\n\n/**\n * Discover providers from convention-based folder structure\n *\n * Convention (folder-based with index.ts):\n * tools/ - CustomToolProvider folders\n * weather/ - Provider folder\n * index.ts - Provider implementation (auto-discovered)\n * helpers.ts - Optional helper files\n * types.ts - Optional type definitions\n * blob-store/ - BlobStoreProvider folders\n * compaction/ - CompactionProvider folders\n * plugins/ - PluginProvider folders\n *\n * Naming Convention (Node.js standard):\n * <folder>/index.ts - Auto-discovered and registered\n * <folder>/other.ts - Ignored unless imported by index.ts\n */\nfunction discoverProviders(imageDir: string): DiscoveredProviders {\n const result: DiscoveredProviders = {\n blobStore: [],\n customTools: [],\n compaction: [],\n plugins: [],\n totalCount: 0,\n };\n\n // Category mapping: folder name -> property name\n const categories = {\n 'blob-store': 'blobStore',\n tools: 'customTools',\n compaction: 'compaction',\n plugins: 'plugins',\n } as const;\n\n for (const [folderName, propName] of Object.entries(categories)) {\n const categoryDir = join(imageDir, folderName);\n\n if (!existsSync(categoryDir)) {\n continue;\n }\n\n // Find all provider folders (those with index.ts)\n const providerFolders = readdirSync(categoryDir)\n .filter((entry) => {\n const entryPath = join(categoryDir, entry);\n const stat = statSync(entryPath);\n\n // Must be a directory\n if (!stat.isDirectory()) {\n return false;\n }\n\n // Must contain index.ts\n const indexPath = join(entryPath, 'index.ts');\n return existsSync(indexPath);\n })\n .map((folder) => {\n // Return relative path for imports\n return `./${folderName}/${folder}/index.js`;\n });\n\n if (providerFolders.length > 0) {\n result[propName as keyof Omit<DiscoveredProviders, 'totalCount'>].push(\n ...providerFolders\n );\n result.totalCount += providerFolders.length;\n console.log(` Found ${providerFolders.length} provider(s) in ${folderName}/`);\n }\n }\n\n return result;\n}\n","/**\n * Code generator for base images\n *\n * Transforms image definitions into importable packages with:\n * - Side-effect provider registration\n * - createAgent() factory\n * - Utility exports\n * - Metadata exports\n */\n\nimport type { ImageDefinition } from '@dexto/core';\nimport type { GeneratedCode } from './types.js';\nimport type { DiscoveredProviders } from './bundler.js';\n\n/**\n * Generate JavaScript entry point for an image\n */\nexport function generateEntryPoint(\n definition: ImageDefinition,\n coreVersion: string,\n discoveredProviders?: DiscoveredProviders\n): GeneratedCode {\n // Generate imports section\n const imports = generateImports(definition, discoveredProviders);\n\n // Generate provider registration section\n const registrations = generateProviderRegistrations(definition, discoveredProviders);\n\n // Generate factory function\n const factory = generateFactory();\n\n // Generate utility exports\n const utilityExports = generateUtilityExports(definition);\n\n // Generate metadata export\n const metadata = generateMetadata(definition, coreVersion);\n\n // Combine all sections\n const js = `// AUTO-GENERATED by @dexto/bundler\n// Do not edit this file directly. Edit dexto.image.ts instead.\n\n${imports}\n\n${registrations}\n\n${factory}\n\n${utilityExports}\n\n${metadata}\n`;\n\n // Generate TypeScript definitions\n const dts = generateTypeDefinitions(definition);\n\n return { js, dts };\n}\n\nfunction generateImports(\n definition: ImageDefinition,\n discoveredProviders?: DiscoveredProviders\n): string {\n const imports: string[] = [];\n\n // Import base image first (if extending) - triggers side-effect provider registration\n if (definition.extends) {\n imports.push(`// Import base image for provider registration (side effect)`);\n imports.push(`import '${definition.extends}';`);\n imports.push(``);\n }\n\n // Core imports\n imports.push(`import { DextoAgent } from '@dexto/core';`);\n\n // Always import all registries since we re-export them in generateFactory()\n // This ensures the re-exports don't reference unimported identifiers\n imports.push(\n `import { customToolRegistry, pluginRegistry, compactionRegistry, blobStoreRegistry } from '@dexto/core';`\n );\n\n // Import discovered providers\n if (discoveredProviders) {\n const categories = [\n { key: 'blobStore', label: 'Blob Storage' },\n { key: 'customTools', label: 'Custom Tools' },\n { key: 'compaction', label: 'Compaction' },\n { key: 'plugins', label: 'Plugins' },\n ] as const;\n\n for (const { key, label } of categories) {\n const providers = discoveredProviders[key];\n if (providers.length > 0) {\n imports.push(``);\n imports.push(`// ${label} providers (auto-discovered)`);\n providers.forEach((path, index) => {\n const varName = `${key}Provider${index}`;\n imports.push(`import * as ${varName} from '${path}';`);\n });\n }\n }\n }\n\n return imports.join('\\n');\n}\n\nfunction generateProviderRegistrations(\n definition: ImageDefinition,\n discoveredProviders?: DiscoveredProviders\n): string {\n const registrations: string[] = [];\n\n if (definition.extends) {\n registrations.push(\n `// Base image providers already registered via import of '${definition.extends}'`\n );\n registrations.push('');\n }\n\n registrations.push('// SIDE EFFECT: Register providers on import');\n registrations.push('');\n\n // Auto-register discovered providers\n if (discoveredProviders) {\n const categoryMap = [\n { key: 'blobStore', registry: 'blobStoreRegistry', label: 'Blob Storage' },\n { key: 'customTools', registry: 'customToolRegistry', label: 'Custom Tools' },\n { key: 'compaction', registry: 'compactionRegistry', label: 'Compaction' },\n { key: 'plugins', registry: 'pluginRegistry', label: 'Plugins' },\n ] as const;\n\n for (const { key, registry, label } of categoryMap) {\n const providers = discoveredProviders[key];\n if (providers.length === 0) continue;\n\n registrations.push(`// Auto-register ${label} providers`);\n providers.forEach((path, index) => {\n const varName = `${key}Provider${index}`;\n registrations.push(`// From ${path}`);\n registrations.push(`for (const exported of Object.values(${varName})) {`);\n registrations.push(\n ` if (exported && typeof exported === 'object' && 'type' in exported && 'create' in exported) {`\n );\n registrations.push(` try {`);\n registrations.push(` ${registry}.register(exported);`);\n registrations.push(\n ` console.log(\\`ā Registered ${key}: \\${exported.type}\\`);`\n );\n registrations.push(` } catch (err) {`);\n registrations.push(` // Ignore duplicate registration errors`);\n registrations.push(\n ` if (!err.message?.includes('already registered')) throw err;`\n );\n registrations.push(` }`);\n registrations.push(` }`);\n registrations.push(`}`);\n });\n registrations.push('');\n }\n }\n\n // Handle manual registration functions (backwards compatibility)\n for (const [category, config] of Object.entries(definition.providers)) {\n if (!config) continue;\n\n if (config.register) {\n // Async registration function with duplicate prevention\n registrations.push(`// Register ${category} via custom function (from dexto.image.ts)`);\n registrations.push(`await (async () => {`);\n registrations.push(` try {`);\n registrations.push(\n ` ${config.register\n .toString()\n .replace(/^async\\s*\\(\\)\\s*=>\\s*{/, '')\n .replace(/}$/, '')}`\n );\n registrations.push(` } catch (err) {`);\n registrations.push(` // Ignore duplicate registration errors`);\n registrations.push(` if (!err.message?.includes('already registered')) {`);\n registrations.push(` throw err;`);\n registrations.push(` }`);\n registrations.push(` }`);\n registrations.push(`})();`);\n registrations.push('');\n }\n }\n\n return registrations.join('\\n');\n}\n\nfunction generateFactory(): string {\n return `/**\n * Create a Dexto agent using this image's registered providers.\n *\n * @param config - Agent configuration\n * @param configPath - Optional path to config file\n * @returns DextoAgent instance with providers already registered\n */\nexport function createAgent(config, configPath) {\n return new DextoAgent(config, configPath);\n}\n\n/**\n * Re-export registries for runtime customization.\n * This allows apps to add custom providers without depending on @dexto/core directly.\n */\nexport {\n customToolRegistry,\n pluginRegistry,\n compactionRegistry,\n blobStoreRegistry,\n} from '@dexto/core';`;\n}\n\nfunction generateUtilityExports(definition: ImageDefinition): string {\n const sections: string[] = [];\n\n // Generate wildcard utility exports\n if (definition.utils && Object.keys(definition.utils).length > 0) {\n sections.push('// Utility exports');\n for (const [name, path] of Object.entries(definition.utils)) {\n sections.push(`export * from '${path}';`);\n }\n }\n\n // Generate selective named exports (filter out type-only exports for runtime JS)\n if (definition.exports && Object.keys(definition.exports).length > 0) {\n if (sections.length > 0) sections.push('');\n sections.push('// Selective package re-exports');\n for (const [packageName, exports] of Object.entries(definition.exports)) {\n // Check for wildcard re-export\n if (exports.length === 1 && exports[0] === '*') {\n sections.push(`export * from '${packageName}';`);\n continue;\n }\n\n // Filter out type-only exports (those starting with 'type ')\n const runtimeExports = exports.filter((exp) => !exp.startsWith('type '));\n if (runtimeExports.length > 0) {\n sections.push(`export {`);\n sections.push(` ${runtimeExports.join(',\\n ')}`);\n sections.push(`} from '${packageName}';`);\n }\n }\n }\n\n if (sections.length === 0) {\n return '// No utilities or exports defined for this image';\n }\n\n return sections.join('\\n');\n}\n\nfunction generateMetadata(definition: ImageDefinition, coreVersion: string): string {\n const metadata: Record<string, any> = {\n name: definition.name,\n version: definition.version,\n description: definition.description,\n target: definition.target || 'custom',\n constraints: definition.constraints || [],\n builtAt: new Date().toISOString(),\n coreVersion: coreVersion,\n };\n\n // Include extends information if present\n if (definition.extends) {\n metadata.extends = definition.extends;\n }\n\n return `/**\n * Image metadata\n * Generated at build time\n */\nexport const imageMetadata = ${JSON.stringify(metadata, null, 4)};`;\n}\n\nfunction generateTypeDefinitions(definition: ImageDefinition): string {\n const sections: string[] = [];\n\n // Wildcard utility exports\n if (definition.utils && Object.keys(definition.utils).length > 0) {\n sections.push('// Utility re-exports');\n for (const path of Object.values(definition.utils)) {\n sections.push(`export * from '${path}';`);\n }\n }\n\n // Selective named exports\n if (definition.exports && Object.keys(definition.exports).length > 0) {\n if (sections.length > 0) sections.push('');\n sections.push('// Selective package re-exports');\n for (const [packageName, exports] of Object.entries(definition.exports)) {\n // Check for wildcard re-export\n if (exports.length === 1 && exports[0] === '*') {\n sections.push(`export * from '${packageName}';`);\n continue;\n }\n\n sections.push(`export {`);\n sections.push(` ${exports.join(',\\n ')}`);\n sections.push(`} from '${packageName}';`);\n }\n }\n\n const utilityExports = sections.length > 0 ? '\\n\\n' + sections.join('\\n') : '';\n\n return `// AUTO-GENERATED TypeScript definitions\n// Do not edit this file directly\n\nimport type { DextoAgent, AgentConfig, ImageMetadata } from '@dexto/core';\n\n/**\n * Create a Dexto agent using this image's registered providers.\n */\nexport declare function createAgent(config: AgentConfig, configPath?: string): DextoAgent;\n\n/**\n * Image metadata\n */\nexport declare const imageMetadata: ImageMetadata;\n\n/**\n * Re-exported registries for runtime customization\n */\nexport {\n customToolRegistry,\n pluginRegistry,\n compactionRegistry,\n blobStoreRegistry,\n} from '@dexto/core';${utilityExports}\n`;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dexto/image-bundler",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Bundler for Dexto base images",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"dexto-bundle": "./dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"commander": "^12.0.0",
|
|
19
|
+
"esbuild": "^0.25.0",
|
|
20
|
+
"picocolors": "^1.0.0",
|
|
21
|
+
"@dexto/core": "1.5.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.11.5",
|
|
25
|
+
"tsup": "^8.0.1",
|
|
26
|
+
"typescript": "^5.3.3"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/bundler.ts
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main bundler logic
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
6
|
+
import { dirname, join, resolve, relative, extname } from 'node:path';
|
|
7
|
+
import { pathToFileURL } from 'node:url';
|
|
8
|
+
import { validateImageDefinition } from '@dexto/core';
|
|
9
|
+
import type { ImageDefinition } from '@dexto/core';
|
|
10
|
+
import type { BundleOptions, BundleResult } from './types.js';
|
|
11
|
+
import { generateEntryPoint } from './generator.js';
|
|
12
|
+
import ts from 'typescript';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Bundle a Dexto base image
|
|
16
|
+
*/
|
|
17
|
+
export async function bundle(options: BundleOptions): Promise<BundleResult> {
|
|
18
|
+
const warnings: string[] = [];
|
|
19
|
+
|
|
20
|
+
// 1. Load and validate image definition
|
|
21
|
+
console.log(`š¦ Loading image definition from ${options.imagePath}`);
|
|
22
|
+
const definition = await loadImageDefinition(options.imagePath);
|
|
23
|
+
|
|
24
|
+
console.log(`ā
Loaded image: ${definition.name} v${definition.version}`);
|
|
25
|
+
|
|
26
|
+
// 2. Validate definition
|
|
27
|
+
console.log(`š Validating image definition...`);
|
|
28
|
+
try {
|
|
29
|
+
validateImageDefinition(definition);
|
|
30
|
+
console.log(`ā
Image definition is valid`);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`Image validation failed: ${error}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 3. Get core version (from package.json)
|
|
36
|
+
const coreVersion = getCoreVersion();
|
|
37
|
+
|
|
38
|
+
// 3.5. Discover providers from convention-based folders
|
|
39
|
+
console.log(`š Discovering providers from folders...`);
|
|
40
|
+
const imageDir = dirname(options.imagePath);
|
|
41
|
+
const discoveredProviders = discoverProviders(imageDir);
|
|
42
|
+
console.log(`ā
Discovered ${discoveredProviders.totalCount} provider(s)`);
|
|
43
|
+
|
|
44
|
+
// 4. Generate code
|
|
45
|
+
console.log(`šØ Generating entry point...`);
|
|
46
|
+
const generated = generateEntryPoint(definition, coreVersion, discoveredProviders);
|
|
47
|
+
|
|
48
|
+
// 5. Ensure output directory exists
|
|
49
|
+
const outDir = resolve(options.outDir);
|
|
50
|
+
if (!existsSync(outDir)) {
|
|
51
|
+
mkdirSync(outDir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 5.5. Compile provider category folders
|
|
55
|
+
console.log(`šØ Compiling provider source files...`);
|
|
56
|
+
const categories = ['blob-store', 'tools', 'compaction', 'plugins'];
|
|
57
|
+
let compiledCount = 0;
|
|
58
|
+
|
|
59
|
+
for (const category of categories) {
|
|
60
|
+
const categoryDir = join(imageDir, category);
|
|
61
|
+
if (existsSync(categoryDir)) {
|
|
62
|
+
compileSourceFiles(categoryDir, join(outDir, category));
|
|
63
|
+
compiledCount++;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (compiledCount > 0) {
|
|
68
|
+
console.log(
|
|
69
|
+
`ā
Compiled ${compiledCount} provider categor${compiledCount === 1 ? 'y' : 'ies'}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 6. Write generated files
|
|
74
|
+
const entryFile = join(outDir, 'index.js');
|
|
75
|
+
const typesFile = join(outDir, 'index.d.ts');
|
|
76
|
+
|
|
77
|
+
console.log(`š Writing ${entryFile}...`);
|
|
78
|
+
writeFileSync(entryFile, generated.js, 'utf-8');
|
|
79
|
+
|
|
80
|
+
console.log(`š Writing ${typesFile}...`);
|
|
81
|
+
writeFileSync(typesFile, generated.dts, 'utf-8');
|
|
82
|
+
|
|
83
|
+
// 7. Generate package.json exports
|
|
84
|
+
updatePackageJson(dirname(options.imagePath), outDir);
|
|
85
|
+
|
|
86
|
+
console.log(`⨠Build complete!`);
|
|
87
|
+
console.log(` Entry: ${entryFile}`);
|
|
88
|
+
console.log(` Types: ${typesFile}`);
|
|
89
|
+
|
|
90
|
+
const metadata = {
|
|
91
|
+
name: definition.name,
|
|
92
|
+
version: definition.version,
|
|
93
|
+
description: definition.description,
|
|
94
|
+
target: definition.target || 'custom',
|
|
95
|
+
constraints: definition.constraints || [],
|
|
96
|
+
builtAt: new Date().toISOString(),
|
|
97
|
+
coreVersion,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
entryFile,
|
|
102
|
+
typesFile,
|
|
103
|
+
metadata,
|
|
104
|
+
warnings,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Load image definition from file
|
|
110
|
+
*/
|
|
111
|
+
async function loadImageDefinition(imagePath: string): Promise<ImageDefinition> {
|
|
112
|
+
const absolutePath = resolve(imagePath);
|
|
113
|
+
|
|
114
|
+
if (!existsSync(absolutePath)) {
|
|
115
|
+
throw new Error(`Image file not found: ${absolutePath}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
// Convert to file:// URL for ESM import
|
|
120
|
+
const fileUrl = pathToFileURL(absolutePath).href;
|
|
121
|
+
|
|
122
|
+
// Dynamic import
|
|
123
|
+
const module = await import(fileUrl);
|
|
124
|
+
|
|
125
|
+
// Get default export
|
|
126
|
+
const definition = module.default as ImageDefinition;
|
|
127
|
+
|
|
128
|
+
if (!definition) {
|
|
129
|
+
throw new Error('Image file must have a default export');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return definition;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof Error) {
|
|
135
|
+
throw new Error(`Failed to load image definition: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get @dexto/core version
|
|
143
|
+
*/
|
|
144
|
+
function getCoreVersion(): string {
|
|
145
|
+
try {
|
|
146
|
+
// Try to read from node_modules
|
|
147
|
+
const corePackageJson = join(process.cwd(), 'node_modules/@dexto/core/package.json');
|
|
148
|
+
if (existsSync(corePackageJson)) {
|
|
149
|
+
const pkg = JSON.parse(readFileSync(corePackageJson, 'utf-8'));
|
|
150
|
+
return pkg.version;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Fallback to workspace version
|
|
154
|
+
return '1.0.0';
|
|
155
|
+
} catch {
|
|
156
|
+
return '1.0.0';
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Update or create package.json with proper exports
|
|
162
|
+
*/
|
|
163
|
+
function updatePackageJson(imageDir: string, outDir: string): void {
|
|
164
|
+
const packageJsonPath = join(imageDir, 'package.json');
|
|
165
|
+
|
|
166
|
+
if (!existsSync(packageJsonPath)) {
|
|
167
|
+
console.log(`ā ļø No package.json found, skipping exports update`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
173
|
+
|
|
174
|
+
// Update exports
|
|
175
|
+
pkg.exports = {
|
|
176
|
+
'.': {
|
|
177
|
+
types: './dist/index.d.ts',
|
|
178
|
+
import: './dist/index.js',
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Update main and types fields
|
|
183
|
+
pkg.main = './dist/index.js';
|
|
184
|
+
pkg.types = './dist/index.d.ts';
|
|
185
|
+
|
|
186
|
+
writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), 'utf-8');
|
|
187
|
+
console.log(`ā
Updated package.json exports`);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
console.warn(`ā ļø Failed to update package.json: ${error}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Compile TypeScript source files to JavaScript
|
|
195
|
+
*/
|
|
196
|
+
function compileSourceFiles(srcDir: string, outDir: string): void {
|
|
197
|
+
// Find all .ts files
|
|
198
|
+
const tsFiles = findTypeScriptFiles(srcDir);
|
|
199
|
+
|
|
200
|
+
if (tsFiles.length === 0) {
|
|
201
|
+
console.log(` No TypeScript files found in ${srcDir}`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log(` Found ${tsFiles.length} TypeScript file(s) to compile`);
|
|
206
|
+
|
|
207
|
+
// TypeScript compiler options
|
|
208
|
+
const compilerOptions: ts.CompilerOptions = {
|
|
209
|
+
target: ts.ScriptTarget.ES2022,
|
|
210
|
+
module: ts.ModuleKind.ESNext,
|
|
211
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
212
|
+
outDir: outDir,
|
|
213
|
+
rootDir: srcDir, // Use srcDir as root
|
|
214
|
+
declaration: true,
|
|
215
|
+
esModuleInterop: true,
|
|
216
|
+
skipLibCheck: true,
|
|
217
|
+
strict: true,
|
|
218
|
+
resolveJsonModule: true,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Create program
|
|
222
|
+
const program = ts.createProgram(tsFiles, compilerOptions);
|
|
223
|
+
|
|
224
|
+
// Emit compiled files
|
|
225
|
+
const emitResult = program.emit();
|
|
226
|
+
|
|
227
|
+
// Check for errors
|
|
228
|
+
const allDiagnostics = ts.getPreEmitDiagnostics(program).concat(emitResult.diagnostics);
|
|
229
|
+
|
|
230
|
+
if (allDiagnostics.length > 0) {
|
|
231
|
+
allDiagnostics.forEach((diagnostic) => {
|
|
232
|
+
if (diagnostic.file) {
|
|
233
|
+
const { line, character } = ts.getLineAndCharacterOfPosition(
|
|
234
|
+
diagnostic.file,
|
|
235
|
+
diagnostic.start!
|
|
236
|
+
);
|
|
237
|
+
const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
|
|
238
|
+
console.error(
|
|
239
|
+
` ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`
|
|
240
|
+
);
|
|
241
|
+
} else {
|
|
242
|
+
console.error(
|
|
243
|
+
` ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
if (emitResult.emitSkipped) {
|
|
249
|
+
throw new Error('TypeScript compilation failed');
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Recursively find all TypeScript files in a directory
|
|
256
|
+
*/
|
|
257
|
+
function findTypeScriptFiles(dir: string): string[] {
|
|
258
|
+
const files: string[] = [];
|
|
259
|
+
|
|
260
|
+
function walk(currentDir: string) {
|
|
261
|
+
const entries = readdirSync(currentDir);
|
|
262
|
+
|
|
263
|
+
for (const entry of entries) {
|
|
264
|
+
const fullPath = join(currentDir, entry);
|
|
265
|
+
const stat = statSync(fullPath);
|
|
266
|
+
|
|
267
|
+
if (stat.isDirectory()) {
|
|
268
|
+
walk(fullPath);
|
|
269
|
+
} else if (stat.isFile() && extname(entry) === '.ts') {
|
|
270
|
+
files.push(fullPath);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
walk(dir);
|
|
276
|
+
return files;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Provider discovery result for a single category
|
|
281
|
+
*/
|
|
282
|
+
export interface DiscoveredProviders {
|
|
283
|
+
blobStore: string[];
|
|
284
|
+
customTools: string[];
|
|
285
|
+
compaction: string[];
|
|
286
|
+
plugins: string[];
|
|
287
|
+
totalCount: number;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Discover providers from convention-based folder structure
|
|
292
|
+
*
|
|
293
|
+
* Convention (folder-based with index.ts):
|
|
294
|
+
* tools/ - CustomToolProvider folders
|
|
295
|
+
* weather/ - Provider folder
|
|
296
|
+
* index.ts - Provider implementation (auto-discovered)
|
|
297
|
+
* helpers.ts - Optional helper files
|
|
298
|
+
* types.ts - Optional type definitions
|
|
299
|
+
* blob-store/ - BlobStoreProvider folders
|
|
300
|
+
* compaction/ - CompactionProvider folders
|
|
301
|
+
* plugins/ - PluginProvider folders
|
|
302
|
+
*
|
|
303
|
+
* Naming Convention (Node.js standard):
|
|
304
|
+
* <folder>/index.ts - Auto-discovered and registered
|
|
305
|
+
* <folder>/other.ts - Ignored unless imported by index.ts
|
|
306
|
+
*/
|
|
307
|
+
function discoverProviders(imageDir: string): DiscoveredProviders {
|
|
308
|
+
const result: DiscoveredProviders = {
|
|
309
|
+
blobStore: [],
|
|
310
|
+
customTools: [],
|
|
311
|
+
compaction: [],
|
|
312
|
+
plugins: [],
|
|
313
|
+
totalCount: 0,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Category mapping: folder name -> property name
|
|
317
|
+
const categories = {
|
|
318
|
+
'blob-store': 'blobStore',
|
|
319
|
+
tools: 'customTools',
|
|
320
|
+
compaction: 'compaction',
|
|
321
|
+
plugins: 'plugins',
|
|
322
|
+
} as const;
|
|
323
|
+
|
|
324
|
+
for (const [folderName, propName] of Object.entries(categories)) {
|
|
325
|
+
const categoryDir = join(imageDir, folderName);
|
|
326
|
+
|
|
327
|
+
if (!existsSync(categoryDir)) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Find all provider folders (those with index.ts)
|
|
332
|
+
const providerFolders = readdirSync(categoryDir)
|
|
333
|
+
.filter((entry) => {
|
|
334
|
+
const entryPath = join(categoryDir, entry);
|
|
335
|
+
const stat = statSync(entryPath);
|
|
336
|
+
|
|
337
|
+
// Must be a directory
|
|
338
|
+
if (!stat.isDirectory()) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Must contain index.ts
|
|
343
|
+
const indexPath = join(entryPath, 'index.ts');
|
|
344
|
+
return existsSync(indexPath);
|
|
345
|
+
})
|
|
346
|
+
.map((folder) => {
|
|
347
|
+
// Return relative path for imports
|
|
348
|
+
return `./${folderName}/${folder}/index.js`;
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
if (providerFolders.length > 0) {
|
|
352
|
+
result[propName as keyof Omit<DiscoveredProviders, 'totalCount'>].push(
|
|
353
|
+
...providerFolders
|
|
354
|
+
);
|
|
355
|
+
result.totalCount += providerFolders.length;
|
|
356
|
+
console.log(` Found ${providerFolders.length} provider(s) in ${folderName}/`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return result;
|
|
361
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for bundling Dexto base images
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Suppress experimental warnings (e.g., Type Stripping)
|
|
7
|
+
process.removeAllListeners('warning');
|
|
8
|
+
process.on('warning', (warning) => {
|
|
9
|
+
if (warning.name !== 'ExperimentalWarning') {
|
|
10
|
+
console.warn(warning);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
import { bundle } from './bundler.js';
|
|
16
|
+
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { join, dirname } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import pc from 'picocolors';
|
|
20
|
+
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
|
|
24
|
+
// Read version from package.json
|
|
25
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
|
|
26
|
+
|
|
27
|
+
const program = new Command();
|
|
28
|
+
|
|
29
|
+
program.name('dexto-bundle').description('Bundle Dexto base images').version(packageJson.version);
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.command('build')
|
|
33
|
+
.description('Build a base image from dexto.image.ts')
|
|
34
|
+
.option('-i, --image <path>', 'Path to dexto.image.ts file', 'dexto.image.ts')
|
|
35
|
+
.option('-o, --out <dir>', 'Output directory', 'dist')
|
|
36
|
+
.option('--sourcemap', 'Generate source maps', false)
|
|
37
|
+
.option('--minify', 'Minify output', false)
|
|
38
|
+
.action(async (options) => {
|
|
39
|
+
try {
|
|
40
|
+
console.log(pc.cyan('š Dexto Image Bundler\n'));
|
|
41
|
+
|
|
42
|
+
const result = await bundle({
|
|
43
|
+
imagePath: options.image,
|
|
44
|
+
outDir: options.out,
|
|
45
|
+
sourcemap: options.sourcemap,
|
|
46
|
+
minify: options.minify,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
console.log(pc.green('\n⨠Build successful!\n'));
|
|
50
|
+
console.log(pc.bold('Image Details:'));
|
|
51
|
+
console.log(` Name: ${result.metadata.name}`);
|
|
52
|
+
console.log(` Version: ${result.metadata.version}`);
|
|
53
|
+
console.log(` Target: ${result.metadata.target}`);
|
|
54
|
+
console.log(` Built at: ${result.metadata.builtAt}`);
|
|
55
|
+
console.log(` Core: v${result.metadata.coreVersion}`);
|
|
56
|
+
|
|
57
|
+
if (result.metadata.constraints.length > 0) {
|
|
58
|
+
console.log(` Constraints: ${result.metadata.constraints.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (result.warnings.length > 0) {
|
|
62
|
+
console.log(pc.yellow('\nā ļø Warnings:'));
|
|
63
|
+
result.warnings.forEach((w) => console.log(` - ${w}`));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Read package.json to get the actual package name
|
|
67
|
+
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
68
|
+
let packageName = result.metadata.name;
|
|
69
|
+
try {
|
|
70
|
+
if (readFileSync) {
|
|
71
|
+
const pkgJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
72
|
+
packageName = pkgJson.name || result.metadata.name;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Use metadata name as fallback
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(pc.green('\nā
Image is ready to use!'));
|
|
79
|
+
console.log(' To use this image in an app:');
|
|
80
|
+
console.log(
|
|
81
|
+
pc.dim(
|
|
82
|
+
` 1. Install it: pnpm add ${packageName}@file:../${packageName.split('/').pop()}`
|
|
83
|
+
)
|
|
84
|
+
);
|
|
85
|
+
console.log(pc.dim(` 2. Import it: import { createAgent } from '${packageName}';`));
|
|
86
|
+
console.log(pc.dim(`\n Or publish to npm and install normally.`));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(pc.red('\nā Build failed:'), error);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
program.parse();
|