@zjy4fun/json-open 0.1.3 → 0.2.0

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.
@@ -18,9 +18,8 @@ jobs:
18
18
 
19
19
  - uses: actions/setup-node@v4
20
20
  with:
21
- node-version: 20
21
+ node-version: 22
22
22
  registry-url: 'https://registry.npmjs.org'
23
- cache: 'npm'
24
23
 
25
24
  - run: npm ci
26
25
  - run: npm test --if-present
@@ -33,3 +32,5 @@ jobs:
33
32
  [ "$TAG_VERSION" = "$PKG_VERSION" ] || (echo "Tag $TAG_VERSION != package.json $PKG_VERSION" && exit 1)
34
33
 
35
34
  - run: npm publish --access public --provenance
35
+ env:
36
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md CHANGED
@@ -1,56 +1,59 @@
1
1
  # json-open
2
2
 
3
- Open JSON in your browser with a collapsible tree view (supports stdin and inline JSON text).
3
+ Open JSON in your browser as a collapsible tree view.
4
4
 
5
- > A tiny CLI for command-line users: feed JSON, instantly inspect it in a browser with foldable structure.
6
-
7
- ## Demo
5
+ > A tiny CLI for quickly inspecting JSON from APIs, logs, or inline text.
8
6
 
9
7
  ![json-open demo](./demo.gif)
10
8
 
11
9
  ---
12
10
 
13
- ## Why this exists
11
+ ## Why json-open?
14
12
 
15
- Reading JSON in a terminal is often painful:
13
+ Reading JSON in terminal is often painful:
16
14
 
17
15
  - Long output is hard to scan
18
16
  - Deep nesting is hard to understand quickly
19
- - Heavy tools feel overkill for quick debugging
17
+ - Full-featured tools can feel heavy for quick checks
20
18
 
21
- `json-open` keeps this simple: **make JSON inspection fast and visual**.
19
+ `json-open` keeps it simple: **pipe JSON in, inspect it visually in seconds**.
22
20
 
23
21
  ---
24
22
 
25
23
  ## Features
26
24
 
27
- - ✅ Pipe input: `curl ... | json`
28
- - ✅ Inline JSON: `json '{"a":1}'`
29
- - ✅ Collapsible tree view in browser
30
- - ✅ Expand all / Collapse all buttons
31
- - ✅ Rendered from local temp file (no remote upload)
32
- - ✅ Cross-platform browser open (macOS / Linux / Windows)
25
+ - ✅ Read from stdin (pipe)
26
+ - ✅ Read inline JSON text
27
+ - ✅ Open browser automatically (macOS / Linux / Windows)
28
+ - ✅ Collapsible tree view
29
+ - ✅ Expand all / Collapse all
30
+ - ✅ Local temp file rendering (no remote upload)
31
+ - ✅ **Auto-parse serialized JSON strings** (escaped/double-encoded)
32
+ - ✅ **Deep nested JSON string expansion** (auto-unwrap JSON strings inside objects)
33
33
 
34
34
  ---
35
35
 
36
36
  ## Installation
37
37
 
38
- ### Option A: GitHub Packages (current primary channel)
38
+ ### npm (recommended)
39
39
 
40
- Configure npm for GitHub Packages first:
40
+ ```bash
41
+ npm i -g @zjy4fun/json-open
42
+ ```
43
+
44
+ After install, use the global command:
41
45
 
42
46
  ```bash
43
- echo "@zjy4fun:registry=https://npm.pkg.github.com" >> ~/.npmrc
44
- echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> ~/.npmrc
47
+ json --version
45
48
  ```
46
49
 
47
- Then install:
50
+ ### Run once with npx (no global install)
48
51
 
49
52
  ```bash
50
- npm i -g @zjy4fun/json-open
53
+ npx @zjy4fun/json-open '{"hello":"world"}'
51
54
  ```
52
55
 
53
- ### Option B: Local development install
56
+ ### Local development
54
57
 
55
58
  ```bash
56
59
  git clone https://github.com/zjy4fun/json-open.git
@@ -59,11 +62,9 @@ npm install
59
62
  npm link
60
63
  ```
61
64
 
62
- After that, the `json` command is available globally.
63
-
64
65
  ---
65
66
 
66
- ## Quick start
67
+ ## Quick Start
67
68
 
