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 CHANGED
@@ -1,8 +1,28 @@
1
1
  # formatarc
2
2
 
3
- Convert JSON, YAML, and CSV from the terminal. No config, no dependencies to manage just pipe or pass your data.
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
- **Web version → [formatarc.com](https://formatarc.com)**
5
+ [![npm version](https://img.shields.io/npm/v/formatarc.svg)](https://www.npmjs.com/package/formatarc)
6
+ [![license: MIT](https://img.shields.io/npm/l/formatarc.svg)](https://github.com/m-naoki-m/formatarc/blob/main/LICENSE)
7
+ [![npm downloads](https://img.shields.io/npm/dm/formatarc.svg)](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 / Japanese)
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 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
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();
@@ -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
- const TOOLS = ["json-format", "yaml-to-json", "json-to-yaml", "csv-to-json"];
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 convertCsv(input);
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 convertCsv(input) {
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.0",
4
- "description": "Convert JSON, YAML, and CSV from the terminal. Browser-based version at formatarc.com.",
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
  }