diff --git a/.gitignore b/.gitignore index 2037a80..d0ce78b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ /node_modules/ -/bin/**/*.d.ts -/bin/**/*.js -/lib/ -/build/ /.npmrc /devel/ diff --git a/README.md b/README.md index 09a9d96..6ac0a24 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ npm install -g @byteb4rb1e/mime-todo ```sh # Create a TODO file and start tracking issues -mime-todo create --type feature --title "Add login" --plan "Implement OAuth2 flow" -mime-todo start 1 --plan "Using passport.js with Google provider" -mime-todo push # sync to Bugzilla -mime-todo done 1 --summary "OAuth2 login with Google, GitHub providers" +todo create --type feature --title "Add login" --plan "Implement OAuth2 flow" +todo start 1 --plan "Using passport.js with Google provider" +todo push # sync to Bugzilla +todo done 1 --summary "OAuth2 login with Google, GitHub providers" ``` ## Commands @@ -27,11 +27,11 @@ mime-todo done 1 --summary "OAuth2 login with Google, GitHub providers" | Command | Branch | Description | |---------|--------|-------------| -| `mime-todo create --type --title --plan` | `develop` | Create a new issue | -| `mime-todo start --plan` | `develop` | Set issue to in-progress | -| `mime-todo done --summary` | `/` | Mark issue as done | -| `mime-todo hold --reason` | `/` | Put issue on hold | -| `mime-todo cancel --reason` | `develop` or `/` | Cancel an issue | +| `todo create --type --title --plan` | `develop` | Create a new issue | +| `todo start --plan` | `develop` | Set issue to in-progress | +| `todo done --summary` | `/` | Mark issue as done | +| `todo hold --reason` | `/` | Put issue on hold | +| `todo cancel --reason` | `develop` or `/` | Cancel an issue | Each lifecycle command creates a dedicated commit (`todo(): `) that modifies only the `TODO` file. @@ -40,30 +40,30 @@ that modifies only the `TODO` file. | Command | Description | |---------|-------------| -| `mime-todo list` | List all issues | -| `mime-todo show ` | Show issue details | -| `mime-todo sprints` | List all sprints | -| `mime-todo issues-in-sprint ` | List issues in a sprint | +| `todo list` | List all issues | +| `todo show ` | Show issue details | +| `todo sprints` | List all sprints | +| `todo issues-in-sprint ` | List issues in a sprint | ### Bugzilla Integration | Command | Description | |---------|-------------| -| `mime-todo init` | Check/create Bugzilla products and components | -| `mime-todo push [ref]` | Push commits as Bugzilla comments | +| `todo init` | Check/create Bugzilla products and components | +| `todo push [ref]` | Push commits as Bugzilla comments | -#### `mime-todo init` +#### `todo init` Reads the `application/bugzilla` part from the `TODO` file and ensures the referenced products and components exist on the Bugzilla server. ```sh -mime-todo init # check what exists -mime-todo init --dry-run # preview changes -mime-todo init --confirm --assignee user@example.com # create missing items +todo init # check what exists +todo init --dry-run # preview changes +todo init --confirm --assignee user@example.com # create missing items ``` -#### `mime-todo push` +#### `todo push` Pushes git commits to Bugzilla as comments. Context-aware: @@ -73,10 +73,10 @@ Pushes git commits to Bugzilla as comments. Context-aware: updating bug status and posting the commit body as a comment. ```sh -mime-todo push # push all unpushed commits -mime-todo push HEAD~3 # push only the last 3 commits -mime-todo push --dry-run # preview without pushing -mime-todo push --strategy full # re-scan all bugs (not just targeted) +todo push # push all unpushed commits +todo push HEAD~3 # push only the last 3 commits +todo push --dry-run # preview without pushing +todo push --strategy full # re-scan all bugs (not just targeted) ``` Each comment includes a clickable link to the commit. Comments are tagged diff --git a/bin/bugzilla.ts b/bin/bugzilla.ts index 76131f9..b926325 100644 --- a/bin/bugzilla.ts +++ b/bin/bugzilla.ts @@ -2,8 +2,8 @@ // Standalone Bugzilla CLI — interact with Bugzilla REST API directly import yargs from "yargs" import { hideBin } from "yargs/helpers" -import { BugzillaClient } from "../lib/bugzilla/client.js" -import { loadConfig } from "../lib/bugzilla/config.js" +import { BugzillaClient } from "../lib/bugzilla/client" +import { loadConfig } from "../lib/bugzilla/config" function getClient() { const config = loadConfig() diff --git a/bin/main.ts b/bin/main.ts index 825e13c..d7c43b9 100644 --- a/bin/main.ts +++ b/bin/main.ts @@ -1,19 +1,19 @@ #!/usr/bin/env node // mime-todo CLI — spec-compliant issue lifecycle management -import { CLI } from "../lib/cli/CLI.js" -import { CreateCommand } from "../lib/commands/CreateCommand.js" -import { StartCommand } from "../lib/commands/StartCommand.js" -import { DoneCommand } from "../lib/commands/DoneCommand.js" -import { HoldCommand } from "../lib/commands/HoldCommand.js" -import { CancelCommand } from "../lib/commands/CancelCommand.js" -import { IssueListCommand } from "../lib/commands/IssueListCommand.js" -import { IssueShowCommand } from "../lib/commands/IssueShowCommand.js" -import { SprintsCommand } from "../lib/commands/SprintsCommand.js" -import { IssuesInSprintCommand } from "../lib/commands/IssuesInSprintCommand.js" -import { PushCommand } from "../lib/commands/PushCommand.js" -import { InitCommand } from "../lib/commands/InitCommand.js" +import { CLI } from "../lib/cli/CLI" +import { CreateCommand } from "../lib/commands/CreateCommand" +import { StartCommand } from "../lib/commands/StartCommand" +import { DoneCommand } from "../lib/commands/DoneCommand" +import { HoldCommand } from "../lib/commands/HoldCommand" +import { CancelCommand } from "../lib/commands/CancelCommand" +import { IssueListCommand } from "../lib/commands/IssueListCommand" +import { IssueShowCommand } from "../lib/commands/IssueShowCommand" +import { SprintsCommand } from "../lib/commands/SprintsCommand" +import { IssuesInSprintCommand } from "../lib/commands/IssuesInSprintCommand" +import { PushCommand } from "../lib/commands/PushCommand" +import { InitCommand } from "../lib/commands/InitCommand" -const cli = new CLI({ prog: "mime-todo", description: "MIME TODO issue tracker" }) +const cli = new CLI({ prog: "todo", description: "MIME TODO issue tracker" }) cli.bootstrap([ InitCommand, CreateCommand, diff --git a/eslint.config.mjs b/eslint.config.mjs deleted file mode 100644 index 0fcbde3..0000000 --- a/eslint.config.mjs +++ /dev/null @@ -1,9 +0,0 @@ -// @ts-check - -import eslint from '@eslint/js'; -import tseslint from 'typescript-eslint'; - -export default tseslint.config( - eslint.configs.recommended, - tseslint.configs.recommended, -); diff --git a/src/bugzilla/client.ts b/lib/bugzilla/client.ts similarity index 98% rename from src/bugzilla/client.ts rename to lib/bugzilla/client.ts index 3dd190c..19f3fcb 100644 --- a/src/bugzilla/client.ts +++ b/lib/bugzilla/client.ts @@ -1,5 +1,5 @@ // Bugzilla 5.0+ REST API client — thin wrapper around fetch -import type { BugzillaConfig } from "./config.js" +import type { BugzillaConfig } from "./config" import type { BugzillaBug, BugzillaComment, @@ -15,7 +15,7 @@ import type { BugzillaUpdateResponse, BugzillaCommentsResponse, BugzillaProductResponse, -} from "./types.js" +} from "./types" export class BugzillaClient { private baseUrl: string diff --git a/src/bugzilla/config.ts b/lib/bugzilla/config.ts similarity index 100% rename from src/bugzilla/config.ts rename to lib/bugzilla/config.ts diff --git a/src/bugzilla/fieldmap.ts b/lib/bugzilla/fieldmap.ts similarity index 92% rename from src/bugzilla/fieldmap.ts rename to lib/bugzilla/fieldmap.ts index f9218d5..6c9e0d5 100644 --- a/src/bugzilla/fieldmap.ts +++ b/lib/bugzilla/fieldmap.ts @@ -1,6 +1,6 @@ // Field mapping between MIME TODO issues and Bugzilla bugs -import type { Issue, IssueType, IssueStatus, IssuePriority } from "../issue.js" -import type { BugzillaCreatePayload, BugzillaUpdatePayload } from "./types.js" +import type { Issue, IssueType, IssueStatus, IssuePriority } from "../issue" +import type { BugzillaCreatePayload, BugzillaUpdatePayload } from "./types" // Status mapping: TODO → Bugzilla const STATUS_TO_BZ: Record = { @@ -39,11 +39,11 @@ export function typeToBugzilla(type: IssueType): string { export function resolveProductComponent( issue: Issue, - bugzilla?: import("../tracker.js").BugzillaTracker + bugzilla?: import("../tracker").BugzillaTracker ): { product: string; component: string } | null { if (!bugzilla) return null if (issue.module) { - const mapping = bugzilla.mappings.find((m: { module: string; product: string; component: string }) => m.module === issue.module) + const mapping = bugzilla.mappings.find(m => m.module === issue.module) if (mapping) return { product: mapping.product, component: mapping.component } } // Fall back to first mapping diff --git a/lib/bugzilla/index.ts b/lib/bugzilla/index.ts new file mode 100644 index 0000000..77fe99a --- /dev/null +++ b/lib/bugzilla/index.ts @@ -0,0 +1,7 @@ +// Re-export all Bugzilla modules for convenient imports +export { BugzillaClient } from "./client" +export { loadConfig } from "./config" +export type { BugzillaConfig } from "./config" +export * from "./types" +export * from "./fieldmap" +export * from "./origin" diff --git a/src/bugzilla/origin.ts b/lib/bugzilla/origin.ts similarity index 100% rename from src/bugzilla/origin.ts rename to lib/bugzilla/origin.ts diff --git a/src/bugzilla/types.ts b/lib/bugzilla/types.ts similarity index 100% rename from src/bugzilla/types.ts rename to lib/bugzilla/types.ts diff --git a/src/cli/CLI.ts b/lib/cli/CLI.ts similarity index 97% rename from src/cli/CLI.ts rename to lib/cli/CLI.ts index 793d33b..0fd85de 100644 --- a/src/cli/CLI.ts +++ b/lib/cli/CLI.ts @@ -1,7 +1,7 @@ // CLI dispatcher — recursively walks CLICommand tree and wires up yargs import yargs, { type Argv } from "yargs" import { hideBin } from "yargs/helpers" -import { CLICommand } from "./CLICommand.js" +import { CLICommand } from "./CLICommand" interface CLIOpts { prog: string diff --git a/src/cli/CLICommand.ts b/lib/cli/CLICommand.ts similarity index 100% rename from src/cli/CLICommand.ts rename to lib/cli/CLICommand.ts diff --git a/src/commands/CancelCommand.ts b/lib/commands/CancelCommand.ts similarity index 86% rename from src/commands/CancelCommand.ts rename to lib/commands/CancelCommand.ts index 0199bb5..0caa31a 100644 --- a/src/commands/CancelCommand.ts +++ b/lib/commands/CancelCommand.ts @@ -1,8 +1,8 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile, writeTodoFile } from "../file.js" -import { getCurrentBranch, commitFileWithBody } from "../git.js" -import { validateStatusTransition } from "../issue.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile, writeTodoFile } from "../file" +import { getCurrentBranch, commitFileWithBody } from "../git" +import { validateStatusTransition } from "../issue" export class CancelCommand extends CLICommand { readonly name = "cancel " diff --git a/src/commands/CreateCommand.ts b/lib/commands/CreateCommand.ts similarity index 90% rename from src/commands/CreateCommand.ts rename to lib/commands/CreateCommand.ts index 8972235..358f245 100644 --- a/src/commands/CreateCommand.ts +++ b/lib/commands/CreateCommand.ts @@ -1,8 +1,8 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile, writeTodoFile } from "../file.js" -import { getCurrentBranch, commitFile } from "../git.js" -import type { IssueType, IssuePriority } from "../issue.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile, writeTodoFile } from "../file" +import { getCurrentBranch, commitFile } from "../git" +import type { IssueType, IssuePriority } from "../issue" export class CreateCommand extends CLICommand { readonly name = "create" diff --git a/src/commands/DoneCommand.ts b/lib/commands/DoneCommand.ts similarity index 86% rename from src/commands/DoneCommand.ts rename to lib/commands/DoneCommand.ts index 13d3ec7..2237907 100644 --- a/src/commands/DoneCommand.ts +++ b/lib/commands/DoneCommand.ts @@ -1,8 +1,8 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile, writeTodoFile } from "../file.js" -import { getCurrentBranch, commitFileWithBody } from "../git.js" -import { validateStatusTransition } from "../issue.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile, writeTodoFile } from "../file" +import { getCurrentBranch, commitFileWithBody } from "../git" +import { validateStatusTransition } from "../issue" export class DoneCommand extends CLICommand { readonly name = "done " diff --git a/src/commands/HoldCommand.ts b/lib/commands/HoldCommand.ts similarity index 85% rename from src/commands/HoldCommand.ts rename to lib/commands/HoldCommand.ts index 65f9223..37b2399 100644 --- a/src/commands/HoldCommand.ts +++ b/lib/commands/HoldCommand.ts @@ -1,8 +1,8 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile, writeTodoFile } from "../file.js" -import { getCurrentBranch, commitFileWithBody } from "../git.js" -import { validateStatusTransition } from "../issue.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile, writeTodoFile } from "../file" +import { getCurrentBranch, commitFileWithBody } from "../git" +import { validateStatusTransition } from "../issue" export class HoldCommand extends CLICommand { readonly name = "hold " diff --git a/src/commands/InitCommand.ts b/lib/commands/InitCommand.ts similarity index 95% rename from src/commands/InitCommand.ts rename to lib/commands/InitCommand.ts index df5a17c..53155a6 100644 --- a/src/commands/InitCommand.ts +++ b/lib/commands/InitCommand.ts @@ -1,8 +1,8 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile } from "../file.js" -import { BugzillaClient } from "../bugzilla/client.js" -import { loadConfig } from "../bugzilla/config.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile } from "../file" +import { BugzillaClient } from "../bugzilla/client" +import { loadConfig } from "../bugzilla/config" interface ProductState { name: string diff --git a/src/commands/IssueListCommand.ts b/lib/commands/IssueListCommand.ts similarity index 83% rename from src/commands/IssueListCommand.ts rename to lib/commands/IssueListCommand.ts index fb2c21d..866a3fa 100644 --- a/src/commands/IssueListCommand.ts +++ b/lib/commands/IssueListCommand.ts @@ -1,6 +1,6 @@ import type { ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile } from "../file.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile } from "../file" export class IssueListCommand extends CLICommand { readonly name = "list" diff --git a/src/commands/IssueShowCommand.ts b/lib/commands/IssueShowCommand.ts similarity index 93% rename from src/commands/IssueShowCommand.ts rename to lib/commands/IssueShowCommand.ts index c56e511..e7d0c1c 100644 --- a/src/commands/IssueShowCommand.ts +++ b/lib/commands/IssueShowCommand.ts @@ -1,6 +1,6 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile } from "../file.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile } from "../file" export class IssueShowCommand extends CLICommand { readonly name = "show " diff --git a/src/commands/IssuesInSprintCommand.ts b/lib/commands/IssuesInSprintCommand.ts similarity index 91% rename from src/commands/IssuesInSprintCommand.ts rename to lib/commands/IssuesInSprintCommand.ts index 06635c5..6f3fcef 100644 --- a/src/commands/IssuesInSprintCommand.ts +++ b/lib/commands/IssuesInSprintCommand.ts @@ -1,6 +1,6 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile } from "../file.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile } from "../file" export class IssuesInSprintCommand extends CLICommand { readonly name = "issues-in-sprint " diff --git a/src/commands/PushCommand.ts b/lib/commands/PushCommand.ts similarity index 93% rename from src/commands/PushCommand.ts rename to lib/commands/PushCommand.ts index a5a6e2b..52e24ae 100644 --- a/src/commands/PushCommand.ts +++ b/lib/commands/PushCommand.ts @@ -1,20 +1,20 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile } from "../file.js" -import { BugzillaClient } from "../bugzilla/client.js" -import { loadConfig } from "../bugzilla/config.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile } from "../file" +import { BugzillaClient } from "../bugzilla/client" +import { loadConfig } from "../bugzilla/config" import { issueToBugzillaCreate, statusToBugzilla, resolveProductComponent, -} from "../bugzilla/fieldmap.js" +} from "../bugzilla/fieldmap" import { buildOriginUrl, buildCommitUrl, shortHash, getGitRemoteUrl, getGitBranch, -} from "../bugzilla/origin.js" +} from "../bugzilla/origin" import { getCurrentBranch, parseIssueBranch, @@ -22,7 +22,7 @@ import { getCommitsFromRef, parseTodoTransition, type GitCommit, -} from "../git.js" +} from "../git" type Strategy = "smart" | "full" @@ -85,8 +85,8 @@ export class PushCommand extends CLICommand { // Push work comments from an issue branch private async pushFromIssueBranch( client: BugzillaClient, - config: import("../bugzilla/config.js").BugzillaConfig, - todo: import("../file.js").TodoFile, + config: import("../bugzilla/config").BugzillaConfig, + todo: import("../file").TodoFile, remoteUrl: string, gitBranch: string, issueBranch: { type: string; id: number }, @@ -94,7 +94,7 @@ export class PushCommand extends CLICommand { strategy: Strategy, dryRun: boolean ): Promise { - const issue = todo.issues.find((i: import("../issue.js").Issue) => i.id === issueBranch.id) + const issue = todo.issues.find(i => i.id === issueBranch.id) if (!issue) { console.error(`Issue #${issueBranch.id} not found in TODO`) return 1 @@ -160,8 +160,8 @@ export class PushCommand extends CLICommand { // Push transitions from develop private async pushFromDevelop( client: BugzillaClient, - config: import("../bugzilla/config.js").BugzillaConfig, - todo: import("../file.js").TodoFile, + config: import("../bugzilla/config").BugzillaConfig, + todo: import("../file").TodoFile, remoteUrl: string, gitBranch: string, ref: string | undefined, @@ -235,11 +235,11 @@ export class PushCommand extends CLICommand { // Find a bug for a specific issue, or create it private async findOrCreateBug( client: BugzillaClient, - config: import("../bugzilla/config.js").BugzillaConfig, - todo: import("../file.js").TodoFile, + config: import("../bugzilla/config").BugzillaConfig, + todo: import("../file").TodoFile, remoteUrl: string, gitBranch: string, - issue: import("../issue.js").Issue, + issue: import("../issue").Issue, strategy: Strategy, dryRun: boolean ): Promise { @@ -287,8 +287,8 @@ export class PushCommand extends CLICommand { // Resolve bugs for all issues in the TODO private async resolveAllBugs( client: BugzillaClient, - config: import("../bugzilla/config.js").BugzillaConfig, - todo: import("../file.js").TodoFile, + config: import("../bugzilla/config").BugzillaConfig, + todo: import("../file").TodoFile, remoteUrl: string, gitBranch: string, strategy: Strategy, diff --git a/src/commands/SprintsCommand.ts b/lib/commands/SprintsCommand.ts similarity index 83% rename from src/commands/SprintsCommand.ts rename to lib/commands/SprintsCommand.ts index 2fee82f..bc50da2 100644 --- a/src/commands/SprintsCommand.ts +++ b/lib/commands/SprintsCommand.ts @@ -1,6 +1,6 @@ import type { ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile } from "../file.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile } from "../file" export class SprintsCommand extends CLICommand { readonly name = "sprints" diff --git a/src/commands/StartCommand.ts b/lib/commands/StartCommand.ts similarity index 90% rename from src/commands/StartCommand.ts rename to lib/commands/StartCommand.ts index a5bed0a..52ae526 100644 --- a/src/commands/StartCommand.ts +++ b/lib/commands/StartCommand.ts @@ -1,8 +1,8 @@ import type { Argv, ArgumentsCamelCase } from "yargs" -import { CLICommand } from "../cli/CLICommand.js" -import { parseTodoFile, writeTodoFile } from "../file.js" -import { getCurrentBranch, branchExists, commitFileWithBody } from "../git.js" -import { validateStatusTransition } from "../issue.js" +import { CLICommand } from "../cli/CLICommand" +import { parseTodoFile, writeTodoFile } from "../file" +import { getCurrentBranch, branchExists, commitFileWithBody } from "../git" +import { validateStatusTransition } from "../issue" export class StartCommand extends CLICommand { readonly name = "start " diff --git a/src/file.ts b/lib/file.ts similarity index 95% rename from src/file.ts rename to lib/file.ts index 28903b3..a777bfc 100644 --- a/src/file.ts +++ b/lib/file.ts @@ -3,15 +3,16 @@ import * as fs from "fs" import { simpleParser } from "mailparser" -import { parseIssue, Issue } from "./issue.js" -import { parseSprints, Sprint } from "./sprint.js" -import { parseModules, parseBugzillaTracker, Module, BugzillaTracker } from "./tracker.js" -import { serializeTodoFile } from "./serializer.js" +import { parseIssue, Issue } from "./issue" +import { parseSprints, Sprint } from "./sprint" +import { parseModules, parseBugzillaTracker, Module, BugzillaTracker } from "./tracker" +import { serializeTodoFile } from "./serializer" -import schema from "./file.schema.json" with { type: "json" } +import * as schema from "./file.schema.json" import Ajv from "ajv" -const ajv = new Ajv.default({ allErrors: true }) + +const ajv = new Ajv({ allErrors: true }) const validateFile = ajv.compile(schema) diff --git a/src/git.ts b/lib/git.ts similarity index 97% rename from src/git.ts rename to lib/git.ts index aac8220..cc20f8b 100644 --- a/src/git.ts +++ b/lib/git.ts @@ -18,7 +18,7 @@ export function commitFile(path: string, message: string, cwd = process.cwd()): export function commitFileWithBody(path: string, header: string, body: string, cwd = process.cwd()): void { execSync(`git add ${path}`, { cwd }) const msg = `${header}\n\n${body}` - execSync(`git commit -F -`, { cwd, input: msg }) + execSync(`git commit -m ${JSON.stringify(msg)}`, { cwd }) } export function branchExists(branch: string, cwd = process.cwd()): boolean { diff --git a/src/issue.ts b/lib/issue.ts similarity index 97% rename from src/issue.ts rename to lib/issue.ts index a68cfa9..725ff25 100644 --- a/src/issue.ts +++ b/lib/issue.ts @@ -75,10 +75,10 @@ export function parseIssue(text: string): Issue { for (const line of lines) { if (!inDescription) { if (line.startsWith("ID:")) issue.id = Number(line.slice(3).trim()) - else if (line.startsWith("Type:")) issue.type = line.slice(5).trim() as IssueType + else if (line.startsWith("Type:")) issue.type = line.slice(5).trim() else if (line.startsWith("Title:")) issue.title = line.slice(6).trim() - else if (line.startsWith("Status:")) issue.status = line.slice(7).trim() as IssueStatus - else if (line.startsWith("Priority:")) issue.priority = line.slice(9).trim() as IssuePriority + else if (line.startsWith("Status:")) issue.status = line.slice(7).trim() + else if (line.startsWith("Priority:")) issue.priority = line.slice(9).trim() else if (line.startsWith("Created:")) issue.created = line.slice(8).trim() else if (line.startsWith("DueStart:")) issue.dueStart = line.slice(9).trim() else if (line.startsWith("DueEnd:")) issue.dueEnd = line.slice(7).trim() diff --git a/src/out.txt b/lib/out.txt similarity index 100% rename from src/out.txt rename to lib/out.txt diff --git a/src/serializer.ts b/lib/serializer.ts similarity index 96% rename from src/serializer.ts rename to lib/serializer.ts index 31a9442..cdc1cba 100644 --- a/src/serializer.ts +++ b/lib/serializer.ts @@ -1,8 +1,8 @@ // Inverse of the parser — serializes TodoFile back to MIME TODO format -import type { TodoFile } from "./file.js" -import type { Issue } from "./issue.js" -import type { Sprint } from "./sprint.js" -import type { Module, BugzillaTracker } from "./tracker.js" +import type { TodoFile } from "./file" +import type { Issue } from "./issue" +import type { Sprint } from "./sprint" +import type { Module, BugzillaTracker } from "./tracker" // Word-wrap text to fit within maxCol, respecting prefix widths // Returns array of lines (without prefix/indent — caller adds those) diff --git a/src/spec-notes.md b/lib/spec-notes.md similarity index 100% rename from src/spec-notes.md rename to lib/spec-notes.md diff --git a/src/sprint.ts b/lib/sprint.ts similarity index 100% rename from src/sprint.ts rename to lib/sprint.ts diff --git a/src/tracker.ts b/lib/tracker.ts similarity index 100% rename from src/tracker.ts rename to lib/tracker.ts diff --git a/package-lock.json b/package-lock.json index b62e673..5882ad2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,35 +1,22 @@ { - "name": "@byteb4rb1e/mime-todo", - "version": "0.2.0", + "name": "mime-todo", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@byteb4rb1e/mime-todo", - "version": "0.2.0", - "license": "CC-BY-ND-4.0", + "name": "mime-todo", + "version": "1.0.0", + "license": "ISC", "dependencies": { + "@types/yargs": "^17.0.35", "ajv": "^8.17.1", "mailparser": "^3.9.3", + "tsx": "^3.7.0", "yargs": "^18.0.0" }, - "bin": { - "bugzilla": "bin/bugzilla.js", - "todo": "bin/main.js" - }, "devDependencies": { - "@eslint/js": "^9.24.0", - "@types/mailparser": "^3.4.5", - "@types/node": "^22.14.1", - "@types/yargs": "^17.0.35", - "eslint": "^9.25.1", - "tsx": "^3.7.0", - "typescript": "^5.8.3", - "typescript-eslint": "^8.31.0", "vitest": "^4.0.18" - }, - "engines": { - "node": ">=18.0.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -55,7 +42,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -71,7 +57,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -87,7 +72,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -103,7 +87,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -119,7 +102,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -135,7 +117,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -151,7 +132,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -167,7 +147,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -183,7 +162,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -199,7 +177,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -215,7 +192,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -231,7 +207,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -247,7 +222,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -263,7 +237,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -279,7 +252,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -295,7 +267,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -327,7 +298,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -359,7 +329,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -391,7 +360,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -407,7 +375,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -423,7 +390,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -439,7 +405,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -448,210 +413,6 @@ "node": ">=12" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1023,48 +784,10 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "node_modules/@types/mailparser": { - "version": "3.4.6", - "resolved": "https://registry.npmjs.org/@types/mailparser/-/mailparser-3.4.6.tgz", - "integrity": "sha512-wVV3cnIKzxTffaPH8iRnddX1zahbYB1ZEoAxyhoBo3TBCBuK6nZ8M8JYO/RhsCuuBVOw/DEN/t/ENbruwlxn6Q==", - "dev": true, - "dependencies": { - "@types/node": "*", - "iconv-lite": "^0.6.3" - } - }, - "node_modules/@types/mailparser/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@types/node": { - "version": "22.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", - "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", - "dev": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "dev": true, "dependencies": { "@types/yargs-parser": "*" } @@ -1072,275 +795,7 @@ "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", - "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/type-utils": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.57.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", - "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", - "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", - "dev": true, - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.0", - "@typescript-eslint/types": "^8.57.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", - "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", - "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", - "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", - "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", - "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/project-service": "8.57.0", - "@typescript-eslint/tsconfig-utils": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", - "dev": true, - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", - "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", - "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.57.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/@vitest/expect": { "version": "4.0.18", @@ -1430,27 +885,6 @@ "libqp": "2.1.1" } }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -1488,12 +922,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1503,36 +931,10 @@ "node": ">=12" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "node_modules/chai": { "version": "6.2.2", @@ -1543,37 +945,6 @@ "node": ">=18" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/cliui": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", @@ -1587,67 +958,6 @@ "node": ">=20" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -1741,7 +1051,6 @@ "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -1782,177 +1091,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -1962,15 +1100,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -1985,18 +1114,6 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -2029,58 +1146,10 @@ } } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", - "dev": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -2113,7 +1182,6 @@ "version": "4.13.3", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.3.tgz", "integrity": "sha512-vp8Cj/+9Q/ibZUrq1rhy8mCTQpCk31A3uu9wc1C50yAb3x2pFHOsGdAZQ7jD86ARayyxZUViYeIztW+GE8dcrg==", - "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -2121,39 +1189,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2210,105 +1245,11 @@ "url": "https://opencollective.com/express" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/leac": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", @@ -2317,19 +1258,6 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/libbase64": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz", @@ -2370,27 +1298,6 @@ "uc.micro": "^2.0.0" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2417,24 +1324,6 @@ "tlds": "1.261.0" } }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -2453,12 +1342,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, "node_modules/nodemailer": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", @@ -2477,65 +1360,6 @@ "https://opencollective.com/debug" ] }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/parseley": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", @@ -2548,24 +1372,6 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -2626,24 +1432,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", @@ -2660,20 +1448,10 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -2738,39 +1516,6 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -2781,7 +1526,6 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2799,7 +1543,6 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -2847,30 +1590,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -2919,23 +1638,10 @@ "tlds": "bin.js" } }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/tsx": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-3.14.0.tgz", "integrity": "sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==", - "dev": true, "dependencies": { "esbuild": "~0.18.20", "get-tsconfig": "^4.7.2", @@ -2948,74 +1654,11 @@ "fsevents": "~2.3.3" } }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.0.tgz", - "integrity": "sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==", - "dev": true, - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.57.0", - "@typescript-eslint/parser": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/vitest": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", @@ -3607,21 +2250,6 @@ } } }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -3638,15 +2266,6 @@ "node": ">=8" } }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -3694,18 +2313,6 @@ "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/package.json b/package.json index ac19d11..4e8ba54 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@byteb4rb1e/mime-todo", - "version": "0.3.1", + "version": "0.2.0", "description": "CLI for the MIME TODO issue tracker specification with Bugzilla integration", "author": "Tiara Rodney ", "license": "CC-BY-ND-4.0", @@ -22,39 +22,28 @@ "gitflow" ], "bin": { - "mime-todo": "./bin/main.js", - "bugzilla": "./bin/bugzilla.js" + "todo": "./bin/main.ts", + "bugzilla": "./bin/bugzilla.ts" }, - "main": "lib/file.js", + "main": "lib/file.ts", "files": [ - "bin/*.js", + "bin/", "lib/", - "README.md", - "LICENSE" + "README.md" ], "scripts": { - "build:bin": "tsc -p tsconfig.bin.json", - "build:lib": "tsc -p tsconfig.lib.json && cp src/file.schema.json lib/", - "build": "npm run build:lib && npm run build:bin", - "mime-todo": "tsx bin/main.ts", + "todo": "tsx bin/main.ts", "bugzilla": "tsx bin/bugzilla.ts", - "test": "vitest run", - "test-reports/unit": "vitest run" + "test": "vitest run" }, "dependencies": { "ajv": "^8.17.1", "mailparser": "^3.9.3", + "tsx": "^3.7.0", "yargs": "^18.0.0" }, "devDependencies": { - "@eslint/js": "^9.24.0", - "@types/mailparser": "^3.4.5", - "@types/node": "^22.14.1", "@types/yargs": "^17.0.35", - "eslint": "^9.25.1", - "tsx": "^3.7.0", - "typescript": "^5.8.3", - "typescript-eslint": "^8.31.0", "vitest": "^4.0.18" }, "engines": { diff --git a/src/bugzilla/client.d.ts b/src/bugzilla/client.d.ts deleted file mode 100644 index 2fddcc6..0000000 --- a/src/bugzilla/client.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { BugzillaConfig } from "./config.js"; -import type { BugzillaBug, BugzillaComment, BugzillaProduct, BugzillaComponent, BugzillaSearchParams, BugzillaCreatePayload, BugzillaUpdatePayload, BugzillaCreateProductPayload, BugzillaCreateComponentPayload, BugzillaUpdateResponse } from "./types.js"; -export declare class BugzillaClient { - private baseUrl; - private apiKey; - constructor(config: BugzillaConfig); - private request; - getBug(id: number): Promise; - searchBugs(params: BugzillaSearchParams): Promise; - createBug(payload: BugzillaCreatePayload): Promise; - updateBug(id: number, payload: BugzillaUpdatePayload): Promise; - getComments(bugId: number): Promise; - addComment(bugId: number, comment: string): Promise; - tagComment(commentId: number, tags: string[]): Promise; - getProduct(nameOrId: string | number): Promise; - getAccessibleProducts(): Promise; - createProduct(payload: BugzillaCreateProductPayload): Promise; - getComponents(product: string): Promise; - createComponent(payload: BugzillaCreateComponentPayload): Promise; -} diff --git a/src/bugzilla/client.js b/src/bugzilla/client.js deleted file mode 100644 index 228ac70..0000000 --- a/src/bugzilla/client.js +++ /dev/null @@ -1,115 +0,0 @@ -export class BugzillaClient { - baseUrl; - apiKey; - constructor(config) { - this.baseUrl = config.baseUrl.replace(/\/+$/, ""); - this.apiKey = config.apiKey; - } - async request(method, path, body) { - // Auth: api_key in query for GET, in body for POST/PUT - let url; - if (method === "GET") { - const sep = path.includes("?") ? "&" : "?"; - url = `${this.baseUrl}${path}${sep}api_key=${encodeURIComponent(this.apiKey)}`; - } - else { - url = `${this.baseUrl}${path}`; - const bodyObj = (body ?? {}); - body = { ...bodyObj, api_key: this.apiKey }; - } - const res = await fetch(url, { - method, - headers: { "Content-Type": "application/json" }, - body: body ? JSON.stringify(body) : undefined, - }); - if (!res.ok) { - const text = await res.text(); - throw new Error(`Bugzilla API ${method} ${path} failed (${res.status}): ${text}`); - } - return res.json(); - } - async getBug(id) { - const data = await this.request("GET", `/bug/${id}`); - if (!data.bugs?.length) - throw new Error(`Bug ${id} not found`); - return data.bugs[0]; - } - async searchBugs(params) { - const query = new URLSearchParams(); - if (params.product) - query.set("product", params.product); - if (params.component) - query.set("component", params.component); - if (params.summary) - query.set("summary", params.summary); - if (params.limit) - query.set("limit", String(params.limit)); - if (params.offset) - query.set("offset", String(params.offset)); - if (params.last_change_time) - query.set("last_change_time", params.last_change_time); - if (params.status) { - const statuses = Array.isArray(params.status) ? params.status : [params.status]; - for (const s of statuses) - query.append("status", s); - } - if (params.id) { - const ids = Array.isArray(params.id) ? params.id : [params.id]; - for (const id of ids) - query.append("id", String(id)); - } - if (params.alias) { - const aliases = Array.isArray(params.alias) ? params.alias : [params.alias]; - for (const a of aliases) - query.append("alias", a); - } - if (params.url) - query.set("url", params.url); - const qs = query.toString(); - const path = `/bug${qs ? `?${qs}` : ""}`; - const data = await this.request("GET", path); - return data.bugs ?? []; - } - async createBug(payload) { - const data = await this.request("POST", "/bug", payload); - return data.id; - } - async updateBug(id, payload) { - return this.request("PUT", `/bug/${id}`, payload); - } - async getComments(bugId) { - const data = await this.request("GET", `/bug/${bugId}/comment`); - return data.bugs?.[String(bugId)]?.comments ?? []; - } - async addComment(bugId, comment) { - const data = await this.request("POST", `/bug/${bugId}/comment`, { comment }); - return data.id; - } - async tagComment(commentId, tags) { - await this.request("PUT", `/bug/comment/${commentId}/tags`, { add: tags }); - } - // Product operations - async getProduct(nameOrId) { - const data = await this.request("GET", `/product/${encodeURIComponent(String(nameOrId))}`); - if (!data.products?.length) - throw new Error(`Product "${nameOrId}" not found`); - return data.products[0]; - } - async getAccessibleProducts() { - const data = await this.request("GET", "/product?type=accessible"); - return data.products ?? []; - } - async createProduct(payload) { - const data = await this.request("POST", "/product", payload); - return data.id; - } - // Component operations - async getComponents(product) { - const prod = await this.getProduct(product); - return prod.components ?? []; - } - async createComponent(payload) { - const data = await this.request("POST", "/component", payload); - return data.id; - } -} diff --git a/src/bugzilla/config.d.ts b/src/bugzilla/config.d.ts deleted file mode 100644 index debc4e9..0000000 --- a/src/bugzilla/config.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface BugzillaConfig { - baseUrl: string; - apiKey: string; - product: string; - component: string; -} -export declare function loadConfig(cwd?: string): BugzillaConfig; diff --git a/src/bugzilla/config.js b/src/bugzilla/config.js deleted file mode 100644 index d85c237..0000000 --- a/src/bugzilla/config.js +++ /dev/null @@ -1,28 +0,0 @@ -// Bugzilla configuration — loads from .bugzilla.json or environment variables -import * as fs from "fs"; -import * as path from "path"; -const CONFIG_FILE = ".bugzilla.json"; -export function loadConfig(cwd = process.cwd()) { - const filePath = path.join(cwd, CONFIG_FILE); - if (fs.existsSync(filePath)) { - const raw = fs.readFileSync(filePath, "utf-8"); - const json = JSON.parse(raw); - return { - baseUrl: json.baseUrl ?? json.base_url ?? "", - apiKey: json.apiKey ?? json.api_key, - product: json.product ?? "", - component: json.component ?? "", - }; - } - const baseUrl = process.env.BUGZILLA_URL; - const apiKey = process.env.BUGZILLA_API_KEY; - if (!baseUrl || !apiKey) { - throw new Error(`Bugzilla config not found. Create ${CONFIG_FILE} or set BUGZILLA_URL and BUGZILLA_API_KEY`); - } - return { - baseUrl: baseUrl.replace(/\/+$/, ""), - apiKey, - product: process.env.BUGZILLA_PRODUCT ?? "", - component: process.env.BUGZILLA_COMPONENT ?? "", - }; -} diff --git a/src/bugzilla/fieldmap.d.ts b/src/bugzilla/fieldmap.d.ts deleted file mode 100644 index 563eff6..0000000 --- a/src/bugzilla/fieldmap.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Issue, IssueType, IssueStatus, IssuePriority } from "../issue.js"; -import type { BugzillaCreatePayload, BugzillaUpdatePayload } from "./types.js"; -export declare function statusToBugzilla(status: IssueStatus): { - status: string; - resolution?: string; -}; -export declare function priorityToBugzilla(priority: IssuePriority): string; -export declare function typeToBugzilla(type: IssueType): string; -export declare function resolveProductComponent(issue: Issue, bugzilla?: import("../tracker.js").BugzillaTracker): { - product: string; - component: string; -} | null; -export declare function issueToBugzillaCreate(issue: Issue, product: string, component: string, url?: string): BugzillaCreatePayload; -export declare function issueToBugzillaUpdate(issue: Issue): BugzillaUpdatePayload; diff --git a/src/bugzilla/fieldmap.js b/src/bugzilla/fieldmap.js deleted file mode 100644 index f910aa8..0000000 --- a/src/bugzilla/fieldmap.js +++ /dev/null @@ -1,74 +0,0 @@ -// Status mapping: TODO → Bugzilla -const STATUS_TO_BZ = { - "open": { status: "CONFIRMED" }, - "in-progress": { status: "IN_PROGRESS" }, - "done": { status: "RESOLVED", resolution: "FIXED" }, - "hold": { status: "RESOLVED", resolution: "LATER" }, - "cancelled": { status: "RESOLVED", resolution: "WONTFIX" }, -}; -// Priority mapping -const PRIORITY_TO_BZ = { - "low": "Low", - "medium": "Normal", - "high": "Highest", -}; -// Type → Severity mapping -const TYPE_TO_SEVERITY = { - "feature": "enhancement", - "bugfix": "normal", - "hotfix": "critical", -}; -export function statusToBugzilla(status) { - return STATUS_TO_BZ[status] ?? { status: "NEW" }; -} -export function priorityToBugzilla(priority) { - return PRIORITY_TO_BZ[priority] ?? "Normal"; -} -export function typeToBugzilla(type) { - return TYPE_TO_SEVERITY[type] ?? "normal"; -} -export function resolveProductComponent(issue, bugzilla) { - if (!bugzilla) - return null; - if (issue.module) { - const mapping = bugzilla.mappings.find((m) => m.module === issue.module); - if (mapping) - return { product: mapping.product, component: mapping.component }; - } - // Fall back to first mapping - return bugzilla.mappings.length > 0 - ? { product: bugzilla.mappings[0].product, component: bugzilla.mappings[0].component } - : null; -} -export function issueToBugzillaCreate(issue, product, component, url) { - const bz = statusToBugzilla(issue.status); - return { - product, - component, - version: "unspecified", - summary: issue.title, - url, - description: issue.description || undefined, - priority: priorityToBugzilla(issue.priority), - severity: typeToBugzilla(issue.type), - op_sys: "All", - rep_platform: "All", - depends_on: issue.relationships.dependsOn ?? [], - blocks: issue.relationships.blocks ?? [], - see_also: (issue.relationships.relatesTo ?? []).map(String), - }; -} -export function issueToBugzillaUpdate(issue) { - const bz = statusToBugzilla(issue.status); - const payload = { - summary: issue.title, - status: bz.status, - priority: priorityToBugzilla(issue.priority), - severity: typeToBugzilla(issue.type), - depends_on: { set: issue.relationships.dependsOn ?? [] }, - blocks: { set: issue.relationships.blocks ?? [] }, - }; - if (bz.resolution) - payload.resolution = bz.resolution; - return payload; -} diff --git a/src/bugzilla/index.ts b/src/bugzilla/index.ts deleted file mode 100644 index cfdf6d0..0000000 --- a/src/bugzilla/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Re-export all Bugzilla modules for convenient imports -export { BugzillaClient } from "./client.js" -export { loadConfig } from "./config.js" -export type { BugzillaConfig } from "./config.js" -export * from "./types.js" -export * from "./fieldmap.js" -export * from "./origin.js" diff --git a/src/bugzilla/origin.d.ts b/src/bugzilla/origin.d.ts deleted file mode 100644 index f7a7fc3..0000000 --- a/src/bugzilla/origin.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export declare function getGitRemoteUrl(cwd?: string): string; -export declare function getGitBranch(cwd?: string): string; -export declare function normalizeRemoteUrl(url: string): string; -export declare function buildOriginUrl(remoteUrl: string, branch: string, todoPath: string, issueId: number): string; -export declare function buildCommitUrl(remoteUrl: string, commitHash: string): string; -export declare function shortHash(hash: string): string; diff --git a/src/bugzilla/origin.js b/src/bugzilla/origin.js deleted file mode 100644 index d8268bb..0000000 --- a/src/bugzilla/origin.js +++ /dev/null @@ -1,70 +0,0 @@ -// Origin URL — builds a resolvable URL pointing to a TODO issue in the repo's web UI -// Format varies by host: -// Bitbucket: /src//# -// GitHub: /blob//# -// GitLab: /-/blob//# -import { execSync } from "child_process"; -export function getGitRemoteUrl(cwd = process.cwd()) { - try { - return execSync("git remote get-url origin", { cwd, encoding: "utf-8" }).trim(); - } - catch { - throw new Error("Could not determine git remote URL. Is this a git repository with an 'origin' remote?"); - } -} -export function getGitBranch(cwd = process.cwd()) { - try { - return execSync("git rev-parse --abbrev-ref HEAD", { cwd, encoding: "utf-8" }).trim(); - } - catch { - return "main"; - } -} -export function normalizeRemoteUrl(url) { - let normalized = url; - const sshMatch = normalized.match(/^git@([^:]+):(.+?)(?:\.git)?$/); - if (sshMatch) { - normalized = `https://${sshMatch[1]}/${sshMatch[2]}`; - } - normalized = normalized.replace(/\.git$/, ""); - normalized = normalized.replace(/\/+$/, ""); - return normalized; -} -function detectHost(url) { - if (url.includes("bitbucket.org")) - return "bitbucket"; - if (url.includes("github.com")) - return "github"; - if (url.includes("gitlab.com") || url.includes("gitlab")) - return "gitlab"; - return "unknown"; -} -export function buildOriginUrl(remoteUrl, branch, todoPath, issueId) { - const base = normalizeRemoteUrl(remoteUrl); - const host = detectHost(base); - switch (host) { - case "github": - return `${base}/blob/${branch}/${todoPath}#${issueId}`; - case "gitlab": - return `${base}/-/blob/${branch}/${todoPath}#${issueId}`; - case "bitbucket": - default: - return `${base}/src/${branch}/${todoPath}#${issueId}`; - } -} -export function buildCommitUrl(remoteUrl, commitHash) { - const base = normalizeRemoteUrl(remoteUrl); - const host = detectHost(base); - switch (host) { - case "github": - return `${base}/commit/${commitHash}`; - case "gitlab": - return `${base}/-/commit/${commitHash}`; - case "bitbucket": - default: - return `${base}/commits/${commitHash}`; - } -} -export function shortHash(hash) { - return hash.slice(0, 7); -} diff --git a/src/bugzilla/types.d.ts b/src/bugzilla/types.d.ts deleted file mode 100644 index 0acb682..0000000 --- a/src/bugzilla/types.d.ts +++ /dev/null @@ -1,125 +0,0 @@ -export interface BugzillaBug { - id: number; - alias: string[]; - summary: string; - status: string; - resolution: string; - priority: string; - severity: string; - product: string; - component: string; - description?: string; - creation_time: string; - last_change_time: string; - depends_on: number[]; - blocks: number[]; - url: string; - see_also: string[]; - assigned_to: string; - creator: string; - is_open: boolean; -} -export interface BugzillaComment { - id: number; - bug_id: number; - text: string; - creator: string; - time: string; - count: number; - is_private: boolean; - tags: string[]; -} -export interface BugzillaSearchParams { - product?: string; - component?: string; - status?: string | string[]; - id?: number | number[]; - alias?: string | string[]; - url?: string; - summary?: string; - limit?: number; - offset?: number; - last_change_time?: string; -} -export interface BugzillaCreatePayload { - product: string; - component: string; - summary: string; - version?: string; - url?: string; - description?: string; - status?: string; - priority?: string; - severity?: string; - op_sys?: string; - rep_platform?: string; - depends_on?: number[]; - blocks?: number[]; - see_also?: string[]; -} -export interface BugzillaUpdatePayload { - summary?: string; - status?: string; - resolution?: string; - priority?: string; - severity?: string; - depends_on?: { - set: number[]; - }; - blocks?: { - set: number[]; - }; - see_also?: { - add?: string[]; - remove?: string[]; - }; -} -export interface BugzillaSearchResponse { - bugs: BugzillaBug[]; -} -export interface BugzillaCreateResponse { - id: number; -} -export interface BugzillaUpdateResponse { - bugs: Array<{ - id: number; - changes: Record; - }>; -} -export interface BugzillaCommentsResponse { - bugs: Record; -} -export interface BugzillaComponent { - id: number; - name: string; - description: string; - default_assigned_to: string; - is_active: boolean; -} -export interface BugzillaProduct { - id: number; - name: string; - description: string; - is_active: boolean; - components: BugzillaComponent[]; -} -export interface BugzillaProductResponse { - products: BugzillaProduct[]; -} -export interface BugzillaCreateProductPayload { - name: string; - description: string; - version: string; - is_open?: boolean; -} -export interface BugzillaCreateComponentPayload { - product: string; - name: string; - description: string; - default_assignee: string; -} diff --git a/src/bugzilla/types.js b/src/bugzilla/types.js deleted file mode 100644 index f204881..0000000 --- a/src/bugzilla/types.js +++ /dev/null @@ -1,2 +0,0 @@ -// Bugzilla 5.0+ REST API type definitions -export {}; diff --git a/src/cli/CLI.d.ts b/src/cli/CLI.d.ts deleted file mode 100644 index 4e597fb..0000000 --- a/src/cli/CLI.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { CLICommand } from "./CLICommand.js"; -interface CLIOpts { - prog: string; - description: string; -} -export declare class CLI { - private prog; - private description; - constructor(opts: CLIOpts); - private registerCommand; - bootstrap(commands: (new () => CLICommand)[]): void; -} -export {}; diff --git a/src/cli/CLI.js b/src/cli/CLI.js deleted file mode 100644 index da43a3a..0000000 --- a/src/cli/CLI.js +++ /dev/null @@ -1,49 +0,0 @@ -// CLI dispatcher — recursively walks CLICommand tree and wires up yargs -import yargs from "yargs"; -import { hideBin } from "yargs/helpers"; -export class CLI { - prog; - description; - constructor(opts) { - this.prog = opts.prog; - this.description = opts.description; - } - registerCommand(y, CommandClass) { - const cmd = new CommandClass(); - const subcommands = CommandClass._subcommands; - if (subcommands?.length) { - // Branch command with subcommands - y.command(cmd.name, cmd.help, (sub) => { - for (const Sub of subcommands) { - this.registerCommand(sub, Sub); - } - return sub.demandCommand(1, `Please specify a ${cmd.name} subcommand`); - }, () => { }); - } - else { - // Leaf command - y.command(cmd.name, cmd.help, (sub) => cmd.addArguments(sub), async (args) => { - const code = await cmd.execute(args); - process.exit(code); - }); - } - } - bootstrap(commands) { - const y = yargs(hideBin(process.argv)) - .scriptName(this.prog) - .usage(`${this.description}\n\n$0 [options]`) - .option("verbose", { - alias: "v", - type: "boolean", - description: "Enable verbose output", - default: false, - }) - .demandCommand(1, "Please specify a command") - .strict() - .help(); - for (const Cmd of commands) { - this.registerCommand(y, Cmd); - } - y.parse(); - } -} diff --git a/src/cli/CLICommand.d.ts b/src/cli/CLICommand.d.ts deleted file mode 100644 index b81c7f3..0000000 --- a/src/cli/CLICommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -export declare abstract class CLICommand { - abstract readonly name: string; - abstract readonly help: string; - abstract readonly description: string; - static _subcommands: (new () => CLICommand)[]; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/cli/CLICommand.js b/src/cli/CLICommand.js deleted file mode 100644 index d60ad3e..0000000 --- a/src/cli/CLICommand.js +++ /dev/null @@ -1,9 +0,0 @@ -export class CLICommand { - static _subcommands = []; - addArguments(yargs) { - return yargs; - } - async execute(args) { - return 0; - } -} diff --git a/src/commands/CancelCommand.d.ts b/src/commands/CancelCommand.d.ts deleted file mode 100644 index 6ad14b5..0000000 --- a/src/commands/CancelCommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class CancelCommand extends CLICommand { - readonly name = "cancel "; - readonly help = "Cancel an issue"; - readonly description = "Set issue to cancelled (must be on issue branch or develop)"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/CancelCommand.js b/src/commands/CancelCommand.js deleted file mode 100644 index e587506..0000000 --- a/src/commands/CancelCommand.js +++ /dev/null @@ -1,43 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile, writeTodoFile } from "../file.js"; -import { getCurrentBranch, commitFileWithBody } from "../git.js"; -import { validateStatusTransition } from "../issue.js"; -export class CancelCommand extends CLICommand { - name = "cancel "; - help = "Cancel an issue"; - description = "Set issue to cancelled (must be on issue branch or develop)"; - addArguments(yargs) { - return yargs - .positional("id", { type: "number", demandOption: true }) - .option("reason", { - type: "string", - demandOption: true, - description: "Reason for cancellation", - }); - } - async execute(args) { - const todo = await parseTodoFile(); - const issue = todo.issues.find(i => i.id === args.id); - if (!issue) { - console.error(`Issue #${args.id} not found`); - return 1; - } - // Cancel allowed from develop (if open) or issue branch (if in-progress/hold) - const issueBranch = `${issue.type}/${issue.id}`; - const branch = getCurrentBranch(); - if (branch !== "develop" && branch !== issueBranch) { - console.error(`Must be on develop or ${issueBranch} to cancel (currently on ${branch})`); - return 1; - } - const err = validateStatusTransition(issue.status, "cancelled"); - if (err) { - console.error(err); - return 1; - } - issue.status = "cancelled"; - writeTodoFile(todo); - commitFileWithBody("TODO", `todo(${issue.id}): cancelled`, args.reason); - console.log(`Issue #${issue.id} is now cancelled`); - return 0; - } -} diff --git a/src/commands/CreateCommand.d.ts b/src/commands/CreateCommand.d.ts deleted file mode 100644 index 7dee217..0000000 --- a/src/commands/CreateCommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class CreateCommand extends CLICommand { - readonly name = "create"; - readonly help = "Create a new issue"; - readonly description = "Add an issue to the TODO file (must be on develop)"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/CreateCommand.js b/src/commands/CreateCommand.js deleted file mode 100644 index bb350dc..0000000 --- a/src/commands/CreateCommand.js +++ /dev/null @@ -1,72 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile, writeTodoFile } from "../file.js"; -import { getCurrentBranch, commitFile } from "../git.js"; -export class CreateCommand extends CLICommand { - name = "create"; - help = "Create a new issue"; - description = "Add an issue to the TODO file (must be on develop)"; - addArguments(yargs) { - return yargs - .option("type", { - alias: "t", - type: "string", - choices: ["feature", "bugfix", "hotfix"], - demandOption: true, - }) - .option("title", { - type: "string", - demandOption: true, - }) - .option("priority", { - alias: "p", - type: "string", - choices: ["low", "medium", "high"], - default: "medium", - }) - .option("plan", { - type: "string", - demandOption: true, - description: "Description of what needs to be done", - }) - .option("module", { - alias: "m", - type: "string", - }); - } - async execute(args) { - const branch = getCurrentBranch(); - if (branch !== "develop") { - console.error(`Must be on develop to create issues (currently on ${branch})`); - return 1; - } - const todo = await parseTodoFile(); - const mod = args.module; - if (mod && todo.modules) { - const valid = todo.modules.map(m => m.name); - if (!valid.includes(mod)) { - console.error(`Module "${mod}" not defined. Valid: ${valid.join(", ")}`); - return 1; - } - } - const nextId = todo.issues.length > 0 - ? Math.max(...todo.issues.map(i => i.id)) + 1 - : 1; - const today = new Date().toISOString().slice(0, 10); - todo.issues.push({ - id: nextId, - type: args.type, - title: args.title, - status: "open", - priority: args.priority, - created: today, - module: mod, - relationships: {}, - description: args.plan, - body: "", - }); - writeTodoFile(todo); - commitFile("TODO", `todo(${nextId}): open`); - console.log(`Created issue #${nextId}: ${args.title}`); - return 0; - } -} diff --git a/src/commands/DoneCommand.d.ts b/src/commands/DoneCommand.d.ts deleted file mode 100644 index 921e79d..0000000 --- a/src/commands/DoneCommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class DoneCommand extends CLICommand { - readonly name = "done "; - readonly help = "Mark an issue as done"; - readonly description = "Set issue to done (must be on issue branch)"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/DoneCommand.js b/src/commands/DoneCommand.js deleted file mode 100644 index be3f342..0000000 --- a/src/commands/DoneCommand.js +++ /dev/null @@ -1,43 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile, writeTodoFile } from "../file.js"; -import { getCurrentBranch, commitFileWithBody } from "../git.js"; -import { validateStatusTransition } from "../issue.js"; -export class DoneCommand extends CLICommand { - name = "done "; - help = "Mark an issue as done"; - description = "Set issue to done (must be on issue branch)"; - addArguments(yargs) { - return yargs - .positional("id", { type: "number", demandOption: true }) - .option("summary", { - type: "string", - demandOption: true, - description: "High-level summary of what was delivered", - }); - } - async execute(args) { - const todo = await parseTodoFile(); - const issue = todo.issues.find(i => i.id === args.id); - if (!issue) { - console.error(`Issue #${args.id} not found`); - return 1; - } - // Must be on the correct issue branch - const expectedBranch = `${issue.type}/${issue.id}`; - const branch = getCurrentBranch(); - if (branch !== expectedBranch) { - console.error(`Must be on ${expectedBranch} to mark issue as done (currently on ${branch})`); - return 1; - } - const err = validateStatusTransition(issue.status, "done"); - if (err) { - console.error(err); - return 1; - } - issue.status = "done"; - writeTodoFile(todo); - commitFileWithBody("TODO", `todo(${issue.id}): done`, args.summary); - console.log(`Issue #${issue.id} is now done`); - return 0; - } -} diff --git a/src/commands/HoldCommand.d.ts b/src/commands/HoldCommand.d.ts deleted file mode 100644 index cf2f732..0000000 --- a/src/commands/HoldCommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class HoldCommand extends CLICommand { - readonly name = "hold "; - readonly help = "Put an issue on hold"; - readonly description = "Set issue to hold (must be on issue branch)"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/HoldCommand.js b/src/commands/HoldCommand.js deleted file mode 100644 index 62f9d73..0000000 --- a/src/commands/HoldCommand.js +++ /dev/null @@ -1,42 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile, writeTodoFile } from "../file.js"; -import { getCurrentBranch, commitFileWithBody } from "../git.js"; -import { validateStatusTransition } from "../issue.js"; -export class HoldCommand extends CLICommand { - name = "hold "; - help = "Put an issue on hold"; - description = "Set issue to hold (must be on issue branch)"; - addArguments(yargs) { - return yargs - .positional("id", { type: "number", demandOption: true }) - .option("reason", { - type: "string", - demandOption: true, - description: "Reason for holding", - }); - } - async execute(args) { - const todo = await parseTodoFile(); - const issue = todo.issues.find(i => i.id === args.id); - if (!issue) { - console.error(`Issue #${args.id} not found`); - return 1; - } - const expectedBranch = `${issue.type}/${issue.id}`; - const branch = getCurrentBranch(); - if (branch !== expectedBranch) { - console.error(`Must be on ${expectedBranch} to hold issue (currently on ${branch})`); - return 1; - } - const err = validateStatusTransition(issue.status, "hold"); - if (err) { - console.error(err); - return 1; - } - issue.status = "hold"; - writeTodoFile(todo); - commitFileWithBody("TODO", `todo(${issue.id}): hold`, args.reason); - console.log(`Issue #${issue.id} is now on hold`); - return 0; - } -} diff --git a/src/commands/InitCommand.d.ts b/src/commands/InitCommand.d.ts deleted file mode 100644 index d460e20..0000000 --- a/src/commands/InitCommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class InitCommand extends CLICommand { - readonly name = "init"; - readonly help = "Initialize Bugzilla products and components"; - readonly description = "Check and optionally create Bugzilla products/components from TODO tracker config"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/InitCommand.js b/src/commands/InitCommand.js deleted file mode 100644 index a9c474a..0000000 --- a/src/commands/InitCommand.js +++ /dev/null @@ -1,131 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile } from "../file.js"; -import { BugzillaClient } from "../bugzilla/client.js"; -import { loadConfig } from "../bugzilla/config.js"; -export class InitCommand extends CLICommand { - name = "init"; - help = "Initialize Bugzilla products and components"; - description = "Check and optionally create Bugzilla products/components from TODO tracker config"; - addArguments(yargs) { - return yargs - .option("dry-run", { - type: "boolean", - default: false, - description: "Show what would be created without creating", - }) - .option("confirm", { - type: "boolean", - default: false, - description: "Actually create missing products/components (required for writes)", - }) - .option("assignee", { - type: "string", - description: "Default assignee email for new components", - }); - } - async execute(args) { - const todo = await parseTodoFile(); - if (!todo.bugzilla) { - console.error("No application/bugzilla part found in TODO file"); - return 1; - } - const config = loadConfig(); - const client = new BugzillaClient(config); - const dryRun = args.dryRun; - const confirm = args.confirm; - const assignee = args.assignee; - // Collect unique products and their components from the mappings - const productMap = new Map(); - for (const m of todo.bugzilla.mappings) { - if (!productMap.has(m.product)) - productMap.set(m.product, new Set()); - productMap.get(m.product).add(m.component); - } - // Check what exists on the server - const states = []; - const existingProducts = await client.getAccessibleProducts(); - const existingByName = new Map(existingProducts.map(p => [p.name, p])); - for (const [productName, componentNames] of productMap) { - const existing = existingByName.get(productName); - const existingComponents = new Set(existing?.components?.map(c => c.name) ?? []); - states.push({ - name: productName, - exists: !!existing, - components: [...componentNames].map(name => ({ - name, - exists: existingComponents.has(name), - })), - }); - } - // Report - let allOk = true; - for (const product of states) { - const pStatus = product.exists ? "ok" : "MISSING"; - console.log(`Product: ${product.name} [${pStatus}]`); - for (const comp of product.components) { - const cStatus = comp.exists ? "ok" : "MISSING"; - console.log(` Component: ${comp.name} [${cStatus}]`); - if (!comp.exists) - allOk = false; - } - if (!product.exists) - allOk = false; - } - if (allOk) { - console.log("\nAll products and components exist. Nothing to do."); - return 0; - } - if (dryRun) { - console.log("\n[dry-run] Would create the MISSING items above."); - return 0; - } - if (!confirm) { - console.log("\nMissing items found. Run with --confirm to create them."); - return 1; - } - // Need assignee for component creation - const needsComponents = states.some(p => p.components.some(c => !c.exists)); - if (needsComponents && !assignee) { - console.error("\n--assignee is required when creating components"); - return 1; - } - // Create missing products and components - let created = 0; - for (const product of states) { - if (!product.exists) { - try { - const id = await client.createProduct({ - name: product.name, - description: product.name, - version: "unspecified", - }); - console.log(`Created product #${id}: ${product.name}`); - created++; - } - catch (err) { - console.error(`Error creating product "${product.name}": ${err.message}`); - continue; - } - } - for (const comp of product.components) { - if (!comp.exists) { - try { - const id = await client.createComponent({ - product: product.name, - name: comp.name, - description: comp.name, - default_assignee: assignee, - }); - console.log(`Created component #${id}: ${comp.name} (in ${product.name})`); - created++; - } - catch (err) { - console.error(`Error creating component "${comp.name}": ${err.message}`); - } - } - } - } - console.log(`\nInit complete: ${created} items created`); - return 0; - } -} diff --git a/src/commands/IssueListCommand.d.ts b/src/commands/IssueListCommand.d.ts deleted file mode 100644 index b742c8a..0000000 --- a/src/commands/IssueListCommand.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class IssueListCommand extends CLICommand { - readonly name = "list"; - readonly help = "List all issues"; - readonly description = "List all issues in the TODO file"; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/IssueListCommand.js b/src/commands/IssueListCommand.js deleted file mode 100644 index cfbcb58..0000000 --- a/src/commands/IssueListCommand.js +++ /dev/null @@ -1,14 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile } from "../file.js"; -export class IssueListCommand extends CLICommand { - name = "list"; - help = "List all issues"; - description = "List all issues in the TODO file"; - async execute(args) { - const todo = await parseTodoFile(); - for (const issue of todo.issues) { - console.log(`#${issue.id} [${issue.type}] (${issue.status}) ${issue.title}`); - } - return 0; - } -} diff --git a/src/commands/IssueShowCommand.d.ts b/src/commands/IssueShowCommand.d.ts deleted file mode 100644 index 6ae29b3..0000000 --- a/src/commands/IssueShowCommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class IssueShowCommand extends CLICommand { - readonly name = "show "; - readonly help = "Show details for a single issue"; - readonly description = "Print all fields for one issue"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/IssueShowCommand.js b/src/commands/IssueShowCommand.js deleted file mode 100644 index b8fcdb5..0000000 --- a/src/commands/IssueShowCommand.js +++ /dev/null @@ -1,40 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile } from "../file.js"; -export class IssueShowCommand extends CLICommand { - name = "show "; - help = "Show details for a single issue"; - description = "Print all fields for one issue"; - addArguments(yargs) { - return yargs.positional("id", { type: "number", demandOption: true }); - } - async execute(args) { - const todo = await parseTodoFile(); - const issue = todo.issues.find(i => i.id === args.id); - if (!issue) { - console.error(`Issue #${args.id} not found`); - return 1; - } - console.log(`ID: ${issue.id}`); - console.log(`Type: ${issue.type}`); - console.log(`Title: ${issue.title}`); - console.log(`Status: ${issue.status}`); - console.log(`Priority: ${issue.priority}`); - console.log(`Created: ${issue.created}`); - if (issue.module) - console.log(`Module: ${issue.module}`); - if (issue.dueStart) - console.log(`DueStart: ${issue.dueStart}`); - if (issue.dueEnd) - console.log(`DueEnd: ${issue.dueEnd}`); - const rels = Object.entries(issue.relationships) - .map(([k, v]) => `${k}:${v.join(" ")}`) - .join(", "); - console.log(`Relationships: ${rels}`); - console.log(`Description: ${issue.description}`); - if (issue.body) { - console.log(); - console.log(issue.body); - } - return 0; - } -} diff --git a/src/commands/IssuesInSprintCommand.d.ts b/src/commands/IssuesInSprintCommand.d.ts deleted file mode 100644 index 40026a9..0000000 --- a/src/commands/IssuesInSprintCommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class IssuesInSprintCommand extends CLICommand { - readonly name = "issues-in-sprint "; - readonly help = "List issues in a sprint"; - readonly description = "Find issues whose date range overlaps with a sprint"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/IssuesInSprintCommand.js b/src/commands/IssuesInSprintCommand.js deleted file mode 100644 index 33869c4..0000000 --- a/src/commands/IssuesInSprintCommand.js +++ /dev/null @@ -1,29 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile } from "../file.js"; -export class IssuesInSprintCommand extends CLICommand { - name = "issues-in-sprint "; - help = "List issues in a sprint"; - description = "Find issues whose date range overlaps with a sprint"; - addArguments(yargs) { - return yargs.positional("name", { type: "string", demandOption: true }); - } - async execute(args) { - const todo = await parseTodoFile(); - const sprint = todo.sprints.find(s => s.name === args.name); - if (!sprint) { - console.error(`Sprint not found: ${args.name}`); - return 1; - } - const { start, end } = sprint; - for (const issue of todo.issues) { - const ds = issue.dueStart ?? issue.dueEnd; - const de = issue.dueEnd ?? issue.dueStart; - if (!ds || !de) - continue; - if (ds <= end && de >= start) { - console.log(`#${issue.id} [${issue.type}] (${issue.status}) ${issue.title}`); - } - } - return 0; - } -} diff --git a/src/commands/PushCommand.d.ts b/src/commands/PushCommand.d.ts deleted file mode 100644 index 66205ed..0000000 --- a/src/commands/PushCommand.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class PushCommand extends CLICommand { - readonly name = "push [ref]"; - readonly help = "Push commits to Bugzilla"; - readonly description = "Push git commits as Bugzilla comments, transitions as status updates"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; - private pushFromIssueBranch; - private pushFromDevelop; - private findOrCreateBug; - private resolveAllBugs; - private fetchCommentMeta; - private postComment; - private tagAsPushed; - private formatComment; -} diff --git a/src/commands/PushCommand.js b/src/commands/PushCommand.js deleted file mode 100644 index 2c601aa..0000000 --- a/src/commands/PushCommand.js +++ /dev/null @@ -1,288 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile } from "../file.js"; -import { BugzillaClient } from "../bugzilla/client.js"; -import { loadConfig } from "../bugzilla/config.js"; -import { issueToBugzillaCreate, statusToBugzilla, resolveProductComponent, } from "../bugzilla/fieldmap.js"; -import { buildOriginUrl, buildCommitUrl, shortHash, getGitRemoteUrl, } from "../bugzilla/origin.js"; -import { getCurrentBranch, parseIssueBranch, getCommitsSinceDiverge, getCommitsFromRef, parseTodoTransition, } from "../git.js"; -export class PushCommand extends CLICommand { - name = "push [ref]"; - help = "Push commits to Bugzilla"; - description = "Push git commits as Bugzilla comments, transitions as status updates"; - addArguments(yargs) { - return yargs - .positional("ref", { - type: "string", - description: "Git ref specifier (e.g. HEAD~3) — issue branches only", - }) - .option("dry-run", { - type: "boolean", - default: false, - description: "Show what would be pushed without pushing", - }) - .option("strategy", { - type: "string", - choices: ["smart", "full"], - default: "smart", - description: "Fetch strategy: smart (only relevant bugs) or full (all bugs)", - }); - } - async execute(args) { - const branch = getCurrentBranch(); - const issueBranch = parseIssueBranch(branch); - const isDevelop = branch === "develop"; - if (!isDevelop && !issueBranch) { - console.error(`Must be on develop or an issue branch (currently on ${branch})`); - return 1; - } - const todo = await parseTodoFile(); - const config = loadConfig(); - const client = new BugzillaClient(config); - const remoteUrl = getGitRemoteUrl(); - // Origin URL always uses develop — that's where the TODO lives canonically - const gitBranch = "develop"; - const strategy = args.strategy; - const dryRun = args.dryRun; - if (issueBranch) { - return this.pushFromIssueBranch(client, config, todo, remoteUrl, gitBranch, issueBranch, args.ref, strategy, dryRun); - } - else { - return this.pushFromDevelop(client, config, todo, remoteUrl, gitBranch, args.ref, strategy, dryRun); - } - } - // Push work comments from an issue branch - async pushFromIssueBranch(client, config, todo, remoteUrl, gitBranch, issueBranch, ref, strategy, dryRun) { - const issue = todo.issues.find((i) => i.id === issueBranch.id); - if (!issue) { - console.error(`Issue #${issueBranch.id} not found in TODO`); - return 1; - } - // Issue must be in-progress to push work comments - if (issue.status !== "in-progress") { - console.error(`Issue #${issue.id} is "${issue.status}" — work comments can only be pushed when in-progress`); - return 1; - } - // Resolve the bug - const bugId = await this.findOrCreateBug(client, config, todo, remoteUrl, gitBranch, issue, strategy, dryRun); - if (!bugId) - return 1; - // Check Bugzilla comments for a done transition — no work after done - const existingComments = await this.fetchCommentMeta(client, bugId); - if (existingComments.hasDone) { - console.error(`Bug #${bugId} already has a [done] transition — no further work comments allowed`); - return 1; - } - // Get commits to push - const commits = ref - ? getCommitsFromRef(`${ref}..HEAD`) - : getCommitsSinceDiverge("develop"); - if (commits.length === 0) { - console.log("No commits to push"); - return 0; - } - let pushed = 0; - for (const commit of commits) { - if (existingComments.hashes.has(shortHash(commit.hash))) - continue; - if (dryRun) { - console.log(`[dry-run] bug #${bugId} ← ${shortHash(commit.hash)} ${commit.subject}`); - continue; - } - try { - const commitUrl = buildCommitUrl(remoteUrl, commit.hash); - const commentId = await this.postComment(client, bugId, commit, commitUrl); - await this.tagAsPushed(client, commentId, commit.hash); - pushed++; - console.log(`bug #${bugId} ← ${shortHash(commit.hash)} ${commit.subject}`); - } - catch (err) { - console.error(`Error pushing ${shortHash(commit.hash)}: ${err.message}`); - } - } - console.log(`Push complete: ${pushed} comments`); - return 0; - } - // Push transitions from develop - async pushFromDevelop(client, config, todo, remoteUrl, gitBranch, ref, strategy, dryRun) { - // Get transition commits - let allCommits; - if (ref) { - allCommits = getCommitsFromRef(`${ref}..HEAD`); - } - else { - allCommits = getCommitsFromRef("HEAD"); - } - const relevantCommits = allCommits.filter(c => parseTodoTransition(c.subject) !== null); - if (relevantCommits.length === 0) { - console.log("No transitions to push"); - return 0; - } - // Resolve all bugs - const bugMap = await this.resolveAllBugs(client, config, todo, remoteUrl, gitBranch, strategy, dryRun); - let pushed = 0; - let transitioned = 0; - for (const commit of relevantCommits) { - const transition = parseTodoTransition(commit.subject); - if (!transition) - continue; - const bugId = bugMap.get(transition.issueId); - if (!bugId) - continue; - // Idempotency check - const meta = await this.fetchCommentMeta(client, bugId); - if (meta.hashes.has(shortHash(commit.hash))) - continue; - if (dryRun) { - console.log(`[dry-run] bug #${bugId} ← ${shortHash(commit.hash)} ${commit.subject}`); - console.log(`[dry-run] → status: ${transition.status}`); - continue; - } - try { - const commitUrl = buildCommitUrl(remoteUrl, commit.hash); - const commentId = await this.postComment(client, bugId, commit, commitUrl); - // Update status, then tag — tag only after everything succeeds - const bz = statusToBugzilla(transition.status); - const payload = { status: bz.status }; - if (bz.resolution) - payload.resolution = bz.resolution; - await client.updateBug(bugId, payload); - await this.tagAsPushed(client, commentId, commit.hash); - pushed++; - transitioned++; - console.log(`bug #${bugId} ← ${shortHash(commit.hash)} [${transition.status}]`); - } - catch (err) { - console.error(`Error pushing ${shortHash(commit.hash)} to bug #${bugId}: ${err.message}`); - } - } - console.log(`Push complete: ${pushed} comments, ${transitioned} transitions`); - return 0; - } - // Find a bug for a specific issue, or create it - async findOrCreateBug(client, config, todo, remoteUrl, gitBranch, issue, strategy, dryRun) { - // Try to find existing bug by URL - const originUrl = buildOriginUrl(remoteUrl, gitBranch, "TODO", issue.id); - if (strategy === "smart") { - const bugs = await client.searchBugs({ url: originUrl }); - if (bugs.length > 0) - return bugs[0].id; - } - // Fallback: search all bugs in product/component - const searchTargets = todo.bugzilla - ? todo.bugzilla.mappings.map(m => ({ product: m.product, component: m.component })) - : [{ product: config.product, component: config.component }]; - for (const target of searchTargets) { - if (!target.product) - continue; - const bugs = await client.searchBugs(target); - for (const bug of bugs) { - if (bug.url === originUrl) - return bug.id; - } - } - // Not found — create it - if (dryRun) { - console.log(`[dry-run] Would create bug for issue #${issue.id}: ${issue.title}`); - return null; - } - try { - const resolved = resolveProductComponent(issue, todo.bugzilla); - const product = resolved?.product ?? config.product; - const component = resolved?.component ?? config.component; - const payload = issueToBugzillaCreate(issue, product, component, originUrl); - const bugId = await client.createBug(payload); - console.log(`Created bug #${bugId} for issue #${issue.id}`); - return bugId; - } - catch (err) { - console.error(`Error creating bug for issue #${issue.id}: ${err.message}`); - return null; - } - } - // Resolve bugs for all issues in the TODO - async resolveAllBugs(client, config, todo, remoteUrl, gitBranch, strategy, dryRun) { - const bugMap = new Map(); - const searchTargets = todo.bugzilla - ? todo.bugzilla.mappings.map(m => ({ product: m.product, component: m.component })) - : [{ product: config.product, component: config.component }]; - const seen = new Set(); - for (const target of searchTargets) { - if (!target.product) - continue; - const bugs = await client.searchBugs(target); - for (const bug of bugs) { - if (seen.has(bug.id)) - continue; - seen.add(bug.id); - if (bug.url) { - const match = bug.url.match(/#(\d+)$/); - if (match) - bugMap.set(Number(match[1]), bug.id); - } - } - } - // Create bugs for issues that don't have one yet - for (const issue of todo.issues) { - if (bugMap.has(issue.id)) - continue; - if (dryRun) { - console.log(`[dry-run] Would create bug for issue #${issue.id}: ${issue.title}`); - continue; - } - try { - const resolved = resolveProductComponent(issue, todo.bugzilla); - const product = resolved?.product ?? config.product; - const component = resolved?.component ?? config.component; - const url = buildOriginUrl(remoteUrl, gitBranch, "TODO", issue.id); - const payload = issueToBugzillaCreate(issue, product, component, url); - const bugId = await client.createBug(payload); - bugMap.set(issue.id, bugId); - console.log(`Created bug #${bugId} for issue #${issue.id}`); - } - catch (err) { - console.error(`Error creating bug for issue #${issue.id}: ${err.message}`); - } - } - return bugMap; - } - // Fetch comment metadata: which commit hashes are already tagged + whether done exists - async fetchCommentMeta(client, bugId) { - const hashes = new Set(); - let hasDone = false; - try { - const comments = await client.getComments(bugId); - for (const c of comments) { - for (const tag of c.tags ?? []) { - if (tag.startsWith("git-")) { - hashes.add(tag.slice(4)); // short hash without "git-" prefix - } - } - if (/^\*\*todo\(\d+\): done\*\*$/m.test(c.text)) - hasDone = true; - } - } - catch { /* non-fatal */ } - return { hashes, hasDone }; - } - // Post a comment, returns the comment ID for later tagging - async postComment(client, bugId, commit, commitUrl) { - const comment = this.formatComment(commit, commitUrl); - return await client.addComment(bugId, comment); - } - // Tag a comment as pushed — call only after all side effects succeed - async tagAsPushed(client, commentId, commitHash) { - await client.tagComment(commentId, [`git-${shortHash(commitHash)}`]); - } - // Format a commit as a markdown Bugzilla comment - formatComment(commit, commitUrl) { - const lines = []; - lines.push(`**${commit.subject}**`); - if (commit.body) { - lines.push(""); - lines.push(commit.body); - } - lines.push(""); - lines.push(`[\`${shortHash(commit.hash)}\`](${commitUrl})`); - return lines.join("\n"); - } -} diff --git a/src/commands/SprintsCommand.d.ts b/src/commands/SprintsCommand.d.ts deleted file mode 100644 index bbb58bb..0000000 --- a/src/commands/SprintsCommand.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class SprintsCommand extends CLICommand { - readonly name = "sprints"; - readonly help = "List all sprints"; - readonly description = "List all sprints in the TODO file"; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/SprintsCommand.js b/src/commands/SprintsCommand.js deleted file mode 100644 index d9d7717..0000000 --- a/src/commands/SprintsCommand.js +++ /dev/null @@ -1,14 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile } from "../file.js"; -export class SprintsCommand extends CLICommand { - name = "sprints"; - help = "List all sprints"; - description = "List all sprints in the TODO file"; - async execute(args) { - const todo = await parseTodoFile(); - for (const sprint of todo.sprints) { - console.log(`${sprint.name}: ${sprint.start}..${sprint.end}`); - } - return 0; - } -} diff --git a/src/commands/StartCommand.d.ts b/src/commands/StartCommand.d.ts deleted file mode 100644 index 676f811..0000000 --- a/src/commands/StartCommand.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Argv, ArgumentsCamelCase } from "yargs"; -import { CLICommand } from "../cli/CLICommand.js"; -export declare class StartCommand extends CLICommand { - readonly name = "start "; - readonly help = "Start work on an issue"; - readonly description = "Set issue to in-progress (must be on develop)"; - addArguments(yargs: Argv): Argv; - execute(args: ArgumentsCamelCase): Promise; -} diff --git a/src/commands/StartCommand.js b/src/commands/StartCommand.js deleted file mode 100644 index 5f0f754..0000000 --- a/src/commands/StartCommand.js +++ /dev/null @@ -1,48 +0,0 @@ -import { CLICommand } from "../cli/CLICommand.js"; -import { parseTodoFile, writeTodoFile } from "../file.js"; -import { getCurrentBranch, branchExists, commitFileWithBody } from "../git.js"; -import { validateStatusTransition } from "../issue.js"; -export class StartCommand extends CLICommand { - name = "start "; - help = "Start work on an issue"; - description = "Set issue to in-progress (must be on develop)"; - addArguments(yargs) { - return yargs - .positional("id", { type: "number", demandOption: true }) - .option("plan", { - type: "string", - demandOption: true, - description: "High-level description of planned approach", - }); - } - async execute(args) { - const branch = getCurrentBranch(); - if (branch !== "develop") { - console.error(`Must be on develop to start an issue (currently on ${branch})`); - return 1; - } - const todo = await parseTodoFile(); - const issue = todo.issues.find(i => i.id === args.id); - if (!issue) { - console.error(`Issue #${args.id} not found`); - return 1; - } - const err = validateStatusTransition(issue.status, "in-progress"); - if (err) { - console.error(err); - return 1; - } - // Check that the issue branch doesn't already exist - const issueBranch = `${issue.type}/${issue.id}`; - if (branchExists(issueBranch)) { - console.error(`Branch ${issueBranch} already exists`); - return 1; - } - issue.status = "in-progress"; - writeTodoFile(todo); - commitFileWithBody("TODO", `todo(${issue.id}): in-progress`, args.plan); - console.log(`Issue #${issue.id} is now in-progress`); - console.log(`Create the issue branch: git checkout -b ${issueBranch}`); - return 0; - } -} diff --git a/src/file.d.ts b/src/file.d.ts deleted file mode 100644 index 7d4e867..0000000 --- a/src/file.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Issue } from "./issue.js"; -import { Sprint } from "./sprint.js"; -import { Module, BugzillaTracker } from "./tracker.js"; -export interface TodoFile { - sprints: Sprint[]; - issues: Issue[]; - modules?: Module[]; - bugzilla?: BugzillaTracker; -} -export declare function parseMime(mimeText: string): Promise; -export declare function preprocessTODO(raw: string): string; -export declare function parseTodoFile(path?: string): Promise; -export declare function writeTodoFile(todo: TodoFile, path?: string): void; diff --git a/src/file.js b/src/file.js deleted file mode 100644 index 3e7e4f9..0000000 --- a/src/file.js +++ /dev/null @@ -1,148 +0,0 @@ -// mime-todo/lib/file.ts -import * as fs from "fs"; -import { simpleParser } from "mailparser"; -import { parseIssue } from "./issue.js"; -import { parseSprints } from "./sprint.js"; -import { parseModules, parseBugzillaTracker } from "./tracker.js"; -import { serializeTodoFile } from "./serializer.js"; -import schema from "./file.schema.json" with { type: "json" }; -import Ajv from "ajv"; -const ajv = new Ajv.default({ allErrors: true }); -const validateFile = ajv.compile(schema); -export async function parseMime(mimeText) { - return await simpleParser(mimeText); -} -export function preprocessTODO(raw) { - const boundary = "ISSUE"; - const rawParts = raw - .split(`--${boundary}`) - .map(p => p.trim()) - .filter(Boolean); - const parts = []; - for (const rawPart of rawParts) { - const lines = rawPart.split(/\r?\n/); - const typeLine = lines[0]; - if (!typeLine?.toLowerCase().startsWith("content-type:")) { - throw new Error(`Part missing Content-Type header:\n${rawPart}`); - } - const type = typeLine.slice("Content-Type:".length).trim(); - const body = lines.slice(1).join("\n").trim(); - parts.push({ type, body }); - } - // Validate singleton parts - for (const ct of ["application/sprints", "application/modules", "application/bugzilla"]) { - const count = parts.filter(p => p.type === ct).length; - if (count > 1) { - throw new Error(`Multiple ${ct} parts found`); - } - } - const sprintsPart = parts.find(p => p.type === "application/sprints") ?? null; - const modulesPart = parts.find(p => p.type === "application/modules") ?? null; - const bugzillaPart = parts.find(p => p.type === "application/bugzilla") ?? null; - const singletonParts = new Set([sprintsPart, modulesPart, bugzillaPart]); - // Reorder: sprints, modules, bugzilla, then everything else - const orderedParts = []; - if (sprintsPart) - orderedParts.push(sprintsPart); - if (modulesPart) - orderedParts.push(modulesPart); - if (bugzillaPart) - orderedParts.push(bugzillaPart); - for (const part of parts) { - if (!singletonParts.has(part)) - orderedParts.push(part); - } - // MIME envelope - const out = []; - out.push(`MIME-Version: 1.0`); - out.push(`Content-Type: multipart/mixed; boundary="${boundary}"`); - out.push(""); - for (const part of orderedParts) { - out.push(`--${boundary}`); - out.push(`Content-Type: ${part.type}`); - out.push(""); - out.push(part.body); - out.push(""); - } - out.push(`--${boundary}--`); - out.push(""); - return out.join("\n"); -} -export async function parseTodoFile(path = "TODO") { - const raw = fs.readFileSync(path, "utf-8"); - const mimeWrapped = preprocessTODO(raw); - const parsed = await parseMime(mimeWrapped); - const sprints = []; - const issues = []; - let modules; - let bugzilla; - const parts = parsed.attachments ?? - []; - for (const part of parts) { - const contentType = String(part.contentType || "").toLowerCase(); - const body = part.content?.toString("utf-8") ?? ""; - if (contentType.startsWith("application/sprints")) { - sprints.push(...parseSprints(body)); - } - else if (contentType.startsWith("application/modules")) { - modules = parseModules(body); - } - else if (contentType.startsWith("application/bugzilla")) { - bugzilla = parseBugzillaTracker(body); - } - else if (contentType.startsWith("application/issue")) { - issues.push(parseIssue(body)); - } - } - const file = { sprints, issues, modules, bugzilla }; - if (!validateFile(file)) { - throw new Error("Schema validation failed: " + - JSON.stringify(validateFile.errors, null, 2)); - } - // Validate: issue modules must reference defined modules - if (modules) { - const validModules = new Set(modules.map(m => m.name)); - for (const issue of issues) { - if (issue.module && !validModules.has(issue.module)) { - throw new Error(`Issue #${issue.id} references module "${issue.module}" ` + - `which is not defined in application/modules. ` + - `Valid modules: ${[...validModules].join(", ")}`); - } - } - } - // Validate: bugzilla tracker mappings must reference defined modules - if (bugzilla && modules) { - const validModules = new Set(modules.map(m => m.name)); - for (const mapping of bugzilla.mappings) { - if (!validModules.has(mapping.module)) { - throw new Error(`Bugzilla mapping references module "${mapping.module}" ` + - `which is not defined in application/modules. ` + - `Valid modules: ${[...validModules].join(", ")}`); - } - } - } - // Validate: relationship targets must reference existing issue IDs - const issueIds = new Set(issues.map(i => i.id)); - const issueMap = new Map(issues.map(i => [i.id, i])); - for (const issue of issues) { - for (const [kind, targets] of Object.entries(issue.relationships)) { - for (const targetId of targets) { - if (!issueIds.has(targetId)) { - throw new Error(`Issue #${issue.id} has ${kind} relationship to #${targetId}, ` + - `but issue #${targetId} does not exist`); - } - // Warn on stale relationships (target is cancelled) - const target = issueMap.get(targetId); - if (target?.status === "cancelled") { - console.error(`Warning: Issue #${issue.id} has ${kind} relationship to #${targetId}, ` + - `but issue #${targetId} is cancelled`); - } - } - } - } - return file; -} -export function writeTodoFile(todo, path = "TODO") { - const content = serializeTodoFile(todo); - fs.writeFileSync(path, content); -} diff --git a/src/file.schema.json b/src/file.schema.json deleted file mode 100644 index b07c8f9..0000000 --- a/src/file.schema.json +++ /dev/null @@ -1,144 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "TODO File Schema", - "type": "object", - "properties": { - "sprints": { - "type": "array", - "items": { "$ref": "#/definitions/Sprint" } - }, - "issues": { - "type": "array", - "items": { "$ref": "#/definitions/Issue" } - }, - "modules": { - "type": "array", - "items": { "$ref": "#/definitions/Module" } - }, - "bugzilla": { - "$ref": "#/definitions/BugzillaTracker" - } - }, - "required": ["sprints", "issues"], - "additionalProperties": false, - - "definitions": { - "Sprint": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "start": { - "type": "string", - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" - }, - "end": { - "type": "string", - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" - } - }, - "required": ["name", "start", "end"], - "additionalProperties": false - }, - - "Issue": { - "type": "object", - "properties": { - "id": { - "type": "integer", - "minimum": 1 - }, - "type": { - "type": "string", - "enum": ["feature", "bugfix", "hotfix"] - }, - "title": { "type": "string" }, - "status": { - "type": "string", - "enum": ["open", "in-progress", "done", "hold", "cancelled"] - }, - "priority": { - "type": "string", - "enum": ["low", "medium", "high"] - }, - "created": { - "type": "string", - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" - }, - "module": { - "type": "string" - }, - "relationships": { - "$ref": "#/definitions/IssueRelationships" - }, - "dueStart": { - "type": "string", - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" - }, - "dueEnd": { - "type": "string", - "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" - }, - "description": { "type": "string" }, - "body": { "type": "string" } - }, - "required": [ - "id", - "type", - "title", - "status", - "priority", - "created", - "relationships", - "description", - "body" - ], - "additionalProperties": false - }, - - "IssueRelationships": { - "type": "object", - "patternProperties": { - "^(dependsOn|relatesTo|blocks)$": { - "type": "array", - "items": { "type": "integer", "minimum": 1 } - } - }, - "additionalProperties": false - }, - - "Module": { - "type": "object", - "properties": { - "name": { "type": "string" }, - "path": { "type": "string" } - }, - "required": ["name"], - "additionalProperties": false - }, - - "BugzillaTrackerMapping": { - "type": "object", - "properties": { - "module": { "type": "string" }, - "product": { "type": "string" }, - "component": { "type": "string" } - }, - "required": ["module", "product", "component"], - "additionalProperties": false - }, - - "BugzillaTracker": { - "type": "object", - "properties": { - "url": { "type": "string" }, - "mappings": { - "type": "array", - "items": { "$ref": "#/definitions/BugzillaTrackerMapping" }, - "minItems": 1 - } - }, - "required": ["url", "mappings"], - "additionalProperties": false - } - } -} diff --git a/src/git.d.ts b/src/git.d.ts deleted file mode 100644 index f71eed3..0000000 --- a/src/git.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -export declare function getCurrentBranch(cwd?: string): string; -export declare function isClean(cwd?: string): boolean; -export declare function commitFile(path: string, message: string, cwd?: string): void; -export declare function commitFileWithBody(path: string, header: string, body: string, cwd?: string): void; -export declare function branchExists(branch: string, cwd?: string): boolean; -export declare function parseIssueBranch(branch: string): { - type: string; - id: number; -} | null; -export interface GitCommit { - hash: string; - subject: string; - body: string; -} -export declare function getCommitsSinceDiverge(base?: string, cwd?: string): GitCommit[]; -export declare function getCommitsFromRef(refSpec: string, cwd?: string): GitCommit[]; -export declare function getAllCommits(branch: string, cwd?: string): GitCommit[]; -export declare function parseTodoTransition(subject: string): { - issueId: number; - status: string; -} | null; diff --git a/src/git.js b/src/git.js deleted file mode 100644 index 89d761f..0000000 --- a/src/git.js +++ /dev/null @@ -1,77 +0,0 @@ -// Git helpers for branch validation and commit operations -import { execSync } from "child_process"; -export function getCurrentBranch(cwd = process.cwd()) { - return execSync("git rev-parse --abbrev-ref HEAD", { cwd, encoding: "utf-8" }).trim(); -} -export function isClean(cwd = process.cwd()) { - const status = execSync("git status --porcelain", { cwd, encoding: "utf-8" }).trim(); - return status === ""; -} -export function commitFile(path, message, cwd = process.cwd()) { - execSync(`git add ${path}`, { cwd }); - execSync(`git commit -m ${JSON.stringify(message)}`, { cwd }); -} -export function commitFileWithBody(path, header, body, cwd = process.cwd()) { - execSync(`git add ${path}`, { cwd }); - const msg = `${header}\n\n${body}`; - execSync(`git commit -m ${JSON.stringify(msg)}`, { cwd }); -} -export function branchExists(branch, cwd = process.cwd()) { - try { - execSync(`git rev-parse --verify ${branch}`, { cwd, stdio: "pipe" }); - return true; - } - catch { - return false; - } -} -// Parse the issue branch name into type and ID -export function parseIssueBranch(branch) { - const match = branch.match(/^(feature|bugfix|hotfix)\/(\d+)$/); - if (!match) - return null; - return { type: match[1], id: Number(match[2]) }; -} -// Get commits on current branch since it diverged from a base branch -export function getCommitsSinceDiverge(base = "develop", cwd = process.cwd()) { - return parseGitLog(`${base}..HEAD`, cwd); -} -// Get commits from a ref spec (e.g. HEAD~3..HEAD) -export function getCommitsFromRef(refSpec, cwd = process.cwd()) { - return parseGitLog(refSpec, cwd); -} -// Get all commits on a branch -export function getAllCommits(branch, cwd = process.cwd()) { - return parseGitLog(branch, cwd); -} -function parseGitLog(range, cwd) { - const SEP = "---COMMIT-SEP---"; - const format = `%H%n%s%n%b%n${SEP}`; - let output; - try { - output = execSync(`git log ${range} --format=${JSON.stringify(format)}`, { cwd, encoding: "utf-8" }); - } - catch { - return []; - } - const commits = []; - const entries = output.split(SEP).filter(e => e.trim()); - for (const entry of entries) { - const lines = entry.trim().split("\n"); - if (lines.length < 2) - continue; - commits.push({ - hash: lines[0], - subject: lines[1], - body: lines.slice(2).join("\n").trim(), - }); - } - return commits; -} -// Check if a commit subject is a todo transition -export function parseTodoTransition(subject) { - const match = subject.match(/^todo\((\d+)\):\s*(\S+)/); - if (!match) - return null; - return { issueId: Number(match[1]), status: match[2] }; -} diff --git a/src/issue.d.ts b/src/issue.d.ts deleted file mode 100644 index e27608c..0000000 --- a/src/issue.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -export type RelationshipKind = "dependsOn" | "relatesTo" | "blocks"; -export interface IssueRelationships { - [kind: string]: number[]; -} -export type IssueType = "feature" | "bugfix" | "hotfix"; -export type IssueStatus = "open" | "in-progress" | "done" | "hold" | "cancelled"; -export type IssuePriority = "low" | "medium" | "high"; -export interface Issue { - id: number; - type: IssueType; - title: string; - status: IssueStatus; - priority: IssuePriority; - created: string; - module?: string; - relationships: IssueRelationships; - dueStart?: string; - dueEnd?: string; - description: string; - body: string; -} -export declare function validateStatusTransition(from: IssueStatus, to: IssueStatus): string | null; -export declare function parseRelationships(text: string): IssueRelationships; -export declare function parseIssue(text: string): Issue; diff --git a/src/issue.js b/src/issue.js deleted file mode 100644 index 4ebb534..0000000 --- a/src/issue.js +++ /dev/null @@ -1,89 +0,0 @@ -// mime-todo/lib/issue.ts -// Valid status transitions -const VALID_TRANSITIONS = { - "open": ["in-progress", "hold", "cancelled"], - "in-progress": ["done", "hold", "open", "cancelled"], - "hold": ["open", "in-progress", "cancelled"], - "done": ["open"], - "cancelled": ["open"], -}; -export function validateStatusTransition(from, to) { - if (from === to) - return null; // no-op is fine - const allowed = VALID_TRANSITIONS[from]; - if (!allowed?.includes(to)) { - return `Invalid status transition: ${from} → ${to}. Allowed from ${from}: ${allowed?.join(", ") ?? "none"}`; - } - return null; -} -export function parseRelationships(text) { - const relationships = {}; - // Handle empty relationships - if (!text || text.trim() === "") { - return relationships; - } - // Simple parsing for format like "dependsOn:3" - const parts = text.split(/,\s*/).filter(p => p.trim() !== ""); - for (const part of parts) { - const [key, value] = part.split(":").map(s => s.trim()); - if (key && value) { - relationships[key] = value.split(/[\s]+/).map(Number).filter(n => !isNaN(n)); - } - } - return relationships; -} -export function parseIssue(text) { - const lines = text.split(/\r?\n/); - const issue = {}; - let inDescription = false; - const descLines = []; - const bodyLines = []; - for (const line of lines) { - if (!inDescription) { - if (line.startsWith("ID:")) - issue.id = Number(line.slice(3).trim()); - else if (line.startsWith("Type:")) - issue.type = line.slice(5).trim(); - else if (line.startsWith("Title:")) - issue.title = line.slice(6).trim(); - else if (line.startsWith("Status:")) - issue.status = line.slice(7).trim(); - else if (line.startsWith("Priority:")) - issue.priority = line.slice(9).trim(); - else if (line.startsWith("Created:")) - issue.created = line.slice(8).trim(); - else if (line.startsWith("DueStart:")) - issue.dueStart = line.slice(9).trim(); - else if (line.startsWith("DueEnd:")) - issue.dueEnd = line.slice(7).trim(); - else if (line.startsWith("Module:")) - issue.module = line.slice("Module:".length).trim(); - else if (line.startsWith("Relationships:")) { - const rel = line.slice("Relationships:".length).trim(); - issue.relationships = parseRelationships(rel); - } - else if (line.startsWith("Description:")) { - inDescription = true; - descLines.push(line.slice("Description:".length).trim()); - } - } - else { - // Description continuation - if (line.trim() === "") { - // blank line ends description - inDescription = false; - continue; - } - if (/^\s+/.test(line)) { - descLines.push(line.trim()); - continue; - } - // first non-indented line ends description - inDescription = false; - bodyLines.push(line); - } - } - issue.description = descLines.join("\n"); - issue.body = bodyLines.join("\n"); - return issue; -} diff --git a/src/serializer.d.ts b/src/serializer.d.ts deleted file mode 100644 index 14650de..0000000 --- a/src/serializer.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { TodoFile } from "./file.js"; -import type { Issue } from "./issue.js"; -import type { Sprint } from "./sprint.js"; -import type { Module, BugzillaTracker } from "./tracker.js"; -export declare function wordWrap(text: string, maxCol: number, firstPrefixLen: number, contIndentLen: number): string[]; -export declare function serializeRelationships(rels: Issue["relationships"]): string; -export declare function serializeSprints(sprints: Sprint[]): string; -export declare function serializeIssue(issue: Issue): string; -export declare function serializeModules(modules: Module[]): string; -export declare function serializeBugzillaTracker(tracker: BugzillaTracker): string; -export declare function serializeTodoFile(todo: TodoFile): string; diff --git a/src/serializer.js b/src/serializer.js deleted file mode 100644 index 62d7251..0000000 --- a/src/serializer.js +++ /dev/null @@ -1,137 +0,0 @@ -// Word-wrap text to fit within maxCol, respecting prefix widths -// Returns array of lines (without prefix/indent — caller adds those) -export function wordWrap(text, maxCol, firstPrefixLen, contIndentLen) { - const result = []; - // Split on existing newlines first, then wrap each paragraph - const paragraphs = text.split("\n"); - for (const para of paragraphs) { - const words = para.split(/\s+/).filter(Boolean); - if (words.length === 0) { - result.push(""); - continue; - } - let line = ""; - const width = result.length === 0 ? maxCol - firstPrefixLen : maxCol - contIndentLen; - for (const word of words) { - const lineWidth = result.length === 0 && line === "" - ? maxCol - firstPrefixLen - : maxCol - contIndentLen; - if (line === "") { - line = word; - } - else if (line.length + 1 + word.length <= lineWidth) { - line += " " + word; - } - else { - result.push(line); - line = word; - } - } - if (line) - result.push(line); - } - return result.length > 0 ? result : [""]; -} -export function serializeRelationships(rels) { - const parts = []; - for (const [kind, ids] of Object.entries(rels)) { - if (ids?.length) { - parts.push(`${kind}:${ids.join(" ")}`); - } - } - return parts.join(", "); -} -export function serializeSprints(sprints) { - if (sprints.length === 0) - return "Sprints:"; - const lines = ["Sprints:"]; - for (const sprint of sprints) { - lines.push(` - Name: ${sprint.name}`); - lines.push(` Range: ${sprint.start}..${sprint.end}`); - } - return lines.join("\n"); -} -export function serializeIssue(issue) { - const lines = []; - lines.push(`ID: ${issue.id}`); - lines.push(`Type: ${issue.type}`); - lines.push(`Title: ${issue.title}`); - lines.push(`Status: ${issue.status}`); - lines.push(`Priority: ${issue.priority}`); - lines.push(`Created: ${issue.created}`); - if (issue.module) - lines.push(`Module: ${issue.module}`); - if (issue.dueStart) - lines.push(`DueStart: ${issue.dueStart}`); - if (issue.dueEnd) - lines.push(`DueEnd: ${issue.dueEnd}`); - lines.push(`Relationships: ${serializeRelationships(issue.relationships)}`); - // Description with word-wrap at 80 columns, continuation indented to column 14 - const INDENT = " "; // 13 spaces — aligns with text after "Description: " - const MAX_COL = 80; - const firstPrefix = "Description: "; - if (issue.description) { - const wrapped = wordWrap(issue.description, MAX_COL, firstPrefix.length, INDENT.length); - lines.push(`${firstPrefix}${wrapped[0]}`); - for (let i = 1; i < wrapped.length; i++) { - lines.push(`${INDENT}${wrapped[i]}`); - } - } - else { - lines.push("Description:"); - } - if (issue.body) { - lines.push(""); - lines.push(issue.body); - } - return lines.join("\n"); -} -export function serializeModules(modules) { - const lines = ["Modules:"]; - for (const m of modules) { - lines.push(` - Name: ${m.name}`); - if (m.path) - lines.push(` Path: ${m.path}`); - } - return lines.join("\n"); -} -export function serializeBugzillaTracker(tracker) { - const lines = []; - lines.push(`URL: ${tracker.url}`); - lines.push("Mappings:"); - for (const m of tracker.mappings) { - lines.push(` - Module: ${m.module}`); - lines.push(` Product: ${m.product}`); - lines.push(` Component: ${m.component}`); - } - return lines.join("\n"); -} -export function serializeTodoFile(todo) { - const parts = []; - // Sprints part - parts.push("--ISSUE"); - parts.push("Content-Type: application/sprints"); - parts.push(serializeSprints(todo.sprints)); - // Modules part - if (todo.modules?.length) { - parts.push(""); - parts.push("--ISSUE"); - parts.push("Content-Type: application/modules"); - parts.push(serializeModules(todo.modules)); - } - // Bugzilla tracker part - if (todo.bugzilla) { - parts.push(""); - parts.push("--ISSUE"); - parts.push("Content-Type: application/bugzilla"); - parts.push(serializeBugzillaTracker(todo.bugzilla)); - } - // Issue parts - for (const issue of todo.issues) { - parts.push(""); - parts.push("--ISSUE"); - parts.push("Content-Type: application/issue"); - parts.push(serializeIssue(issue)); - } - return parts.join("\n") + "\n"; -} diff --git a/src/sprint.d.ts b/src/sprint.d.ts deleted file mode 100644 index 094afb4..0000000 --- a/src/sprint.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Sprint { - name: string; - start: string; - end: string; -} -export declare function parseSprints(text: string): Sprint[]; diff --git a/src/sprint.js b/src/sprint.js deleted file mode 100644 index 6b6c7a7..0000000 --- a/src/sprint.js +++ /dev/null @@ -1,33 +0,0 @@ -export function parseSprints(text) { - const lines = text.split(/\r?\n/); - const sprints = []; - let current = null; - for (const line of lines) { - // Start of sprint entry (must not trim indentation) - if (/^\s*-\s*(Name:.*)?$/.test(line)) { - if (current) - sprints.push(current); - current = {}; - const match = line.match(/^\s*-\s*Name:\s*(.*)$/); - if (match) - current.name = match[1]; - continue; - } - // Key-value pairs (must be indented) - const kv = line.match(/^\s+([A-Za-z][A-Za-z0-9]*):\s*(.*)$/); - if (kv && current) { - const key = kv[1]; - const value = kv[2]; - if (key === "Name") - current.name = value; - if (key === "Range") { - const [start, end] = value.split(".."); - current.start = start; - current.end = end; - } - } - } - if (current) - sprints.push(current); - return sprints; -} diff --git a/src/tracker.d.ts b/src/tracker.d.ts deleted file mode 100644 index 20283d2..0000000 --- a/src/tracker.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Module { - name: string; - path?: string; -} -export interface BugzillaTrackerMapping { - module: string; - product: string; - component: string; -} -export interface BugzillaTracker { - url: string; - mappings: BugzillaTrackerMapping[]; -} -export declare function parseModules(text: string): Module[]; -export declare function parseBugzillaTracker(text: string): BugzillaTracker; diff --git a/src/tracker.js b/src/tracker.js deleted file mode 100644 index d6bbda2..0000000 --- a/src/tracker.js +++ /dev/null @@ -1,77 +0,0 @@ -// Module and tracker part parsers -// application/modules — defines the repo's logical module structure -// application/bugzilla — maps modules to Bugzilla products/components -export function parseModules(text) { - const lines = text.split(/\r?\n/); - const modules = []; - let current = null; - for (const line of lines) { - // Skip the "Modules:" header - if (/^\s*Modules:\s*$/.test(line)) - continue; - // Start of module entry - if (/^\s*-\s*(Name:.*)?$/.test(line)) { - if (current?.name) { - modules.push(current); - } - current = {}; - const match = line.match(/^\s*-\s*Name:\s*(.*)$/); - if (match) - current.name = match[1]; - continue; - } - const kv = line.match(/^\s+([A-Za-z][A-Za-z0-9]*):\s*(.*)$/); - if (kv && current) { - if (kv[1] === "Name") - current.name = kv[2]; - else if (kv[1] === "Path") - current.path = kv[2]; - } - } - if (current?.name) { - modules.push(current); - } - return modules; -} -export function parseBugzillaTracker(text) { - const lines = text.split(/\r?\n/); - const tracker = {}; - const mappings = []; - let inMappings = false; - let current = null; - for (const line of lines) { - if (!inMappings) { - if (line.startsWith("URL:")) { - tracker.url = line.slice("URL:".length).trim(); - } - else if (line.startsWith("Mappings:")) { - inMappings = true; - } - continue; - } - if (/^\s*-\s*(Module:.*)?$/.test(line)) { - if (current?.module && current?.product && current?.component) { - mappings.push(current); - } - current = {}; - const match = line.match(/^\s*-\s*Module:\s*(.*)$/); - if (match) - current.module = match[1]; - continue; - } - const kv = line.match(/^\s+([A-Za-z][A-Za-z0-9]*):\s*(.*)$/); - if (kv && current) { - if (kv[1] === "Module") - current.module = kv[2]; - else if (kv[1] === "Product") - current.product = kv[2]; - else if (kv[1] === "Component") - current.component = kv[2]; - } - } - if (current?.module && current?.product && current?.component) { - mappings.push(current); - } - tracker.mappings = mappings; - return tracker; -} diff --git a/tests/lib/bugzilla/fieldmap.test.ts b/tests/lib/bugzilla/fieldmap.test.ts index cda36c2..f612857 100644 --- a/tests/lib/bugzilla/fieldmap.test.ts +++ b/tests/lib/bugzilla/fieldmap.test.ts @@ -5,9 +5,9 @@ import { typeToBugzilla, issueToBugzillaCreate, resolveProductComponent, -} from "../../../src/bugzilla/fieldmap.js" -import type { Issue } from "../../../src/issue.js" -import type { BugzillaTracker } from "../../../src/tracker.js" +} from "../../../lib/bugzilla/fieldmap" +import type { Issue } from "../../../lib/issue" +import type { BugzillaTracker } from "../../../lib/tracker" describe("status mapping", () => { it("maps TODO statuses to Bugzilla", () => { diff --git a/tests/lib/bugzilla/origin.test.ts b/tests/lib/bugzilla/origin.test.ts index 165e9e5..73f4b08 100644 --- a/tests/lib/bugzilla/origin.test.ts +++ b/tests/lib/bugzilla/origin.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest" import { normalizeRemoteUrl, buildOriginUrl, -} from "../../../src/bugzilla/origin.js" +} from "../../../lib/bugzilla/origin" describe("normalizeRemoteUrl", () => { it("passes HTTPS URLs through", () => { diff --git a/tests/lib/cli.test.ts b/tests/lib/cli.test.ts index c995145..b8fc1dd 100644 --- a/tests/lib/cli.test.ts +++ b/tests/lib/cli.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest" -import { CLICommand } from "../../src/cli/CLICommand.js" +import { CLICommand } from "../../lib/cli/CLICommand" class TestLeafCommand extends CLICommand { readonly name = "leaf" diff --git a/tests/lib/file.test.ts b/tests/lib/file.test.ts index 05669ff..47270e8 100644 --- a/tests/lib/file.test.ts +++ b/tests/lib/file.test.ts @@ -1,6 +1,6 @@ import * as fs from "fs" import { describe, it, expect } from "vitest" -import { preprocessTODO, parseTodoFile } from "../../src/file.js" +import { preprocessTODO, parseTodoFile } from "../../lib/file" describe("parseTodoFile", () => { diff --git a/tests/lib/issue.test.ts b/tests/lib/issue.test.ts index 02adf7b..1d1788b 100644 --- a/tests/lib/issue.test.ts +++ b/tests/lib/issue.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest" import * as fs from "fs" -import { parseIssue, validateStatusTransition } from "../../src/issue.js" +import { parseIssue, validateStatusTransition } from "../../lib/issue" describe("parseIssue", () => { it("parses all required fields", () => { diff --git a/tests/lib/serializer.test.ts b/tests/lib/serializer.test.ts index efdc94c..c7ec184 100644 --- a/tests/lib/serializer.test.ts +++ b/tests/lib/serializer.test.ts @@ -1,12 +1,12 @@ import * as fs from "fs" import { describe, it, expect } from "vitest" -import { parseTodoFile } from "../../src/file.js" +import { parseTodoFile } from "../../lib/file" import { serializeTodoFile, serializeIssue, serializeSprints, serializeRelationships, -} from "../../src/serializer.js" +} from "../../lib/serializer" describe("serializeRelationships", () => { it("serializes empty relationships", () => { diff --git a/tests/lib/sprint.test.ts b/tests/lib/sprint.test.ts index d9332f8..d09c688 100644 --- a/tests/lib/sprint.test.ts +++ b/tests/lib/sprint.test.ts @@ -1,6 +1,6 @@ import * as fs from "fs" import { describe, it, expect } from "vitest" -import { parseSprints } from "../../src/sprint.js" +import { parseSprints } from "../../lib/sprint" describe("parseSprints", () => { it("parses compact and expanded sprint entries", () => { diff --git a/tests/lib/tracker.test.ts b/tests/lib/tracker.test.ts index daddefc..fff47ac 100644 --- a/tests/lib/tracker.test.ts +++ b/tests/lib/tracker.test.ts @@ -1,8 +1,8 @@ import * as fs from "fs" import { describe, it, expect } from "vitest" -import { parseModules, parseBugzillaTracker } from "../../src/tracker.js" -import { parseTodoFile } from "../../src/file.js" -import { serializeTodoFile, serializeModules, serializeBugzillaTracker } from "../../src/serializer.js" +import { parseModules, parseBugzillaTracker } from "../../lib/tracker" +import { parseTodoFile } from "../../lib/file" +import { serializeTodoFile, serializeModules, serializeBugzillaTracker } from "../../lib/serializer" describe("parseModules", () => { it("parses modules list", () => { diff --git a/tsconfig.bin.json b/tsconfig.bin.json deleted file mode 100644 index c398b06..0000000 --- a/tsconfig.bin.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "rootDir": "bin" - }, - "include": ["bin/**/*.ts"] -} diff --git a/tsconfig.debug.json b/tsconfig.debug.json deleted file mode 100644 index 8ba49f6..0000000 --- a/tsconfig.debug.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "sourceMap": true, - "declarationMap": true, - "declaration": true, - "noEmit": false, - "incremental": true, - "outDir": "build/debug" - }, - "watchOptions": { - "watchFile": "useFsEvents", - "watchDirectory": "useFsEvents", - "fallbackPolling": "dynamicPriority", - "synchronousWatchDirectory": true, - "excludeDirectories": ["**/node_modules", "build", "lib"] - } -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 39cc3c0..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "es2022", - "module": "nodenext", - "moduleResolution": "nodenext", - "strict": true, - "sourceMap": false, - "esModuleInterop": true, - "resolveJsonModule": true, - "allowJs": false, - "declaration": true, - "declarationMap": false, - "rootDir": "src", - "lib": ["es2022"] - } -} diff --git a/tsconfig.lib.json b/tsconfig.lib.json deleted file mode 100644 index 1d4b0c6..0000000 --- a/tsconfig.lib.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "lib" - }, - "include": ["src/**/*"] -}