osls
Version:
Open-source alternative to Serverless Framework
181 lines (157 loc) • 5.86 kB
JavaScript
;
const _ = require('lodash');
const { createRequire } = require('module');
const path = require('path');
const fsp = require('fs').promises;
const spawn = require('child-process-ext/spawn');
const inquirer = require('@serverless/utils/inquirer');
const relativeBinPath = '@osls/compose/bin/serverless-compose';
// Logic inspired by `@serverless/utils` which is not used as it currently
// requires logger setup which is not needed when running Compose CLI
const isInteractive = process.stdin.isTTY && process.stdout.isTTY && !process.env.CI;
const ensureMinimalPackageJson = async () => {
return fsp.writeFile(path.join(process.cwd(), 'package.json'), '{}');
};
const resolveAbsoluteModulePath = (contextDirname, modulePath) => {
try {
return createRequire(path.resolve(contextDirname, 'require-resolver')).resolve(modulePath);
} catch {
return null;
}
};
const resolveGlobalNpmPath = async () => {
const npmNodeModulesPath = await (async () => {
try {
return String((await spawn('npm', ['root', '-g'])).stdoutBuffer).trim();
} catch (error) {
return null;
}
})();
if (!npmNodeModulesPath) return null;
try {
return require.resolve(`${npmNodeModulesPath}/${relativeBinPath}`);
} catch (globalDepError) {
return null;
}
};
module.exports = async () => {
// 1. If installed locally in service node_modules, run it
const localNpmPath = resolveAbsoluteModulePath(process.cwd(), relativeBinPath);
if (localNpmPath) {
require(localNpmPath);
return;
}
// 2. If installed as npm global installation, run it
const globalNpmPath = await resolveGlobalNpmPath();
if (globalNpmPath) {
require(globalNpmPath);
return;
}
let packageJsonContent;
try {
packageJsonContent = require(path.join(process.cwd(), 'package.json'));
} catch {
// Pass
}
let hasInstalledCompose = false;
if (isInteractive) {
process.stdout.write(
`${['', 'Serverless Compose needs to be installed first. This is a one-time operation.'].join(
'\n'
)}\n`
);
// In this situation, we want to ask user for installing the `@osls/compose`
// and adding it to `devDependencies` of `package.json`
let promptMessage = 'Do you want to install Serverless Compose locally with "npm"?';
if (!packageJsonContent) {
promptMessage += ' A "package.json" file will also be created in your current directory.';
}
const shouldInstallCompose = (
await inquirer.prompt({
message: promptMessage,
type: 'confirm',
name: 'shouldInstallCompose',
})
).shouldInstallCompose;
// Add progress bar
if (shouldInstallCompose) {
const getCliProgressFooter = require('cli-progress-footer');
const cliProgressFooter = getCliProgressFooter();
cliProgressFooter.shouldAddProgressAnimationPrefix = true;
cliProgressFooter.progressAnimationPrefixFrames =
cliProgressFooter.progressAnimationPrefixFrames.map((frame) => `\x1b[91m${frame}\x1b[39m`);
cliProgressFooter.updateProgress('Installing Serverless Compose CLI');
try {
if (!packageJsonContent) {
try {
await ensureMinimalPackageJson();
} catch {
process.stdout.write(
`${[
'',
'Could not create "package.json" in current directory.',
'Please create it manually and run this command again.',
].join('\n')}\n`
);
return;
}
}
try {
await spawn('npm', ['install', '--save-dev', '@osls/compose']);
} catch {
process.stdout.write(
`${[
'',
'Could not install Serverless Compose CLI locally.',
'Please install it manually with "npm i --save-dev @osls/compose" and run this command again.',
].join('\n')}\n`
);
return;
}
} finally {
cliProgressFooter.updateProgress();
}
hasInstalledCompose = true;
} else {
process.stdout.write(
`${[
'',
'Please install it manually with "npm i --save-dev @osls/compose" and run this command again.',
].join('\n')}\n`
);
return;
}
} else {
// Non-interactive scenario
// Here, we want to check if user has `@osls/compose` in `devDependencies` of `package.json`
// If that is the case, we want to run `npm install` to ensure it's installed
// In all other scenarios, we want to inform user that `@osls/compose` needs to be installed first (and/or added to "package.json")
process.stdout.write(`${['', 'Installing Serverless Compose CLI via NPM'].join('\n')}\n`);
const failedInstallationErrorMessage = `${[
'',
'Installation failed. Make sure the "@osls/compose" package is required in "package.json" in the current directory so that Serverless Framework installs Compose automatically.',
'Alternatively you can install the "@osls/compose" package manually via NPM.',
].join('\n')}\n`;
if (_.get(packageJsonContent, 'devDependencies.@osls/compose')) {
try {
await spawn('npm', ['install', '--no-save', '--no-package-lock', '@osls/compose']);
hasInstalledCompose = true;
} catch {
process.stdout.write(failedInstallationErrorMessage);
// Ensure to crash builds in CI
process.exitCode = 1;
return;
}
} else {
process.stdout.write(failedInstallationErrorMessage);
// Ensure to crash builds in CI
process.exitCode = 1;
return;
}
}
if (hasInstalledCompose) {
// Try to run local compose
const installedLocalNpmPath = resolveAbsoluteModulePath(process.cwd(), relativeBinPath);
require(installedLocalNpmPath);
}
};