UNPKG

@platform/react.ssr

Version:

An SSR (server-side-render) system for react apps bundled with ParcelJS and hosted on S3.

145 lines (144 loc) 5.15 kB
import { fs, http, util } from '../common'; import { Site } from './Site'; let URL_CACHE = {}; export class Manifest { constructor(args) { this.def = args.def; this.baseUrl = args.baseUrl; this.status = args.status || 200; } static reset() { URL_CACHE = {}; } static async fromFile(args) { const { baseUrl } = args; const path = fs.resolve(args.path); if (!(await fs.pathExists(path))) { throw new Error(`Manifest file does not exist: '${args.path}'`); } const text = await fs.readFile(path, 'utf-8'); const def = await Manifest.parse({ yaml: text, baseUrl }); return Manifest.create({ def, baseUrl }); } static async fromUrl(args) { const { manifestUrl, loadBundleManifest } = args; const errorResponse = (status, error) => { return { ok: false, status, error: new Error(error) }; }; try { const res = await http.get(args.manifestUrl); if (!res.ok) { const error = res.status === 403 ? `The manifest YAML has not been made "public" on the internet.` : `Failed while pulling manifest YAML from cloud.`; return errorResponse(403, error); } const baseUrl = args.baseUrl || manifestUrl; const manifest = await Manifest.fromYaml({ yaml: res.body, baseUrl, loadBundleManifest }); return { ok: true, status: 200, manifest, }; } catch (error) { return errorResponse(500, error); } } static async fromYaml(args) { const { yaml: text, baseUrl, loadBundleManifest } = args; const def = await Manifest.parse({ yaml: text, baseUrl, loadBundleManifest }); return Manifest.create({ def, baseUrl }); } static async parse(args) { const { loadBundleManifest } = args; const baseUrl = args.baseUrl.replace(/\/manifest.yml$/, ''); const yaml = util.parseYaml(args.yaml); if (!yaml.ok || !yaml.data) { const error = `Failed to parse manifest YAML. ${yaml.error.message}`; throw new Error(error); } let sites = []; const input = yaml.data.sites; sites = await Site.formatMany({ input, baseUrl, loadBundleManifest }); const manifest = { sites }; return manifest; } static async get(args) { const key = `${args.manifestUrl}::${args.loadBundleManifest || 'false'}`; let manifest = URL_CACHE[key]; if (manifest && !args.force) { return manifest; } const res = await Manifest.fromUrl(args); if (res.manifest) { manifest = res.manifest; URL_CACHE[key] = manifest; } return manifest; } get ok() { return this.status.toString().startsWith('2'); } get sites() { if (!this._sites) { const manifest = this.def; this._sites = this.def.sites.map((def, index) => Site.create({ index, manifest })); } return this._sites; } get site() { return { byName: (name) => { name = (name || '').trim(); return this.sites.find(site => site.name.trim() === name); }, byHost: (domain) => { domain = util.stripHttp(domain || ''); return this.sites.find(site => site.isMatch(domain || '')); }, }; } get change() { return { site: (id) => { const name = typeof id === 'string' ? id : id.name; return { bundle: async (args) => { const site = this.site.byName(name); if (!site) { return undefined; } const bundle = args.value; const def = Object.assign({}, this.def); def.sites[site.index].bundle = bundle; const manifest = Manifest.create({ def, baseUrl: this.baseUrl }); if (args.saveTo) { await manifest.save(args.saveTo); } return manifest; }, }; }, }; } toObject() { return Object.assign({ status: this.status }, this.def); } async save(path, options = {}) { const def = Object.assign({}, this.def); if (options.minimal) { def.sites.forEach(site => { delete site.files; delete site.entries; delete site.baseUrl; delete site.size; delete site.bytes; }); } path = fs.resolve(path); await fs.ensureDir(fs.dirname(path)); await fs.file.stringifyAndSave(path, def); } } Manifest.create = (args) => new Manifest(args);