feat: initial version

This commit is contained in:
Derrick Hammer 2023-08-15 17:28:46 -04:00
parent 35313662d9
commit 5de6422a16
Signed by: pcfreak30
GPG Key ID: C997C339BE476FF2
3 changed files with 173 additions and 0 deletions

28
package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "@lumeweb/publish-webapp",
"version": "0.1.0",
"type": "module",
"main": "lib/index.js",
"bin": "./lib/index.js",
"devDependencies": {
"@lumeweb/node-library-preset": "^0.2.7",
"@types/mime": "^3.0.1",
"@types/prompts": "^2.4.4",
"presetter": "*"
},
"readme": "ERROR: No README data found!",
"_id": "@lumeweb/publish-webapp@0.1.0",
"scripts": {
"prepare": "presetter bootstrap",
"build": "run build"
},
"dependencies": {
"@lumeweb/libweb": "0.2.0-develop.31",
"array-from-async": "^3.0.0",
"chalk": "^5.3.0",
"mime": "^3.0.0",
"msgpackr": "^1.9.7",
"p-queue": "^7.3.4",
"prompts": "^2.4.2"
}
}

122
src/index.ts Normal file
View File

@ -0,0 +1,122 @@
import fs from "fs/promises";
import path from "path";
import * as process from "process";
import fromAsync from "array-from-async";
import * as util from "util";
import {
hexToBytes,
loginActivePortals,
maybeInitDefaultPortals,
setActivePortalMasterKey,
uploadObject,
} from "@lumeweb/libweb";
import chalk from "chalk";
import mime from "mime";
import { pack } from "msgpackr";
import PQueue from "p-queue";
import prompts from "prompts";
import type { WebAppMetadata } from "#types.js";
let key = process.env.PORTAL_PRIVATE_KEY;
let dir = process.env.DIR;
const parallelUploads = parseInt(process.env.PARALLEL_UPLOADS ?? "0", 10) || 10;
if (!key) {
key = await prompts.prompts.password({
name: "private_key",
message: "Enter your private key",
validate: (prev: string) => prev && prev.length === 64,
type: undefined,
});
}
if (!dir) {
dir = (await prompts.prompts.text({
name: "dir",
message: "Enter the directory of the webapp",
validate: (prev: string) => prev && prev.length > 0,
type: undefined,
})) as unknown as string;
}
dir = path.resolve(dir) + "/";
setActivePortalMasterKey(hexToBytes(key as string));
maybeInitDefaultPortals();
const processedFiles: Array<{ cid: string; file: string; size: number }> = [];
const queue = new PQueue({ concurrency: parallelUploads });
void (await loginActivePortals());
const files: string[] = await fromAsync(walkSync(dir));
files.forEach((item) => {
void queue.add(async () => processFile(item));
});
await queue.onIdle();
const metadata: WebAppMetadata = {
type: "web_app",
paths: {},
tryFiles: ["index.html"],
};
processedFiles
.sort((a, b) => {
if (a.file < b.file) {
return -1;
}
if (a.file > b.file) {
return 1;
}
return 0;
})
.forEach((item) => {
metadata.paths[item.file] = {
cid: item.cid,
contentType: mime.getType(item.file) ?? "application/octet-stream",
size: item.size,
};
});
const serializedMetadata = pack(metadata);
const [cid, err] = await uploadObject(serializedMetadata);
if (err) {
console.error("Failed to publish: ", err);
process.exit(1);
}
console.log(
util.format("%s: %s", chalk.green("Web App successfully published"), cid),
);
async function processFile(filePath: string) {
const fd = await fs.open(filePath);
const size = (await fd.stat()).size;
const [cid, err] = await uploadObject(fd.createReadStream(), BigInt(size));
if (err) {
console.error("Failed to publish: ", err);
process.exit(1);
}
processedFiles.push({ cid, file: filePath.replace(dir as string, ""), size });
}
async function* walkSync(dir: string): AsyncGenerator<string> {
const files = await fs.readdir(dir, { withFileTypes: true });
for (const file of files) {
if (file.isDirectory()) {
yield* walkSync(path.join(dir, file.name));
} else {
yield path.join(dir, file.name);
}
}
}

23
src/types.ts Normal file
View File

@ -0,0 +1,23 @@
export interface WebAppMetadata {
type: "web_app";
name?: string;
tryFiles?: string[];
errorPages?: {
[key: string]: string; // key should match the pattern ^\d{3}$
};
paths: {
[path: string]: PathContent; // path has maxLength 255
};
extraMetadata?: ExtraMetadata; // I'm assuming this as any since the actual structure isn't provided
}
export interface PathContent {
cid: CID; // Assuming CID is another interface or type
contentType?: string; // Should match the provided pattern
size: number;
}
// Placeholder definitions based on the $ref in the schema.
// You should replace these with the actual structures if you have them.
export type CID = any;
export type ExtraMetadata = any;