@rejot-dev/thalo-cli 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +9 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/check.js +2 -2
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/format.js +2 -2
- package/dist/commands/format.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/setup-merge-driver.js +1 -1
- package/dist/files.js +2 -2
- package/dist/files.js.map +1 -1
- package/dist/mod.js +14 -1
- package/dist/mod.js.map +1 -1
- package/dist/version.js +79 -0
- package/dist/version.js.map +1 -0
- package/package.json +9 -6
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { formatVersion, getVersionInfo } from "./version.js";
|
|
1
2
|
import { parseArgs } from "node:util";
|
|
2
3
|
import pc from "picocolors";
|
|
3
4
|
|
|
4
5
|
//#region src/cli.ts
|
|
5
|
-
const VERSION = "0.1.0";
|
|
6
6
|
/**
|
|
7
7
|
* Convert our option definitions to node:util parseArgs config
|
|
8
8
|
*/
|
|
@@ -142,8 +142,14 @@ function runCli(rootCommand, argv = process.argv.slice(2)) {
|
|
|
142
142
|
const { values, positionals } = parsed;
|
|
143
143
|
if (values["no-color"]) process.env["NO_COLOR"] = "1";
|
|
144
144
|
if (values["version"] && commandPath.length === 1) {
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
getVersionInfo().then((info) => {
|
|
146
|
+
console.log(formatVersion(info));
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}).catch(() => {
|
|
149
|
+
console.log("thalo (version unknown)");
|
|
150
|
+
process.exit(0);
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
147
153
|
}
|
|
148
154
|
if (values["help"]) {
|
|
149
155
|
console.log(generateHelp(command, commandPath));
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":["config: ParseArgsConfig[\"options\"]","optConfig: NonNullable<ParseArgsConfig[\"options\"]>[string]","globalOptions: Record<string, OptionDef>","lines: string[]","parsed: ReturnType<typeof parseArgs>","ctx: CommandContext"],"sources":["../src/cli.ts"],"sourcesContent":["import { parseArgs, type ParseArgsConfig } from \"node:util\";\nimport pc from \"picocolors\";\n\nconst VERSION = \"0.1.0\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Option definition for a command\n */\nexport interface OptionDef {\n type: \"boolean\" | \"string\";\n short?: string;\n description: string;\n default?: string | boolean;\n choices?: string[];\n multiple?: boolean;\n}\n\n/**\n * Command definition\n */\nexport interface CommandDef {\n name: string;\n description: string;\n usage?: string;\n options?: Record<string, OptionDef>;\n args?: {\n name: string;\n description: string;\n required?: boolean;\n multiple?: boolean;\n };\n subcommands?: Record<string, CommandDef>;\n action?: (ctx: CommandContext) => void | Promise<void>;\n}\n\n/**\n * Parsed command context passed to action handlers\n */\nexport interface CommandContext {\n options: Record<string, string | boolean | string[]>;\n args: string[];\n command: CommandDef;\n commandPath: string[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Framework\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert our option definitions to node:util parseArgs config\n */\nfunction toParseArgsConfig(options: Record<string, OptionDef>): ParseArgsConfig[\"options\"] {\n const config: ParseArgsConfig[\"options\"] = {};\n\n for (const [name, def] of Object.entries(options)) {\n const optConfig: NonNullable<ParseArgsConfig[\"options\"]>[string] = {\n type: def.type,\n };\n\n if (def.short) {\n optConfig.short = def.short;\n }\n\n if (def.default !== undefined) {\n optConfig.default = def.default;\n }\n\n if (def.multiple !== undefined) {\n optConfig.multiple = def.multiple;\n }\n\n config[name] = optConfig;\n }\n\n return config;\n}\n\n/**\n * Global options available to all commands\n */\nconst globalOptions: Record<string, OptionDef> = {\n help: {\n type: \"boolean\",\n short: \"h\",\n description: \"Show help for the command\",\n default: false,\n },\n version: {\n type: \"boolean\",\n short: \"V\",\n description: \"Show version number\",\n default: false,\n },\n \"no-color\": {\n type: \"boolean\",\n description: \"Disable colored output\",\n default: false,\n },\n};\n\n/**\n * Generate help text for a command\n */\nexport function generateHelp(command: CommandDef, commandPath: string[] = []): string {\n const lines: string[] = [];\n const fullCommandName = commandPath.length > 0 ? commandPath.join(\" \") : \"thalo\";\n\n // Header\n lines.push(\"\");\n lines.push(pc.bold(command.description));\n lines.push(\"\");\n\n // Usage\n lines.push(pc.bold(\"USAGE\"));\n let usageLine = ` ${fullCommandName}`;\n\n if (command.subcommands && Object.keys(command.subcommands).length > 0) {\n usageLine += \" <command>\";\n }\n\n if (command.options && Object.keys(command.options).length > 0) {\n usageLine += \" [options]\";\n }\n\n if (command.args) {\n const argName = command.args.multiple ? `<${command.args.name}...>` : `<${command.args.name}>`;\n usageLine += command.args.required ? ` ${argName}` : ` [${argName}]`;\n }\n\n if (command.usage) {\n usageLine = ` ${fullCommandName} ${command.usage}`;\n }\n\n lines.push(usageLine);\n lines.push(\"\");\n\n // Subcommands\n if (command.subcommands && Object.keys(command.subcommands).length > 0) {\n lines.push(pc.bold(\"COMMANDS\"));\n\n const subcommandNames = Object.keys(command.subcommands);\n const maxLen = Math.max(...subcommandNames.map((n) => n.length));\n\n for (const [name, subcmd] of Object.entries(command.subcommands)) {\n const paddedName = name.padEnd(maxLen + 2);\n lines.push(` ${pc.cyan(paddedName)}${subcmd.description}`);\n }\n\n lines.push(\"\");\n lines.push(` Run '${fullCommandName} <command> --help' for more information on a command.`);\n lines.push(\"\");\n }\n\n // Options\n const allOptions = { ...globalOptions, ...command.options };\n if (Object.keys(allOptions).length > 0) {\n lines.push(pc.bold(\"OPTIONS\"));\n\n const optionEntries = Object.entries(allOptions);\n const optionStrings = optionEntries.map(([name, def]) => {\n const shortFlag = def.short ? `-${def.short}, ` : \" \";\n const longFlag = `--${name}`;\n const valueHint =\n def.type === \"string\" ? ` <${def.choices ? def.choices.join(\"|\") : \"value\"}>` : \"\";\n return `${shortFlag}${longFlag}${valueHint}`;\n });\n const maxOptLen = Math.max(...optionStrings.map((s) => s.length));\n\n for (let i = 0; i < optionEntries.length; i++) {\n const [, def] = optionEntries[i];\n const optStr = optionStrings[i].padEnd(maxOptLen + 2);\n let description = def.description;\n\n if (def.default !== undefined && def.default !== false && def.default !== \"\") {\n description += pc.dim(` (default: ${def.default})`);\n }\n\n lines.push(` ${pc.dim(optStr)}${description}`);\n }\n\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Find a command by traversing the path\n */\nfunction findCommand(\n root: CommandDef,\n args: string[],\n): { command: CommandDef; commandPath: string[]; remainingArgs: string[] } {\n let current = root;\n const commandPath = [\"thalo\"];\n const remainingArgs = [...args];\n\n while (remainingArgs.length > 0) {\n const nextArg = remainingArgs[0];\n\n // Stop if it's an option\n if (nextArg.startsWith(\"-\")) {\n break;\n }\n\n // Check if it's a subcommand\n if (current.subcommands && current.subcommands[nextArg]) {\n current = current.subcommands[nextArg];\n commandPath.push(nextArg);\n remainingArgs.shift();\n } else {\n // Not a subcommand, must be a positional arg\n break;\n }\n }\n\n return { command: current, commandPath, remainingArgs };\n}\n\n/**\n * Parse and run a command\n */\nexport function runCli(rootCommand: CommandDef, argv: string[] = process.argv.slice(2)): void {\n // Find the target command\n const { command, commandPath, remainingArgs } = findCommand(rootCommand, argv);\n\n // Merge global options with command options\n const allOptions = { ...globalOptions, ...command.options };\n const parseArgsOptions = toParseArgsConfig(allOptions);\n\n // Parse arguments\n let parsed: ReturnType<typeof parseArgs>;\n try {\n parsed = parseArgs({\n args: remainingArgs,\n options: parseArgsOptions,\n allowPositionals: true,\n strict: true,\n });\n } catch (err) {\n if (err instanceof Error) {\n console.error(pc.red(`Error: ${err.message}`));\n console.error(`\\nRun '${commandPath.join(\" \")} --help' for usage information.`);\n }\n process.exit(2);\n }\n\n const { values, positionals } = parsed;\n\n // Handle no-color by setting env var (picocolors respects NO_COLOR)\n if (values[\"no-color\"]) {\n process.env[\"NO_COLOR\"] = \"1\";\n }\n\n // Handle version (only at root level)\n if (values[\"version\"] && commandPath.length === 1) {\n console.log(`thalo v${VERSION}`);\n process.exit(0);\n }\n\n // Handle help\n if (values[\"help\"]) {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n\n // If command has subcommands but no action and no subcommand was specified, show help\n if (command.subcommands && Object.keys(command.subcommands).length > 0 && !command.action) {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n\n // Run the command action\n if (command.action) {\n const ctx: CommandContext = {\n options: values as Record<string, string | boolean | string[]>,\n args: positionals,\n command,\n commandPath,\n };\n\n Promise.resolve(command.action(ctx)).catch((err) => {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : err}`));\n process.exit(1);\n });\n } else {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":["config: ParseArgsConfig[\"options\"]","optConfig: NonNullable<ParseArgsConfig[\"options\"]>[string]","globalOptions: Record<string, OptionDef>","lines: string[]","parsed: ReturnType<typeof parseArgs>","ctx: CommandContext"],"sources":["../src/cli.ts"],"sourcesContent":["import { parseArgs, type ParseArgsConfig } from \"node:util\";\nimport pc from \"picocolors\";\nimport { getVersionInfo, formatVersion } from \"./version.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Option definition for a command\n */\nexport interface OptionDef {\n type: \"boolean\" | \"string\";\n short?: string;\n description: string;\n default?: string | boolean;\n choices?: string[];\n multiple?: boolean;\n}\n\n/**\n * Command definition\n */\nexport interface CommandDef {\n name: string;\n description: string;\n usage?: string;\n options?: Record<string, OptionDef>;\n args?: {\n name: string;\n description: string;\n required?: boolean;\n multiple?: boolean;\n };\n subcommands?: Record<string, CommandDef>;\n action?: (ctx: CommandContext) => void | Promise<void>;\n}\n\n/**\n * Parsed command context passed to action handlers\n */\nexport interface CommandContext {\n options: Record<string, string | boolean | string[]>;\n args: string[];\n command: CommandDef;\n commandPath: string[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Framework\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert our option definitions to node:util parseArgs config\n */\nfunction toParseArgsConfig(options: Record<string, OptionDef>): ParseArgsConfig[\"options\"] {\n const config: ParseArgsConfig[\"options\"] = {};\n\n for (const [name, def] of Object.entries(options)) {\n const optConfig: NonNullable<ParseArgsConfig[\"options\"]>[string] = {\n type: def.type,\n };\n\n if (def.short) {\n optConfig.short = def.short;\n }\n\n if (def.default !== undefined) {\n optConfig.default = def.default;\n }\n\n if (def.multiple !== undefined) {\n optConfig.multiple = def.multiple;\n }\n\n config[name] = optConfig;\n }\n\n return config;\n}\n\n/**\n * Global options available to all commands\n */\nconst globalOptions: Record<string, OptionDef> = {\n help: {\n type: \"boolean\",\n short: \"h\",\n description: \"Show help for the command\",\n default: false,\n },\n version: {\n type: \"boolean\",\n short: \"V\",\n description: \"Show version number\",\n default: false,\n },\n \"no-color\": {\n type: \"boolean\",\n description: \"Disable colored output\",\n default: false,\n },\n};\n\n/**\n * Generate help text for a command\n */\nexport function generateHelp(command: CommandDef, commandPath: string[] = []): string {\n const lines: string[] = [];\n const fullCommandName = commandPath.length > 0 ? commandPath.join(\" \") : \"thalo\";\n\n // Header\n lines.push(\"\");\n lines.push(pc.bold(command.description));\n lines.push(\"\");\n\n // Usage\n lines.push(pc.bold(\"USAGE\"));\n let usageLine = ` ${fullCommandName}`;\n\n if (command.subcommands && Object.keys(command.subcommands).length > 0) {\n usageLine += \" <command>\";\n }\n\n if (command.options && Object.keys(command.options).length > 0) {\n usageLine += \" [options]\";\n }\n\n if (command.args) {\n const argName = command.args.multiple ? `<${command.args.name}...>` : `<${command.args.name}>`;\n usageLine += command.args.required ? ` ${argName}` : ` [${argName}]`;\n }\n\n if (command.usage) {\n usageLine = ` ${fullCommandName} ${command.usage}`;\n }\n\n lines.push(usageLine);\n lines.push(\"\");\n\n // Subcommands\n if (command.subcommands && Object.keys(command.subcommands).length > 0) {\n lines.push(pc.bold(\"COMMANDS\"));\n\n const subcommandNames = Object.keys(command.subcommands);\n const maxLen = Math.max(...subcommandNames.map((n) => n.length));\n\n for (const [name, subcmd] of Object.entries(command.subcommands)) {\n const paddedName = name.padEnd(maxLen + 2);\n lines.push(` ${pc.cyan(paddedName)}${subcmd.description}`);\n }\n\n lines.push(\"\");\n lines.push(` Run '${fullCommandName} <command> --help' for more information on a command.`);\n lines.push(\"\");\n }\n\n // Options\n const allOptions = { ...globalOptions, ...command.options };\n if (Object.keys(allOptions).length > 0) {\n lines.push(pc.bold(\"OPTIONS\"));\n\n const optionEntries = Object.entries(allOptions);\n const optionStrings = optionEntries.map(([name, def]) => {\n const shortFlag = def.short ? `-${def.short}, ` : \" \";\n const longFlag = `--${name}`;\n const valueHint =\n def.type === \"string\" ? ` <${def.choices ? def.choices.join(\"|\") : \"value\"}>` : \"\";\n return `${shortFlag}${longFlag}${valueHint}`;\n });\n const maxOptLen = Math.max(...optionStrings.map((s) => s.length));\n\n for (let i = 0; i < optionEntries.length; i++) {\n const [, def] = optionEntries[i];\n const optStr = optionStrings[i].padEnd(maxOptLen + 2);\n let description = def.description;\n\n if (def.default !== undefined && def.default !== false && def.default !== \"\") {\n description += pc.dim(` (default: ${def.default})`);\n }\n\n lines.push(` ${pc.dim(optStr)}${description}`);\n }\n\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Find a command by traversing the path\n */\nfunction findCommand(\n root: CommandDef,\n args: string[],\n): { command: CommandDef; commandPath: string[]; remainingArgs: string[] } {\n let current = root;\n const commandPath = [\"thalo\"];\n const remainingArgs = [...args];\n\n while (remainingArgs.length > 0) {\n const nextArg = remainingArgs[0];\n\n // Stop if it's an option\n if (nextArg.startsWith(\"-\")) {\n break;\n }\n\n // Check if it's a subcommand\n if (current.subcommands && current.subcommands[nextArg]) {\n current = current.subcommands[nextArg];\n commandPath.push(nextArg);\n remainingArgs.shift();\n } else {\n // Not a subcommand, must be a positional arg\n break;\n }\n }\n\n return { command: current, commandPath, remainingArgs };\n}\n\n/**\n * Parse and run a command\n */\nexport function runCli(rootCommand: CommandDef, argv: string[] = process.argv.slice(2)): void {\n // Find the target command\n const { command, commandPath, remainingArgs } = findCommand(rootCommand, argv);\n\n // Merge global options with command options\n const allOptions = { ...globalOptions, ...command.options };\n const parseArgsOptions = toParseArgsConfig(allOptions);\n\n // Parse arguments\n let parsed: ReturnType<typeof parseArgs>;\n try {\n parsed = parseArgs({\n args: remainingArgs,\n options: parseArgsOptions,\n allowPositionals: true,\n strict: true,\n });\n } catch (err) {\n if (err instanceof Error) {\n console.error(pc.red(`Error: ${err.message}`));\n console.error(`\\nRun '${commandPath.join(\" \")} --help' for usage information.`);\n }\n process.exit(2);\n }\n\n const { values, positionals } = parsed;\n\n // Handle no-color by setting env var (picocolors respects NO_COLOR)\n if (values[\"no-color\"]) {\n process.env[\"NO_COLOR\"] = \"1\";\n }\n\n // Handle version (only at root level)\n if (values[\"version\"] && commandPath.length === 1) {\n getVersionInfo()\n .then((info) => {\n console.log(formatVersion(info));\n process.exit(0);\n })\n .catch(() => {\n console.log(\"thalo (version unknown)\");\n process.exit(0);\n });\n return;\n }\n\n // Handle help\n if (values[\"help\"]) {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n\n // If command has subcommands but no action and no subcommand was specified, show help\n if (command.subcommands && Object.keys(command.subcommands).length > 0 && !command.action) {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n\n // Run the command action\n if (command.action) {\n const ctx: CommandContext = {\n options: values as Record<string, string | boolean | string[]>,\n args: positionals,\n command,\n commandPath,\n };\n\n Promise.resolve(command.action(ctx)).catch((err) => {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : err}`));\n process.exit(1);\n });\n } else {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n}\n"],"mappings":"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"}
|
package/dist/commands/check.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { relativePath, resolveFilesSync } from "../files.js";
|
|
2
|
+
import { createWorkspace } from "@rejot-dev/thalo/node";
|
|
2
3
|
import pc from "picocolors";
|
|
4
|
+
import * as path from "node:path";
|
|
3
5
|
import { formatDiagnostic, runCheck } from "@rejot-dev/thalo";
|
|
4
6
|
import * as fs from "node:fs";
|
|
5
|
-
import * as path from "node:path";
|
|
6
|
-
import { createWorkspace } from "@rejot-dev/thalo/native";
|
|
7
7
|
|
|
8
8
|
//#region src/commands/check.ts
|
|
9
9
|
const SEVERITY_ORDER = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.js","names":["SEVERITY_ORDER: Record<SeverityKey, number>","formatDiagnostic","formatDiagnosticPlain","filtered: DiagnosticInfo[]","parts: string[]","debounceTimer: NodeJS.Timeout | null","severity: SeverityKey","config: CheckConfig","checkCommand: CommandDef"],"sources":["../../src/commands/check.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n runCheck,\n formatDiagnostic as formatDiagnosticPlain,\n type Severity,\n type CheckConfig,\n type CheckResult,\n type DiagnosticInfo,\n type DiagnosticSeverity,\n} from \"@rejot-dev/thalo\";\nimport { createWorkspace } from \"@rejot-dev/thalo/native\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\nimport { resolveFilesSync, relativePath } from \"../files.js\";\n\ntype SeverityKey = DiagnosticSeverity;\ntype OutputFormat = \"default\" | \"json\" | \"compact\" | \"github\";\n\nconst SEVERITY_ORDER: Record<SeverityKey, number> = {\n error: 0,\n warning: 1,\n info: 2,\n};\n\nconst severityColor = {\n error: pc.red,\n warning: pc.yellow,\n info: pc.cyan,\n} as const;\n\nfunction formatDiagnosticDefault(d: DiagnosticInfo): string {\n const color = severityColor[d.severity];\n\n const loc = `${d.line}:${d.column}`.padEnd(8);\n const severityLabel = color(d.severity.padEnd(7));\n const codeLabel = pc.dim(d.code);\n\n return ` ${pc.dim(loc)} ${severityLabel} ${d.message} ${codeLabel}`;\n}\n\nfunction formatDiagnostic(d: DiagnosticInfo, format: OutputFormat): string {\n switch (format) {\n case \"compact\":\n case \"github\":\n // Use shared formatter for compact/github formats (no colors needed)\n return formatDiagnosticPlain(d, format);\n default:\n return formatDiagnosticDefault(d);\n }\n}\n\ninterface RunResult {\n files: string[];\n result: CheckResult;\n}\n\nfunction executeCheck(files: string[], config: CheckConfig): RunResult {\n const workspace = createWorkspace();\n\n for (const file of files) {\n try {\n const source = fs.readFileSync(file, \"utf-8\");\n workspace.addDocument(source, { filename: file });\n } catch (err) {\n console.error(pc.red(`Error reading ${file}: ${err instanceof Error ? err.message : err}`));\n }\n }\n\n const result = runCheck(workspace, { config });\n\n return { files, result };\n}\n\ninterface OutputOptions {\n format: OutputFormat;\n severity: SeverityKey;\n}\n\nfunction outputResults(runResult: RunResult, options: OutputOptions): void {\n const { result, files } = runResult;\n const { diagnosticsByFile, errorCount, warningCount, infoCount } = result;\n\n // Collect and filter diagnostics by severity\n const minSeverity = SEVERITY_ORDER[options.severity];\n const filtered: DiagnosticInfo[] = [];\n for (const diagnostics of diagnosticsByFile.values()) {\n for (const d of diagnostics) {\n if (SEVERITY_ORDER[d.severity] <= minSeverity) {\n filtered.push(d);\n }\n }\n }\n\n // Track which files have issues\n const filesWithIssues = new Set(filtered.map((d) => d.file));\n\n if (options.format === \"json\") {\n const output = {\n files: files.length,\n issues: filtered.length,\n errors: errorCount,\n warnings: warningCount,\n info: infoCount,\n diagnostics: filtered.map((d) => ({\n file: d.file,\n line: d.line,\n column: d.column,\n endLine: d.endLine,\n endColumn: d.endColumn,\n severity: d.severity,\n code: d.code,\n message: d.message,\n })),\n };\n console.log(JSON.stringify(output, null, 2));\n return;\n }\n\n // Always print all files that were checked\n if (options.format === \"default\") {\n // Print all files first\n for (const file of files) {\n const hasIssues = filesWithIssues.has(file);\n if (hasIssues) {\n // Make files with issues bold\n console.log(pc.bold(pc.red(`✗`) + ` ${relativePath(file)}`));\n } else {\n // Files without issues in regular text\n console.log(pc.green(`✓`) + ` ${relativePath(file)}`);\n }\n }\n\n // Then show diagnostics grouped by file\n if (filtered.length > 0) {\n console.log();\n const byFile = new Map<string, DiagnosticInfo[]>();\n for (const d of filtered) {\n const existing = byFile.get(d.file) || [];\n existing.push(d);\n byFile.set(d.file, existing);\n }\n\n for (const [file, fileDiagnostics] of byFile) {\n console.log();\n console.log(pc.underline(relativePath(file)));\n for (const diagnostic of fileDiagnostics) {\n console.log(formatDiagnosticDefault(diagnostic));\n }\n }\n }\n } else {\n for (const diagnostic of filtered) {\n console.log(formatDiagnostic(diagnostic, options.format));\n }\n }\n\n if (options.format !== \"github\") {\n console.log();\n const parts: string[] = [];\n if (errorCount > 0) {\n parts.push(pc.red(`${errorCount} error${errorCount !== 1 ? \"s\" : \"\"}`));\n }\n if (warningCount > 0) {\n parts.push(pc.yellow(`${warningCount} warning${warningCount !== 1 ? \"s\" : \"\"}`));\n }\n if (infoCount > 0) {\n parts.push(pc.cyan(`${infoCount} info`));\n }\n\n const summary = parts.length > 0 ? parts.join(\", \") : pc.green(\"no issues\");\n console.log(`${pc.bold(String(files.length))} files checked, ${summary}`);\n }\n}\n\nfunction watchFiles(\n paths: string[],\n fileTypes: string[],\n options: OutputOptions,\n config: CheckConfig,\n): void {\n console.log(pc.dim(\"Watching for file changes...\"));\n console.log();\n\n const runAndReport = (): void => {\n console.clear();\n console.log(pc.dim(`[${new Date().toLocaleTimeString()}] Checking...`));\n console.log();\n\n const files = resolveFilesSync(paths, fileTypes);\n if (files.length === 0) {\n const fileTypesStr = fileTypes.join(\", \");\n console.log(`No .${fileTypesStr} files found.`);\n return;\n }\n\n const runResult = executeCheck(files, config);\n outputResults(runResult, options);\n\n console.log();\n console.log(pc.dim(\"Watching for file changes... (Ctrl+C to exit)\"));\n };\n\n runAndReport();\n\n const watchedDirs = new Set<string>();\n for (const targetPath of paths) {\n const resolved = path.resolve(targetPath);\n const stat = fs.statSync(resolved);\n const dir = stat.isDirectory() ? resolved : path.dirname(resolved);\n watchedDirs.add(dir);\n }\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const extensions = fileTypes.map((type) => `.${type}`);\n\n for (const dir of watchedDirs) {\n fs.watch(dir, { recursive: true }, (_eventType, filename) => {\n if (!filename) {\n return;\n }\n if (!extensions.some((ext) => filename.endsWith(ext))) {\n return;\n }\n\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n\n debounceTimer = setTimeout(() => {\n runAndReport();\n }, 100);\n });\n }\n}\n\nfunction parseRuleOverrides(ruleArgs: string | string[] | undefined): Map<string, Severity> {\n const rules = new Map<string, Severity>();\n\n if (!ruleArgs) {\n return rules;\n }\n\n const ruleList = Array.isArray(ruleArgs) ? ruleArgs : [ruleArgs];\n\n for (const ruleArg of ruleList) {\n const match = ruleArg.match(/^([^=]+)=(.+)$/);\n if (!match) {\n console.error(`Invalid rule format: ${ruleArg}. Use: --rule <rule>=<severity>`);\n process.exit(2);\n }\n const [, ruleCode, ruleSev] = match;\n if (![\"error\", \"warning\", \"info\", \"off\"].includes(ruleSev)) {\n console.error(`Invalid rule severity: ${ruleSev}. Use: error, warning, info, off`);\n process.exit(2);\n }\n rules.set(ruleCode, ruleSev as Severity);\n }\n\n return rules;\n}\n\nfunction checkAction(ctx: CommandContext): void {\n const { options, args } = ctx;\n\n // Handle format-dependent color disabling\n const format = (options[\"format\"] as OutputFormat) || \"default\";\n if (format === \"json\" || format === \"github\") {\n process.env[\"NO_COLOR\"] = \"1\";\n }\n\n // Determine severity level\n let severity: SeverityKey = (options[\"severity\"] as SeverityKey) || \"info\";\n if (options[\"quiet\"]) {\n severity = \"error\";\n }\n\n // Parse file types\n const fileTypeStr = (options[\"file-type\"] as string) || \"md,thalo\";\n const fileTypes = fileTypeStr.split(\",\").map((t) => t.trim());\n\n // Parse rule overrides\n const rules = parseRuleOverrides(options[\"rule\"] as string | string[] | undefined);\n\n // Build check config\n const config: CheckConfig = {};\n if (rules.size > 0) {\n config.rules = Object.fromEntries(rules);\n }\n\n // Determine target paths\n const targetPaths = args.length > 0 ? args : [\".\"];\n\n // Watch mode\n if (options[\"watch\"]) {\n watchFiles(targetPaths, fileTypes, { format, severity }, config);\n return;\n }\n\n // Collect files\n const files = resolveFilesSync(targetPaths, fileTypes);\n\n if (files.length === 0) {\n const fileTypesStr = fileTypes.join(\", \");\n console.log(`No .${fileTypesStr} files found.`);\n process.exit(0);\n }\n\n // Run checks\n const runResult = executeCheck(files, config);\n\n // Output results\n outputResults(runResult, { format, severity });\n\n // Determine exit code\n if (runResult.result.errorCount > 0) {\n process.exit(1);\n }\n\n const maxWarnings = options[\"max-warnings\"];\n if (maxWarnings !== undefined) {\n const maxWarningsNum = parseInt(maxWarnings as string, 10);\n if (isNaN(maxWarningsNum) || maxWarningsNum < 0) {\n console.error(`Invalid max-warnings value: ${maxWarnings}`);\n process.exit(2);\n }\n\n if (runResult.result.warningCount > maxWarningsNum) {\n if (format !== \"json\") {\n console.log();\n console.error(\n pc.red(\n `Warning threshold exceeded: ${runResult.result.warningCount} warnings (max: ${maxWarningsNum})`,\n ),\n );\n }\n process.exit(1);\n }\n }\n}\n\nexport const checkCommand: CommandDef = {\n name: \"check\",\n description: \"Check and lint thalo and markdown files for errors and warnings\",\n args: {\n name: \"paths\",\n description: \"Files or directories to check\",\n required: false,\n multiple: true,\n },\n options: {\n quiet: {\n type: \"boolean\",\n short: \"q\",\n description: \"Only show errors, suppress warnings and info\",\n default: false,\n },\n format: {\n type: \"string\",\n short: \"f\",\n description: \"Output format\",\n choices: [\"default\", \"json\", \"compact\", \"github\"],\n default: \"default\",\n },\n severity: {\n type: \"string\",\n description: \"Minimum severity to report\",\n choices: [\"error\", \"warning\", \"info\"],\n default: \"info\",\n },\n \"max-warnings\": {\n type: \"string\",\n description: \"Exit with error if warnings exceed threshold\",\n },\n rule: {\n type: \"string\",\n description: \"Set rule severity (e.g., unknown-entity=off)\",\n multiple: true,\n },\n watch: {\n type: \"boolean\",\n short: \"w\",\n description: \"Watch files for changes and re-run\",\n default: false,\n },\n \"file-type\": {\n type: \"string\",\n description: \"Comma-separated list of file types to check (e.g., 'md,thalo')\",\n default: \"md,thalo\",\n },\n },\n action: checkAction,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"check.js","names":["SEVERITY_ORDER: Record<SeverityKey, number>","formatDiagnostic","formatDiagnosticPlain","filtered: DiagnosticInfo[]","parts: string[]","debounceTimer: NodeJS.Timeout | null","severity: SeverityKey","config: CheckConfig","checkCommand: CommandDef"],"sources":["../../src/commands/check.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n runCheck,\n formatDiagnostic as formatDiagnosticPlain,\n type Severity,\n type CheckConfig,\n type CheckResult,\n type DiagnosticInfo,\n type DiagnosticSeverity,\n} from \"@rejot-dev/thalo\";\nimport { createWorkspace } from \"@rejot-dev/thalo/node\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\nimport { resolveFilesSync, relativePath } from \"../files.js\";\n\ntype SeverityKey = DiagnosticSeverity;\ntype OutputFormat = \"default\" | \"json\" | \"compact\" | \"github\";\n\nconst SEVERITY_ORDER: Record<SeverityKey, number> = {\n error: 0,\n warning: 1,\n info: 2,\n};\n\nconst severityColor = {\n error: pc.red,\n warning: pc.yellow,\n info: pc.cyan,\n} as const;\n\nfunction formatDiagnosticDefault(d: DiagnosticInfo): string {\n const color = severityColor[d.severity];\n\n const loc = `${d.line}:${d.column}`.padEnd(8);\n const severityLabel = color(d.severity.padEnd(7));\n const codeLabel = pc.dim(d.code);\n\n return ` ${pc.dim(loc)} ${severityLabel} ${d.message} ${codeLabel}`;\n}\n\nfunction formatDiagnostic(d: DiagnosticInfo, format: OutputFormat): string {\n switch (format) {\n case \"compact\":\n case \"github\":\n // Use shared formatter for compact/github formats (no colors needed)\n return formatDiagnosticPlain(d, format);\n default:\n return formatDiagnosticDefault(d);\n }\n}\n\ninterface RunResult {\n files: string[];\n result: CheckResult;\n}\n\nfunction executeCheck(files: string[], config: CheckConfig): RunResult {\n const workspace = createWorkspace();\n\n for (const file of files) {\n try {\n const source = fs.readFileSync(file, \"utf-8\");\n workspace.addDocument(source, { filename: file });\n } catch (err) {\n console.error(pc.red(`Error reading ${file}: ${err instanceof Error ? err.message : err}`));\n }\n }\n\n const result = runCheck(workspace, { config });\n\n return { files, result };\n}\n\ninterface OutputOptions {\n format: OutputFormat;\n severity: SeverityKey;\n}\n\nfunction outputResults(runResult: RunResult, options: OutputOptions): void {\n const { result, files } = runResult;\n const { diagnosticsByFile, errorCount, warningCount, infoCount } = result;\n\n // Collect and filter diagnostics by severity\n const minSeverity = SEVERITY_ORDER[options.severity];\n const filtered: DiagnosticInfo[] = [];\n for (const diagnostics of diagnosticsByFile.values()) {\n for (const d of diagnostics) {\n if (SEVERITY_ORDER[d.severity] <= minSeverity) {\n filtered.push(d);\n }\n }\n }\n\n // Track which files have issues\n const filesWithIssues = new Set(filtered.map((d) => d.file));\n\n if (options.format === \"json\") {\n const output = {\n files: files.length,\n issues: filtered.length,\n errors: errorCount,\n warnings: warningCount,\n info: infoCount,\n diagnostics: filtered.map((d) => ({\n file: d.file,\n line: d.line,\n column: d.column,\n endLine: d.endLine,\n endColumn: d.endColumn,\n severity: d.severity,\n code: d.code,\n message: d.message,\n })),\n };\n console.log(JSON.stringify(output, null, 2));\n return;\n }\n\n // Always print all files that were checked\n if (options.format === \"default\") {\n // Print all files first\n for (const file of files) {\n const hasIssues = filesWithIssues.has(file);\n if (hasIssues) {\n // Make files with issues bold\n console.log(pc.bold(pc.red(`✗`) + ` ${relativePath(file)}`));\n } else {\n // Files without issues in regular text\n console.log(pc.green(`✓`) + ` ${relativePath(file)}`);\n }\n }\n\n // Then show diagnostics grouped by file\n if (filtered.length > 0) {\n console.log();\n const byFile = new Map<string, DiagnosticInfo[]>();\n for (const d of filtered) {\n const existing = byFile.get(d.file) || [];\n existing.push(d);\n byFile.set(d.file, existing);\n }\n\n for (const [file, fileDiagnostics] of byFile) {\n console.log();\n console.log(pc.underline(relativePath(file)));\n for (const diagnostic of fileDiagnostics) {\n console.log(formatDiagnosticDefault(diagnostic));\n }\n }\n }\n } else {\n for (const diagnostic of filtered) {\n console.log(formatDiagnostic(diagnostic, options.format));\n }\n }\n\n if (options.format !== \"github\") {\n console.log();\n const parts: string[] = [];\n if (errorCount > 0) {\n parts.push(pc.red(`${errorCount} error${errorCount !== 1 ? \"s\" : \"\"}`));\n }\n if (warningCount > 0) {\n parts.push(pc.yellow(`${warningCount} warning${warningCount !== 1 ? \"s\" : \"\"}`));\n }\n if (infoCount > 0) {\n parts.push(pc.cyan(`${infoCount} info`));\n }\n\n const summary = parts.length > 0 ? parts.join(\", \") : pc.green(\"no issues\");\n console.log(`${pc.bold(String(files.length))} files checked, ${summary}`);\n }\n}\n\nfunction watchFiles(\n paths: string[],\n fileTypes: string[],\n options: OutputOptions,\n config: CheckConfig,\n): void {\n console.log(pc.dim(\"Watching for file changes...\"));\n console.log();\n\n const runAndReport = (): void => {\n console.clear();\n console.log(pc.dim(`[${new Date().toLocaleTimeString()}] Checking...`));\n console.log();\n\n const files = resolveFilesSync(paths, fileTypes);\n if (files.length === 0) {\n const fileTypesStr = fileTypes.join(\", \");\n console.log(`No .${fileTypesStr} files found.`);\n return;\n }\n\n const runResult = executeCheck(files, config);\n outputResults(runResult, options);\n\n console.log();\n console.log(pc.dim(\"Watching for file changes... (Ctrl+C to exit)\"));\n };\n\n runAndReport();\n\n const watchedDirs = new Set<string>();\n for (const targetPath of paths) {\n const resolved = path.resolve(targetPath);\n const stat = fs.statSync(resolved);\n const dir = stat.isDirectory() ? resolved : path.dirname(resolved);\n watchedDirs.add(dir);\n }\n\n let debounceTimer: NodeJS.Timeout | null = null;\n const extensions = fileTypes.map((type) => `.${type}`);\n\n for (const dir of watchedDirs) {\n fs.watch(dir, { recursive: true }, (_eventType, filename) => {\n if (!filename) {\n return;\n }\n if (!extensions.some((ext) => filename.endsWith(ext))) {\n return;\n }\n\n if (debounceTimer) {\n clearTimeout(debounceTimer);\n }\n\n debounceTimer = setTimeout(() => {\n runAndReport();\n }, 100);\n });\n }\n}\n\nfunction parseRuleOverrides(ruleArgs: string | string[] | undefined): Map<string, Severity> {\n const rules = new Map<string, Severity>();\n\n if (!ruleArgs) {\n return rules;\n }\n\n const ruleList = Array.isArray(ruleArgs) ? ruleArgs : [ruleArgs];\n\n for (const ruleArg of ruleList) {\n const match = ruleArg.match(/^([^=]+)=(.+)$/);\n if (!match) {\n console.error(`Invalid rule format: ${ruleArg}. Use: --rule <rule>=<severity>`);\n process.exit(2);\n }\n const [, ruleCode, ruleSev] = match;\n if (![\"error\", \"warning\", \"info\", \"off\"].includes(ruleSev)) {\n console.error(`Invalid rule severity: ${ruleSev}. Use: error, warning, info, off`);\n process.exit(2);\n }\n rules.set(ruleCode, ruleSev as Severity);\n }\n\n return rules;\n}\n\nfunction checkAction(ctx: CommandContext): void {\n const { options, args } = ctx;\n\n // Handle format-dependent color disabling\n const format = (options[\"format\"] as OutputFormat) || \"default\";\n if (format === \"json\" || format === \"github\") {\n process.env[\"NO_COLOR\"] = \"1\";\n }\n\n // Determine severity level\n let severity: SeverityKey = (options[\"severity\"] as SeverityKey) || \"info\";\n if (options[\"quiet\"]) {\n severity = \"error\";\n }\n\n // Parse file types\n const fileTypeStr = (options[\"file-type\"] as string) || \"md,thalo\";\n const fileTypes = fileTypeStr.split(\",\").map((t) => t.trim());\n\n // Parse rule overrides\n const rules = parseRuleOverrides(options[\"rule\"] as string | string[] | undefined);\n\n // Build check config\n const config: CheckConfig = {};\n if (rules.size > 0) {\n config.rules = Object.fromEntries(rules);\n }\n\n // Determine target paths\n const targetPaths = args.length > 0 ? args : [\".\"];\n\n // Watch mode\n if (options[\"watch\"]) {\n watchFiles(targetPaths, fileTypes, { format, severity }, config);\n return;\n }\n\n // Collect files\n const files = resolveFilesSync(targetPaths, fileTypes);\n\n if (files.length === 0) {\n const fileTypesStr = fileTypes.join(\", \");\n console.log(`No .${fileTypesStr} files found.`);\n process.exit(0);\n }\n\n // Run checks\n const runResult = executeCheck(files, config);\n\n // Output results\n outputResults(runResult, { format, severity });\n\n // Determine exit code\n if (runResult.result.errorCount > 0) {\n process.exit(1);\n }\n\n const maxWarnings = options[\"max-warnings\"];\n if (maxWarnings !== undefined) {\n const maxWarningsNum = parseInt(maxWarnings as string, 10);\n if (isNaN(maxWarningsNum) || maxWarningsNum < 0) {\n console.error(`Invalid max-warnings value: ${maxWarnings}`);\n process.exit(2);\n }\n\n if (runResult.result.warningCount > maxWarningsNum) {\n if (format !== \"json\") {\n console.log();\n console.error(\n pc.red(\n `Warning threshold exceeded: ${runResult.result.warningCount} warnings (max: ${maxWarningsNum})`,\n ),\n );\n }\n process.exit(1);\n }\n }\n}\n\nexport const checkCommand: CommandDef = {\n name: \"check\",\n description: \"Check and lint thalo and markdown files for errors and warnings\",\n args: {\n name: \"paths\",\n description: \"Files or directories to check\",\n required: false,\n multiple: true,\n },\n options: {\n quiet: {\n type: \"boolean\",\n short: \"q\",\n description: \"Only show errors, suppress warnings and info\",\n default: false,\n },\n format: {\n type: \"string\",\n short: \"f\",\n description: \"Output format\",\n choices: [\"default\", \"json\", \"compact\", \"github\"],\n default: \"default\",\n },\n severity: {\n type: \"string\",\n description: \"Minimum severity to report\",\n choices: [\"error\", \"warning\", \"info\"],\n default: \"info\",\n },\n \"max-warnings\": {\n type: \"string\",\n description: \"Exit with error if warnings exceed threshold\",\n },\n rule: {\n type: \"string\",\n description: \"Set rule severity (e.g., unknown-entity=off)\",\n multiple: true,\n },\n watch: {\n type: \"boolean\",\n short: \"w\",\n description: \"Watch files for changes and re-run\",\n default: false,\n },\n \"file-type\": {\n type: \"string\",\n description: \"Comma-separated list of file types to check (e.g., 'md,thalo')\",\n default: \"md,thalo\",\n },\n },\n action: checkAction,\n};\n"],"mappings":"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"}
|
package/dist/commands/format.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { relativePath } from "../files.js";
|
|
2
|
+
import { createWorkspace } from "@rejot-dev/thalo/node";
|
|
2
3
|
import pc from "picocolors";
|
|
3
|
-
import { runFormat } from "@rejot-dev/thalo";
|
|
4
4
|
import * as fs from "node:fs/promises";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { runFormat } from "@rejot-dev/thalo";
|
|
7
7
|
import ignore from "ignore";
|
|
8
8
|
|
|
9
9
|
//#region src/commands/format.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","names":["files: string[]","stat","resolve","result","files: FormatFileInput[]","result: FormatResult","parts: string[]","formatCommand: CommandDef"],"sources":["../../src/commands/format.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport ignore from \"ignore\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\nimport { createWorkspace } from \"@rejot-dev/thalo/native\";\nimport {\n runFormat,\n type FormatResult,\n type FormatFileInput,\n type SyntaxErrorInfo,\n} from \"@rejot-dev/thalo\";\nimport { relativePath } from \"../files.js\";\n\n// ===================\n// File Collection (format-specific with ignore patterns)\n// ===================\n\nasync function loadIgnoreFile(filePath: string): Promise<string[]> {\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n return content.split(\"\\n\").filter((line) => line.trim() && !line.startsWith(\"#\"));\n } catch {\n return [];\n }\n}\n\nasync function createIgnoreFilter(dir: string) {\n const ig = ignore();\n ig.add(await loadIgnoreFile(path.join(dir, \".gitignore\")));\n ig.add(await loadIgnoreFile(path.join(dir, \".prettierignore\")));\n return ig;\n}\n\nasync function collectFilesWithIgnore(dir: string, fileTypes: string[]): Promise<string[]> {\n const files: string[] = [];\n const ig = await createIgnoreFilter(dir);\n\n // Build glob patterns for each file type\n const patterns = fileTypes.map((type) => `**/*.${type}`);\n\n // exclude prevents traversing into node_modules/.git (perf), ig.ignores handles user patterns\n for (const pattern of patterns) {\n for await (const entry of fs.glob(pattern, {\n cwd: dir,\n exclude: (name) => name === \"node_modules\" || name.startsWith(\".\"),\n })) {\n // Normalize to forward slashes for ignore matching (ignore lib expects posix paths)\n const igPath = entry.split(path.sep).join(\"/\");\n if (!ig.ignores(igPath)) {\n files.push(path.join(dir, entry));\n }\n }\n }\n\n return files;\n}\n\nasync function resolveFormatFiles(paths: string[], fileTypes: string[]): Promise<string[]> {\n const files: string[] = [];\n\n for (const targetPath of paths) {\n const resolved = path.resolve(targetPath);\n\n try {\n const stat = await fs.stat(resolved);\n if (stat.isDirectory()) {\n files.push(...(await collectFilesWithIgnore(resolved, fileTypes)));\n } else if (stat.isFile()) {\n const ext = path.extname(resolved).slice(1); // Remove leading dot\n if (fileTypes.includes(ext)) {\n files.push(resolved);\n }\n }\n } catch {\n console.error(pc.red(`Error: Path not found: ${targetPath}`));\n process.exit(2);\n }\n }\n\n return files;\n}\n\nfunction formatSyntaxError(error: SyntaxErrorInfo): string {\n const loc = `${error.line}:${error.column}`.padEnd(8);\n const severityLabel = pc.red(\"error\".padEnd(7));\n const codeLabel = pc.dim(error.code);\n\n return ` ${pc.dim(loc)} ${severityLabel} ${error.message} ${codeLabel}`;\n}\n\n// ===================\n// Prettier Integration\n// ===================\n\nfunction getParser(filePath: string): string {\n const ext = path.extname(filePath).slice(1);\n if (ext === \"thalo\") {\n return \"thalo\";\n }\n if (ext === \"md\") {\n return \"markdown\";\n }\n return \"thalo\"; // default\n}\n\nasync function createPrettierFormatter(): Promise<\n (source: string, filepath: string) => Promise<string>\n> {\n const prettier = await import(\"prettier\");\n const thaloPrettier = await import(\"@rejot-dev/thalo-prettier\");\n\n return async (source: string, filepath: string): Promise<string> => {\n const parser = getParser(filepath);\n // Load project's prettier config (prettier.config.mjs, .prettierrc, etc.)\n const resolvedConfig = await prettier.resolveConfig(filepath);\n return prettier.format(source, {\n ...resolvedConfig,\n filepath,\n parser,\n plugins: [thaloPrettier],\n });\n };\n}\n\n// ===================\n// Command Action\n// ===================\n\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n let settled = false;\n process.stdin.setEncoding(\"utf-8\");\n\n const cleanup = () => {\n process.stdin.removeListener(\"data\", onData);\n process.stdin.removeListener(\"end\", onEnd);\n process.stdin.removeListener(\"error\", onError);\n process.stdin.removeListener(\"close\", onClose);\n };\n\n const settle = (fn: () => void) => {\n if (!settled) {\n settled = true;\n cleanup();\n fn();\n }\n };\n\n const onData = (chunk: string) => {\n data += chunk;\n };\n\n const onEnd = () => {\n settle(() => resolve(data));\n };\n\n const onError = (error: Error) => {\n settle(() => reject(error));\n };\n\n const onClose = () => {\n // This handles cases where stdin closes without 'end' (e.g., EOF)\n settle(() => resolve(data));\n };\n\n // 'data' can fire multiple times, so use 'on'\n process.stdin.on(\"data\", onData);\n // 'end', 'error', and 'close' should only fire once, so use 'once'\n process.stdin.once(\"end\", onEnd);\n process.stdin.once(\"error\", onError);\n process.stdin.once(\"close\", onClose);\n });\n}\n\nasync function formatAction(ctx: CommandContext): Promise<void> {\n const { options, args } = ctx;\n const checkOnly = options[\"check\"] as boolean;\n const writeBack = options[\"write\"] as boolean;\n const useStdin = options[\"stdin\"] as boolean;\n const fileTypeStr = (options[\"file-type\"] as string) || \"md,thalo\";\n const fileTypes = fileTypeStr.split(\",\").map((t) => t.trim());\n\n // Handle stdin mode - read from stdin, output to stdout\n if (useStdin) {\n const content = await readStdin();\n const workspace = createWorkspace();\n const formatter = await createPrettierFormatter();\n\n // Use a placeholder filepath for parser detection (default to .thalo)\n const filepath = args[0] || \"stdin.thalo\";\n const files: FormatFileInput[] = [{ file: filepath, content }];\n const result = await runFormat(workspace, files, { formatter });\n\n const fileResult = result.fileResults[0];\n if (fileResult) {\n // Output formatted content to stdout\n process.stdout.write(fileResult.formatted);\n }\n\n // Exit with error code if there were syntax errors\n if (result.syntaxErrorCount > 0) {\n process.exit(1);\n }\n return;\n }\n\n const targetPaths = args.length > 0 ? args : [\".\"];\n const filePaths = await resolveFormatFiles(targetPaths, fileTypes);\n\n if (filePaths.length === 0) {\n const fileTypesStr = fileTypes.join(\", \");\n console.log(`No .${fileTypesStr} files found.`);\n process.exit(0);\n }\n\n // Read all file contents\n const files: FormatFileInput[] = await Promise.all(\n filePaths.map(async (file) => ({\n file,\n content: await fs.readFile(file, \"utf-8\"),\n })),\n );\n\n // Create workspace and formatter\n const workspace = createWorkspace();\n const formatter = await createPrettierFormatter();\n\n // Run format\n const result: FormatResult = await runFormat(workspace, files, { formatter });\n\n // Output results\n let writeCount = 0;\n\n for (const fileResult of result.fileResults) {\n const relPath = relativePath(fileResult.file);\n\n if (fileResult.hasSyntaxErrors) {\n // File has syntax errors - mark as failed\n console.log(pc.bold(pc.red(`✗`) + ` ${relPath}`));\n } else if (checkOnly) {\n if (fileResult.isChanged) {\n // Make files needing formatting bold with ✗\n console.log(pc.bold(pc.red(`✗`) + ` ${relPath}`));\n } else {\n // Files already formatted\n console.log(pc.green(`✓`) + ` ${relPath}`);\n }\n } else if (writeBack) {\n if (fileResult.isChanged) {\n await fs.writeFile(fileResult.file, fileResult.formatted, \"utf-8\");\n // Make formatted files bold (like prettier)\n console.log(pc.bold(pc.green(`✓`) + ` ${relPath}`));\n writeCount++;\n } else {\n // Print unchanged files in regular text\n console.log(pc.green(`✓`) + ` ${relPath}`);\n }\n } else {\n // This branch shouldn't happen since write defaults to true, but keep for safety\n if (fileResult.isChanged) {\n console.log(pc.yellow(`⚠`) + ` ${relPath} (needs formatting)`);\n } else {\n console.log(pc.green(`✓`) + ` ${relPath}`);\n }\n }\n }\n\n // Print syntax errors grouped by file (like check command does)\n const filesWithErrors = result.fileResults.filter((r) => r.hasSyntaxErrors);\n if (filesWithErrors.length > 0) {\n console.log();\n for (const fileResult of filesWithErrors) {\n console.log();\n console.log(pc.underline(relativePath(fileResult.file)));\n for (const error of fileResult.syntaxErrors) {\n console.log(formatSyntaxError(error));\n }\n }\n }\n\n // Print summary\n if (result.filesProcessed > 1 || checkOnly) {\n console.log();\n if (checkOnly) {\n const totalIssues = result.changedCount + result.syntaxErrorCount;\n if (totalIssues > 0) {\n const parts: string[] = [];\n if (result.syntaxErrorCount > 0) {\n parts.push(\n pc.red(\n `${result.syntaxErrorCount} file${result.syntaxErrorCount !== 1 ? \"s\" : \"\"} with syntax errors`,\n ),\n );\n }\n if (result.changedCount > 0) {\n parts.push(\n pc.yellow(\n `${result.changedCount} file${result.changedCount !== 1 ? \"s\" : \"\"} need${result.changedCount === 1 ? \"s\" : \"\"} formatting`,\n ),\n );\n }\n console.log(parts.join(\", \"));\n process.exit(1);\n } else {\n console.log(pc.green(`All ${result.filesProcessed} files are properly formatted`));\n }\n } else if (writeBack) {\n console.log(`Formatted ${writeCount} file${writeCount !== 1 ? \"s\" : \"\"}`);\n }\n }\n\n if (result.syntaxErrorCount > 0) {\n process.exit(1);\n }\n}\n\nexport const formatCommand: CommandDef = {\n name: \"format\",\n description: \"Format thalo and markdown files using Prettier\",\n args: {\n name: \"paths\",\n description: \"Files or directories to format\",\n required: false,\n multiple: true,\n },\n options: {\n check: {\n type: \"boolean\",\n short: \"c\",\n description: \"Check if files are formatted (exit 1 if not)\",\n default: false,\n },\n write: {\n type: \"boolean\",\n short: \"w\",\n description: \"Write formatted output back to files\",\n default: true,\n },\n stdin: {\n type: \"boolean\",\n description: \"Read from stdin and output to stdout (for editor integration)\",\n default: false,\n },\n \"file-type\": {\n type: \"string\",\n description: \"Comma-separated list of file types to format (e.g., 'md,thalo')\",\n default: \"md,thalo\",\n },\n },\n action: formatAction,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"format.js","names":["files: string[]","stat","resolve","result","files: FormatFileInput[]","result: FormatResult","parts: string[]","formatCommand: CommandDef"],"sources":["../../src/commands/format.ts"],"sourcesContent":["import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport ignore from \"ignore\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\nimport { createWorkspace } from \"@rejot-dev/thalo/node\";\nimport {\n runFormat,\n type FormatResult,\n type FormatFileInput,\n type SyntaxErrorInfo,\n} from \"@rejot-dev/thalo\";\nimport { relativePath } from \"../files.js\";\n\n// ===================\n// File Collection (format-specific with ignore patterns)\n// ===================\n\nasync function loadIgnoreFile(filePath: string): Promise<string[]> {\n try {\n const content = await fs.readFile(filePath, \"utf-8\");\n return content.split(\"\\n\").filter((line) => line.trim() && !line.startsWith(\"#\"));\n } catch {\n return [];\n }\n}\n\nasync function createIgnoreFilter(dir: string) {\n const ig = ignore();\n ig.add(await loadIgnoreFile(path.join(dir, \".gitignore\")));\n ig.add(await loadIgnoreFile(path.join(dir, \".prettierignore\")));\n return ig;\n}\n\nasync function collectFilesWithIgnore(dir: string, fileTypes: string[]): Promise<string[]> {\n const files: string[] = [];\n const ig = await createIgnoreFilter(dir);\n\n // Build glob patterns for each file type\n const patterns = fileTypes.map((type) => `**/*.${type}`);\n\n // exclude prevents traversing into node_modules/.git (perf), ig.ignores handles user patterns\n for (const pattern of patterns) {\n for await (const entry of fs.glob(pattern, {\n cwd: dir,\n exclude: (name) => name === \"node_modules\" || name.startsWith(\".\"),\n })) {\n // Normalize to forward slashes for ignore matching (ignore lib expects posix paths)\n const igPath = entry.split(path.sep).join(\"/\");\n if (!ig.ignores(igPath)) {\n files.push(path.join(dir, entry));\n }\n }\n }\n\n return files;\n}\n\nasync function resolveFormatFiles(paths: string[], fileTypes: string[]): Promise<string[]> {\n const files: string[] = [];\n\n for (const targetPath of paths) {\n const resolved = path.resolve(targetPath);\n\n try {\n const stat = await fs.stat(resolved);\n if (stat.isDirectory()) {\n files.push(...(await collectFilesWithIgnore(resolved, fileTypes)));\n } else if (stat.isFile()) {\n const ext = path.extname(resolved).slice(1); // Remove leading dot\n if (fileTypes.includes(ext)) {\n files.push(resolved);\n }\n }\n } catch {\n console.error(pc.red(`Error: Path not found: ${targetPath}`));\n process.exit(2);\n }\n }\n\n return files;\n}\n\nfunction formatSyntaxError(error: SyntaxErrorInfo): string {\n const loc = `${error.line}:${error.column}`.padEnd(8);\n const severityLabel = pc.red(\"error\".padEnd(7));\n const codeLabel = pc.dim(error.code);\n\n return ` ${pc.dim(loc)} ${severityLabel} ${error.message} ${codeLabel}`;\n}\n\n// ===================\n// Prettier Integration\n// ===================\n\nfunction getParser(filePath: string): string {\n const ext = path.extname(filePath).slice(1);\n if (ext === \"thalo\") {\n return \"thalo\";\n }\n if (ext === \"md\") {\n return \"markdown\";\n }\n return \"thalo\"; // default\n}\n\nasync function createPrettierFormatter(): Promise<\n (source: string, filepath: string) => Promise<string>\n> {\n const prettier = await import(\"prettier\");\n const thaloPrettier = await import(\"@rejot-dev/thalo-prettier\");\n\n return async (source: string, filepath: string): Promise<string> => {\n const parser = getParser(filepath);\n // Load project's prettier config (prettier.config.mjs, .prettierrc, etc.)\n const resolvedConfig = await prettier.resolveConfig(filepath);\n return prettier.format(source, {\n ...resolvedConfig,\n filepath,\n parser,\n plugins: [thaloPrettier],\n });\n };\n}\n\n// ===================\n// Command Action\n// ===================\n\nasync function readStdin(): Promise<string> {\n return new Promise((resolve, reject) => {\n let data = \"\";\n let settled = false;\n process.stdin.setEncoding(\"utf-8\");\n\n const cleanup = () => {\n process.stdin.removeListener(\"data\", onData);\n process.stdin.removeListener(\"end\", onEnd);\n process.stdin.removeListener(\"error\", onError);\n process.stdin.removeListener(\"close\", onClose);\n };\n\n const settle = (fn: () => void) => {\n if (!settled) {\n settled = true;\n cleanup();\n fn();\n }\n };\n\n const onData = (chunk: string) => {\n data += chunk;\n };\n\n const onEnd = () => {\n settle(() => resolve(data));\n };\n\n const onError = (error: Error) => {\n settle(() => reject(error));\n };\n\n const onClose = () => {\n // This handles cases where stdin closes without 'end' (e.g., EOF)\n settle(() => resolve(data));\n };\n\n // 'data' can fire multiple times, so use 'on'\n process.stdin.on(\"data\", onData);\n // 'end', 'error', and 'close' should only fire once, so use 'once'\n process.stdin.once(\"end\", onEnd);\n process.stdin.once(\"error\", onError);\n process.stdin.once(\"close\", onClose);\n });\n}\n\nasync function formatAction(ctx: CommandContext): Promise<void> {\n const { options, args } = ctx;\n const checkOnly = options[\"check\"] as boolean;\n const writeBack = options[\"write\"] as boolean;\n const useStdin = options[\"stdin\"] as boolean;\n const fileTypeStr = (options[\"file-type\"] as string) || \"md,thalo\";\n const fileTypes = fileTypeStr.split(\",\").map((t) => t.trim());\n\n // Handle stdin mode - read from stdin, output to stdout\n if (useStdin) {\n const content = await readStdin();\n const workspace = createWorkspace();\n const formatter = await createPrettierFormatter();\n\n // Use a placeholder filepath for parser detection (default to .thalo)\n const filepath = args[0] || \"stdin.thalo\";\n const files: FormatFileInput[] = [{ file: filepath, content }];\n const result = await runFormat(workspace, files, { formatter });\n\n const fileResult = result.fileResults[0];\n if (fileResult) {\n // Output formatted content to stdout\n process.stdout.write(fileResult.formatted);\n }\n\n // Exit with error code if there were syntax errors\n if (result.syntaxErrorCount > 0) {\n process.exit(1);\n }\n return;\n }\n\n const targetPaths = args.length > 0 ? args : [\".\"];\n const filePaths = await resolveFormatFiles(targetPaths, fileTypes);\n\n if (filePaths.length === 0) {\n const fileTypesStr = fileTypes.join(\", \");\n console.log(`No .${fileTypesStr} files found.`);\n process.exit(0);\n }\n\n // Read all file contents\n const files: FormatFileInput[] = await Promise.all(\n filePaths.map(async (file) => ({\n file,\n content: await fs.readFile(file, \"utf-8\"),\n })),\n );\n\n // Create workspace and formatter\n const workspace = createWorkspace();\n const formatter = await createPrettierFormatter();\n\n // Run format\n const result: FormatResult = await runFormat(workspace, files, { formatter });\n\n // Output results\n let writeCount = 0;\n\n for (const fileResult of result.fileResults) {\n const relPath = relativePath(fileResult.file);\n\n if (fileResult.hasSyntaxErrors) {\n // File has syntax errors - mark as failed\n console.log(pc.bold(pc.red(`✗`) + ` ${relPath}`));\n } else if (checkOnly) {\n if (fileResult.isChanged) {\n // Make files needing formatting bold with ✗\n console.log(pc.bold(pc.red(`✗`) + ` ${relPath}`));\n } else {\n // Files already formatted\n console.log(pc.green(`✓`) + ` ${relPath}`);\n }\n } else if (writeBack) {\n if (fileResult.isChanged) {\n await fs.writeFile(fileResult.file, fileResult.formatted, \"utf-8\");\n // Make formatted files bold (like prettier)\n console.log(pc.bold(pc.green(`✓`) + ` ${relPath}`));\n writeCount++;\n } else {\n // Print unchanged files in regular text\n console.log(pc.green(`✓`) + ` ${relPath}`);\n }\n } else {\n // This branch shouldn't happen since write defaults to true, but keep for safety\n if (fileResult.isChanged) {\n console.log(pc.yellow(`⚠`) + ` ${relPath} (needs formatting)`);\n } else {\n console.log(pc.green(`✓`) + ` ${relPath}`);\n }\n }\n }\n\n // Print syntax errors grouped by file (like check command does)\n const filesWithErrors = result.fileResults.filter((r) => r.hasSyntaxErrors);\n if (filesWithErrors.length > 0) {\n console.log();\n for (const fileResult of filesWithErrors) {\n console.log();\n console.log(pc.underline(relativePath(fileResult.file)));\n for (const error of fileResult.syntaxErrors) {\n console.log(formatSyntaxError(error));\n }\n }\n }\n\n // Print summary\n if (result.filesProcessed > 1 || checkOnly) {\n console.log();\n if (checkOnly) {\n const totalIssues = result.changedCount + result.syntaxErrorCount;\n if (totalIssues > 0) {\n const parts: string[] = [];\n if (result.syntaxErrorCount > 0) {\n parts.push(\n pc.red(\n `${result.syntaxErrorCount} file${result.syntaxErrorCount !== 1 ? \"s\" : \"\"} with syntax errors`,\n ),\n );\n }\n if (result.changedCount > 0) {\n parts.push(\n pc.yellow(\n `${result.changedCount} file${result.changedCount !== 1 ? \"s\" : \"\"} need${result.changedCount === 1 ? \"s\" : \"\"} formatting`,\n ),\n );\n }\n console.log(parts.join(\", \"));\n process.exit(1);\n } else {\n console.log(pc.green(`All ${result.filesProcessed} files are properly formatted`));\n }\n } else if (writeBack) {\n console.log(`Formatted ${writeCount} file${writeCount !== 1 ? \"s\" : \"\"}`);\n }\n }\n\n if (result.syntaxErrorCount > 0) {\n process.exit(1);\n }\n}\n\nexport const formatCommand: CommandDef = {\n name: \"format\",\n description: \"Format thalo and markdown files using Prettier\",\n args: {\n name: \"paths\",\n description: \"Files or directories to format\",\n required: false,\n multiple: true,\n },\n options: {\n check: {\n type: \"boolean\",\n short: \"c\",\n description: \"Check if files are formatted (exit 1 if not)\",\n default: false,\n },\n write: {\n type: \"boolean\",\n short: \"w\",\n description: \"Write formatted output back to files\",\n default: true,\n },\n stdin: {\n type: \"boolean\",\n description: \"Read from stdin and output to stdout (for editor integration)\",\n default: false,\n },\n \"file-type\": {\n type: \"string\",\n description: \"Comma-separated list of file types to format (e.g., 'md,thalo')\",\n default: \"md,thalo\",\n },\n },\n action: formatAction,\n};\n"],"mappings":";;;;;;;;;AAkBA,eAAe,eAAe,UAAqC;AACjE,KAAI;AAEF,UADgB,MAAM,GAAG,SAAS,UAAU,QAAQ,EACrC,MAAM,KAAK,CAAC,QAAQ,SAAS,KAAK,MAAM,IAAI,CAAC,KAAK,WAAW,IAAI,CAAC;SAC3E;AACN,SAAO,EAAE;;;AAIb,eAAe,mBAAmB,KAAa;CAC7C,MAAM,KAAK,QAAQ;AACnB,IAAG,IAAI,MAAM,eAAe,KAAK,KAAK,KAAK,aAAa,CAAC,CAAC;AAC1D,IAAG,IAAI,MAAM,eAAe,KAAK,KAAK,KAAK,kBAAkB,CAAC,CAAC;AAC/D,QAAO;;AAGT,eAAe,uBAAuB,KAAa,WAAwC;CACzF,MAAMA,QAAkB,EAAE;CAC1B,MAAM,KAAK,MAAM,mBAAmB,IAAI;CAGxC,MAAM,WAAW,UAAU,KAAK,SAAS,QAAQ,OAAO;AAGxD,MAAK,MAAM,WAAW,SACpB,YAAW,MAAM,SAAS,GAAG,KAAK,SAAS;EACzC,KAAK;EACL,UAAU,SAAS,SAAS,kBAAkB,KAAK,WAAW,IAAI;EACnE,CAAC,EAAE;EAEF,MAAM,SAAS,MAAM,MAAM,KAAK,IAAI,CAAC,KAAK,IAAI;AAC9C,MAAI,CAAC,GAAG,QAAQ,OAAO,CACrB,OAAM,KAAK,KAAK,KAAK,KAAK,MAAM,CAAC;;AAKvC,QAAO;;AAGT,eAAe,mBAAmB,OAAiB,WAAwC;CACzF,MAAMA,QAAkB,EAAE;AAE1B,MAAK,MAAM,cAAc,OAAO;EAC9B,MAAM,WAAW,KAAK,QAAQ,WAAW;AAEzC,MAAI;GACF,MAAMC,SAAO,MAAM,GAAG,KAAK,SAAS;AACpC,OAAIA,OAAK,aAAa,CACpB,OAAM,KAAK,GAAI,MAAM,uBAAuB,UAAU,UAAU,CAAE;YACzDA,OAAK,QAAQ,EAAE;IACxB,MAAM,MAAM,KAAK,QAAQ,SAAS,CAAC,MAAM,EAAE;AAC3C,QAAI,UAAU,SAAS,IAAI,CACzB,OAAM,KAAK,SAAS;;UAGlB;AACN,WAAQ,MAAM,GAAG,IAAI,0BAA0B,aAAa,CAAC;AAC7D,WAAQ,KAAK,EAAE;;;AAInB,QAAO;;AAGT,SAAS,kBAAkB,OAAgC;CACzD,MAAM,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM,SAAS,OAAO,EAAE;CACrD,MAAM,gBAAgB,GAAG,IAAI,QAAQ,OAAO,EAAE,CAAC;CAC/C,MAAM,YAAY,GAAG,IAAI,MAAM,KAAK;AAEpC,QAAO,KAAK,GAAG,IAAI,IAAI,CAAC,GAAG,cAAc,GAAG,MAAM,QAAQ,IAAI;;AAOhE,SAAS,UAAU,UAA0B;CAC3C,MAAM,MAAM,KAAK,QAAQ,SAAS,CAAC,MAAM,EAAE;AAC3C,KAAI,QAAQ,QACV,QAAO;AAET,KAAI,QAAQ,KACV,QAAO;AAET,QAAO;;AAGT,eAAe,0BAEb;CACA,MAAM,WAAW,MAAM,OAAO;CAC9B,MAAM,gBAAgB,MAAM,OAAO;AAEnC,QAAO,OAAO,QAAgB,aAAsC;EAClE,MAAM,SAAS,UAAU,SAAS;EAElC,MAAM,iBAAiB,MAAM,SAAS,cAAc,SAAS;AAC7D,SAAO,SAAS,OAAO,QAAQ;GAC7B,GAAG;GACH;GACA;GACA,SAAS,CAAC,cAAc;GACzB,CAAC;;;AAQN,eAAe,YAA6B;AAC1C,QAAO,IAAI,SAAS,WAAS,WAAW;EACtC,IAAI,OAAO;EACX,IAAI,UAAU;AACd,UAAQ,MAAM,YAAY,QAAQ;EAElC,MAAM,gBAAgB;AACpB,WAAQ,MAAM,eAAe,QAAQ,OAAO;AAC5C,WAAQ,MAAM,eAAe,OAAO,MAAM;AAC1C,WAAQ,MAAM,eAAe,SAAS,QAAQ;AAC9C,WAAQ,MAAM,eAAe,SAAS,QAAQ;;EAGhD,MAAM,UAAU,OAAmB;AACjC,OAAI,CAAC,SAAS;AACZ,cAAU;AACV,aAAS;AACT,QAAI;;;EAIR,MAAM,UAAU,UAAkB;AAChC,WAAQ;;EAGV,MAAM,cAAc;AAClB,gBAAaC,UAAQ,KAAK,CAAC;;EAG7B,MAAM,WAAW,UAAiB;AAChC,gBAAa,OAAO,MAAM,CAAC;;EAG7B,MAAM,gBAAgB;AAEpB,gBAAaA,UAAQ,KAAK,CAAC;;AAI7B,UAAQ,MAAM,GAAG,QAAQ,OAAO;AAEhC,UAAQ,MAAM,KAAK,OAAO,MAAM;AAChC,UAAQ,MAAM,KAAK,SAAS,QAAQ;AACpC,UAAQ,MAAM,KAAK,SAAS,QAAQ;GACpC;;AAGJ,eAAe,aAAa,KAAoC;CAC9D,MAAM,EAAE,SAAS,SAAS;CAC1B,MAAM,YAAY,QAAQ;CAC1B,MAAM,YAAY,QAAQ;CAC1B,MAAM,WAAW,QAAQ;CAEzB,MAAM,aADe,QAAQ,gBAA2B,YAC1B,MAAM,IAAI,CAAC,KAAK,MAAM,EAAE,MAAM,CAAC;AAG7D,KAAI,UAAU;EACZ,MAAM,UAAU,MAAM,WAAW;EACjC,MAAM,YAAY,iBAAiB;EACnC,MAAM,YAAY,MAAM,yBAAyB;EAKjD,MAAMC,WAAS,MAAM,UAAU,WADE,CAAC;GAAE,MADnB,KAAK,MAAM;GACwB;GAAS,CAAC,EACb,EAAE,WAAW,CAAC;EAE/D,MAAM,aAAaA,SAAO,YAAY;AACtC,MAAI,WAEF,SAAQ,OAAO,MAAM,WAAW,UAAU;AAI5C,MAAIA,SAAO,mBAAmB,EAC5B,SAAQ,KAAK,EAAE;AAEjB;;CAIF,MAAM,YAAY,MAAM,mBADJ,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,EACM,UAAU;AAElE,KAAI,UAAU,WAAW,GAAG;EAC1B,MAAM,eAAe,UAAU,KAAK,KAAK;AACzC,UAAQ,IAAI,OAAO,aAAa,eAAe;AAC/C,UAAQ,KAAK,EAAE;;CAIjB,MAAMC,QAA2B,MAAM,QAAQ,IAC7C,UAAU,IAAI,OAAO,UAAU;EAC7B;EACA,SAAS,MAAM,GAAG,SAAS,MAAM,QAAQ;EAC1C,EAAE,CACJ;CAOD,MAAMC,SAAuB,MAAM,UAJjB,iBAAiB,EAIqB,OAAO,EAAE,WAH/C,MAAM,yBAAyB,EAG2B,CAAC;CAG7E,IAAI,aAAa;AAEjB,MAAK,MAAM,cAAc,OAAO,aAAa;EAC3C,MAAM,UAAU,aAAa,WAAW,KAAK;AAE7C,MAAI,WAAW,gBAEb,SAAQ,IAAI,GAAG,KAAK,GAAG,IAAI,IAAI,GAAG,IAAI,UAAU,CAAC;WACxC,UACT,KAAI,WAAW,UAEb,SAAQ,IAAI,GAAG,KAAK,GAAG,IAAI,IAAI,GAAG,IAAI,UAAU,CAAC;MAGjD,SAAQ,IAAI,GAAG,MAAM,IAAI,GAAG,IAAI,UAAU;WAEnC,UACT,KAAI,WAAW,WAAW;AACxB,SAAM,GAAG,UAAU,WAAW,MAAM,WAAW,WAAW,QAAQ;AAElE,WAAQ,IAAI,GAAG,KAAK,GAAG,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC;AACnD;QAGA,SAAQ,IAAI,GAAG,MAAM,IAAI,GAAG,IAAI,UAAU;WAIxC,WAAW,UACb,SAAQ,IAAI,GAAG,OAAO,IAAI,GAAG,IAAI,QAAQ,qBAAqB;MAE9D,SAAQ,IAAI,GAAG,MAAM,IAAI,GAAG,IAAI,UAAU;;CAMhD,MAAM,kBAAkB,OAAO,YAAY,QAAQ,MAAM,EAAE,gBAAgB;AAC3E,KAAI,gBAAgB,SAAS,GAAG;AAC9B,UAAQ,KAAK;AACb,OAAK,MAAM,cAAc,iBAAiB;AACxC,WAAQ,KAAK;AACb,WAAQ,IAAI,GAAG,UAAU,aAAa,WAAW,KAAK,CAAC,CAAC;AACxD,QAAK,MAAM,SAAS,WAAW,aAC7B,SAAQ,IAAI,kBAAkB,MAAM,CAAC;;;AAM3C,KAAI,OAAO,iBAAiB,KAAK,WAAW;AAC1C,UAAQ,KAAK;AACb,MAAI,UAEF,KADoB,OAAO,eAAe,OAAO,mBAC/B,GAAG;GACnB,MAAMC,QAAkB,EAAE;AAC1B,OAAI,OAAO,mBAAmB,EAC5B,OAAM,KACJ,GAAG,IACD,GAAG,OAAO,iBAAiB,OAAO,OAAO,qBAAqB,IAAI,MAAM,GAAG,qBAC5E,CACF;AAEH,OAAI,OAAO,eAAe,EACxB,OAAM,KACJ,GAAG,OACD,GAAG,OAAO,aAAa,OAAO,OAAO,iBAAiB,IAAI,MAAM,GAAG,OAAO,OAAO,iBAAiB,IAAI,MAAM,GAAG,aAChH,CACF;AAEH,WAAQ,IAAI,MAAM,KAAK,KAAK,CAAC;AAC7B,WAAQ,KAAK,EAAE;QAEf,SAAQ,IAAI,GAAG,MAAM,OAAO,OAAO,eAAe,+BAA+B,CAAC;WAE3E,UACT,SAAQ,IAAI,aAAa,WAAW,OAAO,eAAe,IAAI,MAAM,KAAK;;AAI7E,KAAI,OAAO,mBAAmB,EAC5B,SAAQ,KAAK,EAAE;;AAInB,MAAaC,gBAA4B;CACvC,MAAM;CACN,aAAa;CACb,MAAM;EACJ,MAAM;EACN,aAAa;EACb,UAAU;EACV,UAAU;EACX;CACD,SAAS;EACP,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,aAAa;GACX,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACF;CACD,QAAQ;CACT"}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pc from "picocolors";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
2
|
import * as path from "node:path";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
4
|
|
|
5
5
|
//#region src/commands/init.ts
|
|
6
6
|
const ENTITIES_THALO = `{{TIMESTAMP}} define-entity journal "Personal thoughts, reflections, and experiences" ^journal
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { promisify } from "node:util";
|
|
2
2
|
import pc from "picocolors";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
3
4
|
import * as fs from "node:fs/promises";
|
|
4
5
|
import * as path from "node:path";
|
|
5
|
-
import { execFile } from "node:child_process";
|
|
6
6
|
|
|
7
7
|
//#region src/commands/setup-merge-driver.ts
|
|
8
8
|
const execFile$1 = promisify(execFile);
|
package/dist/files.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { createWorkspace } from "@rejot-dev/thalo/node";
|
|
1
2
|
import pc from "picocolors";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
3
|
import { readFile, readdir, stat } from "node:fs/promises";
|
|
4
4
|
import * as path from "node:path";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
|
-
import
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
7
|
|
|
8
8
|
//#region src/files.ts
|
|
9
9
|
/**
|
package/dist/files.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.js","names":["files: string[]"],"sources":["../src/files.ts"],"sourcesContent":["/**\n * Centralized file collection and workspace utilities for CLI commands.\n */\n\nimport * as fs from \"node:fs\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { join, resolve } from \"node:path\";\nimport pc from \"picocolors\";\nimport { createWorkspace, type Workspace } from \"@rejot-dev/thalo/
|
|
1
|
+
{"version":3,"file":"files.js","names":["files: string[]"],"sources":["../src/files.ts"],"sourcesContent":["/**\n * Centralized file collection and workspace utilities for CLI commands.\n */\n\nimport * as fs from \"node:fs\";\nimport { readdir, readFile, stat } from \"node:fs/promises\";\nimport * as path from \"node:path\";\nimport { join, resolve } from \"node:path\";\nimport pc from \"picocolors\";\nimport { createWorkspace, type Workspace } from \"@rejot-dev/thalo/node\";\n\n/**\n * Default file extensions for thalo files.\n */\nexport const DEFAULT_EXTENSIONS = [\".thalo\", \".md\"];\n\n/**\n * Default file types (without leading dot).\n */\nexport const DEFAULT_FILE_TYPES = [\"thalo\", \"md\"];\n\n/**\n * Get relative path from current working directory.\n */\nexport function relativePath(filePath: string): string {\n const cwd = process.cwd();\n const resolvedCwd = path.resolve(cwd);\n const resolvedFilePath = path.resolve(filePath);\n const rel = path.relative(resolvedCwd, resolvedFilePath);\n return rel || filePath;\n}\n\n// ===================\n// Synchronous versions (for check, actualize)\n// ===================\n\n/**\n * Collect all thalo files from a directory (sync).\n */\nexport function collectThaloFilesSync(\n dir: string,\n extensions: string[] = DEFAULT_EXTENSIONS,\n): string[] {\n const files: string[] = [];\n\n function walk(currentDir: string): void {\n let entries;\n try {\n entries = fs.readdirSync(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n if (!entry.name.startsWith(\".\") && entry.name !== \"node_modules\") {\n walk(fullPath);\n }\n } else if (entry.isFile()) {\n if (extensions.some((ext) => entry.name.endsWith(ext))) {\n files.push(fullPath);\n }\n }\n }\n }\n\n walk(dir);\n return files;\n}\n\n/**\n * Resolve file paths from command arguments (sync).\n * Supports both file types with leading dot ([\".md\", \".thalo\"]) or without ([\"md\", \"thalo\"]).\n */\nexport function resolveFilesSync(\n paths: string[],\n fileTypes: string[] = DEFAULT_FILE_TYPES,\n): string[] {\n const files: string[] = [];\n // Normalize to extensions with leading dot\n const extensions = fileTypes.map((type) => (type.startsWith(\".\") ? type : `.${type}`));\n\n for (const targetPath of paths) {\n const resolved = path.resolve(targetPath);\n\n if (!fs.existsSync(resolved)) {\n console.error(pc.red(`Error: Path not found: ${targetPath}`));\n process.exit(2);\n }\n\n const fileStat = fs.statSync(resolved);\n if (fileStat.isDirectory()) {\n files.push(...collectThaloFilesSync(resolved, extensions));\n } else if (fileStat.isFile()) {\n // Accept file if extension matches or if no filtering is needed\n if (extensions.some((ext) => resolved.endsWith(ext))) {\n files.push(resolved);\n }\n }\n }\n\n return files;\n}\n\n/**\n * Load workspace from files (sync).\n */\nexport function loadWorkspaceSync(files: string[]): Workspace {\n const workspace = createWorkspace();\n\n for (const file of files) {\n try {\n const source = fs.readFileSync(file, \"utf-8\");\n workspace.addDocument(source, { filename: file });\n } catch (err) {\n console.error(pc.red(`Error reading ${file}: ${err instanceof Error ? err.message : err}`));\n }\n }\n\n return workspace;\n}\n\n// ===================\n// Async versions (for query, format)\n// ===================\n\n/**\n * Collect all thalo files from a directory (async).\n */\nexport async function collectThaloFiles(\n dir: string,\n extensions: string[] = DEFAULT_EXTENSIONS,\n): Promise<string[]> {\n const files: string[] = [];\n\n async function walk(currentDir: string): Promise<void> {\n let entries;\n try {\n entries = await readdir(currentDir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n const fullPath = join(currentDir, entry.name);\n\n if (entry.isDirectory()) {\n if (!entry.name.startsWith(\".\") && entry.name !== \"node_modules\") {\n await walk(fullPath);\n }\n } else if (entry.isFile()) {\n if (extensions.some((ext) => entry.name.endsWith(ext))) {\n files.push(fullPath);\n }\n }\n }\n }\n\n await walk(dir);\n return files;\n}\n\n/**\n * Resolve file paths from command arguments (async).\n * Supports both file types with leading dot ([\".md\", \".thalo\"]) or without ([\"md\", \"thalo\"]).\n */\nexport async function resolveFiles(\n paths: string[],\n fileTypes: string[] = DEFAULT_FILE_TYPES,\n): Promise<string[]> {\n const files: string[] = [];\n // Normalize to extensions with leading dot\n const extensions = fileTypes.map((type) => (type.startsWith(\".\") ? type : `.${type}`));\n\n for (const targetPath of paths) {\n const resolved = resolve(targetPath);\n\n let fileStat;\n try {\n fileStat = await stat(resolved);\n } catch {\n console.error(pc.red(`Error: Path not found: ${targetPath}`));\n process.exit(2);\n }\n\n if (fileStat.isDirectory()) {\n files.push(...(await collectThaloFiles(resolved, extensions)));\n } else if (fileStat.isFile()) {\n // Accept file if extension matches\n if (extensions.some((ext) => resolved.endsWith(ext))) {\n files.push(resolved);\n }\n }\n }\n\n return files;\n}\n\n/**\n * Load workspace from files (async).\n */\nexport async function loadWorkspace(files: string[]): Promise<Workspace> {\n const workspace = createWorkspace();\n\n for (const file of files) {\n try {\n const source = await readFile(file, \"utf-8\");\n workspace.addDocument(source, { filename: file });\n } catch (err) {\n console.error(pc.red(`Error reading ${file}: ${err instanceof Error ? err.message : err}`));\n }\n }\n\n return workspace;\n}\n\n/**\n * Load the full workspace from the current working directory (async).\n * This is the standard way to load a workspace - always includes all files from CWD.\n */\nexport async function loadFullWorkspace(\n fileTypes: string[] = DEFAULT_FILE_TYPES,\n): Promise<{ workspace: Workspace; files: string[] }> {\n const files = await resolveFiles([\".\"], fileTypes);\n const workspace = await loadWorkspace(files);\n return { workspace, files };\n}\n"],"mappings":"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"}
|
package/dist/mod.js
CHANGED
|
@@ -8,9 +8,15 @@ import { queryCommand } from "./commands/query.js";
|
|
|
8
8
|
import { rulesCommand } from "./commands/rules.js";
|
|
9
9
|
import { mergeDriverCommand } from "./commands/merge-driver.js";
|
|
10
10
|
import { setupMergeDriverCommand } from "./commands/setup-merge-driver.js";
|
|
11
|
+
import { initParser } from "@rejot-dev/thalo/node";
|
|
11
12
|
|
|
12
13
|
//#region src/mod.ts
|
|
13
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Root command definition
|
|
16
|
+
*
|
|
17
|
+
* When invoked without a subcommand, shows help
|
|
18
|
+
*/
|
|
19
|
+
const rootCommand = {
|
|
14
20
|
name: "thalo",
|
|
15
21
|
description: "Lint and check thalo files",
|
|
16
22
|
subcommands: {
|
|
@@ -24,6 +30,13 @@ runCli({
|
|
|
24
30
|
"merge-driver": mergeDriverCommand,
|
|
25
31
|
"setup-merge-driver": setupMergeDriverCommand
|
|
26
32
|
}
|
|
33
|
+
};
|
|
34
|
+
initParser().then(() => {
|
|
35
|
+
runCli(rootCommand);
|
|
36
|
+
}).catch((err) => {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
console.error(`Error: Failed to initialize parser: ${message}`);
|
|
39
|
+
process.exit(1);
|
|
27
40
|
});
|
|
28
41
|
|
|
29
42
|
//#endregion
|
package/dist/mod.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mod.js","names":[],"sources":["../src/mod.ts"],"sourcesContent":["import { runCli, type CommandDef } from \"./cli.js\";\nimport { actualizeCommand } from \"./commands/actualize.js\";\nimport { checkCommand } from \"./commands/check.js\";\nimport { formatCommand } from \"./commands/format.js\";\nimport { initCommand } from \"./commands/init.js\";\nimport { lspCommand } from \"./commands/lsp.js\";\nimport { queryCommand } from \"./commands/query.js\";\nimport { rulesCommand } from \"./commands/rules.js\";\nimport { mergeDriverCommand } from \"./commands/merge-driver.js\";\nimport { setupMergeDriverCommand } from \"./commands/setup-merge-driver.js\";\n\n/**\n * Root command definition\n *\n * When invoked without a subcommand, shows help\n */\nconst rootCommand: CommandDef = {\n name: \"thalo\",\n description: \"Lint and check thalo files\",\n subcommands: {\n actualize: actualizeCommand,\n check: checkCommand,\n format: formatCommand,\n init: initCommand,\n lsp: lspCommand,\n query: queryCommand,\n rules: rulesCommand,\n \"merge-driver\": mergeDriverCommand,\n \"setup-merge-driver\": setupMergeDriverCommand,\n },\n};\n\
|
|
1
|
+
{"version":3,"file":"mod.js","names":["rootCommand: CommandDef"],"sources":["../src/mod.ts"],"sourcesContent":["import { initParser } from \"@rejot-dev/thalo/node\";\nimport { runCli, type CommandDef } from \"./cli.js\";\nimport { actualizeCommand } from \"./commands/actualize.js\";\nimport { checkCommand } from \"./commands/check.js\";\nimport { formatCommand } from \"./commands/format.js\";\nimport { initCommand } from \"./commands/init.js\";\nimport { lspCommand } from \"./commands/lsp.js\";\nimport { queryCommand } from \"./commands/query.js\";\nimport { rulesCommand } from \"./commands/rules.js\";\nimport { mergeDriverCommand } from \"./commands/merge-driver.js\";\nimport { setupMergeDriverCommand } from \"./commands/setup-merge-driver.js\";\n\n/**\n * Root command definition\n *\n * When invoked without a subcommand, shows help\n */\nconst rootCommand: CommandDef = {\n name: \"thalo\",\n description: \"Lint and check thalo files\",\n subcommands: {\n actualize: actualizeCommand,\n check: checkCommand,\n format: formatCommand,\n init: initCommand,\n lsp: lspCommand,\n query: queryCommand,\n rules: rulesCommand,\n \"merge-driver\": mergeDriverCommand,\n \"setup-merge-driver\": setupMergeDriverCommand,\n },\n};\n\n// Initialize parser (with WASM fallback if native bindings unavailable)\n// then run the CLI\ninitParser()\n .then(() => {\n runCli(rootCommand);\n })\n .catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`Error: Failed to initialize parser: ${message}`);\n process.exit(1);\n });\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,MAAMA,cAA0B;CAC9B,MAAM;CACN,aAAa;CACb,aAAa;EACX,WAAW;EACX,OAAO;EACP,QAAQ;EACR,MAAM;EACN,KAAK;EACL,OAAO;EACP,OAAO;EACP,gBAAgB;EAChB,sBAAsB;EACvB;CACF;AAID,YAAY,CACT,WAAW;AACV,QAAO,YAAY;EACnB,CACD,OAAO,QAAQ;CACd,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAQ,MAAM,uCAAuC,UAAU;AAC/D,SAAQ,KAAK,EAAE;EACf"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { isInitialized, isUsingNative } from "@rejot-dev/thalo/node";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
|
|
8
|
+
//#region src/version.ts
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
/**
|
|
12
|
+
* Get the package version from package.json
|
|
13
|
+
*/
|
|
14
|
+
async function getPackageVersion() {
|
|
15
|
+
try {
|
|
16
|
+
const content = await readFile(join(packageRoot, "package.json"), "utf-8");
|
|
17
|
+
return JSON.parse(content).version || "unknown";
|
|
18
|
+
} catch {
|
|
19
|
+
return "unknown";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get git commit hash if running from a git repository
|
|
24
|
+
*/
|
|
25
|
+
async function getGitHash() {
|
|
26
|
+
try {
|
|
27
|
+
const { stdout } = await execAsync("git rev-parse --short HEAD", { cwd: packageRoot });
|
|
28
|
+
return stdout.trim() || null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if there are uncommitted changes in the git repo
|
|
35
|
+
*/
|
|
36
|
+
async function isGitDirty() {
|
|
37
|
+
try {
|
|
38
|
+
const { stdout } = await execAsync("git status --porcelain", { cwd: packageRoot });
|
|
39
|
+
return stdout.trim().length > 0;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the parser backend being used (native, wasm, or unknown if not initialized)
|
|
46
|
+
*/
|
|
47
|
+
function getParserBackend() {
|
|
48
|
+
if (!isInitialized()) return "unknown";
|
|
49
|
+
return isUsingNative() ? "native" : "wasm";
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get all version information
|
|
53
|
+
*/
|
|
54
|
+
async function getVersionInfo() {
|
|
55
|
+
const [version, gitHash, gitDirty] = await Promise.all([
|
|
56
|
+
getPackageVersion(),
|
|
57
|
+
getGitHash(),
|
|
58
|
+
isGitDirty()
|
|
59
|
+
]);
|
|
60
|
+
return {
|
|
61
|
+
version,
|
|
62
|
+
gitHash,
|
|
63
|
+
gitDirty,
|
|
64
|
+
parserBackend: getParserBackend()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Format version info as a string for display
|
|
69
|
+
*/
|
|
70
|
+
function formatVersion(info) {
|
|
71
|
+
let versionStr = `thalo v${info.version}`;
|
|
72
|
+
if (info.gitHash) versionStr += ` (${info.gitHash}${info.gitDirty ? "-dirty" : ""})`;
|
|
73
|
+
versionStr += ` [${info.parserBackend}]`;
|
|
74
|
+
return versionStr;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { formatVersion, getVersionInfo };
|
|
79
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","names":[],"sources":["../src/version.ts"],"sourcesContent":["import { exec } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\nimport { promisify } from \"node:util\";\nimport { fileURLToPath } from \"node:url\";\nimport { dirname, join } from \"node:path\";\nimport { isInitialized, isUsingNative } from \"@rejot-dev/thalo/node\";\n\nconst execAsync = promisify(exec);\n\n// Compute package root once at module level\n// In dist/, go up one level to find package.json\n// In src/, go up one level to find package.json\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst packageRoot = join(__dirname, \"..\");\n\n/**\n * Get the package version from package.json\n */\nasync function getPackageVersion(): Promise<string> {\n try {\n const packageJsonPath = join(packageRoot, \"package.json\");\n const content = await readFile(packageJsonPath, \"utf-8\");\n const pkg = JSON.parse(content);\n return pkg.version || \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\n/**\n * Get git commit hash if running from a git repository\n */\nasync function getGitHash(): Promise<string | null> {\n try {\n const { stdout } = await execAsync(\"git rev-parse --short HEAD\", { cwd: packageRoot });\n return stdout.trim() || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Check if there are uncommitted changes in the git repo\n */\nasync function isGitDirty(): Promise<boolean> {\n try {\n const { stdout } = await execAsync(\"git status --porcelain\", { cwd: packageRoot });\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the parser backend being used (native, wasm, or unknown if not initialized)\n */\nfunction getParserBackend(): \"native\" | \"wasm\" | \"unknown\" {\n if (!isInitialized()) {\n return \"unknown\";\n }\n return isUsingNative() ? \"native\" : \"wasm\";\n}\n\n/**\n * Version info structure\n */\nexport interface VersionInfo {\n version: string;\n gitHash: string | null;\n gitDirty: boolean;\n parserBackend: \"native\" | \"wasm\" | \"unknown\";\n}\n\n/**\n * Get all version information\n */\nexport async function getVersionInfo(): Promise<VersionInfo> {\n const [version, gitHash, gitDirty] = await Promise.all([\n getPackageVersion(),\n getGitHash(),\n isGitDirty(),\n ]);\n\n return {\n version,\n gitHash,\n gitDirty,\n parserBackend: getParserBackend(),\n };\n}\n\n/**\n * Format version info as a string for display\n */\nexport function formatVersion(info: VersionInfo): string {\n let versionStr = `thalo v${info.version}`;\n\n if (info.gitHash) {\n versionStr += ` (${info.gitHash}${info.gitDirty ? \"-dirty\" : \"\"})`;\n }\n\n versionStr += ` [${info.parserBackend}]`;\n\n return versionStr;\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,YAAY,UAAU,KAAK;AAMjC,MAAM,cAAc,KADF,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACrB,KAAK;;;;AAKzC,eAAe,oBAAqC;AAClD,KAAI;EAEF,MAAM,UAAU,MAAM,SADE,KAAK,aAAa,eAAe,EACT,QAAQ;AAExD,SADY,KAAK,MAAM,QAAQ,CACpB,WAAW;SAChB;AACN,SAAO;;;;;;AAOX,eAAe,aAAqC;AAClD,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,UAAU,8BAA8B,EAAE,KAAK,aAAa,CAAC;AACtF,SAAO,OAAO,MAAM,IAAI;SAClB;AACN,SAAO;;;;;;AAOX,eAAe,aAA+B;AAC5C,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,UAAU,0BAA0B,EAAE,KAAK,aAAa,CAAC;AAClF,SAAO,OAAO,MAAM,CAAC,SAAS;SACxB;AACN,SAAO;;;;;;AAOX,SAAS,mBAAkD;AACzD,KAAI,CAAC,eAAe,CAClB,QAAO;AAET,QAAO,eAAe,GAAG,WAAW;;;;;AAgBtC,eAAsB,iBAAuC;CAC3D,MAAM,CAAC,SAAS,SAAS,YAAY,MAAM,QAAQ,IAAI;EACrD,mBAAmB;EACnB,YAAY;EACZ,YAAY;EACb,CAAC;AAEF,QAAO;EACL;EACA;EACA;EACA,eAAe,kBAAkB;EAClC;;;;;AAMH,SAAgB,cAAc,MAA2B;CACvD,IAAI,aAAa,UAAU,KAAK;AAEhC,KAAI,KAAK,QACP,eAAc,KAAK,KAAK,UAAU,KAAK,WAAW,WAAW,GAAG;AAGlE,eAAc,KAAK,KAAK,cAAc;AAEtC,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rejot-dev/thalo-cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,12 +28,15 @@
|
|
|
28
28
|
"ignore": "^7.0.5",
|
|
29
29
|
"picocolors": "^1.1.1",
|
|
30
30
|
"prettier": "^3.5.3",
|
|
31
|
-
"tree-sitter": "^0.25.0",
|
|
32
31
|
"vscode-languageserver": "^9.0.1",
|
|
33
|
-
"
|
|
34
|
-
"@rejot-dev/thalo
|
|
35
|
-
"@rejot-dev/thalo-
|
|
36
|
-
"@rejot-dev/
|
|
32
|
+
"web-tree-sitter": "^0.25.0",
|
|
33
|
+
"@rejot-dev/thalo": "0.2.1",
|
|
34
|
+
"@rejot-dev/thalo-lsp": "0.2.1",
|
|
35
|
+
"@rejot-dev/thalo-prettier": "0.2.1",
|
|
36
|
+
"@rejot-dev/tree-sitter-thalo": "0.2.1"
|
|
37
|
+
},
|
|
38
|
+
"optionalDependencies": {
|
|
39
|
+
"tree-sitter": "^0.25.0"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
39
42
|
"@types/node": "^24",
|