Skip to content

Commit

Permalink
feat(cli): show prompt if local version doesn't match remote
Browse files Browse the repository at this point in the history
  • Loading branch information
binoy14 committed May 16, 2024
1 parent 5a2b02f commit 7f5f7c7
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 28 deletions.
44 changes: 27 additions & 17 deletions packages/sanity/src/_internal/cli/actions/build/buildAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,13 @@ import {checkStudioDependencyVersions} from '../../util/checkStudioDependencyVer
import {checkRequiredDependencies} from '../../util/checkRequiredDependencies'
import {getTimer} from '../../util/timing'
import {BuildTrace} from './build.telemetry'
import {
AUTO_UPDATES_IMPORTMAP,
compareStudioDependencyVersions,
} from '../../util/compareStudioDependencyVersions'

const rimraf = promisify(rimrafCallback)

// TODO: replace this with a manifest somewhere
const AUTO_UPDATES_IMPORTMAP = {
imports: {
// Shared modules
'react': 'https://api.sanity.work/v1/modules/react/^18',
'react/': 'https://api.sanity.work/v1/modules/react/^18/',
'react-dom': 'https://api.sanity.work/v1/modules/react-dom/^18',
'react-dom/': 'https://api.sanity.work/v1/modules/react-dom/^18/',
'styled-components': 'https://api.sanity.work/v1/modules/styled-components/^6',

// Sanity Modules
'sanity': 'https://api.sanity.work/v1/modules/sanity/^3',
'sanity/': 'https://api.sanity.work/v1/modules/sanity/^3/',
'@sanity/vision': 'https://api.sanity.work/v1/modules/@sanity__vision/^3 ',
},
}

export interface BuildSanityStudioCommandFlags {
'yes'?: boolean
'y'?: boolean
Expand Down Expand Up @@ -75,6 +62,29 @@ export default async function buildSanityStudio(

if (enableAutoUpdates) {
output.print(`${info} Building with auto-updates enabled`)

// Check the versions
try {
const result = await compareStudioDependencyVersions(workDir)

if (result?.error) {
const {pkg, installed, remote} = result.error
const shouldContinue = await prompt.single({
type: 'confirm',
message: chalk.yellow(
`The version of ${chalk.underline(pkg)} installed (${chalk.underline(installed)}) does not match the version on the remote (${chalk.underline(remote)}).\n` +
`Do you want to continue anyway?`,
),
default: false,
})

if (!shouldContinue) {
process.exit(0)
}
}
} catch (err) {
throw err
}
}

const envVarKeys = getSanityEnvVars()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import fs from 'node:fs'
import path from 'node:path'

import {type PackageJson} from '@sanity/cli'
import {generateHelpUrl} from '@sanity/generate-help-url'
import resolveFrom from 'resolve-from'
import semver, {type SemVer} from 'semver'

import {readPackageJson} from './readPackageJson'

interface PackageInfo {
name: string
supported: string[]
Expand Down Expand Up @@ -174,12 +174,3 @@ function getDowngradeInstructions(pkgs: PackageInfo[]) {
pnpm install ${inst}`
}

function readPackageJson(filePath: string): PackageJson {
try {
// eslint-disable-next-line no-sync
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
} catch (err) {
throw new Error(`Failed to read "${filePath}": ${err.message}`)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import path from 'node:path'

import resolveFrom from 'resolve-from'
import semver from 'semver'

import {readPackageJson} from './readPackageJson'

// TODO: support custom hostname
const AUTO_UPDATE_PACKAGES = {
'react': {
url: 'https://api.sanity.work/v1/modules/react/^18',
shouldAddPathPrefix: true,
},
'react-dom': {url: 'https://api.sanity.work/v1/modules/react-dom/^18', shouldAddPathPrefix: true},
'styled-components': {
url: 'https://api.sanity.work/v1/modules/styled-components/^6',
shouldAddPathPrefix: false,
},
'sanity': {url: 'https://api.sanity.work/v1/modules/sanity/^3', shouldAddPathPrefix: true},
'@sanity/vision': {
url: 'https://api.sanity.work/v1/modules/@sanity__vision/^3',
shouldAddPathPrefix: false,
},
}

// TODO: replace this with a manifest somewhere
export const AUTO_UPDATES_IMPORTMAP = {
imports: Object.keys(AUTO_UPDATE_PACKAGES).reduce<Record<string, string>>((acc, curr) => {
const key = curr as keyof typeof AUTO_UPDATE_PACKAGES
const pkg = AUTO_UPDATE_PACKAGES[key]

acc[key] = pkg.url
if (pkg.shouldAddPathPrefix) {
acc[`${key}/`] = `${pkg.url}/`
}

return acc
}, {}),
}

async function getRemoteResolvedVersion(url: string) {
try {
const res = await fetch(url, {method: 'HEAD', redirect: 'manual'})
return res.headers.get('x-resolved-version')
} catch (err) {
throw new Error(`Failed to fetch remote version for ${url}: ${err.message}`)
}
}

export async function compareStudioDependencyVersions(workDir: string): Promise<
| {
error?: {
pkg: string
installed: string
remote: string
}
}
| undefined
> {
const manifest = readPackageJson(path.join(workDir, 'package.json'))
const dependencies = {...manifest.dependencies, ...manifest.devDependencies}

for (const [pkg, value] of Object.entries(AUTO_UPDATE_PACKAGES)) {
const resolvedVersion = await getRemoteResolvedVersion(value.url)

if (!resolvedVersion) {
throw new Error(`Failed to fetch remote version for ${value.url}`)
}

const dependency = dependencies[pkg]
const manifestPath = resolveFrom.silent(workDir, path.join(pkg, 'package.json'))

const installed = semver.coerce(
manifestPath ? readPackageJson(manifestPath).version : dependency.replace(/[\D.]/g, ''),
)

if (!installed) {
throw new Error(`Failed to parse installed version for ${pkg}`)
}

if (!semver.eq(resolvedVersion, installed.version)) {
return {
error: {
pkg,
installed: installed.version,
remote: resolvedVersion,
},
}
}
}

return undefined
}
18 changes: 18 additions & 0 deletions packages/sanity/src/_internal/cli/util/readPackageJson.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import fs from 'node:fs'

import {type PackageJson} from '@sanity/cli'

/**
* Read the `package.json` file at the given path
*
* @param filePath - Path to package.json to read
* @returns The parsed package.json
*/
export function readPackageJson(filePath: string): PackageJson {
try {
// eslint-disable-next-line no-sync
return JSON.parse(fs.readFileSync(filePath, 'utf8'))
} catch (err) {
throw new Error(`Failed to read "${filePath}": ${err.message}`)
}
}

0 comments on commit 7f5f7c7

Please sign in to comment.