165 lines
4.3 KiB
TypeScript
Raw Normal View History

/**
* Scaffolding utilities for mx CLI.
*
* Provides functions for creating package structures, templates, etc.
*/
import * as fs from 'node:fs';
import * as path from 'node:path';
import { validateManifestForMxCli } from './manifestValidator.js';
export interface ScaffoldOptions {
name: string;
description?: string;
version?: string;
}
/**
* Create a basic package structure.
*/
export function createPackageStructure(options: ScaffoldOptions): void {
const root = path.resolve(options.name);
const folders = ['src', 'examples', 'skills', 'tests'];
fs.mkdirSync(root, { recursive: true });
for (const folder of folders) {
fs.mkdirSync(path.join(root, folder), { recursive: true });
}
}
/**
* Generate matrix.json content.
*/
export function generateMatrixJson(options: ScaffoldOptions): string {
return JSON.stringify({
name: options.name,
version: options.version ?? '0.1.0',
description: options.description ?? '',
runtime: {
language: 'typescript',
entry: 'src/index.ts',
},
components: [],
permissions: {
fsPolicy: 'none',
},
}, null, 2);
}
export function toKebabCase(value: string): string {
return value
.trim()
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/[\s_]+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
.toLowerCase();
}
export function toPascalCase(value: string): string {
return value
.trim()
.replace(/[_-]+/g, ' ')
.split(/\s+/)
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join('');
}
export function writeJsonFile(filePath: string, value: unknown): void {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + '\n', 'utf8');
}
export function validateMatrixManifest(manifest: unknown): { valid: boolean; errors: string[] } {
const result = validateManifestForMxCli(manifest);
return { valid: result.valid, errors: result.errors };
}
/**
* Generate package.json for a scaffolded package.
*/
export function generatePackageJson(name: string, options?: {
webapp?: boolean;
service?: boolean;
scripts?: Record<string, string>;
}): Record<string, unknown> {
const scripts: Record<string, string> = options?.scripts ?? (options?.webapp
? { dev: 'vite', build: 'vite build', test: 'node --import tsx --test tests/**/*.spec.ts' }
: options?.service
? {
build: 'tsc -p tsconfig.json',
'build:watch': 'tsc -w -p tsconfig.json --preserveWatchOutput',
dev: 'tsc -w -p tsconfig.json --preserveWatchOutput',
test: 'node --import tsx --test tests/**/*.spec.ts',
}
: { build: 'tsc', test: 'node --import tsx --test tests/**/*.spec.ts' });
const dependencies: Record<string, string> = {};
const devDependencies: Record<string, string> = {
'typescript': '^5.0.0',
};
if (options?.service) {
dependencies['@open-matrix/core'] = '*';
} else {
devDependencies['@open-matrix/core'] = '*';
}
if (options?.webapp) {
devDependencies['@open-matrix/federation'] = '*';
devDependencies['vite'] = '^5.0.0';
devDependencies['nats'] = '^5.0.0';
}
return {
name: `@matrix/${name}`,
version: '0.1.0',
type: 'module',
files: [
'dist',
'matrix.json',
],
scripts,
...(Object.keys(dependencies).length > 0 ? { dependencies } : {}),
devDependencies,
};
}
/**
* Generate tsconfig.json for a scaffolded package.
*/
export function generateTsconfig(options?: { webapp?: boolean; service?: boolean }): Record<string, unknown> {
const compilerOptions: Record<string, unknown> = {
target: 'ES2022',
module: 'NodeNext',
moduleResolution: 'NodeNext',
outDir: 'dist',
rootDir: 'src',
declaration: true,
strict: true,
esModuleInterop: true,
skipLibCheck: true,
};
if (options?.service) {
compilerOptions.incremental = true;
compilerOptions.tsBuildInfoFile = '.tsbuildinfo';
compilerOptions.sourceMap = true;
}
if (options?.webapp) {
compilerOptions.module = 'ESNext';
compilerOptions.moduleResolution = 'Bundler';
compilerOptions.noEmit = true;
compilerOptions.lib = ['ES2022', 'DOM', 'DOM.Iterable'];
delete compilerOptions.outDir;
delete compilerOptions.rootDir;
delete compilerOptions.declaration;
}
return {
compilerOptions,
include: ['src'],
};
}