Skip to content

Commit a748e1e

Browse files
improve domain availability logic
1 parent 8f6f83b commit a748e1e

9 files changed

Lines changed: 439 additions & 34 deletions

File tree

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,9 @@ js/coverage
6868

6969
# results outputs
7070
./**/domain-results.*
71-
js/domain-results.*
71+
./**/domain-check-results.*
72+
js/domain-results.*
73+
js/domain-check-results.*
74+
75+
js/domains-to-test.*
76+
domains-to-test.*

js/__tests__/domain-availability-checker.test.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { describe, it, expect, vi, beforeEach } from "vitest";
21
import fs from "node:fs";
2+
import { beforeEach, describe, expect, it, vi } from "vitest";
33
import {
4+
inferAvailability,
45
readNamesFromInputs,
56
readTldsFromInputs,
67
readYamlConfig,
7-
inferAvailability,
88
summarizeParsed,
99
} from "../commands/domain-availability-checker/domain-availability-checker";
1010

@@ -147,8 +147,12 @@ tlds:
147147
});
148148

149149
it("should return likely-available for 'not found' phrases", () => {
150-
expect(inferAvailability({}, "No match for domain")).toBe("likely-available");
151-
expect(inferAvailability({}, "Domain not found")).toBe("likely-available");
150+
expect(inferAvailability({}, "No match for domain")).toBe(
151+
"likely-available",
152+
);
153+
expect(inferAvailability({}, "Domain not found")).toBe(
154+
"likely-available",
155+
);
152156
expect(inferAvailability({}, "Available")).toBe("likely-available");
153157
});
154158

@@ -216,4 +220,4 @@ tlds:
216220
expect(result).toContain("...");
217221
});
218222
});
219-
});
223+
});

js/commands/domain-availability-checker/domain-availability-checker.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
* - Also runnable directly (detects direct-run vs import).
66
*/
77

