Sign In

@moshcoder/moshpit-name

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@moshcoder/moshpit-name - npm Package Compare versions

Comparing version
0.4.0
to
0.5.0
+195
-32
bin/moshpit-name.mjs

@@ -8,2 +8,4 @@ #!/usr/bin/env node

import { writeFileSync } from "node:fs";
import {

@@ -16,5 +18,7 @@ CHILD_PRICE_USD, ENDING_PRICE_USD, MAX_BULK_TLDS, RESERVED_TLDS,

moshpit-name check <ending> [--json] can this ending 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]
moshpit-name check (<ending...> | -) [--json | --ndjson]
can endings be claimed; - reads one per line
moshpit-name parse (<name...> | -) [--json | --ndjson]
split names into labels; - reads one per line
moshpit-name list [-] [--limit N] [--json | --ndjson]
parse up to N pasted entries; - reads stdin

@@ -27,3 +31,4 @@ moshpit-name reserved [--json] list endings that cannot be claimed

const [sub, ...rawRest] = process.argv.slice(2);
const json = rawRest.includes("--json");
let json = false;
let ndjson = false;
const rest = [];

@@ -33,6 +38,27 @@ let limit = MAX_BULK_TLDS;

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 === "--ndjson") {
if (!["check", "parse", "list"].includes(sub)) {
optionError ??= 'unknown option "--ndjson"';
} else {
ndjson = true;
}
continue;
}
if (parsingOptions && arg === "--limit") {
if (sub !== "list") {
optionError ??= 'unknown option "--limit"';
continue;
}
limitFlags++;

@@ -46,6 +72,23 @@ const candidate = rawRest[index + 1];

}
if (parsingOptions && arg.startsWith("--")) {
optionError ??= `unknown option "${arg}"`;
continue;
}
rest.push(arg);
}
if (json && ndjson) optionError ??= "--json and --ndjson cannot be used together";
const out = console.log;
const outJson = (value) => out(JSON.stringify(value, null, 2));
const writeStdout = (value) => {
try {
writeFileSync(1, value);
} catch (error) {
if (error?.code === "EPIPE") process.exit(0);
throw error;
}
};
const outJson = (value) => writeStdout(`${JSON.stringify(value, null, 2)}\n`);
const outNdjson = (values) => {
if (values.length) writeStdout(`${values.map((value) => JSON.stringify(value)).join("\n")}\n`);
};
const MAX_STDIN_BYTES = 1024 * 1024;

