@gjsify/tar 0.3.12 → 0.3.14
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/lib/esm/extract.js +95 -92
- package/lib/esm/index.js +3 -12
- package/lib/esm/parser.js +166 -177
- package/package.json +3 -3
package/lib/esm/extract.js
CHANGED
|
@@ -1,102 +1,105 @@
|
|
|
1
|
+
import { parseTar } from "./parser.js";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
|
-
|
|
4
|
+
|
|
5
|
+
//#region src/extract.ts
|
|
6
|
+
/** Extract a `.tar` or `.tar.gz` buffer into `destDir`, creating dirs as needed. */
|
|
4
7
|
async function extractTarball(input, destDir, opts = {}) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
8
|
+
const buf = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
9
|
+
const isGz = opts.gzip ?? (buf.length >= 2 && buf[0] === 31 && buf[1] === 139);
|
|
10
|
+
const tarBytes = isGz ? await gunzip(buf) : buf;
|
|
11
|
+
const entries = parseTar(tarBytes);
|
|
12
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
13
|
+
const strip = opts.strip ?? 1;
|
|
14
|
+
const preventEscape = opts.preventEscape ?? true;
|
|
15
|
+
const result = {
|
|
16
|
+
files: [],
|
|
17
|
+
directories: [],
|
|
18
|
+
symlinks: [],
|
|
19
|
+
skipped: 0
|
|
20
|
+
};
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const stripped = stripComponents(entry.name, strip);
|
|
23
|
+
if (stripped === null || stripped === "") {
|
|
24
|
+
result.skipped++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const resolved = path.resolve(destDir, stripped);
|
|
28
|
+
if (preventEscape && !isInside(resolved, destDir)) {
|
|
29
|
+
throw new Error(`tar: refusing to extract ${entry.name} outside ${destDir} (resolved=${resolved})`);
|
|
30
|
+
}
|
|
31
|
+
if (opts.filter && !opts.filter(entry, resolved)) {
|
|
32
|
+
result.skipped++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (entry.type === "directory") {
|
|
36
|
+
fs.mkdirSync(resolved, { recursive: true });
|
|
37
|
+
result.directories.push(resolved);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (entry.type === "file") {
|
|
41
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
42
|
+
fs.writeFileSync(resolved, entry.body);
|
|
43
|
+
const overrideMode = opts.chmod?.(entry, resolved);
|
|
44
|
+
const finalMode = overrideMode ?? entry.mode & 511;
|
|
45
|
+
if (finalMode > 0) {
|
|
46
|
+
try {
|
|
47
|
+
fs.chmodSync(resolved, finalMode);
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
result.files.push(resolved);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (entry.type === "symlink") {
|
|
54
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
55
|
+
try {
|
|
56
|
+
fs.symlinkSync(entry.linkname, resolved);
|
|
57
|
+
result.symlinks.push(resolved);
|
|
58
|
+
} catch {
|
|
59
|
+
fs.writeFileSync(resolved, entry.linkname);
|
|
60
|
+
result.files.push(resolved);
|
|
61
|
+
}
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
result.skipped++;
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
62
67
|
}
|
|
68
|
+
/** Decompress a gzip buffer using Web DecompressionStream (cross-platform). */
|
|
63
69
|
async function gunzip(input) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
return out;
|
|
70
|
+
const Decomp = globalThis.DecompressionStream;
|
|
71
|
+
if (typeof Decomp !== "function") {
|
|
72
|
+
throw new Error("@gjsify/tar: globalThis.DecompressionStream is not available — " + "import '@gjsify/compression-streams/register' on GJS to register it");
|
|
73
|
+
}
|
|
74
|
+
const stream = new Blob([new Uint8Array(input)]).stream().pipeThrough(new Decomp("gzip"));
|
|
75
|
+
const chunks = [];
|
|
76
|
+
let total = 0;
|
|
77
|
+
const reader = stream.getReader();
|
|
78
|
+
for (;;) {
|
|
79
|
+
const { value, done } = await reader.read();
|
|
80
|
+
if (done) break;
|
|
81
|
+
const chunk = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
82
|
+
chunks.push(chunk);
|
|
83
|
+
total += chunk.length;
|
|
84
|
+
}
|
|
85
|
+
const out = new Uint8Array(total);
|
|
86
|
+
let pos = 0;
|
|
87
|
+
for (const c of chunks) {
|
|
88
|
+
out.set(c, pos);
|
|
89
|
+
pos += c.length;
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
88
92
|
}
|
|
89
93
|
function stripComponents(name, n) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
if (n <= 0) return name;
|
|
95
|
+
const parts = name.split("/").filter((s) => s !== "");
|
|
96
|
+
if (parts.length <= n) return null;
|
|
97
|
+
return parts.slice(n).join("/");
|
|
94
98
|
}
|
|
95
99
|
function isInside(child, parent) {
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
const rel = path.relative(parent, child);
|
|
101
|
+
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
|
|
98
102
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
};
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
export { extractTarball, gunzip };
|
package/lib/esm/index.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BLOCK_SIZE,
|
|
3
|
-
parseTar,
|
|
4
|
-
TarParseError
|
|
5
|
-
} from "./parser.js";
|
|
1
|
+
import { BLOCK_SIZE, TarParseError, parseTar } from "./parser.js";
|
|
6
2
|
import { extractTarball, gunzip } from "./extract.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
TarParseError,
|
|
10
|
-
extractTarball,
|
|
11
|
-
gunzip,
|
|
12
|
-
parseTar
|
|
13
|
-
};
|
|
3
|
+
|
|
4
|
+
export { BLOCK_SIZE, TarParseError, extractTarball, gunzip, parseTar };
|
package/lib/esm/parser.js
CHANGED
|
@@ -1,199 +1,188 @@
|
|
|
1
|
+
//#region src/parser.ts
|
|
1
2
|
const BLOCK_SIZE = 512;
|
|
3
|
+
/** Parse a tar archive (already-uncompressed) into entries. */
|
|
2
4
|
function parseTar(buf) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
return out;
|
|
5
|
+
const out = [];
|
|
6
|
+
let pendingPaxHeader = null;
|
|
7
|
+
let pendingLongName = null;
|
|
8
|
+
let pendingLongLink = null;
|
|
9
|
+
let offset = 0;
|
|
10
|
+
while (offset + 512 <= buf.length) {
|
|
11
|
+
const header = buf.subarray(offset, offset + 512);
|
|
12
|
+
if (allZeros(header)) {
|
|
13
|
+
const next = buf.subarray(offset + 512, offset + 2 * 512);
|
|
14
|
+
if (next.length === 512 && allZeros(next)) break;
|
|
15
|
+
offset += 512;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (!validateChecksum(header)) {
|
|
19
|
+
throw new TarParseError(`Bad header checksum at offset ${offset} — file is not a valid tar archive`);
|
|
20
|
+
}
|
|
21
|
+
const rawName = readString(header, 0, 100);
|
|
22
|
+
const mode = parseOctal(header, 100, 8);
|
|
23
|
+
const size = parseOctal(header, 124, 12);
|
|
24
|
+
const mtime = parseOctal(header, 136, 12);
|
|
25
|
+
const typeflag = String.fromCharCode(header[156] || 0);
|
|
26
|
+
const linkname = readString(header, 157, 100);
|
|
27
|
+
const magic = readString(header, 257, 6);
|
|
28
|
+
const prefix = readString(header, 345, 155);
|
|
29
|
+
const uname = readString(header, 265, 32);
|
|
30
|
+
const gname = readString(header, 297, 32);
|
|
31
|
+
offset += 512;
|
|
32
|
+
const body = buf.subarray(offset, offset + size);
|
|
33
|
+
offset += alignToBlock(size);
|
|
34
|
+
if (typeflag === "x") {
|
|
35
|
+
pendingPaxHeader = parsePaxRecords(body);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (typeflag === "g") {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (typeflag === "L") {
|
|
42
|
+
pendingLongName = bytesToString(body).replace(/\0+$/, "");
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (typeflag === "K") {
|
|
46
|
+
pendingLongLink = bytesToString(body).replace(/\0+$/, "");
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
let name = rawName;
|
|
50
|
+
if (magic === "ustar" && prefix !== "") {
|
|
51
|
+
name = `${prefix}/${rawName}`;
|
|
52
|
+
}
|
|
53
|
+
if (pendingLongName !== null) {
|
|
54
|
+
name = pendingLongName;
|
|
55
|
+
pendingLongName = null;
|
|
56
|
+
}
|
|
57
|
+
let resolvedLink = linkname;
|
|
58
|
+
if (pendingLongLink !== null) {
|
|
59
|
+
resolvedLink = pendingLongLink;
|
|
60
|
+
pendingLongLink = null;
|
|
61
|
+
}
|
|
62
|
+
if (pendingPaxHeader !== null) {
|
|
63
|
+
const paxName = pendingPaxHeader.get("path");
|
|
64
|
+
if (paxName !== undefined) name = paxName;
|
|
65
|
+
const paxLink = pendingPaxHeader.get("linkpath");
|
|
66
|
+
if (paxLink !== undefined) resolvedLink = paxLink;
|
|
67
|
+
const paxSize = pendingPaxHeader.get("size");
|
|
68
|
+
if (paxSize !== undefined) {
|
|
69
|
+
const overrideSize = Number(paxSize);
|
|
70
|
+
if (Number.isFinite(overrideSize)) {
|
|
71
|
+
const realStart = offset - alignToBlock(size);
|
|
72
|
+
const sliced = buf.subarray(realStart, realStart + overrideSize);
|
|
73
|
+
offset = realStart + alignToBlock(overrideSize);
|
|
74
|
+
pendingPaxHeader = null;
|
|
75
|
+
out.push(buildEntry(name, resolvedLink, typeflag, mode, mtime, uname, gname, sliced));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
pendingPaxHeader = null;
|
|
80
|
+
}
|
|
81
|
+
out.push(buildEntry(name, resolvedLink, typeflag, mode, mtime, uname, gname, body));
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
84
|
}
|
|
85
85
|
function buildEntry(name, linkname, typeflag, mode, mtime, uname, gname, body) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
return {
|
|
87
|
+
name,
|
|
88
|
+
linkname,
|
|
89
|
+
type: typeflagToType(typeflag, name),
|
|
90
|
+
mode,
|
|
91
|
+
mtime,
|
|
92
|
+
body,
|
|
93
|
+
uname,
|
|
94
|
+
gname
|
|
95
|
+
};
|
|
96
96
|
}
|
|
97
97
|
function typeflagToType(typeflag, name) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
case "g":
|
|
112
|
-
return "pax-global";
|
|
113
|
-
case "L":
|
|
114
|
-
return "gnu-longname";
|
|
115
|
-
case "K":
|
|
116
|
-
return "gnu-longlink";
|
|
117
|
-
default:
|
|
118
|
-
return "unknown";
|
|
119
|
-
}
|
|
98
|
+
switch (typeflag) {
|
|
99
|
+
case "0":
|
|
100
|
+
case "\0":
|
|
101
|
+
case "": return name.endsWith("/") ? "directory" : "file";
|
|
102
|
+
case "1": return "hardlink";
|
|
103
|
+
case "2": return "symlink";
|
|
104
|
+
case "5": return "directory";
|
|
105
|
+
case "x": return "pax-header";
|
|
106
|
+
case "g": return "pax-global";
|
|
107
|
+
case "L": return "gnu-longname";
|
|
108
|
+
case "K": return "gnu-longlink";
|
|
109
|
+
default: return "unknown";
|
|
110
|
+
}
|
|
120
111
|
}
|
|
121
112
|
function parsePaxRecords(body) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
113
|
+
const out = new Map();
|
|
114
|
+
let i = 0;
|
|
115
|
+
while (i < body.length) {
|
|
116
|
+
let space = i;
|
|
117
|
+
while (space < body.length && body[space] !== 32) space++;
|
|
118
|
+
if (space >= body.length) break;
|
|
119
|
+
const lenStr = bytesToString(body.subarray(i, space));
|
|
120
|
+
const len = Number(lenStr);
|
|
121
|
+
if (!Number.isFinite(len) || len <= 0) break;
|
|
122
|
+
const recordEnd = i + len;
|
|
123
|
+
if (recordEnd > body.length) break;
|
|
124
|
+
const recBytes = body.subarray(space + 1, recordEnd - 1);
|
|
125
|
+
const recText = bytesToString(recBytes);
|
|
126
|
+
const eq = recText.indexOf("=");
|
|
127
|
+
if (eq > 0) {
|
|
128
|
+
const key = recText.slice(0, eq);
|
|
129
|
+
const value = recText.slice(eq + 1);
|
|
130
|
+
out.set(key, value);
|
|
131
|
+
}
|
|
132
|
+
i = recordEnd;
|
|
133
|
+
}
|
|
134
|
+
return out;
|
|
144
135
|
}
|
|
145
136
|
function readString(buf, start, len) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
137
|
+
let end = start;
|
|
138
|
+
const limit = start + len;
|
|
139
|
+
while (end < limit && buf[end] !== 0) end++;
|
|
140
|
+
return bytesToString(buf.subarray(start, end));
|
|
150
141
|
}
|
|
151
142
|
function bytesToString(buf) {
|
|
152
|
-
|
|
143
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(buf);
|
|
153
144
|
}
|
|
154
145
|
function parseOctal(buf, start, len) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
146
|
+
if (len > 0 && (buf[start] & 128) !== 0) {
|
|
147
|
+
let n = buf[start] & 127;
|
|
148
|
+
for (let i = 1; i < len; i++) n = n * 256 + buf[start + i];
|
|
149
|
+
return n;
|
|
150
|
+
}
|
|
151
|
+
let s = "";
|
|
152
|
+
for (let i = 0; i < len; i++) {
|
|
153
|
+
const c = buf[start + i];
|
|
154
|
+
if (c === 0 || c === 32) continue;
|
|
155
|
+
s += String.fromCharCode(c);
|
|
156
|
+
}
|
|
157
|
+
if (s === "") return 0;
|
|
158
|
+
return parseInt(s, 8);
|
|
168
159
|
}
|
|
169
160
|
function alignToBlock(n) {
|
|
170
|
-
|
|
161
|
+
return Math.ceil(n / 512) * 512;
|
|
171
162
|
}
|
|
172
163
|
function allZeros(buf) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
164
|
+
for (let i = 0; i < buf.length; i++) {
|
|
165
|
+
if (buf[i] !== 0) return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
177
168
|
}
|
|
178
169
|
function validateChecksum(header) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
170
|
+
const stored = parseOctal(header, 148, 8);
|
|
171
|
+
let unsigned = 0;
|
|
172
|
+
let signed = 0;
|
|
173
|
+
for (let i = 0; i < 512; i++) {
|
|
174
|
+
const byte = i >= 148 && i < 156 ? 32 : header[i];
|
|
175
|
+
unsigned += byte;
|
|
176
|
+
signed += byte > 127 ? byte - 256 : byte;
|
|
177
|
+
}
|
|
178
|
+
return stored === unsigned || stored === signed;
|
|
188
179
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
export {
|
|
196
|
-
BLOCK_SIZE,
|
|
197
|
-
TarParseError,
|
|
198
|
-
parseTar
|
|
180
|
+
var TarParseError = class extends Error {
|
|
181
|
+
constructor(msg) {
|
|
182
|
+
super(msg);
|
|
183
|
+
this.name = "TarParseError";
|
|
184
|
+
}
|
|
199
185
|
};
|
|
186
|
+
|
|
187
|
+
//#endregion
|
|
188
|
+
export { BLOCK_SIZE, TarParseError, parseTar };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/tar",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Streaming .tar / .tar.gz reader for the gjsify install backend (Node + GJS)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
],
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@gjsify/cli": "^0.3.
|
|
38
|
-
"@gjsify/unit": "^0.3.
|
|
37
|
+
"@gjsify/cli": "^0.3.14",
|
|
38
|
+
"@gjsify/unit": "^0.3.14",
|
|
39
39
|
"typescript": "^6.0.3"
|
|
40
40
|
}
|
|
41
41
|
}
|