8-
import fs from "node:fs";
8+
import * as fs from "node:fs";
9+
import { dirname, resolve } from "node:path";
910
import { fileURLToPath, pathToFileURL } from "node:url";
1011
import { Command } from "commander";
1112
import { stringify as csvStringify } from "csv-stringify/sync";
@@ -47,7 +48,14 @@ interface YamlConfig {
4748

4849
export function readYamlConfig(filePath: string): YamlConfig | null {
4950
try {
50-
const text = fs.readFileSync(filePath, "utf8");
51+
let resolvedPath = filePath;
52+
// Only try workspace root resolution for relative paths that look like they should be relative to workspace
53+
if ((filePath.startsWith('./') || filePath.startsWith('../')) && !fs.existsSync(filePath)) {
54+
const currentDir = dirname(fileURLToPath(import.meta.url));
55+
const workspaceRoot = resolve(currentDir, '..', '..', '..');
56+
resolvedPath = resolve(workspaceRoot, filePath);
57+
}
58+
const text = fs.readFileSync(resolvedPath, "utf8");
5159
const config = yaml.load(text) as YamlConfig;
5260
return config || null;
5361
} catch {
@@ -78,7 +86,13 @@ export function readNamesFromInputs(
7886
}
7987
} else {
8088
// Fall back to plain text (one name per line)
81-
const text = fs.readFileSync(filePath, "utf8");
89+
let resolvedPath = filePath;
90+
if ((filePath.startsWith('./') || filePath.startsWith('../')) && !fs.existsSync(filePath)) {
91+
const currentDir = dirname(fileURLToPath(import.meta.url));
92+
const workspaceRoot = resolve(currentDir, '..', '..', '..');
93+
resolvedPath = resolve(workspaceRoot, filePath);
94+
}
95+
const text = fs.readFileSync(resolvedPath, "utf8");
8296
for (const line of text.split(/\r?\n/)) {
8397
const t = line.trim().toLowerCase();
8498
if (t) names.add(t);
@@ -89,7 +103,10 @@ export function readNamesFromInputs(
89103
return Array.from(names);
90104
}
91105

92-
export function readTldsFromInputs(tldsList: string[], filePath?: string): string[] {
106+
export function readTldsFromInputs(
107+
tldsList: string[],
108+
filePath?: string,
109+
): string[] {
93110
const tlds = new Set<string>();
94111

95112
// Add provided tlds
@@ -320,7 +337,11 @@ export function makeCheckDomainsCommand(): Command {
320337
)
321338
.option("--format <format>", "Output format: csv, json, or yaml", "csv")
322339
.option("--timeout-ms <n>", "Lookup timeout (ms, default 20000)", "20000")
323-
.option("-o, --out <file>", "Output CSV path", "domain-check-results.csv")
340+
.option(
341+
"-o, --out <file>",
342+
"Output file name (extension added automatically)",
343+
"domain-check-results",
344+
)
324345
.option("--no-header", "Do not write CSV header (for appending)")
325346
.option(
326347
"--max-rows-print <n>",
@@ -362,6 +383,10 @@ export function makeCheckDomainsCommand(): Command {
362383
});
363384

364385
const format = (opts.format || "csv").toLowerCase();
386+
const outFile =
387+
opts.out +
388+
(format === "csv" ? ".csv" : format === "json" ? ".json" : ".yaml");
389+
365390
if (format === "json") {
366391
const output = {
367392
summary: results.reduce<Record<string, number>>((acc, r) => {
@@ -372,8 +397,8 @@ export function makeCheckDomainsCommand(): Command {
372397
generatedAt: new Date().toISOString(),
373398
total: results.length,
374399
};
375-
fs.writeFileSync(opts.out, JSON.stringify(output, null, 2), "utf8");
376-
console.log(`Wrote ${results.length} results to ${opts.out}`);
400+
fs.writeFileSync(outFile, JSON.stringify(output, null, 2), "utf8");
401+
console.log(`Wrote ${results.length} results to ${outFile}`);
377402
} else if (format === "yaml") {
378403
const output = {
379404
summary: results.reduce<Record<string, number>>((acc, r) => {
@@ -384,15 +409,15 @@ export function makeCheckDomainsCommand(): Command {
384409
generatedAt: new Date().toISOString(),
385410
total: results.length,
386411
};
387-
fs.writeFileSync(opts.out, yaml.dump(output), "utf8");
388-
console.log(`Wrote ${results.length} results to ${opts.out}`);
412+
fs.writeFileSync(outFile, yaml.dump(output), "utf8");
413+
console.log(`Wrote ${results.length} results to ${outFile}`);
389414
} else {
390415
writeResultsCsv({
391-
outFile: opts.out,
416+
outFile,
392417
results,
393418
writeHeader: opts.header,
394419
});
395-
console.log(`Wrote ${results.length} rows to ${opts.out}`);
420+
console.log(`Wrote ${results.length} rows to ${outFile}`);
396421
}
397422

398423
const summary = results.reduce<Record<string, number>>((acc, r) => {

js/commands/domain-availability-checker/domains-to-test.txt

Lines changed: 0 additions & 3 deletions
This file was deleted.

js/commands/domain-availability-checker/domains-to-test.yaml

Lines changed: 0 additions & 12 deletions
This file was deleted.

js/domains-to-test-example.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# osa/scripts/domain-input.yaml
2+
3+
names:
4+
- swift-brown-fox
5+
- clever-red-hedgehog
6+
- bright-yellow-canary
7+
- silent-white-owl
8+
- agile-panda
9+
- mightyblue
10+
- frog
11+
12+
domainProviders:
13+
- whois # your existing WHOIS/RDAP checker
14+
- domainr # if you add API support later
15+
- cloudflare # ideal if you want authoritative registrar results
16+
17+
tlds:
18+
- dev
19+
- com
20+
- net
21+
- ai
22+
- io

js/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,5 @@
4444
"publishConfig": {
4545
"access": "public"
4646
},
47-
"sideEffects": false,
48-
"packageManager": "yarn@4.10.3"
47+
"sideEffects": false
4948
}

js/test-output.json

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
{
2+
"summary": {
3+
"registered": 12,
4+
"likely-available": 6,
5+
"unknown": 2
6+
},
7+
"results": [
8+
{
9+
"domain": "virtualize.com",
10+
"name": "virtualize",
11+
"tld": "com",
12+
"status": "registered",
13+
"elapsedMs": 84,
14+
"parsedSummary": "domainName:VIRTUALIZE.COM | registrar:DYNADOT LLC | creationDate:2002-08-25T18:05:14.0Z | updatedDate:2025-08-03T22:14:12.0Z",
15+
"rawSnippet": ""
16+
},
17+
{
18+
"domain": "virtualize.dev",
19+
"name": "virtualize",
20+
"tld": "dev",
21+
"status": "likely-available",
22+
"elapsedMs": 3,
23+
"parsedSummary": "",
24+
"rawSnippet": "Error: getaddrinfo ENOTFOUND whois.nic.google"
25+
},
26+
{
27+
"domain": "virtualize.io",
28+
"name": "virtualize",
29+
"tld": "io",
30+
"status": "registered",
31+
"elapsedMs": 101,
32+
"parsedSummary": "domainName:VIRTUALIZE.IO | registrar:DYNADOT LLC | creationDate:2013-07-09T16:04:31.0Z | updatedDate:2025-09-05T21:54:58.0Z",
33+
"rawSnippet": ""
34+
},
35+
{
36+
"domain": "virtualize.net",
37+
"name": "virtualize",
38+
"tld": "net",
39+
"status": "registered",
40+
"elapsedMs": 2079,
41+
"parsedSummary": "domainName:virtualize.net | registrar:NAMECHEAP INC | creationDate:1997-11-26T05:00:00.00Z | updatedDate:2024-02-03T05:06:33.00Z",
42+
"rawSnippet": ""
43+
},
44+
{
45+
"domain": "virtualize.ai",
46+
"name": "virtualize",
47+
"tld": "ai",
48+
"status": "registered",
49+
"elapsedMs": 127,
50+
"parsedSummary": "domainName:virtualize.ai | registrar:GoDaddy.com, LLC | creationDate:2020-06-14T23:47:33Z | updatedDate:2025-01-21T15:27:50Z",
51+
"rawSnippet": ""
52+
},
53+
{
54+
"domain": "vize.com",
55+
"name": "vize",
56+
"tld": "com",
57+
"status": "registered",
58+
"elapsedMs": 381,
59+
"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",
60+
"rawSnippet": ""
61+
},
62+
{
63+
"domain": "vize.dev",
64+
"name": "vize",
65+
"tld": "dev",
66+
"status": "likely-available",
67+
"elapsedMs": 17,
68+
"parsedSummary": "",
69+
"rawSnippet": "Error: getaddrinfo ENOTFOUND whois.nic.google"
70+
},
71+
{
72+
"domain": "vize.io",
73+
"name": "vize",
74+
"tld": "io",
75+
"status": "likely-available",
76+
"elapsedMs": 43,
77+
"parsedSummary": "",
78+
"rawSnippet": "Error: read ECONNRESET"
79+
},
80+
{
81+
"domain": "vize.net",
82+
"name": "vize",
83+
"tld": "net",
84+
"status": "registered",
85+
"elapsedMs": 1227,
86+
"parsedSummary": "domainName:VIZE.NET | registrar:NICS Telekomunikasyon A.S. | creationDate:2006-03-19T19:07:10Z | updatedDate:2025-03-21T18:57:34Z",
87+
"rawSnippet": ""
88+
},
89+
{
90+
"domain": "vize.ai",
91+
"name": "vize",
92+
"tld": "ai",
93+
"status": "likely-available",
94+
"elapsedMs": 26,
95+
"parsedSummary": "",
96+
"rawSnippet": "Error: read ECONNRESET"
97+
},
98+
{
99+
"domain": "vldev.com",
100+
"name": "vldev",
101+
"tld": "com",
102+
"status": "registered",
103+
"elapsedMs": 176,
104+
"parsedSummary": "domainName:VLDEV.COM | registrar:TurnCommerce, Inc. DBA NameBright.com | creationDate:2018-08-29T18:30:24.000Z | updatedDate:2020-08-23T07:17:39.321Z",
105+
"rawSnippet": ""
106+
},
107+
{
108+
"domain": "vldev.dev",
109+
"name": "vldev",
110+
"tld": "dev",
111+
"status": "likely-available",
112+
"elapsedMs": 2,
113+
"parsedSummary": "",
114+
"rawSnippet": "Error: getaddrinfo ENOTFOUND whois.nic.google"
115+
},
116+
{
117+
"domain": "vldev.io",
118+
"name": "vldev",
119+
"tld": "io",
120+
"status": "unknown",
121+
"elapsedMs": 50,
122+
"parsedSummary": "",
123+
"rawSnippet": ""
124+
},
125+
{
126+
"domain": "vldev.net",
127+
"name": "vldev",
128+
"tld": "net",
129+
"status": "registered",
130+
"elapsedMs": 440,
131+
"parsedSummary": "domainName:VLDEV.NET | registrar:TUCOWS DOMAINS, INC. | creationDate:2017-09-26T07:21:17 | updatedDate:2025-06-02T00:04:12",
132+
"rawSnippet": ""
133+
},
134+
{
135+
"domain": "vldev.ai",
136+
"name": "vldev",
137+
"tld": "ai",
138+
"status": "unknown",
139+
"elapsedMs": 49,
140+
"parsedSummary": "",
141+
"rawSnippet": ""
142+
},
143+
{
144+
"domain": "testdomain.com",
145+
"name": "testdomain",
146+
"tld": "com",
147+
"status": "registered",
148+
"elapsedMs": 95,
149+
"parsedSummary": "domainName:testdomain.com | registrar:GoDaddy.com, LLC | creationDate:2002-05-28T13:22:16Z | updatedDate:2025-05-08T15:30:07Z",
150+
"rawSnippet": ""
151+
},
152+
{
153+
"domain": "testdomain.dev",
154+
"name": "testdomain",
155+
"tld": "dev",
156+
"status": "likely-available",
157+
"elapsedMs": 1,
158+
"parsedSummary": "",
159+
"rawSnippet": "Error: getaddrinfo ENOTFOUND whois.nic.google"
160+
},
161+
{
162+
"domain": "testdomain.io",
163+
"name": "testdomain",
164+
"tld": "io",
165+
"status": "registered",
166+
"elapsedMs": 438,
167+
"parsedSummary": "domainName:testdomain.io | registrar:GANDI SAS | creationDate:2015-11-25T03:26:32Z | updatedDate:2025-10-21T04:27:42Z",
168+
"rawSnippet": ""
169+
},
170+
{
171+
"domain": "testdomain.net",
172+
"name": "testdomain",
173+
"tld": "net",
174+
"status": "registered",
175+
"elapsedMs": 445,
176+
"parsedSummary": "domainName:testdomain.net | registrar:Key-Systems GmbH | creationDate:2018-08-09T18:08:03Z | updatedDate:2025-08-10T07:28:00Z",
177+
"rawSnippet": ""
178+
},
179+
{
180+
"domain": "testdomain.ai",
181+
"name": "testdomain",
182+
"tld": "ai",
183+
"status": "registered",
184+
"elapsedMs": 154,
185+
"parsedSummary": "domainName:testdomain.ai | registrar:One.com A/S | creationDate:2025-03-14T12:02:50Z | updatedDate:2025-03-19T12:03:27Z",
186+
"rawSnippet": ""
187+
}
188+
],
189+
"generatedAt": "2025-11-09T07:31:13.914Z",
190+
"total": 20
191+
}

0 commit comments

Comments
 (0)