UNPKG

piral-cli

Version:

The standard CLI for creating and building a Piral instance or a Pilet.

656 lines • 28.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPiralPath = getPiralPath; exports.findPiralInstance = findPiralInstance; exports.findPiralInstances = findPiralInstances; exports.readPiralPackage = readPiralPackage; exports.patchPiralPackage = patchPiralPackage; exports.getPiralPackage = getPiralPackage; exports.getFileStats = getFileStats; exports.copyScaffoldingFiles = copyScaffoldingFiles; exports.copyPiralFiles = copyPiralFiles; exports.getPiletsInfo = getPiletsInfo; exports.retrievePiralRoot = retrievePiralRoot; exports.findDependencyVersion = findDependencyVersion; exports.findPackageVersion = findPackageVersion; exports.flattenExternals = flattenExternals; exports.retrieveExternals = retrieveExternals; exports.retrievePiletsInfo = retrievePiletsInfo; exports.validateSharedDependencies = validateSharedDependencies; exports.isValidDependency = isValidDependency; exports.patchPiletPackage = patchPiletPackage; exports.checkAppShellPackage = checkAppShellPackage; exports.combinePiletExternals = combinePiletExternals; exports.findPiletRoot = findPiletRoot; exports.retrievePiletData = retrievePiletData; const path_1 = require("path"); const log_1 = require("./log"); const info_1 = require("./info"); const archive_1 = require("./archive"); const enums_1 = require("./enums"); const compatibility_1 = require("./compatibility"); const merge_1 = require("./merge"); const utils_1 = require("./utils"); const importmap_1 = require("./importmap"); const io_1 = require("./io"); const io_2 = require("./io"); const npm_1 = require("./npm"); const npm_2 = require("./npm"); const website_1 = require("./website"); const language_1 = require("./language"); const language_2 = require("./language"); const constants_1 = require("./constants"); const constants_2 = require("./constants"); const version_1 = require("./version"); const external_1 = require("../external"); async function appendBundler(devDependencies, bundler, proposedVersion) { if (bundler && bundler !== 'none') { if (isValidDependency(bundler)) { const sep = bundler.indexOf('@', 1); const hasVersion = sep !== -1; const proposedName = bundler.substring(0, hasVersion ? sep : bundler.length); const givenVersion = hasVersion ? bundler.substring(sep + 1) : proposedVersion; const name = constants_1.bundlerNames.includes(proposedName) ? `piral-cli-${bundler}` : proposedName; const versions = new Set([ givenVersion, givenVersion.includes('-beta.') && 'next', givenVersion.includes('-alpha.') && 'canary', givenVersion.includes('.') && givenVersion.split('.').slice(0, 2).join('.'), 'latest', ]); for (const version of versions) { if (version) { const isAvailable = await (0, npm_2.findSpecificVersion)(name, version); // only if something was returned we know that the version exists; so we can take it. if (isAvailable) { devDependencies[name] = version; return; } } } (0, log_1.log)('generalWarning_0001', `Could not find a valid version for the provided bundler "${bundler}".'`); } else { //Error case - print warning and ignore (0, log_1.log)('generalWarning_0001', `The provided bundler name "${bundler}" does not refer to a valid package name.'`); } } } function getDependencyVersion(name, devDependencies, allDependencies) { const version = devDependencies[name]; const selected = typeof version === 'string' ? version : version === true ? allDependencies[name] : undefined; if (!selected) { (0, log_1.log)('cannotResolveVersion_0052', name); } return selected || 'latest'; } const globPatternStartIndicators = ['*', '?', '[', '!(', '?(', '+(', '@(']; async function getMatchingFiles(source, target, file) { const { from, to, deep = true } = typeof file === 'string' ? { from: file, to: file, deep: true } : file; const sourcePath = (0, path_1.resolve)(source, from); const targetPath = (0, path_1.resolve)(target, to); const isDirectory = await (0, io_1.checkIsDirectory)(sourcePath); if (isDirectory) { (0, log_1.log)('generalDebug_0003', `Matching in directory "${sourcePath}".`); const pattern = deep ? '**/*' : '*'; const files = await (0, io_1.matchFiles)(sourcePath, pattern); return files.map((file) => ({ sourcePath: file, targetPath: (0, path_1.resolve)(targetPath, (0, path_1.relative)(sourcePath, file)), })); } else if (globPatternStartIndicators.some((m) => from.indexOf(m) !== -1)) { (0, log_1.log)('generalDebug_0003', `Matching using glob "${sourcePath}".`); const files = await (0, io_1.matchFiles)(source, from); const parts = sourcePath.split('/'); for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (globPatternStartIndicators.some((m) => part.indexOf(m) !== -1)) { parts.splice(i, parts.length - i); break; } } const relRoot = parts.join('/'); const tarRoot = (0, path_1.resolve)(target, to); return files.map((file) => ({ sourcePath: file, targetPath: (0, path_1.resolve)(tarRoot, (0, path_1.relative)(relRoot, file)), })); } (0, log_1.log)('generalDebug_0003', `Assume direct path source "${sourcePath}".`); return [ { sourcePath, targetPath, }, ]; } function getPiralPath(root, name) { const path = (0, npm_2.findPackageRoot)(name, root); if (!path) { (0, log_1.fail)('invalidPiralReference_0043'); } return (0, path_1.dirname)(path); } async function loadPiralInstance(root, details) { (0, log_1.log)('generalDebug_0003', `Following the app package in "${root}" ...`); const appPackage = await (0, io_2.readJson)(root, constants_1.packageJson); const relPath = appPackage.app; appPackage.app = relPath && (0, path_1.resolve)(root, relPath); appPackage.root = root; appPackage.port = details?.port || 0; return appPackage; } async function findPiralInstance(proposedApp, rootDir, details, agent, interactive = false) { const path = (0, npm_2.findPackageRoot)(proposedApp, rootDir); const url = details?.url; if (path) { const root = (0, path_1.dirname)(path); if (url) { (0, log_1.log)('generalDebug_0003', `Updating the emulator from remote "${url}" ...`); await (0, website_1.updateFromEmulatorWebsite)(root, url, agent, interactive); } return await loadPiralInstance(root, details); } else if (url) { (0, log_1.log)('generalDebug_0003', `Piral instance not installed yet - trying from remote "${url}" ...`); const emulator = await (0, website_1.scaffoldFromEmulatorWebsite)(rootDir, url, agent); return await loadPiralInstance(emulator.path, details); } (0, log_1.fail)('appInstanceNotFound_0010', proposedApp); } async function findPiralInstances(proposedApps, piletPackage, piletDefinition, rootDir, agent, interactive) { if (proposedApps) { // do nothing } else if (piletDefinition) { const availableApps = Object.keys(piletDefinition.piralInstances || {}); proposedApps = availableApps.filter((m) => piletDefinition.piralInstances[m].selected); if (proposedApps.length === 0) { proposedApps = availableApps.slice(0, 1); } } else { proposedApps = [piletPackage.piral?.name].filter(Boolean); } if (proposedApps.length > 0) { return Promise.all(proposedApps.map((proposedApp) => findPiralInstance(proposedApp, rootDir, piletDefinition?.piralInstances?.[proposedApp], agent, interactive))); } return []; } function readPiralPackage(root, name) { (0, log_1.log)('generalDebug_0003', `Reading the piral package in "${root}" ...`); const path = getPiralPath(root, name); return (0, io_2.readJson)(path, constants_1.packageJson); } async function patchPiralPackage(root, app, data, version, bundler) { (0, log_1.log)('generalDebug_0003', `Patching the ${constants_1.packageJson} in "${root}" ...`); const pkg = await getPiralPackage(app, data, version, bundler); await (0, io_2.updateExistingJson)(root, constants_1.packageJson, pkg); (0, log_1.log)('generalDebug_0003', `Succesfully patched the ${constants_1.packageJson}.`); await (0, io_2.updateExistingJson)(root, constants_2.piralJson, { $schema: constants_1.piralJsonSchemaUrl, isolation: 'modern', pilets: getPiletsInfo({}), }); (0, log_1.log)('generalDebug_0003', `Succesfully patched the pilet.json.`); } async function getPiralPackage(app, data, version, bundler) { const framework = data.packageName; const devDependencies = { ...(0, language_1.getDevDependencies)(data.language, (0, language_2.getDevDependencyPackages)(framework, data.reactVersion, data.reactRouterVersion)), 'piral-cli': `${version}`, }; const dependencies = { ...(0, language_2.getFrameworkDependencies)(framework, version), ...(0, language_1.getDependencies)(data.language, (0, language_1.getDependencyPackages)(framework, data.reactVersion, data.reactRouterVersion)), }; await appendBundler(devDependencies, bundler, version); return { app, scripts: { start: 'piral debug', build: 'piral build', postinstall: 'piral declaration', }, types: 'dist/index.d.ts', importmap: { imports: {}, inherit: [ 'piral-base', // this we take in any case framework !== 'piral-base' && 'piral-core', // this we take unless we selected piral-base, then obviously core is not invited to the party framework === 'piral' && 'piral', // this we take only if we selected piral ].filter(Boolean), }, dependencies, devDependencies, }; } async function getAvailableFiles(root, name, dirName) { const source = getPiralPath(root, name); const tgz = `${dirName}.tar`; (0, log_1.log)('generalDebug_0003', `Checking if "${tgz}" exists in "${source}" ...`); const exists = await (0, io_2.checkExists)((0, path_1.resolve)(source, tgz)); if (exists) { await (0, archive_1.unpackTarball)(source, tgz); } (0, log_1.log)('generalDebug_0003', `Get matching files from "${source}".`); const base = (0, path_1.resolve)(source, dirName); const files = await (0, io_1.matchFiles)(base, '**/*'); return files.map((file) => ({ sourcePath: file, targetPath: (0, path_1.resolve)(root, (0, path_1.relative)(base, file)), })); } async function getFileStats(root, name) { const files = await getAvailableFiles(root, name, constants_1.filesTar); return await Promise.all(files.map(async (file) => { const { sourcePath, targetPath } = file; const sourceHash = await (0, io_1.getHash)(sourcePath); (0, log_1.log)('generalDebug_0003', `Obtained hash from "${sourcePath}": ${sourceHash}`); const targetHash = await (0, io_1.getHash)(targetPath); (0, log_1.log)('generalDebug_0003', `Obtained hash from "${targetPath}": ${targetHash}`); return { path: targetPath, hash: targetHash, changed: sourceHash !== targetHash, }; })); } async function copyFiles(subfiles, forceOverwrite, originalFiles, variables) { for (const subfile of subfiles) { const { sourcePath, targetPath } = subfile; const exists = await (0, io_2.checkExists)(sourcePath); if (exists) { const overwrite = originalFiles.some((m) => m.path === targetPath && !m.changed); const force = overwrite ? enums_1.ForceOverwrite.yes : forceOverwrite; await (0, io_2.copy)(sourcePath, targetPath, force); } else { (0, log_1.fail)('cannotFindFile_0046', sourcePath); } } } async function copyScaffoldingFiles(source, target, files, piralInfo, variables) { (0, log_1.log)('generalDebug_0003', `Copying the scaffolding files ...`); const allFiles = []; for (const file of files) { const subfiles = await getMatchingFiles(source, target, file); allFiles.push(...subfiles); } if (piralInfo) { await extendPackageOverridesFromTemplateFragment(target, piralInfo, allFiles); } await copyFiles(allFiles, enums_1.ForceOverwrite.yes, [], variables); } async function extendPackageOverridesFromTemplateFragment(root, piralInfo, files) { const packageTarget = (0, path_1.resolve)(root, constants_1.packageJson); for (let i = files.length; i--;) { const file = files[i]; if (file.targetPath === packageTarget) { const fragment = await (0, io_2.readJson)((0, path_1.dirname)(file.sourcePath), (0, path_1.basename)(file.sourcePath)); files.splice(i, 1); if (!piralInfo.pilets) { piralInfo.pilets = {}; } if (!piralInfo.pilets.packageOverrides) { piralInfo.pilets.packageOverrides = {}; } piralInfo.pilets.packageOverrides = { ...piralInfo.pilets.packageOverrides, ...fragment, }; } } } function isTemplateFileLocation(item) { return typeof item === 'object'; } function tryFindPackageVersion(packageName) { try { const { version } = require(`${packageName}/${constants_1.packageJson}`); return version; } catch { return undefined; } } async function copyPiralFiles(root, name, piralInfo, forceOverwrite, variables, originalFiles) { (0, log_1.log)('generalDebug_0003', `Copying the Piral files ...`); const files = await getAvailableFiles(root, name, constants_1.filesTar); if (originalFiles === undefined) { const initialFiles = await getAvailableFiles(root, name, constants_1.filesOnceTar); files.push(...initialFiles); originalFiles = []; } await extendPackageOverridesFromTemplateFragment(root, piralInfo, files); await copyFiles(files, forceOverwrite, originalFiles, variables); } function getPiletsInfo(piralInfo) { const { files = [], scripts = {}, template = 'default', validators = {}, devDependencies = {}, preScaffold = '', postScaffold = '', preUpgrade = '', postUpgrade = '', packageOverrides = {}, } = piralInfo.pilets || {}; return { files, scripts, template, validators, devDependencies, preScaffold, postScaffold, preUpgrade, postUpgrade, packageOverrides, }; } async function retrievePiralRoot(baseDir, entry) { const rootDir = (0, path_1.join)(baseDir, entry); (0, log_1.log)('generalDebug_0003', `Retrieving Piral root from "${rootDir}" ...`); if (!constants_2.declarationEntryExtensions.includes((0, path_1.extname)(rootDir).toLowerCase())) { const packageName = (0, path_1.basename)(rootDir) === constants_1.packageJson ? rootDir : (0, path_1.join)(rootDir, constants_1.packageJson); (0, log_1.log)('generalDebug_0003', `Trying to get entry point from "${packageName}".`); const exists = await (0, io_2.checkExists)(packageName); if (!exists) { (0, log_1.fail)('entryPointMissing_0070', rootDir); } const { app } = require(packageName); if (!app) { (0, log_1.fail)('entryPointMissing_0071'); } (0, log_1.log)('generalDebug_0003', `Found app entry point in "${app}".`); return (0, path_1.join)((0, path_1.dirname)(packageName), app); } (0, log_1.log)('generalDebug_0003', `Found app entry point in "${rootDir}".`); return rootDir; } function checkArrayOrUndefined(obj, key) { const items = obj[key]; if (Array.isArray(items)) { return items; } else if (items !== undefined) { (0, log_1.log)('expectedArray_0072', key, typeof items); } return undefined; } async function findDependencyVersion(pckg, rootPath, dependency) { const { devDependencies = {}, dependencies = {} } = pckg; const packageName = dependency.name; const desiredVersion = dependencies[packageName] ?? devDependencies[packageName]; const [parent] = dependency.parents || []; if (desiredVersion) { if ((0, npm_1.isNpmPackage)(desiredVersion)) { return desiredVersion; } else if ((0, npm_1.isGitPackage)(desiredVersion)) { return (0, npm_1.makeGitUrl)(desiredVersion); } else if ((0, npm_1.isLocalPackage)(rootPath, desiredVersion)) { return (0, npm_1.makeFilePath)(rootPath, desiredVersion); } } if (parent) { // in case the dependency came from another package (= parent) // we should start the lookup in its directory (pnpm issue) const parentPath = (0, npm_1.tryResolvePackage)(parent, rootPath); if (parentPath) { rootPath = (0, path_1.dirname)(parentPath); } } const version = await findPackageVersion(rootPath, packageName); if (dependency.alias) { return (0, npm_2.makeNpmAlias)(dependency.alias, version); } return version; } async function findPackageVersion(rootPath, packageName) { const packages = Array.isArray(packageName) ? packageName : [packageName]; for (const pckg of packages) { try { (0, log_1.log)('generalDebug_0003', `Finding the version of "${packageName}" in "${rootPath}".`); const moduleName = (0, external_1.getModulePath)(rootPath, pckg); const packageJsonPath = await (0, io_2.findFile)(moduleName, constants_1.packageJson); const root = (0, path_1.dirname)(packageJsonPath); const { version } = await (0, io_2.readJson)(root, constants_1.packageJson); return version; } catch { } } (0, log_1.log)('cannotResolveDependency_0053', packages, rootPath); return 'latest'; } function flattenExternals(dependencies, disableAsync = false) { const getName = (dep) => `${dep.name}${dep.isAsync && !disableAsync ? '?' : ''}`; return dependencies.map(getName).filter(utils_1.onlyUnique); } async function retrieveExternals(root, packageInfo) { const importmap = await (0, importmap_1.readImportmap)(root, packageInfo, 'exact', 'host'); if (importmap.length === 0) { const allDeps = { ...packageInfo.devDependencies, ...packageInfo.dependencies, }; const deps = packageInfo.pilets?.externals; const externals = await (0, npm_2.makeExternals)(root, allDeps, deps); return externals.map((ext) => ({ id: ext, name: ext, entry: ext, type: 'local', ref: undefined, requireId: ext, })); } return importmap; } async function retrievePiletsInfo(entryFile) { const exists = await (0, io_2.checkExists)(entryFile); if (!exists) { (0, log_1.fail)('entryPointDoesNotExist_0073', entryFile); } const packageJsonPath = await (0, io_2.findFile)(entryFile, constants_1.packageJson); if (!packageJsonPath) { (0, log_1.fail)('packageJsonMissing_0074'); } const root = (0, path_1.dirname)(packageJsonPath); const packageInfo = await (0, io_2.readJson)(root, constants_1.packageJson); const piralJsonPkg = await (0, io_2.readJson)(root, constants_2.piralJson); const pilets = { ...getPiletsInfo(packageInfo), ...piralJsonPkg.pilets, }; const externals = await retrieveExternals(root, packageInfo); const dependencies = { std: packageInfo.dependencies || {}, dev: packageInfo.devDependencies || {}, peer: packageInfo.peerDependencies || {}, }; const framework = constants_2.frameworkLibs.find((lib) => lib in dependencies.std || lib in dependencies.dev); return { ...pilets, externals, name: packageInfo.name, version: packageInfo.version, emulator: piralJsonPkg.emulator, shared: piralJsonPkg.shared, framework, dependencies, scripts: packageInfo.scripts, ignored: checkArrayOrUndefined(packageInfo, 'preservedDependencies'), root, }; } // This is an ugly workaround for having *some* packages that // are not only suitable as shared dependencies, but actually encouraged const toleratedDependencies = ['piral-ng-common']; function validateSharedDependencies(externals) { // See #591 - we should warn in case somebody shared piral packages for (const external of externals) { const name = external.name; if (external.type === 'local' && name.startsWith('piral-') && name.indexOf('/') === -1 && !toleratedDependencies.includes(name)) { (0, log_1.log)('invalidSharedDependency_0029', name); } } } function isValidDependency(name) { // super simple check at the moment // just to filter out things like "redux-saga/effects" and "@scope/redux-saga/effects" return name.indexOf('/') === -1 || (name.indexOf('@') === 0 && name.split('/').length < 3); } async function patchPiletPackage(root, piralInfo, fromEmulator, client, newInfo) { (0, log_1.log)('generalDebug_0003', `Patching the ${constants_1.packageJson} in "${root}" ...`); const pkg = await getPiletPackage(root, piralInfo, fromEmulator, client, newInfo); await (0, io_2.updateExistingJson)(root, constants_1.packageJson, pkg); (0, log_1.log)('generalDebug_0003', `Succesfully patched the ${constants_1.packageJson}.`); } function isWebsiteCompatible(version) { return (0, version_1.satisfies)(version, '>=1.4.0'); } async function getExistingDependencies(client) { if (client.monorepo) { const existingData = await (0, io_2.readJson)(client.monorepo, constants_1.packageJson); return [ ...Object.keys(existingData.devDependencies || {}), ...Object.keys(existingData.dependencies || {}), ]; } return []; } async function getPiletPackage(root, piralInfo, fromEmulator, client, newInfo) { const { piralCLI = { version: info_1.cliVersion } } = piralInfo; const { packageOverrides, ...info } = getPiletsInfo(piralInfo); const existingData = newInfo ? {} : await (0, io_2.readJson)(root, constants_1.packageJson); const existingDependencies = await getExistingDependencies(client); const piralDependencies = { ...piralInfo.devDependencies, ...piralInfo.dependencies, }; const toolVersion = piralCLI.version; const typeDependencies = newInfo ? (0, language_1.getDevDependencies)(newInfo.language) : {}; const scripts = newInfo ? { start: 'pilet debug', build: 'pilet build', upgrade: 'pilet upgrade', postinstall: isWebsiteCompatible(toolVersion) ? 'pilet declaration' : undefined, ...info.scripts, } : info.scripts; const allExternals = await (0, npm_2.makePiletExternals)(root, piralDependencies, fromEmulator, piralInfo); const devDependencies = { ...Object.keys(typeDependencies).reduce((deps, name) => { deps[name] = piralDependencies[name] || typeDependencies[name]; return deps; }, {}), ...Object.keys(info.devDependencies).reduce((deps, name) => { deps[name] = getDependencyVersion(name, info.devDependencies, piralDependencies); return deps; }, {}), ...allExternals.filter(isValidDependency).reduce((deps, name) => { const existingDeps = existingData.devDependencies; const shouldSpecify = newInfo || (existingDeps && name in existingDeps); if (shouldSpecify) { deps[name] = piralDependencies[name] || tryFindPackageVersion(name) || 'latest'; } return deps; }, {}), ['piral-cli']: toolVersion, }; const dependencies = { ['piral-cli']: undefined, }; if (newInfo) { await appendBundler(devDependencies, newInfo.bundler, toolVersion); } for (const name of existingDependencies) { delete devDependencies[name]; delete dependencies[name]; } return (0, merge_1.deepMerge)(packageOverrides, { importmap: { imports: {}, inherit: [], }, devDependencies, dependencies, scripts, }); } /** * Returns true if its an emulator package, otherwise it has to be a "raw" app shell. */ function checkAppShellPackage(appPackage) { const { piralCLI = { generated: false, version: info_1.cliVersion } } = appPackage; if (piralCLI.generated) { (0, compatibility_1.checkAppShellCompatibility)(piralCLI.version); return true; } (0, log_1.log)('generalDebug_0003', `Missing "piralCLI" section. Assume raw app shell.`); return false; } function combinePiletExternals(appShells, peerDependencies, peerModules, importmap) { const externals = [...Object.keys(peerDependencies), ...peerModules, ...appShells]; for (let i = importmap.length; i--;) { const entry = importmap[i]; // if the entry has no parents, i.e., it was explicitly mentioned in the importmap // then keep it in the importmap (=> prefer the distributed approach, which will always work) if (Array.isArray(entry.parents)) { // only accept entry as a centrally shared dependency if the entry appears in all // mentioned / referenced app shells // in other cases (e.g., if one app shell does not share this) use the distributed // mechanism to ensure that the dependency can also be resolved in this shell if (appShells.every((app) => entry.parents.includes(app))) { externals.push(entry.name); importmap.splice(i, 1); } } } return externals; } async function findPiletRoot(proposedRoot) { const packageJsonPath = await (0, io_2.findFile)(proposedRoot, constants_1.packageJson); if (!packageJsonPath) { (0, log_1.fail)('packageJsonMissing_0075'); } return (0, path_1.dirname)(packageJsonPath); } async function retrievePiletData(target, app, agent, interactive) { const piletJsonPath = await (0, io_2.findFile)(target, constants_2.piletJson); const proposedRoot = piletJsonPath ? (0, path_1.dirname)(piletJsonPath) : target; const root = await findPiletRoot(proposedRoot); const piletPackage = await (0, io_2.readJson)(root, constants_1.packageJson); const piletDefinition = piletJsonPath && (await (0, io_2.readJson)(proposedRoot, constants_2.piletJson)); const appPackages = await findPiralInstances(app && [app], piletPackage, piletDefinition, root, agent, interactive); const apps = []; for (const appPackage of appPackages) { const appFile = appPackage?.app; const appRoot = appPackage?.root; const appPort = appPackage?.port; if (!appFile || !appRoot) { (0, log_1.fail)('appInstanceInvalid_0011'); } const emulator = checkAppShellPackage(appPackage); apps.push({ appPackage, appFile, appRoot, emulator, appPort, }); } const importmap = await (0, importmap_1.readImportmap)(root, piletPackage, piletDefinition?.importmapVersions, 'remote'); return { dependencies: piletPackage.dependencies || {}, devDependencies: piletPackage.devDependencies || {}, peerDependencies: piletPackage.peerDependencies || {}, peerModules: piletPackage.peerModules || [], ignored: checkArrayOrUndefined(piletPackage, 'preservedDependencies'), schema: piletDefinition?.schemaVersion, importmap, apps, piletPackage, root, }; } //# sourceMappingURL=package.js.map