Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ catch drift between two copies is a good sign the copies should be one thing.
## CLI

```sh
moshpit-name check <ending> [--json] can this ending be claimed, and if not why
moshpit-name check <ending...> [--json]
can these endings be claimed, and if not why
moshpit-name parse <name> [--json] split a name into its label and ending
moshpit-name list [-] [--limit N] [--json]
parse up to N pasted entries; - reads stdin
Expand All @@ -46,6 +47,27 @@ moshpit-name prices [--json] what an ending and a name cost
$ moshpit-name check .420
.420 — claimable

$ moshpit-name check .eggs .bank --json
{
"count": 2,
"claimableCount": 1,
"rejectedCount": 1,
"results": [
{
"input": ".eggs",
"tld": "eggs",
"claimable": true,
"reason": null
},
{
"input": ".bank",
"tld": "bank",
"claimable": false,
"reason": "that name is reserved"
}
]
}

$ moshpit-name parse 1.420
1.420 — not a Moshpit name (one label and one ending; both numeric reads as an address)

Expand Down Expand Up @@ -85,6 +107,10 @@ $ moshpit-name check .420 --json
}
```

For compatibility, `check <ending> --json` returns the established bare result
object shown above. Passing two or more endings returns the batch wrapper with
counts and a `results` array.

`list --limit N` stops after `N` unique entries and reports the remainder in
`skipped`. `N` must be an integer from 1 through 1000, the package's bulk
ceiling. The option works with inline arguments or stdin and can appear before
Expand Down
76 changes: 61 additions & 15 deletions bin/moshpit-name.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {

const USAGE = `moshpit-name — the Moshpit namespace rules

moshpit-name check <ending> [--json] can this ending be claimed, and if not why
moshpit-name check <ending...> [--json]
can these endings be claimed, and if not why
moshpit-name parse <name> [--json] split a name into its label and ending
moshpit-name list [-] [--limit N] [--json]
parse up to N pasted entries; - reads stdin
Expand All @@ -22,15 +23,28 @@ const USAGE = `moshpit-name — the Moshpit namespace rules
Pure rules, no network. The same answers the registry gives, without asking it.`;

const [sub, ...rawRest] = process.argv.slice(2);
const json = rawRest.includes("--json");
let json = false;
const rest = [];
let limit = MAX_BULK_TLDS;
let limitValue;
let limitFlags = 0;
let optionError = null;
let parsingOptions = true;
for (let index = 0; index < rawRest.length; index++) {
const arg = rawRest[index];
if (arg === "--json") continue;
if (arg === "--limit") {
if (parsingOptions && arg === "--") {
parsingOptions = false;
continue;
}
if (parsingOptions && arg === "--json") {
json = true;
continue;
}
if (parsingOptions && arg === "--limit") {
if (sub !== "list") {
optionError ??= 'unknown option "--limit"';
continue;
}
limitFlags++;
const candidate = rawRest[index + 1];
if (candidate !== undefined && !candidate.startsWith("--")) {
Expand All @@ -39,6 +53,10 @@ for (let index = 0; index < rawRest.length; index++) {
}
continue;
}
if (parsingOptions && arg.startsWith("--")) {
optionError ??= `unknown option "${arg}"`;
continue;
}
rest.push(arg);
}
const out = console.log;
Expand All @@ -55,19 +73,47 @@ if (!sub || sub === "help" || sub === "--help") {
process.exit(0);
}

if (optionError) {
if (json) outJson({ error: optionError });
else console.error(`moshpit-name: ${optionError}`);
process.exit(1);
}

if (sub === "check") {
const raw = rest[0];
const tld = normalizeTld(raw);
if (!tld) {
const reason = "not a valid ending (letters, digits and dashes only, no dots)";
if (json) outJson({ input: raw ?? null, tld: null, claimable: false, reason });
else out(`.${raw ?? ""} — ${reason}`);
process.exit(1);
const inputs = rest.length ? rest : [undefined];
const results = inputs.map((input) => {
const tld = normalizeTld(input);
if (!tld) {
return {
input: input ?? null,
tld: null,
claimable: false,
reason: "not a valid ending (letters, digits and dashes only, no dots)",
};
}
const reason = tldRejection(tld);
return { input, tld, claimable: !reason, reason };
});

if (json) {
if (results.length === 1) outJson(results[0]);
else {
const claimableCount = results.filter((result) => result.claimable).length;
outJson({
count: results.length,
claimableCount,
rejectedCount: results.length - claimableCount,
results,
});
}
} else {
for (const result of results) {
out(result.claimable
? `.${result.tld} — claimable`
: `.${result.tld ?? result.input ?? ""} — ${result.reason}`);
}
}
const why = tldRejection(tld);
if (json) outJson({ input: raw, tld, claimable: !why, reason: why });
else out(why ? `.${tld} — ${why}` : `.${tld} — claimable`);
process.exit(why ? 1 : 0);
process.exit(results.some((result) => !result.claimable) ? 1 : 0);
}

if (sub === "parse") {
Expand Down
52 changes: 52 additions & 0 deletions test/cli.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,58 @@ test("check --json describes claimable, reserved, and malformed endings", () =>
});
});

test("check validates multiple endings in one invocation", () => {
const result = run(["check", ".420", ".bank", "two.labels", "--json"]);

assert.equal(result.status, 1);
assert.deepEqual(output(result), {
count: 3,
claimableCount: 1,
rejectedCount: 2,
results: [
{ input: ".420", tld: "420", claimable: true, reason: null },
{ input: ".bank", tld: "bank", claimable: false, reason: "that name is reserved" },
{
input: "two.labels",
tld: null,
claimable: false,
reason: "not a valid ending (letters, digits and dashes only, no dots)",
},
],
});

const human = run(["check", "eggs", ".bank", "two.labels"]);
assert.equal(human.status, 1);
assert.equal(human.stderr, "");
assert.equal(human.stdout,
".eggs — claimable\n"
+ ".bank — that name is reserved\n"
+ ".two.labels — not a valid ending (letters, digits and dashes only, no dots)\n");

const empty = run(["check"]);
assert.equal(empty.status, 1);
assert.equal(empty.stderr, "");
assert.equal(empty.stdout,
". — not a valid ending (letters, digits and dashes only, no dots)\n");
});

test("check rejects unknown options and supports an end-of-options separator", () => {
const limit = run(["check", ".eggs", "--limit", ".bank"]);
assert.equal(limit.status, 1);
assert.equal(limit.stdout, "");
assert.equal(limit.stderr, 'moshpit-name: unknown option "--limit"\n');

const typo = run(["check", ".eggs", "--jsn", "--json"]);
assert.equal(typo.status, 1);
assert.deepEqual(output(typo), { error: 'unknown option "--jsn"' });

const literal = run(["check", "--", "--jsn"]);
assert.equal(literal.status, 1);
assert.equal(literal.stderr, "");
assert.equal(literal.stdout,
".--jsn — not a valid ending (letters, digits and dashes only, no dots)\n");
});

test("parse --json returns normalized fields and structured invalid output", () => {
const valid = run(["parse", " Blue.EGGS ", "--json"]);
assert.equal(valid.status, 0);
Expand Down
Loading