Package not found. Please check the package name and try again.

@rejot-dev/thalo-cli 0.0.0 → 0.2.0

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.
@@ -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";
3
4
  import { formatDiagnostic, runCheck } from "@rejot-dev/thalo";
4
5
  import * as fs from "node:fs";
5
6
  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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"}
@@ -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
4
  import { runFormat } from "@rejot-dev/thalo";
4
5
  import * as fs from "node:fs/promises";
5
6
  import * as path from "node:path";
6
- import { createWorkspace } from "@rejot-dev/thalo/native";
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":"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"}
package/dist/files.js CHANGED
@@ -1,9 +1,9 @@
1
+ import { createWorkspace } from "@rejot-dev/thalo/node";
1
2
  import pc from "picocolors";
2
3
  import * as fs from "node:fs";
3
4
  import { readFile, readdir, stat } from "node:fs/promises";
4
5
  import * as path from "node:path";
5
6
  import { join, resolve } from "node:path";
6
- import { createWorkspace } from "@rejot-dev/thalo/native";
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/native\";\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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"}
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
- runCli({
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\nrunCli(rootCommand);\n"],"mappings":";;;;;;;;;;;;AAgCA,OAhBgC;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,CAEkB"}
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rejot-dev/thalo-cli",
3
- "version": "0.0.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,18 +28,21 @@
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
- "@rejot-dev/thalo": "0.0.0",
34
- "@rejot-dev/thalo-lsp": "0.0.0",
35
- "@rejot-dev/thalo-prettier": "0.0.0",
36
- "@rejot-dev/tree-sitter-thalo": "0.0.0"
32
+ "web-tree-sitter": "^0.25.0",
33
+ "@rejot-dev/thalo": "0.2.0",
34
+ "@rejot-dev/thalo-lsp": "0.2.0",
35
+ "@rejot-dev/thalo-prettier": "0.2.0",
36
+ "@rejot-dev/tree-sitter-thalo": "0.2.0"
37
+ },
38
+ "optionalDependencies": {
39
+ "tree-sitter": "^0.25.0"
37
40
  },
38
41
  "devDependencies": {
39
42
  "@types/node": "^24",
40
43
  "tsdown": "^0.15.12",
41
44
  "tsx": "4.21.0",
42
- "typescript": "^5.7.3",
45
+ "typescript": "^5.9.3",
43
46
  "vitest": "^3.2.4",
44
47
  "@rejot-private/typescript-config": "0.0.1"
45
48
  },