68
69
  ```bash
69
70
  # 1) API response
@@ -76,41 +77,25 @@ json '{"hello":"world","list":[1,2,3]}'
76
77
  cat response.json | json
77
78
  ```
78
79
 
79
- The command opens your browser and shows a JSON tree view.
80
+ The command opens your default browser and shows a structured JSON tree.
80
81
 
81
82
  ---
82
83
 
83
- ## Common use cases
84
-
85
- 1. **API debugging**
86
- Inspect response shape quickly, especially nested data.
87
-
88
- 2. **Backend/frontend integration checks**
89
- Verify missing fields or type mismatches after API changes.
90
-
91
- 3. **Ad-hoc JSON inspection**
92
- Visualize JSON copied from logs, queues, or snapshots.
93
-
94
- 4. **Team discussion/demo**
95
- Share a clearer structure view when discussing payloads.
96
-
97
- ---
98
-
99
- ## Command behavior
84
+ ## CLI Usage
100
85
 
101
86
  ```bash
102
- json
87
+ json [inline-json]
103
88
  ```
104
89
 
105
- Input source:
90
+ Input sources:
106
91
 
107
- - stdin (pipe)
108
- - inline argument JSON string
92
+ - `stdin` (pipe)
93
+ - Inline JSON argument
109
94
 
110
95
  Options:
111
96
 
112
- - `-h, --help` show help
113
- - `-v, --version` show CLI version
97
+ - `-h, --help` Show help
98
+ - `-v, --version` Show version
114
99
 
115
100
  Examples:
116
101
 
@@ -120,19 +105,43 @@ json --version
120
105
  json '{"ok":true}'
121
106
  ```
122
107
 
123
- If no input is provided, it prints usage help.
108
+ If no input is provided, usage help is printed.
124
109
 
125
110
  ---
126
111
 
127
- ## Release & distribution
112
+ ## Serialized JSON String Support
128
113
 
129
- Included GitHub Actions workflows:
114
+ `json-open` now automatically handles serialized/escaped JSON strings — a common pain point when working with logs, databases, and APIs.
130
115
 
131
- - `CI`: basic validation flow
132
- - `Publish to GitHub Packages`: publish to GPR
133
- - `Publish to npm (Trusted Publishing)`: reserved for npm OIDC flow
116
+ ```bash
117
+ # Double-encoded JSON string (e.g. from database or API response body)
118
+ json '"{\"name\":\"test\",\"age\":25}"'
119
+ # → auto-detects and parses as { "name": "test", "age": 25 }
134
120
 
135
- Current primary distribution: **GitHub Packages**.
121
+ # Nested JSON strings inside objects
122
+ json '{"status":"ok","data":"{\"users\":[{\"id\":1}]}"}'
123
+ # → auto-expands "data" field into a real JSON tree
124
+
125
+ # Multi-level serialization
126
+ json '"\"[1,2,3]\""'
127
+ # → recursively unwraps to [1, 2, 3]
128
+ ```
129
+
130
+ This works for:
131
+ - Escaped JSON from `JSON.stringify()` output
132
+ - Log files with embedded JSON payloads
133
+ - API responses where a field contains a JSON string
134
+ - Database columns storing serialized JSON
135
+
136
+ ---
137
+
138
+ ## Common Use Cases
139
+
140
+ - API debugging (inspect response shape quickly)
141
+ - Backend/frontend contract checks
142
+ - Ad-hoc JSON visualization from logs
143
+ - Payload discussion/demo with teammates
144
+ - **Inspecting serialized JSON from databases or message queues**
136
145
 
137
146
  ---
138
147
 
@@ -142,24 +151,18 @@ Issues and PRs are welcome.
142
151
 
143
152
  ### Good contribution ideas
144
153
 
145
- - Better error diagnostics (e.g. JSON syntax location)
154
+ - Better JSON parse error location hints
146
155
  - Theme switch (light/dark)
147
156
  - Direct file path support (e.g. `json ./data.json`)
148
- - Rich interactions (search, highlight, copy JSON path)
157
+ - Search / highlight / copy JSON path
149
158
 
150
- ### Local development
159
+ ### Local dev
151
160
 
152
161
  ```bash
153
162
  npm install
154
163
  npm test
