studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
114 lines (96 loc) • 4.25 kB
text/typescript
// import { askToContinue } from '@withstudiocms/cli-kit/messages';
import { exec } from '@withstudiocms/cli-kit/utils';
import { askToContinue, note, spinner } from '@withstudiocms/effect/clack';
import { detect, resolveCommand } from 'package-manager-detector';
import { Console, Effect, genLogger } from '../../effect.js';
import { CliContext } from '../utils/context.js';
import { effectBoxen } from '../utils/effectBoxen.js';
import { type PluginInfo, UpdateResult } from './index.js';
import { convertPluginsToInstallSpecifiers } from './npm-utils.js';
export class TryToInstallPlugins extends Effect.Service<TryToInstallPlugins>()(
'TryToInstallPlugins',
{
effect: genLogger('studiocms/cli/add/validatePlugins/TryToInstallPlugins.effect')(function* () {
const run = (plugins: PluginInfo[]) =>
genLogger('studiocms/cli/add/validatePlugins/TryToInstallPlugins.effect.run')(function* () {
const { chalk, cwd } = yield* CliContext;
const packageManager = yield* Effect.tryPromise(() =>
detect({
cwd,
// Include the `install-metadata` strategy to have the package manager that's
// used for installation take precedence
strategies: ['install-metadata', 'lockfile', 'packageManager-field'],
})
);
yield* Console.debug(`[add]: package manager: '${packageManager?.name || 'none'}'`);
if (!packageManager) {
yield* Console.error(
'[add]: No package manager detected. Please ensure npm, yarn, pnpm, or another supported package manager is installed.'
);
return UpdateResult.none;
}
const agent = packageManager?.agent ?? 'npm';
if (!packageManager?.agent) {
yield* Console.debug('[add]: No package manager agent detected, falling back to npm');
}
const installCommand = resolveCommand(agent, 'add', []);
if (!installCommand) return UpdateResult.none;
const installSpecifiers = (yield* convertPluginsToInstallSpecifiers(plugins)).map(
(specifier) =>
installCommand.command === 'deno'
? `npm:${specifier}` // Deno requires npm prefix to install packages
: specifier
);
const coloredOutput = `${chalk.bold(installCommand.command)} ${installCommand.args.join(' ')} ${chalk.magenta(installSpecifiers.join(' '))}`;
const boxenMessage = yield* effectBoxen((boxen) =>
boxen(coloredOutput, {
margin: 0.5,
padding: 0.5,
borderStyle: 'round',
})
);
const message = `\n${boxenMessage}\n`;
yield* note(
`${chalk.magenta('StudioCMS will run the following command:')}\n ${chalk.dim('If you skip this step, you can always run it yourself later')}\n${message}`
);
if (yield* askToContinue()) {
const spin = yield* spinner();
yield* spin.start('Installing dependencies...');
const response = yield* Effect.tryPromise({
try: async () => {
await exec(installCommand.command, [...installCommand.args, ...installSpecifiers], {
nodeOptions: {
cwd,
env: { NODE_ENV: undefined },
},
});
return UpdateResult.updated;
},
catch: (err) => err,
}).pipe(Effect.catchAll((err) => Effect.succeed(err)));
if (response === UpdateResult.updated) {
yield* spin.stop('Dependencies installed!');
return response;
}
if (
response instanceof Error ||
(response && typeof response === 'object' && 'message' in response)
) {
yield* spin.stop('Error installing dependencies');
yield* Console.debug(`[add]: Error installing dependencies ${response}`);
// NOTE: `err.stdout` can be an empty string, so log the full error instead for a more helpful log
// @ts-expect-error
yield* Console.error(`\n${response.stdout || response.message}\n`);
yield* Console.error(
`\n${chalk.yellow('You may want to try:')}\n- Checking your network connection\n- Running the package manager command manually\n- Ensuring you have permissions to install packages\n`
);
return UpdateResult.failure;
}
return response as UpdateResult;
}
return UpdateResult.cancelled;
});
return { run };
}),
}
) {}