Skip to content

Commit

Permalink
chore(repo): update cli process
Browse files Browse the repository at this point in the history
  • Loading branch information
rellafella committed Feb 17, 2025
1 parent 2084e14 commit 5d1ab87
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 65 deletions.
41 changes: 23 additions & 18 deletions packages/repo-config/package.json
Original file line number Diff line number Diff line change
@@ -1,49 +1,54 @@
{
"name": "@envsa/repo-config",
"version": "8.2.0",
"type": "module",
"description": "Repository configuration and GitHub workflows for @envsa/shared-config.",
"keywords": [
"shared-config",
"github-actions",
"cli",
"envsa",
"envsa-repo"
],
"homepage": "https://github.com/envsa/shared-config/packages/repo-config",
"bugs": {
"url": "https://github.com/envsa/shared-config/issues",
"email": "[email protected]"
},
"repository": {
"type": "git",
"url": "[email protected]:envsa/shared-config.git",
"directory": "packages/repo-config"
},
"bugs": {
"url": "https://github.com/envsa/shared-config/issues",
"email": "[email protected]"
},
"license": "MIT",
"author": {
"name": "Liam Rella",
"email": "[email protected]",
"url": "https://github.com/rellafella"
},
"license": "MIT",
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"type": "module",
"bin": {
"repo-config": "bin/cli.js"
"envsa-repo": "bin/cli.js"
},
"files": [
"bin/*",
"init/*"
],
"keywords": [
"shared-config",
"github-actions",
"cli"
],
"scripts": {
"build": "../../scripts/build.ts && mdat readme",
"build": "../../scripts/build.ts",
"cli": "node ./bin/cli.js",
"prepublishOnly": "pnpm run build"
},
"dependencies": {
"@pinojs/json-colorizer": "^4.0.0",
"cosmiconfig": "^9.0.0",
"execa": "^9.5.2",
"fs-extra": "^11.2.0"
"find-workspaces": "^0.3.1",
"fs-extra": "^11.2.0",
"prettier": "^3.5.1"
},
"engines": {
"node": ">=22.0.0",
"pnpm": ">=10.0.0"
},
"publishConfig": {
"access": "public"
Expand Down
50 changes: 3 additions & 47 deletions packages/repo-config/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,5 @@
#!/usr/bin/env node
import { buildCommands } from '$root/src/command-builder.ts';
import fse from 'fs-extra';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { packageUp } from 'package-up';
import { buildCommands } from '../../../src/command-builder.js';
import { commandDefinition } from './command.js';

await buildCommands('repo-config', '[Repo Config]', 'gray', {
init: {},
printConfig: {
async command(logStream) {
const destinationPackage = await packageUp();
if (destinationPackage === undefined) {
logStream.write(
'Error: The `--print-config` flag must be used in a directory with a package.json file somewhere above it\n',
);
return 1;
}

const sourcePackage = await packageUp({ cwd: fileURLToPath(import.meta.url) });
if (sourcePackage === undefined) {
logStream.write('Error: The script being called was not in a package, weird.\n');
return 1;
}

const sourceDirectory = path.join(path.dirname(sourcePackage), 'init/');
const destinationDirectory = path.dirname(destinationPackage);

let exitCode = 0;

for (const file of await fse.readdir(sourceDirectory)) {
const destinationPath = path.join(destinationDirectory, file);

// Merge with existing, if present
if (await fse.exists(destinationPath)) {
const fileContent = await fse.readFile(destinationPath, 'utf8');
logStream.write(`💾 Contents of "${file}":\n`);
logStream.write(fileContent);
logStream.write('\n');
} else {
logStream.write(`Error: Could not find ${file}\n`);
exitCode = 1;
}
}

return exitCode;
},
},
});
await buildCommands(commandDefinition);
35 changes: 35 additions & 0 deletions packages/repo-config/src/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type CommandDefinition, DESCRIPTION } from '../../../src/command-builder.js';
import { copyrightYearFixerCommand, copyrightYearLinterCommand } from './copyright-year-updater.js';

export const commandDefinition: CommandDefinition = {
commands: {
fix: {
commands: [
{
execute: copyrightYearFixerCommand,
name: copyrightYearLinterCommand.name,
},
],
description: `Fix common issues like outdated copyright years in license files. ${DESCRIPTION.packageRun} ${DESCRIPTION.monorepoRun}`,
positionalArgumentMode: 'none',
},
init: {
locationOptionFlag: false,
},
lint: {
commands: [
{
execute: copyrightYearLinterCommand,
name: copyrightYearFixerCommand.name,
},
],
description: `Check the repo for common issues. ${DESCRIPTION.packageRun} ${DESCRIPTION.monorepoRun}`,
positionalArgumentMode: 'none',
},
},
description: "Envsa's repository-related shared configuration tools.",
logColor: 'gray',
logPrefix: '[Repo Config]',
name: 'envsa-repo',
order: 1,
};
102 changes: 102 additions & 0 deletions packages/repo-config/src/copyright-year-updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import fs from 'node:fs/promises';
import { restoreNodeWarnings, suppressNodeWarnings } from '../../../src/node-utils';
import { getPackageDirectory } from '../../../src/path-utils';
import { pluralize } from '../../../src/string-utils';

function updateLicenseContent(content: string, currentYear: number): string {
// Match a four-digit range with optional spaces around the dash.
const rangeRegex = /(\d{4})\s*-\s*(\d{4})/;
const rangeMatch = rangeRegex.exec(content);

if (rangeMatch) {
const [, startYear, endYear] = rangeMatch;
if (Number.parseInt(endYear, 10) !== currentYear) {
const newRange = `${startYear}-${currentYear}`;
return content.replace(rangeRegex, newRange);
}
return content;
}

// If no range was found, try matching a single four-digit year.
const singleYearRegex = /(\d{4})/;
const singleMatch = singleYearRegex.exec(content);
if (singleMatch) {
const [, year] = singleMatch;
if (Number.parseInt(year, 10) !== currentYear) {
const newRange = `${year}-${currentYear}`;
return content.replace(singleYearRegex, newRange);
}
return content;
}

// Return original content if no year was found.
return content;
}

async function copyrightYear(logStream: NodeJS.WritableStream, fix = false): Promise<number> {
const currentYear = new Date().getFullYear();
const licenseFiles: string[] = [];

// Use multiple glob patterns to cover different casings for "license.txt"
const patterns = [
'**/license.txt',
'**/LICENSE.txt',
'**/License.txt',
'**/LICENSE',
'!node_modules/**',
];

suppressNodeWarnings();
for await (const filePath of fs.glob(patterns, {
cwd: getPackageDirectory(), // Will find monorepo packages
withFileTypes: false,
})) {
licenseFiles.push(filePath);
}
restoreNodeWarnings();

const outdatedLicenseFiles: string[] = [];

for (const filePath of licenseFiles) {
try {
const originalContent = await fs.readFile(filePath, 'utf8');
const updatedContent = updateLicenseContent(originalContent, currentYear);

if (updatedContent !== originalContent) {
outdatedLicenseFiles.push(filePath);
if (fix) {
await fs.writeFile(filePath, updatedContent, 'utf8');
}
}
} catch (error) {
console.error(`Failed to process ${filePath}:`, error);
}
}

if (outdatedLicenseFiles.length > 0) {
logStream.write(
`${fix ? 'Fixed' : 'Found'} ${outdatedLicenseFiles.length} license ${pluralize('file', outdatedLicenseFiles.length)} with outdated copyright year:\n`,
);
for (const outdatedLicenseFile of outdatedLicenseFiles) {
logStream.write(` - ${outdatedLicenseFile}\n`);
}

return fix ? 0 : 1;
}

return 0;
}

/**
* Linter for the year in license files.
*/
export async function copyrightYearLinterCommand(logStream: NodeJS.WritableStream) {
return copyrightYear(logStream, false);
}

/**
* Fixer for the year in license files.
*/
export async function copyrightYearFixerCommand(logStream: NodeJS.WritableStream) {
return copyrightYear(logStream, true);
}
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5d1ab87

Please sign in to comment.