155
164
  ```
156
165
 
157
- Before submitting:
158
-
159
- - Ensure code runs correctly
160
- - Ensure README examples still work
161
- - Keep changes focused and clear
162
-
163
166
  ---
164
167
 
165
168
  ## License
package/bin/json.js CHANGED
@@ -100,8 +100,11 @@ function valueToHtml(value, key = null) {
100
100
  return `<div class=\"line\">${keyHtml}<span>${escapeHtml(String(value))}</span></div>`
101
101
  }
102
102
 
103
- function toHtml(jsonObj) {
104
- const body = valueToHtml(jsonObj)
103
+ function toHtml(jsonObj, deepParsedObj) {
104
+ const rawBody = valueToHtml(jsonObj)
105
+ const parsedBody = valueToHtml(deepParsedObj)
106
+ // 检测是否有差异(有嵌套 JSON 字符串可以展开)
107
+ const hasDiff = JSON.stringify(jsonObj) !== JSON.stringify(deepParsedObj)
105
108
  return `<!doctype html>
106
109
  <html lang=\"en\">
107
110
  <head>
@@ -130,6 +133,8 @@ function toHtml(jsonObj) {
130
133
  margin-bottom: 16px;
131
134
  display: flex;
132
135
  gap: 8px;
136
+ align-items: center;
137
+ flex-wrap: wrap;
133
138
  }
134
139
  button {
135
140
  border: 1px solid #475569;
@@ -142,6 +147,51 @@ function toHtml(jsonObj) {
142
147
  button:hover {
143
148
  background: #334155;
144
149
  }
150
+ /* 开关样式 */
151
+ .toggle-wrap {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: 8px;
155
+ margin-left: auto;
156
+ font-size: 13px;
157
+ color: #94a3b8;
158
+ }
159
+ .toggle-wrap.hidden { display: none; }
160
+ .toggle {
161
+ position: relative;
162
+ width: 40px;
163
+ height: 22px;
164
+ cursor: pointer;
165
+ }
166
+ .toggle input {
167
+ opacity: 0;
168
+ width: 0;
169
+ height: 0;
170
+ }
171
+ .toggle .slider {
172
+ position: absolute;
173
+ inset: 0;
174
+ background: #334155;
175
+ border-radius: 22px;
176
+ transition: background 0.2s;
177
+ }
178
+ .toggle .slider::before {
179
+ content: '';
180
+ position: absolute;
181
+ width: 16px;
182
+ height: 16px;
183
+ left: 3px;
184
+ bottom: 3px;
185
+ background: #e2e8f0;
186
+ border-radius: 50%;
187
+ transition: transform 0.2s;
188
+ }
189
+ .toggle input:checked + .slider {
190
+ background: #58a6ff;
191
+ }
192
+ .toggle input:checked + .slider::before {
193
+ transform: translateX(18px);
194
+ }
145
195
  details {
146
196
  margin-left: 16px;
147
197
  }
@@ -174,23 +224,280 @@ function toHtml(jsonObj) {
174
224
  .null { color: #cbd5e1; }
175
225
  .symbol { color: #c4b5fd; }
176
226
  .meta { color: #64748b; }
227
+ /* 搜索框 */
228
+ .search-wrap {
229
+ display: flex;
230
+ align-items: center;
231
+ gap: 6px;
232
+ }
233
+ .search-wrap input {
234
+ border: 1px solid #475569;
235
+ background: #1e293b;
236
+ color: #e2e8f0;
237
+ border-radius: 8px;
238
+ padding: 7px 12px;
239
+ font-size: 13px;
240
+ font-family: inherit;
241
+ width: 200px;
242
+ outline: none;
243
+ transition: border-color 0.2s;
244
+ }
245
+ .search-wrap input:focus {
246
+ border-color: #58a6ff;
247
+ }
248
+ .search-wrap input::placeholder {
249
+ color: #64748b;
250
+ }
251
+ .search-count {
252
+ font-size: 12px;
253
+ color: #64748b;
254
+ min-width: 60px;
255
+ }
256
+ .search-nav button {
257
+ padding: 4px 8px;
258
+ font-size: 12px;
259
+ border-radius: 6px;
260
+ }
261
+ /* 搜索高亮 */
262
+ mark.highlight {
263
+ background: #f0883e;
264
+ color: #0d1117;
265
+ border-radius: 2px;
266
+ padding: 0 1px;
267
+ }
268
+ mark.highlight.current {
269
+ background: #58a6ff;
270
+ color: #fff;
271
+ box-shadow: 0 0 0 2px rgba(88,166,255,0.4);
272
+ }
273
+ /* 搜索时隐藏不匹配的行 */
274
+ .search-active .line.hidden-by-search,
275
+ .search-active li.hidden-by-search {
276
+ display: none;
277
+ }
177
278
  </style>
178
279
  </head>
179
280
  <body>
180
281
  <div class=\"toolbar\">
181
282
  <button id=\"expand-all\">Expand all</button>
182
283
  <button id=\"collapse-all\">Collapse all</button>
284
+ <div class=\"search-wrap\">
285
+ <input type=\"text\" id=\"search-input\" placeholder=\"Search...\" autocomplete=\"off\" />
286
+ <span class=\"search-count\" id=\"search-count\"></span>
287
+ <span class=\"search-nav\">
288
+ <button id=\"search-prev\" title=\"Previous (Shift+Enter)\">▲</button>
289
+ <button id=\"search-next\" title=\"Next (Enter)\">▼</button>
290
+ </span>
291
+ </div>
292
+ <div class=\"toggle-wrap${hasDiff ? '' : ' hidden'}\" title=\"Parse embedded JSON strings inside values\">
293
+ <span>Parse JSON strings</span>
294
+ <label class=\"toggle\">
295
+ <input type=\"checkbox\" id=\"deep-parse-toggle\" />
296
+ <span class=\"slider\"></span>
297
+ </label>
298
+ </div>
183
299
  </div>
184
- <main>${body}</main>
300
+ <main id=\"raw-view\">${rawBody}</main>
301
+ <main id=\"parsed-view\" style=\"display:none\">${parsedBody}</main>
185
302
  <script>
186
- const details = () => Array.from(document.querySelectorAll('details'))
303
+ const details = () => Array.from(document.querySelectorAll('main:not([style*=\"display:none\"]) details'))
187
304
  document.getElementById('expand-all').addEventListener('click', () => details().forEach((d) => d.open = true))
188
305
  document.getElementById('collapse-all').addEventListener('click', () => details().forEach((d) => d.open = false))
306
+
307
+ const toggle = document.getElementById('deep-parse-toggle')
308
+ const rawView = document.getElementById('raw-view')
309
+ const parsedView = document.getElementById('parsed-view')
310
+ if (toggle) {
311
+ toggle.addEventListener('change', () => {
312
+ if (toggle.checked) {
313
+ rawView.style.display = 'none'
314
+ parsedView.style.display = ''
315
+ } else {
316
+ rawView.style.display = ''
317
+ parsedView.style.display = 'none'
318
+ }
319
+ // 切换视图后重新搜索
320
+ if (searchInput.value.trim()) doSearch()
321
+ })
322
+ }
323
+
324
+ // ===== 搜索功能 =====
325
+ const searchInput = document.getElementById('search-input')
326
+ const searchCount = document.getElementById('search-count')
327
+ const searchPrev = document.getElementById('search-prev')
328
+ const searchNext = document.getElementById('search-next')
329
+ let highlights = []
330
+ let currentIdx = -1
331
+
332
+ function getActiveView() {
333
+ return parsedView.style.display === 'none' ? rawView : parsedView
334
+ }
335
+
336
+ function clearSearch() {
337
+ // 移除所有高亮
338
+ document.querySelectorAll('mark.highlight').forEach(mark => {
339
+ const parent = mark.parentNode
340
+ parent.replaceChild(document.createTextNode(mark.textContent), mark)
341
+ parent.normalize()
342
+ })
343
+ highlights = []
344
+ currentIdx = -1
345
+ searchCount.textContent = ''
346
+ document.body.classList.remove('search-active')
347
+ }
348
+
349
+ function doSearch() {
350
+ clearSearch()
351
+ const query = searchInput.value.trim()
352
+ if (!query) return
353
+
354
+ const view = getActiveView()
355
+ const regex = new RegExp(query.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&'), 'gi')
356
+
357
+ // 遍历所有文本节点进行高亮
358
+ const walker = document.createTreeWalker(view, NodeFilter.SHOW_TEXT, null)
359
+ const textNodes = []
360
+ while (walker.nextNode()) textNodes.push(walker.currentNode)
361
+
362
+ textNodes.forEach(node => {
363
+ const text = node.textContent
364
+ if (!regex.test(text)) return
365
+ regex.lastIndex = 0
366
+
367
+ const frag = document.createDocumentFragment()
368
+ let lastIdx = 0
369
+ let match
370
+ while ((match = regex.exec(text)) !== null) {
371
+ if (match.index > lastIdx) {
372
+ frag.appendChild(document.createTextNode(text.slice(lastIdx, match.index)))
373
+ }
374
+ const mark = document.createElement('mark')
375
+ mark.className = 'highlight'
376
+ mark.textContent = match[0]
377
+ frag.appendChild(mark)
378
+ lastIdx = regex.lastIndex
379
+ }
380
+ if (lastIdx < text.length) {
381
+ frag.appendChild(document.createTextNode(text.slice(lastIdx)))
382
+ }
383
+ node.parentNode.replaceChild(frag, node)
384
+ })
385
+
386
+ highlights = Array.from(view.querySelectorAll('mark.highlight'))
387
+ if (highlights.length > 0) {
388
+ // 展开所有包含匹配的 <details>
389
+ highlights.forEach(h => {
390
+ let el = h.parentElement
391
+ while (el && el !== view) {
392
+ if (el.tagName === 'DETAILS') el.open = true
393
+ el = el.parentElement
394
+ }
395
+ })
396
+ currentIdx = 0
397
+ scrollToCurrent()
398
+ }
399
+ updateCount()
400
+ }
401
+
402
+ function updateCount() {
403
+ if (highlights.length === 0 && searchInput.value.trim()) {
404
+ searchCount.textContent = 'No match'
405
+ } else if (highlights.length > 0) {
406
+ searchCount.textContent = (currentIdx + 1) + ' / ' + highlights.length
407
+ } else {
408
+ searchCount.textContent = ''
409
+ }
410
+ }
411
+
412
+ function scrollToCurrent() {
413
+ highlights.forEach((h, i) => {
414
+ h.classList.toggle('current', i === currentIdx)
415
+ })
416
+ if (highlights[currentIdx]) {
417
+ highlights[currentIdx].scrollIntoView({ behavior: 'smooth', block: 'center' })
418
+ }
419
+ updateCount()
420
+ }
421
+
422
+ function goNext() {
423
+ if (highlights.length === 0) return
424
+ currentIdx = (currentIdx + 1) % highlights.length
425
+ scrollToCurrent()
426
+ }
427
+
428
+ function goPrev() {
429
+ if (highlights.length === 0) return
430
+ currentIdx = (currentIdx - 1 + highlights.length) % highlights.length
431
+ scrollToCurrent()
432
+ }
433
+
434
+ // 输入时实时搜索(防抖 200ms)
435
+ let debounceTimer
436
+ searchInput.addEventListener('input', () => {
437
+ clearTimeout(debounceTimer)
438
+ debounceTimer = setTimeout(doSearch, 200)
439
+ })
440
+
441
+ // Enter = 下一个,Shift+Enter = 上一个
442
+ searchInput.addEventListener('keydown', (e) => {
443
+ if (e.key === 'Enter') {
444
+ e.preventDefault()
445
+ e.shiftKey ? goPrev() : goNext()
446
+ }
447
+ if (e.key === 'Escape') {
448
+ searchInput.value = ''
449
+ clearSearch()
450
+ searchInput.blur()
451
+ }
452
+ })
453
+
454
+ searchNext.addEventListener('click', goNext)
455
+ searchPrev.addEventListener('click', goPrev)
456
+
457
+ // Ctrl+F / Cmd+F 聚焦搜索框
458
+ document.addEventListener('keydown', (e) => {
459
+ if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
460
+ e.preventDefault()
461
+ searchInput.focus()
462
+ searchInput.select()
463
+ }
464
+ })
189
465
  </script>
190
466
  </body>
191
467
  </html>`
192
468
  }
193
469
 
470
+ /**
471
+ * 递归遍历 JSON 对象,尝试将值为 JSON 字符串的字段自动解析为对象
472
+ * 比如 { "data": "{\"name\":\"test\"}" } → { "data": { "name": "test" } }
473
+ * 这在 API 响应和日志中非常常见
474
+ */
475
+ function deepParseJsonStrings(obj) {
476
+ if (obj === null || obj === undefined) return obj
477
+ if (Array.isArray(obj)) return obj.map(deepParseJsonStrings)
478
+ if (typeof obj === 'object') {
479
+ const result = {}
480
+ for (const [key, value] of Object.entries(obj)) {
481
+ result[key] = deepParseJsonStrings(value)
482
+ }
483
+ return result
484
+ }
485
+ if (typeof obj === 'string') {
486
+ const trimmed = obj.trim()
487
+ // 只尝试解析看起来像 JSON 对象或数组的字符串
488
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
489
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
490
+ try {
491
+ const parsed = JSON.parse(trimmed)
492
+ return deepParseJsonStrings(parsed)
493
+ } catch {
494
+ return obj
495
+ }
496
+ }
497
+ }
498
+ return obj
499
+ }
500
+
194
501
  function openInBrowser(filePath) {
195
502
  const platform = process.platform
196
503
  const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open'
@@ -234,11 +541,54 @@ async function main() {
234
541
  try {
235
542
  parsed = JSON.parse(input)
236
543
  } catch {
237
- console.error('Input is not valid JSON.')
238
- process.exit(1)
544
+ // 尝试处理 JSON 序列化后的字符串(双重转义)
545
+ // 比如:"{\"name\":\"test\"}" 或 '"{\\\"name\\\":\\\"test\\\"}"'
546
+ // 这种情况常见于:日志输出、API 响应中嵌套的 JSON 字符串、数据库存储的 JSON
547
+ try {
548
+ // 第一步:去掉首尾引号(如果有的话)
549
+ let cleaned = input.trim()
550
+ if ((cleaned.startsWith('"') && cleaned.endsWith('"')) ||
551
+ (cleaned.startsWith("'") && cleaned.endsWith("'"))) {
552
+ cleaned = cleaned.slice(1, -1)
553
+ }
554
+ // 第二步:处理转义字符
555
+ // 替换 \" → ",\\ → \,\n → 换行,\t → tab
556
+ cleaned = cleaned
557
+ .replace(/\\"/g, '"')
558
+ .replace(/\\\\/g, '\\')
559
+ .replace(/\\n/g, '\n')
560
+ .replace(/\\t/g, '\t')
561
+ .replace(/\\r/g, '\r')
562
+ parsed = JSON.parse(cleaned)
563
+ console.log('ℹ️ Detected serialized JSON string, auto-unescaped.')
564
+ } catch {
565
+ // 第三步:尝试递归解析(多层序列化的情况)
566
+ try {
567
+ let result = input.trim()
568
+ let depth = 0
569
+ const maxDepth = 5
570
+ while (typeof result === 'string' && depth < maxDepth) {
571
+ result = JSON.parse(result)
572
+ depth++
573
+ }
574
+ if (typeof result === 'object' && result !== null) {
575
+ parsed = result
576
+ console.log(`ℹ️ Detected ${depth}-level serialized JSON string, auto-parsed.`)
577
+ } else {
578
+ console.error('Input is not valid JSON.')
579
+ process.exit(1)
580
+ }
581
+ } catch {
582
+ console.error('Input is not valid JSON.')
583
+ process.exit(1)
584
+ }
585
+ }
239
586
  }
240
587
 
241
- const html = toHtml(parsed)
588
+ // 生成深度解析版本(展开嵌套 JSON 字符串),但默认不启用
589
+ const deepParsed = deepParseJsonStrings(parsed)
590
+
591
+ const html = toHtml(parsed, deepParsed)
242
592
  const filePath = path.join(os.tmpdir(), `json-viewer-${Date.now()}.html`)
243
593
  await fs.writeFile(filePath, html, 'utf8')
244
594
  openInBrowser(filePath)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zjy4fun/json-open",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Open JSON (stdin or inline text) in a browser with collapsible tree view",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,8 +17,12 @@
17
17
  "browser",
18
18
  "formatter"
19
19
  ],
20
- "author": "",
20
+ "author": "zjy4fun",
21
21
  "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/zjy4fun/json-open"
25
+ },
22
26
  "publishConfig": {
23
27
  "access": "public",
24
28
  "registry": "https://registry.npmjs.org"