168 lines
5.9 KiB
TypeScript
168 lines
5.9 KiB
TypeScript
import { BaseFormatter } from '../classes.baseformatter.js';
|
|
import type { IPlannedChange } from '../interfaces.format.js';
|
|
import * as plugins from '../mod.plugins.js';
|
|
import * as paths from '../../paths.js';
|
|
import { logger, logVerbose } from '../../gitzone.logging.js';
|
|
|
|
export class TemplatesFormatter extends BaseFormatter {
|
|
get name(): string {
|
|
return 'templates';
|
|
}
|
|
|
|
/**
|
|
* Render a template directory through smartscaf and return a map of path → content.
|
|
*/
|
|
private async renderTemplate(templateName: string): Promise<Map<string, string>> {
|
|
const templateDir = plugins.path.join(paths.templatesDir, templateName);
|
|
|
|
const scafTemplate = new plugins.smartscaf.ScafTemplate(templateDir);
|
|
await scafTemplate.readTemplateFromDir();
|
|
|
|
const gitzoneData = this.project.gitzoneConfig?.data;
|
|
if (gitzoneData) {
|
|
await scafTemplate.supplyVariables({
|
|
module: gitzoneData.module,
|
|
projectType: gitzoneData.projectType,
|
|
});
|
|
}
|
|
|
|
const renderedFiles = await scafTemplate.renderToMemory();
|
|
|
|
const fileMap = new Map<string, string>();
|
|
for (const file of renderedFiles) {
|
|
fileMap.set(file.path, file.contents.toString());
|
|
}
|
|
return fileMap;
|
|
}
|
|
|
|
async analyze(): Promise<IPlannedChange[]> {
|
|
const changes: IPlannedChange[] = [];
|
|
const project = this.project;
|
|
const projectType = project.gitzoneConfig?.data?.projectType;
|
|
|
|
// VSCode template - for all projects
|
|
const vscodeChanges = await this.analyzeTemplate('vscode', [
|
|
{ templatePath: '.vscode/settings.json', destPath: '.vscode/settings.json' },
|
|
{ templatePath: '.vscode/launch.json', destPath: '.vscode/launch.json' },
|
|
]);
|
|
changes.push(...vscodeChanges);
|
|
|
|
// CI and other templates based on projectType
|
|
switch (projectType) {
|
|
case 'npm':
|
|
case 'wcc':
|
|
const accessLevel = (project.gitzoneConfig?.data as any)?.release?.accessLevel
|
|
|| project.gitzoneConfig?.data?.npmciOptions?.npmAccessLevel;
|
|
const ciTemplate = accessLevel === 'public' ? 'ci_default' : 'ci_default_private';
|
|
const ciChanges = await this.analyzeTemplate(ciTemplate, [
|
|
{ templatePath: '.gitea/workflows/default_nottags.yaml', destPath: '.gitea/workflows/default_nottags.yaml' },
|
|
{ templatePath: '.gitea/workflows/default_tags.yaml', destPath: '.gitea/workflows/default_tags.yaml' },
|
|
]);
|
|
changes.push(...ciChanges);
|
|
break;
|
|
|
|
case 'service':
|
|
case 'website':
|
|
const dockerCiChanges = await this.analyzeTemplate('ci_docker', [
|
|
{ templatePath: '.gitea/workflows/docker_nottags.yaml', destPath: '.gitea/workflows/docker_nottags.yaml' },
|
|
{ templatePath: '.gitea/workflows/docker_tags.yaml', destPath: '.gitea/workflows/docker_tags.yaml' },
|
|
]);
|
|
changes.push(...dockerCiChanges);
|
|
|
|
const dockerfileChanges = await this.analyzeTemplate('dockerfile_service', [
|
|
{ templatePath: 'Dockerfile', destPath: 'Dockerfile' },
|
|
{ templatePath: 'dockerignore', destPath: '.dockerignore' },
|
|
]);
|
|
changes.push(...dockerfileChanges);
|
|
|
|
const cliChanges = await this.analyzeTemplate('cli', [
|
|
{ templatePath: 'cli.js', destPath: 'cli.js' },
|
|
{ templatePath: 'cli.ts.js', destPath: 'cli.ts.js' },
|
|
]);
|
|
changes.push(...cliChanges);
|
|
break;
|
|
}
|
|
|
|
// Update templates based on projectType
|
|
if (projectType === 'website') {
|
|
const websiteChanges = await this.analyzeTemplate('website_update', [
|
|
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
|
]);
|
|
changes.push(...websiteChanges);
|
|
} else if (projectType === 'wcc') {
|
|
const wccChanges = await this.analyzeTemplate('wcc_update', [
|
|
{ templatePath: 'html/index.html', destPath: 'html/index.html' },
|
|
{ templatePath: 'html/index.ts', destPath: 'html/index.ts' },
|
|
]);
|
|
changes.push(...wccChanges);
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
private async analyzeTemplate(
|
|
templateName: string,
|
|
files: Array<{ templatePath: string; destPath: string }>,
|
|
): Promise<IPlannedChange[]> {
|
|
const changes: IPlannedChange[] = [];
|
|
const templateDir = plugins.path.join(paths.templatesDir, templateName);
|
|
|
|
const templateExists = await plugins.smartfs.directory(templateDir).exists();
|
|
if (!templateExists) {
|
|
logVerbose(`Template ${templateName} not found`);
|
|
return changes;
|
|
}
|
|
|
|
let renderedFiles: Map<string, string>;
|
|
try {
|
|
renderedFiles = await this.renderTemplate(templateName);
|
|
} catch (error) {
|
|
logVerbose(`Failed to render template ${templateName}: ${error.message}`);
|
|
return changes;
|
|
}
|
|
|
|
for (const file of files) {
|
|
// Look up by templatePath first, then destPath (frontmatter may rename files)
|
|
const processedContent = renderedFiles.get(file.templatePath)
|
|
|| renderedFiles.get(file.destPath);
|
|
|
|
if (!processedContent) {
|
|
logVerbose(`Template file ${file.templatePath} not found in rendered output`);
|
|
continue;
|
|
}
|
|
|
|
const destExists = await plugins.smartfs.file(file.destPath).exists();
|
|
let currentContent = '';
|
|
if (destExists) {
|
|
currentContent = (await plugins.smartfs
|
|
.file(file.destPath)
|
|
.encoding('utf8')
|
|
.read()) as string;
|
|
}
|
|
|
|
if (processedContent !== currentContent) {
|
|
changes.push({
|
|
type: destExists ? 'modify' : 'create',
|
|
path: file.destPath,
|
|
module: this.name,
|
|
description: `Apply template ${templateName}/${file.templatePath}`,
|
|
content: processedContent,
|
|
});
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
async applyChange(change: IPlannedChange): Promise<void> {
|
|
if (!change.content) return;
|
|
|
|
if (change.type === 'create') {
|
|
await this.createFile(change.path, change.content);
|
|
} else {
|
|
await this.modifyFile(change.path, change.content);
|
|
}
|
|
logger.log('info', `Applied template to ${change.path}`);
|
|
}
|
|
}
|