@zjy4fun/json-open 0.1.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.
@@ -0,0 +1,24 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Setup Node
16
+ uses: actions/setup-node@v4
17
+ with:
18
+ node-version: 20
19
+
20
+ - name: Install deps
21
+ run: npm ci
22
+
23
+ - name: Run tests
24
+ run: npm test
@@ -0,0 +1,31 @@
1
+ name: Publish to GitHub Packages
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ release:
6
+ types: [published]
7
+
8
+ jobs:
9
+ publish-gpr:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ packages: write
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup Node for GitHub Packages
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 20
22
+ registry-url: https://npm.pkg.github.com
23
+ scope: '@zjy4fun'
24
+
25
+ - name: Install dependencies
26
+ run: npm ci
27
+
28
+ - name: Publish package to GitHub Packages
29
+ run: npm publish
30
+ env:
31
+ NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,35 @@
1
+ name: Publish to npm (tag)
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*.*.*'
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: read
14
+ id-token: write
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: 20
22
+ registry-url: 'https://registry.npmjs.org'
23
+ cache: 'npm'
24
+
25
+ - run: npm ci
26
+ - run: npm test --if-present
27
+ - run: npm run build --if-present
28
+
29
+ - name: Verify tag matches package version
30
+ run: |
31
+ TAG_VERSION="${GITHUB_REF_NAME#v}"
32
+ PKG_VERSION=$(node -p "require('./package.json').version")
33
+ [ "$TAG_VERSION" = "$PKG_VERSION" ] || (echo "Tag $TAG_VERSION != package.json $PKG_VERSION" && exit 1)
34
+
35
+ - run: npm publish --access public --provenance
package/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # json-open
2
+
3
+ Open JSON in your browser with a collapsible tree view (supports stdin and inline JSON text).
4
+
5
+ > A tiny CLI for command-line users: feed JSON, instantly inspect it in a browser with foldable structure.
6
+
7
+ ## Demo
8
+
9
+ ![json-open demo](./demo.gif)
10
+
11
+ ---
12
+
13
+ ## Why this exists
14
+
15
+ Reading JSON in a terminal is often painful:
16
+
17
+ - Long output is hard to scan
18
+ - Deep nesting is hard to understand quickly
19
+ - Heavy tools feel overkill for quick debugging
20
+
21
+ `json-open` keeps this simple: **make JSON inspection fast and visual**.
22
+
23
+ ---
24
+
25
+ ## Features
26
+
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)
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ ### Option A: GitHub Packages (current primary channel)
39
+
40
+ Configure npm for GitHub Packages first:
41
+
42
+ ```bash
43
+ echo "@zjy4fun:registry=https://npm.pkg.github.com" >> ~/.npmrc
44
+ echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> ~/.npmrc
45
+ ```
46
+
47
+ Then install:
48
+
49
+ ```bash
50
+ npm i -g @zjy4fun/json-open
51
+ ```
52
+
53
+ ### Option B: Local development install
54
+
55
+ ```bash
56
+ git clone https://github.com/zjy4fun/json-open.git
57
+ cd json-open
58
+ npm install
59
+ npm link
60
+ ```
61
+
62
+ After that, the `json` command is available globally.
63
+
64
+ ---
65
+
66
+ ## Quick start
67
+
68
+ ```bash
69
+ # 1) API response
70
+ curl https://jsonplaceholder.typicode.com/todos/1 | json
71
+
72
+ # 2) Inline JSON
73
+ json '{"hello":"world","list":[1,2,3]}'
74
+
75
+ # 3) JSON file content
76
+ cat response.json | json
77
+ ```
78
+
79
+ The command opens your browser and shows a JSON tree view.
80
+
81
+ ---
82
+
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
100
+
101
+ ```bash
102
+ json
103
+ ```
104
+
105
+ Input source:
106
+
107
+ - stdin (pipe)
108
+ - inline argument JSON string
109
+
110
+ Example:
111
+
112
+ ```bash
113
+ json '{"ok":true}'
114
+ ```
115
+
116
+ If no input is provided, it prints usage help.
117
+
118
+ ---
119
+
120
+ ## Release & distribution
121
+
122
+ Included GitHub Actions workflows:
123
+
124
+ - `CI`: basic validation flow
125
+ - `Publish to GitHub Packages`: publish to GPR
126
+ - `Publish to npm (Trusted Publishing)`: reserved for npm OIDC flow
127
+
128
+ Current primary distribution: **GitHub Packages**.
129
+
130
+ ---
131
+
132
+ ## Contributing
133
+
134
+ Issues and PRs are welcome.
135
+
136
+ ### Good contribution ideas
137
+
138
+ - Better error diagnostics (e.g. JSON syntax location)
139
+ - Theme switch (light/dark)
140
+ - Direct file path support (e.g. `json ./data.json`)
141
+ - Rich interactions (search, highlight, copy JSON path)
142
+
143
+ ### Local development
144
+
145
+ ```bash
146
+ npm install
147
+ npm test
148
+ ```
149
+
150
+ Before submitting:
151
+
152
+ - Ensure code runs correctly
153
+ - Ensure README examples still work
154
+ - Keep changes focused and clear
155
+
156
+ ---
157
+
158
+ ## License
159
+
160
+ MIT
@@ -0,0 +1,160 @@
1
+ # json-open
2
+
3
+ 把 JSON 直接在浏览器里以可折叠树形结构查看(支持 stdin 和行内 JSON 字符串)。
4
+
5
+ > 一个给命令行用户准备的「JSON 临时观察器」:输入 JSON,立刻打开浏览器查看结构。
6
+
7
+ ## 演示
8
+
9
+ ![json-open 演示](./demo.gif)
10
+
11
+ ---
12
+
13
+ ## 为什么做这个
14
+
15
+ 在终端里看 JSON 常见痛点:
16
+
17
+ - 内容太长,不好定位
18
+ - 嵌套太深,不直观
19
+ - 临时调试不想上重型工具
20
+
21
+ `json-open` 的目标是:**让“看 JSON”这一步更快更顺手**。
22
+
23
+ ---
24
+
25
+ ## 功能特性
26
+
27
+ - ✅ 支持管道输入:`curl ... | json`
28
+ - ✅ 支持行内 JSON:`json '{"a":1}'`
29
+ - ✅ 浏览器树形展示(可折叠/展开)
30
+ - ✅ 一键全展开 / 全收起
31
+ - ✅ 本地临时文件渲染,不上传数据
32
+ - ✅ 跨平台打开浏览器(macOS / Linux / Windows)
33
+
34
+ ---
35
+
36
+ ## 安装
37
+
38
+ ### 方式一:GitHub Packages(当前主分发)
39
+
40
+ 先配置 npm 使用 GitHub Packages:
41
+
42
+ ```bash
43
+ echo "@zjy4fun:registry=https://npm.pkg.github.com" >> ~/.npmrc
44
+ echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> ~/.npmrc
45
+ ```
46
+
47
+ 然后安装:
48
+
49
+ ```bash
50
+ npm i -g @zjy4fun/json-open
51
+ ```
52
+
53
+ ### 方式二:本地开发安装
54
+
55
+ ```bash
56
+ git clone https://github.com/zjy4fun/json-open.git
57
+ cd json-open
58
+ npm install
59
+ npm link
60
+ ```
61
+
62
+ 安装后可全局使用 `json` 命令。
63
+
64
+ ---
65
+
66
+ ## 快速开始
67
+
68
+ ```bash
69
+ # 1) API 返回
70
+ curl https://jsonplaceholder.typicode.com/todos/1 | json
71
+
72
+ # 2) 行内 JSON
73
+ json '{"hello":"world","list":[1,2,3]}'
74
+
75
+ # 3) 文件内容
76
+ cat response.json | json
77
+ ```
78
+
79
+ 执行后会自动打开浏览器,展示 JSON 树形视图。
80
+
81
+ ---
82
+
83
+ ## 常见应用场景
84
+
85
+ 1. **API 调试**
86
+ 快速查看接口返回结构,尤其是深层嵌套数据。
87
+
88
+ 2. **前后端联调**
89
+ 接口变更后快速确认字段缺失或类型异常。
90
+
91
+ 3. **临时数据检查**
92
+ 对日志、队列、快照中的 JSON 做可视化排查。
93
+
94
+ 4. **沟通与演示**
95
+ 与同事讨论 payload 时更直观。
96
+
97
+ ---
98
+
99
+ ## 命令行为
100
+
101
+ ```bash
102
+ json
103
+ ```
104
+
105
+ 输入来源:
106
+
107
+ - stdin(管道)
108
+ - 命令行参数中的 JSON 字符串
109
+
110
+ 示例:
111
+
112
+ ```bash
113
+ json '{"ok":true}'
114
+ ```
115
+
116
+ 如果没有输入,会输出 usage 提示。
117
+
118
+ ---
119
+
120
+ ## 发布与分发
121
+
122
+ 仓库已包含 GitHub Actions:
123
+
124
+ - `CI`:基础校验流程
125
+ - `Publish to GitHub Packages`:发布到 GPR
126
+ - `Publish to npm (Trusted Publishing)`:预留 npm OIDC 发布流程
127
+
128
+ 当前主分发方式:**GitHub Packages**。
129
+
130
+ ---
131
+
132
+ ## 参与贡献
133
+
134
+ 欢迎提 Issue / PR。
135
+
136
+ ### 建议贡献方向
137
+
138
+ - 更好的错误提示(如 JSON 语法错误定位)
139
+ - 主题切换(亮色/暗色)
140
+ - 支持直接读取文件路径(如 `json ./data.json`)
141
+ - 更丰富交互(搜索、高亮、复制 JSON Path)
142
+
143
+ ### 本地开发
144
+
145
+ ```bash
146
+ npm install
147
+ npm test
148
+ ```
149
+
150
+ 提交前建议:
151
+
152
+ - 确保代码可运行
153
+ - 确保 README 示例可复现
154
+ - 改动保持聚焦、清晰
155
+
156
+ ---
157
+
158
+ ## 许可证
159
+
160
+ MIT
package/bin/json.js ADDED
@@ -0,0 +1,213 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { spawn } from 'node:child_process'
6
+
7
+ function readStdin() {
8
+ return new Promise((resolve, reject) => {
9
+ let data = ''
10
+ process.stdin.setEncoding('utf8')
11
+ process.stdin.on('data', (chunk) => (data += chunk))
12
+ process.stdin.on('end', () => resolve(data.trim()))
13
+ process.stdin.on('error', reject)
14
+ })
15
+ }
16
+
17
+ function escapeHtml(str) {
18
+ return str
19
+ .replaceAll('&', '&')
20
+ .replaceAll('<', '&lt;')
21
+ .replaceAll('>', '&gt;')
22
+ .replaceAll('"', '&quot;')
23
+ .replaceAll("'", '&#39;')
24
+ }
25
+
26
+ function valueToHtml(value, key = null) {
27
+ const keyHtml = key === null ? '' : `<span class=\"key\">${escapeHtml(String(key))}</span><span class=\"colon\">: </span>`
28
+
29
+ if (value === null) {
30
+ return `<div class=\"line\">${keyHtml}<span class=\"null\">null</span></div>`
31
+ }
32
+
33
+ const type = typeof value
34
+
35
+ if (type === 'string') {
36
+ return `<div class=\"line\">${keyHtml}<span class=\"string\">\"${escapeHtml(value)}\"</span></div>`
37
+ }
38
+
39
+ if (type === 'number') {
40
+ return `<div class=\"line\">${keyHtml}<span class=\"number\">${String(value)}</span></div>`
41
+ }
42
+
43
+ if (type === 'boolean') {
44
+ return `<div class=\"line\">${keyHtml}<span class=\"boolean\">${String(value)}</span></div>`
45
+ }
46
+
47
+ if (Array.isArray(value)) {
48
+ const children = value
49
+ .map((item, idx) => `<li>${valueToHtml(item, idx)}</li>`)
50
+ .join('')
51
+
52
+ return `
53
+ <details open>
54
+ <summary>${keyHtml}<span class=\"symbol\">[ ]</span> <span class=\"meta\">(${value.length} items)</span></summary>
55
+ <ul>${children}</ul>
56
+ </details>
57
+ `
58
+ }
59
+
60
+ if (type === 'object') {
61
+ const entries = Object.entries(value)
62
+ const children = entries
63
+ .map(([childKey, childValue]) => `<li>${valueToHtml(childValue, childKey)}</li>`)
64
+ .join('')
65
+
66
+ return `
67
+ <details open>
68
+ <summary>${keyHtml}<span class=\"symbol\">{ }</span> <span class=\"meta\">(${entries.length} keys)</span></summary>
69
+ <ul>${children}</ul>
70
+ </details>
71
+ `
72
+ }
73
+
74
+ return `<div class=\"line\">${keyHtml}<span>${escapeHtml(String(value))}</span></div>`
75
+ }
76
+
77
+ function toHtml(jsonObj) {
78
+ const body = valueToHtml(jsonObj)
79
+ return `<!doctype html>
80
+ <html lang=\"en\">
81
+ <head>
82
+ <meta charset=\"UTF-8\" />
83
+ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />
84
+ <title>JSON Response Viewer</title>
85
+ <style>
86
+ :root {
87
+ color-scheme: dark;
88
+ }
89
+ body {
90
+ margin: 0;
91
+ padding: 24px;
92
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
93
+ background: #0f172a;
94
+ color: #e2e8f0;
95
+ line-height: 1.5;
96
+ }
97
+ .toolbar {
98
+ position: sticky;
99
+ top: 0;
100
+ background: rgba(15, 23, 42, 0.8);
101
+ backdrop-filter: blur(6px);
102
+ border-bottom: 1px solid #334155;
103
+ padding: 12px 0;
104
+ margin-bottom: 16px;
105
+ display: flex;
106
+ gap: 8px;
107
+ }
108
+ button {
109
+ border: 1px solid #475569;
110
+ background: #1e293b;
111
+ color: #e2e8f0;
112
+ border-radius: 8px;
113
+ padding: 8px 12px;
114
+ cursor: pointer;
115
+ }
116
+ button:hover {
117
+ background: #334155;
118
+ }
119
+ details {
120
+ margin-left: 16px;
121
+ }
122
+ summary {
123
+ cursor: pointer;
124
+ list-style: none;
125
+ }
126
+ summary::-webkit-details-marker {
127
+ display: none;
128
+ }
129
+ summary::before {
130
+ content: '▸';
131
+ margin-right: 6px;
132
+ color: #94a3b8;
133
+ }
134
+ details[open] > summary::before {
135
+ content: '▾';
136
+ }
137
+ ul {
138
+ list-style: none;
139
+ margin: 4px 0 0 12px;
140
+ padding-left: 12px;
141
+ border-left: 1px dashed #334155;
142
+ }
143
+ .key { color: #93c5fd; }
144
+ .colon { color: #94a3b8; }
145
+ .string { color: #86efac; }
146
+ .number { color: #fcd34d; }
147
+ .boolean { color: #f9a8d4; }
148
+ .null { color: #cbd5e1; }
149
+ .symbol { color: #c4b5fd; }
150
+ .meta { color: #64748b; }
151
+ </style>
152
+ </head>
153
+ <body>
154
+ <div class=\"toolbar\">
155
+ <button id=\"expand-all\">Expand all</button>
156
+ <button id=\"collapse-all\">Collapse all</button>
157
+ </div>
158
+ <main>${body}</main>
159
+ <script>
160
+ const details = () => Array.from(document.querySelectorAll('details'))
161
+ document.getElementById('expand-all').addEventListener('click', () => details().forEach((d) => d.open = true))
162
+ document.getElementById('collapse-all').addEventListener('click', () => details().forEach((d) => d.open = false))
163
+ </script>
164
+ </body>
165
+ </html>`
166
+ }
167
+
168
+ function openInBrowser(filePath) {
169
+ const platform = process.platform
170
+ const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open'
171
+
172
+ if (platform === 'win32') {
173
+ spawn('cmd', ['/c', command, filePath], { detached: true, stdio: 'ignore' }).unref()
174
+ return
175
+ }
176
+
177
+ spawn(command, [filePath], { detached: true, stdio: 'ignore' }).unref()
178
+ }
179
+
180
+ async function main() {
181
+ const inlineInput = process.argv.slice(2).join(' ').trim()
182
+
183
+ if (!inlineInput && process.stdin.isTTY) {
184
+ console.error('Usage: curl https://example.com | json\n or: json "{\"hello\":\"world\"}"')
185
+ process.exit(1)
186
+ }
187
+
188
+ const input = inlineInput || (await readStdin())
189
+
190
+ if (!input) {
191
+ console.error('No JSON input received.')
192
+ process.exit(1)
193
+ }
194
+
195
+ let parsed
196
+ try {
197
+ parsed = JSON.parse(input)
198
+ } catch {
199
+ console.error('Input is not valid JSON.')
200
+ process.exit(1)
201
+ }
202
+
203
+ const html = toHtml(parsed)
204
+ const filePath = path.join(os.tmpdir(), `json-viewer-${Date.now()}.html`)
205
+ await fs.writeFile(filePath, html, 'utf8')
206
+ openInBrowser(filePath)
207
+ console.log(`Opened JSON viewer: ${filePath}`)
208
+ }
209
+
210
+ main().catch((err) => {
211
+ console.error('Unexpected error:', err.message)
212
+ process.exit(1)
213
+ })
package/demo.gif ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@zjy4fun/json-open",
3
+ "version": "0.1.1",
4
+ "description": "Open JSON (stdin or inline text) in a browser with collapsible tree view",
5
+ "type": "module",
6
+ "bin": {
7
+ "json": "bin/json.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node bin/json.js",
11
+ "test": "node --test"
12
+ },
13
+ "keywords": [
14
+ "json",
15
+ "curl",
16
+ "cli",
17
+ "browser",
18
+ "formatter"
19
+ ],
20
+ "author": "",
21
+ "license": "MIT",
22
+ "publishConfig": {
23
+ "access": "public",
24
+ "registry": "https://registry.npmjs.org"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
28
+ }
29
+ }