Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
- implement unit tests for converters

# 1.1 Refinement
- Better cmd description and help texts
- Alternative config path via `--config` flag
- `--force` flag to overwrite existing output files
- `@error` tag should not be required in JSDoc comments with default config
- You can change this behavior via config if needed
- `sourceFile.requireErrorTag` config option
Expand Down
50 changes: 31 additions & 19 deletions bin/commands/init.command.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {SwerrCommand} from "./interfaces/swerr-command.js";
import path from "path";
import * as fs from "node:fs";
import {LogUtils} from "@swerr/core";
import {SWERR_CONFIG_FILE} from "../config.js";

const initConfigTemplate = `import {markdownConverter, htmlConverter} from "@swerr/converter"

// See more configuration options at https://swerr.apidocumentation.com/guide/introduction/config
export default {
sourceFile: {
inputDir: "./src/errors", // Directory to scan for error definitions
inputDir: "./src", // Directory to scan for error definitions
meta: {
projectName: "Your Application Name",
description: "The Application description",
Expand All @@ -19,7 +19,7 @@ export default {
},
options: {
ignoreDirs: [], // Directories to ignore during scanning (optional)
whitelistExtensions: [".js"] // File extensions to include during scanning (optional)
whitelistExtensions: [".js", ".ts"] // File extensions to include during scanning (optional)
}
},
converter: [ // Example converters
Expand All @@ -38,23 +38,35 @@ export default {
]
}`;

