UNPKG

syncpack

Version:

Consistent dependency versions in large JavaScript Monorepos

60 lines (59 loc) 3.82 kB
import chalk from 'chalk-template'; import { Context, Effect, flow, pipe } from 'effect'; import { gtr } from 'semver'; import { CliConfigTag } from '../config/tag.js'; import { ICON } from '../constants.js'; import { defaultErrorHandlers } from '../error-handlers/default-error-handlers.js'; import { getContext } from '../get-context/index.js'; import { getInstances } from '../get-instances/index.js'; import { exitIfInvalid } from '../io/exit-if-invalid.js'; import { IoTag } from '../io/index.js'; import { writeIfChanged } from '../io/write-if-changed.js'; import { withLogger } from '../lib/with-logger.js'; import { Specifier } from '../specifier/index.js'; import { updateEffects } from './effects.js'; export function update(io, cli, effects = updateEffects, errorHandlers = defaultErrorHandlers) { return pipe(Effect.Do, Effect.bind('ctx', () => getContext({ io, cli, errorHandlers })), Effect.bind('instances', ({ ctx }) => getInstances(ctx, io, errorHandlers)), Effect.bind('update', ({ instances }) => pipe(Effect.succeed(instances.all), Effect.map(instances => { const isVisitedByName = {}; const updateable = []; instances.forEach(instance => { if (!isVisitedByName[instance.name] && (instance.versionGroup._tag === 'SameRange' || instance.versionGroup._tag === 'Standard')) { const specifier = Specifier.create(instance, instance.rawSpecifier.raw); if (specifier._tag === 'Range' || specifier._tag === 'Exact') { isVisitedByName[instance.name] = true; updateable.push(instance); } } }); return updateable; }), Effect.tap(updateEffects.onFetchAllStart), Effect.flatMap(instances => pipe(instances, Effect.partition(instance => pipe(Effect.succeed(instance), Effect.tap(() => updateEffects.onFetchStart(instance, instances.length)), Effect.flatMap(effects.fetchLatestVersions), Effect.tapBoth({ onFailure: () => updateEffects.onFetchEnd(instance), onSuccess: ({ versions }) => updateEffects.onFetchEnd(instance, versions), }), // move up to date dependencies to error channel Effect.flatMap(updateable => gtr(updateable.versions.latest, String(instance.rawSpecifier.raw)) ? pipe(updateEffects.onOutdated(instance, updateable.versions.latest), Effect.map(() => updateable)) : pipe(updateEffects.onUpToDate(instance), Effect.flatMap(() => Effect.fail(updateable)))), // log error but don't catch it Effect.tapErrorTag('HttpError', ({ error }) => Effect.logError(chalk `{red ${ICON.cross} ${error}}`)), // log error but don't catch it Effect.tapErrorTag('NpmRegistryError', ({ error }) => Effect.logError(chalk `{red ${ICON.cross} ${error}}`))), { concurrency: 10 }), // discard errors and up to date dependencies Effect.flatMap(([_, outOfDate]) => Effect.succeed(outOfDate)))), // always remove the spinner when we're done Effect.tapBoth({ onFailure: updateEffects.onFetchAllEnd, onSuccess: updateEffects.onFetchAllEnd, }), // ask the user which updates they want Effect.flatMap(updateEffects.promptForUpdates), // if we think the user cancelled, say so Effect.catchTag('PromptCancelled', () => Effect.logInfo(chalk `{red ${ICON.panic}} aborting after {blue syncpack update} was cancelled`)))), Effect.flatMap(({ ctx }) => pipe(writeIfChanged(ctx), Effect.catchTags({ WriteFileError: flow(errorHandlers.WriteFileError, Effect.map(() => { ctx.isInvalid = true; return ctx; })), }))), Effect.flatMap(exitIfInvalid), Effect.withConcurrency(10), Effect.provide(pipe(Context.empty(), Context.add(CliConfigTag, cli), Context.add(IoTag, io))), withLogger); }