formatarc 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -3
- package/dist/cli.js +10 -4
- package/dist/converter.d.ts +1 -1
- package/dist/converter.js +122 -3
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
# formatarc
|
|
2
2
|
|
|
3
|
-
Convert JSON, YAML, and
|
|
3
|
+
Convert JSON, YAML, CSV, Markdown, and HTML from the terminal — your data never leaves your machine. No config, no telemetry, no upload.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/formatarc)
|
|
6
|
+
[](https://github.com/m-naoki-m/formatarc/blob/main/LICENSE)
|
|
7
|
+
[](https://www.npmjs.com/package/formatarc)
|
|
8
|
+
|
|
9
|
+
**Web version → [formatarc.com](https://formatarc.com)** — the same conversions, 100% in your browser, no signup.
|
|
10
|
+
|
|
11
|
+
## Why formatarc?
|
|
12
|
+
|
|
13
|
+
Most "online JSON / YAML / CSV converters" send the data you paste to a server. In November 2025, security firm watchTowr disclosed that two popular formatter sites (jsonformatter.org and codebeautify.org) had publicly exposed **over 80,000 saved submissions (5 GB+)** through a predictable "Recent Links" URL — including Active Directory credentials, cloud access keys, private keys, CI/CD secrets, JWTs, and full AWS Secrets Manager exports from government, banking, healthcare, and aerospace organizations. ([watchTowr report](https://labs.watchtowr.com/stop-putting-your-passwords-into-random-websites-yes-seriously-you-are-the-problem/))
|
|
14
|
+
|
|
15
|
+
formatarc is built so that can't happen. The CLI runs entirely on your machine, and the [web version](https://formatarc.com) runs entirely in your browser — no upload, no logging, no telemetry, no account.
|
|
16
|
+
|
|
17
|
+
It is not only paste-to-a-server sites, either: in early 2026, a widely used JSON formatter browser extension was reported (on Hacker News and dev.to) to add third-party tracking and checkout-page injection after moving to a closed-source model — a reminder that an installed extension can change behaviour through an automatic update. Background on both risks: [are online converters safe?](https://formatarc.com/en/blog/online-converter-safety/) and [picking a JSON formatter Chrome extension by privacy and permissions](https://formatarc.com/en/blog/chrome-extension-json-formatter/).
|
|
18
|
+
|
|
19
|
+
| | formatarc CLI | formatarc web | Typical online converter | jq / yq / pandoc |
|
|
20
|
+
|---|:---:|:---:|:---:|:---:|
|
|
21
|
+
| Data stays local | ✅ | ✅ | ❌ | ✅ |
|
|
22
|
+
| No signup / no upload | ✅ | ✅ | ⚠️ | ✅ |
|
|
23
|
+
| JSON + YAML + CSV + Markdown + HTML in one tool | ✅ | ✅ | ⚠️ | ❌ |
|
|
24
|
+
| Works offline | ✅ | after first load | ❌ | ✅ |
|
|
25
|
+
| Single install | ✅ | n/a | n/a | ❌ |
|
|
6
26
|
|
|
7
27
|
## Install
|
|
8
28
|
|
|
@@ -31,6 +51,9 @@ cat file | formatarc <tool>
|
|
|
31
51
|
| `yaml-to-json` | Convert YAML to JSON |
|
|
32
52
|
| `json-to-yaml` | Convert JSON to YAML |
|
|
33
53
|
| `csv-to-json` | Convert CSV (with header row) to JSON |
|
|
54
|
+
| `csv-to-markdown` | Convert CSV to a Markdown (GFM) table |
|
|
55
|
+
| `markdown-to-html` | Convert Markdown to HTML |
|
|
56
|
+
| `html-to-markdown` | Convert HTML to Markdown |
|
|
34
57
|
|
|
35
58
|
### Examples
|
|
36
59
|
|
|
@@ -69,6 +92,24 @@ Convert CSV from stdin:
|
|
|
69
92
|
cat users.csv | formatarc csv-to-json
|
|
70
93
|
```
|
|
71
94
|
|
|
95
|
+
Convert CSV to a Markdown table:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
cat users.csv | formatarc csv-to-markdown
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Render Markdown as HTML:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
cat README.md | formatarc markdown-to-html > README.html
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Strip HTML to Markdown (handy for piping web pages into LLMs):
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
curl -s https://example.com | formatarc html-to-markdown
|
|
111
|
+
```
|
|
112
|
+
|
|
72
113
|
## Programmatic API
|
|
73
114
|
|
|
74
115
|
```typescript
|
|
@@ -95,8 +136,11 @@ For a browser-based experience with no signup and no data upload:
|
|
|
95
136
|
**[formatarc.com](https://formatarc.com)**
|
|
96
137
|
|
|
97
138
|
- JSON Formatter, YAML ↔ JSON, CSV → JSON
|
|
139
|
+
- CSV → Markdown, Markdown ↔ HTML
|
|
98
140
|
- Runs entirely in the browser
|
|
99
|
-
- Multilingual (English
|
|
141
|
+
- Multilingual (English, Japanese, Spanish, Portuguese)
|
|
142
|
+
|
|
143
|
+
There is also a [Chrome extension](https://formatarc.com/en/blog/chrome-extension-json-formatter/) for popup and right-click conversion — same browser-side processing, no upload.
|
|
100
144
|
|
|
101
145
|
## License
|
|
102
146
|
|
package/dist/cli.js
CHANGED
|
@@ -10,15 +10,21 @@ Usage:
|
|
|
10
10
|
cat file | formatarc <tool>
|
|
11
11
|
|
|
12
12
|
Tools:
|
|
13
|
-
json-format
|
|
14
|
-
yaml-to-json
|
|
15
|
-
json-to-yaml
|
|
16
|
-
csv-to-json
|
|
13
|
+
json-format Pretty-print JSON
|
|
14
|
+
yaml-to-json Convert YAML to JSON
|
|
15
|
+
json-to-yaml Convert JSON to YAML
|
|
16
|
+
csv-to-json Convert CSV to JSON
|
|
17
|
+
csv-to-markdown Convert CSV to a Markdown table
|
|
18
|
+
markdown-to-html Convert Markdown to HTML
|
|
19
|
+
html-to-markdown Convert HTML to Markdown
|
|
17
20
|
|
|
18
21
|
Examples:
|
|
19
22
|
formatarc json-format '{"a":1}'
|
|
20
23
|
formatarc yaml-to-json config.yaml
|
|
21
24
|
cat data.csv | formatarc csv-to-json
|
|
25
|
+
cat table.csv | formatarc csv-to-markdown
|
|
26
|
+
cat README.md | formatarc markdown-to-html
|
|
27
|
+
cat page.html | formatarc html-to-markdown
|
|
22
28
|
|
|
23
29
|
Web version: https://formatarc.com
|
|
24
30
|
`.trim();
|
package/dist/converter.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ export type ConvertResult = {
|
|
|
2
2
|
output: string;
|
|
3
3
|
error: string;
|
|
4
4
|
};
|
|
5
|
-
export type Tool = "json-format" | "yaml-to-json" | "json-to-yaml" | "csv-to-json";
|
|
5
|
+
export type Tool = "json-format" | "yaml-to-json" | "json-to-yaml" | "csv-to-json" | "csv-to-markdown" | "markdown-to-html" | "html-to-markdown";
|
|
6
6
|
export declare function isValidTool(value: string): value is Tool;
|
|
7
7
|
export declare function convert(tool: Tool, input: string): ConvertResult;
|
package/dist/converter.js
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import Papa from "papaparse";
|
|
2
2
|
import YAML from "yaml";
|
|
3
|
-
|
|
3
|
+
import { marked } from "marked";
|
|
4
|
+
import TurndownService from "turndown";
|
|
5
|
+
const TOOLS = [
|
|
6
|
+
"json-format",
|
|
7
|
+
"yaml-to-json",
|
|
8
|
+
"json-to-yaml",
|
|
9
|
+
"csv-to-json",
|
|
10
|
+
"csv-to-markdown",
|
|
11
|
+
"markdown-to-html",
|
|
12
|
+
"html-to-markdown",
|
|
13
|
+
];
|
|
4
14
|
export function isValidTool(value) {
|
|
5
15
|
return TOOLS.includes(value);
|
|
6
16
|
}
|
|
@@ -26,7 +36,19 @@ export function convert(tool, input) {
|
|
|
26
36
|
error: "",
|
|
27
37
|
};
|
|
28
38
|
case "csv-to-json":
|
|
29
|
-
return
|
|
39
|
+
return convertCsvToJson(input);
|
|
40
|
+
case "csv-to-markdown":
|
|
41
|
+
return convertCsvToMarkdown(input);
|
|
42
|
+
case "markdown-to-html":
|
|
43
|
+
return {
|
|
44
|
+
output: marked.parse(input, { async: false, gfm: true, breaks: false }),
|
|
45
|
+
error: "",
|
|
46
|
+
};
|
|
47
|
+
case "html-to-markdown":
|
|
48
|
+
return {
|
|
49
|
+
output: turndownService.turndown(input),
|
|
50
|
+
error: "",
|
|
51
|
+
};
|
|
30
52
|
default:
|
|
31
53
|
return { output: "", error: `Unknown tool: ${tool}` };
|
|
32
54
|
}
|
|
@@ -35,7 +57,7 @@ export function convert(tool, input) {
|
|
|
35
57
|
return { output: "", error: formatError(err, input, tool) };
|
|
36
58
|
}
|
|
37
59
|
}
|
|
38
|
-
function
|
|
60
|
+
function convertCsvToJson(input) {
|
|
39
61
|
const parsed = Papa.parse(input, {
|
|
40
62
|
header: true,
|
|
41
63
|
skipEmptyLines: "greedy",
|
|
@@ -54,12 +76,103 @@ function convertCsv(input) {
|
|
|
54
76
|
error: "",
|
|
55
77
|
};
|
|
56
78
|
}
|
|
79
|
+
function renderMarkdownTable(rows) {
|
|
80
|
+
const columnCount = rows[0].length;
|
|
81
|
+
const escapeCell = (value) => value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
|
|
82
|
+
const normalizeRow = (row) => {
|
|
83
|
+
const trimmed = row.length > columnCount ? row.slice(0, columnCount) : row;
|
|
84
|
+
const padded = trimmed.length === columnCount
|
|
85
|
+
? trimmed
|
|
86
|
+
: [...trimmed, ...Array(columnCount - trimmed.length).fill("")];
|
|
87
|
+
return padded.map(escapeCell);
|
|
88
|
+
};
|
|
89
|
+
const renderRow = (cells) => `| ${cells.join(" | ")} |`;
|
|
90
|
+
const header = normalizeRow(rows[0]);
|
|
91
|
+
const separator = Array(columnCount).fill("---");
|
|
92
|
+
const body = rows.slice(1).map(normalizeRow);
|
|
93
|
+
return [renderRow(header), renderRow(separator), ...body.map(renderRow)].join("\n");
|
|
94
|
+
}
|
|
95
|
+
function convertCsvToMarkdown(input) {
|
|
96
|
+
const parsed = Papa.parse(input, {
|
|
97
|
+
header: false,
|
|
98
|
+
skipEmptyLines: "greedy",
|
|
99
|
+
});
|
|
100
|
+
if (parsed.errors.length > 0) {
|
|
101
|
+
const first = parsed.errors[0];
|
|
102
|
+
return { output: "", error: `CSV parse error: ${first.message}` };
|
|
103
|
+
}
|
|
104
|
+
const rows = parsed.data.filter((row) => row.length > 0);
|
|
105
|
+
if (rows.length === 0) {
|
|
106
|
+
return { output: "", error: "CSV is empty." };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
output: renderMarkdownTable(rows) + "\n",
|
|
110
|
+
error: "",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const turndownService = (() => {
|
|
114
|
+
const service = new TurndownService({
|
|
115
|
+
headingStyle: "atx",
|
|
116
|
+
codeBlockStyle: "fenced",
|
|
117
|
+
bulletListMarker: "-",
|
|
118
|
+
emDelimiter: "_",
|
|
119
|
+
});
|
|
120
|
+
service.addRule("table", {
|
|
121
|
+
filter: "table",
|
|
122
|
+
replacement: (_content, node) => {
|
|
123
|
+
const rows = Array.from(node.querySelectorAll("tr")).map((row) => Array.from(row.querySelectorAll("th, td")).map((cell) => cell.textContent ?? ""));
|
|
124
|
+
if (rows.length === 0)
|
|
125
|
+
return "";
|
|
126
|
+
return `\n\n${renderMarkdownTable(rows)}\n\n`;
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return service;
|
|
130
|
+
})();
|
|
131
|
+
// Locate a trailing comma (a comma whose next non-whitespace character is `}`
|
|
132
|
+
// or `]`). String-aware so commas/brackets inside string values are ignored.
|
|
133
|
+
// Returns the comma's 1-based line, or null if there is none.
|
|
134
|
+
function findTrailingCommaLine(input) {
|
|
135
|
+
let inString = false;
|
|
136
|
+
let escape = false;
|
|
137
|
+
for (let i = 0; i < input.length; i++) {
|
|
138
|
+
const ch = input[i];
|
|
139
|
+
if (inString) {
|
|
140
|
+
if (escape)
|
|
141
|
+
escape = false;
|
|
142
|
+
else if (ch === "\\")
|
|
143
|
+
escape = true;
|
|
144
|
+
else if (ch === '"')
|
|
145
|
+
inString = false;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (ch === '"') {
|
|
149
|
+
inString = true;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (ch === ",") {
|
|
153
|
+
let j = i + 1;
|
|
154
|
+
while (j < input.length && /\s/.test(input[j]))
|
|
155
|
+
j++;
|
|
156
|
+
if (j < input.length && (input[j] === "}" || input[j] === "]")) {
|
|
157
|
+
return input.slice(0, i + 1).split("\n").length;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
57
163
|
function formatError(err, input, tool) {
|
|
58
164
|
if (err instanceof YAML.YAMLParseError) {
|
|
59
165
|
const line = err.linePos?.[0]?.line;
|
|
60
166
|
return line ? `YAML syntax error near line ${line}.` : "YAML syntax error.";
|
|
61
167
|
}
|
|
62
168
|
if (err instanceof Error && err.name === "SyntaxError") {
|
|
169
|
+
// Trailing comma is the most common JSON mistake, and parsers report it at
|
|
170
|
+
// the *following* bracket (often on the next line), not at the comma. Find
|
|
171
|
+
// the comma ourselves and point at its line.
|
|
172
|
+
const trailingCommaLine = findTrailingCommaLine(input);
|
|
173
|
+
if (trailingCommaLine !== null) {
|
|
174
|
+
return `Invalid JSON: remove the trailing comma on line ${trailingCommaLine}.`;
|
|
175
|
+
}
|
|
63
176
|
const match = err.message.match(/position\s+(\d+)/i);
|
|
64
177
|
if (match) {
|
|
65
178
|
const line = input.slice(0, Number(match[1])).split("\n").length;
|
|
@@ -67,5 +180,11 @@ function formatError(err, input, tool) {
|
|
|
67
180
|
}
|
|
68
181
|
return "Invalid JSON.";
|
|
69
182
|
}
|
|
183
|
+
if (tool === "markdown-to-html") {
|
|
184
|
+
return "Failed to parse Markdown. Check the syntax.";
|
|
185
|
+
}
|
|
186
|
+
if (tool === "html-to-markdown") {
|
|
187
|
+
return "Failed to parse HTML. Check for unclosed tags.";
|
|
188
|
+
}
|
|
70
189
|
return "Failed to parse input. Check the format.";
|
|
71
190
|
}
|
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "formatarc",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Convert JSON, YAML, and
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Convert JSON, YAML, CSV, Markdown, and HTML from the terminal. Browser-based version at formatarc.com.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"json",
|
|
7
7
|
"yaml",
|
|
8
8
|
"csv",
|
|
9
|
+
"markdown",
|
|
10
|
+
"html",
|
|
9
11
|
"formatter",
|
|
10
12
|
"converter",
|
|
11
13
|
"json-formatter",
|
|
12
14
|
"yaml-to-json",
|
|
13
15
|
"csv-to-json",
|
|
16
|
+
"csv-to-markdown",
|
|
17
|
+
"markdown-to-html",
|
|
18
|
+
"html-to-markdown",
|
|
14
19
|
"cli"
|
|
15
20
|
],
|
|
16
21
|
"author": "FormatArc",
|
|
@@ -36,11 +41,14 @@
|
|
|
36
41
|
"prepublishOnly": "npm run build"
|
|
37
42
|
},
|
|
38
43
|
"dependencies": {
|
|
44
|
+
"marked": "^18.0.1",
|
|
39
45
|
"papaparse": "^5.5.2",
|
|
46
|
+
"turndown": "^7.2.4",
|
|
40
47
|
"yaml": "^2.8.1"
|
|
41
48
|
},
|
|
42
49
|
"devDependencies": {
|
|
43
50
|
"@types/papaparse": "^5.3.15",
|
|
51
|
+
"@types/turndown": "^5.0.6",
|
|
44
52
|
"typescript": "^5.8.3"
|
|
45
53
|
}
|
|
46
54
|
}
|