@@ -58,2 +101,56 @@ const readStdin = async () => {

const readCommandStdin = async () => {
const chunks = [];
let bytes = 0;
for await (const chunk of process.stdin) {
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
bytes += data.length;
if (bytes > MAX_STDIN_BYTES) {
return { lines: [], error: "stdin accepts at most 1 MiB" };
}
chunks.push(data);
}
const lines = [];
for (const line of Buffer.concat(chunks).toString("utf8").split(/\r?\n/)) {
if (!line.trim()) continue;
if (lines.length === MAX_BULK_TLDS) {
return {
lines: [],
error: `stdin accepts at most ${MAX_BULK_TLDS} non-empty lines`,
};
}
lines.push(line);
}
return { lines, error: null };
};
const commandInputs = async (args) => {
const fromStdin = args.length === 1 && args[0] === "-";
if (!fromStdin && args.includes("-")) {
return {
inputs: [],
fromStdin: false,
error: '"-" must be the only input when reading from stdin',
};
}
if (!fromStdin) {
return { inputs: args.length ? args : [undefined], fromStdin, error: null };
}
const { lines, error } = await readCommandStdin();
return {
inputs: lines.length ? lines : [undefined],
fromStdin,
error,
};
};
const exitInputError = (error) => {
if (ndjson) outNdjson([{ error }]);
else if (json) outJson({ error });
else console.error(`moshpit-name: ${error}`);
process.exit(1);
};
if (!sub || sub === "help" || sub === "--help") {

@@ -64,31 +161,82 @@ out(USAGE);

if (optionError) {
if (ndjson) outNdjson([{ error: optionError }]);
else 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, fromStdin, error } = await commandInputs(rest);
if (error) exitInputError(error);
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 (ndjson) outNdjson(results);
else if (json) {
if (!fromStdin && 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") {
const input = rest[0];
const parsed = parseMoshpitName(input);
if (!parsed) {
// The two ways this fails are worth telling apart: too many labels, and a
// pair of numbers that reads as an IPv4 literal.
const reason = "not a Moshpit name (one label and one ending; both numeric reads as an address)";
if (json) outJson({ input: input ?? null, valid: false, label: null, tld: null, reason });
else out(`${input ?? ""} — ${reason}`);
process.exit(1);
const { inputs, fromStdin, error } = await commandInputs(rest);
if (error) exitInputError(error);
const reason = "not a Moshpit name (one label and one ending; both numeric reads as an address)";
const results = inputs.map((input) => {
const parsed = parseMoshpitName(input);
if (!parsed) {
// The two ways this fails are worth telling apart: too many labels, and
// a pair of numbers that reads as an IPv4 literal.
return { input: input ?? null, valid: false, label: null, tld: null, reason };
}
return { input, valid: true, ...parsed, reason: null };
});
if (ndjson) outNdjson(results);
else if (json) {
if (!fromStdin && results.length === 1) outJson(results[0]);
else {
const validCount = results.filter((result) => result.valid).length;
outJson({
count: results.length,
validCount,
invalidCount: results.length - validCount,
results,
});
}
} else {
for (const result of results) {
out(result.valid
? `${result.label}.${result.tld} label=${result.label} ending=${result.tld}`
: `${result.input ?? ""} — ${result.reason}`);
}
}
if (json) outJson({ input, valid: true, ...parsed, reason: null });
else out(`${parsed.label}.${parsed.tld} label=${parsed.label} ending=${parsed.tld}`);
process.exit(0);
process.exit(results.some((result) => !result.valid) ? 1 : 0);
}

@@ -107,3 +255,4 @@

const error = `--limit must be an integer from 1 to ${MAX_BULK_TLDS}`;
if (json) outJson({ error });
if (ndjson) outNdjson([{ error }]);
else if (json) outJson({ error });
else console.error(`moshpit-name: ${error}`);

@@ -114,4 +263,18 @@ process.exit(1);

const input = rest[0] === "-" || !rest.length ? await readStdin() : rest.join("\n");
if (rest.includes("-") && !(rest.length === 1 && rest[0] === "-")) {
exitInputError('"-" must be the only input when reading from stdin');
}
let input;
if (rest[0] === "-" || !rest.length) {
const stdin = await readCommandStdin();
if (stdin.error) exitInputError(stdin.error);
input = stdin.lines.join("\n");
} else {
input = rest.join("\n");
}
const parsed = parseTldList(input, limit);
if (ndjson) {
outNdjson(parsed.entries);
process.exit(0);
}
if (json) {

@@ -118,0 +281,0 @@ outJson(parsed);

+1
-1
{
"name": "@moshcoder/moshpit-name",
"version": "0.4.0",
"version": "0.5.0",
"type": "module",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -37,5 +37,7 @@ # @moshcoder/moshpit-name

```sh
moshpit-name check <ending> [--json] can this ending 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]
moshpit-name check (<ending...> | -) [--json | --ndjson]
can endings be claimed; - reads one per line
moshpit-name parse (<name...> | -) [--json | --ndjson]
split names into labels; - reads one per line
moshpit-name list [-] [--limit N] [--json | --ndjson]
parse up to N pasted entries; - reads stdin

@@ -50,2 +52,23 @@ moshpit-name reserved [--json] list endings that cannot be claimed

$ 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

@@ -90,2 +113,24 @@ 1.420 — not a Moshpit name (one label and one ending; both numeric reads as an address)

For compatibility, `check <ending> --json` and `parse <name> --json` return
their established bare result objects. Passing two or more inputs returns a
batch wrapper with counts and a `results` array; results stay in input order and
any rejected input makes the command exit non-zero.
Pass `-` as the only input to `check` or `parse` to read up to 1000 non-empty
lines from stdin. Stdin JSON always uses the batch wrapper, even for one line,
so a pipeline receives a stable shape. More than 1000 lines is an error. An
empty stream is treated like a missing input and exits non-zero.
Use `--ndjson` with `check`, `parse`, or `list` when each result should be a
compact JSON object on its own line. This keeps input order, writes even a
single result as one record, and preserves the command's normal exit status.
For `list`, the records are the parsed entries; an empty list produces no
records. `--json` and `--ndjson` cannot be combined.
```sh
$ printf '.eggs\n.bank\n' | moshpit-name check - --ndjson
{"input":".eggs","tld":"eggs","claimable":true,"reason":null}
{"input":".bank","tld":"bank","claimable":false,"reason":"that name is reserved"}
```
`list --limit N` stops after `N` unique entries and reports the remainder in

@@ -92,0 +137,0 @@ `skipped`. `N` must be an integer from 1 through 1000, the package's bulk