import L from "lodash" ; import { InvalidatedProjectKind } from "typescript" ; import { SyncArgs } from "../actions/sync" ; import { ProjectVersionMeta , VersionResolution } from "../api" ; import { logger } from "../deps" ; import { CONFIG_FILE_NAME , PlasmicContext } from "./config-utils" ; import { HandledError } from "./error" ; import { ensure } from "./lang-utils" ; import * as semver from "./semver" ; import { confirmWithUser } from "./user-utils" ; param root param versionResolution function walkDependencyTree ( root , available ) { root . indirect = false ; const queue = [ root ] ; const result = [ ] ; const getMeta = ( projectId , version ) : ProjectVersionMeta => { const meta = available . find ( ( m ) => m . projectId === projectId && m . version === version ) ; if ( ! meta ) { throw new Error ( `Cannot find projectId=${ projectId }, version=${ version } in the sync resolution results.` ) ; } return { ... meta , indirect : meta . projectId !== root . projectId , } ; } ; while ( queue . length > 0 ) { const curr = ensure ( queue . shift ( ) ) ; result . push ( curr ) ; queue . push ( ... L . toPairs ( curr . dependencies ) . map ( ( [ projectId , version ] ) => getMeta ( projectId , version ) ) ) ; } return result ; } param meta param context async function checkProjectMeta ( meta , root , context , opts ) { const projectId = meta . projectId ; const projectName = meta . projectName ; const newVersion = meta . version ; const indirect = meta . indirect ; const checkVersionLock = async ( ) : Promise < boolean > => { const projectLock = context . lock . projects . find ( ( p ) => p . projectId === projectId ) ; const versionOnDisk = projectLock ?. version ; if ( ! versionOnDisk ) { return true ; } if ( semver . isLatest ( versionOnDisk ) && semver . isLatest ( newVersion ) && meta !== root ) { logger . warn ( `'${ root . projectName }' depends on ${ projectName }@${ newVersion }. To update this project, explicitly specify this project for sync. Skipping...` ) ; return false ; } if ( semver . isLatest ( newVersion ) ) { return true ; } if ( semver . isLatest ( versionOnDisk ) ) { return true ; } if ( semver . eq ( newVersion , versionOnDisk ) ) { if ( opts . force ) { logger . info ( `Project '${ projectName }'@${ newVersion } is already up to date, but syncing anyway because --force is used` ) ; return true ; } else { logger . info ( `Project '${ projectName }'@${ newVersion } is already up to date; skipping. (To force an update, run again with "--force")` ) ; return false ; } } if ( semver . lt ( newVersion , versionOnDisk ) ) { meta === root ? logger . warn ( `The local version of '${ projectName }' (${ versionOnDisk }) is higher than requested version @${ newVersion }. Plasmic does not support downgrading a project. You should consider updating the version range in ${ CONFIG_FILE_NAME }.` ) : logger . warn ( `'${ root . projectName }' uses '${ projectName }'@${ newVersion }, but your code has '${ projectName }'@${ versionOnDisk }. You should consider upgrading this dependency in Plasmic Studio.` ) ; return false ; } if ( semver . gt ( newVersion , versionOnDisk ) ) { if ( meta === root ) { return true ; } else { logger . info ( `'${ root . projectName }' uses '${ projectName }'@${ newVersion }, but your code has version ${ versionOnDisk }` ) ; return await confirmWithUser ( `Do you want to upgrade '${ projectName }' to ${ newVersion }?` , opts . yes ) ; } } throw new Error ( `Error comparing version=${ newVersion } with the version found in plasmic.lock (${ versionOnDisk }) for '${ projectName }'` ) ; } ; const checkVersionRange = async ( ) : Promise < boolean > => { const projectConfig = context . config . projects . find ( ( p ) => p . projectId === projectId ) ; const versionRange = projectConfig ?. version ; if ( ! versionRange ) { projectId !== root . projectId ? logger . info ( `'${ root . projectName }' uses '${ projectName }', which has never been synced before. We will also sync '${ projectName }'@${ newVersion }.` ) : logger . info ( `'${ projectName } has never been synced before. Syncing...'` ) ; return true ; } if ( semver . satisfies ( newVersion , versionRange ) ) { logger . info ( `Updating project '${ projectName }' to ${ newVersion }` ) ; return true ; } logger . warn ( `${ projectName }@${ newVersion } falls outside the range specified in ${ CONFIG_FILE_NAME } (${ versionRange })\nTip: To avoid this warning in the future, update your ${ CONFIG_FILE_NAME }.` ) ; return await confirmWithUser ( "Do you want to force it?" , opts . force || opts . yes , "n" ) ; } ; const checkIndirect = async ( ) : Promise < boolean > => { const projectConfig = context . config . projects . find ( ( p ) => p . projectId === projectId ) ; const configIndirect = projectConfig ?. indirect ; if ( configIndirect && ! indirect ) { logger . warn ( `'${ projectName }' was synced indirectly before, but a direct sync was requested. If it has page components, they will be synced and saved to plasmic.json.` ) ; return await confirmWithUser ( "Do you want to confirm it?" , opts . force || opts . yes , "n" ) ; } return true ; } ; const projectIds = opts . projects . length > 0 ? opts . projects : context . config . projects . map ( ( p ) => p . projectId ) ; if ( projectIds . includes ( projectId ) && opts . force ) { return true ; } return ( ( await checkVersionLock ( ) ) && ( await checkVersionRange ( ) ) && ( await checkIndirect ( ) ) ) ; } param versionResolution param context export async function checkVersionResolution ( versionResolution , context , opts ) { if ( versionResolution . projects . length <= 0 ) { throw new HandledError ( `Found nothing to sync. Make sure the projectId and version values are valid in ${ CONFIG_FILE_NAME }.` ) ; } const seen = [ ] ; const result = [ ] ; for ( const root of versionResolution . projects ) { const queue = opts . nonRecursive ? [ root ] : walkDependencyTree ( root , versionResolution . dependencies ) . reverse ( ) ; for ( const m of queue ) { if ( ! seen . find ( ( p ) => p . projectId === m . projectId ) ) { if ( await checkProjectMeta ( m , root , context , opts ) ) { result . push ( m ) ; } seen . push ( m ) ; } else if ( root . projectId === m . projectId ) { const project = ensure ( seen . find ( ( p ) => p . projectId === m . projectId ) ) ; project . indirect = false ; } } } return result ; } export function getDependencies ( projectId , version , versionResolution ) { const filterFn = ( m ) => m . projectId === projectId && m . version === version ; const meta = versionResolution . projects . find ( filterFn ) ?? versionResolution . dependencies . find ( filterFn ) ; return meta ?. dependencies ; }