Compare commits

..

6 Commits

Author SHA1 Message Date
274405e364 v2.13.14
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-24 19:59:13 +00:00
bf858c8650 fix(mod_format): move smartconfig file renaming into the formatter orchestrator 2026-03-24 19:59:13 +00:00
b257c82bd6 v2.13.13
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-24 19:42:12 +00:00
5a1f6d8c76 fix(vscode-template): update VS Code schema matching to use .smartconfig.json 2026-03-24 19:42:12 +00:00
d44ad6e4e4 v2.13.12
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-24 16:56:34 +00:00
142adfd396 fix(mod_format): render format templates through smartscaf before comparing generated files 2026-03-24 16:56:34 +00:00
9 changed files with 126 additions and 103 deletions

View File

@@ -1,7 +1,7 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"fileMatch": ["/.smartconfig.json"],
"schema": {
"type": "object",
"properties": {

View File

@@ -1,5 +1,24 @@
# Changelog
## 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)
migrate configuration handling from npmextra to smartconfig

View File

@@ -1,7 +1,7 @@
{
"name": "@git.zone/cli",
"private": false,
"version": "2.13.11",
"version": "2.13.14",
"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",
"typings": "dist_ts/index.d.ts",
@@ -86,7 +86,7 @@
"@push.rocks/smartopen": "^2.0.0",
"@push.rocks/smartpath": "^6.0.0",
"@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/smartunique": "^3.0.9",
"@push.rocks/smartupdate": "^2.0.6",

10
pnpm-lock.yaml generated
View File

@@ -78,8 +78,8 @@ importers:
specifier: ^4.2.3
version: 4.2.3
'@push.rocks/smartscaf':
specifier: ^4.0.19
version: 4.0.19
specifier: ^4.0.21
version: 4.0.21
'@push.rocks/smartshell':
specifier: ^3.3.7
version: 3.3.7
@@ -1216,8 +1216,8 @@ packages:
'@push.rocks/smarts3@5.3.0':
resolution: {integrity: sha512-6bo55ovCDEylbTxwPFZYDrZrz2babQEUmxHIexmVcP2j+6LYRHDbGYnWoyKdtqniqDFZ04pFkOoZ85hUzU5xCw==}
'@push.rocks/smartscaf@4.0.19':
resolution: {integrity: sha512-J9z2rWtFhT55kDZfWGpghnwy4aybmJoatzq5icfbZMXohS2KALuj1N17iXZ9L+43F1AwpUeZ/KnokNsh7yHZ5w==}
'@push.rocks/smartscaf@4.0.21':
resolution: {integrity: sha512-vt+O/dp/uSTdfK2CtkgMSJenYwfZLMTi9IM9uHfkKd8DaI4UvqiFCO6/smkv08zWQlLBVlUD2UWVd7RM9h1BJg==}
'@push.rocks/smartserve@2.0.1':
resolution: {integrity: sha512-YQb2qexfCzCqOlLWBBXKMg6xG4zahCPAxomz/KEKAwHtW6wMTtuHKSTSkRTQ0vl9jssLMAmRz2OyafiL9XGJXQ==}
@@ -6224,7 +6224,7 @@ snapshots:
'@push.rocks/smartrust': 1.3.1
'@tsclass/tsclass': 9.3.0
'@push.rocks/smartscaf@4.0.19':
'@push.rocks/smartscaf@4.0.21':
dependencies:
'@push.rocks/lik': 6.3.1
'@push.rocks/smartfile': 11.2.7

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@git.zone/cli',
version: '2.13.10',
version: '2.13.14',
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.'
}

View File

@@ -61,49 +61,33 @@ const migrateAccessLevel = (smartconfigJson: any): boolean => {
return true;
};
// Config file names in priority order (newest → oldest)
const CONFIG_FILE_NAMES = ['.smartconfig.json', 'smartconfig.json', 'npmextra.json'];
const TARGET_CONFIG_FILE = '.smartconfig.json';
const CONFIG_FILE = '.smartconfig.json';
export class SmartconfigFormatter extends BaseFormatter {
get name(): string {
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[]> {
const changes: IPlannedChange[] = [];
const configFile = await this.findConfigFile();
if (!configFile) {
logVerbose('No config file found (.smartconfig.json, smartconfig.json, or npmextra.json), skipping');
// File rename (npmextra.json/smartconfig.json → .smartconfig.json)
// is handled by the orchestrator before analysis.
// 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;
}
// Read current content
const currentContent = (await plugins.smartfs
.file(configFile.path)
.file(CONFIG_FILE)
.encoding('utf8')
.read()) as string;
// Parse and apply migrations
const smartconfigJson = JSON.parse(currentContent);
// Apply key migrations
migrateNamespaceKeys(smartconfigJson);
migrateAccessLevel(smartconfigJson);
@@ -117,26 +101,10 @@ export class SmartconfigFormatter extends BaseFormatter {
const newContent = JSON.stringify(smartconfigJson, null, 2);
// If file needs renaming, plan a create + delete
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
if (newContent !== currentContent) {
changes.push({
type: 'modify',
path: TARGET_CONFIG_FILE,
path: CONFIG_FILE,
module: this.name,
description: 'Migrate and format .smartconfig.json',
content: newContent,
@@ -147,17 +115,11 @@ export class SmartconfigFormatter extends BaseFormatter {
}
async applyChange(change: IPlannedChange): Promise<void> {
if (change.type === 'delete') {
await this.deleteFile(change.path);
logger.log('info', `Removed old config file ${change.path}`);
return;
}
if (change.type !== 'modify' || !change.content) return;
if (!change.content) return;
// Parse the content to check for missing required fields
const smartconfigJson = JSON.parse(change.content);
// Check for missing required module information
const expectedRepoInformation: string[] = [
'projectType',
'module.githost',
@@ -202,12 +164,7 @@ export class SmartconfigFormatter extends BaseFormatter {
}
const finalContent = JSON.stringify(smartconfigJson, null, 2);
if (change.type === 'create') {
await this.createFile(change.path, finalContent);
} else {
await this.modifyFile(change.path, finalContent);
}
logger.log('info', `Updated ${change.path}`);
await this.modifyFile(change.path, finalContent);
logger.log('info', 'Updated .smartconfig.json');
}
}

View File

@@ -9,6 +9,32 @@ export class TemplatesFormatter extends BaseFormatter {
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;
@@ -25,7 +51,8 @@ export class TemplatesFormatter extends BaseFormatter {
switch (projectType) {
case 'npm':
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 ciChanges = await this.analyzeTemplate(ciTemplate, [
{ templatePath: '.gitea/workflows/default_nottags.yaml', destPath: '.gitea/workflows/default_nottags.yaml' },
@@ -80,53 +107,47 @@ export class TemplatesFormatter extends BaseFormatter {
const changes: IPlannedChange[] = [];
const templateDir = plugins.path.join(paths.templatesDir, templateName);
// Check if template exists
const templateExists = await plugins.smartfs.directory(templateDir).exists();
if (!templateExists) {
logVerbose(`Template ${templateName} not found`);
return changes;
}
for (const file of files) {
const templateFilePath = plugins.path.join(templateDir, file.templatePath);
const destFilePath = file.destPath;
let renderedFiles: Map<string, string>;
try {
renderedFiles = await this.renderTemplate(templateName);
} catch (error) {
logVerbose(`Failed to render template ${templateName}: ${error.message}`);
return changes;
}
// Check if template file exists
const fileExists = await plugins.smartfs.file(templateFilePath).exists();
if (!fileExists) {
logVerbose(`Template file ${templateFilePath} not found`);
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;
}
try {
// Read template content
const templateContent = (await plugins.smartfs
.file(templateFilePath)
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;
}
// Check if destination file exists
const destExists = await plugins.smartfs.file(destFilePath).exists();
let currentContent = '';
if (destExists) {
currentContent = (await plugins.smartfs
.file(destFilePath)
.encoding('utf8')
.read()) as string;
}
// Only add change if content differs
if (templateContent !== currentContent) {
changes.push({
type: destExists ? 'modify' : 'create',
path: destFilePath,
module: this.name,
description: `Apply template ${templateName}/${file.templatePath}`,
content: templateContent,
});
}
} catch (error) {
logVerbose(`Failed to read template ${templateFilePath}: ${error.message}`);
if (processedContent !== currentContent) {
changes.push({
type: destExists ? 'modify' : 'create',
path: file.destPath,
module: this.name,
description: `Apply template ${templateName}/${file.templatePath}`,
content: processedContent,
});
}
}

View File

@@ -16,6 +16,27 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js';
import { ReadmeFormatter } from './formatters/readme.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()
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
cleanup: CleanupFormatter,
@@ -53,6 +74,9 @@ export let run = async (
const shouldWrite = options.write ?? (options.dryRun === false);
// Migrate config file before anything reads it
await migrateConfigFile();
const project = await Project.fromCwd({ requireProjectType: false });
const context = new FormatContext();
const planner = new FormatPlanner();

View File

@@ -8,6 +8,7 @@ import * as smartobject from '@push.rocks/smartobject';
import * as smartnpm from '@push.rocks/smartnpm';
import * as smartconfig from '@push.rocks/smartconfig';
import * as smartdiff from '@push.rocks/smartdiff';
import * as smartscaf from '@push.rocks/smartscaf';
export {
path,
@@ -18,4 +19,5 @@ export {
smartnpm,
smartconfig,
smartdiff,
smartscaf,
};