diff --git a/.gitignore b/.gitignore index 07e6e47..8225baa 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /node_modules +/dist diff --git a/package.json b/package.json index e7defd4..63fd56c 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,23 @@ { "name": "issue-scheduler", + "description": "Schedule issues in Gitea or Github from issue templates", "version": "0.0.0", - "description": "Issue scheduler", - "main": "src/index.ts", - "scripts": { - "test": "test" + "main": "dist/index.js", + + "author": "Luís Figueiredo (TxFig)", + "homepage": "https://gitea.alluna.pt/TxFig/issue-scheduler", + "repository": { + "type": "git", + "url": "https://gitea.alluna.pt/TxFig/issue-scheduler.git" }, - "keywords": [], - "author": "", "license": "ISC", + "keywords": [], + + "scripts": { + "test": "vitest --run", + "build": "tsc", + "start": "node dist/main.js" + }, "devDependencies": { "typescript": "^5.9.3", "vitest": "^3.2.4" diff --git a/src/cli.test.ts b/src/cli.test.ts new file mode 100644 index 0000000..845b780 --- /dev/null +++ b/src/cli.test.ts @@ -0,0 +1,41 @@ +import { test, expect, describe } from "vitest"; +import * as cli from "./cli.js" + + + +test("get flag value", () => { + +}) + +test("optional string flag", () => { + +}) + +test("optional string literal flag", () => { + +}) + +test("optional boolean flag", () => { + // no flags at all + expect(cli.optionalBooleanFlag([], [])).toBe(false) + // target flag not present in args + expect(cli.optionalBooleanFlag([], ["--verbose"])).toBe(false) + // flags list empty (nothing to match) + expect(cli.optionalBooleanFlag(["--verbose"], [])).toBe(false) + // flag present with no value + expect(cli.optionalBooleanFlag(["--verbose"], ["--verbose"])).toBe(true) + // flag followed by another flag + expect(cli.optionalBooleanFlag(["--verbose", "--other"], ["--verbose"])).toBe(true) + // duplicate flag; second ignored even if invalid + expect(cli.optionalBooleanFlag(["--verbose", "--verbose", "true"], ["--verbose"])).toBe(true) + // inline assignment not recognized as the flag token + expect(cli.optionalBooleanFlag(["--verbose=true"], ["--verbose"])).toBe(false) + // flag followed by a value + expect(() => cli.optionalBooleanFlag(["--verbose", "true"], ["--verbose"])).toThrowError(cli.NoValueExpected) + // flag followed by empty string (still a value) + expect(() => cli.optionalBooleanFlag(["--verbose", ""], ["--verbose"])).toThrowError(cli.NoValueExpected) +}) + +test("parse args", () => { + +}) diff --git a/src/cli.ts b/src/cli.ts new file mode 100644 index 0000000..9375946 --- /dev/null +++ b/src/cli.ts @@ -0,0 +1,121 @@ +export interface CLIConfig { + // path to config file + config_filepath: string, + git_client: "gitea" | "github", + log_file: string | undefined, + // verbose logging + verbose: boolean, + // doesn't log to stdout/stderr (still logs to file if --log-file) + quiet: boolean +} + +export const defaults: CLIConfig = { + config_filepath: "config.toml", + git_client: "gitea", + log_file: undefined, + verbose: false, + quiet: false +} + +export class EmptyArgumentError extends Error {} +export class NoValueExpected extends Error {} +export class InvalidValue extends Error {} + + +/** + * Searches for first argument from flags options + * Valid values don't start with dashes + * @example Flag + Valid value + * getFlagValue(["--config", "config.toml"], ["--config", "-c"]) + * // result: "config.toml" + * @example Flag + No value + * getFlagValue(["-c", "--verbose"], ["--config", "-c"]) + * // result: null + * @example No Flag + * getFlagValue(["config.toml"], ["--config", "-c"]) + * // result: undefined + * + * @param args An array of all command-line arguments + * @param flags An array of flag strings to search for + * @return {string}: When a flag is found and has a valid value + * @return {null}: When a flag is found but has no valid value + * @return {undefined}: When no flag is found in the arguments + * + */ +export function getFlagValue(args: string[], flags: string[]): string | null | undefined { + const index = args.findIndex(arg => flags.includes(arg)) + if (index == -1) return undefined + if (index == args.length - 1) return null + + const next = args[index + 1]! + if (next.startsWith("-")) { + return null + } + return next +} + +/** + * Used for when the flag is optional, but a value is required if the flag appears. + * @throws EmptyArgumentError when the flag is present but has no value. + * @returns string | undefined — string when present with value, undefined when flag not present + */ +export function optionalStringFlag(args: string[], flags: string[]): string | undefined { + const value = getFlagValue(args, flags) + if (value === null) { + const errorMessage = `${flags.join(", ")} flag requires a value` + throw new EmptyArgumentError(errorMessage) + } + return value +} + +export function optionalStringLiteralFlag