projview 0.1.0 โ 0.2.4
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/bin/projview.js +10 -3
- package/docs/README.md +1 -0
- package/docs/how-to-set-up.md +130 -0
- package/package.json +1 -1
- package/public/index.html +13 -4
- package/public/live.js +4 -10
- package/public/style.css +12 -29
- package/public/toc.js +14 -4
- package/public/tree.js +3 -3
- package/public/viewer.js +3 -1
- package/src/render.js +3 -1
- package/src/server.js +2 -1
package/bin/projview.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { readFileSync } from 'node:fs';
|
|
5
6
|
import { startServer } from '../src/server.js';
|
|
6
7
|
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
const DEMO_DIR = path.join(__dirname, '..', 'docs'); // bundled sample docs
|
|
10
|
+
const VERSION = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
9
11
|
|
|
10
12
|
function parseArgs(argv) {
|
|
11
13
|
const opts = {
|
|
@@ -20,7 +22,9 @@ function parseArgs(argv) {
|
|
|
20
22
|
if (arg === '--port' || arg === '-p') opts.port = Number(argv[++i]) || opts.port;
|
|
21
23
|
else if (arg === '--no-open') opts.open = false;
|
|
22
24
|
else if (arg === '--demo') opts.demo = true;
|
|
25
|
+
else if (arg === '--version' || arg === '-v') opts.version = true;
|
|
23
26
|
else if (arg === '--help' || arg === '-h') opts.help = true;
|
|
27
|
+
else if (arg.startsWith('-')) opts.unknown = arg; // unrecognised flag -> error, don't treat as a path
|
|
24
28
|
else rest.push(arg);
|
|
25
29
|
}
|
|
26
30
|
if (rest[0]) opts.root = path.resolve(process.cwd(), rest[0]);
|
|
@@ -49,15 +53,18 @@ options:
|
|
|
49
53
|
-p, --port <n> preferred port (default: 4321, climbs if taken)
|
|
50
54
|
--demo preview projview's bundled sample docs
|
|
51
55
|
--no-open do not auto-open the browser
|
|
56
|
+
-v, --version print the version and exit
|
|
52
57
|
-h, --help show this help
|
|
53
58
|
|
|
54
59
|
nothing is written to disk - ctrl-c and it's gone.`;
|
|
55
60
|
|
|
56
61
|
async function main() {
|
|
57
62
|
const opts = parseArgs(process.argv.slice(2));
|
|
58
|
-
if (opts.help) {
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
if (opts.help) { console.log(HELP); return; }
|
|
64
|
+
if (opts.version) { console.log(VERSION); return; }
|
|
65
|
+
if (opts.unknown) {
|
|
66
|
+
console.error(`projview: unknown option "${opts.unknown}"\nrun "projview --help" for usage.`);
|
|
67
|
+
process.exit(1);
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
const { port, close } = await startServer(opts.root, { port: opts.port, host: opts.host });
|
package/docs/README.md
CHANGED
|
@@ -8,6 +8,7 @@ faults. use this tree while iterating on the tool.
|
|
|
8
8
|
|
|
9
9
|
| area | doc | what it exercises |
|
|
10
10
|
| --- | --- | --- |
|
|
11
|
+
| **setup** | [**how to set up**](how-to-set-up.md) | install / clone / use as a library + links to the repo & npm |
|
|
11
12
|
| start | [getting started](getting-started.md) | headings h1-h6, code, tables, anchors, a valid diagram |
|
|
12
13
|
| guides | [installation](guides/installation.md) | relative links up/down, one dead link |
|
|
13
14
|
| guides | [configuration](guides/configuration.md) | explicit `{#id}` anchors, a missing-anchor link |
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# how to set up projview
|
|
2
|
+
|
|
3
|
+
Everything you need to **install, run, or hack on** projview - a tiny local
|
|
4
|
+
previewer for markdown + mermaid files.
|
|
5
|
+
|
|
6
|
+
- ๐ฆ npm: <https://www.npmjs.com/package/projview>
|
|
7
|
+
- ๐ repo: <https://github.com/vikingsearth/projview>
|
|
8
|
+
- ๐ issues: <https://github.com/vikingsearth/projview/issues>
|
|
9
|
+
|
|
10
|
+
## requirements
|
|
11
|
+
|
|
12
|
+
| need | version |
|
|
13
|
+
| --- | --- |
|
|
14
|
+
| Node.js | **18+** |
|
|
15
|
+
| npm | ships with Node |
|
|
16
|
+
|
|
17
|
+
No build step, no database - indexing is in-memory and nothing is written to disk.
|
|
18
|
+
|
|
19
|
+
## pick your path
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
flowchart TD
|
|
23
|
+
Q{what do you want to do?}
|
|
24
|
+
Q -->|just try it, zero install| A["npx projview --demo"]
|
|
25
|
+
Q -->|preview my own docs often| B["npm i -g projview"]
|
|
26
|
+
Q -->|modify it / contribute| C["clone the repo"]
|
|
27
|
+
A --> R[browser opens on a local port]
|
|
28
|
+
B --> R
|
|
29
|
+
C --> R
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 1. run instantly with npx (no install)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# preview the bundled sample docs (this very doc set)
|
|
36
|
+
npx projview --demo
|
|
37
|
+
|
|
38
|
+
# preview the current directory
|
|
39
|
+
npx projview
|
|
40
|
+
|
|
41
|
+
# point it somewhere, on a chosen port, without auto-opening the browser
|
|
42
|
+
npx projview ./docs --port 5000 --no-open
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`npx` downloads + runs the latest published version, then cleans up.
|
|
46
|
+
|
|
47
|
+
## 2. install the CLI globally
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npm i -g projview # adds `projview` to your PATH
|
|
51
|
+
projview --demo # try the sample docs
|
|
52
|
+
projview ~/notes # preview any directory
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
To update later: `npm i -g projview@latest`. To remove: `npm rm -g projview`.
|
|
56
|
+
|
|
57
|
+
## 3. clone the repo + run from source
|
|
58
|
+
|
|
59
|
+
For hacking on projview itself, or running an unpublished build:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/vikingsearth/projview.git
|
|
63
|
+
cd projview
|
|
64
|
+
npm install
|
|
65
|
+
|
|
66
|
+
npm run demo # preview the bundled docs/ (alias for --demo)
|
|
67
|
+
npm start # index the current directory
|
|
68
|
+
node bin/projview.js ./docs # or call the CLI directly
|
|
69
|
+
npm test # run the test suite (node:test, 23 tests)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
See the [project README](README.md) for the architecture and the
|
|
73
|
+
[api reference](reference/api.md) for the HTTP surface.
|
|
74
|
+
|
|
75
|
+
## CLI reference
|
|
76
|
+
|
|
77
|
+
| argument / flag | meaning |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `[path]` | directory to index (default: current directory) |
|
|
80
|
+
| `--demo` | preview projview's own bundled sample docs |
|
|
81
|
+
| `-p`, `--port <n>` | preferred port (default `4321`, climbs if taken) |
|
|
82
|
+
| `--no-open` | don't auto-open the browser |
|
|
83
|
+
| `-h`, `--help` | show help |
|
|
84
|
+
|
|
85
|
+
**Environment variables** (handy for containers / servers):
|
|
86
|
+
|
|
87
|
+
| var | default | use |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `PORT` | `4321` | port to serve on |
|
|
90
|
+
| `HOST` | `127.0.0.1` | interface to bind (`0.0.0.0` to expose) |
|
|
91
|
+
|
|
92
|
+
## use the source directly (advanced)
|
|
93
|
+
|
|
94
|
+
The server is a small ES module you can embed. There's no stable public API yet,
|
|
95
|
+
so treat this as internal:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { startServer } from 'projview/src/server.js';
|
|
99
|
+
|
|
100
|
+
const { port, close } = await startServer(process.cwd(), { port: 4321, host: '127.0.0.1' });
|
|
101
|
+
console.log(`serving on http://127.0.0.1:${port}`);
|
|
102
|
+
// ... later
|
|
103
|
+
await close();
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The client is plain ES modules under `public/` (`main.js` wires it together) -
|
|
107
|
+
no bundler, so you can read and tweak it directly.
|
|
108
|
+
|
|
109
|
+
## run it as a container (optional)
|
|
110
|
+
|
|
111
|
+
The repo ships a `Dockerfile` that serves the demo docs on port `8080`:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
docker build -t projview-demo .
|
|
115
|
+
docker run -p 8080:8080 projview-demo # then open http://localhost:8080
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> Behind a TLS-intercepting proxy? Pass your corporate CA as a build secret:
|
|
119
|
+
> `docker build --secret id=cacert,src=corp-ca.pem -t projview-demo .` - the
|
|
120
|
+
> cert is used only during install and never written into an image layer.
|
|
121
|
+
|
|
122
|
+
## links & references
|
|
123
|
+
|
|
124
|
+
| | |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| repository | <https://github.com/vikingsearth/projview> |
|
|
127
|
+
| npm package | <https://www.npmjs.com/package/projview> |
|
|
128
|
+
| report a bug | <https://github.com/vikingsearth/projview/issues> |
|
|
129
|
+
| license | MIT |
|
|
130
|
+
| more docs | [getting started](getting-started.md) ยท [api reference](reference/api.md) ยท [glossary](reference/glossary.md) |
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -9,15 +9,24 @@
|
|
|
9
9
|
<title>projview</title>
|
|
10
10
|
<link rel="stylesheet" href="/vendor/hljs.css" />
|
|
11
11
|
<link rel="stylesheet" href="/style.css" />
|
|
12
|
-
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0
|
|
12
|
+
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'><rect x='2' y='4.5' width='20' height='15' rx='4' stroke='%23b4befe' stroke-width='1.8'/><line x1='8.5' y1='4.5' x2='8.5' y2='19.5' stroke='%23b4befe' stroke-width='1.8'/><circle cx='13.4' cy='9.4' r='1.25' fill='%23cba6f7'/><circle cx='18' cy='9.4' r='1.25' fill='%23cba6f7'/><circle cx='15.7' cy='14.6' r='1.25' fill='%23cba6f7'/></svg>" />
|
|
13
13
|
</head>
|
|
14
14
|
<body class="toc-empty">
|
|
15
15
|
<aside id="sidebar">
|
|
16
16
|
<header id="brand">
|
|
17
|
-
<span class="logo"
|
|
17
|
+
<span class="logo" aria-hidden="true">
|
|
18
|
+
<svg viewBox="0 0 24 24" width="26" height="26" fill="none">
|
|
19
|
+
<rect x="2" y="4.5" width="20" height="15" rx="4" stroke="#b4befe" stroke-width="1.8"/>
|
|
20
|
+
<line x1="8.5" y1="4.5" x2="8.5" y2="19.5" stroke="#b4befe" stroke-width="1.8"/>
|
|
21
|
+
<circle cx="13.4" cy="9.4" r="1.25" fill="#cba6f7"/>
|
|
22
|
+
<circle cx="18" cy="9.4" r="1.25" fill="#cba6f7"/>
|
|
23
|
+
<circle cx="15.7" cy="14.6" r="1.25" fill="#cba6f7"/>
|
|
24
|
+
<path d="M13.4 9.4 15.7 14.6 18 9.4" stroke="#cba6f7" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
|
|
25
|
+
</svg>
|
|
26
|
+
</span>
|
|
18
27
|
<div class="brand-text">
|
|
19
|
-
<strong
|
|
20
|
-
<small id="
|
|
28
|
+
<strong class="wordmark"><span class="wm-proj">proj</span><span class="wm-view">view</span></strong>
|
|
29
|
+
<small id="version" class="version"></small>
|
|
21
30
|
</div>
|
|
22
31
|
</header>
|
|
23
32
|
<button id="open-palette" class="open-palette" title="search (โK)">
|
package/public/live.js
CHANGED
|
@@ -5,7 +5,7 @@ import { state } from './store.js';
|
|
|
5
5
|
import { loadTree } from './tree.js';
|
|
6
6
|
import { openFile } from './viewer.js';
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const versionEl = $('version');
|
|
9
9
|
|
|
10
10
|
export function connectEvents() {
|
|
11
11
|
const es = new EventSource('/api/events');
|
|
@@ -14,18 +14,12 @@ export function connectEvents() {
|
|
|
14
14
|
if (path === state.currentPath) { pulse(); openFile(path); }
|
|
15
15
|
});
|
|
16
16
|
es.addEventListener('tree', async () => { pulse(); await loadTree(); });
|
|
17
|
-
es.onerror = () => setLive(false);
|
|
18
|
-
es.addEventListener('ready', () => setLive(true));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function setLive(on) {
|
|
22
|
-
liveStatusEl.classList.toggle('off', !on);
|
|
23
|
-
liveStatusEl.querySelector('.dot')?.classList.toggle('off', !on);
|
|
24
17
|
}
|
|
25
18
|
|
|
19
|
+
// brief flash of the version line as live-reload feedback
|
|
26
20
|
let pulseTimer;
|
|
27
21
|
function pulse() {
|
|
28
|
-
|
|
22
|
+
versionEl.classList.add('pulse');
|
|
29
23
|
clearTimeout(pulseTimer);
|
|
30
|
-
pulseTimer = setTimeout(() =>
|
|
24
|
+
pulseTimer = setTimeout(() => versionEl.classList.remove('pulse'), 700);
|
|
31
25
|
}
|
package/public/style.css
CHANGED
|
@@ -58,40 +58,23 @@ body {
|
|
|
58
58
|
#brand {
|
|
59
59
|
display: flex;
|
|
60
60
|
align-items: center;
|
|
61
|
-
gap:
|
|
62
|
-
padding:
|
|
61
|
+
gap: 11px;
|
|
62
|
+
padding: 16px 16px;
|
|
63
63
|
border-bottom: 2px solid var(--surface0);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
#brand .logo {
|
|
67
|
-
font-size: 28px;
|
|
68
|
-
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4));
|
|
69
|
-
}
|
|
66
|
+
#brand .logo svg { display: block; filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.35)); }
|
|
70
67
|
|
|
71
|
-
.brand-text { display: flex; flex-direction: column; line-height: 1.
|
|
72
|
-
.
|
|
68
|
+
.brand-text { display: flex; flex-direction: column; line-height: 1.25; }
|
|
69
|
+
.wordmark { font-size: 17px; font-weight: 800; letter-spacing: 0.2px; }
|
|
70
|
+
.wordmark .wm-proj { color: var(--lavender); }
|
|
71
|
+
.wordmark .wm-view { color: var(--mauve); }
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
color: var(--
|
|
79
|
-
font-size: 12px;
|
|
80
|
-
font-weight: 600;
|
|
81
|
-
}
|
|
82
|
-
#live-status .dot {
|
|
83
|
-
width: 8px; height: 8px; border-radius: 50%;
|
|
84
|
-
background: var(--green);
|
|
85
|
-
box-shadow: 0 0 8px var(--green);
|
|
86
|
-
transition: background 0.2s, box-shadow 0.2s;
|
|
87
|
-
}
|
|
88
|
-
#live-status.off { color: var(--overlay); }
|
|
89
|
-
#live-status .dot.off { background: var(--overlay); box-shadow: none; }
|
|
90
|
-
#live-status.pulse .dot { animation: blip 0.6s ease; }
|
|
91
|
-
@keyframes blip {
|
|
92
|
-
0% { transform: scale(1); }
|
|
93
|
-
40% { transform: scale(2.2); }
|
|
94
|
-
100% { transform: scale(1); }
|
|
73
|
+
.version { color: var(--overlay); font-size: 11px; font-weight: 500; letter-spacing: 0.3px; }
|
|
74
|
+
.version.pulse { animation: vpulse 0.7s ease; }
|
|
75
|
+
@keyframes vpulse {
|
|
76
|
+
0% { color: var(--green); }
|
|
77
|
+
100% { color: var(--overlay); }
|
|
95
78
|
}
|
|
96
79
|
|
|
97
80
|
.open-palette {
|
package/public/toc.js
CHANGED
|
@@ -8,6 +8,11 @@ const viewerEl = $('viewer');
|
|
|
8
8
|
const tocListEl = $('toc-list');
|
|
9
9
|
|
|
10
10
|
let tocEntries = []; // [{ id, el, link }]
|
|
11
|
+
let spyLock = 0; // timestamp until which scroll-spy yields to a click
|
|
12
|
+
|
|
13
|
+
function setActive(current) {
|
|
14
|
+
for (const entry of tocEntries) entry.link.classList.toggle('active', entry === current);
|
|
15
|
+
}
|
|
11
16
|
|
|
12
17
|
export function buildToc() {
|
|
13
18
|
const headings = [...contentEl.querySelectorAll('h1, h2, h3')].filter((h) => h.id);
|
|
@@ -23,22 +28,27 @@ export function buildToc() {
|
|
|
23
28
|
const link = document.createElement('button');
|
|
24
29
|
link.className = `toc-item lvl-${h.tagName.toLowerCase()}`;
|
|
25
30
|
link.textContent = h.textContent;
|
|
26
|
-
|
|
31
|
+
const entry = { id: h.id, el: h, link };
|
|
32
|
+
link.addEventListener('click', () => {
|
|
33
|
+
scrollToFragment(h.id);
|
|
34
|
+
setActive(entry); // clicked item wins immediately
|
|
35
|
+
spyLock = performance.now() + 700; // and holds briefly against scroll-spy
|
|
36
|
+
});
|
|
27
37
|
frag.appendChild(link);
|
|
28
|
-
tocEntries.push(
|
|
38
|
+
tocEntries.push(entry);
|
|
29
39
|
}
|
|
30
40
|
tocListEl.replaceChildren(frag);
|
|
31
41
|
updateScrollSpy();
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
function updateScrollSpy() {
|
|
35
|
-
if (!tocEntries.length) return;
|
|
45
|
+
if (!tocEntries.length || performance.now() < spyLock) return; // a recent click wins
|
|
36
46
|
const top = viewerEl.scrollTop + 90;
|
|
37
47
|
let current = tocEntries[0];
|
|
38
48
|
for (const entry of tocEntries) {
|
|
39
49
|
if (entry.el.offsetTop <= top) current = entry; else break;
|
|
40
50
|
}
|
|
41
|
-
|
|
51
|
+
setActive(current);
|
|
42
52
|
}
|
|
43
53
|
|
|
44
54
|
let spyRaf = 0;
|
package/public/tree.js
CHANGED
|
@@ -7,16 +7,16 @@ import { openFile } from './viewer.js';
|
|
|
7
7
|
|
|
8
8
|
const treeEl = $('tree');
|
|
9
9
|
const fileCountEl = $('file-count');
|
|
10
|
-
const
|
|
10
|
+
const versionEl = $('version');
|
|
11
11
|
let fileCount = 0;
|
|
12
12
|
|
|
13
13
|
export async function loadTree() {
|
|
14
14
|
const res = await fetch('/api/tree');
|
|
15
|
-
const { root, tree } = await res.json();
|
|
15
|
+
const { root, tree, version } = await res.json();
|
|
16
16
|
state.treeData = tree;
|
|
17
17
|
state.rootName = root;
|
|
18
|
-
rootNameEl.textContent = root;
|
|
19
18
|
document.title = `${root} ยท projview`;
|
|
19
|
+
if (version) versionEl.textContent = `version ${version}`;
|
|
20
20
|
renderTree();
|
|
21
21
|
}
|
|
22
22
|
|
package/public/viewer.js
CHANGED
|
@@ -37,7 +37,9 @@ export async function openFile(p, fragment, highlight) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export function scrollToFragment(frag) {
|
|
40
|
-
|
|
40
|
+
// headings are rendered with a "pv-" id prefix (collision-proof); try that
|
|
41
|
+
// first, then fall back to a raw id and an already-prefixed value (TOC).
|
|
42
|
+
const el = document.getElementById(`pv-${frag}`) || document.getElementById(frag);
|
|
41
43
|
// instant, not smooth: smooth scrollIntoView is a no-op on this overflow container
|
|
42
44
|
if (el) el.scrollIntoView({ block: 'start' });
|
|
43
45
|
else toast(`no section "#${frag}" on this page`);
|
package/src/render.js
CHANGED
|
@@ -77,7 +77,9 @@ md.core.ruler.push('heading_anchors', (state) => {
|
|
|
77
77
|
let n = 1;
|
|
78
78
|
while (used[unique]) unique = `${id}-${n++}`;
|
|
79
79
|
used[unique] = true;
|
|
80
|
-
|
|
80
|
+
// prefix so heading ids can never collide with the app's own element ids
|
|
81
|
+
// (e.g. a "## viewer" heading vs the <main id="viewer"> container)
|
|
82
|
+
tokens[i].attrSet('id', `pv-${unique}`);
|
|
81
83
|
}
|
|
82
84
|
});
|
|
83
85
|
|
package/src/server.js
CHANGED
|
@@ -11,6 +11,7 @@ import { buildIndex, updateFile, removeFile, search } from './search.js';
|
|
|
11
11
|
|
|
12
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
13
|
const PUBLIC_DIR = path.join(__dirname, '..', 'public');
|
|
14
|
+
const VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
14
15
|
// vendored client libs (mermaid bundle, hljs theme) live in public/vendor/ and
|
|
15
16
|
// are served by the static handler below - no node_modules resolution needed.
|
|
16
17
|
|
|
@@ -72,7 +73,7 @@ export async function startServer(root, { port = 4321, host = '127.0.0.1' } = {}
|
|
|
72
73
|
|
|
73
74
|
// --- file tree ---
|
|
74
75
|
if (pathname === '/api/tree') {
|
|
75
|
-
sendJson(res, { root: path.basename(root) || root, tree: buildTree(root) });
|
|
76
|
+
sendJson(res, { root: path.basename(root) || root, tree: buildTree(root), version: VERSION });
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
|