@sanity/cli 3.65.2-corel.472 → 3.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,289 +0,0 @@
1
- import {promises as fs} from 'node:fs'
2
- import path from 'node:path'
3
- import util from 'node:util'
4
-
5
- import boxen from 'boxen'
6
- import {noop, padStart} from 'lodash'
7
- import resolveFrom from 'resolve-from'
8
- import rimrafCb from 'rimraf'
9
- import semver from 'semver'
10
-
11
- import {type CliCommandContext} from '../..'
12
- import {
13
- findSanityModuleVersions,
14
- type ModuleVersionResult,
15
- } from '../../actions/versions/findSanityModuleVersions'
16
- import {debug} from '../../debug'
17
- import {type CliCommandAction, type PackageJson} from '../../types'
18
- import {getFormatters} from '../versions/printVersionResult'
19
-
20
- const rimraf = util.promisify(rimrafCb)
21
-
22
- const unsupportedMessage = `
23
- \`sanity upgrade\` is not supported as of sanity v3.
24
- Use npm-check-updates or similar (https://www.npmjs.com/package/npm-check-updates)
25
- `.trim()
26
-
27
- export interface UpgradeCommandFlags {
28
- 'range'?: string
29
- 'tag'?: string
30
- 'offline'?: boolean
31
- 'save-exact'?: boolean
32
- }
33
-
34
- const upgradeDependencies: CliCommandAction<UpgradeCommandFlags> =
35
- async function upgradeDependencies(args, context) {
36
- const {output, workDir, yarn, chalk, sanityMajorVersion} = context
37
- if (sanityMajorVersion >= 3) {
38
- throw new Error(unsupportedMessage)
39
- }
40
-
41
- const {extOptions, argsWithoutOptions} = args
42
- const modules = argsWithoutOptions.slice()
43
- const {range, tag} = extOptions
44
- const saveExact = extOptions['save-exact']
45
- const targetRange = tag || range
46
-
47
- if (range && tag) {
48
- throw new Error('Both --tag and --range specified, can only use one')
49
- }
50
-
51
- if (range && !semver.validRange(range)) {
52
- throw new Error(`Invalid semver range "${range}"`)
53
- }
54
-
55
- // Find which modules needs update according to the target range
56
- const versions = await findSanityModuleVersions(context, {
57
- target: targetRange,
58
- includeCli: false,
59
- })
60
- const allNeedsUpdate = versions.filter((mod) => mod.needsUpdate)
61
-
62
- debug('In need of update: %s', allNeedsUpdate.map((mod) => mod.name).join(', '))
63
-
64
- const needsUpdate =
65
- modules.length === 0
66
- ? allNeedsUpdate
67
- : allNeedsUpdate.filter((outOfDate) => modules.indexOf(outOfDate.name) !== -1)
68
-
69
- const semverBreakingUpgrades = versions.filter(hasSemverBreakingUpgrade)
70
- const baseMajorUpgrade = semverBreakingUpgrades.find((mod) => mod.name === '@sanity/base')
71
- const majorUpgrades = semverBreakingUpgrades.filter((mod) => mod.name !== '@sanity/base')
72
- schedulePrintMajorUpgrades({baseMajorUpgrade, majorUpgrades}, context)
73
-
74
- // If all modules are up-to-date, say so and exit
75
- if (needsUpdate.length === 0) {
76
- const specified = modules.length === 0 ? 'All' : 'All *specified*'
77
- context.output.print(
78
- `${chalk.green('✔')} ${specified} Sanity modules are at latest compatible versions`,
79
- )
80
- return
81
- }
82
-
83
- // Ignore modules that are pinned, but give some indication that this has happened
84
- const pinned = needsUpdate.filter((mod) => mod.isPinned)
85
- const nonPinned = needsUpdate.filter((mod) => !mod.isPinned)
86
- const pinnedNames = pinned.map((mod) => mod.name).join(`\n - `)
87
- if (nonPinned.length === 0) {
88
- context.output.warn(
89
- `${chalk.yellow(
90
- '⚠',
91
- )} All modules are pinned to specific versions, not upgrading:\n - ${pinnedNames}`,
92
- )
93
- return
94
- }
95
-
96
- if (pinned.length > 0) {
97
- context.output.warn(
98
- `${chalk.yellow(
99
- '⚠',
100
- )} The follow modules are pinned to specific versions, not upgrading:\n - ${pinnedNames}`,
101
- )
102
- }
103
-
104
- // Yarn fails to upgrade `react-ace` in some versions, see function for details
105
- await maybeDeleteReactAce(nonPinned, workDir)
106
-
107
- // Forcefully remove non-symlinked module paths to force upgrade
108
- await Promise.all(
109
- nonPinned.map((mod) =>
110
- deleteIfNotSymlink(
111
- path.join(context.workDir, 'node_modules', mod.name.replace(/\//g, path.sep)),
112
- ),
113
- ),
114
- )
115
-
116
- // Replace versions in `package.json`
117
- const versionPrefix = saveExact ? '' : '^'
118
- const oldManifest = await readLocalManifest(workDir)
119
- const newManifest = nonPinned.reduce((target, mod) => {
120
- if (oldManifest.dependencies && oldManifest.dependencies[mod.name]) {
121
- target.dependencies[mod.name] =
122
- mod.latestInRange === 'unknown'
123
- ? oldManifest.dependencies[mod.name]
124
- : versionPrefix + mod.latestInRange
125
- }
126
-
127
- if (oldManifest.devDependencies && oldManifest.devDependencies[mod.name]) {
128
- target.devDependencies[mod.name] =
129
- mod.latestInRange === 'unknown'
130
- ? oldManifest.devDependencies[mod.name]
131
- : versionPrefix + mod.latestInRange
132
- }
133
-
134
- return target
135
- }, oldManifest)
136
-
137
- // Write new `package.json`
138
- const manifestPath = path.join(context.workDir, 'package.json')
139
- await writeJson(manifestPath, newManifest)
140
-
141
- // Run `yarn install`
142
- const flags = extOptions.offline ? ['--offline'] : []
143
- const cmd = ['install'].concat(flags)
144
-
145
- debug('Running yarn %s', cmd.join(' '))
146
- await yarn(cmd, {...output, rootDir: workDir})
147
-
148
- context.output.print('')
149
- context.output.print(`${chalk.green('✔')} Modules upgraded:`)
150
-
151
- const {versionLength, formatName} = getFormatters(nonPinned)
152
- nonPinned.forEach((mod) => {
153
- const current = chalk.yellow(padStart(mod.installed || '<missing>', versionLength))
154
- const latest = chalk.green(mod.latestInRange)
155
- context.output.print(`${formatName(mod.name)} ${current} → ${latest}`)
156
- })
157
- }
158
-
159
- export default upgradeDependencies
160
-
161
- function writeJson(filePath: string, data: PackageJson) {
162
- return fs.writeFile(filePath, `${JSON.stringify(data, null, 2)}\n`)
163
- }
164
-
165
- async function deleteIfNotSymlink(modPath: string): Promise<void | null> {
166
- const stats = await fs.lstat(modPath).catch(noop)
167
- if (!stats || stats.isSymbolicLink()) {
168
- return null
169
- }
170
-
171
- return rimraf(modPath)
172
- }
173
-
174
- function hasSemverBreakingUpgrade(mod: ModuleVersionResult): boolean {
175
- const current = mod.installed || semver.minVersion(mod.declared)?.toString() || ''
176
- return !semver.satisfies(mod.latest, `^${current}`) && semver.gt(mod.latest, current)
177
- }
178
-
179
- function getMajorUpgradeText(
180
- mods: ModuleVersionResult[],
181
- chalk: CliCommandContext['chalk'],
182
- ): string {
183
- const modNames = mods.map((mod) => `${mod.name} (v${semver.major(mod.latest)})`).join('\n - ')
184
-
185
- return [
186
- `The following modules has new major versions\n`,
187
- `released and will have to be manually upgraded:\n\n`,
188
- ` - ${modNames}\n\n`,
189
- chalk.yellow('⚠'),
190
- ` Note that major versions can contain backwards\n`,
191
- ` incompatible changes and should be handled with care.`,
192
- ].join('')
193
- }
194
-
195
- function getMajorStudioUpgradeText(
196
- mod: ModuleVersionResult,
197
- chalk: CliCommandContext['chalk'],
198
- ): string {
199
- const prev = semver.major(mod.installed || semver.minVersion(mod.declared)?.toString() || '')
200
- const next = semver.major(mod.latest)
201
- return [
202
- 'There is now a new major version of Sanity Studio!',
203
- '',
204
- 'Read more about the new version and how to upgrade:',
205
- chalk.blueBright(`https://www.sanity.io/changelog/studio?from=v${prev}&to=v${next}`),
206
- ].join('\n')
207
- }
208
-
209
- function schedulePrintMajorUpgrades(
210
- {
211
- baseMajorUpgrade,
212
- majorUpgrades,
213
- }: {baseMajorUpgrade?: ModuleVersionResult; majorUpgrades: ModuleVersionResult[]},
214
- {chalk, output}: CliCommandContext,
215
- ): void {
216
- if (majorUpgrades.length === 0 && !baseMajorUpgrade) {
217
- return
218
- }
219
-
220
- process.on('beforeExit', () => {
221
- output.print('') // Separate previous output with a newline
222
-
223
- if (baseMajorUpgrade) {
224
- output.warn(
225
- boxen(getMajorStudioUpgradeText(baseMajorUpgrade, chalk), {
226
- borderColor: 'green',
227
- padding: 1,
228
- }),
229
- )
230
- return
231
- }
232
-
233
- output.warn(
234
- boxen(getMajorUpgradeText(majorUpgrades, chalk), {
235
- borderColor: 'yellow',
236
- padding: 1,
237
- }),
238
- )
239
- })
240
- }
241
-
242
- // Workaround for https://github.com/securingsincity/react-ace/issues/1048
243
- // Yarn fails to upgrade `react-ace` because `react-ace.min.js` is a _file_ in one version
244
- // but a _folder_ in the next. If we're upgrading the `@sanity/code-input`, remove the
245
- // `react-ace` dependency before installing
246
- async function maybeDeleteReactAce(toUpgrade: ModuleVersionResult[], workDir: string) {
247
- const codeInputUpdate = toUpgrade.find((mod) => mod.name === '@sanity/code-input')
248
- if (!codeInputUpdate) {
249
- return
250
- }
251
-
252
- // Assume it is an old version if we can't figure out which one is installed
253
- const installed = codeInputUpdate.installed ? codeInputUpdate.installed : '2.4.0'
254
- const upgradeTo = codeInputUpdate.latestInRange
255
-
256
- // react-ace was upgraded in 2.24.1, so if we're going from <= 2.24.0 to => 2.24.1,
257
- // we should remove it.
258
- const shouldDelete = semver.lte(installed, '2.24.0') && semver.gte(upgradeTo, '2.24.1')
259
- if (!shouldDelete) {
260
- return
261
- }
262
-
263
- // Try to find the path to it from `@sanity/code-input`, otherwise try the studio root `node_modules`
264
- const depRootPath = path.join(workDir, 'node_modules')
265
- const closestReactAcePath =
266
- getModulePath('react-ace', path.join(depRootPath, '@sanity', 'code-input')) ||
267
- path.join(depRootPath, 'react-ace')
268
-
269
- await rimraf(closestReactAcePath)
270
- }
271
-
272
- function getModulePath(modName: string, fromPath: string): string | undefined {
273
- const manifestFile = `${modName.replace(/\//g, path.sep)}/package.json`
274
- const manifestPath = resolveFrom.silent(fromPath, manifestFile)
275
- return manifestPath ? path.dirname(manifestPath) : undefined
276
- }
277
-
278
- async function readLocalManifest(dirName: string, fileName = 'package.json') {
279
- try {
280
- const content = await fs.readFile(path.join(dirName, fileName), 'utf8')
281
- return JSON.parse(content)
282
- } catch (err) {
283
- if (err.code === 'ENOENT') {
284
- return {}
285
- }
286
-
287
- throw new Error(`Error while attempting to read projects "${fileName}":\n${err.message}`)
288
- }
289
- }