From 8f6f83bd94b89a3fe010ac55a6c0547521defdf5 Mon Sep 17 00:00:00 2001 From: fengelhardt Date: Sat, 8 Nov 2025 23:27:41 -0800 Subject: [PATCH 1/7] - use node 24 - add yaml parser for cli - add support for domain-availability checking --- .gitignore | 6 +- .pnp.cjs | 601 +++++++++++++++++- .../domain-availability-checker.test.ts | 219 +++++++ js/cli.ts | 3 + .../domain-availability-checker.ts | 442 +++++++++++++ .../domains-to-test.txt | 3 + .../domains-to-test.yaml | 12 + js/package.json | 11 +- mise.toml | 2 +- package.json | 13 +- tsconfig.json | 8 + yarn.lock | 511 ++++++++++++++- 12 files changed, 1770 insertions(+), 61 deletions(-) create mode 100644 js/__tests__/domain-availability-checker.test.ts create mode 100644 js/commands/domain-availability-checker/domain-availability-checker.ts create mode 100644 js/commands/domain-availability-checker/domains-to-test.txt create mode 100644 js/commands/domain-availability-checker/domains-to-test.yaml create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index e90b069..24a2c76 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,8 @@ yarn-error.* # vites coverage -js/coverage \ No newline at end of file +js/coverage + +# results outputs +./**/domain-results.* +js/domain-results.* \ No newline at end of file diff --git a/.pnp.cjs b/.pnp.cjs index 4f54b53..485a2aa 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -35,10 +35,19 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@biomejs/biome", "npm:2.3.3"],\ ["@types/commander", "npm:2.12.5"],\ - ["@types/node", "npm:22.19.0"],\ + ["@types/js-yaml", "npm:4.0.9"],\ + ["@types/node", "npm:24.10.0"],\ + ["@types/whois-json", "npm:2.0.4"],\ ["@vitest/coverage-v8", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"],\ + ["commander", "npm:14.0.2"],\ + ["csv-stringify", "npm:6.6.0"],\ + ["js-yaml", "npm:4.1.0"],\ ["osa-snippets", "workspace:."],\ - ["vitest", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"]\ + ["p-limit", "npm:7.2.0"],\ + ["tsx", "npm:4.20.6"],\ + ["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5"],\ + ["vitest", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"],\ + ["whois-json", "npm:2.0.4"]\ ],\ "linkType": "SOFT"\ }]\ @@ -756,12 +765,30 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@types/js-yaml", [\ + ["npm:4.0.9", {\ + "packageLocation": "../../.yarn/berry/cache/@types-js-yaml-npm-4.0.9-6a16d01bd2-10c0.zip/node_modules/@types/js-yaml/",\ + "packageDependencies": [\ + ["@types/js-yaml", "npm:4.0.9"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/node", [\ - ["npm:22.19.0", {\ - "packageLocation": "../../.yarn/berry/cache/@types-node-npm-22.19.0-13cee709ff-10c0.zip/node_modules/@types/node/",\ + ["npm:24.10.0", {\ + "packageLocation": "../../.yarn/berry/cache/@types-node-npm-24.10.0-293a9fc97d-10c0.zip/node_modules/@types/node/",\ "packageDependencies": [\ - ["@types/node", "npm:22.19.0"],\ - ["undici-types", "npm:6.21.0"]\ + ["@types/node", "npm:24.10.0"],\ + ["undici-types", "npm:7.16.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["@types/whois-json", [\ + ["npm:2.0.4", {\ + "packageLocation": "../../.yarn/berry/cache/@types-whois-json-npm-2.0.4-c7eae50a48-10c0.zip/node_modules/@types/whois-json/",\ + "packageDependencies": [\ + ["@types/whois-json", "npm:2.0.4"]\ ],\ "linkType": "HARD"\ }]\ @@ -771,13 +798,18 @@ const RAW_RUNTIME_STATE = "packageLocation": "./js/",\ "packageDependencies": [\ ["@types/commander", "npm:2.12.5"],\ - ["@types/node", "npm:22.19.0"],\ + ["@types/js-yaml", "npm:4.0.9"],\ + ["@types/node", "npm:24.10.0"],\ ["@virtualize/osa-snippets", "workspace:js"],\ ["@vitest/coverage-v8", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"],\ - ["commander", "npm:12.1.0"],\ + ["commander", "npm:14.0.2"],\ + ["csv-stringify", "npm:6.6.0"],\ + ["js-yaml", "npm:4.1.0"],\ + ["p-limit", "npm:7.2.0"],\ ["tsx", "npm:4.20.6"],\ ["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5"],\ - ["vitest", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"]\ + ["vitest", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"],\ + ["whois-json", "npm:2.0.4"]\ ],\ "linkType": "SOFT"\ }]\ @@ -800,7 +832,7 @@ const RAW_RUNTIME_STATE = ["@vitest/coverage-v8", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"],\ ["@vitest/utils", "npm:4.0.6"],\ ["ast-v8-to-istanbul", "npm:0.3.8"],\ - ["debug", "virtual:e18ad1aaaee71856c8618c92ebbcee187ad490da91f816fa2cd1abd550e96735d3b7ca6820bb15ac7883b0178149f52bbfa078bf86f89229e4aed2f3ed5555cc#npm:4.4.3"],\ + ["debug", "virtual:643ed7cc338bcf145a82d8b05b3bef6bcf150ca545df386225596f10ce53cc90b88b3ca83e348ade1ccea5f3f8e76c92d2f0e2ba544da60d40aff9921c56872d#npm:4.4.3"],\ ["istanbul-lib-coverage", "npm:3.2.2"],\ ["istanbul-lib-report", "npm:3.0.1"],\ ["istanbul-lib-source-maps", "npm:5.0.6"],\ @@ -967,6 +999,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["argparse", [\ + ["npm:2.0.1", {\ + "packageLocation": "../../.yarn/berry/cache/argparse-npm-2.0.1-faff7999e6-10c0.zip/node_modules/argparse/",\ + "packageDependencies": [\ + ["argparse", "npm:2.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["assertion-error", [\ ["npm:2.0.1", {\ "packageLocation": "../../.yarn/berry/cache/assertion-error-npm-2.0.1-8169d136f2-10c0.zip/node_modules/assertion-error/",\ @@ -1028,6 +1069,26 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["camel-case", [\ + ["npm:3.0.0", {\ + "packageLocation": "../../.yarn/berry/cache/camel-case-npm-3.0.0-d87e5afe35-10c0.zip/node_modules/camel-case/",\ + "packageDependencies": [\ + ["camel-case", "npm:3.0.0"],\ + ["no-case", "npm:2.3.2"],\ + ["upper-case", "npm:1.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["camelcase", [\ + ["npm:5.3.1", {\ + "packageLocation": "../../.yarn/berry/cache/camelcase-npm-5.3.1-5db8af62c5-10c0.zip/node_modules/camelcase/",\ + "packageDependencies": [\ + ["camelcase", "npm:5.3.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["chai", [\ ["npm:6.2.0", {\ "packageLocation": "../../.yarn/berry/cache/chai-npm-6.2.0-ce657a084d-10c0.zip/node_modules/chai/",\ @@ -1037,6 +1098,33 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["change-case", [\ + ["npm:3.1.0", {\ + "packageLocation": "../../.yarn/berry/cache/change-case-npm-3.1.0-f29e0003bb-10c0.zip/node_modules/change-case/",\ + "packageDependencies": [\ + ["camel-case", "npm:3.0.0"],\ + ["change-case", "npm:3.1.0"],\ + ["constant-case", "npm:2.0.0"],\ + ["dot-case", "npm:2.1.1"],\ + ["header-case", "npm:1.0.1"],\ + ["is-lower-case", "npm:1.1.3"],\ + ["is-upper-case", "npm:1.1.2"],\ + ["lower-case", "npm:1.1.4"],\ + ["lower-case-first", "npm:1.0.2"],\ + ["no-case", "npm:2.3.2"],\ + ["param-case", "npm:2.1.1"],\ + ["pascal-case", "npm:2.0.1"],\ + ["path-case", "npm:2.1.1"],\ + ["sentence-case", "npm:2.1.1"],\ + ["snake-case", "npm:2.1.0"],\ + ["swap-case", "npm:1.1.2"],\ + ["title-case", "npm:2.1.1"],\ + ["upper-case", "npm:1.1.3"],\ + ["upper-case-first", "npm:1.1.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["chownr", [\ ["npm:3.0.0", {\ "packageLocation": "../../.yarn/berry/cache/chownr-npm-3.0.0-5275e85d25-10c0.zip/node_modules/chownr/",\ @@ -1046,6 +1134,18 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["cliui", [\ + ["npm:6.0.0", {\ + "packageLocation": "../../.yarn/berry/cache/cliui-npm-6.0.0-488b2414c6-10c0.zip/node_modules/cliui/",\ + "packageDependencies": [\ + ["cliui", "npm:6.0.0"],\ + ["string-width", "npm:4.2.3"],\ + ["strip-ansi", "npm:6.0.1"],\ + ["wrap-ansi", "npm:6.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["color-convert", [\ ["npm:2.0.1", {\ "packageLocation": "../../.yarn/berry/cache/color-convert-npm-2.0.1-79730e935b-10c0.zip/node_modules/color-convert/",\ @@ -1066,13 +1166,6 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["commander", [\ - ["npm:12.1.0", {\ - "packageLocation": "../../.yarn/berry/cache/commander-npm-12.1.0-65c868e907-10c0.zip/node_modules/commander/",\ - "packageDependencies": [\ - ["commander", "npm:12.1.0"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:14.0.2", {\ "packageLocation": "../../.yarn/berry/cache/commander-npm-14.0.2-538b84c387-10c0.zip/node_modules/commander/",\ "packageDependencies": [\ @@ -1081,6 +1174,17 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["constant-case", [\ + ["npm:2.0.0", {\ + "packageLocation": "../../.yarn/berry/cache/constant-case-npm-2.0.0-b287998b5e-10c0.zip/node_modules/constant-case/",\ + "packageDependencies": [\ + ["constant-case", "npm:2.0.0"],\ + ["snake-case", "npm:2.1.0"],\ + ["upper-case", "npm:1.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["cross-spawn", [\ ["npm:7.0.6", {\ "packageLocation": "../../.yarn/berry/cache/cross-spawn-npm-7.0.6-264bddf921-10c0.zip/node_modules/cross-spawn/",\ @@ -1093,6 +1197,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["csv-stringify", [\ + ["npm:6.6.0", {\ + "packageLocation": "../../.yarn/berry/cache/csv-stringify-npm-6.6.0-afbd791b30-10c0.zip/node_modules/csv-stringify/",\ + "packageDependencies": [\ + ["csv-stringify", "npm:6.6.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["debug", [\ ["npm:4.4.3", {\ "packageLocation": "../../.yarn/berry/cache/debug-npm-4.4.3-0105c6123a-10c0.zip/node_modules/debug/",\ @@ -1101,11 +1214,11 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ - ["virtual:e18ad1aaaee71856c8618c92ebbcee187ad490da91f816fa2cd1abd550e96735d3b7ca6820bb15ac7883b0178149f52bbfa078bf86f89229e4aed2f3ed5555cc#npm:4.4.3", {\ - "packageLocation": "./.yarn/__virtual__/debug-virtual-b72081c4d0/3/.yarn/berry/cache/debug-npm-4.4.3-0105c6123a-10c0.zip/node_modules/debug/",\ + ["virtual:643ed7cc338bcf145a82d8b05b3bef6bcf150ca545df386225596f10ce53cc90b88b3ca83e348ade1ccea5f3f8e76c92d2f0e2ba544da60d40aff9921c56872d#npm:4.4.3", {\ + "packageLocation": "./.yarn/__virtual__/debug-virtual-5da9fc757d/3/.yarn/berry/cache/debug-npm-4.4.3-0105c6123a-10c0.zip/node_modules/debug/",\ "packageDependencies": [\ ["@types/supports-color", null],\ - ["debug", "virtual:e18ad1aaaee71856c8618c92ebbcee187ad490da91f816fa2cd1abd550e96735d3b7ca6820bb15ac7883b0178149f52bbfa078bf86f89229e4aed2f3ed5555cc#npm:4.4.3"],\ + ["debug", "virtual:643ed7cc338bcf145a82d8b05b3bef6bcf150ca545df386225596f10ce53cc90b88b3ca83e348ade1ccea5f3f8e76c92d2f0e2ba544da60d40aff9921c56872d#npm:4.4.3"],\ ["ms", "npm:2.1.3"],\ ["supports-color", null]\ ],\ @@ -1116,6 +1229,34 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["decamelize", [\ + ["npm:1.2.0", {\ + "packageLocation": "../../.yarn/berry/cache/decamelize-npm-1.2.0-c5a2fdc622-10c0.zip/node_modules/decamelize/",\ + "packageDependencies": [\ + ["decamelize", "npm:1.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["dedent-js", [\ + ["npm:1.0.1", {\ + "packageLocation": "../../.yarn/berry/cache/dedent-js-npm-1.0.1-ddf8ce03f4-10c0.zip/node_modules/dedent-js/",\ + "packageDependencies": [\ + ["dedent-js", "npm:1.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["dot-case", [\ + ["npm:2.1.1", {\ + "packageLocation": "../../.yarn/berry/cache/dot-case-npm-2.1.1-f591fd2e48-10c0.zip/node_modules/dot-case/",\ + "packageDependencies": [\ + ["dot-case", "npm:2.1.1"],\ + ["no-case", "npm:2.3.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["eastasianwidth", [\ ["npm:0.2.0", {\ "packageLocation": "../../.yarn/berry/cache/eastasianwidth-npm-0.2.0-c37eb16bd1-10c0.zip/node_modules/eastasianwidth/",\ @@ -1263,6 +1404,17 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["find-up", [\ + ["npm:4.1.0", {\ + "packageLocation": "../../.yarn/berry/cache/find-up-npm-4.1.0-c3ccf8d855-10c0.zip/node_modules/find-up/",\ + "packageDependencies": [\ + ["find-up", "npm:4.1.0"],\ + ["locate-path", "npm:5.0.0"],\ + ["path-exists", "npm:4.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["foreground-child", [\ ["npm:3.3.1", {\ "packageLocation": "../../.yarn/berry/cache/foreground-child-npm-3.3.1-b7775fda04-10c0.zip/node_modules/foreground-child/",\ @@ -1294,6 +1446,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["get-caller-file", [\ + ["npm:2.0.5", {\ + "packageLocation": "../../.yarn/berry/cache/get-caller-file-npm-2.0.5-80e8a86305-10c0.zip/node_modules/get-caller-file/",\ + "packageDependencies": [\ + ["get-caller-file", "npm:2.0.5"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["get-tsconfig", [\ ["npm:4.13.0", {\ "packageLocation": "../../.yarn/berry/cache/get-tsconfig-npm-4.13.0-009b232bdd-10c0.zip/node_modules/get-tsconfig/",\ @@ -1337,6 +1498,26 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["header-case", [\ + ["npm:1.0.1", {\ + "packageLocation": "../../.yarn/berry/cache/header-case-npm-1.0.1-3a0bfdc9cc-10c0.zip/node_modules/header-case/",\ + "packageDependencies": [\ + ["header-case", "npm:1.0.1"],\ + ["no-case", "npm:2.3.2"],\ + ["upper-case", "npm:1.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["html-entities", [\ + ["npm:1.4.0", {\ + "packageLocation": "../../.yarn/berry/cache/html-entities-npm-1.4.0-39a1121015-10c0.zip/node_modules/html-entities/",\ + "packageDependencies": [\ + ["html-entities", "npm:1.4.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["html-escaper", [\ ["npm:2.0.2", {\ "packageLocation": "../../.yarn/berry/cache/html-escaper-npm-2.0.2-38e51ef294-10c0.zip/node_modules/html-escaper/",\ @@ -1360,7 +1541,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "../../.yarn/berry/cache/http-proxy-agent-npm-7.0.2-643ed7cc33-10c0.zip/node_modules/http-proxy-agent/",\ "packageDependencies": [\ ["agent-base", "npm:7.1.4"],\ - ["debug", "virtual:e18ad1aaaee71856c8618c92ebbcee187ad490da91f816fa2cd1abd550e96735d3b7ca6820bb15ac7883b0178149f52bbfa078bf86f89229e4aed2f3ed5555cc#npm:4.4.3"],\ + ["debug", "virtual:643ed7cc338bcf145a82d8b05b3bef6bcf150ca545df386225596f10ce53cc90b88b3ca83e348ade1ccea5f3f8e76c92d2f0e2ba544da60d40aff9921c56872d#npm:4.4.3"],\ ["http-proxy-agent", "npm:7.0.2"]\ ],\ "linkType": "HARD"\ @@ -1371,7 +1552,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "../../.yarn/berry/cache/https-proxy-agent-npm-7.0.6-27a95c2690-10c0.zip/node_modules/https-proxy-agent/",\ "packageDependencies": [\ ["agent-base", "npm:7.1.4"],\ - ["debug", "virtual:e18ad1aaaee71856c8618c92ebbcee187ad490da91f816fa2cd1abd550e96735d3b7ca6820bb15ac7883b0178149f52bbfa078bf86f89229e4aed2f3ed5555cc#npm:4.4.3"],\ + ["debug", "virtual:643ed7cc338bcf145a82d8b05b3bef6bcf150ca545df386225596f10ce53cc90b88b3ca83e348ade1ccea5f3f8e76c92d2f0e2ba544da60d40aff9921c56872d#npm:4.4.3"],\ ["https-proxy-agent", "npm:7.0.6"]\ ],\ "linkType": "HARD"\ @@ -1414,6 +1595,26 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["is-lower-case", [\ + ["npm:1.1.3", {\ + "packageLocation": "../../.yarn/berry/cache/is-lower-case-npm-1.1.3-2f95af21e5-10c0.zip/node_modules/is-lower-case/",\ + "packageDependencies": [\ + ["is-lower-case", "npm:1.1.3"],\ + ["lower-case", "npm:1.1.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["is-upper-case", [\ + ["npm:1.1.2", {\ + "packageLocation": "../../.yarn/berry/cache/is-upper-case-npm-1.1.2-0ce2928e8f-10c0.zip/node_modules/is-upper-case/",\ + "packageDependencies": [\ + ["is-upper-case", "npm:1.1.2"],\ + ["upper-case", "npm:1.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["isexe", [\ ["npm:2.0.0", {\ "packageLocation": "../../.yarn/berry/cache/isexe-npm-2.0.0-b58870bd2e-10c0.zip/node_modules/isexe/",\ @@ -1456,7 +1657,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "../../.yarn/berry/cache/istanbul-lib-source-maps-npm-5.0.6-e18ad1aaae-10c0.zip/node_modules/istanbul-lib-source-maps/",\ "packageDependencies": [\ ["@jridgewell/trace-mapping", "npm:0.3.31"],\ - ["debug", "virtual:e18ad1aaaee71856c8618c92ebbcee187ad490da91f816fa2cd1abd550e96735d3b7ca6820bb15ac7883b0178149f52bbfa078bf86f89229e4aed2f3ed5555cc#npm:4.4.3"],\ + ["debug", "virtual:643ed7cc338bcf145a82d8b05b3bef6bcf150ca545df386225596f10ce53cc90b88b3ca83e348ade1ccea5f3f8e76c92d2f0e2ba544da60d40aff9921c56872d#npm:4.4.3"],\ ["istanbul-lib-coverage", "npm:3.2.2"],\ ["istanbul-lib-source-maps", "npm:5.0.6"]\ ],\ @@ -1494,6 +1695,45 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["js-yaml", [\ + ["npm:4.1.0", {\ + "packageLocation": "../../.yarn/berry/cache/js-yaml-npm-4.1.0-3606f32312-10c0.zip/node_modules/js-yaml/",\ + "packageDependencies": [\ + ["argparse", "npm:2.0.1"],\ + ["js-yaml", "npm:4.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["locate-path", [\ + ["npm:5.0.0", {\ + "packageLocation": "../../.yarn/berry/cache/locate-path-npm-5.0.0-46580c43e4-10c0.zip/node_modules/locate-path/",\ + "packageDependencies": [\ + ["locate-path", "npm:5.0.0"],\ + ["p-locate", "npm:4.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["lower-case", [\ + ["npm:1.1.4", {\ + "packageLocation": "../../.yarn/berry/cache/lower-case-npm-1.1.4-9880e9dcb0-10c0.zip/node_modules/lower-case/",\ + "packageDependencies": [\ + ["lower-case", "npm:1.1.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["lower-case-first", [\ + ["npm:1.0.2", {\ + "packageLocation": "../../.yarn/berry/cache/lower-case-first-npm-1.0.2-9d3e4f27ec-10c0.zip/node_modules/lower-case-first/",\ + "packageDependencies": [\ + ["lower-case", "npm:1.1.4"],\ + ["lower-case-first", "npm:1.0.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["lru-cache", [\ ["npm:10.4.3", {\ "packageLocation": "../../.yarn/berry/cache/lru-cache-npm-10.4.3-30c10b861a-10c0.zip/node_modules/lru-cache/",\ @@ -1672,6 +1912,16 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["no-case", [\ + ["npm:2.3.2", {\ + "packageLocation": "../../.yarn/berry/cache/no-case-npm-2.3.2-5403767f87-10c0.zip/node_modules/no-case/",\ + "packageDependencies": [\ + ["lower-case", "npm:1.1.4"],\ + ["no-case", "npm:2.3.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["node-gyp", [\ ["npm:11.5.0", {\ "packageLocation": "./.yarn/unplugged/node-gyp-npm-11.5.0-6cfe9d790c/node_modules/node-gyp/",\ @@ -1707,14 +1957,51 @@ const RAW_RUNTIME_STATE = "packageDependencies": [\ ["@biomejs/biome", "npm:2.3.3"],\ ["@types/commander", "npm:2.12.5"],\ - ["@types/node", "npm:22.19.0"],\ + ["@types/js-yaml", "npm:4.0.9"],\ + ["@types/node", "npm:24.10.0"],\ + ["@types/whois-json", "npm:2.0.4"],\ ["@vitest/coverage-v8", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"],\ + ["commander", "npm:14.0.2"],\ + ["csv-stringify", "npm:6.6.0"],\ + ["js-yaml", "npm:4.1.0"],\ ["osa-snippets", "workspace:."],\ - ["vitest", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"]\ + ["p-limit", "npm:7.2.0"],\ + ["tsx", "npm:4.20.6"],\ + ["typescript", "patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5"],\ + ["vitest", "virtual:6fddcb6bd99765bac7f2b290599c78196c6e6e0b69854e91620b6ccb38ae963c32e85d4bd2b1bd018e76b720595ed01206569ae47872fa1aad64c63460e22100#npm:4.0.6"],\ + ["whois-json", "npm:2.0.4"]\ ],\ "linkType": "SOFT"\ }]\ ]],\ + ["p-limit", [\ + ["npm:2.3.0", {\ + "packageLocation": "../../.yarn/berry/cache/p-limit-npm-2.3.0-94a0310039-10c0.zip/node_modules/p-limit/",\ + "packageDependencies": [\ + ["p-limit", "npm:2.3.0"],\ + ["p-try", "npm:2.2.0"]\ + ],\ + "linkType": "HARD"\ + }],\ + ["npm:7.2.0", {\ + "packageLocation": "../../.yarn/berry/cache/p-limit-npm-7.2.0-72063c9642-10c0.zip/node_modules/p-limit/",\ + "packageDependencies": [\ + ["p-limit", "npm:7.2.0"],\ + ["yocto-queue", "npm:1.2.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["p-locate", [\ + ["npm:4.1.0", {\ + "packageLocation": "../../.yarn/berry/cache/p-locate-npm-4.1.0-eec6872537-10c0.zip/node_modules/p-locate/",\ + "packageDependencies": [\ + ["p-limit", "npm:2.3.0"],\ + ["p-locate", "npm:4.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["p-map", [\ ["npm:7.0.3", {\ "packageLocation": "../../.yarn/berry/cache/p-map-npm-7.0.3-93bbec0d8c-10c0.zip/node_modules/p-map/",\ @@ -1724,6 +2011,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["p-try", [\ + ["npm:2.2.0", {\ + "packageLocation": "../../.yarn/berry/cache/p-try-npm-2.2.0-e0390dbaf8-10c0.zip/node_modules/p-try/",\ + "packageDependencies": [\ + ["p-try", "npm:2.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["package-json-from-dist", [\ ["npm:1.0.1", {\ "packageLocation": "../../.yarn/berry/cache/package-json-from-dist-npm-1.0.1-4631a88465-10c0.zip/node_modules/package-json-from-dist/",\ @@ -1733,6 +2029,46 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["param-case", [\ + ["npm:2.1.1", {\ + "packageLocation": "../../.yarn/berry/cache/param-case-npm-2.1.1-e0aef3c289-10c0.zip/node_modules/param-case/",\ + "packageDependencies": [\ + ["no-case", "npm:2.3.2"],\ + ["param-case", "npm:2.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["pascal-case", [\ + ["npm:2.0.1", {\ + "packageLocation": "../../.yarn/berry/cache/pascal-case-npm-2.0.1-97fc825dec-10c0.zip/node_modules/pascal-case/",\ + "packageDependencies": [\ + ["camel-case", "npm:3.0.0"],\ + ["pascal-case", "npm:2.0.1"],\ + ["upper-case-first", "npm:1.1.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["path-case", [\ + ["npm:2.1.1", {\ + "packageLocation": "../../.yarn/berry/cache/path-case-npm-2.1.1-fafa84599b-10c0.zip/node_modules/path-case/",\ + "packageDependencies": [\ + ["no-case", "npm:2.3.2"],\ + ["path-case", "npm:2.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["path-exists", [\ + ["npm:4.0.0", {\ + "packageLocation": "../../.yarn/berry/cache/path-exists-npm-4.0.0-e9e4f63eb0-10c0.zip/node_modules/path-exists/",\ + "packageDependencies": [\ + ["path-exists", "npm:4.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["path-key", [\ ["npm:3.1.1", {\ "packageLocation": "../../.yarn/berry/cache/path-key-npm-3.1.1-0e66ea8321-10c0.zip/node_modules/path-key/",\ @@ -1812,6 +2148,33 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["punycode", [\ + ["npm:2.3.1", {\ + "packageLocation": "../../.yarn/berry/cache/punycode-npm-2.3.1-97543c420d-10c0.zip/node_modules/punycode/",\ + "packageDependencies": [\ + ["punycode", "npm:2.3.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["require-directory", [\ + ["npm:2.1.1", {\ + "packageLocation": "../../.yarn/berry/cache/require-directory-npm-2.1.1-8608aee50b-10c0.zip/node_modules/require-directory/",\ + "packageDependencies": [\ + ["require-directory", "npm:2.1.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["require-main-filename", [\ + ["npm:2.0.0", {\ + "packageLocation": "../../.yarn/berry/cache/require-main-filename-npm-2.0.0-03eef65c84-10c0.zip/node_modules/require-main-filename/",\ + "packageDependencies": [\ + ["require-main-filename", "npm:2.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["resolve-pkg-maps", [\ ["npm:1.0.0", {\ "packageLocation": "../../.yarn/berry/cache/resolve-pkg-maps-npm-1.0.0-135b70c854-10c0.zip/node_modules/resolve-pkg-maps/",\ @@ -1881,6 +2244,26 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["sentence-case", [\ + ["npm:2.1.1", {\ + "packageLocation": "../../.yarn/berry/cache/sentence-case-npm-2.1.1-ffe9ddf186-10c0.zip/node_modules/sentence-case/",\ + "packageDependencies": [\ + ["no-case", "npm:2.3.2"],\ + ["sentence-case", "npm:2.1.1"],\ + ["upper-case-first", "npm:1.1.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["set-blocking", [\ + ["npm:2.0.0", {\ + "packageLocation": "../../.yarn/berry/cache/set-blocking-npm-2.0.0-49e2cffa24-10c0.zip/node_modules/set-blocking/",\ + "packageDependencies": [\ + ["set-blocking", "npm:2.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["shebang-command", [\ ["npm:2.0.0", {\ "packageLocation": "../../.yarn/berry/cache/shebang-command-npm-2.0.0-eb2b01921d-10c0.zip/node_modules/shebang-command/",\ @@ -1927,6 +2310,16 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["snake-case", [\ + ["npm:2.1.0", {\ + "packageLocation": "../../.yarn/berry/cache/snake-case-npm-2.1.0-4134611dfc-10c0.zip/node_modules/snake-case/",\ + "packageDependencies": [\ + ["no-case", "npm:2.3.2"],\ + ["snake-case", "npm:2.1.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["socks", [\ ["npm:2.8.7", {\ "packageLocation": "../../.yarn/berry/cache/socks-npm-2.8.7-d1d20aae19-10c0.zip/node_modules/socks/",\ @@ -1943,7 +2336,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "../../.yarn/berry/cache/socks-proxy-agent-npm-8.0.5-24d77a90dc-10c0.zip/node_modules/socks-proxy-agent/",\ "packageDependencies": [\ ["agent-base", "npm:7.1.4"],\ - ["debug", "virtual:e18ad1aaaee71856c8618c92ebbcee187ad490da91f816fa2cd1abd550e96735d3b7ca6820bb15ac7883b0178149f52bbfa078bf86f89229e4aed2f3ed5555cc#npm:4.4.3"],\ + ["debug", "virtual:643ed7cc338bcf145a82d8b05b3bef6bcf150ca545df386225596f10ce53cc90b88b3ca83e348ade1ccea5f3f8e76c92d2f0e2ba544da60d40aff9921c56872d#npm:4.4.3"],\ ["socks", "npm:2.8.7"],\ ["socks-proxy-agent", "npm:8.0.5"]\ ],\ @@ -2037,6 +2430,17 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["swap-case", [\ + ["npm:1.1.2", {\ + "packageLocation": "../../.yarn/berry/cache/swap-case-npm-1.1.2-2d186deabd-10c0.zip/node_modules/swap-case/",\ + "packageDependencies": [\ + ["lower-case", "npm:1.1.4"],\ + ["swap-case", "npm:1.1.2"],\ + ["upper-case", "npm:1.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["tar", [\ ["npm:7.5.2", {\ "packageLocation": "../../.yarn/berry/cache/tar-npm-7.5.2-6d8cfb7a13-10c0.zip/node_modules/tar/",\ @@ -2089,6 +2493,17 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["title-case", [\ + ["npm:2.1.1", {\ + "packageLocation": "../../.yarn/berry/cache/title-case-npm-2.1.1-d828015841-10c0.zip/node_modules/title-case/",\ + "packageDependencies": [\ + ["no-case", "npm:2.3.2"],\ + ["title-case", "npm:2.1.1"],\ + ["upper-case", "npm:1.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["tsx", [\ ["npm:4.20.6", {\ "packageLocation": "../../.yarn/berry/cache/tsx-npm-4.20.6-78231068b5-10c0.zip/node_modules/tsx/",\ @@ -2110,11 +2525,20 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["underscore", [\ + ["npm:1.13.7", {\ + "packageLocation": "../../.yarn/berry/cache/underscore-npm-1.13.7-f57feeae48-10c0.zip/node_modules/underscore/",\ + "packageDependencies": [\ + ["underscore", "npm:1.13.7"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["undici-types", [\ - ["npm:6.21.0", {\ - "packageLocation": "../../.yarn/berry/cache/undici-types-npm-6.21.0-eb2b0ed56a-10c0.zip/node_modules/undici-types/",\ + ["npm:7.16.0", {\ + "packageLocation": "../../.yarn/berry/cache/undici-types-npm-7.16.0-0e23b08124-10c0.zip/node_modules/undici-types/",\ "packageDependencies": [\ - ["undici-types", "npm:6.21.0"]\ + ["undici-types", "npm:7.16.0"]\ ],\ "linkType": "HARD"\ }]\ @@ -2139,6 +2563,25 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["upper-case", [\ + ["npm:1.1.3", {\ + "packageLocation": "../../.yarn/berry/cache/upper-case-npm-1.1.3-061d82781f-10c0.zip/node_modules/upper-case/",\ + "packageDependencies": [\ + ["upper-case", "npm:1.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["upper-case-first", [\ + ["npm:1.1.2", {\ + "packageLocation": "../../.yarn/berry/cache/upper-case-first-npm-1.1.2-a07735d821-10c0.zip/node_modules/upper-case-first/",\ + "packageDependencies": [\ + ["upper-case", "npm:1.1.3"],\ + ["upper-case-first", "npm:1.1.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["vite", [\ ["npm:7.1.12", {\ "packageLocation": "../../.yarn/berry/cache/vite-npm-7.1.12-4c5705516c-10c0.zip/node_modules/vite/",\ @@ -2153,7 +2596,7 @@ const RAW_RUNTIME_STATE = ["@types/jiti", null],\ ["@types/less", null],\ ["@types/lightningcss", null],\ - ["@types/node", "npm:22.19.0"],\ + ["@types/node", "npm:24.10.0"],\ ["@types/sass", null],\ ["@types/sass-embedded", null],\ ["@types/stylus", null],\ @@ -2222,7 +2665,7 @@ const RAW_RUNTIME_STATE = ["@types/edge-runtime__vm", null],\ ["@types/happy-dom", null],\ ["@types/jsdom", null],\ - ["@types/node", "npm:22.19.0"],\ + ["@types/node", "npm:24.10.0"],\ ["@types/vitest__browser-playwright", null],\ ["@types/vitest__browser-preview", null],\ ["@types/vitest__browser-webdriverio", null],\ @@ -2238,7 +2681,7 @@ const RAW_RUNTIME_STATE = ["@vitest/spy", "npm:4.0.6"],\ ["@vitest/ui", null],\ ["@vitest/utils", "npm:4.0.6"],\ - ["debug", "virtual:e18ad1aaaee71856c8618c92ebbcee187ad490da91f816fa2cd1abd550e96735d3b7ca6820bb15ac7883b0178149f52bbfa078bf86f89229e4aed2f3ed5555cc#npm:4.4.3"],\ + ["debug", "virtual:643ed7cc338bcf145a82d8b05b3bef6bcf150ca545df386225596f10ce53cc90b88b3ca83e348ade1ccea5f3f8e76c92d2f0e2ba544da60d40aff9921c56872d#npm:4.4.3"],\ ["es-module-lexer", "npm:1.7.0"],\ ["expect-type", "npm:1.2.2"],\ ["happy-dom", null],\ @@ -2294,6 +2737,41 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["which-module", [\ + ["npm:2.0.1", {\ + "packageLocation": "../../.yarn/berry/cache/which-module-npm-2.0.1-90f889f6f6-10c0.zip/node_modules/which-module/",\ + "packageDependencies": [\ + ["which-module", "npm:2.0.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["whois", [\ + ["npm:2.15.0", {\ + "packageLocation": "../../.yarn/berry/cache/whois-npm-2.15.0-a23ba8bc81-10c0.zip/node_modules/whois/",\ + "packageDependencies": [\ + ["punycode", "npm:2.3.1"],\ + ["socks", "npm:2.8.7"],\ + ["underscore", "npm:1.13.7"],\ + ["whois", "npm:2.15.0"],\ + ["yargs", "npm:15.4.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["whois-json", [\ + ["npm:2.0.4", {\ + "packageLocation": "../../.yarn/berry/cache/whois-json-npm-2.0.4-4dda81b20d-10c0.zip/node_modules/whois-json/",\ + "packageDependencies": [\ + ["change-case", "npm:3.1.0"],\ + ["dedent-js", "npm:1.0.1"],\ + ["html-entities", "npm:1.4.0"],\ + ["whois", "npm:2.15.0"],\ + ["whois-json", "npm:2.0.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["why-is-node-running", [\ ["npm:2.3.0", {\ "packageLocation": "../../.yarn/berry/cache/why-is-node-running-npm-2.3.0-011cf61a18-10c0.zip/node_modules/why-is-node-running/",\ @@ -2306,6 +2784,16 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["wrap-ansi", [\ + ["npm:6.2.0", {\ + "packageLocation": "../../.yarn/berry/cache/wrap-ansi-npm-6.2.0-439a7246d8-10c0.zip/node_modules/wrap-ansi/",\ + "packageDependencies": [\ + ["ansi-styles", "npm:4.3.0"],\ + ["string-width", "npm:4.2.3"],\ + ["strip-ansi", "npm:6.0.1"],\ + ["wrap-ansi", "npm:6.2.0"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:7.0.0", {\ "packageLocation": "../../.yarn/berry/cache/wrap-ansi-npm-7.0.0-ad6e1a0554-10c0.zip/node_modules/wrap-ansi/",\ "packageDependencies": [\ @@ -2327,6 +2815,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["y18n", [\ + ["npm:4.0.3", {\ + "packageLocation": "../../.yarn/berry/cache/y18n-npm-4.0.3-ced95acdbc-10c0.zip/node_modules/y18n/",\ + "packageDependencies": [\ + ["y18n", "npm:4.0.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["yallist", [\ ["npm:4.0.0", {\ "packageLocation": "../../.yarn/berry/cache/yallist-npm-4.0.0-b493d9e907-10c0.zip/node_modules/yallist/",\ @@ -2342,6 +2839,46 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }]\ + ]],\ + ["yargs", [\ + ["npm:15.4.1", {\ + "packageLocation": "../../.yarn/berry/cache/yargs-npm-15.4.1-ca1c444de1-10c0.zip/node_modules/yargs/",\ + "packageDependencies": [\ + ["cliui", "npm:6.0.0"],\ + ["decamelize", "npm:1.2.0"],\ + ["find-up", "npm:4.1.0"],\ + ["get-caller-file", "npm:2.0.5"],\ + ["require-directory", "npm:2.1.1"],\ + ["require-main-filename", "npm:2.0.0"],\ + ["set-blocking", "npm:2.0.0"],\ + ["string-width", "npm:4.2.3"],\ + ["which-module", "npm:2.0.1"],\ + ["y18n", "npm:4.0.3"],\ + ["yargs", "npm:15.4.1"],\ + ["yargs-parser", "npm:18.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["yargs-parser", [\ + ["npm:18.1.3", {\ + "packageLocation": "../../.yarn/berry/cache/yargs-parser-npm-18.1.3-0ba9c4f088-10c0.zip/node_modules/yargs-parser/",\ + "packageDependencies": [\ + ["camelcase", "npm:5.3.1"],\ + ["decamelize", "npm:1.2.0"],\ + ["yargs-parser", "npm:18.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ + ["yocto-queue", [\ + ["npm:1.2.1", {\ + "packageLocation": "../../.yarn/berry/cache/yocto-queue-npm-1.2.1-98b92882fa-10c0.zip/node_modules/yocto-queue/",\ + "packageDependencies": [\ + ["yocto-queue", "npm:1.2.1"]\ + ],\ + "linkType": "HARD"\ + }]\ ]]\ ]\ }'; diff --git a/js/__tests__/domain-availability-checker.test.ts b/js/__tests__/domain-availability-checker.test.ts new file mode 100644 index 0000000..1c6c676 --- /dev/null +++ b/js/__tests__/domain-availability-checker.test.ts @@ -0,0 +1,219 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import fs from "node:fs"; +import { + readNamesFromInputs, + readTldsFromInputs, + readYamlConfig, + inferAvailability, + summarizeParsed, +} from "../commands/domain-availability-checker/domain-availability-checker"; + +// Mock only readFileSync +const mockReadFileSync = vi.fn(); +vi.spyOn(fs, "readFileSync").mockImplementation(mockReadFileSync); + +describe("Domain Availability Checker", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("readYamlConfig", () => { + it("should parse valid YAML config", () => { + const yamlContent = ` +names: + - test1 + - test2 +tlds: + - com + - dev +`; + mockReadFileSync.mockReturnValue(yamlContent); + + const result = readYamlConfig("test.yaml"); + + expect(result).toEqual({ + names: ["test1", "test2"], + tlds: ["com", "dev"], + }); + }); + + it("should return null for invalid YAML", () => { + mockReadFileSync.mockImplementation(() => { + throw new Error("Invalid YAML"); + }); + + const result = readYamlConfig("test.yaml"); + + expect(result).toBeNull(); + }); + + it("should return null for empty file", () => { + mockReadFileSync.mockReturnValue(""); + + const result = readYamlConfig("test.yaml"); + + expect(result).toBeNull(); + }); + }); + + describe("readNamesFromInputs", () => { + it("should read names from array", () => { + const result = readNamesFromInputs(["test1", "test2"]); + + expect(result).toEqual(["test1", "test2"]); + }); + + it("should read names from YAML file", () => { + const yamlContent = "names:\n - yamlname1\n - yamlname2"; + mockReadFileSync.mockReturnValue(yamlContent); + + const result = readNamesFromInputs(undefined, "test.yaml"); + + expect(result).toEqual(["yamlname1", "yamlname2"]); + }); + + it("should read names from plain text file when YAML fails", () => { + mockReadFileSync.mockReturnValue("name1\nname2\n\nname3"); + + const result = readNamesFromInputs(undefined, "test.txt"); + + expect(result).toEqual(["name1", "name2", "name3"]); + }); + + it("should combine array and file names", () => { + const yamlContent = "names:\n - file1\n - file2"; + mockReadFileSync.mockReturnValue(yamlContent); + + const result = readNamesFromInputs(["arg1", "arg2"], "test.yaml"); + + expect(result).toEqual(["arg1", "arg2", "file1", "file2"]); + }); + + it("should deduplicate names", () => { + const result = readNamesFromInputs(["test", "test", "other"]); + + expect(result).toEqual(["test", "other"]); + }); + + it("should trim and lowercase names", () => { + const result = readNamesFromInputs([" TEST ", " Other "]); + + expect(result).toEqual(["test", "other"]); + }); + }); + + describe("readTldsFromInputs", () => { + it("should read TLDs from array", () => { + const result = readTldsFromInputs(["com", "dev"]); + + expect(result).toEqual(["com", "dev"]); + }); + + it("should read TLDs from YAML file", () => { + const yamlContent = "tlds:\n - com\n - dev"; + mockReadFileSync.mockReturnValue(yamlContent); + + const result = readTldsFromInputs(["net"], "test.yaml"); + + expect(result).toEqual(["net", "com", "dev"]); + }); + + it("should remove dots and lowercase TLDs", () => { + const result = readTldsFromInputs([".COM", "DEV"]); + + expect(result).toEqual(["com", "dev"]); + }); + + it("should deduplicate TLDs", () => { + const result = readTldsFromInputs(["com", "com", "dev"]); + + expect(result).toEqual(["com", "dev"]); + }); + }); + + describe("inferAvailability", () => { + it("should return likely-available for empty/null results", () => { + expect(inferAvailability(null)).toBe("likely-available"); + expect(inferAvailability({})).toBe("likely-available"); + }); + + it("should return registered for domains with domain name", () => { + const result = inferAvailability({ + "Domain Name": "example.com", + registrar: "Test Registrar", + }); + + expect(result).toBe("registered"); + }); + + it("should return likely-available for 'not found' phrases", () => { + expect(inferAvailability({}, "No match for domain")).toBe("likely-available"); + expect(inferAvailability({}, "Domain not found")).toBe("likely-available"); + expect(inferAvailability({}, "Available")).toBe("likely-available"); + }); + + it("should return registered for registrar indicators", () => { + const result = inferAvailability({ + registrar: "Test Registrar", + "Creation Date": "2020-01-01", + }); + + expect(result).toBe("registered"); + }); + + it("should return unknown for unrecognized results", () => { + const result = inferAvailability({ + someField: "someValue", + }); + + expect(result).toBe("unknown"); + }); + }); + + describe("summarizeParsed", () => { + it("should return empty string for null/undefined", () => { + expect(summarizeParsed(null)).toBe(""); + expect(summarizeParsed(undefined)).toBe(""); + }); + + it("should summarize WHOIS data", () => { + const parsed = { + domainName: "example.com", + registrar: "Test Registrar", + creationDate: "2020-01-01T00:00:00.000Z", + status: "active", + }; + + const result = summarizeParsed(parsed); + + expect(result).toContain("domainName:example.com"); + expect(result).toContain("registrar:Test Registrar"); + expect(result).toContain("creationDate:2020-01-01T00:00:00.000Z"); + expect(result).toContain("status:active"); + }); + + it("should handle multiple domain name fields", () => { + const parsed = { + "Domain Name": "EXAMPLE.COM", + domainName: "example.com", + }; + + const result = summarizeParsed(parsed); + + expect(result).toContain("domainName:example.com"); + expect(result).toContain("Domain Name:EXAMPLE.COM"); + }); + + it("should truncate long values", () => { + const longValue = "a".repeat(100); + const parsed = { + domainName: longValue, + }; + + const result = summarizeParsed(parsed); + + expect(result.length).toBeLessThan(100); + expect(result).toContain("..."); + }); + }); +}); \ No newline at end of file diff --git a/js/cli.ts b/js/cli.ts index b82581c..c8108cf 100755 --- a/js/cli.ts +++ b/js/cli.ts @@ -2,6 +2,7 @@ import { execSync } from "child_process"; import { Command } from "commander"; import { auditAutoApprove } from "./commands/copilot/audit-auto-approve.ts"; +import { makeCheckDomainsCommand } from "./commands/domain-availability-checker/domain-availability-checker.ts"; const packageJson = await import("./package.json", { with: { type: "json" }, @@ -21,6 +22,8 @@ const copilot = program .command("copilot") .description("GitHub Copilot helpers"); +program.addCommand(makeCheckDomainsCommand()); + // Subcommand: copilot audit-auto-approve copilot .command("audit-auto-approve") diff --git a/js/commands/domain-availability-checker/domain-availability-checker.ts b/js/commands/domain-availability-checker/domain-availability-checker.ts new file mode 100644 index 0000000..b5994c9 --- /dev/null +++ b/js/commands/domain-availability-checker/domain-availability-checker.ts @@ -0,0 +1,442 @@ +#!/usr/bin/env -S tsx +/** + * Domain availability checker using WHOIS/RDAP (whois-json). + * - Exported Commander Command for composition into a parent CLI (osa). + * - Also runnable directly (detects direct-run vs import). + */ + +import fs from "node:fs"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import { Command } from "commander"; +import { stringify as csvStringify } from "csv-stringify/sync"; +import yaml from "js-yaml"; +import pLimit from "p-limit"; +import whois from "whois-json"; + +// ---------- Types ---------- +export interface CheckResult { + domain: string; + name: string; + tld: string; + status: "registered" | "likely-available" | "unknown" | "error"; + elapsedMs: number; + parsedSummary: string; + rawSnippet: string; +} + +export interface RunChecksOptions { + names?: string[]; // candidate names without TLD + filePath?: string; // optional file with one name per line, or YAML with names/tlds + tlds: string[]; // TLDs without leading dot + concurrency?: number; // default 8 + timeoutMs?: number; // default 20000 + provider?: string; // WHOIS provider/server to use +} + +export interface WriteCsvOptions { + outFile: string; + results: CheckResult[]; + writeHeader?: boolean; // default true +} + +// ---------- Utils ---------- +interface YamlConfig { + names?: string[]; + tlds?: string[]; +} + +export function readYamlConfig(filePath: string): YamlConfig | null { + try { + const text = fs.readFileSync(filePath, "utf8"); + const config = yaml.load(text) as YamlConfig; + return config || null; + } catch { + return null; + } +} + +export function readNamesFromInputs( + namesList?: string[], + filePath?: string, +): string[] { + const names = new Set(); + + if (Array.isArray(namesList)) { + for (const n of namesList) { + const v = n.trim().toLowerCase(); + if (v) names.add(v); + } + } + + if (filePath) { + // Try YAML first + const yamlConfig = readYamlConfig(filePath); + if (yamlConfig?.names) { + for (const n of yamlConfig.names) { + const v = String(n).trim().toLowerCase(); + if (v) names.add(v); + } + } else { + // Fall back to plain text (one name per line) + const text = fs.readFileSync(filePath, "utf8"); + for (const line of text.split(/\r?\n/)) { + const t = line.trim().toLowerCase(); + if (t) names.add(t); + } + } + } + + return Array.from(names); +} + +export function readTldsFromInputs(tldsList: string[], filePath?: string): string[] { + const tlds = new Set(); + + // Add provided tlds + for (const t of tldsList) { + const v = t.replace(/^\./, "").trim().toLowerCase(); + if (v) tlds.add(v); + } + + // Add tlds from YAML file if present + if (filePath) { + const yamlConfig = readYamlConfig(filePath); + if (yamlConfig?.tlds) { + for (const t of yamlConfig.tlds) { + const v = String(t).replace(/^\./, "").trim().toLowerCase(); + if (v) tlds.add(v); + } + } + } + + return Array.from(tlds); +} + +export function inferAvailability( + whoisResult: any, + rawText?: string, +): CheckResult["status"] { + if (!whoisResult || Object.keys(whoisResult).length === 0) + return "likely-available"; + const keys = Object.keys(whoisResult).map((k) => k.toLowerCase()); + + if ( + keys.some( + (k) => + k.includes("domain name") || k.includes("domainname") || k === "domain", + ) + ) { + if ( + whoisResult.domainName || + whoisResult["Domain Name"] || + whoisResult["domain name"] + ) { + return "registered"; + } + } + + if (rawText) { + const lower = rawText.toLowerCase(); + const notFoundPhrases = [ + "no match", + "not found", + "no entries found", + "status: free", + "no data found", + "available", + "domain not found", + "no such domain", + "not registered", + "no object found", + ]; + for (const p of notFoundPhrases) { + if (lower.includes(p)) return "likely-available"; + } + } + + const registrarIndicators = [ + "registrar", + "name server", + "creation date", + "updated date", + "registry domain id", + "sponsoring registrar", + ]; + if (registrarIndicators.some((i) => keys.some((k) => k.includes(i)))) + return "registered"; + + return "unknown"; +} + +async function lookupWhois( + domain: string, + timeoutMs = 20_000, + provider?: string, +): Promise<{ parsed: any; raw: string }> { + const options: any = {}; + if (provider) { + options.server = provider; + } + + const p = (whois as any)(domain, options) + .then((res: any) => ({ parsed: res, raw: "" })) + .catch((err: any) => ({ parsed: null, raw: String(err) })); + const timeout = new Promise<{ parsed: any; raw: string }>((res) => + setTimeout(() => res({ parsed: null, raw: "__TIMEOUT__" }), timeoutMs), + ); + return Promise.race([p, timeout]) as Promise<{ parsed: any; raw: string }>; +} + +function truncate(s: string, n: number): string { + if (!s) return ""; + return s.length > n ? `${s.slice(0, n - 3)}...` : s; +} + +export function summarizeParsed(parsed: any): string { + if (!parsed) return ""; + const pick = [ + "domainName", + "Domain Name", + "Registrar", + "registrar", + "Creation Date", + "creationDate", + "Updated Date", + "updatedDate", + "status", + ]; + const out: string[] = []; + for (const k of pick) { + if (parsed[k]) out.push(`${k}:${truncate(String(parsed[k]), 80)}`); + } + if (parsed.domainName && !out.some((x) => x.startsWith("domainName:"))) { + out.push(`domainName:${truncate(String(parsed.domainName), 80)}`); + } + return out.join(" | "); +} + +// ---------- Core engine ---------- +export async function runChecks( + opts: RunChecksOptions, +): Promise { + const names = readNamesFromInputs(opts.names, opts.filePath); + if (names.length === 0) { + throw new Error("No candidate names provided. Use names[] or filePath."); + } + const tlds = readTldsFromInputs(opts.tlds, opts.filePath); + if (tlds.length === 0) throw new Error("No TLDs supplied."); + + const concurrency = Math.max(1, Math.min(200, Number(opts.concurrency ?? 8))); + const timeoutMs = Number(opts.timeoutMs ?? 20_000); + const limit = pLimit(concurrency); + + const jobs: Array> = []; + for (const name of names) { + for (const tld of tlds) { + const fqdn = `${name}.${tld}`; + jobs.push( + limit(async () => { + const started = Date.now(); + let status: CheckResult["status"] = "error"; + let parsed: any = null; + let raw = ""; + try { + const { parsed: p, raw: r } = await lookupWhois( + fqdn, + timeoutMs, + opts.provider, + ); + parsed = p; + raw = r ?? ""; + status = inferAvailability(parsed, raw); + } catch (e: any) { + raw = String(e); + status = "error"; + } + const elapsedMs = Date.now() - started; + return { + domain: fqdn, + name, + tld, + status, + elapsedMs, + parsedSummary: parsed ? summarizeParsed(parsed) : "", + rawSnippet: raw ? truncate(raw, 1200) : "", + }; + }), + ); + } + } + + return Promise.all(jobs); +} + +export function writeResultsCsv({ + outFile, + results, + writeHeader = true, +}: WriteCsvOptions): void { + const columns = [ + "domain", + "status", + "elapsedMs", + "parsedSummary", + "rawSnippet", + ]; + const records = results.map((r) => [ + r.domain, + r.status, + r.elapsedMs, + r.parsedSummary, + r.rawSnippet, + ]); + const csv = csvStringify(records, { header: writeHeader, columns }); + fs.writeFileSync(outFile, csv, "utf8"); +} + +// ---------- Exported Commander Command (for parent CLI composition) ---------- +export function makeCheckDomainsCommand(): Command { + const cmd = new Command("check-domains") + .description( + "Check domain availability via WHOIS/RDAP (heuristic). Use registrar APIs for purchase-grade checks.", + ) + .option( + "-n, --names ", + "Comma-separated names (no TLD). Example: virtualize,vize", + "", + ) + .option( + "-f, --file ", + "Path to file with one candidate name per line", + ) + .option( + "-t, --tlds ", + "Comma-separated TLDs (no dot). Default: com,dev,io", + "com,dev,io", + ) + .option( + "-p, --provider ", + "WHOIS server to use (e.g., whois.verisign-grs.com)", + ) + .option("--format ", "Output format: csv, json, or yaml", "csv") + .option("--timeout-ms ", "Lookup timeout (ms, default 20000)", "20000") + .option("-o, --out ", "Output CSV path", "domain-check-results.csv") + .option("--no-header", "Do not write CSV header (for appending)") + .option( + "--max-rows-print ", + "How many available-ish domains to log", + "50", + ) + .action( + async (opts: { + names?: string; + file?: string; + tlds: string; + concurrency?: string | number; + timeoutMs?: string | number; + out: string; + header: boolean; // from --no-header + maxRowsPrint?: string | number; + provider?: string; + format?: string; + }) => { + const namesArg = opts.names + ? String(opts.names) + .split(",") + .map((s) => s.trim()) + .filter(Boolean) + : undefined; + + const tldsArg = String(opts.tlds) + .split(",") + .map((s) => s.trim()) + .filter(Boolean); + + const results = await runChecks({ + names: namesArg, + filePath: opts.file, + tlds: tldsArg, + concurrency: Number(opts.concurrency ?? 8), + timeoutMs: Number(opts.timeoutMs ?? 20_000), + provider: opts.provider, + }); + + const format = (opts.format || "csv").toLowerCase(); + if (format === "json") { + const output = { + summary: results.reduce>((acc, r) => { + acc[r.status] = (acc[r.status] ?? 0) + 1; + return acc; + }, {}), + results, + generatedAt: new Date().toISOString(), + total: results.length, + }; + fs.writeFileSync(opts.out, JSON.stringify(output, null, 2), "utf8"); + console.log(`Wrote ${results.length} results to ${opts.out}`); + } else if (format === "yaml") { + const output = { + summary: results.reduce>((acc, r) => { + acc[r.status] = (acc[r.status] ?? 0) + 1; + return acc; + }, {}), + results, + generatedAt: new Date().toISOString(), + total: results.length, + }; + fs.writeFileSync(opts.out, yaml.dump(output), "utf8"); + console.log(`Wrote ${results.length} results to ${opts.out}`); + } else { + writeResultsCsv({ + outFile: opts.out, + results, + writeHeader: opts.header, + }); + console.log(`Wrote ${results.length} rows to ${opts.out}`); + } + + const summary = results.reduce>((acc, r) => { + acc[r.status] = (acc[r.status] ?? 0) + 1; + return acc; + }, {}); + console.log("Summary:", summary); + + const max = Math.max(1, Number(opts.maxRowsPrint ?? 50)); + console.log(`Top ${max} likely-available/unknown:`); + results + .filter( + (r) => r.status === "likely-available" || r.status === "unknown", + ) + .slice(0, max) + .forEach((r) => console.log(` - ${r.domain} => ${r.status}`)); + }, + ); + + return cmd; +} + +// Named + default export (handy for consumers) +const exported = { + makeCheckDomainsCommand, + runChecks, + writeResultsCsv, + inferAvailability, + summarizeParsed, +}; +export default exported; + +// ---------- Direct-run detection (standalone mode) ---------- +const isDirectRun = + typeof process !== "undefined" && + process.argv[1] && + import.meta.url === pathToFileURL(process.argv[1]).toString(); + +if (isDirectRun) { + // Build a local program and attach our command. + const program = new Command().name("check-domains"); + program.addCommand(makeCheckDomainsCommand()); + program.parseAsync(process.argv).catch((err) => { + console.error("Fatal error:", err); + process.exit(1); + }); +} diff --git a/js/commands/domain-availability-checker/domains-to-test.txt b/js/commands/domain-availability-checker/domains-to-test.txt new file mode 100644 index 0000000..4742543 --- /dev/null +++ b/js/commands/domain-availability-checker/domains-to-test.txt @@ -0,0 +1,3 @@ +virtualize +vize +vldev diff --git a/js/commands/domain-availability-checker/domains-to-test.yaml b/js/commands/domain-availability-checker/domains-to-test.yaml new file mode 100644 index 0000000..3680bfd --- /dev/null +++ b/js/commands/domain-availability-checker/domains-to-test.yaml @@ -0,0 +1,12 @@ +names: + - virtualize + - vize + - vldev + - testdomain + +tlds: + - dev + - com + - net + - ai + - io \ No newline at end of file diff --git a/js/package.json b/js/package.json index e39d89c..c0e6b71 100644 --- a/js/package.json +++ b/js/package.json @@ -18,12 +18,17 @@ "LICENSE" ], "dependencies": { - "commander": "^12.1.0", - "tsx": "^4.19.0" + "commander": "^14.0.2", + "csv-stringify": "^6.6.0", + "js-yaml": "^4.1.0", + "p-limit": "^7.2.0", + "tsx": "^4.20.0", + "whois-json": "2.0.4" }, "devDependencies": { "@types/commander": "^2.12.5", - "@types/node": "^22.0.0", + "@types/js-yaml": "^4", + "@types/node": "^24.0.0", "@vitest/coverage-v8": "4.0.6", "typescript": "^5.6.3", "vitest": "^4.0.6" diff --git a/mise.toml b/mise.toml index da41fe4..f2c15e1 100644 --- a/mise.toml +++ b/mise.toml @@ -1,2 +1,2 @@ [tools] -node = "22" \ No newline at end of file +node = "24" \ No newline at end of file diff --git a/package.json b/package.json index 494e0e1..fea2de1 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,19 @@ "devDependencies": { "@biomejs/biome": "^2.3.3", "@types/commander": "^2.12.5", - "@types/node": "^22.0.0", + "@types/js-yaml": "^4", + "@types/node": "^24.10.0", + "@types/whois-json": "^2", "@vitest/coverage-v8": "4.0.6", + "tsx": "^4.20.6", + "typescript": "^5.9.3", "vitest": "^4.0.6" + }, + "dependencies": { + "commander": "^14.0.2", + "csv-stringify": "^6.6.0", + "js-yaml": "^4.1.0", + "p-limit": "^7.2.0", + "whois-json": "2.0.4" } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d209a09 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "composite": true + }, + "references": [{ "path": "./js/tsconfig.json" }] +} diff --git a/yarn.lock b/yarn.lock index c7757e7..5a7e07c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -590,12 +590,26 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^22.0.0": - version: 22.19.0 - resolution: "@types/node@npm:22.19.0" +"@types/js-yaml@npm:^4": + version: 4.0.9 + resolution: "@types/js-yaml@npm:4.0.9" + checksum: 10c0/24de857aa8d61526bbfbbaa383aa538283ad17363fcd5bb5148e2c7f604547db36646440e739d78241ed008702a8920665d1add5618687b6743858fae00da211 + languageName: node + linkType: hard + +"@types/node@npm:^24.0.0, @types/node@npm:^24.10.0": + version: 24.10.0 + resolution: "@types/node@npm:24.10.0" dependencies: - undici-types: "npm:~6.21.0" - checksum: 10c0/66b98fcd38efb4ae58c628d61fed2b8f17c830eb665d8bceabc4174cae1dd81b8e4caac4c70d7dc929f9f76a3dc46cb21f480e904d0b898297bd12c0a2d1571a + undici-types: "npm:~7.16.0" + checksum: 10c0/f82ed7194e16f5590ef7afdc20c6d09068c76d50278b485ede8f0c5749683536e3064ffa8def8db76915196afb3724b854aa5723c64d6571b890b14492943b46 + languageName: node + linkType: hard + +"@types/whois-json@npm:^2": + version: 2.0.4 + resolution: "@types/whois-json@npm:2.0.4" + checksum: 10c0/845d2ffe6253e59caa946e574b812b4ce40ae20fbe3e0f6691b70f006f4884761ab2ea8c3c38f8e34eb06b8d4cb24279b512d29880f295fed5c64cca5aaa3993 languageName: node linkType: hard @@ -604,12 +618,17 @@ __metadata: resolution: "@virtualize/osa-snippets@workspace:js" dependencies: "@types/commander": "npm:^2.12.5" - "@types/node": "npm:^22.0.0" + "@types/js-yaml": "npm:^4" + "@types/node": "npm:^24.0.0" "@vitest/coverage-v8": "npm:4.0.6" - commander: "npm:^12.1.0" - tsx: "npm:^4.19.0" + commander: "npm:^14.0.2" + csv-stringify: "npm:^6.6.0" + js-yaml: "npm:^4.1.0" + p-limit: "npm:^7.2.0" + tsx: "npm:^4.20.0" typescript: "npm:^5.6.3" vitest: "npm:^4.0.6" + whois-json: "npm:2.0.4" bin: osa: ./cli.ts languageName: unknown @@ -764,6 +783,13 @@ __metadata: languageName: node linkType: hard +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 10c0/c5640c2d89045371c7cedd6a70212a04e360fd34d6edeae32f6952c63949e3525ea77dbec0289d8213a99bbaeab5abfa860b5c12cf88a2e6cf8106e90dd27a7e + languageName: node + linkType: hard + "assertion-error@npm:^2.0.1": version: 2.0.1 resolution: "assertion-error@npm:2.0.1" @@ -818,6 +844,23 @@ __metadata: languageName: node linkType: hard +"camel-case@npm:^3.0.0": + version: 3.0.0 + resolution: "camel-case@npm:3.0.0" + dependencies: + no-case: "npm:^2.2.0" + upper-case: "npm:^1.1.1" + checksum: 10c0/491c6bbf986b9d8355e12cca6beb719b44c2fe96e8526c09958a1b4e0dbb081a82ea59c13b5a6ccf9158ce5979cbe56a8a10d7322bfeed2d84725c6b89d8f934 + languageName: node + linkType: hard + +"camelcase@npm:^5.0.0": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: 10c0/92ff9b443bfe8abb15f2b1513ca182d16126359ad4f955ebc83dc4ddcc4ef3fdd2c078bc223f2673dc223488e75c99b16cc4d056624374b799e6a1555cf61b23 + languageName: node + linkType: hard + "chai@npm:^6.0.1": version: 6.2.0 resolution: "chai@npm:6.2.0" @@ -825,6 +868,32 @@ __metadata: languageName: node linkType: hard +"change-case@npm:^3.0.2": + version: 3.1.0 + resolution: "change-case@npm:3.1.0" + dependencies: + camel-case: "npm:^3.0.0" + constant-case: "npm:^2.0.0" + dot-case: "npm:^2.1.0" + header-case: "npm:^1.0.0" + is-lower-case: "npm:^1.1.0" + is-upper-case: "npm:^1.1.0" + lower-case: "npm:^1.1.1" + lower-case-first: "npm:^1.0.0" + no-case: "npm:^2.3.2" + param-case: "npm:^2.1.0" + pascal-case: "npm:^2.0.0" + path-case: "npm:^2.1.0" + sentence-case: "npm:^2.1.0" + snake-case: "npm:^2.1.0" + swap-case: "npm:^1.1.0" + title-case: "npm:^2.1.0" + upper-case: "npm:^1.1.1" + upper-case-first: "npm:^1.1.0" + checksum: 10c0/cb44722e596e0c69e8ba28dce664b36e537ec76c8296c0baaef11d2b3db1e4a797ed50a99ff9c98a008c69dbe0270cfb96e384417a264d33de4baa709b79b9bb + languageName: node + linkType: hard + "chownr@npm:^3.0.0": version: 3.0.0 resolution: "chownr@npm:3.0.0" @@ -832,6 +901,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^6.0.0": + version: 6.0.0 + resolution: "cliui@npm:6.0.0" + dependencies: + string-width: "npm:^4.2.0" + strip-ansi: "npm:^6.0.0" + wrap-ansi: "npm:^6.2.0" + checksum: 10c0/35229b1bb48647e882104cac374c9a18e34bbf0bace0e2cf03000326b6ca3050d6b59545d91e17bfe3705f4a0e2988787aa5cde6331bf5cbbf0164732cef6492 + languageName: node + linkType: hard + "color-convert@npm:^2.0.1": version: 2.0.1 resolution: "color-convert@npm:2.0.1" @@ -848,17 +928,20 @@ __metadata: languageName: node linkType: hard -"commander@npm:*": +"commander@npm:*, commander@npm:^14.0.2": version: 14.0.2 resolution: "commander@npm:14.0.2" checksum: 10c0/245abd1349dbad5414cb6517b7b5c584895c02c4f7836ff5395f301192b8566f9796c82d7bd6c92d07eba8775fe4df86602fca5d86d8d10bcc2aded1e21c2aeb languageName: node linkType: hard -"commander@npm:^12.1.0": - version: 12.1.0 - resolution: "commander@npm:12.1.0" - checksum: 10c0/6e1996680c083b3b897bfc1cfe1c58dfbcd9842fd43e1aaf8a795fbc237f65efcc860a3ef457b318e73f29a4f4a28f6403c3d653d021d960e4632dd45bde54a9 +"constant-case@npm:^2.0.0": + version: 2.0.0 + resolution: "constant-case@npm:2.0.0" + dependencies: + snake-case: "npm:^2.1.0" + upper-case: "npm:^1.1.1" + checksum: 10c0/795142a64dd61da267e937502a1ce060abdbc42d4f68367d08f1de34fc06a1db240ac09658275122f8e171448b19a4645b023ee8229803def1a11559e80b6132 languageName: node linkType: hard @@ -873,6 +956,13 @@ __metadata: languageName: node linkType: hard +"csv-stringify@npm:^6.6.0": + version: 6.6.0 + resolution: "csv-stringify@npm:6.6.0" + checksum: 10c0/2e5b14ff1e434aba7b8cae74faa0329c1d967654820f2ae3f358a660b5887ab623224ed8eb7f3ab6d0f7342663c965ca079ca420b6046210709f72e8aec87d94 + languageName: node + linkType: hard + "debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" @@ -885,6 +975,29 @@ __metadata: languageName: node linkType: hard +"decamelize@npm:^1.2.0": + version: 1.2.0 + resolution: "decamelize@npm:1.2.0" + checksum: 10c0/85c39fe8fbf0482d4a1e224ef0119db5c1897f8503bcef8b826adff7a1b11414972f6fef2d7dec2ee0b4be3863cf64ac1439137ae9e6af23a3d8dcbe26a5b4b2 + languageName: node + linkType: hard + +"dedent-js@npm:^1.0.1": + version: 1.0.1 + resolution: "dedent-js@npm:1.0.1" + checksum: 10c0/a8cff2e02d5a1ce64615c5c53c9789e7ef1abb9ae7bf2322dc991fcbaf08d901ace1a679c1e021de15a85db7787b8ccfb02011e1f394afef0f698fc857a47009 + languageName: node + linkType: hard + +"dot-case@npm:^2.1.0": + version: 2.1.1 + resolution: "dot-case@npm:2.1.1" + dependencies: + no-case: "npm:^2.2.0" + checksum: 10c0/6859ba3bfe3106388c05eba9bec709856bbc9917d2c081aed5d268a2afc73b03bc062ea19925e29bdd482f6a8c032ae7a7d73f75c12d4159978e809b9418f7ef + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -1060,6 +1173,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: "npm:^5.0.0" + path-exists: "npm:^4.0.0" + checksum: 10c0/0406ee89ebeefa2d507feb07ec366bebd8a6167ae74aa4e34fb4c4abd06cf782a3ce26ae4194d70706f72182841733f00551c209fe575cb00bd92104056e78c1 + languageName: node + linkType: hard + "foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" @@ -1098,6 +1221,13 @@ __metadata: languageName: node linkType: hard +"get-caller-file@npm:^2.0.1": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: 10c0/c6c7b60271931fa752aeb92f2b47e355eac1af3a2673f47c9589e8f8a41adc74d45551c1bc57b5e66a80609f10ffb72b6f575e4370d61cc3f7f3aaff01757cde + languageName: node + linkType: hard + "get-tsconfig@npm:^4.7.5": version: 4.13.0 resolution: "get-tsconfig@npm:4.13.0" @@ -1137,6 +1267,23 @@ __metadata: languageName: node linkType: hard +"header-case@npm:^1.0.0": + version: 1.0.1 + resolution: "header-case@npm:1.0.1" + dependencies: + no-case: "npm:^2.2.0" + upper-case: "npm:^1.1.3" + checksum: 10c0/973b81b3fba82140cf8cdc819edb32edd5959ff61ff42128c5f54e56f7454bb8f61c0197180c38cde84a4be1dddbc780e1413d5e1602c96caf0195d863e6bd03 + languageName: node + linkType: hard + +"html-entities@npm:^1.2.1": + version: 1.4.0 + resolution: "html-entities@npm:1.4.0" + checksum: 10c0/eb2de616fb5948e681157805687672ea90e67c8a4f21a3215888ab422a984cab61fec96860708dca3bde0ae52577515683c8e28157ac8637220bb6a57a031b85 + languageName: node + linkType: hard + "html-escaper@npm:^2.0.0": version: 2.0.2 resolution: "html-escaper@npm:2.0.2" @@ -1201,6 +1348,24 @@ __metadata: languageName: node linkType: hard +"is-lower-case@npm:^1.1.0": + version: 1.1.3 + resolution: "is-lower-case@npm:1.1.3" + dependencies: + lower-case: "npm:^1.1.0" + checksum: 10c0/af174cfdd50e4ab997bd4aeaf96d5b1841490a721c62a9ab07b14dfb63885134065683d5027f53e2f76180ff972a3c9a0155815e715c37815757a6bd67d4459e + languageName: node + linkType: hard + +"is-upper-case@npm:^1.1.0": + version: 1.1.2 + resolution: "is-upper-case@npm:1.1.2" + dependencies: + upper-case: "npm:^1.1.0" + checksum: 10c0/81b8defdee0e0de7310446ac717422c586c4d013c2a517c5fcf8b119349aa2798be56fa213169b0de3936cb00e796a383683c2504d221596ae09a0eb282a5b25 + languageName: node + linkType: hard + "isexe@npm:^2.0.0": version: 2.0.0 resolution: "isexe@npm:2.0.0" @@ -1274,6 +1439,42 @@ __metadata: languageName: node linkType: hard +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: "npm:^2.0.1" + bin: + js-yaml: bin/js-yaml.js + checksum: 10c0/184a24b4eaacfce40ad9074c64fd42ac83cf74d8c8cd137718d456ced75051229e5061b8633c3366b8aada17945a7a356b337828c19da92b51ae62126575018f + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: "npm:^4.1.0" + checksum: 10c0/33a1c5247e87e022f9713e6213a744557a3e9ec32c5d0b5efb10aa3a38177615bf90221a5592674857039c1a0fd2063b82f285702d37b792d973e9e72ace6c59 + languageName: node + linkType: hard + +"lower-case-first@npm:^1.0.0": + version: 1.0.2 + resolution: "lower-case-first@npm:1.0.2" + dependencies: + lower-case: "npm:^1.1.2" + checksum: 10c0/e0689a82df329db44e28b0dd53ccace09a8a4918fc86aa6c08b091ec31bc5f3496a0b07cf7e81be065335bea996f7aa0fbe0163a3e6f019b0480a5f20a79e871 + languageName: node + linkType: hard + +"lower-case@npm:^1.1.0, lower-case@npm:^1.1.1, lower-case@npm:^1.1.2": + version: 1.1.4 + resolution: "lower-case@npm:1.1.4" + checksum: 10c0/2153ae5490d655a63addc8e7d2f848c6c94803b342ed2d177f75e8073e9fbb50a733d1432c82e1cb8425fa6eae14b2877bf5bbdcb93ab93bb982fb5c3962c57b + languageName: node + linkType: hard + "lru-cache@npm:^10.0.1, lru-cache@npm:^10.2.0": version: 10.4.3 resolution: "lru-cache@npm:10.4.3" @@ -1437,6 +1638,15 @@ __metadata: languageName: node linkType: hard +"no-case@npm:^2.2.0, no-case@npm:^2.3.2": + version: 2.3.2 + resolution: "no-case@npm:2.3.2" + dependencies: + lower-case: "npm:^1.1.1" + checksum: 10c0/63f306e83c18efa0bb37f1c23a25baf4ccf5ebaec70b482fa04d4c5bf8bbb8bcc9a8fbcd818af828ab69f2b602153daf81ec26e448b2bda2d704b8d0c7eec8fa + languageName: node + linkType: hard + "node-gyp@npm:latest": version: 11.5.0 resolution: "node-gyp@npm:11.5.0" @@ -1474,12 +1684,48 @@ __metadata: dependencies: "@biomejs/biome": "npm:^2.3.3" "@types/commander": "npm:^2.12.5" - "@types/node": "npm:^22.0.0" + "@types/js-yaml": "npm:^4" + "@types/node": "npm:^24.10.0" + "@types/whois-json": "npm:^2" "@vitest/coverage-v8": "npm:4.0.6" + commander: "npm:^14.0.2" + csv-stringify: "npm:^6.6.0" + js-yaml: "npm:^4.1.0" + p-limit: "npm:^7.2.0" + tsx: "npm:^4.20.6" + typescript: "npm:^5.9.3" vitest: "npm:^4.0.6" + whois-json: "npm:2.0.4" languageName: unknown linkType: soft +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: "npm:^2.0.0" + checksum: 10c0/8da01ac53efe6a627080fafc127c873da40c18d87b3f5d5492d465bb85ec7207e153948df6b9cbaeb130be70152f874229b8242ee2be84c0794082510af97f12 + languageName: node + linkType: hard + +"p-limit@npm:^7.2.0": + version: 7.2.0 + resolution: "p-limit@npm:7.2.0" + dependencies: + yocto-queue: "npm:^1.2.1" + checksum: 10c0/18e5ea305c31fdc0cf5260da7b63adfd46748cd72d4b40a31a13bd23eeece729c9308047fb5d848d7fa006d1b2fc2f36bb0d9dd173a300c38cd6dc1ed3355382 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: "npm:^2.2.0" + checksum: 10c0/1b476ad69ad7f6059744f343b26d51ce091508935c1dbb80c4e0a2f397ffce0ca3a1f9f5cd3c7ce19d7929a09719d5c65fe70d8ee289c3f267cd36f2881813e9 + languageName: node + linkType: hard + "p-map@npm:^7.0.2": version: 7.0.3 resolution: "p-map@npm:7.0.3" @@ -1487,6 +1733,13 @@ __metadata: languageName: node linkType: hard +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: 10c0/c36c19907734c904b16994e6535b02c36c2224d433e01a2f1ab777237f4d86e6289fd5fd464850491e940379d4606ed850c03e0f9ab600b0ebddb511312e177f + languageName: node + linkType: hard + "package-json-from-dist@npm:^1.0.0": version: 1.0.1 resolution: "package-json-from-dist@npm:1.0.1" @@ -1494,6 +1747,41 @@ __metadata: languageName: node linkType: hard +"param-case@npm:^2.1.0": + version: 2.1.1 + resolution: "param-case@npm:2.1.1" + dependencies: + no-case: "npm:^2.2.0" + checksum: 10c0/8ea1b8472fd51d5f50b28d1d754899713805d05f2241e9b8c4acafa2c500b3f47457a3b4932ab75220f14d2c69180bb7338b78a45576e2b4d90da1e6f0285833 + languageName: node + linkType: hard + +"pascal-case@npm:^2.0.0": + version: 2.0.1 + resolution: "pascal-case@npm:2.0.1" + dependencies: + camel-case: "npm:^3.0.0" + upper-case-first: "npm:^1.1.0" + checksum: 10c0/84420c1ceeee36eebe7a6975926f50500563f2c664160b952ff78774af85696d06d52a0fbfeb28c063ee37da6c83665d2518a4fefc9c66996226cabb04a1319e + languageName: node + linkType: hard + +"path-case@npm:^2.1.0": + version: 2.1.1 + resolution: "path-case@npm:2.1.1" + dependencies: + no-case: "npm:^2.2.0" + checksum: 10c0/ea74c24b55cbc2a9d766415e79f53d48a4227cecd0259a00dc4392df6195f68055de164f90c27a3b2056c1977a28a99b2916b66bade0cbf6cf18a8045e76c922 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 10c0/8c0bd3f5238188197dc78dced15207a4716c51cc4e3624c44fc97acf69558f5ebb9a2afff486fe1b4ee148e0c133e96c5e11a9aa5c48a3006e3467da070e5e1b + languageName: node + linkType: hard + "path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" @@ -1560,6 +1848,27 @@ __metadata: languageName: node linkType: hard +"punycode@npm:^2.3.1": + version: 2.3.1 + resolution: "punycode@npm:2.3.1" + checksum: 10c0/14f76a8206bc3464f794fb2e3d3cc665ae416c01893ad7a02b23766eb07159144ee612ad67af5e84fa4479ccfe67678c4feb126b0485651b302babf66f04f9e9 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: 10c0/83aa76a7bc1531f68d92c75a2ca2f54f1b01463cb566cf3fbc787d0de8be30c9dbc211d1d46be3497dac5785fe296f2dd11d531945ac29730643357978966e99 + languageName: node + linkType: hard + +"require-main-filename@npm:^2.0.0": + version: 2.0.0 + resolution: "require-main-filename@npm:2.0.0" + checksum: 10c0/db91467d9ead311b4111cbd73a4e67fa7820daed2989a32f7023785a2659008c6d119752d9c4ac011ae07e537eb86523adff99804c5fdb39cd3a017f9b401bb6 + languageName: node + linkType: hard + "resolve-pkg-maps@npm:^1.0.0": version: 1.0.0 resolution: "resolve-pkg-maps@npm:1.0.0" @@ -1671,6 +1980,23 @@ __metadata: languageName: node linkType: hard +"sentence-case@npm:^2.1.0": + version: 2.1.1 + resolution: "sentence-case@npm:2.1.1" + dependencies: + no-case: "npm:^2.2.0" + upper-case-first: "npm:^1.1.2" + checksum: 10c0/3572fe33dd5df4156bc2e5f46a8f7642906234c448484016d5fbb8c7214bdd1f5f01d1791a7b2b1a4f5a99e6e43141d22aa097c0fdcfe41214fd56a85f1ee7f6 + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 10c0/9f8c1b2d800800d0b589de1477c753492de5c1548d4ade52f57f1d1f5e04af5481554d75ce5e5c43d4004b80a3eb714398d6907027dc0534177b7539119f4454 + languageName: node + linkType: hard + "shebang-command@npm:^2.0.0": version: 2.0.0 resolution: "shebang-command@npm:2.0.0" @@ -1708,6 +2034,15 @@ __metadata: languageName: node linkType: hard +"snake-case@npm:^2.1.0": + version: 2.1.0 + resolution: "snake-case@npm:2.1.0" + dependencies: + no-case: "npm:^2.2.0" + checksum: 10c0/fd8b21537263d4e64cadd62da0cb5fd96c95f8685ee8e290c912b79950385dbb9eccf7216a913d96db5efc4ade426badae5e3e35b69ea1f10cbb0b4898a38236 + languageName: node + linkType: hard + "socks-proxy-agent@npm:^8.0.3": version: 8.0.5 resolution: "socks-proxy-agent@npm:8.0.5" @@ -1719,7 +2054,7 @@ __metadata: languageName: node linkType: hard -"socks@npm:^2.8.3": +"socks@npm:^2.2.2, socks@npm:^2.8.3": version: 2.8.7 resolution: "socks@npm:2.8.7" dependencies: @@ -1759,7 +2094,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -1808,6 +2143,16 @@ __metadata: languageName: node linkType: hard +"swap-case@npm:^1.1.0": + version: 1.1.2 + resolution: "swap-case@npm:1.1.2" + dependencies: + lower-case: "npm:^1.1.1" + upper-case: "npm:^1.1.1" + checksum: 10c0/0fb57c2427cec53c85cab0dd85f243b8b84b68e039eb550c50e01340cfa43a62e5276e04d088e9066b89073f781b258eeb97bf2c22d6232e005feeca2a11d6bc + languageName: node + linkType: hard + "tar@npm:^7.4.3": version: 7.5.2 resolution: "tar@npm:7.5.2" @@ -1852,7 +2197,17 @@ __metadata: languageName: node linkType: hard -"tsx@npm:^4.19.0": +"title-case@npm:^2.1.0": + version: 2.1.1 + resolution: "title-case@npm:2.1.1" + dependencies: + no-case: "npm:^2.2.0" + upper-case: "npm:^1.0.3" + checksum: 10c0/7b4e51036af10d99c48fac9eee8c6f59f92c1c6cc56fc1f790c7727f5eb139c7ebf7a22381626855ce74b6534f36041e91922469dbe086e67d0d71e7e8e40fc3 + languageName: node + linkType: hard + +"tsx@npm:^4.20.0, tsx@npm:^4.20.6": version: 4.20.6 resolution: "tsx@npm:4.20.6" dependencies: @@ -1868,7 +2223,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.6.3": +"typescript@npm:^5.6.3, typescript@npm:^5.9.3": version: 5.9.3 resolution: "typescript@npm:5.9.3" bin: @@ -1878,7 +2233,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.6.3#optional!builtin": +"typescript@patch:typescript@npm%3A^5.6.3#optional!builtin, typescript@patch:typescript@npm%3A^5.9.3#optional!builtin": version: 5.9.3 resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: @@ -1888,10 +2243,17 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.21.0": - version: 6.21.0 - resolution: "undici-types@npm:6.21.0" - checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 +"underscore@npm:^1.9.1": + version: 1.13.7 + resolution: "underscore@npm:1.13.7" + checksum: 10c0/fad2b4aac48847674aaf3c30558f383399d4fdafad6dd02dd60e4e1b8103b52c5a9e5937e0cc05dacfd26d6a0132ed0410ab4258241240757e4a4424507471cd + languageName: node + linkType: hard + +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a languageName: node linkType: hard @@ -1913,6 +2275,22 @@ __metadata: languageName: node linkType: hard +"upper-case-first@npm:^1.1.0, upper-case-first@npm:^1.1.2": + version: 1.1.2 + resolution: "upper-case-first@npm:1.1.2" + dependencies: + upper-case: "npm:^1.1.1" + checksum: 10c0/db3aff30538ed53c35b91a68faf46119d35ceb8800fec373ad781cbfc487f10ccd4d60609e4188e85d20ea3ec9db9a2392fa6372c334f1d1107cbde0bcb5edfe + languageName: node + linkType: hard + +"upper-case@npm:^1.0.3, upper-case@npm:^1.1.0, upper-case@npm:^1.1.1, upper-case@npm:^1.1.3": + version: 1.1.3 + resolution: "upper-case@npm:1.1.3" + checksum: 10c0/3e4d3a90519915bb591db84d72610392518806d8287b8f7541d87642d30388f42b2def1ed2f687e5792ee025e8f7e17d3a0dcbd5b3b59e306ceb1f3b8121ef54 + languageName: node + linkType: hard + "vite@npm:^6.0.0 || ^7.0.0": version: 7.1.12 resolution: "vite@npm:7.1.12" @@ -2027,6 +2405,13 @@ __metadata: languageName: node linkType: hard +"which-module@npm:^2.0.0": + version: 2.0.1 + resolution: "which-module@npm:2.0.1" + checksum: 10c0/087038e7992649eaffa6c7a4f3158d5b53b14cf5b6c1f0e043dccfacb1ba179d12f17545d5b85ebd94a42ce280a6fe65d0cbcab70f4fc6daad1dfae85e0e6a3e + languageName: node + linkType: hard + "which@npm:^2.0.1": version: 2.0.2 resolution: "which@npm:2.0.2" @@ -2049,6 +2434,32 @@ __metadata: languageName: node linkType: hard +"whois-json@npm:2.0.4": + version: 2.0.4 + resolution: "whois-json@npm:2.0.4" + dependencies: + change-case: "npm:^3.0.2" + dedent-js: "npm:^1.0.1" + html-entities: "npm:^1.2.1" + whois: "npm:^2.6.0" + checksum: 10c0/266fdf8574265b3733d9b2fc71008b8f1bd813378bcfeaf140df8e98105dd69e72827a997b726e657cdbab77840cd04d17939ff2e9054617603dc9d1e0fda722 + languageName: node + linkType: hard + +"whois@npm:^2.6.0": + version: 2.15.0 + resolution: "whois@npm:2.15.0" + dependencies: + punycode: "npm:^2.3.1" + socks: "npm:^2.2.2" + underscore: "npm:^1.9.1" + yargs: "npm:^15.4.1" + bin: + whois: bin.js + checksum: 10c0/06e4fb1428c118291972663b2fb6a70acf181e8054be1ea4dd7ab8e8e3c5b050b3180e29c1d29d006db0abe624762752a6ab2314cf68b8c6acad0bb15a48d435 + languageName: node + linkType: hard + "why-is-node-running@npm:^2.3.0": version: 2.3.0 resolution: "why-is-node-running@npm:2.3.0" @@ -2072,6 +2483,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^6.2.0": + version: 6.2.0 + resolution: "wrap-ansi@npm:6.2.0" + dependencies: + ansi-styles: "npm:^4.0.0" + string-width: "npm:^4.1.0" + strip-ansi: "npm:^6.0.0" + checksum: 10c0/baad244e6e33335ea24e86e51868fe6823626e3a3c88d9a6674642afff1d34d9a154c917e74af8d845fd25d170c4ea9cf69a47133c3f3656e1252b3d462d9f6c + languageName: node + linkType: hard + "wrap-ansi@npm:^8.1.0": version: 8.1.0 resolution: "wrap-ansi@npm:8.1.0" @@ -2083,6 +2505,13 @@ __metadata: languageName: node linkType: hard +"y18n@npm:^4.0.0": + version: 4.0.3 + resolution: "y18n@npm:4.0.3" + checksum: 10c0/308a2efd7cc296ab2c0f3b9284fd4827be01cfeb647b3ba18230e3a416eb1bc887ac050de9f8c4fd9e7856b2e8246e05d190b53c96c5ad8d8cb56dffb6f81024 + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" @@ -2096,3 +2525,39 @@ __metadata: checksum: 10c0/a499c81ce6d4a1d260d4ea0f6d49ab4da09681e32c3f0472dee16667ed69d01dae63a3b81745a24bd78476ec4fcf856114cb4896ace738e01da34b2c42235416 languageName: node linkType: hard + +"yargs-parser@npm:^18.1.2": + version: 18.1.3 + resolution: "yargs-parser@npm:18.1.3" + dependencies: + camelcase: "npm:^5.0.0" + decamelize: "npm:^1.2.0" + checksum: 10c0/25df918833592a83f52e7e4f91ba7d7bfaa2b891ebf7fe901923c2ee797534f23a176913ff6ff7ebbc1cc1725a044cc6a6539fed8bfd4e13b5b16376875f9499 + languageName: node + linkType: hard + +"yargs@npm:^15.4.1": + version: 15.4.1 + resolution: "yargs@npm:15.4.1" + dependencies: + cliui: "npm:^6.0.0" + decamelize: "npm:^1.2.0" + find-up: "npm:^4.1.0" + get-caller-file: "npm:^2.0.1" + require-directory: "npm:^2.1.1" + require-main-filename: "npm:^2.0.0" + set-blocking: "npm:^2.0.0" + string-width: "npm:^4.2.0" + which-module: "npm:^2.0.0" + y18n: "npm:^4.0.0" + yargs-parser: "npm:^18.1.2" + checksum: 10c0/f1ca680c974333a5822732825cca7e95306c5a1e7750eb7b973ce6dc4f97a6b0a8837203c8b194f461969bfe1fb1176d1d423036635285f6010b392fa498ab2d + languageName: node + linkType: hard + +"yocto-queue@npm:^1.2.1": + version: 1.2.1 + resolution: "yocto-queue@npm:1.2.1" + checksum: 10c0/5762caa3d0b421f4bdb7a1926b2ae2189fc6e4a14469258f183600028eb16db3e9e0306f46e8ebf5a52ff4b81a881f22637afefbef5399d6ad440824e9b27f9f + languageName: node + linkType: hard From a748e1ecf9ac3b41fe9090e47808ba2586bb9014 Mon Sep 17 00:00:00 2001 From: fengelhardt Date: Sun, 9 Nov 2025 00:26:52 -0800 Subject: [PATCH 2/7] improve domain availability logic --- .gitignore | 7 +- .../domain-availability-checker.test.ts | 14 +- .../domain-availability-checker.ts | 47 ++++- .../domains-to-test.txt | 3 - .../domains-to-test.yaml | 12 -- js/domains-to-test-example.yaml | 22 ++ js/package.json | 3 +- js/test-output.json | 191 ++++++++++++++++++ js/test-output.yaml | 174 ++++++++++++++++ 9 files changed, 439 insertions(+), 34 deletions(-) delete mode 100644 js/commands/domain-availability-checker/domains-to-test.txt delete mode 100644 js/commands/domain-availability-checker/domains-to-test.yaml create mode 100644 js/domains-to-test-example.yaml create mode 100644 js/test-output.json create mode 100644 js/test-output.yaml diff --git a/.gitignore b/.gitignore index 24a2c76..627eab9 100644 --- a/.gitignore +++ b/.gitignore @@ -68,4 +68,9 @@ js/coverage # results outputs ./**/domain-results.* -js/domain-results.* \ No newline at end of file +./**/domain-check-results.* +js/domain-results.* +js/domain-check-results.* + +js/domains-to-test.* +domains-to-test.* \ No newline at end of file diff --git a/js/__tests__/domain-availability-checker.test.ts b/js/__tests__/domain-availability-checker.test.ts index 1c6c676..061d29d 100644 --- a/js/__tests__/domain-availability-checker.test.ts +++ b/js/__tests__/domain-availability-checker.test.ts @@ -1,10 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from "vitest"; import fs from "node:fs"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import { + inferAvailability, readNamesFromInputs, readTldsFromInputs, readYamlConfig, - inferAvailability, summarizeParsed, } from "../commands/domain-availability-checker/domain-availability-checker"; @@ -147,8 +147,12 @@ tlds: }); it("should return likely-available for 'not found' phrases", () => { - expect(inferAvailability({}, "No match for domain")).toBe("likely-available"); - expect(inferAvailability({}, "Domain not found")).toBe("likely-available"); + expect(inferAvailability({}, "No match for domain")).toBe( + "likely-available", + ); + expect(inferAvailability({}, "Domain not found")).toBe( + "likely-available", + ); expect(inferAvailability({}, "Available")).toBe("likely-available"); }); @@ -216,4 +220,4 @@ tlds: expect(result).toContain("..."); }); }); -}); \ No newline at end of file +}); diff --git a/js/commands/domain-availability-checker/domain-availability-checker.ts b/js/commands/domain-availability-checker/domain-availability-checker.ts index b5994c9..3b7afa1 100644 --- a/js/commands/domain-availability-checker/domain-availability-checker.ts +++ b/js/commands/domain-availability-checker/domain-availability-checker.ts @@ -5,7 +5,8 @@ * - Also runnable directly (detects direct-run vs import). */ -import fs from "node:fs"; +import * as fs from "node:fs"; +import { dirname, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { Command } from "commander"; import { stringify as csvStringify } from "csv-stringify/sync"; @@ -47,7 +48,14 @@ interface YamlConfig { export function readYamlConfig(filePath: string): YamlConfig | null { try { - const text = fs.readFileSync(filePath, "utf8"); + let resolvedPath = filePath; + // Only try workspace root resolution for relative paths that look like they should be relative to workspace + if ((filePath.startsWith('./') || filePath.startsWith('../')) && !fs.existsSync(filePath)) { + const currentDir = dirname(fileURLToPath(import.meta.url)); + const workspaceRoot = resolve(currentDir, '..', '..', '..'); + resolvedPath = resolve(workspaceRoot, filePath); + } + const text = fs.readFileSync(resolvedPath, "utf8"); const config = yaml.load(text) as YamlConfig; return config || null; } catch { @@ -78,7 +86,13 @@ export function readNamesFromInputs( } } else { // Fall back to plain text (one name per line) - const text = fs.readFileSync(filePath, "utf8"); + let resolvedPath = filePath; + if ((filePath.startsWith('./') || filePath.startsWith('../')) && !fs.existsSync(filePath)) { + const currentDir = dirname(fileURLToPath(import.meta.url)); + const workspaceRoot = resolve(currentDir, '..', '..', '..'); + resolvedPath = resolve(workspaceRoot, filePath); + } + const text = fs.readFileSync(resolvedPath, "utf8"); for (const line of text.split(/\r?\n/)) { const t = line.trim().toLowerCase(); if (t) names.add(t); @@ -89,7 +103,10 @@ export function readNamesFromInputs( return Array.from(names); } -export function readTldsFromInputs(tldsList: string[], filePath?: string): string[] { +export function readTldsFromInputs( + tldsList: string[], + filePath?: string, +): string[] { const tlds = new Set(); // Add provided tlds @@ -320,7 +337,11 @@ export function makeCheckDomainsCommand(): Command { ) .option("--format ", "Output format: csv, json, or yaml", "csv") .option("--timeout-ms ", "Lookup timeout (ms, default 20000)", "20000") - .option("-o, --out ", "Output CSV path", "domain-check-results.csv") + .option( + "-o, --out ", + "Output file name (extension added automatically)", + "domain-check-results", + ) .option("--no-header", "Do not write CSV header (for appending)") .option( "--max-rows-print ", @@ -362,6 +383,10 @@ export function makeCheckDomainsCommand(): Command { }); const format = (opts.format || "csv").toLowerCase(); + const outFile = + opts.out + + (format === "csv" ? ".csv" : format === "json" ? ".json" : ".yaml"); + if (format === "json") { const output = { summary: results.reduce>((acc, r) => { @@ -372,8 +397,8 @@ export function makeCheckDomainsCommand(): Command { generatedAt: new Date().toISOString(), total: results.length, }; - fs.writeFileSync(opts.out, JSON.stringify(output, null, 2), "utf8"); - console.log(`Wrote ${results.length} results to ${opts.out}`); + fs.writeFileSync(outFile, JSON.stringify(output, null, 2), "utf8"); + console.log(`Wrote ${results.length} results to ${outFile}`); } else if (format === "yaml") { const output = { summary: results.reduce>((acc, r) => { @@ -384,15 +409,15 @@ export function makeCheckDomainsCommand(): Command { generatedAt: new Date().toISOString(), total: results.length, }; - fs.writeFileSync(opts.out, yaml.dump(output), "utf8"); - console.log(`Wrote ${results.length} results to ${opts.out}`); + fs.writeFileSync(outFile, yaml.dump(output), "utf8"); + console.log(`Wrote ${results.length} results to ${outFile}`); } else { writeResultsCsv({ - outFile: opts.out, + outFile, results, writeHeader: opts.header, }); - console.log(`Wrote ${results.length} rows to ${opts.out}`); + console.log(`Wrote ${results.length} rows to ${outFile}`); } const summary = results.reduce>((acc, r) => { diff --git a/js/commands/domain-availability-checker/domains-to-test.txt b/js/commands/domain-availability-checker/domains-to-test.txt deleted file mode 100644 index 4742543..0000000 --- a/js/commands/domain-availability-checker/domains-to-test.txt +++ /dev/null @@ -1,3 +0,0 @@ -virtualize -vize -vldev diff --git a/js/commands/domain-availability-checker/domains-to-test.yaml b/js/commands/domain-availability-checker/domains-to-test.yaml deleted file mode 100644 index 3680bfd..0000000 --- a/js/commands/domain-availability-checker/domains-to-test.yaml +++ /dev/null @@ -1,12 +0,0 @@ -names: - - virtualize - - vize - - vldev - - testdomain - -tlds: - - dev - - com - - net - - ai - - io \ No newline at end of file diff --git a/js/domains-to-test-example.yaml b/js/domains-to-test-example.yaml new file mode 100644 index 0000000..de6166f --- /dev/null +++ b/js/domains-to-test-example.yaml @@ -0,0 +1,22 @@ +# osa/scripts/domain-input.yaml + +names: + - swift-brown-fox + - clever-red-hedgehog + - bright-yellow-canary + - silent-white-owl + - agile-panda + - mightyblue + - frog + +domainProviders: + - whois # your existing WHOIS/RDAP checker + - domainr # if you add API support later + - cloudflare # ideal if you want authoritative registrar results + +tlds: + - dev + - com + - net + - ai + - io diff --git a/js/package.json b/js/package.json index c0e6b71..dae944f 100644 --- a/js/package.json +++ b/js/package.json @@ -44,6 +44,5 @@ "publishConfig": { "access": "public" }, - "sideEffects": false, - "packageManager": "yarn@4.10.3" + "sideEffects": false } diff --git a/js/test-output.json b/js/test-output.json new file mode 100644 index 0000000..5401a8d --- /dev/null +++ b/js/test-output.json @@ -0,0 +1,191 @@ +{ + "summary": { + "registered": 12, + "likely-available": 6, + "unknown": 2 + }, + "results": [ + { + "domain": "virtualize.com", + "name": "virtualize", + "tld": "com", + "status": "registered", + "elapsedMs": 84, + "parsedSummary": "domainName:VIRTUALIZE.COM | registrar:DYNADOT LLC | creationDate:2002-08-25T18:05:14.0Z | updatedDate:2025-08-03T22:14:12.0Z", + "rawSnippet": "" + }, + { + "domain": "virtualize.dev", + "name": "virtualize", + "tld": "dev", + "status": "likely-available", + "elapsedMs": 3, + "parsedSummary": "", + "rawSnippet": "Error: getaddrinfo ENOTFOUND whois.nic.google" + }, + { + "domain": "virtualize.io", + "name": "virtualize", + "tld": "io", + "status": "registered", + "elapsedMs": 101, + "parsedSummary": "domainName:VIRTUALIZE.IO | registrar:DYNADOT LLC | creationDate:2013-07-09T16:04:31.0Z | updatedDate:2025-09-05T21:54:58.0Z", + "rawSnippet": "" + }, + { + "domain": "virtualize.net", + "name": "virtualize", + "tld": "net", + "status": "registered", + "elapsedMs": 2079, + "parsedSummary": "domainName:virtualize.net | registrar:NAMECHEAP INC | creationDate:1997-11-26T05:00:00.00Z | updatedDate:2024-02-03T05:06:33.00Z", + "rawSnippet": "" + }, + { + "domain": "virtualize.ai", + "name": "virtualize", + "tld": "ai", + "status": "registered", + "elapsedMs": 127, + "parsedSummary": "domainName:virtualize.ai | registrar:GoDaddy.com, LLC | creationDate:2020-06-14T23:47:33Z | updatedDate:2025-01-21T15:27:50Z", + "rawSnippet": "" + }, + { + "domain": "vize.com", + "name": "vize", + "tld": "com", + "status": "registered", + "elapsedMs": 381, + "parsedSummary": "domainName:vize.com | registrar:GRANSY S.R.O D/B/A SUBREG.CZ | creationDate:1999-01-23T00:00:00Z | updatedDate:2025-01-24T00:00:00Z", + "rawSnippet": "" + }, + { + "domain": "vize.dev", + "name": "vize", + "tld": "dev", + "status": "likely-available", + "elapsedMs": 17, + "parsedSummary": "", + "rawSnippet": "Error: getaddrinfo ENOTFOUND whois.nic.google" + }, + { + "domain": "vize.io", + "name": "vize", + "tld": "io", + "status": "likely-available", + "elapsedMs": 43, + "parsedSummary": "", + "rawSnippet": "Error: read ECONNRESET" + }, + { + "domain": "vize.net", + "name": "vize", + "tld": "net", + "status": "registered", + "elapsedMs": 1227, + "parsedSummary": "domainName:VIZE.NET | registrar:NICS Telekomunikasyon A.S. | creationDate:2006-03-19T19:07:10Z | updatedDate:2025-03-21T18:57:34Z", + "rawSnippet": "" + }, + { + "domain": "vize.ai", + "name": "vize", + "tld": "ai", + "status": "likely-available", + "elapsedMs": 26, + "parsedSummary": "", + "rawSnippet": "Error: read ECONNRESET" + }, + { + "domain": "vldev.com", + "name": "vldev", + "tld": "com", + "status": "registered", + "elapsedMs": 176, + "parsedSummary": "domainName:VLDEV.COM | registrar:TurnCommerce, Inc. DBA NameBright.com | creationDate:2018-08-29T18:30:24.000Z | updatedDate:2020-08-23T07:17:39.321Z", + "rawSnippet": "" + }, + { + "domain": "vldev.dev", + "name": "vldev", + "tld": "dev", + "status": "likely-available", + "elapsedMs": 2, + "parsedSummary": "", + "rawSnippet": "Error: getaddrinfo ENOTFOUND whois.nic.google" + }, + { + "domain": "vldev.io", + "name": "vldev", + "tld": "io", + "status": "unknown", + "elapsedMs": 50, + "parsedSummary": "", + "rawSnippet": "" + }, + { + "domain": "vldev.net", + "name": "vldev", + "tld": "net", + "status": "registered", + "elapsedMs": 440, + "parsedSummary": "domainName:VLDEV.NET | registrar:TUCOWS DOMAINS, INC. | creationDate:2017-09-26T07:21:17 | updatedDate:2025-06-02T00:04:12", + "rawSnippet": "" + }, + { + "domain": "vldev.ai", + "name": "vldev", + "tld": "ai", + "status": "unknown", + "elapsedMs": 49, + "parsedSummary": "", + "rawSnippet": "" + }, + { + "domain": "testdomain.com", + "name": "testdomain", + "tld": "com", + "status": "registered", + "elapsedMs": 95, + "parsedSummary": "domainName:testdomain.com | registrar:GoDaddy.com, LLC | creationDate:2002-05-28T13:22:16Z | updatedDate:2025-05-08T15:30:07Z", + "rawSnippet": "" + }, + { + "domain": "testdomain.dev", + "name": "testdomain", + "tld": "dev", + "status": "likely-available", + "elapsedMs": 1, + "parsedSummary": "", + "rawSnippet": "Error: getaddrinfo ENOTFOUND whois.nic.google" + }, + { + "domain": "testdomain.io", + "name": "testdomain", + "tld": "io", + "status": "registered", + "elapsedMs": 438, + "parsedSummary": "domainName:testdomain.io | registrar:GANDI SAS | creationDate:2015-11-25T03:26:32Z | updatedDate:2025-10-21T04:27:42Z", + "rawSnippet": "" + }, + { + "domain": "testdomain.net", + "name": "testdomain", + "tld": "net", + "status": "registered", + "elapsedMs": 445, + "parsedSummary": "domainName:testdomain.net | registrar:Key-Systems GmbH | creationDate:2018-08-09T18:08:03Z | updatedDate:2025-08-10T07:28:00Z", + "rawSnippet": "" + }, + { + "domain": "testdomain.ai", + "name": "testdomain", + "tld": "ai", + "status": "registered", + "elapsedMs": 154, + "parsedSummary": "domainName:testdomain.ai | registrar:One.com A/S | creationDate:2025-03-14T12:02:50Z | updatedDate:2025-03-19T12:03:27Z", + "rawSnippet": "" + } + ], + "generatedAt": "2025-11-09T07:31:13.914Z", + "total": 20 +} \ No newline at end of file diff --git a/js/test-output.yaml b/js/test-output.yaml new file mode 100644 index 0000000..d2a4acc --- /dev/null +++ b/js/test-output.yaml @@ -0,0 +1,174 @@ +summary: + registered: 13 + likely-available: 4 + unknown: 3 +results: + - domain: virtualize.com + name: virtualize + tld: com + status: registered + elapsedMs: 262 + parsedSummary: >- + domainName:VIRTUALIZE.COM | registrar:DYNADOT LLC | + creationDate:2002-08-25T18:05:14.0Z | updatedDate:2025-08-03T22:14:12.0Z + rawSnippet: '' + - domain: virtualize.dev + name: virtualize + tld: dev + status: likely-available + elapsedMs: 110 + parsedSummary: '' + rawSnippet: 'Error: getaddrinfo ENOTFOUND whois.nic.google' + - domain: virtualize.io + name: virtualize + tld: io + status: registered + elapsedMs: 359 + parsedSummary: >- + domainName:VIRTUALIZE.IO | registrar:DYNADOT LLC | + creationDate:2013-07-09T16:04:31.0Z | updatedDate:2025-09-05T21:54:58.0Z + rawSnippet: '' + - domain: virtualize.net + name: virtualize + tld: net + status: registered + elapsedMs: 4006 + parsedSummary: >- + domainName:virtualize.net | registrar:NAMECHEAP INC | + creationDate:1997-11-26T05:00:00.00Z | updatedDate:2024-02-03T05:06:33.00Z + rawSnippet: '' + - domain: virtualize.ai + name: virtualize + tld: ai + status: registered + elapsedMs: 430 + parsedSummary: >- + domainName:virtualize.ai | registrar:GoDaddy.com, LLC | + creationDate:2020-06-14T23:47:33Z | updatedDate:2025-01-21T15:27:50Z + rawSnippet: '' + - domain: vize.com + name: vize + tld: com + status: registered + elapsedMs: 654 + parsedSummary: >- + domainName:vize.com | registrar:GRANSY S.R.O D/B/A SUBREG.CZ | + creationDate:1999-01-23T00:00:00Z | updatedDate:2025-01-24T00:00:00Z + rawSnippet: '' + - domain: vize.dev + name: vize + tld: dev + status: likely-available + elapsedMs: 198 + parsedSummary: '' + rawSnippet: 'Error: getaddrinfo ENOTFOUND whois.nic.google' + - domain: vize.io + name: vize + tld: io + status: unknown + elapsedMs: 639 + parsedSummary: '' + rawSnippet: '' + - domain: vize.net + name: vize + tld: net + status: registered + elapsedMs: 1840 + parsedSummary: >- + domainName:VIZE.NET | registrar:NICS Telekomunikasyon A.S. | + creationDate:2006-03-19T19:07:10Z | updatedDate:2025-03-21T18:57:34Z + rawSnippet: '' + - domain: vize.ai + name: vize + tld: ai + status: registered + elapsedMs: 1646 + parsedSummary: >- + domainName:vize.ai | registrar:NAMECHEAP INC | + creationDate:2025-08-09T13:52:49.14Z | updatedDate:0001-01-01T00:00:00.00Z + rawSnippet: '' + - domain: vldev.com + name: vldev + tld: com + status: registered + elapsedMs: 237 + parsedSummary: >- + domainName:VLDEV.COM | registrar:TurnCommerce, Inc. DBA NameBright.com | + creationDate:2018-08-29T18:30:24.000Z | + updatedDate:2020-08-23T07:17:39.321Z + rawSnippet: '' + - domain: vldev.dev + name: vldev + tld: dev + status: likely-available + elapsedMs: 8 + parsedSummary: '' + rawSnippet: 'Error: getaddrinfo ENOTFOUND whois.nic.google' + - domain: vldev.io + name: vldev + tld: io + status: unknown + elapsedMs: 49 + parsedSummary: '' + rawSnippet: '' + - domain: vldev.net + name: vldev + tld: net + status: registered + elapsedMs: 570 + parsedSummary: >- + domainName:VLDEV.NET | registrar:TUCOWS DOMAINS, INC. | + creationDate:2017-09-26T07:21:17 | updatedDate:2025-06-02T00:04:12 + rawSnippet: '' + - domain: vldev.ai + name: vldev + tld: ai + status: unknown + elapsedMs: 49 + parsedSummary: '' + rawSnippet: '' + - domain: testdomain.com + name: testdomain + tld: com + status: registered + elapsedMs: 247 + parsedSummary: >- + domainName:testdomain.com | registrar:GoDaddy.com, LLC | + creationDate:2002-05-28T13:22:16Z | updatedDate:2025-05-08T15:30:07Z + rawSnippet: '' + - domain: testdomain.dev + name: testdomain + tld: dev + status: likely-available + elapsedMs: 136 + parsedSummary: '' + rawSnippet: 'Error: getaddrinfo ENOTFOUND whois.nic.google' + - domain: testdomain.io + name: testdomain + tld: io + status: registered + elapsedMs: 618 + parsedSummary: >- + domainName:testdomain.io | registrar:GANDI SAS | + creationDate:2015-11-25T03:26:32Z | updatedDate:2025-10-21T04:27:42Z + rawSnippet: '' + - domain: testdomain.net + name: testdomain + tld: net + status: registered + elapsedMs: 459 + parsedSummary: >- + domainName:testdomain.net | registrar:Key-Systems GmbH | + creationDate:2018-08-09T18:08:03Z | updatedDate:2025-08-10T07:28:00Z + rawSnippet: '' + - domain: testdomain.ai + name: testdomain + tld: ai + status: registered + elapsedMs: 192 + parsedSummary: >- + domainName:testdomain.ai | registrar:One.com A/S | + creationDate:2025-03-14T12:02:50Z | updatedDate:2025-03-19T12:03:27Z + rawSnippet: '' +generatedAt: '2025-11-09T07:30:46.041Z' +total: 20 From 6f2a216cba019e92c945b0736a5df89e07fc524f Mon Sep 17 00:00:00 2001 From: fengelhardt Date: Fri, 14 Nov 2025 08:54:13 -0800 Subject: [PATCH 3/7] fix tests --- .../domain-availability-checker.test.ts | 21 ++++++-- .../domain-availability-checker.ts | 53 +++++++++++++------ 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/js/__tests__/domain-availability-checker.test.ts b/js/__tests__/domain-availability-checker.test.ts index 061d29d..647e583 100644 --- a/js/__tests__/domain-availability-checker.test.ts +++ b/js/__tests__/domain-availability-checker.test.ts @@ -1,5 +1,19 @@ import fs from "node:fs"; import { beforeEach, describe, expect, it, vi } from "vitest"; + +// Mock only readFileSync. Use vitest's vi.mock to ensure the mock is in place +// before the tested module is imported. Store the mock on globalThis so the +// factory (which is hoisted) can create it and tests can still access it. +vi.mock("node:fs", async () => { + const actual = await vi.importActual("node:fs"); + // create and expose mock on globalThis for test access + (globalThis as any).__mockReadFileSync = vi.fn(); + return { + ...actual, + readFileSync: (globalThis as any).__mockReadFileSync, + }; +}); + import { inferAvailability, readNamesFromInputs, @@ -8,9 +22,10 @@ import { summarizeParsed, } from "../commands/domain-availability-checker/domain-availability-checker"; -// Mock only readFileSync -const mockReadFileSync = vi.fn(); -vi.spyOn(fs, "readFileSync").mockImplementation(mockReadFileSync); +// Grab the mock created by the vi.mock factory above so tests can configure it. +const mockReadFileSync = + ((globalThis as any).__mockReadFileSync as jest.Mock) || + (globalThis as any).__mockReadFileSync; describe("Domain Availability Checker", () => { beforeEach(() => { diff --git a/js/commands/domain-availability-checker/domain-availability-checker.ts b/js/commands/domain-availability-checker/domain-availability-checker.ts index 3b7afa1..797ef84 100644 --- a/js/commands/domain-availability-checker/domain-availability-checker.ts +++ b/js/commands/domain-availability-checker/domain-availability-checker.ts @@ -48,14 +48,24 @@ interface YamlConfig { export function readYamlConfig(filePath: string): YamlConfig | null { try { - let resolvedPath = filePath; - // Only try workspace root resolution for relative paths that look like they should be relative to workspace - if ((filePath.startsWith('./') || filePath.startsWith('../')) && !fs.existsSync(filePath)) { - const currentDir = dirname(fileURLToPath(import.meta.url)); - const workspaceRoot = resolve(currentDir, '..', '..', '..'); - resolvedPath = resolve(workspaceRoot, filePath); + // Prefer reading the path as-provided first. This ensures tests that spy on + // fs.readFileSync(filePath) will be exercised. If that fails, try a + // workspace-root-relative fallback for CLI invocations where paths are + // relative to the repo root. + let text: string; + try { + text = fs.readFileSync(filePath, "utf8"); + } catch (err) { + // Only attempt workspace-root fallback for explicitly relative paths + if (filePath.startsWith("./") || filePath.startsWith("../")) { + const currentDir = dirname(fileURLToPath(import.meta.url)); + const workspaceRoot = resolve(currentDir, "..", "..", ".."); + const resolvedPath = resolve(workspaceRoot, filePath); + text = fs.readFileSync(resolvedPath, "utf8"); + } else { + throw err; + } } - const text = fs.readFileSync(resolvedPath, "utf8"); const config = yaml.load(text) as YamlConfig; return config || null; } catch { @@ -85,14 +95,22 @@ export function readNamesFromInputs( if (v) names.add(v); } } else { - // Fall back to plain text (one name per line) - let resolvedPath = filePath; - if ((filePath.startsWith('./') || filePath.startsWith('../')) && !fs.existsSync(filePath)) { - const currentDir = dirname(fileURLToPath(import.meta.url)); - const workspaceRoot = resolve(currentDir, '..', '..', '..'); - resolvedPath = resolve(workspaceRoot, filePath); + // Fall back to plain text (one name per line). Prefer reading the + // provided path first (so tests can mock readFileSync), and only if + // that fails attempt the workspace-root-relative fallback. + let text: string; + try { + text = fs.readFileSync(filePath, "utf8"); + } catch (err) { + if (filePath.startsWith("./") || filePath.startsWith("../")) { + const currentDir = dirname(fileURLToPath(import.meta.url)); + const workspaceRoot = resolve(currentDir, "..", "..", ".."); + const resolvedPath = resolve(workspaceRoot, filePath); + text = fs.readFileSync(resolvedPath, "utf8"); + } else { + throw err; + } } - const text = fs.readFileSync(resolvedPath, "utf8"); for (const line of text.split(/\r?\n/)) { const t = line.trim().toLowerCase(); if (t) names.add(t); @@ -265,7 +283,12 @@ export async function runChecks( ); parsed = p; raw = r ?? ""; - status = inferAvailability(parsed, raw); + // If parsed object is empty and we didn't receive any raw text, be conservative + if (parsed && Object.keys(parsed).length === 0 && !raw) { + status = "unknown"; + } else { + status = inferAvailability(parsed, raw); + } } catch (e: any) { raw = String(e); status = "error"; From 361d1059461f996f544ea847402561641f042b04 Mon Sep 17 00:00:00 2001 From: fengelhardt Date: Fri, 14 Nov 2025 08:57:27 -0800 Subject: [PATCH 4/7] make tests for e2e for both vitest and bats --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fea2de1..1b5e373 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,11 @@ "cli": "yarn workspace @virtualize/osa-snippets run cli", "lint": "biome check ./js", "test": "vitest ./js --run", + "test-all": "vitest ./js --run", "test:coverage": "vitest ./js --run --coverage", "lint:fix": "biome check --fix ./js", "build": "echo 'Nothing to build at root'", - "test:e2e": "cd js && node ../tests/run-tests.zsh" + "test:e2e": "yarn test && ./tests/run-tests.zsh" }, "devDependencies": { "@biomejs/biome": "^2.3.3", From 1b528cc90871d1209b021f7f738a38592a0ac601 Mon Sep 17 00:00:00 2001 From: fengelhardt Date: Fri, 14 Nov 2025 08:57:38 -0800 Subject: [PATCH 5/7] remove note9 and pixel5 hardcode aliases --- zsh/platform/android/android-emulator.zsh | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/zsh/platform/android/android-emulator.zsh b/zsh/platform/android/android-emulator.zsh index 6be40dd..07c7034 100755 --- a/zsh/platform/android/android-emulator.zsh +++ b/zsh/platform/android/android-emulator.zsh @@ -42,14 +42,4 @@ launch_emulator() { fi emulator -avd "$name" $EMULATOR_DEFAULT_ARGS -} - -# Named aliases for quick access -note9(){ - emulator -avd Note9-API-${1:-33} $EMULATOR_DEFAULT_ARGS; -} - -# Android Quick-Boot Aliases -pixel5(){ - emulator -avd pixel5 $EMULATOR_DEFAULT_ARGS } \ No newline at end of file From 2a5f2982e6c6e3fc9892dc4ea23be313f2286281 Mon Sep 17 00:00:00 2001 From: fengelhardt Date: Sat, 24 Jan 2026 00:40:14 -0800 Subject: [PATCH 6/7] Migrate max files to macos section and add feature flag for it --- README.md | 19 ++ apps/watchman/set-max-file-limit.sh | 24 -- entry.zsh | 5 + zsh/platform/android/android-users.zsh | 98 +++++++++ zsh/platform/android/pull-apks-by-user.zsh | 205 ++++++++++++++++++ zsh/platform/detect-platform.zsh | 0 .../platform/mac}/limit.maxfiles.plist | 2 +- zsh/platform/mac/set-max-file-limit.sh | 32 +++ 8 files changed, 360 insertions(+), 25 deletions(-) delete mode 100755 apps/watchman/set-max-file-limit.sh create mode 100644 zsh/platform/android/android-users.zsh create mode 100755 zsh/platform/android/pull-apks-by-user.zsh mode change 100644 => 100755 zsh/platform/detect-platform.zsh rename {apps/watchman => zsh/platform/mac}/limit.maxfiles.plist (98%) create mode 100644 zsh/platform/mac/set-max-file-limit.sh diff --git a/README.md b/README.md index db656c2..cf19335 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,25 @@ Key scripts designed to boost developer productivity: - **Usage**: `rn-fix` - **Saves Time**: 5-10 minutes of manual cleanup per fix +### macOS File Descriptor Management + +**`fix-max-files-temp`** - Temporary File Descriptor Limit + +- **Problem Solved**: "Too many open files" errors when running file watchers or tools that open many file descriptors +- **How It Works**: Applies temporary system-level file descriptor limits for the current session using `launchctl` +- **Usage**: `fix-max-files-temp` +- **Duration**: Session only; resets on reboot +- **Perfect For**: Quick fixes during development sessions + +**`fix-max-files-permanently`** - Permanent File Descriptor Configuration + +- **Problem Solved**: Sets persistent file descriptor limits across reboots +- **How It Works**: Installs LaunchDaemon configuration at `/Library/LaunchDaemons/limit.maxfiles.plist` +- **Usage**: `fix-max-files-permanently` +- **Requirements**: The `limit.maxfiles.plist` file should be located alongside the script +- **Warning**: Will not override an existing configuration file +- **Perfect For**: Permanent solution on development machines + **`rnios` / `rnra`** - iOS/Android Launch Shortcuts - **Usage**: `rnios` (iOS) or `rnra` (Android) diff --git a/apps/watchman/set-max-file-limit.sh b/apps/watchman/set-max-file-limit.sh deleted file mode 100755 index 9f1924f..0000000 --- a/apps/watchman/set-max-file-limit.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -MAXFILES_SOFT_LIMIT=1048576 -MAXFILES_HARD_LIMIT=10485760 - -# MacOSX >= 10.6.x permanent fix -fix-max-files-permanently(){ - # do not enable this until the script is tested - # local SYSTCTL_CONF=/etc/sysctl.conf - local LIMIT_CONFIG_FILE=/Library/LaunchDaemons/limit.maxfiles.plist - - if [ ! -f $LIMIT_CONFIG_FILE ]; then - sudo cp ./limit.maxfiles.plist $LIMIT_CONFIG_FILE - sudo chown root:wheel $LIMIT_CONFIG_FILE - else - echo "$LIMIT_CONFIG_FILE already exists. This script will not override a pre-existing file." - fi -} - -fix-max-files-permanently - -echo "adding a temp override for " -# sudo sysctl -w kern.maxfiles=10485760 && sudo sysctl -w kern.maxfilesperproc=$MAXFILES_SOFT_LIMIT -sudo launchctl limit maxfiles $MAXFILES_SOFT_LIMIT $MAXFILES_HARD_LIMIT \ No newline at end of file diff --git a/entry.zsh b/entry.zsh index b14ab91..de8b09a 100755 --- a/entry.zsh +++ b/entry.zsh @@ -132,6 +132,11 @@ if [[ "${OSA_CONFIG_COMPONENTS_MAC_TOOLS}" == "true" ]] || [[ "${OSA_CONFIG_SNIP [[ -f "$OSA_SCRIPTS_ZSH_ROOT/platform/mac/rmAsync.zsh" ]] && source "$OSA_SCRIPTS_ZSH_ROOT/platform/mac/rmAsync.zsh" fi +# macOS File Descriptor Management +if [[ "${OSA_CONFIG_SNIPPETS_OSASNIPPETS_FILELIMITS}" == "true" ]] || [[ "${OSA_CONFIG_SNIPPETS_OSASNIPPETS_FILELIMITS}" == "true" ]]; then + [[ -f "$OSA_SCRIPTS_ZSH_ROOT/platform/mac/set-max-file-limit.sh" ]] && source "$OSA_SCRIPTS_ZSH_ROOT/platform/mac/set-max-file-limit.sh" +fi + # macOS eGPU Management if [[ "${OSA_CONFIG_COMPONENTS_EGPU}" == "true" ]] || [[ "${OSA_CONFIG_SNIPPETS_OSASNIPPETS_EGPU}" == "true" ]]; then [[ -f "$OSA_SCRIPTS_ZSH_ROOT/platform/mac/egpu.zsh" ]] && source "$OSA_SCRIPTS_ZSH_ROOT/platform/mac/egpu.zsh" diff --git a/zsh/platform/android/android-users.zsh b/zsh/platform/android/android-users.zsh new file mode 100644 index 0000000..c0f5c38 --- /dev/null +++ b/zsh/platform/android/android-users.zsh @@ -0,0 +1,98 @@ +#!/usr/bin/env zsh +set -euo pipefail + +command -v adb >/dev/null 2>&1 || { echo "adb not found in PATH" >&2; exit 1; } + +normalize() { echo "$1" | tr '[:upper:]' '[:lower:]'; } + +guess_type() { + local label="$1" + local flags="$2" + local id="$3" + + local l f + l="$(normalize "$label")" + f="$(normalize "$flags")" + + if [[ "$id" == "0" ]]; then echo "owner"; return; fi + if echo "$l $f" | grep -qE 'managed|work|profile_managed|ismanagedprofile'; then echo "work"; return; fi + if echo "$l $f" | grep -qE 'secure folder|securefolder|knox'; then echo "secure"; return; fi + echo "user" +} + +echo "== Raw: cmd user list ==" +RAW_CMD="$(adb shell cmd user list 2>/dev/null || true)" +echo "$RAW_CMD" +echo + +echo "== Raw: dumpsys user (top) ==" +RAW_DUMP="$(adb shell dumpsys user 2>/dev/null || true)" +echo "$RAW_DUMP" | sed -n '1,220p' +echo + +# Build a merged view primarily from dumpsys user because it carries flags/parents. +# We parse blocks that contain "UserInfo{::}" and try to capture flags and parentId. +# Output columns: +# user_id | type | name | flags | parent_id +# +# Note: parsing is best-effort; Android OEMs vary. + +# Extract candidate user lines from dumpsys +mapfile -t USER_LINES < <( + echo "$RAW_DUMP" \ + | sed -n 's/.*UserInfo{\([0-9]\+\):\([^:}]*\):\([0-9]\+\).*/\1|\2|\3/p' \ + | sort -n -t'|' -k1,1 +) + +if [[ ${#USER_LINES[@]} -eq 0 ]]; then + echo "!! Could not parse users from dumpsys user; falling back to cmd user list only." >&2 + # Fallback parse from cmd user list + echo "$RAW_CMD" \ + | sed -n 's/.*UserInfo{\([0-9]\+\):\([^:}]*\).*/\1|\2/p' \ + | sort -n \ + | while IFS='|' read -r id name; do + printf "u%-4s %-7s %s\n" "$id" "$(guess_type "$name" "" "$id")" "$name" + done + exit 0 +fi + +# Function to find a likely flags line for a given user id from dumpsys (best-effort) +get_flags_for_user() { + local id="$1" + # Look for "UserInfo{ID:" line and take nearby "flags=" occurrence + echo "$RAW_DUMP" \ + | awk -v uid="$id" ' + $0 ~ "UserInfo\\{"uid":" {in=1; c=0} + in==1 {print; c++; if (c>40) in=0} + ' \ + | sed -n 's/.*flags=\([^ ]*\).*/\1/p' \ + | head -n 1 +} + +get_parent_for_user() { + local id="$1" + # Try to locate "profileGroupId=" or "parentId=" nearby + echo "$RAW_DUMP" \ + | awk -v uid="$id" ' + $0 ~ "UserInfo\\{"uid":" {in=1; c=0} + in==1 {print; c++; if (c>40) in=0} + ' \ + | sed -n 's/.*profileGroupId=\([-0-9]\+\).*/\1/p; s/.*parentId=\([-0-9]\+\).*/\1/p' \ + | head -n 1 +} + +printf "%-6s %-8s %-28s %-18s %-10s\n" "USER" "TYPE" "NAME" "FLAGS" "PARENT" +printf "%-6s %-8s %-28s %-18s %-10s\n" "----" "----" "----" "-----" "------" + +for L in "${USER_LINES[@]}"; do + UID="${L%%|*}" + REST="${L#*|}" + NAME="${REST%%|*}" + FLAGS="$(get_flags_for_user "$UID")" + PARENT="$(get_parent_for_user "$UID")" + [[ -z "$FLAGS" ]] && FLAGS="-" + [[ -z "$PARENT" ]] && PARENT="-" + TYPE="$(guess_type "$NAME" "$FLAGS" "$UID")" + + printf "u%-5s %-8s %-28s %-18s %-10s\n" "$UID" "$TYPE" "$NAME" "$FLAGS" "$PARENT" +done diff --git a/zsh/platform/android/pull-apks-by-user.zsh b/zsh/platform/android/pull-apks-by-user.zsh new file mode 100755 index 0000000..67b7b72 --- /dev/null +++ b/zsh/platform/android/pull-apks-by-user.zsh @@ -0,0 +1,205 @@ +#!/usr/bin/env zsh + +set -euo pipefail + +# pull-apks-by-user.sh +# Usage: +# ./pull-apks-by-user.sh [output-dir] +# +# Examples: +# ./pull-apks-by-user.sh filemacros +# ./pull-apks-by-user.sh com.virtualize ./tab-s9-apks +# +# Notes: +# - Attempts to pull APK dirs per Android user/profile. +# - Some profiles (e.g., Samsung Secure Folder / Knox) may deny shell access; those get skipped. + +SEARCH_TERM="${1:-}" +OUT_ROOT="${2:-./pulled-apks}" + +if [[ -z "$SEARCH_TERM" ]]; then + echo "Usage: $0 [output-dir]" >&2 + exit 2 +fi + +command -v adb >/dev/null 2>&1 || { echo "adb not found in PATH" >&2; exit 1; } + +mkdir -p "$OUT_ROOT" + +safe_name() { + local s="$1" + echo "$s" | tr -c 'A-Za-z0-9._-' '_' +} + +# Best-effort user type guesser using the label from cmd user list +guess_user_type() { + local label="$1" + local id="$2" + + # Normalize + local l + l="$(echo "$label" | tr '[:upper:]' '[:lower:]')" + + if [[ "$id" == "0" ]]; then + echo "owner" + return + fi + + if echo "$l" | grep -qE 'work|managed|profile'; then + echo "work" + return + fi + + if echo "$l" | grep -qE 'secure folder|securefolder|knox'; then + echo "secure" + return + fi + + # Common Samsung patterns sometimes show "UserInfo{150:...}" without clear label + echo "user" +} + +echo "==> Output dir: $OUT_ROOT" +echo "==> Search term: $SEARCH_TERM" +echo + +# Get list of users: lines like "UserInfo{0:Owner:13} running" +USER_LIST_RAW="$(adb shell cmd user list 2>/dev/null || true)" + +if [[ -z "$USER_LIST_RAW" ]]; then + echo "!! Failed to read user list (adb shell cmd user list)." >&2 + exit 1 +fi + +# Parse user ids + labels +# Extract: id and label inside UserInfo{: