Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f43f88a3cb | |||
| 4c86ad62fb | |||
| 4214a1fdf1 | |||
| 1c33735799 | |||
| 274405e364 | |||
| bf858c8650 | |||
| b257c82bd6 | |||
| 5a1f6d8c76 | |||
| d44ad6e4e4 | |||
| 142adfd396 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"json.schemas": [
|
"json.schemas": [
|
||||||
{
|
{
|
||||||
"fileMatch": ["/npmextra.json"],
|
"fileMatch": ["/.smartconfig.json"],
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1,5 +1,35 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-04-16 - 2.13.16 - fix(mod_format)
|
||||||
|
stop package.json formatter from modifying buildDocs and dependency entries
|
||||||
|
|
||||||
|
- removes automatic buildDocs script injection from the package.json formatter
|
||||||
|
- removes dependency include/exclude and latest-version update logic from package.json formatting
|
||||||
|
- drops the unused smartnpm plugin import after removing registry lookups
|
||||||
|
|
||||||
|
## 2026-03-24 - 2.13.15 - fix(repo)
|
||||||
|
no changes to commit
|
||||||
|
|
||||||
|
|
||||||
|
## 2026-03-24 - 2.13.14 - fix(mod_format)
|
||||||
|
move smartconfig file renaming into the formatter orchestrator
|
||||||
|
|
||||||
|
- Renames smartconfig.json or npmextra.json to .smartconfig.json before formatters run
|
||||||
|
- Simplifies the smartconfig formatter to only read and modify .smartconfig.json
|
||||||
|
- Removes create/delete change planning for config renames and applies only content updates within the formatter
|
||||||
|
|
||||||
|
## 2026-03-24 - 2.13.13 - fix(vscode-template)
|
||||||
|
update VS Code schema matching to use .smartconfig.json
|
||||||
|
|
||||||
|
- Changes the VS Code template settings so the JSON schema applies to /.smartconfig.json instead of /npmextra.json.
|
||||||
|
|
||||||
|
## 2026-03-24 - 2.13.12 - fix(mod_format)
|
||||||
|
render format templates through smartscaf before comparing generated files
|
||||||
|
|
||||||
|
- adds smartscaf-based in-memory template rendering so supplied variables are applied before detecting changes
|
||||||
|
- supports release.accessLevel as a fallback when selecting public vs private CI templates
|
||||||
|
- matches rendered output by template or destination path to handle renamed files from template frontmatter
|
||||||
|
|
||||||
## 2026-03-24 - 2.13.10 - fix(config)
|
## 2026-03-24 - 2.13.10 - fix(config)
|
||||||
migrate configuration handling from npmextra to smartconfig
|
migrate configuration handling from npmextra to smartconfig
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/cli",
|
"name": "@git.zone/cli",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "2.13.11",
|
"version": "2.13.16",
|
||||||
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
|
||||||
"main": "dist_ts/index.ts",
|
"main": "dist_ts/index.ts",
|
||||||
"typings": "dist_ts/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
"@push.rocks/smartopen": "^2.0.0",
|
"@push.rocks/smartopen": "^2.0.0",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartscaf": "^4.0.19",
|
"@push.rocks/smartscaf": "^4.0.21",
|
||||||
"@push.rocks/smartshell": "^3.3.7",
|
"@push.rocks/smartshell": "^3.3.7",
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@push.rocks/smartupdate": "^2.0.6",
|
"@push.rocks/smartupdate": "^2.0.6",
|
||||||
|
|||||||
Generated
+5
-5
@@ -78,8 +78,8 @@ importers:
|
|||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3
|
version: 4.2.3
|
||||||
'@push.rocks/smartscaf':
|
'@push.rocks/smartscaf':
|
||||||
specifier: ^4.0.19
|
specifier: ^4.0.21
|
||||||
version: 4.0.19
|
version: 4.0.21
|
||||||
'@push.rocks/smartshell':
|
'@push.rocks/smartshell':
|
||||||
specifier: ^3.3.7
|
specifier: ^3.3.7
|
||||||
version: 3.3.7
|
version: 3.3.7
|
||||||
@@ -1216,8 +1216,8 @@ packages:
|
|||||||
'@push.rocks/smarts3@5.3.0':
|
'@push.rocks/smarts3@5.3.0':
|
||||||
resolution: {integrity: sha512-6bo55ovCDEylbTxwPFZYDrZrz2babQEUmxHIexmVcP2j+6LYRHDbGYnWoyKdtqniqDFZ04pFkOoZ85hUzU5xCw==}
|
resolution: {integrity: sha512-6bo55ovCDEylbTxwPFZYDrZrz2babQEUmxHIexmVcP2j+6LYRHDbGYnWoyKdtqniqDFZ04pFkOoZ85hUzU5xCw==}
|
||||||
|
|
||||||
'@push.rocks/smartscaf@4.0.19':
|
'@push.rocks/smartscaf@4.0.21':
|
||||||
resolution: {integrity: sha512-J9z2rWtFhT55kDZfWGpghnwy4aybmJoatzq5icfbZMXohS2KALuj1N17iXZ9L+43F1AwpUeZ/KnokNsh7yHZ5w==}
|
resolution: {integrity: sha512-vt+O/dp/uSTdfK2CtkgMSJenYwfZLMTi9IM9uHfkKd8DaI4UvqiFCO6/smkv08zWQlLBVlUD2UWVd7RM9h1BJg==}
|
||||||
|
|
||||||
'@push.rocks/smartserve@2.0.1':
|
'@push.rocks/smartserve@2.0.1':
|
||||||
resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==}
|
resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==}
|
||||||
@@ -6224,7 +6224,7 @@ snapshots:
|
|||||||
'@push.rocks/smartrust': 1.3.1
|
'@push.rocks/smartrust': 1.3.1
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
|
||||||
'@push.rocks/smartscaf@4.0.19':
|
'@push.rocks/smartscaf@4.0.21':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.3.1
|
'@push.rocks/lik': 6.3.1
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/cli',
|
name: '@git.zone/cli',
|
||||||
version: '2.13.10',
|
version: '2.13.16',
|
||||||
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,77 +4,6 @@ import * as plugins from '../mod.plugins.js';
|
|||||||
import * as paths from '../../paths.js';
|
import * as paths from '../../paths.js';
|
||||||
import { logger, logVerbose } from '../../gitzone.logging.js';
|
import { logger, logVerbose } from '../../gitzone.logging.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures a certain dependency exists or is excluded
|
|
||||||
*/
|
|
||||||
const ensureDependency = async (
|
|
||||||
packageJsonObject: any,
|
|
||||||
position: 'dep' | 'devDep' | 'everywhere',
|
|
||||||
constraint: 'exclude' | 'include' | 'latest',
|
|
||||||
dependencyArg: string,
|
|
||||||
): Promise<void> => {
|
|
||||||
// Parse package name and version, handling scoped packages like @scope/name@version
|
|
||||||
const isScoped = dependencyArg.startsWith('@');
|
|
||||||
const lastAtIndex = dependencyArg.lastIndexOf('@');
|
|
||||||
|
|
||||||
// For scoped packages, the version @ must come after the /
|
|
||||||
// For unscoped packages, any @ indicates a version
|
|
||||||
const hasVersion = isScoped
|
|
||||||
? lastAtIndex > dependencyArg.indexOf('/')
|
|
||||||
: lastAtIndex >= 0;
|
|
||||||
|
|
||||||
const packageName = hasVersion ? dependencyArg.slice(0, lastAtIndex) : dependencyArg;
|
|
||||||
const version = hasVersion ? dependencyArg.slice(lastAtIndex + 1) : 'latest';
|
|
||||||
|
|
||||||
const targetSections: string[] = [];
|
|
||||||
|
|
||||||
switch (position) {
|
|
||||||
case 'dep':
|
|
||||||
targetSections.push('dependencies');
|
|
||||||
break;
|
|
||||||
case 'devDep':
|
|
||||||
targetSections.push('devDependencies');
|
|
||||||
break;
|
|
||||||
case 'everywhere':
|
|
||||||
targetSections.push('dependencies', 'devDependencies');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const section of targetSections) {
|
|
||||||
if (!packageJsonObject[section]) {
|
|
||||||
packageJsonObject[section] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (constraint) {
|
|
||||||
case 'exclude':
|
|
||||||
delete packageJsonObject[section][packageName];
|
|
||||||
break;
|
|
||||||
case 'include':
|
|
||||||
if (!packageJsonObject[section][packageName]) {
|
|
||||||
packageJsonObject[section][packageName] =
|
|
||||||
version === 'latest' ? '^1.0.0' : version;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'latest':
|
|
||||||
try {
|
|
||||||
const registry = new plugins.smartnpm.NpmRegistry();
|
|
||||||
const packageInfo = await registry.getPackageInfo(packageName);
|
|
||||||
const latestVersion = packageInfo['dist-tags'].latest;
|
|
||||||
packageJsonObject[section][packageName] = `^${latestVersion}`;
|
|
||||||
} catch (error) {
|
|
||||||
logVerbose(
|
|
||||||
`Could not fetch latest version for ${packageName}, using existing or default`,
|
|
||||||
);
|
|
||||||
if (!packageJsonObject[section][packageName]) {
|
|
||||||
packageJsonObject[section][packageName] =
|
|
||||||
version === 'latest' ? '^1.0.0' : version;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export class PackageJsonFormatter extends BaseFormatter {
|
export class PackageJsonFormatter extends BaseFormatter {
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return 'packagejson';
|
return 'packagejson';
|
||||||
@@ -141,11 +70,6 @@ export class PackageJsonFormatter extends BaseFormatter {
|
|||||||
packageJson.scripts.build = `echo "Not needed for now"`;
|
packageJson.scripts.build = `echo "Not needed for now"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure buildDocs script exists
|
|
||||||
if (!packageJson.scripts.buildDocs) {
|
|
||||||
packageJson.scripts.buildDocs = `tsdoc`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set files array
|
// Set files array
|
||||||
packageJson.files = [
|
packageJson.files = [
|
||||||
'ts/**/*',
|
'ts/**/*',
|
||||||
@@ -160,21 +84,6 @@ export class PackageJsonFormatter extends BaseFormatter {
|
|||||||
'readme.md',
|
'readme.md',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Handle dependencies
|
|
||||||
await ensureDependency(
|
|
||||||
packageJson,
|
|
||||||
'devDep',
|
|
||||||
'exclude',
|
|
||||||
'@push.rocks/tapbundle',
|
|
||||||
);
|
|
||||||
await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tstest');
|
|
||||||
await ensureDependency(
|
|
||||||
packageJson,
|
|
||||||
'devDep',
|
|
||||||
'latest',
|
|
||||||
'@git.zone/tsbuild',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set pnpm overrides from assets
|
// Set pnpm overrides from assets
|
||||||
try {
|
try {
|
||||||
const overridesContent = (await plugins.smartfs
|
const overridesContent = (await plugins.smartfs
|
||||||
|
|||||||
@@ -61,49 +61,33 @@ const migrateAccessLevel = (smartconfigJson: any): boolean => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Config file names in priority order (newest → oldest)
|
const CONFIG_FILE = '.smartconfig.json';
|
||||||
const CONFIG_FILE_NAMES = ['.smartconfig.json', 'smartconfig.json', 'npmextra.json'];
|
|
||||||
const TARGET_CONFIG_FILE = '.smartconfig.json';
|
|
||||||
|
|
||||||
export class SmartconfigFormatter extends BaseFormatter {
|
export class SmartconfigFormatter extends BaseFormatter {
|
||||||
get name(): string {
|
get name(): string {
|
||||||
return 'smartconfig';
|
return 'smartconfig';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the config file, checking in priority order.
|
|
||||||
* Returns the path and whether it needs renaming.
|
|
||||||
*/
|
|
||||||
private async findConfigFile(): Promise<{ path: string; needsRename: boolean } | null> {
|
|
||||||
for (const filename of CONFIG_FILE_NAMES) {
|
|
||||||
const exists = await plugins.smartfs.file(filename).exists();
|
|
||||||
if (exists) {
|
|
||||||
return {
|
|
||||||
path: filename,
|
|
||||||
needsRename: filename !== TARGET_CONFIG_FILE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async analyze(): Promise<IPlannedChange[]> {
|
async analyze(): Promise<IPlannedChange[]> {
|
||||||
const changes: IPlannedChange[] = [];
|
const changes: IPlannedChange[] = [];
|
||||||
|
|
||||||
const configFile = await this.findConfigFile();
|
// File rename (npmextra.json/smartconfig.json → .smartconfig.json)
|
||||||
if (!configFile) {
|
// is handled by the orchestrator before analysis.
|
||||||
logVerbose('No config file found (.smartconfig.json, smartconfig.json, or npmextra.json), skipping');
|
// This formatter only operates on .smartconfig.json.
|
||||||
|
const exists = await plugins.smartfs.file(CONFIG_FILE).exists();
|
||||||
|
if (!exists) {
|
||||||
|
logVerbose('.smartconfig.json does not exist, skipping');
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read current content
|
|
||||||
const currentContent = (await plugins.smartfs
|
const currentContent = (await plugins.smartfs
|
||||||
.file(configFile.path)
|
.file(CONFIG_FILE)
|
||||||
.encoding('utf8')
|
.encoding('utf8')
|
||||||
.read()) as string;
|
.read()) as string;
|
||||||
|
|
||||||
// Parse and apply migrations
|
|
||||||
const smartconfigJson = JSON.parse(currentContent);
|
const smartconfigJson = JSON.parse(currentContent);
|
||||||
|
|
||||||
|
// Apply key migrations
|
||||||
migrateNamespaceKeys(smartconfigJson);
|
migrateNamespaceKeys(smartconfigJson);
|
||||||
migrateAccessLevel(smartconfigJson);
|
migrateAccessLevel(smartconfigJson);
|
||||||
|
|
||||||
@@ -117,26 +101,10 @@ export class SmartconfigFormatter extends BaseFormatter {
|
|||||||
|
|
||||||
const newContent = JSON.stringify(smartconfigJson, null, 2);
|
const newContent = JSON.stringify(smartconfigJson, null, 2);
|
||||||
|
|
||||||
// If file needs renaming, plan a create + delete
|
if (newContent !== currentContent) {
|
||||||
if (configFile.needsRename) {
|
|
||||||
changes.push({
|
|
||||||
type: 'create',
|
|
||||||
path: TARGET_CONFIG_FILE,
|
|
||||||
module: this.name,
|
|
||||||
description: `Migrate ${configFile.path} to ${TARGET_CONFIG_FILE}`,
|
|
||||||
content: newContent,
|
|
||||||
});
|
|
||||||
changes.push({
|
|
||||||
type: 'delete',
|
|
||||||
path: configFile.path,
|
|
||||||
module: this.name,
|
|
||||||
description: `Remove old ${configFile.path}`,
|
|
||||||
});
|
|
||||||
} else if (newContent !== currentContent) {
|
|
||||||
// File is already .smartconfig.json, just needs content update
|
|
||||||
changes.push({
|
changes.push({
|
||||||
type: 'modify',
|
type: 'modify',
|
||||||
path: TARGET_CONFIG_FILE,
|
path: CONFIG_FILE,
|
||||||
module: this.name,
|
module: this.name,
|
||||||
description: 'Migrate and format .smartconfig.json',
|
description: 'Migrate and format .smartconfig.json',
|
||||||
content: newContent,
|
content: newContent,
|
||||||
@@ -147,17 +115,11 @@ export class SmartconfigFormatter extends BaseFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async applyChange(change: IPlannedChange): Promise<void> {
|
async applyChange(change: IPlannedChange): Promise<void> {
|
||||||
if (change.type === 'delete') {
|
if (change.type !== 'modify' || !change.content) return;
|
||||||
await this.deleteFile(change.path);
|
|
||||||
logger.log('info', `Removed old config file ${change.path}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!change.content) return;
|
|
||||||
|
|
||||||
// Parse the content to check for missing required fields
|
|
||||||
const smartconfigJson = JSON.parse(change.content);
|
const smartconfigJson = JSON.parse(change.content);
|
||||||
|
|
||||||
|
// Check for missing required module information
|
||||||
const expectedRepoInformation: string[] = [
|
const expectedRepoInformation: string[] = [
|
||||||
'projectType',
|
'projectType',
|
||||||
'module.githost',
|
'module.githost',
|
||||||
@@ -202,12 +164,7 @@ export class SmartconfigFormatter extends BaseFormatter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const finalContent = JSON.stringify(smartconfigJson, null, 2);
|
const finalContent = JSON.stringify(smartconfigJson, null, 2);
|
||||||
|
|
||||||
if (change.type === 'create') {
|
|
||||||
await this.createFile(change.path, finalContent);
|
|
||||||
} else {
|
|
||||||
await this.modifyFile(change.path, finalContent);
|
await this.modifyFile(change.path, finalContent);
|
||||||
}
|
logger.log('info', 'Updated .smartconfig.json');
|
||||||
logger.log('info', `Updated ${change.path}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,32 @@ export class TemplatesFormatter extends BaseFormatter {
|
|||||||
return 'templates';
|
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[]> {
|
async analyze(): Promise<IPlannedChange[]> {
|
||||||
const changes: IPlannedChange[] = [];
|
const changes: IPlannedChange[] = [];
|
||||||
const project = this.project;
|
const project = this.project;
|
||||||
@@ -25,7 +51,8 @@ export class TemplatesFormatter extends BaseFormatter {
|
|||||||
switch (projectType) {
|
switch (projectType) {
|
||||||
case 'npm':
|
case 'npm':
|
||||||
case 'wcc':
|
case 'wcc':
|
||||||
const accessLevel = project.gitzoneConfig?.data?.npmciOptions?.npmAccessLevel;
|
const accessLevel = (project.gitzoneConfig?.data as any)?.release?.accessLevel
|
||||||
|
|| project.gitzoneConfig?.data?.npmciOptions?.npmAccessLevel;
|
||||||
const ciTemplate = accessLevel === 'public' ? 'ci_default' : 'ci_default_private';
|
const ciTemplate = accessLevel === 'public' ? 'ci_default' : 'ci_default_private';
|
||||||
const ciChanges = await this.analyzeTemplate(ciTemplate, [
|
const ciChanges = await this.analyzeTemplate(ciTemplate, [
|
||||||
{ templatePath: '.gitea/workflows/default_nottags.yaml', destPath: '.gitea/workflows/default_nottags.yaml' },
|
{ templatePath: '.gitea/workflows/default_nottags.yaml', destPath: '.gitea/workflows/default_nottags.yaml' },
|
||||||
@@ -80,54 +107,48 @@ export class TemplatesFormatter extends BaseFormatter {
|
|||||||
const changes: IPlannedChange[] = [];
|
const changes: IPlannedChange[] = [];
|
||||||
const templateDir = plugins.path.join(paths.templatesDir, templateName);
|
const templateDir = plugins.path.join(paths.templatesDir, templateName);
|
||||||
|
|
||||||
// Check if template exists
|
|
||||||
const templateExists = await plugins.smartfs.directory(templateDir).exists();
|
const templateExists = await plugins.smartfs.directory(templateDir).exists();
|
||||||
if (!templateExists) {
|
if (!templateExists) {
|
||||||
logVerbose(`Template ${templateName} not found`);
|
logVerbose(`Template ${templateName} not found`);
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
let renderedFiles: Map<string, string>;
|
||||||
const templateFilePath = plugins.path.join(templateDir, file.templatePath);
|
try {
|
||||||
const destFilePath = file.destPath;
|
renderedFiles = await this.renderTemplate(templateName);
|
||||||
|
} catch (error) {
|
||||||
|
logVerbose(`Failed to render template ${templateName}: ${error.message}`);
|
||||||
|
return changes;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if template file exists
|
for (const file of files) {
|
||||||
const fileExists = await plugins.smartfs.file(templateFilePath).exists();
|
// Look up by templatePath first, then destPath (frontmatter may rename files)
|
||||||
if (!fileExists) {
|
const processedContent = renderedFiles.get(file.templatePath)
|
||||||
logVerbose(`Template file ${templateFilePath} not found`);
|
|| renderedFiles.get(file.destPath);
|
||||||
|
|
||||||
|
if (!processedContent) {
|
||||||
|
logVerbose(`Template file ${file.templatePath} not found in rendered output`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const destExists = await plugins.smartfs.file(file.destPath).exists();
|
||||||
// Read template content
|
|
||||||
const templateContent = (await plugins.smartfs
|
|
||||||
.file(templateFilePath)
|
|
||||||
.encoding('utf8')
|
|
||||||
.read()) as string;
|
|
||||||
|
|
||||||
// Check if destination file exists
|
|
||||||
const destExists = await plugins.smartfs.file(destFilePath).exists();
|
|
||||||
let currentContent = '';
|
let currentContent = '';
|
||||||
if (destExists) {
|
if (destExists) {
|
||||||
currentContent = (await plugins.smartfs
|
currentContent = (await plugins.smartfs
|
||||||
.file(destFilePath)
|
.file(file.destPath)
|
||||||
.encoding('utf8')
|
.encoding('utf8')
|
||||||
.read()) as string;
|
.read()) as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only add change if content differs
|
if (processedContent !== currentContent) {
|
||||||
if (templateContent !== currentContent) {
|
|
||||||
changes.push({
|
changes.push({
|
||||||
type: destExists ? 'modify' : 'create',
|
type: destExists ? 'modify' : 'create',
|
||||||
path: destFilePath,
|
path: file.destPath,
|
||||||
module: this.name,
|
module: this.name,
|
||||||
description: `Apply template ${templateName}/${file.templatePath}`,
|
description: `Apply template ${templateName}/${file.templatePath}`,
|
||||||
content: templateContent,
|
content: processedContent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
logVerbose(`Failed to read template ${templateFilePath}: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
|
|||||||
@@ -16,6 +16,27 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js';
|
|||||||
import { ReadmeFormatter } from './formatters/readme.formatter.js';
|
import { ReadmeFormatter } from './formatters/readme.formatter.js';
|
||||||
import { CopyFormatter } from './formatters/copy.formatter.js';
|
import { CopyFormatter } from './formatters/copy.formatter.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename npmextra.json or smartconfig.json to .smartconfig.json
|
||||||
|
* before any formatter tries to read config.
|
||||||
|
*/
|
||||||
|
async function migrateConfigFile(): Promise<void> {
|
||||||
|
const target = '.smartconfig.json';
|
||||||
|
const targetExists = await plugins.smartfs.file(target).exists();
|
||||||
|
if (targetExists) return;
|
||||||
|
|
||||||
|
for (const oldName of ['smartconfig.json', 'npmextra.json']) {
|
||||||
|
const exists = await plugins.smartfs.file(oldName).exists();
|
||||||
|
if (exists) {
|
||||||
|
const content = await plugins.smartfs.file(oldName).encoding('utf8').read() as string;
|
||||||
|
await plugins.smartfs.file(`./${target}`).encoding('utf8').write(content);
|
||||||
|
await plugins.smartfs.file(oldName).delete();
|
||||||
|
logger.log('info', `Migrated ${oldName} to ${target}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Shared formatter class map used by both run() and runFormatter()
|
// Shared formatter class map used by both run() and runFormatter()
|
||||||
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
|
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
|
||||||
cleanup: CleanupFormatter,
|
cleanup: CleanupFormatter,
|
||||||
@@ -53,6 +74,9 @@ export let run = async (
|
|||||||
|
|
||||||
const shouldWrite = options.write ?? (options.dryRun === false);
|
const shouldWrite = options.write ?? (options.dryRun === false);
|
||||||
|
|
||||||
|
// Migrate config file before anything reads it
|
||||||
|
await migrateConfigFile();
|
||||||
|
|
||||||
const project = await Project.fromCwd({ requireProjectType: false });
|
const project = await Project.fromCwd({ requireProjectType: false });
|
||||||
const context = new FormatContext();
|
const context = new FormatContext();
|
||||||
const planner = new FormatPlanner();
|
const planner = new FormatPlanner();
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import * as smartfile from '@push.rocks/smartfile';
|
|||||||
import * as smartinteract from '@push.rocks/smartinteract';
|
import * as smartinteract from '@push.rocks/smartinteract';
|
||||||
import * as smartlegal from '@push.rocks/smartlegal';
|
import * as smartlegal from '@push.rocks/smartlegal';
|
||||||
import * as smartobject from '@push.rocks/smartobject';
|
import * as smartobject from '@push.rocks/smartobject';
|
||||||
import * as smartnpm from '@push.rocks/smartnpm';
|
|
||||||
import * as smartconfig from '@push.rocks/smartconfig';
|
import * as smartconfig from '@push.rocks/smartconfig';
|
||||||
import * as smartdiff from '@push.rocks/smartdiff';
|
import * as smartdiff from '@push.rocks/smartdiff';
|
||||||
|
import * as smartscaf from '@push.rocks/smartscaf';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
path,
|
path,
|
||||||
@@ -15,7 +15,7 @@ export {
|
|||||||
smartinteract,
|
smartinteract,
|
||||||
smartlegal,
|
smartlegal,
|
||||||
smartobject,
|
smartobject,
|
||||||
smartnpm,
|
|
||||||
smartconfig,
|
smartconfig,
|
||||||
smartdiff,
|
smartdiff,
|
||||||
|
smartscaf,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user