Compare commits
22 Commits
Author | SHA1 | Date |
---|---|---|
semantic-release-bot | fcded7350d | |
Derrick Hammer | c825f740bd | |
Derrick Hammer | 578d965550 | |
Derrick Hammer | ca5c983cbb | |
Derrick Hammer | 01858e6579 | |
semantic-release-bot | 329ab925ce | |
Derrick Hammer | f26fa8a6a9 | |
Derrick Hammer | 421cf81f36 | |
semantic-release-bot | 4bf00ff7fa | |
Derrick Hammer | 0d64ec45f2 | |
Derrick Hammer | 232669e776 | |
semantic-release-bot | 7f46d47ce4 | |
Derrick Hammer | 388c67d818 | |
Derrick Hammer | 8e963d4b04 | |
Derrick Hammer | b03234e38e | |
semantic-release-bot | 48dd3481b8 | |
Derrick Hammer | 458a809c1d | |
Derrick Hammer | cd91d929b1 | |
Derrick Hammer | c2f7ac4317 | |
Derrick Hammer | a429dc4bd8 | |
Derrick Hammer | 9294a4abb0 | |
Derrick Hammer | 5de6422a16 |
|
@ -0,0 +1,13 @@
|
||||||
|
name: Build/Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- develop-*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
uses: lumeweb/github-node-deploy-workflow/.github/workflows/main.yml@master
|
||||||
|
secrets: inherit
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"preset": [
|
||||||
|
"@lumeweb/node-library-preset"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
# [0.1.0-develop.5](https://git.lumeweb.com/LumeWeb/publish-webapp/compare/v0.1.0-develop.4...v0.1.0-develop.5) (2023-09-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* need to slice out the key type byte ([578d965](https://git.lumeweb.com/LumeWeb/publish-webapp/commit/578d965550e3c381c9a92a2a61fee5b498353ee8))
|
||||||
|
|
||||||
|
# [0.1.0-develop.4](https://git.lumeweb.com/LumeWeb/publish-webapp/compare/v0.1.0-develop.3...v0.1.0-develop.4) (2023-09-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* only submit a new registry entry if the data has changed ([421cf81](https://git.lumeweb.com/LumeWeb/publish-webapp/commit/421cf81f362455b607373097a4173eda5780f060))
|
||||||
|
|
||||||
|
# [0.1.0-develop.3](https://git.lumeweb.com/LumeWeb/publish-webapp/compare/v0.1.0-develop.2...v0.1.0-develop.3) (2023-09-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* need to use the sre.pk not cidBytes ([232669e](https://git.lumeweb.com/LumeWeb/publish-webapp/commit/232669e7760a975017748f14d52b8f9a5cb13689))
|
||||||
|
|
||||||
|
# [0.1.0-develop.2](https://git.lumeweb.com/LumeWeb/publish-webapp/compare/v0.1.0-develop.1...v0.1.0-develop.2) (2023-09-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* re-encode cid to be a webapp ([8e963d4](https://git.lumeweb.com/LumeWeb/publish-webapp/commit/8e963d4b0449dfcbeb55eae61c202cbfea5ffc9e))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add support for creating a resolver CID for the app if APP_SEED is not false ([b03234e](https://git.lumeweb.com/LumeWeb/publish-webapp/commit/b03234e38e2bc8e1cd3cd21d8571fd809b768b5d))
|
||||||
|
|
||||||
|
# [0.1.0-develop.1](https://git.lumeweb.com/LumeWeb/publish-webapp/compare/v0.0.1...v0.1.0-develop.1) (2023-08-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* initial version ([5de6422](https://git.lumeweb.com/LumeWeb/publish-webapp/commit/5de6422a16ef9603c713951932019c8299436cb4))
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "@lumeweb/publish-webapp",
|
||||||
|
"version": "0.1.0-develop.5",
|
||||||
|
"type": "module",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"bin": "./lib/index.js",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "gitea@git.lumeweb.com:LumeWeb/publish-webapp.git"
|
||||||
|
},
|
||||||
|
"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!",
|
||||||
|
"scripts": {
|
||||||
|
"prepare": "presetter bootstrap",
|
||||||
|
"build": "run build",
|
||||||
|
"semantic-release": "semantic-release"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@lumeweb/libweb": "0.2.0-develop.39",
|
||||||
|
"@scure/bip39": "^1.2.1",
|
||||||
|
"array-from-async": "^3.0.0",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"ed25519-keygen": "^0.4.8",
|
||||||
|
"memory-level": "^1.0.0",
|
||||||
|
"mime": "^3.0.0",
|
||||||
|
"msgpackr": "^1.9.7",
|
||||||
|
"p-defer": "^4.0.0",
|
||||||
|
"p-queue": "^7.3.4",
|
||||||
|
"prompts": "^2.4.2"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
import { decodeCid, encodeCid } from "@lumeweb/libportal";
|
||||||
|
import {
|
||||||
|
BOOTSTRAP_NODES,
|
||||||
|
CID_HASH_TYPES,
|
||||||
|
CID_TYPES,
|
||||||
|
createKeyPair,
|
||||||
|
createNode,
|
||||||
|
REGISTRY_TYPES,
|
||||||
|
S5NodeConfig,
|
||||||
|
SignedRegistryEntry,
|
||||||
|
} from "@lumeweb/libs5";
|
||||||
|
import KeyPairEd25519 from "@lumeweb/libs5/lib/ed25519.js";
|
||||||
|
import fs from "fs/promises";
|
||||||
|
import { MemoryLevel } from "memory-level";
|
||||||
|
import { base58btc } from "multiformats/bases/base58";
|
||||||
|
import path from "path";
|
||||||
|
import * as process from "process";
|
||||||
|
import fromAsync from "array-from-async";
|
||||||
|
import * as util from "util";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CID,
|
||||||
|
concatBytes,
|
||||||
|
equalBytes,
|
||||||
|
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 * as bip39 from "@scure/bip39";
|
||||||
|
import { wordlist } from "@scure/bip39/wordlists/english";
|
||||||
|
import { HDKey } from "ed25519-keygen/hdkey";
|
||||||
|
import defer from "p-defer";
|
||||||
|
|
||||||
|
import type { WebAppMetadata } from "#types.js";
|
||||||
|
|
||||||
|
const BIP44_PATH = "m/44'/1627'/0'/0'/0'";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let seed = process.env.APP_SEED;
|
||||||
|
if (!seed && seed === undefined) {
|
||||||
|
// @ts-ignore
|
||||||
|
seed = await prompts.prompts.password({
|
||||||
|
name: "module_seed",
|
||||||
|
message: "Enter your app seed",
|
||||||
|
validate: (prev) => prev && bip39.validateMnemonic(prev, wordlist),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hdKey = seed
|
||||||
|
? HDKey.fromMasterSeed(await bip39.mnemonicToSeed(seed as string)).derive(
|
||||||
|
BIP44_PATH,
|
||||||
|
)
|
||||||
|
: false;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let [cid, err] = await uploadObject(serializedMetadata);
|
||||||
|
if (err) {
|
||||||
|
console.error("Failed to publish: ", err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
cid = decodeCid(cid) as CID;
|
||||||
|
cid = encodeCid(cid.hash, cid.size, CID_TYPES.METADATA_WEBAPP);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
util.format("%s: %s", chalk.green("Web App successfully published"), cid),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hdKey) {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = new MemoryLevel<string, Uint8Array>({
|
||||||
|
storeEncoding: "view",
|
||||||
|
valueEncoding: "buffer",
|
||||||
|
});
|
||||||
|
await db.open();
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
keyPair: createKeyPair(),
|
||||||
|
db,
|
||||||
|
p2p: {
|
||||||
|
peers: {
|
||||||
|
initial: [...BOOTSTRAP_NODES],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logger: {
|
||||||
|
info: (s: string) => {},
|
||||||
|
verbose: (s: string) => {},
|
||||||
|
warn: (s: string) => {},
|
||||||
|
error: (s: string) => {},
|
||||||
|
catched: (e: any, context?: string | null) => {},
|
||||||
|
},
|
||||||
|
} as S5NodeConfig;
|
||||||
|
|
||||||
|
const node = createNode(config);
|
||||||
|
await node.start();
|
||||||
|
|
||||||
|
const peerDefer = defer();
|
||||||
|
|
||||||
|
node.services.p2p.once("peerConnected", peerDefer.resolve);
|
||||||
|
|
||||||
|
await peerDefer.promise;
|
||||||
|
{
|
||||||
|
const cidBytes = decodeCid(cid);
|
||||||
|
const key = hdKey as HDKey;
|
||||||
|
let sre: SignedRegistryEntry;
|
||||||
|
|
||||||
|
let revision = 0;
|
||||||
|
|
||||||
|
const ret = await node.services.registry.get(
|
||||||
|
new KeyPairEd25519(key.privateKey).publicKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
revision = ret.revision + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newEntry = concatBytes(
|
||||||
|
Uint8Array.from([
|
||||||
|
REGISTRY_TYPES.CID,
|
||||||
|
CID_TYPES.RESOLVER,
|
||||||
|
CID_HASH_TYPES.BLAKE3,
|
||||||
|
]),
|
||||||
|
cidBytes.hash,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!equalBytes(ret?.data ?? new Uint8Array(), newEntry)) {
|
||||||
|
sre = node.services.registry.signRegistryEntry({
|
||||||
|
kp: new KeyPairEd25519((hdKey as HDKey).privateKey),
|
||||||
|
data: newEntry,
|
||||||
|
revision,
|
||||||
|
});
|
||||||
|
|
||||||
|
await node.services.registry.set(sre);
|
||||||
|
} else {
|
||||||
|
sre = ret as SignedRegistryEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
util.format(
|
||||||
|
"%s: %s",
|
||||||
|
chalk.green("Resolver entry"),
|
||||||
|
encodeCid(sre.pk.slice(1), 0, CID_TYPES.RESOLVER, CID_HASH_TYPES.ED25519),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await node.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
Loading…
Reference in New Issue