165 lines
4.3 KiB
TypeScript
165 lines
4.3 KiB
TypeScript
|
|
/**
|
||
|
|
* 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'],
|
||
|
|
};
|
||
|
|
}
|