UNPKG

@simplito/webendpoint-manager

Version:

ClI tool for managing PrivMX Webendpoint assets

328 lines (272 loc) 9.7 kB
#!/usr/bin/env node import { confirm, input } from '@inquirer/prompts'; import { join } from 'node:path'; import { rm } from 'node:fs/promises'; import { existsSync, readFileSync } from 'node:fs'; import { createWriteStream } from 'fs'; import { Readable } from 'stream'; // @ts-ignore import AdmZip from 'adm-zip'; import ora from 'ora'; type ProjectType = { name: string, matchFn: (pckgJson: PackageJsonDependencies) => Promise<boolean>, setup: (assetsPath: string) => Promise<void> } type AssetFetcher = (version: string, saveTo: string) => Promise<string> async function pressEnterToContinue() { await input({ message: 'Press ENTER to continue' }); } const fetchInternalRepository: AssetFetcher = async (version: string, saveTo: string) => { const link = `https://builds.s24.simplito.com/web/main/privmx-webendpoint-${version}.zip`; const fileName = link.split('/').pop() || 'wasm-assets.zip'; const resp = await fetch(link); const savedFilePath = join(saveTo, fileName); if (resp.ok && resp.body) { let writer = createWriteStream(savedFilePath); Readable.fromWeb(resp.body as any).pipe(writer); } while (true) { if (existsSync(savedFilePath)) { break; } await new Promise((resolve, reject) => { }); } return savedFilePath; }; const fetchGithubRepository: AssetFetcher = async (version, saveTo) => { const versionName = version.startsWith('v') ? version : `v${version}` const link = `https://github.com/simplito/privmx-webendpoint/releases/download/${versionName}/privmx-webendpoint-${versionName}.zip` const fileName = link.split('/').pop() || 'wasm-assets.zip'; const resp = await fetch(link,{redirect:"follow"}); const savedFilePath = join(saveTo, fileName); if (resp.ok && resp.body) { let writer = createWriteStream(savedFilePath); Readable.fromWeb(resp.body as any).pipe(writer); await new Promise((resolve, reject) => { writer.on("finish",resolve) writer.on("error",reject) }); }else{ throw new Error('Unable to download assets aborting.'); } return savedFilePath; } async function extractAssets(archivePath: string, extractPath: string): Promise<void> { const assetsZip = new AdmZip(join(process.cwd(), archivePath)); assetsZip.getEntries().forEach(zipFile => { if (zipFile.name.endsWith('js') || zipFile.name.endsWith('.wasm')) { assetsZip.extractEntryTo(zipFile, extractPath, false); } }); } async function clearArchive(archivePath: string) { if (existsSync(archivePath)) { await rm(archivePath); } } async function checkDestPath(destPath: string) { if (existsSync(destPath)) { const override = await confirm({ message: 'Assets already exists, override ?', default: false }); if (override) { await rm(destPath, { recursive: true, force: true }); return false; } return true; } else { return false; } } async function choseDestFolder() { const destinationFolder = await input({ message: 'Please specify the destination folder for the assets (e.g., ./public/privmx-assets):', default: './public/privmx-assets' }); return destinationFolder; } async function setupNext(destinationFolder: string) { console.log('\n-- Paste following script tags in to your root layout <head> tag --'); const withoutPublic = destinationFolder.replace('/public', ''); console.log(`\x1b[34m <Script src="${withoutPublic}/privmx-endpoint-web.js"></Script> <Script src="${withoutPublic}/driver-web-context.js"></Script> <Script src="${withoutPublic}/endpoint-wasm-module.js"></Script>\x1b[0m\n`); await pressEnterToContinue(); console.log('-- Setup your next.config.mjs to handle specified headers --'); console.log(`\x1b[34m /** @type {import("next").NextConfig} */ const nextConfig = { headers: async () => { return [ { source: "/(.*)", headers: [ { key: "Cross-Origin-Embedder-Policy", value: "require-corp", }, { key: "Cross-Origin-Opener-Policy", value: "same-origin", }, ], }, ]; }, }; export default nextConfig; \x1b[0m `); } async function setupVite(destinationFolder: string) { console.log('\n-- Paste following script tags in to your index.html <head> tag --'); const withoutPublic = destinationFolder.replace('/public', ''); console.log(`\x1b[34m <script src="${withoutPublic}/privmx-endpoint-web.js"></script> <script src="${withoutPublic}/driver-web-context.js"></script> <script src="${withoutPublic}/endpoint-wasm-module.js"></script>\x1b[0m\n`); await pressEnterToContinue(); console.log('-- Setup your vite.config.ts to handle specified headers --\n'); console.log(`\x1b[34m import { defineConfig } from "vite"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [ { name: "configure-response-headers", configureServer: (server) => { server.middlewares.use((_req, res, next) => { res.setHeader("Cross-Origin-Embedder-Policy", "require-corp"); res.setHeader("Cross-Origin-Opener-Policy", "same-origin"); next(); }); }, }, ], }); \x1b[0m `); } type PackageJsonDependencies = [dependencies: Record<string, string>, devDependencies: Record<string, string>] function getProjectDependencies(): PackageJsonDependencies { const packageJsonPath: string = join(process.cwd(), 'package.json'); if (!existsSync(packageJsonPath)) { console.log('No package.json found. Exiting...'); process.exit(1); } let packageJson: any; try { const packageJsonContent = readFileSync(packageJsonPath, 'utf8'); packageJson = JSON.parse(packageJsonContent); } catch (error) { console.error('Error reading or parsing package.json:', error); process.exit(1); } const dependencies: Record<string, string> = packageJson.dependencies || {}; const devDependencies: Record<string, string> = packageJson.devDependencies || {}; return [dependencies, devDependencies]; } const PROJECT_TYPES = [ { name: 'Vite', matchFn: async (packageJson) => { const [dep, devDep] = packageJson; const isVite = 'vite' in dep || 'vite' in devDep; return isVite; }, setup: async (assetsPath) => { await setupVite(assetsPath); } }, { name: 'Next', matchFn: async (packageJson) => { const [dep, devDep] = packageJson; const isNext = 'next' in dep || 'next' in devDep; return isNext; }, setup: async (assetsPath) => { await setupNext(assetsPath); } } ] satisfies ProjectType[]; const DEFAULT_PROJECT_TYPE = { name: 'Default', matchFn: async () => { console.log('Unknown project type'); return true; }, setup: async () => { } } satisfies ProjectType; function extractParam(params: string[]): { version?: string, repository?: string } { const args = {}; const repositoryArg = params.find(param => param.startsWith('--repository')); if (repositoryArg) { Object.assign(args, { repository: repositoryArg.split('=')[1] }); } const versionArg = params.find(param => param.startsWith('--version')); if (versionArg) { Object.assign(args, { version: versionArg.split('=')[1] }); } return args; } async function main() { const params = extractParam(process.argv); const projectDependencies = getProjectDependencies(); let userProjectType: ProjectType = DEFAULT_PROJECT_TYPE; for (const projectType of PROJECT_TYPES) { if (await projectType.matchFn(projectDependencies)) { console.log(`Detected as ${projectType.name} project`); const answer = await confirm({ message: 'Please confirm if detected properly: Y/N' }); if (!answer) { continue; } userProjectType = projectType; break; } } // get versions // select version const version = params.version ?? 'v2.0.2'; // select folder const destFolder = await choseDestFolder(); //check if assets exists const abort = await checkDestPath(destFolder); if (abort) { process.exit(0); } // fetch assets let fetcher: AssetFetcher; const spinner = ora(`Fetching assets version ${version}`).start(); if (params.repository && params.repository.startsWith('https://builds.s24')) { fetcher = fetchInternalRepository; } else { fetcher = fetchGithubRepository; } try{ const fetchedAsset = await fetcher(version, './'); spinner.text = 'Extracting...'; await extractAssets(fetchedAsset, destFolder); await clearArchive(fetchedAsset); spinner.succeed(`Assets fetched to: ${destFolder}`); await userProjectType.setup(destFolder); }catch (e) { spinner.fail(e.message || e); } // extract assets } main() .then(() => { console.log( 'For more information please visit our documentation: https://docs.privmx.cloud/' ); }) .catch((e) => { console.log(e); process.exit(1); });