-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from UmbrellaDocs/export-as-func
Feat: Spinner, JS API, check files
- Loading branch information
Showing
9 changed files
with
171 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,129 +1,66 @@ | ||
#!/usr/bin/env node | ||
|
||
import { readFileSync, existsSync } from "fs"; | ||
import path from "path"; | ||
import yaml from "js-yaml"; | ||
import { program } from "commander"; | ||
import kleur from "kleur"; | ||
import { validateConfig } from "./lib/validate-config.js"; | ||
import { prepareFilesList } from "./lib/prepare-file-list.js"; | ||
import { extractMarkdownHyperlinks } from "./lib/extract-markdown-hyperlinks.js"; | ||
import { extractAsciiDocLinks } from "./lib/extract-asciidoc-hyperlinks.js"; | ||
import { getUniqueLinks } from "./lib/get-unique-links.js"; | ||
import { checkHyperlinks } from "./lib/batch-check-links.js"; | ||
import { updateLinkstatusObj } from "./lib/update-linkstatus-obj.js"; | ||
import ora from "ora"; | ||
import { linkspector } from "./linkspector.js"; | ||
|
||
// Define the program and its options | ||
program | ||
.version("0.1.0") | ||
.description("Check for dead hyperlinks in your markup language files.") | ||
.version("0.2.1") | ||
.description("🔍 Uncover broken links in your content.") | ||
.command("check") | ||
.description("Check hyperlinks based on the configuration file.") | ||
.option("-c, --config <path>", "Specify a custom configuration file path") | ||
.action(async (cmd) => { | ||
const configFile = cmd.config || ".linkspector.yml"; // Use custom config file path if provided | ||
|
||
// Check if the config file exists | ||
if (!existsSync(configFile)) { | ||
console.error( | ||
kleur.red( | ||
"Error: Configuration file not found. Create a '.linkspector.yml' file at the root of your project or use the '--config' option to specify another configuration file." | ||
) | ||
); | ||
process.exit(1); | ||
} | ||
// Start the loading spinner | ||
const spinner = ora().start(); | ||
|
||
// Read and validate the config file | ||
try { | ||
// Read the YAML file | ||
const configContent = readFileSync(configFile, "utf-8"); | ||
|
||
// Check if the YAML content is empty | ||
if (!configContent.trim()) { | ||
console.error("Error: The configuration file is empty."); | ||
return false; | ||
} | ||
|
||
// Parse the YAML content | ||
const config = yaml.load(configContent); | ||
|
||
// Check if the parsed YAML object is null or lacks properties | ||
if (config === null || Object.keys(config).length === 0) { | ||
console.error("Error: Failed to parse the YAML content."); | ||
return false; | ||
} | ||
|
||
if (!validateConfig(config)) { | ||
console.error(kleur.red("Error: Invalid config file.")); | ||
process.exit(1); | ||
} | ||
|
||
let hasErrorLinks = false; | ||
|
||
// Prepare the list of files to check | ||
const filesToCheck = prepareFilesList(config); | ||
|
||
// Initialize an array to store link status objects | ||
let linkStatusObjects = []; | ||
|
||
// Process each file | ||
for (const file of filesToCheck) { | ||
const relativeFilePath = path.relative(process.cwd(), file); | ||
|
||
// Get the file extension | ||
const fileExtension = path.extname(file).substring(1).toLowerCase(); // Get the file extension without the leading dot and convert to lowercase | ||
|
||
let astNodes; | ||
|
||
// Check the file extension and use the appropriate function to extract links | ||
if ( | ||
["asciidoc", "adoc", "asc"].includes(fileExtension) && | ||
config.fileExtensions && | ||
config.fileExtensions.includes(fileExtension) | ||
) { | ||
astNodes = await extractAsciiDocLinks(file); | ||
} else { | ||
const fileContent = readFileSync(file, "utf8"); | ||
astNodes = extractMarkdownHyperlinks(fileContent); | ||
} | ||
|
||
// Get unique hyperlinks | ||
const uniqueLinks = getUniqueLinks(astNodes); | ||
|
||
// Check the status of hyperlinks | ||
const linkStatus = await checkHyperlinks(uniqueLinks); | ||
// Update linkStatusObjects with information about removed links | ||
|
||
linkStatusObjects = await updateLinkstatusObj( | ||
linkStatusObjects, | ||
linkStatus | ||
); | ||
|
||
const errorLinks = linkStatusObjects.filter( | ||
(link) => link.status === "error" | ||
); | ||
|
||
if (errorLinks.length > 0) { | ||
for (const item of errorLinks) { | ||
for await (const { file, result } of linkspector(configFile)) { | ||
for (const linkStatusObj of result) { | ||
if (linkStatusObj.status === "error") { | ||
hasErrorLinks = true; | ||
// Stop the spinner before printing an error message | ||
spinner.stop(); | ||
console.error( | ||
kleur.red( | ||
`${relativeFilePath}, ${item.link}, ${item.status_code}, ${item.line_number}, ${item.error_message}` | ||
`🚫 ${file}, ${linkStatusObj.link}, ${linkStatusObj.status_code}, ${linkStatusObj.line_number}, ${linkStatusObj.error_message}` | ||
) | ||
); | ||
// Start the spinner again after printing an error message | ||
spinner.start(); | ||
} | ||
hasErrorLinks = true; | ||
} | ||
} | ||
if (hasErrorLinks) { | ||
console.error(kleur.red("❌ Found link errors in one or more files.")); | ||
process.exit(1); | ||
|
||
spinner.stop(); | ||
|
||
if (!hasErrorLinks) { | ||
console.log( | ||
kleur.green( | ||
"✨ Success: All hyperlinks in the specified files are valid." | ||
) | ||
); | ||
process.exit(0); | ||
} else { | ||
console.log(kleur.green("✅ All links are working.")); | ||
console.error( | ||
kleur.red( | ||
"❌ Error: Some links in the specified files are not valid." | ||
) | ||
); | ||
process.exit(1); | ||
} | ||
} catch (error) { | ||
console.error(kleur.red(`Error: ${error.message}`)); | ||
spinner.fail(kleur.red(`💥 Error: ${error.message}`)); | ||
process.exit(1); | ||
} | ||
}); | ||
|
||
// Parse the command line arguments | ||
program.parse(process.argv); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { readFileSync, existsSync } from "fs"; | ||
import path from "path"; | ||
import yaml from "js-yaml"; | ||
import { validateConfig } from "./lib/validate-config.js"; | ||
import { prepareFilesList } from "./lib/prepare-file-list.js"; | ||
import { extractMarkdownHyperlinks } from "./lib/extract-markdown-hyperlinks.js"; | ||
import { extractAsciiDocLinks } from "./lib/extract-asciidoc-hyperlinks.js"; | ||
import { getUniqueLinks } from "./lib/get-unique-links.js"; | ||
import { checkHyperlinks } from "./lib/batch-check-links.js"; | ||
import { updateLinkstatusObj } from "./lib/update-linkstatus-obj.js"; | ||
|
||
export async function* linkspector(configFile) { | ||
// Check if the config file exists | ||
if (!existsSync(configFile)) { | ||
throw new Error( | ||
"Configuration file not found. Create a '.linkspector.yml' file at the root of your project or use the '--config' option to specify another configuration file." | ||
); | ||
} | ||
|
||
// Read and validate the config file | ||
const configContent = readFileSync(configFile, "utf-8"); | ||
|
||
// Check if the YAML content is empty | ||
if (!configContent.trim()) { | ||
throw new Error("The configuration file is empty."); | ||
} | ||
|
||
// Parse the YAML content | ||
const config = yaml.load(configContent); | ||
|
||
// Check if the parsed YAML object is null or lacks properties | ||
if (config === null || Object.keys(config).length === 0) { | ||
throw new Error("Failed to parse the YAML content."); | ||
} | ||
|
||
try { | ||
const isValid = await validateConfig(config); | ||
if (!isValid) { | ||
console.error("Validation failed!") | ||
process.exit(1); | ||
} | ||
} catch (error) { | ||
console.error(`💥 Error: Please check your configuration file.`) | ||
process.exit(1); | ||
} | ||
|
||
// Prepare the list of files to check | ||
const filesToCheck = prepareFilesList(config); | ||
|
||
// Initialize an array to store link status objects | ||
let linkStatusObjects = []; | ||
|
||
// Process each file | ||
for (const file of filesToCheck) { | ||
const relativeFilePath = path.relative(process.cwd(), file); | ||
|
||
// Get the file extension | ||
const fileExtension = path.extname(file).substring(1).toLowerCase(); // Get the file extension without the leading dot and convert to lowercase | ||
|
||
let astNodes; | ||
|
||
// Check the file extension and use the appropriate function to extract links | ||
if ( | ||
["asciidoc", "adoc", "asc"].includes(fileExtension) && | ||
config.fileExtensions && | ||
config.fileExtensions.includes(fileExtension) | ||
) { | ||
astNodes = await extractAsciiDocLinks(file); | ||
} else { | ||
const fileContent = readFileSync(file, "utf8"); | ||
astNodes = extractMarkdownHyperlinks(fileContent); | ||
} | ||
|
||
// Get unique hyperlinks | ||
const uniqueLinks = getUniqueLinks(astNodes); | ||
//console.log(JSON.stringify(uniqueLinks)) | ||
|
||
// Check the status of hyperlinks | ||
const linkStatus = await checkHyperlinks(uniqueLinks); | ||
|
||
// Update linkStatusObjects with information about removed links | ||
linkStatusObjects = await updateLinkstatusObj( | ||
linkStatusObjects, | ||
linkStatus | ||
); | ||
|
||
// Yield an object with the relative file path and its result | ||
yield { | ||
file: relativeFilePath, | ||
result: linkStatusObjects, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters