piral-cli
Version:
The standard CLI for creating and building a Piral instance or a Pilet.
155 lines (140 loc) • 5.67 kB
text/typescript
import { Agent } from 'https';
import { posix, relative, resolve } from 'path';
import { createPiralStubIndexIfNotExists } from './template';
import { getAuthorizationHeaders, getAxiosOptions, handleAxiosError } from './http';
import { packageJson } from './constants';
import { updateConfig } from './config';
import { ForceOverwrite } from './enums';
import { createDirectory, readJson, writeBinary } from './io';
import { writeJson } from './io';
import { progress, log } from './log';
import { axios, isInteractive } from '../external';
import { EmulatorWebsiteManifestFiles, EmulatorWebsiteManifest } from '../types';
async function requestManifest(url: string, httpsAgent: Agent, interactive: boolean) {
const opts = getAxiosOptions(url);
try {
return await axios.get(url, { ...opts, httpsAgent });
} catch (error) {
return await handleAxiosError(error, interactive, httpsAgent, async (mode, key) => {
const headers = getAuthorizationHeaders(mode, key);
await updateConfig('auth', {
[url]: {
mode: 'header',
key: 'authorization',
value: headers.authorization,
},
});
return await requestManifest(url, httpsAgent, false);
});
}
}
async function downloadEmulatorFiles(
manifestUrl: string,
targetDir: string,
appDir: string,
files: EmulatorWebsiteManifestFiles,
httpsAgent?: Agent,
) {
const requiredFiles = [files.typings, files.main, files.app];
const optionalFiles = [files.always, files.once];
const opts = {
...getAxiosOptions(manifestUrl),
httpsAgent,
responseType: 'arraybuffer' as const,
};
const downloadFiles = (files: Array<string>, target: string) => {
return files
.filter((file) => file && typeof file === 'string')
.map(async (file) => {
const url = new URL(file, manifestUrl);
const res = await axios.get(url.href, opts);
const data: Buffer = res.data;
await writeBinary(target, file, data);
});
};
await Promise.all([...downloadFiles(requiredFiles, appDir), ...downloadFiles(optionalFiles, targetDir)]);
}
async function createEmulatorFiles(
targetDir: string,
appDir: string,
manifestUrl: string,
emulatorJson: EmulatorWebsiteManifest,
httpsAgent?: Agent,
) {
const mainFile = 'index.js';
const appDirName = relative(targetDir, appDir);
await writeJson(
targetDir,
packageJson,
{
name: emulatorJson.name,
description: emulatorJson.description,
version: emulatorJson.version,
importmap: emulatorJson.importmap,
pilets: emulatorJson.scaffolding.pilets,
piralCLI: {
version: emulatorJson.scaffolding.cli,
remoteTypes: emulatorJson.scaffolding.remoteTypes,
timestamp: emulatorJson.timestamp,
source: manifestUrl,
generated: true,
},
files: emulatorJson.files.assets,
main: posix.join(appDirName, mainFile),
typings: posix.join(appDirName, emulatorJson.files.typings),
app: posix.join(appDirName, emulatorJson.files.app),
peerDependencies: {},
optionalDependencies: emulatorJson.dependencies.optional,
devDependencies: emulatorJson.dependencies.included,
sharedDependencies: [Object.keys(emulatorJson.importmap.imports)],
},
true,
);
// actually including this one hints that the app shell should have been included - which is forbidden
await createPiralStubIndexIfNotExists(appDir, mainFile, ForceOverwrite.yes, {
name: emulatorJson.name,
outFile: emulatorJson.files.main,
});
await downloadEmulatorFiles(manifestUrl, targetDir, appDir, emulatorJson.files, httpsAgent);
}
export async function retrieveExtraTypings(url: string, httpsAgent: Agent): Promise<string> {
const opts = getAxiosOptions(url);
const result = await axios.get(url, { ...opts, httpsAgent });
return result.data;
}
export async function updateFromEmulatorWebsite(
targetDir: string,
manifestUrl: string,
httpsAgent: Agent,
interactive: boolean,
) {
progress(`Updating emulator from %s ...`, manifestUrl);
try {
const response = await requestManifest(manifestUrl, httpsAgent, interactive);
const nextEmulator: EmulatorWebsiteManifest = response.data;
const currentEmulator = await readJson(targetDir, packageJson);
if (currentEmulator.name !== nextEmulator.name) {
log('remoteEmulatorNameChanged_0121', currentEmulator.name);
} else if (currentEmulator.piralCLI.timstamp !== nextEmulator.timestamp) {
log('generalDebug_0003', `The timestamp on "${currentEmulator.name}" is different (${nextEmulator.timestamp}).`);
const appDir = resolve(targetDir, 'app');
await createEmulatorFiles(targetDir, appDir, manifestUrl, nextEmulator, httpsAgent);
} else {
log('generalDebug_0003', `Nothing to update for "${currentEmulator.name}".`);
}
} catch (ex) {
log('generalDebug_0003', `HTTP request for emulator update failed: ${ex}`);
log('skipEmulatorUpdate_0120', manifestUrl);
}
}
export async function scaffoldFromEmulatorWebsite(rootDir: string, manifestUrl: string, httpsAgent: Agent) {
progress(`Downloading emulator from %s ...`, manifestUrl);
const interactive = isInteractive();
const response = await requestManifest(manifestUrl, httpsAgent, interactive);
const emulatorJson: EmulatorWebsiteManifest = response.data;
const targetDir = resolve(rootDir, 'node_modules', emulatorJson.name);
const appDir = resolve(targetDir, 'app');
await createDirectory(appDir);
await createEmulatorFiles(targetDir, appDir, manifestUrl, emulatorJson, httpsAgent);
return { name: emulatorJson.name, path: targetDir };
}