memory-extract 0.1.0 → 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.
- package/package.json +6 -6
- package/src/index.js +1 -1
- package/src/loadMemory.js +136 -20
- package/src/memoryPlay.js +246 -0
- package/src/payloadFormat.js +4 -1
- package/tools/buildPlayLauncher.mjs +30 -0
- package/tools/hostProject.mjs +129 -11
- package/tools/memoryFormat.mjs +13 -3
- package/tools/pack.mjs +155 -99
- package/tools/play.mjs +177 -0
- package/tools/playDirectory.mjs +156 -0
- package/tools/playLauncher.bundle.js +736 -0
- package/tools/playMemory.mjs +10 -0
- package/tools/unpack.mjs +18 -7
- package/tools/packCache.mjs +0 -62
- package/tools/resizeCover.mjs +0 -55
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-extract",
|
|
3
|
-
"description": "
|
|
3
|
+
"description": "Tool for embedding data into PNG images",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.1",
|
|
6
6
|
"author": "semigarden",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
},
|
|
23
23
|
"bin": {
|
|
24
24
|
"memory-pack": "./tools/pack.mjs",
|
|
25
|
-
"memory-unpack": "./tools/unpack.mjs"
|
|
25
|
+
"memory-unpack": "./tools/unpack.mjs",
|
|
26
|
+
"memory-play": "./tools/play.mjs"
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"pack": "node tools/pack.mjs",
|
|
29
30
|
"unpack": "node tools/unpack.mjs",
|
|
31
|
+
"play": "node tools/play.mjs",
|
|
32
|
+
"build:launcher": "node tools/buildPlayLauncher.mjs",
|
|
30
33
|
"test": "node --test test/memory.test.mjs"
|
|
31
|
-
},
|
|
32
|
-
"dependencies": {
|
|
33
|
-
"sharp": "^0.34.5"
|
|
34
34
|
}
|
|
35
35
|
}
|
package/src/index.js
CHANGED
package/src/loadMemory.js
CHANGED
|
@@ -3,6 +3,11 @@ import {
|
|
|
3
3
|
readMemoryFile,
|
|
4
4
|
extractMemory,
|
|
5
5
|
} from "./memoryFormat.js";
|
|
6
|
+
import {
|
|
7
|
+
buildInteractiveBrowseDocument,
|
|
8
|
+
buildMemoryBrowseListings,
|
|
9
|
+
resolveMemoryPlayMode,
|
|
10
|
+
} from "./memoryPlay.js";
|
|
6
11
|
|
|
7
12
|
const textDecoder = new TextDecoder();
|
|
8
13
|
|
|
@@ -87,9 +92,9 @@ const extractCssAssetPaths = (cssText) => {
|
|
|
87
92
|
return paths;
|
|
88
93
|
};
|
|
89
94
|
|
|
90
|
-
const collectRequiredFiles = (manifest, fileBytes) => {
|
|
95
|
+
const collectRequiredFiles = (manifest, fileBytes, entryPath) => {
|
|
91
96
|
const manifestPaths = listManifestFiles(manifest);
|
|
92
|
-
const required = new Set([
|
|
97
|
+
const required = new Set([entryPath]);
|
|
93
98
|
|
|
94
99
|
for (const filePath of manifestPaths) {
|
|
95
100
|
if (filePath.endsWith(".js") || filePath.endsWith(".css")) {
|
|
@@ -131,13 +136,61 @@ const collectRequiredFiles = (manifest, fileBytes) => {
|
|
|
131
136
|
return required;
|
|
132
137
|
};
|
|
133
138
|
|
|
134
|
-
const
|
|
139
|
+
const BLOB_LAUNCH_BASE = "http://localhost/";
|
|
140
|
+
|
|
141
|
+
const injectBlobLaunchShim = (doc) => {
|
|
142
|
+
const base = doc.createElement("base");
|
|
143
|
+
base.setAttribute("href", BLOB_LAUNCH_BASE);
|
|
144
|
+
doc.head.prepend(base);
|
|
145
|
+
|
|
146
|
+
const shim = doc.createElement("script");
|
|
147
|
+
shim.textContent = `(function () {
|
|
148
|
+
var base = ${JSON.stringify(BLOB_LAUNCH_BASE)};
|
|
149
|
+
var NativeURL = URL;
|
|
150
|
+
var invalidBase = function (value) {
|
|
151
|
+
if (!value) return true;
|
|
152
|
+
var text = String(value);
|
|
153
|
+
return text === "null" || text.indexOf("blob:") === 0;
|
|
154
|
+
};
|
|
155
|
+
URL = function (url, baseUrl) {
|
|
156
|
+
if (typeof url === "string" && url.charAt(0) === "/" && invalidBase(baseUrl)) {
|
|
157
|
+
return new NativeURL(url, base);
|
|
158
|
+
}
|
|
159
|
+
return new NativeURL(url, baseUrl);
|
|
160
|
+
};
|
|
161
|
+
URL.prototype = NativeURL.prototype;
|
|
162
|
+
URL.createObjectURL = NativeURL.createObjectURL.bind(NativeURL);
|
|
163
|
+
URL.revokeObjectURL = NativeURL.revokeObjectURL.bind(NativeURL);
|
|
164
|
+
})();`;
|
|
165
|
+
doc.head.prepend(shim);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
export const resolveLaunchAssetUrl = (rawPath, urlByPath, entryPath, manifestPaths) => {
|
|
169
|
+
const direct = resolveAssetUrl(rawPath, urlByPath);
|
|
170
|
+
if (direct) {
|
|
171
|
+
return direct;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!rawPath || !entryPath || !manifestPaths) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const resolved = resolveManifestPath(entryPath, rawPath, manifestPaths);
|
|
179
|
+
return resolved ? urlByPath.get(resolved) ?? null : null;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const buildLaunchDocument = (html, urlByPath, entryPath, manifestPaths) => {
|
|
135
183
|
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
136
184
|
|
|
137
185
|
REWRITABLE_ATTRIBUTES.forEach(([tag, attribute]) => {
|
|
138
186
|
doc.querySelectorAll(`${tag}[${attribute}]`).forEach((element) => {
|
|
139
187
|
const rawPath = element.getAttribute(attribute);
|
|
140
|
-
const assetUrl =
|
|
188
|
+
const assetUrl = resolveLaunchAssetUrl(
|
|
189
|
+
rawPath,
|
|
190
|
+
urlByPath,
|
|
191
|
+
entryPath,
|
|
192
|
+
manifestPaths
|
|
193
|
+
);
|
|
141
194
|
|
|
142
195
|
if (assetUrl) {
|
|
143
196
|
element.setAttribute(attribute, assetUrl);
|
|
@@ -151,6 +204,8 @@ const buildLaunchDocument = (html, urlByPath) => {
|
|
|
151
204
|
element.removeAttribute("crossorigin");
|
|
152
205
|
});
|
|
153
206
|
|
|
207
|
+
injectBlobLaunchShim(doc);
|
|
208
|
+
|
|
154
209
|
return `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
|
155
210
|
};
|
|
156
211
|
|
|
@@ -162,37 +217,98 @@ const getManifestFileMeta = (manifest, filePath) => {
|
|
|
162
217
|
return manifest.files?.[filePath] ?? null;
|
|
163
218
|
};
|
|
164
219
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
220
|
+
const attachBlobDispose = (blob, blobUrls) => {
|
|
221
|
+
blob.dispose = () => {
|
|
222
|
+
blobUrls.forEach((url) => URL.revokeObjectURL(url));
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return blob;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const createFileLaunchBlob = (manifest, fileBytes, filePath, blobUrls) => {
|
|
229
|
+
const fileMeta = getManifestFileMeta(manifest, filePath);
|
|
230
|
+
const fileContent = readMemoryFile(manifest, filePath, fileBytes);
|
|
231
|
+
const mime = guessMimeType(filePath, fileMeta);
|
|
232
|
+
const launchBlob = new Blob([fileContent], { type: mime });
|
|
233
|
+
|
|
234
|
+
return attachBlobDispose(launchBlob, blobUrls);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const createBrowseLaunchBlob = (manifest, fileBytes, filePaths, blobUrls) => {
|
|
238
|
+
const fileUrlByPath = new Map();
|
|
239
|
+
|
|
240
|
+
for (const filePath of filePaths) {
|
|
241
|
+
const fileMeta = getManifestFileMeta(manifest, filePath);
|
|
242
|
+
const fileContent = readMemoryFile(manifest, filePath, fileBytes);
|
|
243
|
+
const mime = guessMimeType(filePath, fileMeta);
|
|
244
|
+
const assetUrl = URL.createObjectURL(new Blob([fileContent], { type: mime }));
|
|
245
|
+
blobUrls.push(assetUrl);
|
|
246
|
+
fileUrlByPath.set(filePath, assetUrl);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const listings = buildMemoryBrowseListings(filePaths, fileUrlByPath);
|
|
250
|
+
const launchDocument = buildInteractiveBrowseDocument(listings);
|
|
251
|
+
const launchBlob = new Blob([launchDocument], { type: "text/html" });
|
|
252
|
+
|
|
253
|
+
return attachBlobDispose(launchBlob, blobUrls);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const createPlayLaunchBlob = (manifest, fileBytes, entryPath, blobUrls) => {
|
|
169
257
|
const urlByPath = new Map();
|
|
170
|
-
const requiredFiles = collectRequiredFiles(manifest, fileBytes);
|
|
258
|
+
const requiredFiles = collectRequiredFiles(manifest, fileBytes, entryPath);
|
|
171
259
|
|
|
172
260
|
for (const filePath of requiredFiles) {
|
|
173
261
|
const fileMeta = getManifestFileMeta(manifest, filePath);
|
|
174
262
|
const fileContent = readMemoryFile(manifest, filePath, fileBytes);
|
|
175
263
|
const mime = guessMimeType(filePath, fileMeta);
|
|
176
|
-
const
|
|
177
|
-
const assetUrl = URL.createObjectURL(assetBlob);
|
|
264
|
+
const assetUrl = URL.createObjectURL(new Blob([fileContent], { type: mime }));
|
|
178
265
|
blobUrls.push(assetUrl);
|
|
179
266
|
urlByPath.set(filePath, assetUrl);
|
|
180
267
|
urlByPath.set(normalizeAssetPath(filePath), assetUrl);
|
|
181
268
|
}
|
|
182
269
|
|
|
183
|
-
if (!requiredFiles.has(
|
|
184
|
-
|
|
185
|
-
throw new Error(`Manifest entry not found: ${manifest.entry}`);
|
|
270
|
+
if (!requiredFiles.has(entryPath)) {
|
|
271
|
+
throw new Error(`Manifest entry not found: ${entryPath}`);
|
|
186
272
|
}
|
|
187
273
|
|
|
188
|
-
const htmlBytes = readMemoryFile(manifest,
|
|
274
|
+
const htmlBytes = readMemoryFile(manifest, entryPath, fileBytes);
|
|
189
275
|
const html = textDecoder.decode(htmlBytes);
|
|
190
|
-
const
|
|
276
|
+
const manifestPaths = listManifestFiles(manifest);
|
|
277
|
+
const launchDocument = buildLaunchDocument(
|
|
278
|
+
html,
|
|
279
|
+
urlByPath,
|
|
280
|
+
entryPath,
|
|
281
|
+
manifestPaths
|
|
282
|
+
);
|
|
191
283
|
const launchBlob = new Blob([launchDocument], { type: "text/html" });
|
|
192
284
|
|
|
193
|
-
launchBlob
|
|
194
|
-
|
|
195
|
-
|
|
285
|
+
return attachBlobDispose(launchBlob, blobUrls);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
export const createMemoryBlob = async (input) => {
|
|
289
|
+
const bytes = await readInput(input);
|
|
290
|
+
const { manifest, fileBytes } = await extractMemory(bytes);
|
|
291
|
+
const filePaths = listManifestFiles(manifest);
|
|
292
|
+
const playMode = resolveMemoryPlayMode(manifest, filePaths);
|
|
293
|
+
const blobUrls = [];
|
|
196
294
|
|
|
197
|
-
|
|
295
|
+
try {
|
|
296
|
+
if (playMode.mode === "file") {
|
|
297
|
+
return createFileLaunchBlob(manifest, fileBytes, playMode.path, blobUrls);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (playMode.mode === "browse") {
|
|
301
|
+
return createBrowseLaunchBlob(manifest, fileBytes, filePaths, blobUrls);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return createPlayLaunchBlob(
|
|
305
|
+
manifest,
|
|
306
|
+
fileBytes,
|
|
307
|
+
playMode.entry ?? manifest.entry,
|
|
308
|
+
blobUrls
|
|
309
|
+
);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
blobUrls.forEach((url) => URL.revokeObjectURL(url));
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
198
314
|
};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
export const INDEX_NAMES = ["index.html", "index.htm"];
|
|
2
|
+
|
|
3
|
+
export const isHtmlEntry = (filePath) =>
|
|
4
|
+
filePath.endsWith(".html") || filePath.endsWith(".htm");
|
|
5
|
+
|
|
6
|
+
export const resolveMemoryPlayMode = (
|
|
7
|
+
manifest,
|
|
8
|
+
filePaths
|
|
9
|
+
) => {
|
|
10
|
+
if (filePaths.length === 1) {
|
|
11
|
+
return { mode: "file", path: filePaths[0] };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const indexEntry = filePaths.find(
|
|
15
|
+
(filePath) =>
|
|
16
|
+
filePath === "index.html" ||
|
|
17
|
+
filePath.endsWith("/index.html") ||
|
|
18
|
+
filePath === "index.htm" ||
|
|
19
|
+
filePath.endsWith("/index.htm")
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (indexEntry) {
|
|
23
|
+
return { mode: "play", entry: indexEntry };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (filePaths.includes(manifest.entry) && isHtmlEntry(manifest.entry)) {
|
|
27
|
+
return { mode: "play", entry: manifest.entry };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { mode: "browse" };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const urlPathToPrefix = (urlPath) =>
|
|
34
|
+
urlPath === "/" ? "" : urlPath.slice(1);
|
|
35
|
+
|
|
36
|
+
export const listVirtualEntries = (filePaths, urlPath = "/") => {
|
|
37
|
+
const prefix = urlPathToPrefix(urlPath);
|
|
38
|
+
const entries = new Map();
|
|
39
|
+
|
|
40
|
+
for (const filePath of filePaths) {
|
|
41
|
+
if (prefix && !filePath.startsWith(`${prefix}/`)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const rest = prefix ? filePath.slice(prefix.length + 1) : filePath;
|
|
46
|
+
if (!rest) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const slash = rest.indexOf("/");
|
|
51
|
+
|
|
52
|
+
if (slash === -1) {
|
|
53
|
+
entries.set(rest, { name: rest, isDirectory: false });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
entries.set(rest.slice(0, slash), {
|
|
58
|
+
name: rest.slice(0, slash),
|
|
59
|
+
isDirectory: true,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return [...entries.values()];
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const collectVirtualDirectoryPaths = (filePaths) => {
|
|
67
|
+
const dirs = new Set(["/"]);
|
|
68
|
+
|
|
69
|
+
for (const filePath of filePaths) {
|
|
70
|
+
const parts = filePath.split("/");
|
|
71
|
+
|
|
72
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
73
|
+
dirs.add(`/${parts.slice(0, index).join("/")}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return [...dirs].sort(
|
|
78
|
+
(left, right) => right.split("/").length - left.split("/").length
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const parentVirtualPath = (urlPath) => {
|
|
83
|
+
if (urlPath === "/") {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const trimmed = urlPath.replace(/\/+$/, "");
|
|
88
|
+
const slash = trimmed.lastIndexOf("/");
|
|
89
|
+
return slash <= 0 ? "/" : trimmed.slice(0, slash);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const buildBrowseListingDocument = (
|
|
93
|
+
urlPath,
|
|
94
|
+
entries,
|
|
95
|
+
{ showParent = false, parentHref = "../" } = {}
|
|
96
|
+
) => {
|
|
97
|
+
const sorted = [...entries].sort((left, right) => {
|
|
98
|
+
if (left.isDirectory !== right.isDirectory) {
|
|
99
|
+
return left.isDirectory ? -1 : 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return left.name.localeCompare(right.name);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const rows = sorted.map((entry) => {
|
|
106
|
+
const suffix = entry.isDirectory ? "/" : "";
|
|
107
|
+
return `<li><a href="${entry.href}">${entry.name}${suffix}</a></li>`;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (showParent) {
|
|
111
|
+
rows.unshift(`<li><a href="${parentHref}">../</a></li>`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return `<!DOCTYPE html>
|
|
115
|
+
<html lang="en">
|
|
116
|
+
<head>
|
|
117
|
+
<meta charset="utf-8" />
|
|
118
|
+
<title>Index of ${urlPath}</title>
|
|
119
|
+
<style>
|
|
120
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; }
|
|
121
|
+
h1 { font-size: 1.1rem; font-weight: 600; }
|
|
122
|
+
ul { list-style: none; padding: 0; }
|
|
123
|
+
li { margin: 0.35rem 0; }
|
|
124
|
+
a { text-decoration: none; }
|
|
125
|
+
a:hover { text-decoration: underline; }
|
|
126
|
+
</style>
|
|
127
|
+
</head>
|
|
128
|
+
<body>
|
|
129
|
+
<h1>Index of ${urlPath}</h1>
|
|
130
|
+
<ul>
|
|
131
|
+
${rows.join("\n ")}
|
|
132
|
+
</ul>
|
|
133
|
+
</body>
|
|
134
|
+
</html>
|
|
135
|
+
`;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const buildMemoryBrowseListings = (filePaths, fileUrlByPath) => {
|
|
139
|
+
const listings = {};
|
|
140
|
+
|
|
141
|
+
for (const urlPath of collectVirtualDirectoryPaths(filePaths)) {
|
|
142
|
+
const prefix = urlPath === "/" ? "" : urlPath.slice(1);
|
|
143
|
+
|
|
144
|
+
listings[urlPath] = listVirtualEntries(filePaths, urlPath).map((entry) => {
|
|
145
|
+
const fullPath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
146
|
+
|
|
147
|
+
if (entry.isDirectory) {
|
|
148
|
+
return {
|
|
149
|
+
name: entry.name,
|
|
150
|
+
isDirectory: true,
|
|
151
|
+
path: `/${fullPath}`.replace(/\/+/g, "/"),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
name: entry.name,
|
|
157
|
+
isDirectory: false,
|
|
158
|
+
href: fileUrlByPath.get(fullPath) ?? "#",
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return listings;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export const buildInteractiveBrowseDocument = (listings, rootPath = "/") => `<!DOCTYPE html>
|
|
167
|
+
<html lang="en">
|
|
168
|
+
<head>
|
|
169
|
+
<meta charset="utf-8" />
|
|
170
|
+
<title>Index of ${rootPath}</title>
|
|
171
|
+
<style>
|
|
172
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; }
|
|
173
|
+
h1 { font-size: 1.1rem; font-weight: 600; }
|
|
174
|
+
ul { list-style: none; padding: 0; }
|
|
175
|
+
li { margin: 0.35rem 0; }
|
|
176
|
+
a { text-decoration: none; color: inherit; }
|
|
177
|
+
a:hover { text-decoration: underline; }
|
|
178
|
+
</style>
|
|
179
|
+
</head>
|
|
180
|
+
<body>
|
|
181
|
+
<h1 id="title">Index of ${rootPath}</h1>
|
|
182
|
+
<ul id="list"></ul>
|
|
183
|
+
<script>
|
|
184
|
+
const listings = ${JSON.stringify(listings)};
|
|
185
|
+
const title = document.getElementById("title");
|
|
186
|
+
const list = document.getElementById("list");
|
|
187
|
+
|
|
188
|
+
const parentPath = (path) => {
|
|
189
|
+
if (path === "/") return "/";
|
|
190
|
+
const trimmed = path.replace(/\\/+$/, "");
|
|
191
|
+
const slash = trimmed.lastIndexOf("/");
|
|
192
|
+
return slash <= 0 ? "/" : trimmed.slice(0, slash);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const render = (path) => {
|
|
196
|
+
const entries = listings[path] || [];
|
|
197
|
+
title.textContent = "Index of " + path;
|
|
198
|
+
list.replaceChildren();
|
|
199
|
+
|
|
200
|
+
if (path !== "/") {
|
|
201
|
+
const parentItem = document.createElement("li");
|
|
202
|
+
const parentLink = document.createElement("a");
|
|
203
|
+
parentLink.href = "#";
|
|
204
|
+
parentLink.textContent = "../";
|
|
205
|
+
parentLink.addEventListener("click", (event) => {
|
|
206
|
+
event.preventDefault();
|
|
207
|
+
render(parentPath(path));
|
|
208
|
+
});
|
|
209
|
+
parentItem.appendChild(parentLink);
|
|
210
|
+
list.appendChild(parentItem);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
entries
|
|
214
|
+
.slice()
|
|
215
|
+
.sort((left, right) => {
|
|
216
|
+
if (left.isDirectory !== right.isDirectory) {
|
|
217
|
+
return left.isDirectory ? -1 : 1;
|
|
218
|
+
}
|
|
219
|
+
return left.name.localeCompare(right.name);
|
|
220
|
+
})
|
|
221
|
+
.forEach((entry) => {
|
|
222
|
+
const item = document.createElement("li");
|
|
223
|
+
const link = document.createElement("a");
|
|
224
|
+
|
|
225
|
+
if (entry.isDirectory) {
|
|
226
|
+
link.href = "#";
|
|
227
|
+
link.textContent = entry.name + "/";
|
|
228
|
+
link.addEventListener("click", (event) => {
|
|
229
|
+
event.preventDefault();
|
|
230
|
+
render(entry.path);
|
|
231
|
+
});
|
|
232
|
+
} else {
|
|
233
|
+
link.href = entry.href;
|
|
234
|
+
link.textContent = entry.name;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
item.appendChild(link);
|
|
238
|
+
list.appendChild(item);
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
render(${JSON.stringify(rootPath)});
|
|
243
|
+
</script>
|
|
244
|
+
</body>
|
|
245
|
+
</html>
|
|
246
|
+
`;
|
package/src/payloadFormat.js
CHANGED
|
@@ -83,6 +83,7 @@ export const buildV2Manifest = ({
|
|
|
83
83
|
files,
|
|
84
84
|
kind = "web-app",
|
|
85
85
|
runtime = "iframe-sandbox",
|
|
86
|
+
source = "",
|
|
86
87
|
}) => {
|
|
87
88
|
const sortedPaths = Object.keys(files).sort();
|
|
88
89
|
|
|
@@ -93,6 +94,7 @@ export const buildV2Manifest = ({
|
|
|
93
94
|
name,
|
|
94
95
|
entry,
|
|
95
96
|
runtime,
|
|
97
|
+
...(source ? { source } : {}),
|
|
96
98
|
files: sortedPaths.map((filePath) => {
|
|
97
99
|
const bytes = toUint8Array(files[filePath]);
|
|
98
100
|
return {
|
|
@@ -106,13 +108,14 @@ export const buildV2Manifest = ({
|
|
|
106
108
|
};
|
|
107
109
|
};
|
|
108
110
|
|
|
109
|
-
export const encodeV2Archive = ({ name, entry, files, kind, runtime }) => {
|
|
111
|
+
export const encodeV2Archive = ({ name, entry, files, kind, runtime, source }) => {
|
|
110
112
|
const { manifest, sortedPaths } = buildV2Manifest({
|
|
111
113
|
name,
|
|
112
114
|
entry,
|
|
113
115
|
files,
|
|
114
116
|
kind,
|
|
115
117
|
runtime,
|
|
118
|
+
source,
|
|
116
119
|
});
|
|
117
120
|
const json = new TextEncoder().encode(JSON.stringify(manifest));
|
|
118
121
|
const header = new Uint8Array(4);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const toolsDir = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const packageRoot = path.dirname(toolsDir);
|
|
8
|
+
const outfile = path.join(toolsDir, "playLauncher.bundle.js");
|
|
9
|
+
|
|
10
|
+
const result = spawnSync(
|
|
11
|
+
"npx",
|
|
12
|
+
[
|
|
13
|
+
"--yes",
|
|
14
|
+
"esbuild",
|
|
15
|
+
path.join(packageRoot, "src/index.js"),
|
|
16
|
+
"--bundle",
|
|
17
|
+
"--platform=browser",
|
|
18
|
+
"--format=iife",
|
|
19
|
+
"--global-name=MemoryExtract",
|
|
20
|
+
"--target=chrome109,firefox115,safari16",
|
|
21
|
+
`--outfile=${outfile}`,
|
|
22
|
+
],
|
|
23
|
+
{ cwd: packageRoot, stdio: "inherit" }
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (result.status !== 0) {
|
|
27
|
+
process.exit(result.status ?? 1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log("Built tools/playLauncher.bundle.js");
|