fluxplugin 1.0.3 → 1.0.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/README.md +15 -12
- package/dist/browser/chunks/chunk-AV4KI7LI.js +1792 -0
- package/dist/browser/chunks/{chunk-TL63576R.js → chunk-OAYWYU5O.js} +48 -11
- package/dist/browser/index.js +3 -1
- package/dist/browser/manager-ui.js +386 -185
- package/dist/browser/react.js +46 -2
- package/dist/cli.js +3622 -257
- package/dist/node/chunks/{chunk-EVX2O65A.js → chunk-SDCGUBEW.js} +49 -11
- package/dist/node/chunks/{chunk-C7VWZZOX.js → chunk-ZLA5XUXN.js} +175 -51
- package/dist/node/cli-api.js +742 -76
- package/dist/node/index.js +3 -1
- package/dist/node/main.js +8 -2
- package/dist/node/preload.js +1 -1
- package/dist/types/cli/build.d.ts +5 -0
- package/dist/types/cli/cli.d.ts +2 -0
- package/dist/types/cli/dist.d.ts +18 -0
- package/dist/types/cli/host-templates.d.ts +2 -0
- package/dist/types/cli/store.d.ts +13 -0
- package/dist/types/cli/templates.d.ts +2 -0
- package/dist/types/core/manifest.d.ts +15 -1
- package/dist/types/core/protocol.d.ts +18 -5
- package/dist/types/core/registry.d.ts +11 -0
- package/dist/types/core/routes.d.ts +3 -1
- package/dist/types/main/adapters.d.ts +3 -0
- package/dist/types/main/assets.d.ts +8 -0
- package/dist/types/main/github-registry.d.ts +2 -1
- package/dist/types/main/index.d.ts +1 -0
- package/dist/types/main/registry.d.ts +55 -18
- package/dist/types/main/stores.d.ts +7 -1
- package/dist/types/manager-ui/index.d.ts +35 -3
- package/dist/types/react/base-css.d.ts +6 -2
- package/dist/types/react/http-bridge.d.ts +5 -0
- package/dist/types/react/i18n.d.ts +33 -0
- package/dist/types/react/index.d.ts +5 -0
- package/dist/types/react/markdown.d.ts +2 -1
- package/dist/types/react/media.d.ts +33 -0
- package/dist/types/react/settings.d.ts +6 -0
- package/dist/types/react/sidebar.d.ts +42 -0
- package/dist/types/react/theme.d.ts +42 -4
- package/dist/types/react/ui.d.ts +42 -0
- package/docs/CUSTOMIZING.md +56 -0
- package/docs/STORE.md +19 -0
- package/docs/screenshots/plugin-page.png +0 -0
- package/docs/screenshots/settings-appearance.png +0 -0
- package/docs/screenshots/site-guide-dark.png +0 -0
- package/docs/screenshots/site-home.png +0 -0
- package/docs/screenshots/site-mobile.png +0 -0
- package/docs/screenshots/site-search.png +0 -0
- package/package.json +1 -1
- package/dist/browser/chunks/chunk-VXQJIDXW.js +0 -1232
|
@@ -63,6 +63,7 @@ __export(core_exports, {
|
|
|
63
63
|
normalizePath: () => normalizePath,
|
|
64
64
|
parseOrThrow: () => parseOrThrow,
|
|
65
65
|
parseVersion: () => parseVersion,
|
|
66
|
+
parseVideo: () => parseVideo,
|
|
66
67
|
pluginApi: () => pluginApi,
|
|
67
68
|
preloadMember: () => preloadMember,
|
|
68
69
|
routePatternMatches: () => routePatternMatches,
|
|
@@ -610,7 +611,7 @@ var manifestSchema = s.object({
|
|
|
610
611
|
/** Detailed description as Markdown (default: ./README.md). Rendered safely: no raw HTML. */
|
|
611
612
|
readme: s.string().optional(),
|
|
612
613
|
/** Explanation video (https URL; YouTube and Vimeo are embedded, other hosts become a link). */
|
|
613
|
-
video: s.string({ pattern: /^https:\/\/\S
|
|
614
|
+
video: s.string({ pattern: /^(https:\/\/\S+|\.\/\S+)$/ }).optional(),
|
|
614
615
|
categories: s.array(s.string()).default([]),
|
|
615
616
|
keywords: s.array(s.string()).default([]),
|
|
616
617
|
/** Extra links: docs, support, changelog, funding... */
|
|
@@ -671,24 +672,33 @@ function validateManifest(raw, opts = {}) {
|
|
|
671
672
|
}
|
|
672
673
|
return { ok: issues.length === 0, manifest: issues.length ? void 0 : manifest, issues, warnings };
|
|
673
674
|
}
|
|
674
|
-
|
|
675
|
+
var MEDIA_EXT = /\.(mp4|m4v|webm|ogv|ogg|mov)(\?.*)?$/i;
|
|
676
|
+
function parseVideo(url) {
|
|
675
677
|
if (!url) return void 0;
|
|
678
|
+
if (url.startsWith("./")) return MEDIA_EXT.test(url) ? { kind: "html5", url, src: url } : void 0;
|
|
676
679
|
let u;
|
|
677
680
|
try {
|
|
678
681
|
u = new URL(url);
|
|
679
682
|
} catch {
|
|
680
683
|
return void 0;
|
|
681
684
|
}
|
|
685
|
+
if (u.protocol !== "https:") return void 0;
|
|
686
|
+
if (MEDIA_EXT.test(u.pathname)) return { kind: "html5", url, src: url };
|
|
682
687
|
const host = u.hostname.replace(/^www\./, "");
|
|
688
|
+
const last = u.pathname.split("/").filter(Boolean).pop() ?? "";
|
|
683
689
|
let id;
|
|
684
690
|
if (host === "youtu.be") id = u.pathname.slice(1);
|
|
685
|
-
else if (host === "youtube.com" || host === "m.youtube.com") id = u.searchParams.get("v") ?? (/^\/(embed|shorts)\//.test(u.pathname) ? u.pathname.split("/")[2] : void 0);
|
|
686
|
-
if (id && /^[\w-]{6,20}$/.test(id)) return `https://www.youtube-nocookie.com/embed/${id}
|
|
687
|
-
if (host === "vimeo.com" || host === "player.vimeo.com") {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
return
|
|
691
|
+
else if (host === "youtube.com" || host === "m.youtube.com" || host === "youtube-nocookie.com") id = u.searchParams.get("v") ?? (/^\/(embed|shorts|live)\//.test(u.pathname) ? u.pathname.split("/")[2] : void 0);
|
|
692
|
+
if (id && /^[\w-]{6,20}$/.test(id)) return { kind: "youtube", url, embedUrl: `https://www.youtube-nocookie.com/embed/${id}` };
|
|
693
|
+
if ((host === "vimeo.com" || host === "player.vimeo.com") && /^\d+$/.test(last)) return { kind: "vimeo", url, embedUrl: `https://player.vimeo.com/video/${last}` };
|
|
694
|
+
if (host === "dailymotion.com" && /^\/(embed\/)?video\//.test(u.pathname) && /^\w+$/.test(last)) return { kind: "dailymotion", url, embedUrl: `https://www.dailymotion.com/embed/video/${last}` };
|
|
695
|
+
if (host === "dai.ly" && /^\w+$/.test(last)) return { kind: "dailymotion", url, embedUrl: `https://www.dailymotion.com/embed/video/${last}` };
|
|
696
|
+
if (host === "loom.com" && /^\/(share|embed)\//.test(u.pathname) && /^[\w-]+$/.test(last)) return { kind: "loom", url, embedUrl: `https://www.loom.com/embed/${last}` };
|
|
697
|
+
if (host === "streamable.com" && /^\w+$/.test(last)) return { kind: "streamable", url, embedUrl: `https://streamable.com/e/${last}` };
|
|
698
|
+
return { kind: "link", url };
|
|
699
|
+
}
|
|
700
|
+
function videoEmbedUrl(url) {
|
|
701
|
+
return parseVideo(url)?.embedUrl;
|
|
692
702
|
}
|
|
693
703
|
|
|
694
704
|
// src/core/events.ts
|
|
@@ -1121,6 +1131,32 @@ var MenuRegistry = class extends Registry {
|
|
|
1121
1131
|
register(item, owner) {
|
|
1122
1132
|
return this.add(`${item.location}:${item.id}`, item, owner);
|
|
1123
1133
|
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Hierarchical view of a location: items that name a `parent`, plus the parent's own `children`, are nested under it
|
|
1136
|
+
* (sorted by order). Unknown parents fall back to top level, so a missing plugin never hides its siblings.
|
|
1137
|
+
*/
|
|
1138
|
+
tree(location) {
|
|
1139
|
+
const flat = this.items(location);
|
|
1140
|
+
const byId = new Map(flat.map((i) => [i.id, i]));
|
|
1141
|
+
const toNode = (i) => ({ ...i, children: [...i.children ?? []] });
|
|
1142
|
+
const nodes = new Map(flat.map((i) => [i.id, toNode(i)]));
|
|
1143
|
+
const roots = [];
|
|
1144
|
+
for (const i of flat) {
|
|
1145
|
+
const n = nodes.get(i.id);
|
|
1146
|
+
const parent = i.parent && i.parent !== i.id ? nodes.get(i.parent) : void 0;
|
|
1147
|
+
if (parent && byId.has(i.parent)) parent.children.push(n);
|
|
1148
|
+
else roots.push(n);
|
|
1149
|
+
}
|
|
1150
|
+
const sort = (list) => {
|
|
1151
|
+
list.sort((a, b) => (a.order ?? 100) - (b.order ?? 100) || a.label.localeCompare(b.label));
|
|
1152
|
+
for (const n of list) {
|
|
1153
|
+
if (n.children?.length) sort(n.children);
|
|
1154
|
+
else delete n.children;
|
|
1155
|
+
}
|
|
1156
|
+
};
|
|
1157
|
+
sort(roots);
|
|
1158
|
+
return roots;
|
|
1159
|
+
}
|
|
1124
1160
|
items(location) {
|
|
1125
1161
|
return this.list().filter((m) => m.location === location && (!m.when || m.when())).sort((a, b) => (a.group ?? "").localeCompare(b.group ?? "") || (a.order ?? 100) - (b.order ?? 100) || a.label.localeCompare(b.label));
|
|
1126
1162
|
}
|
|
@@ -2387,10 +2423,11 @@ var PluginManager = class {
|
|
|
2387
2423
|
id: `route:${route.fullPath}`,
|
|
2388
2424
|
location: menu.location ?? "sidebar",
|
|
2389
2425
|
label: menu.label ?? meta.title ?? route.fullPath,
|
|
2390
|
-
icon: meta.icon,
|
|
2426
|
+
icon: menu.icon ?? meta.icon,
|
|
2391
2427
|
path: route.fullPath,
|
|
2392
2428
|
order: menu.order,
|
|
2393
|
-
group: menu.group
|
|
2429
|
+
group: menu.group,
|
|
2430
|
+
parent: menu.parent
|
|
2394
2431
|
},
|
|
2395
2432
|
owner
|
|
2396
2433
|
);
|
|
@@ -2678,6 +2715,7 @@ export {
|
|
|
2678
2715
|
menuLocations,
|
|
2679
2716
|
manifestSchema,
|
|
2680
2717
|
validateManifest,
|
|
2718
|
+
parseVideo,
|
|
2681
2719
|
videoEmbedUrl,
|
|
2682
2720
|
matchesPattern,
|
|
2683
2721
|
EventBus,
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
compareVersions,
|
|
9
9
|
core_exports,
|
|
10
10
|
errorMessage,
|
|
11
|
+
parseVideo,
|
|
11
12
|
serializeError,
|
|
12
|
-
validateManifest
|
|
13
|
-
|
|
14
|
-
} from "./chunk-EVX2O65A.js";
|
|
13
|
+
validateManifest
|
|
14
|
+
} from "./chunk-SDCGUBEW.js";
|
|
15
15
|
|
|
16
16
|
// src/main/framework.ts
|
|
17
17
|
import { promises as fsp, watch } from "node:fs";
|
|
@@ -398,20 +398,80 @@ async function copyDir(src, dest) {
|
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
400
|
function entriesFromIndex(index, baseUrl, source, query = "") {
|
|
401
|
+
const list = Array.isArray(index) ? index : index?.plugins ?? [];
|
|
401
402
|
const q = query.toLowerCase();
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
403
|
+
const abs = (u) => {
|
|
404
|
+
if (!u) return void 0;
|
|
405
|
+
try {
|
|
406
|
+
return new URL(u, baseUrl).href;
|
|
407
|
+
} catch {
|
|
408
|
+
return void 0;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const out = [];
|
|
412
|
+
for (const p of list) {
|
|
413
|
+
if (!p || typeof p.id !== "string" || typeof p.version !== "string") continue;
|
|
414
|
+
const isFolder = typeof p.folder === "string" && Array.isArray(p.files);
|
|
415
|
+
if (typeof p.url !== "string" && !isFolder) continue;
|
|
416
|
+
if (q && !`${p.id} ${p.name ?? ""} ${p.description ?? ""} ${(p.keywords ?? []).join(" ")}`.toLowerCase().includes(q)) continue;
|
|
417
|
+
const folderUrl = isFolder ? abs(p.folder.endsWith("/") ? p.folder : p.folder + "/") : void 0;
|
|
418
|
+
const location = isFolder ? folderUrl : abs(p.url);
|
|
419
|
+
if (!location) continue;
|
|
420
|
+
out.push({
|
|
421
|
+
id: p.id,
|
|
422
|
+
name: p.name ?? p.id,
|
|
423
|
+
version: p.version,
|
|
424
|
+
description: p.description,
|
|
425
|
+
author: p.author,
|
|
426
|
+
permissions: p.permissions ?? [],
|
|
427
|
+
location,
|
|
428
|
+
source,
|
|
429
|
+
sha256: p.sha256,
|
|
430
|
+
homepage: p.homepage,
|
|
431
|
+
license: p.license,
|
|
432
|
+
iconUrl: abs(p.icon),
|
|
433
|
+
bannerUrl: abs(p.banner),
|
|
434
|
+
screenshots: (p.screenshots ?? []).map((s) => abs(s)).filter((s) => !!s),
|
|
435
|
+
video: p.video ? p.video.startsWith("./") && folderUrl ? abs(new URL(p.video, folderUrl).href) : abs(p.video) : void 0,
|
|
436
|
+
categories: p.categories,
|
|
437
|
+
keywords: p.keywords,
|
|
438
|
+
links: p.links,
|
|
439
|
+
readme: p.readme,
|
|
440
|
+
folder: isFolder || void 0,
|
|
441
|
+
files: isFolder ? p.files : void 0,
|
|
442
|
+
hashes: p.hashes
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return out;
|
|
446
|
+
}
|
|
447
|
+
var SAFE_FILE = /^[\w@+.,() \-]+(\/[\w@+.,() \-]+)*$/;
|
|
448
|
+
async function downloadFolder(entry, dest, get) {
|
|
449
|
+
const base = entry.location.endsWith("/") ? entry.location : entry.location + "/";
|
|
450
|
+
const files = entry.files ?? [];
|
|
451
|
+
if (!files.includes("plugin.json")) throw new Error("catalog entry does not list plugin.json");
|
|
452
|
+
if (files.length > 800) throw new Error("catalog entry lists too many files");
|
|
453
|
+
if (!entry.hashes) throw new Error("catalog entry has no file hashes; refusing to install an unverifiable folder (run `fluxplugin store build`)");
|
|
454
|
+
const root = path5.resolve(dest);
|
|
455
|
+
await fs5.mkdir(root, { recursive: true });
|
|
456
|
+
let total = 0;
|
|
457
|
+
const queue = [...files];
|
|
458
|
+
const worker = async () => {
|
|
459
|
+
for (let f = queue.shift(); f !== void 0; f = queue.shift()) {
|
|
460
|
+
if (!SAFE_FILE.test(f) || f.split("/").some((s) => s === ".." || s === ".")) throw new Error(`unsafe file path in catalog: ${f}`);
|
|
461
|
+
const expected = entry.hashes[f];
|
|
462
|
+
if (!expected || !/^[0-9a-f]{64}$/.test(expected)) throw new Error(`no sha256 for ${f} in the catalog`);
|
|
463
|
+
const res = await get(base + f.split("/").map(encodeURIComponent).join("/"));
|
|
464
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
465
|
+
total += buf.length;
|
|
466
|
+
if (total > 100 * 1024 * 1024) throw new Error("plugin is too large");
|
|
467
|
+
if (crypto2.createHash("sha256").update(buf).digest("hex") !== expected) throw new Error(`${f} does not match the catalog checksum`);
|
|
468
|
+
const target = path5.resolve(root, f);
|
|
469
|
+
if (!target.startsWith(root + path5.sep)) throw new Error(`unsafe file path in catalog: ${f}`);
|
|
470
|
+
await fs5.mkdir(path5.dirname(target), { recursive: true });
|
|
471
|
+
await fs5.writeFile(target, buf);
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
await Promise.all(Array.from({ length: 6 }, worker));
|
|
415
475
|
}
|
|
416
476
|
async function pruneNonRuntime(dir) {
|
|
417
477
|
const removed = [];
|
|
@@ -460,23 +520,39 @@ var LocalRegistry = class {
|
|
|
460
520
|
}
|
|
461
521
|
};
|
|
462
522
|
var HttpRegistry = class {
|
|
463
|
-
constructor(baseUrl, fetchImpl = fetch) {
|
|
523
|
+
constructor(baseUrl, fetchImpl = fetch, headers = {}, direct = false) {
|
|
464
524
|
this.baseUrl = baseUrl;
|
|
465
525
|
this.fetchImpl = fetchImpl;
|
|
526
|
+
this.headers = headers;
|
|
527
|
+
this.direct = direct;
|
|
466
528
|
this.name = `http:${baseUrl}`;
|
|
467
529
|
}
|
|
468
530
|
baseUrl;
|
|
469
531
|
fetchImpl;
|
|
532
|
+
headers;
|
|
533
|
+
direct;
|
|
470
534
|
name;
|
|
535
|
+
/** Direct link (".json" file, or `direct: true` for an API endpoint) or a folder that contains index.json. */
|
|
536
|
+
get catalogUrl() {
|
|
537
|
+
if (this.direct || new URL(this.baseUrl).pathname.toLowerCase().endsWith(".json")) return this.baseUrl;
|
|
538
|
+
return new URL("index.json", this.baseUrl.endsWith("/") ? this.baseUrl : this.baseUrl + "/").href;
|
|
539
|
+
}
|
|
540
|
+
get(url) {
|
|
541
|
+
return this.fetchImpl(url, { headers: { accept: "application/json, application/octet-stream", ...this.headers } }).then((r) => {
|
|
542
|
+
if (!r.ok) throw new Error(`store responded ${r.status} for ${new URL(url).pathname}`);
|
|
543
|
+
return r;
|
|
544
|
+
});
|
|
545
|
+
}
|
|
471
546
|
async search(query = "") {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
if (!res.ok) throw new Error(`store responded ${res.status}`);
|
|
475
|
-
return entriesFromIndex(await res.json(), base, this.name, query);
|
|
547
|
+
const url = this.catalogUrl;
|
|
548
|
+
return entriesFromIndex(await (await this.get(url)).json(), url, this.name, query);
|
|
476
549
|
}
|
|
477
550
|
async fetch(entry, dest) {
|
|
478
|
-
|
|
479
|
-
|
|
551
|
+
if (entry.folder) {
|
|
552
|
+
await downloadFolder(entry, dest, (u) => this.get(u));
|
|
553
|
+
return (await readPluginManifest(dest)).manifest;
|
|
554
|
+
}
|
|
555
|
+
const res = await this.get(entry.location);
|
|
480
556
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
481
557
|
if (entry.sha256 && crypto2.createHash("sha256").update(buf).digest("hex") !== entry.sha256.replace(/^sha256-/, "")) {
|
|
482
558
|
throw new Error("downloaded archive does not match the registry checksum");
|
|
@@ -662,6 +738,10 @@ var GitHubRegistry = class {
|
|
|
662
738
|
return out.sort((a, b) => (b.stars ?? 0) - (a.stars ?? 0) || a.id.localeCompare(b.id));
|
|
663
739
|
}
|
|
664
740
|
async fetch(entry, dest) {
|
|
741
|
+
if (entry.folder) {
|
|
742
|
+
await downloadFolder(entry, dest, (u) => this.get(u, "application/octet-stream"));
|
|
743
|
+
return (await readPluginManifest(dest)).manifest;
|
|
744
|
+
}
|
|
665
745
|
const res = await this.get(entry.location, "application/octet-stream");
|
|
666
746
|
const len = Number(res.headers.get("content-length") ?? 0);
|
|
667
747
|
if (len > MAX_PACKAGE_BYTES) throw new Error("package is too large");
|
|
@@ -684,7 +764,8 @@ function createRegistries(specs = [DEFAULT_STORE], opts = {}) {
|
|
|
684
764
|
case "github":
|
|
685
765
|
return new GitHubRegistry({ ...s, token, fetchImpl: opts.fetchImpl });
|
|
686
766
|
case "http":
|
|
687
|
-
|
|
767
|
+
case "json":
|
|
768
|
+
return new HttpRegistry(s.url, opts.fetchImpl, s.headers, s.type === "json");
|
|
688
769
|
case "local":
|
|
689
770
|
return new LocalRegistry(path6.resolve(opts.baseDir ?? process.cwd(), s.path));
|
|
690
771
|
default:
|
|
@@ -893,8 +974,11 @@ var MainFramework = class {
|
|
|
893
974
|
h(MGMT.logs, ({ id }) => this.logs.query(id ? { source: id } : {}).slice(-200));
|
|
894
975
|
h(MGMT.diagnostics, ({ id }) => this.manager.diagnostics(id));
|
|
895
976
|
h(MGMT.details, ({ id }) => this.details(id));
|
|
896
|
-
h(MGMT.search, ({ query }) =>
|
|
897
|
-
|
|
977
|
+
h(MGMT.search, async ({ query }) => {
|
|
978
|
+
const installed = new Map(this.manager.list().map((p) => [p.id, p.version]));
|
|
979
|
+
return (await this.installer.search(query)).map((e) => ({ ...e, installedVersion: installed.get(e.id) }));
|
|
980
|
+
});
|
|
981
|
+
h(MGMT.updates, async () => (await this.installer.updates()).map((e) => ({ ...e, installedVersion: this.manager.has(e.id) ? this.manager.info(e.id).version : void 0 })));
|
|
898
982
|
h(MGMT.install, async ({ entry }) => this.installAndLoad(entry));
|
|
899
983
|
h(MGMT.update, async ({ entry }) => this.installAndLoad(entry));
|
|
900
984
|
h(MGMT.uninstall, async ({ id }) => {
|
|
@@ -943,8 +1027,12 @@ var MainFramework = class {
|
|
|
943
1027
|
iconUrl: m.icon ? url(id, m.icon) : void 0,
|
|
944
1028
|
bannerUrl: m.banner ? url(id, m.banner) : void 0,
|
|
945
1029
|
screenshotUrls: (m.screenshots ?? []).map((s) => url(id, s)),
|
|
946
|
-
video:
|
|
947
|
-
|
|
1030
|
+
video: (() => {
|
|
1031
|
+
const v = parseVideo(m.video);
|
|
1032
|
+
return v?.kind === "html5" && v.src?.startsWith("./") ? { ...v, src: url(id, v.src) } : v;
|
|
1033
|
+
})(),
|
|
1034
|
+
readme,
|
|
1035
|
+
assetBase: url(id, "./")
|
|
948
1036
|
};
|
|
949
1037
|
}
|
|
950
1038
|
requireConfig(id) {
|
|
@@ -967,7 +1055,54 @@ var MainFramework = class {
|
|
|
967
1055
|
// src/main/adapters.ts
|
|
968
1056
|
import http from "node:http";
|
|
969
1057
|
import path8 from "node:path";
|
|
1058
|
+
import { promises as fs7 } from "node:fs";
|
|
1059
|
+
|
|
1060
|
+
// src/main/assets.ts
|
|
970
1061
|
import { promises as fs6 } from "node:fs";
|
|
1062
|
+
var MIME = {
|
|
1063
|
+
".js": "text/javascript",
|
|
1064
|
+
".mjs": "text/javascript",
|
|
1065
|
+
".css": "text/css",
|
|
1066
|
+
".json": "application/json",
|
|
1067
|
+
".map": "application/json",
|
|
1068
|
+
".svg": "image/svg+xml",
|
|
1069
|
+
".png": "image/png",
|
|
1070
|
+
".jpg": "image/jpeg",
|
|
1071
|
+
".jpeg": "image/jpeg",
|
|
1072
|
+
".webp": "image/webp",
|
|
1073
|
+
".gif": "image/gif",
|
|
1074
|
+
".avif": "image/avif",
|
|
1075
|
+
".ico": "image/x-icon",
|
|
1076
|
+
".woff2": "font/woff2",
|
|
1077
|
+
".mp4": "video/mp4",
|
|
1078
|
+
".m4v": "video/mp4",
|
|
1079
|
+
".webm": "video/webm",
|
|
1080
|
+
".ogv": "video/ogg",
|
|
1081
|
+
".ogg": "video/ogg",
|
|
1082
|
+
".mov": "video/quicktime",
|
|
1083
|
+
".mp3": "audio/mpeg",
|
|
1084
|
+
".wav": "audio/wav"
|
|
1085
|
+
};
|
|
1086
|
+
async function readAsset(file, mime, range) {
|
|
1087
|
+
const { size } = await fs6.stat(file);
|
|
1088
|
+
const base = { "content-type": mime, "accept-ranges": "bytes", "access-control-allow-origin": "*", "cache-control": "no-store" };
|
|
1089
|
+
const m = range ? /^bytes=(\d*)-(\d*)$/.exec(range.trim()) : null;
|
|
1090
|
+
if (!m || m[1] === "" && m[2] === "") return { status: 200, headers: { ...base, "content-length": String(size) }, body: await fs6.readFile(file) };
|
|
1091
|
+
let start = m[1] === "" ? Math.max(0, size - Number(m[2])) : Number(m[1]);
|
|
1092
|
+
let end = m[1] === "" || m[2] === "" ? size - 1 : Math.min(Number(m[2]), size - 1);
|
|
1093
|
+
if (start > end || start >= size) return { status: 416, headers: { ...base, "content-range": `bytes */${size}` }, body: Buffer.alloc(0) };
|
|
1094
|
+
const fh = await fs6.open(file, "r");
|
|
1095
|
+
try {
|
|
1096
|
+
const len = end - start + 1;
|
|
1097
|
+
const body = Buffer.alloc(len);
|
|
1098
|
+
await fh.read(body, 0, len, start);
|
|
1099
|
+
return { status: 206, headers: { ...base, "content-range": `bytes ${start}-${end}/${size}`, "content-length": String(len) }, body };
|
|
1100
|
+
} finally {
|
|
1101
|
+
await fh.close();
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/main/adapters.ts
|
|
971
1106
|
function attachElectron(fw, electron) {
|
|
972
1107
|
const { ipcMain, webContents } = electron;
|
|
973
1108
|
ipcMain.handle(CHANNELS.invoke, (e, req) => fw.handleInvoke(req, e.sender.id));
|
|
@@ -992,22 +1127,6 @@ var PLUGIN_SCHEME = {
|
|
|
992
1127
|
scheme: "fluxplugin",
|
|
993
1128
|
privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true, stream: true }
|
|
994
1129
|
};
|
|
995
|
-
var MIME = {
|
|
996
|
-
".js": "text/javascript",
|
|
997
|
-
".mjs": "text/javascript",
|
|
998
|
-
".css": "text/css",
|
|
999
|
-
".json": "application/json",
|
|
1000
|
-
".svg": "image/svg+xml",
|
|
1001
|
-
".png": "image/png",
|
|
1002
|
-
".jpg": "image/jpeg",
|
|
1003
|
-
".jpeg": "image/jpeg",
|
|
1004
|
-
".webp": "image/webp",
|
|
1005
|
-
".gif": "image/gif",
|
|
1006
|
-
".avif": "image/avif",
|
|
1007
|
-
".ico": "image/x-icon",
|
|
1008
|
-
".woff2": "font/woff2",
|
|
1009
|
-
".map": "application/json"
|
|
1010
|
-
};
|
|
1011
1130
|
async function resolvePluginAsset(fw, url) {
|
|
1012
1131
|
let u;
|
|
1013
1132
|
try {
|
|
@@ -1028,7 +1147,7 @@ async function resolvePluginAsset(fw, url) {
|
|
|
1028
1147
|
const ext = path8.extname(file).toLowerCase();
|
|
1029
1148
|
if (!(ext in MIME)) return null;
|
|
1030
1149
|
try {
|
|
1031
|
-
await
|
|
1150
|
+
await fs7.access(file);
|
|
1032
1151
|
} catch {
|
|
1033
1152
|
return null;
|
|
1034
1153
|
}
|
|
@@ -1038,7 +1157,8 @@ function registerPluginProtocol(fw, protocol) {
|
|
|
1038
1157
|
protocol.handle(PLUGIN_SCHEME.scheme, async (req) => {
|
|
1039
1158
|
const r = await resolvePluginAsset(fw, req.url);
|
|
1040
1159
|
if (!r) return new Response("Not found", { status: 404 });
|
|
1041
|
-
|
|
1160
|
+
const a = await readAsset(r.file, r.mime, req.headers?.get("range"));
|
|
1161
|
+
return new Response(new Uint8Array(a.body), { status: a.status, headers: a.headers });
|
|
1042
1162
|
});
|
|
1043
1163
|
}
|
|
1044
1164
|
function attachHttp(fw, opts = {}) {
|
|
@@ -1091,8 +1211,9 @@ function attachHttp(fw, opts = {}) {
|
|
|
1091
1211
|
const id = decodeURIComponent(rest.slice(0, slash));
|
|
1092
1212
|
const r = await resolvePluginAsset(fw, `fluxplugin://${encodeURIComponent(id)}/${rest.slice(slash + 1)}`);
|
|
1093
1213
|
if (!r) return json(res, 404, { error: "not found" });
|
|
1094
|
-
|
|
1095
|
-
|
|
1214
|
+
const a = await readAsset(r.file, r.mime, req.headers.range);
|
|
1215
|
+
res.writeHead(a.status, a.headers);
|
|
1216
|
+
return res.end(a.body);
|
|
1096
1217
|
}
|
|
1097
1218
|
if (opts.fallback && await opts.fallback(req, res)) return;
|
|
1098
1219
|
json(res, 404, { error: "not found" });
|
|
@@ -1142,8 +1263,8 @@ function createLoopbackBridge(fw) {
|
|
|
1142
1263
|
|
|
1143
1264
|
// src/main/serve-web.ts
|
|
1144
1265
|
import path9 from "node:path";
|
|
1145
|
-
import { promises as
|
|
1146
|
-
var TYPES = { ".html": "text/html", ".js": "text/javascript", ".mjs": "text/javascript", ".css": "text/css", ".map": "application/json", ".svg": "image/svg+xml", ".png": "image/png" };
|
|
1266
|
+
import { promises as fs8 } from "node:fs";
|
|
1267
|
+
var TYPES = { ".html": "text/html", ".js": "text/javascript", ".mjs": "text/javascript", ".css": "text/css", ".map": "application/json", ".svg": "image/svg+xml", ".png": "image/png", ".json": "application/json", ".jpg": "image/jpeg", ".webp": "image/webp" };
|
|
1147
1268
|
function serveWeb(fw, opts) {
|
|
1148
1269
|
const dist = path9.resolve(opts.distDir);
|
|
1149
1270
|
return attachHttp(fw, {
|
|
@@ -1156,7 +1277,7 @@ function serveWeb(fw, opts) {
|
|
|
1156
1277
|
if (!type || !file.startsWith(dist + path9.sep)) return false;
|
|
1157
1278
|
try {
|
|
1158
1279
|
res.writeHead(200, { "content-type": type, "cache-control": "no-store" });
|
|
1159
|
-
res.end(await
|
|
1280
|
+
res.end(await fs8.readFile(file));
|
|
1160
1281
|
return true;
|
|
1161
1282
|
} catch {
|
|
1162
1283
|
return false;
|
|
@@ -1186,6 +1307,7 @@ export {
|
|
|
1186
1307
|
copyPluginRuntime,
|
|
1187
1308
|
copyDir,
|
|
1188
1309
|
entriesFromIndex,
|
|
1310
|
+
downloadFolder,
|
|
1189
1311
|
pruneNonRuntime,
|
|
1190
1312
|
LocalRegistry,
|
|
1191
1313
|
HttpRegistry,
|
|
@@ -1195,6 +1317,8 @@ export {
|
|
|
1195
1317
|
DEFAULT_STORE,
|
|
1196
1318
|
createRegistries,
|
|
1197
1319
|
MainFramework,
|
|
1320
|
+
MIME,
|
|
1321
|
+
readAsset,
|
|
1198
1322
|
attachElectron,
|
|
1199
1323
|
PLUGIN_SCHEME,
|
|
1200
1324
|
resolvePluginAsset,
|