export const initCommand: SwerrCommand<[string, string]> = {
command: "init",
description: "Create a basic swerr config file.",
action: async () => {
const configFilePath = path.resolve(process.cwd(), SWERR_CONFIG_FILE);

if (fs.existsSync(configFilePath)) {
LogUtils.error(`A ${SWERR_CONFIG_FILE} file already exists in the current directory.`);
process.exit(1);
}

export const initCommand = async (options: { force?: boolean; config?: string }) => {
const configFilePath = path.resolve(process.cwd(), options.config ?? SWERR_CONFIG_FILE);
const targetName = path.basename(configFilePath);

const existedBefore = fs.existsSync(configFilePath);
if (existedBefore && !options.force) {
try {
await fs.promises.writeFile(configFilePath, initConfigTemplate);
LogUtils.success(SWERR_CONFIG_FILE + " file has been created successfully.");
} catch (err) {
LogUtils.error(`Failed to create ${SWERR_CONFIG_FILE} file: ${err}`);
process.exit(1);
const stat = fs.lstatSync(configFilePath);
if (stat.isDirectory()) {
LogUtils.error(`A directory named ${targetName} already exists in the current directory.`);
} else {
LogUtils.error(`A ${targetName} file already exists in the current directory.`);
}
} catch {
LogUtils.error(`A ${targetName} entry already exists in the current directory.`);
}
process.exit(1);
}

try {
await fs.promises.mkdir(path.dirname(configFilePath), { recursive: true });
await fs.promises.writeFile(configFilePath, initConfigTemplate, { encoding: "utf8" });
if (existedBefore && options.force) {
LogUtils.success(`${targetName} file has been overwritten successfully.`);
} else {
LogUtils.success(`${targetName} file has been created successfully.`);
}
} catch (err) {
LogUtils.error(`Failed to create ${targetName} file: ${err}`);
process.exit(1);
}
}
6 changes: 0 additions & 6 deletions bin/commands/interfaces/swerr-command.ts

This file was deleted.

161 changes: 81 additions & 80 deletions bin/commands/run.command.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {SwerrCommand} from "./interfaces/swerr-command.js";
import path from "path";
import * as fs from "node:fs";
import {scanJsdocs} from "../extraction/swerr-scan.js";
Expand All @@ -8,92 +7,94 @@ import {existsSync} from "node:fs";
import {pathToFileURL} from "node:url";
import {LogUtils, SwerrConfig, SwerrScheme} from "@swerr/core";

export const runCommand: SwerrCommand<[string, string]> = {
command: "run [configPath]",
description: "Create swerr documentation based on the config file.",
action: async (configPath: string | undefined) => {
const swerrConfig = await config(configPath);
const sourceDir = swerrConfig?.sourceFile?.inputDir || null
const outputDir = swerrConfig?.sourceFile?.export?.outputDir || null
LogUtils.success("Swerr Configuration loaded.");

if (!sourceDir || !outputDir) {
LogUtils.error("Source and output directories must be specified either via configuration file.");
process.exit(1);
}

const absoluteSourceDir = path.resolve(process.cwd(), sourceDir);
const absoluteOutputDir = path.resolve(process.cwd(), outputDir);

const sourceExists = fs.existsSync(absoluteSourceDir);
if (!sourceExists) {
LogUtils.error(`Source directory "${absoluteSourceDir}" does not exist.`);
process.exit(1);
}

try {
await fs.promises.mkdir(absoluteOutputDir, {recursive: true});
} catch (err) {
LogUtils.error(`Failed to create output directory "${absoluteOutputDir}": ${err}`);
process.exit(1);
}

scanJsdocs(absoluteSourceDir, swerrConfig?.sourceFile.options || {}).then(async result => {
LogUtils.info(`Scanned ${result.blocks.length} JSDocs block(s) from ${result.scannedFiles} file(s).`);
const scheme = translateToSourceScheme(result, swerrConfig)
LogUtils.info(`Translated scan result to swerr Scheme with ${scheme.errors.length} error(s).`);
await saveSourceScheme(swerrConfig!, absoluteOutputDir, scheme);
await runConverter(swerrConfig!, scheme);
}).catch(err => {
LogUtils.error(`Error during scanning: ${err}`);
})
}
export const runCommand = async (configPath: string | undefined) => {
const swerrConfig = await config(configPath);
const sourceDir = swerrConfig?.sourceFile?.inputDir || null
const outputDir = swerrConfig?.sourceFile?.export?.outputDir || null

if (!swerrConfig) {
LogUtils.error(`Swerr configuration file not found. Please create a ${SWERR_CONFIG_FILE} file in the current directory or specify a custom path using --config option.`);
process.exit(1);
}

if (!sourceDir || !outputDir) {
LogUtils.error("Source and output directories must be specified either via configuration file.");
process.exit(1);
}

LogUtils.success("Swerr Configuration loaded.");

const absoluteSourceDir = path.resolve(process.cwd(), sourceDir);
const absoluteOutputDir = path.resolve(process.cwd(), outputDir);

const sourceExists = fs.existsSync(absoluteSourceDir);
if (!sourceExists) {
LogUtils.error(`Source directory "${absoluteSourceDir}" does not exist.`);
process.exit(1);
}

try {
await fs.promises.mkdir(absoluteOutputDir, {recursive: true});
} catch (err) {
LogUtils.error(`Failed to create output directory "${absoluteOutputDir}": ${err}`);
process.exit(1);
}

scanJsdocs(absoluteSourceDir, swerrConfig?.sourceFile.options || {}).then(async result => {
LogUtils.info(`Scanned ${result.blocks.length} JSDocs block(s) from ${result.scannedFiles} file(s).`);
const scheme = translateToSourceScheme(result, swerrConfig)
LogUtils.info(`Translated scan result to swerr Scheme with ${scheme.errors.length} error(s).`);
await saveSourceScheme(swerrConfig!, absoluteOutputDir, scheme);
await runConverter(swerrConfig!, scheme);
}).catch(err => {
LogUtils.error(`Error during scanning: ${err}`);
})
}

async function config(configPath: string | undefined): Promise<SwerrConfig | null> {
try {
if (!configPath) {
configPath = path.resolve(process.cwd(), SWERR_CONFIG_FILE);
} else {
configPath = path.resolve(process.cwd(), configPath);
}
let cfg: SwerrConfig | null = null;
if (existsSync(configPath)) {
LogUtils.info(`Loading configuration from ${configPath}`);
try {
const imported = await import(pathToFileURL(configPath).href);
cfg = imported.default ?? imported;
return cfg;
} catch (err) {
LogUtils.error(`Failed to load configuration from ${configPath}: ${err}`);
process.exit(1);
}
}
return null;
} catch (err) {
LogUtils.error(`Error loading configuration: ${err}`);
process.exit(1);
}
try {
if (!configPath) {
configPath = path.resolve(process.cwd(), SWERR_CONFIG_FILE);
} else {
configPath = path.resolve(process.cwd(), configPath);
}
let cfg: SwerrConfig | null = null;
if (existsSync(configPath)) {
LogUtils.info(`Loading configuration from ${configPath}`);
try {
const imported = await import(pathToFileURL(configPath).href);
cfg = imported.default ?? imported;
return cfg;
} catch (err) {
LogUtils.error(`Failed to load configuration from ${configPath}: ${err}`);
process.exit(1);
}
}
return null;
} catch (err) {
LogUtils.error(`Error loading configuration: ${err}`);
process.exit(1);
}
}

async function saveSourceScheme(config: SwerrConfig, absoluteOutputDir: string, scheme: SwerrScheme) {
if (!config?.sourceFile?.export?.saveToFile) return
const fileName = config?.sourceFile?.export?.fileName || "swerr-docs.json";
const outputFilePath = path.join(absoluteOutputDir, fileName);
const docContent = JSON.stringify(scheme, null, 2);
try {
await fs.promises.writeFile(outputFilePath, docContent, "utf8");
LogUtils.success(`Swerr Source File written to ${outputFilePath}`);
} catch (err) {
console.error(`Failed to write documentation to "${outputFilePath}":`, err);
process.exit(1);
}
if (!config?.sourceFile?.export?.saveToFile) return
const fileName = config?.sourceFile?.export?.fileName || "swerr-docs.json";
const outputFilePath = path.join(absoluteOutputDir, fileName);
const docContent = JSON.stringify(scheme, null, 2);
try {
await fs.promises.writeFile(outputFilePath, docContent, "utf8");
LogUtils.success(`Swerr Source File written to ${outputFilePath}`);
} catch (err) {
console.error(`Failed to write documentation to "${outputFilePath}":`, err);
process.exit(1);
}
}

async function runConverter(config: SwerrConfig, scheme: SwerrScheme) {
for (const converter of config.converter) {
await converter.factory(converter.config, scheme);
}
for (const converter of config.converter) {
await converter.factory(converter.config, scheme);
}
}
19 changes: 10 additions & 9 deletions bin/swerr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ cli
.description("Create documentation from your errors.")
.version(packageJson.version);

const commandModules = [
runCommand, initCommand
];
cli
.command("init")
.description("Create a basic swerr config file.")
.option("-f, --force", "Overwrite existing config file if it exists.")
.option("-c, --config <path>", "Path to save the swerr config file.", "swerr.config.js")
.action(initCommand);

for (const commandModule of commandModules) {
cli
.command(commandModule.command)
.description(commandModule.description)
.action(commandModule.action);
}
cli
.command("run [configPath]")
.description("Create swerr documentation based on the config file.")
.action(runCommand);

cli.parse();