@napi-rs/cli 3.7.3 → 3.8.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.
- package/README.md +33 -0
- package/cli.mjs +9 -12
- package/dist/cli.js +9277 -1313
- package/dist/index.cjs +9266 -1302
- package/dist/index.d.cts +176 -158
- package/dist/index.d.ts +176 -158
- package/dist/index.js +9277 -1313
- package/docs/artifacts.md +33 -0
- package/docs/build.md +58 -0
- package/docs/create-npm-dirs.md +32 -0
- package/docs/new.md +39 -0
- package/docs/pre-publish.md +37 -0
- package/docs/rename.md +37 -0
- package/docs/universalize.md +31 -0
- package/docs/version.md +31 -0
- package/docs/wasi.md +161 -0
- package/package.json +11 -10
- package/src/api/__tests__/__snapshots__/templates.spec.ts.md +1019 -114
- package/src/api/__tests__/__snapshots__/templates.spec.ts.snap +0 -0
- package/src/api/__tests__/artifacts.spec.ts +17 -0
- package/src/api/__tests__/create-npm-dirs.spec.ts +14 -15
- package/src/api/__tests__/new.spec.ts +41 -5
- package/src/api/artifacts.ts +934 -108
- package/src/api/build.ts +1325 -194
- package/src/api/create-npm-dirs.ts +472 -101
- package/src/api/new.ts +354 -35
- package/src/api/pre-publish.ts +2845 -99
- package/src/api/rename.ts +1638 -124
- package/src/api/templates/js-binding.ts +216 -38
- package/src/api/templates/load-wasi-template.ts +1641 -111
- package/src/api/templates/wasi-worker-template.ts +32 -3
- package/src/commands/rename.ts +8 -3
- package/src/def/artifacts.ts +2 -2
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/Cargo.toml +3 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/Cargo.toml +8 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/main-crate/src/lib.rs +1 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/Cargo.toml +5 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/napi-derive/src/lib.rs +1 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/Cargo.toml +11 -0
- package/src/utils/__tests__/__fixtures__/optional-napi-derive/with-optional-derive/src/lib.rs +1 -0
- package/src/utils/__tests__/__snapshots__/target.spec.ts.md +2 -2
- package/src/utils/__tests__/__snapshots__/target.spec.ts.snap +0 -0
- package/src/utils/__tests__/metadata.spec.ts +43 -2
- package/src/utils/__tests__/misc.spec.ts +124 -3
- package/src/utils/__tests__/version.spec.ts +47 -1
- package/src/utils/config.ts +15 -2
- package/src/utils/metadata.ts +206 -7
- package/src/utils/misc.ts +6079 -0
- package/src/utils/target.ts +66 -8
- package/src/utils/typegen.ts +1223 -63
- package/src/utils/version.ts +96 -0
package/src/api/rename.ts
CHANGED
|
@@ -1,162 +1,1676 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { lstatSync, realpathSync } from 'node:fs'
|
|
2
|
+
import { builtinModules } from 'node:module'
|
|
3
|
+
import { mkdtemp, realpath, rm, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import {
|
|
6
|
+
basename,
|
|
7
|
+
dirname,
|
|
8
|
+
isAbsolute,
|
|
9
|
+
join,
|
|
10
|
+
relative,
|
|
11
|
+
resolve,
|
|
12
|
+
sep,
|
|
13
|
+
} from 'node:path'
|
|
4
14
|
|
|
5
15
|
import { parse as parseToml, stringify as stringifyToml } from '@std/toml'
|
|
6
16
|
import { load as yamlParse, dump as yamlStringify } from 'js-yaml'
|
|
7
|
-
import { isNil,
|
|
8
|
-
import * as find from 'empathic/find'
|
|
17
|
+
import { isNil, omitBy, pick } from 'es-toolkit'
|
|
9
18
|
|
|
10
19
|
import { applyDefaultRenameOptions, type RenameOptions } from '../def/rename.js'
|
|
11
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
commitFileSystemTransaction,
|
|
22
|
+
readConfig,
|
|
23
|
+
readFileAsync,
|
|
24
|
+
resolvePackageReconciliationPaths,
|
|
25
|
+
type Target,
|
|
26
|
+
wasiLoaderSuffix,
|
|
27
|
+
wasiTargetHasThreads,
|
|
28
|
+
withPackageFileSystemReconciliation,
|
|
29
|
+
} from '../utils/index.js'
|
|
12
30
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
31
|
+
const WASI_ARTIFACT_METADATA_PREFIX = '// napi-rs-artifact-metadata:'
|
|
32
|
+
const SCOPED_PACKAGE_PATTERN = /^(?:@([^/]+?)\/)?([^/]+?)$/
|
|
33
|
+
const EXCLUDED_PACKAGE_NAMES = new Set(['node_modules', 'favicon.ico'])
|
|
34
|
+
const WINDOWS_RESERVED_FILENAME =
|
|
35
|
+
/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i
|
|
36
|
+
const PACKAGE_REFERENCE_FIELDS = new Set([
|
|
37
|
+
'browser',
|
|
38
|
+
'exports',
|
|
39
|
+
'files',
|
|
40
|
+
'imports',
|
|
41
|
+
'main',
|
|
42
|
+
'man',
|
|
43
|
+
'module',
|
|
44
|
+
'types',
|
|
45
|
+
'typesVersions',
|
|
46
|
+
'typings',
|
|
47
|
+
])
|
|
48
|
+
const DEPENDENCY_FIELDS = new Set([
|
|
49
|
+
'dependencies',
|
|
50
|
+
'devDependencies',
|
|
51
|
+
'optionalDependencies',
|
|
52
|
+
'peerDependencies',
|
|
53
|
+
'peerDependenciesMeta',
|
|
54
|
+
])
|
|
55
|
+
const RECURSIVE_PACKAGE_KEY_FIELDS = new Set(['overrides', 'resolutions'])
|
|
56
|
+
const PNPM_PACKAGE_ARRAY_FIELDS = new Set([
|
|
57
|
+
'ignoredBuiltDependencies',
|
|
58
|
+
'ignoredOptionalDependencies',
|
|
59
|
+
'minimumReleaseAgeExclude',
|
|
60
|
+
'neverBuiltDependencies',
|
|
61
|
+
'onlyBuiltDependencies',
|
|
62
|
+
'trustPolicyExclude',
|
|
63
|
+
])
|
|
17
64
|
|
|
18
|
-
|
|
19
|
-
const cargoTomlPath = resolve(options.cwd, options.manifestPath)
|
|
65
|
+
type JsonRecord = Record<string, unknown>
|
|
20
66
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
interface ManagedFileRename {
|
|
68
|
+
source: string
|
|
69
|
+
destination: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface PlannedManagedFileRename extends ManagedFileRename {
|
|
73
|
+
replacesSameFile: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface PreparedWrite {
|
|
77
|
+
content: Buffer
|
|
78
|
+
destination: string
|
|
79
|
+
mode: number
|
|
80
|
+
removeBeforeWrite?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
class RenameTransactionPlan {
|
|
84
|
+
readonly removals = new Set<string>()
|
|
85
|
+
readonly writes = new Map<string, PreparedWrite>()
|
|
86
|
+
|
|
87
|
+
addWrite(
|
|
88
|
+
destination: string,
|
|
89
|
+
content: Buffer | string,
|
|
90
|
+
mode: number,
|
|
91
|
+
removeBeforeWrite?: string,
|
|
92
|
+
) {
|
|
93
|
+
const buffer = Buffer.isBuffer(content) ? content : Buffer.from(content)
|
|
94
|
+
const existing = this.writes.get(destination)
|
|
95
|
+
if (existing) {
|
|
96
|
+
if (
|
|
97
|
+
!existing.content.equals(buffer) ||
|
|
98
|
+
existing.mode !== mode ||
|
|
99
|
+
existing.removeBeforeWrite !== removeBeforeWrite
|
|
100
|
+
) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Rename prepared conflicting updates for ${destination}`,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
this.writes.set(destination, {
|
|
108
|
+
content: buffer,
|
|
109
|
+
destination,
|
|
110
|
+
mode,
|
|
111
|
+
removeBeforeWrite,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
hasWrite(destination: string) {
|
|
116
|
+
return this.writes.has(destination)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function pathExists(path: string) {
|
|
121
|
+
try {
|
|
122
|
+
lstatSync(path)
|
|
123
|
+
return true
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
throw error
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function assertRegularFile(path: string, description: string) {
|
|
133
|
+
const stats = lstatSync(path)
|
|
134
|
+
if (!stats.isFile()) {
|
|
135
|
+
throw new Error(`${description} must be a regular file: ${path}`)
|
|
136
|
+
}
|
|
137
|
+
return stats
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function fileMode(path: string, description: string) {
|
|
141
|
+
return assertRegularFile(path, description).mode & 0o7777
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function assertSafeBinaryName(name: unknown, description: string) {
|
|
145
|
+
const hasControlCharacter =
|
|
146
|
+
typeof name === 'string' &&
|
|
147
|
+
[...name].some((character) => character.charCodeAt(0) <= 0x1f)
|
|
148
|
+
if (
|
|
149
|
+
typeof name !== 'string' ||
|
|
150
|
+
name.length === 0 ||
|
|
151
|
+
name === '.' ||
|
|
152
|
+
name === '..' ||
|
|
153
|
+
name.trim() !== name ||
|
|
154
|
+
isAbsolute(name) ||
|
|
155
|
+
/[<>:"/\\|?*]/.test(name) ||
|
|
156
|
+
hasControlCharacter ||
|
|
157
|
+
name.endsWith('.') ||
|
|
158
|
+
WINDOWS_RESERVED_FILENAME.test(name)
|
|
159
|
+
) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`${description} must be a safe filename stem: ${JSON.stringify(name)}`,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function npmPackageNameIssues(name: unknown) {
|
|
167
|
+
if (typeof name !== 'string') {
|
|
168
|
+
return { errors: ['name must be a string'], warnings: [] }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const errors: string[] = []
|
|
172
|
+
const warnings: string[] = []
|
|
173
|
+
if (name.length === 0) {
|
|
174
|
+
errors.push('name length must be greater than zero')
|
|
175
|
+
}
|
|
176
|
+
if (name.length > 214) {
|
|
177
|
+
warnings.push('name can no longer contain more than 214 characters')
|
|
178
|
+
}
|
|
179
|
+
if (name.startsWith('.')) {
|
|
180
|
+
errors.push('name cannot start with a period')
|
|
181
|
+
}
|
|
182
|
+
if (name.startsWith('_')) {
|
|
183
|
+
errors.push('name cannot start with an underscore')
|
|
184
|
+
}
|
|
185
|
+
if (name.trim() !== name) {
|
|
186
|
+
errors.push('name cannot contain leading or trailing spaces')
|
|
187
|
+
}
|
|
188
|
+
if (name.toLowerCase() !== name) {
|
|
189
|
+
warnings.push('name can no longer contain capital letters')
|
|
190
|
+
}
|
|
191
|
+
if (EXCLUDED_PACKAGE_NAMES.has(name.toLowerCase())) {
|
|
192
|
+
errors.push(`${name} is not a valid package name`)
|
|
193
|
+
}
|
|
194
|
+
if (builtinModules.includes(name.toLowerCase())) {
|
|
195
|
+
warnings.push(`${name} is a core module name`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const match = name.match(SCOPED_PACKAGE_PATTERN)
|
|
199
|
+
const scope = match?.[1]
|
|
200
|
+
const packageName = match?.[2]
|
|
201
|
+
if (packageName && /[~'!()*]/.test(packageName)) {
|
|
202
|
+
warnings.push('name can no longer contain special characters ("~\'!()*")')
|
|
203
|
+
}
|
|
204
|
+
if (
|
|
205
|
+
!packageName ||
|
|
206
|
+
packageName.startsWith('.') ||
|
|
207
|
+
(scope
|
|
208
|
+
? encodeURIComponent(scope) !== scope ||
|
|
209
|
+
encodeURIComponent(packageName) !== packageName
|
|
210
|
+
: encodeURIComponent(name) !== name)
|
|
211
|
+
) {
|
|
212
|
+
errors.push('name can only contain URL-friendly characters')
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
errors: [...new Set(errors)],
|
|
216
|
+
warnings: [...new Set(warnings)],
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function assertValidNpmPackageName(
|
|
221
|
+
name: unknown,
|
|
222
|
+
description: string,
|
|
223
|
+
strict: boolean,
|
|
224
|
+
): asserts name is string {
|
|
225
|
+
const { errors, warnings } = npmPackageNameIssues(name)
|
|
226
|
+
const issues = strict ? [...errors, ...warnings] : errors
|
|
227
|
+
if (issues.length > 0) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`${description} is not a valid npm package name: ${issues.join('; ')}`,
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function validatePackageIdentity(
|
|
235
|
+
packageName: unknown,
|
|
236
|
+
targets: Target[],
|
|
237
|
+
description: string,
|
|
238
|
+
strict: boolean,
|
|
239
|
+
) {
|
|
240
|
+
assertValidNpmPackageName(packageName, description, strict)
|
|
241
|
+
for (const target of targets) {
|
|
242
|
+
assertValidNpmPackageName(
|
|
243
|
+
`${packageName}-${target.platformArchABI}`,
|
|
244
|
+
`${description} flavor for ${target.triple}`,
|
|
245
|
+
strict,
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function createManagedWasiFiles(binaryName: string, targets: Target[]) {
|
|
251
|
+
const files = new Set<string>()
|
|
252
|
+
const add = (suffix: string) => files.add(`${binaryName}.${suffix}`)
|
|
253
|
+
const wasiTargets = targets.filter((target) => target.platform === 'wasi')
|
|
254
|
+
const flavors =
|
|
255
|
+
wasiTargets.length > 0
|
|
256
|
+
? wasiTargets.map((target) => ({
|
|
257
|
+
hasThreads: wasiTargetHasThreads(target),
|
|
258
|
+
platformArchABI: target.platformArchABI,
|
|
259
|
+
}))
|
|
260
|
+
: [{ hasThreads: true, platformArchABI: 'wasm32-wasi' }]
|
|
261
|
+
|
|
262
|
+
for (const flavor of flavors) {
|
|
263
|
+
const loaderSuffix = wasiLoaderSuffix(flavor.platformArchABI)
|
|
264
|
+
for (const suffix of [
|
|
265
|
+
`${flavor.platformArchABI}.wasm`,
|
|
266
|
+
`${flavor.platformArchABI}.debug.wasm`,
|
|
267
|
+
`${loaderSuffix}.cjs`,
|
|
268
|
+
`${loaderSuffix}.d.cts`,
|
|
269
|
+
`${loaderSuffix}-browser.js`,
|
|
270
|
+
]) {
|
|
271
|
+
add(suffix)
|
|
272
|
+
}
|
|
273
|
+
if (!flavor.hasThreads) {
|
|
274
|
+
for (const suffix of [
|
|
275
|
+
`${flavor.platformArchABI}.wasm.d.ts`,
|
|
276
|
+
`${flavor.platformArchABI}.wasm.d.mts`,
|
|
277
|
+
`${flavor.platformArchABI}.workerd.mjs`,
|
|
278
|
+
`${flavor.platformArchABI}.workerd.d.mts`,
|
|
279
|
+
`${loaderSuffix}-deferred.js`,
|
|
280
|
+
`${loaderSuffix}-deferred.d.ts`,
|
|
281
|
+
]) {
|
|
282
|
+
add(suffix)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
add('wasm')
|
|
288
|
+
add('debug.wasm')
|
|
289
|
+
return files
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function createManagedWasiRenames(
|
|
293
|
+
oldName: string,
|
|
294
|
+
newName: string,
|
|
295
|
+
targets: Target[],
|
|
296
|
+
) {
|
|
297
|
+
const renames = new Map<string, string>()
|
|
298
|
+
for (const oldFile of createManagedWasiFiles(oldName, targets)) {
|
|
299
|
+
renames.set(oldFile, `${newName}${oldFile.slice(oldName.length)}`)
|
|
300
|
+
}
|
|
301
|
+
return renames
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function createManagedPackageRenames(
|
|
305
|
+
oldPackageName: string,
|
|
306
|
+
newPackageName: string,
|
|
307
|
+
targets: Target[],
|
|
308
|
+
) {
|
|
309
|
+
return new Map(
|
|
310
|
+
targets.map((target) => [
|
|
311
|
+
`${oldPackageName}-${target.platformArchABI}`,
|
|
312
|
+
`${newPackageName}-${target.platformArchABI}`,
|
|
313
|
+
]),
|
|
42
314
|
)
|
|
315
|
+
}
|
|
43
316
|
|
|
44
|
-
|
|
317
|
+
function replaceManifestReference(
|
|
318
|
+
value: string,
|
|
319
|
+
fileRenames: Map<string, string>,
|
|
320
|
+
packageRenames: Map<string, string>,
|
|
321
|
+
) {
|
|
322
|
+
const directFile = fileRenames.get(value)
|
|
323
|
+
if (directFile) {
|
|
324
|
+
return directFile
|
|
325
|
+
}
|
|
326
|
+
if (value.startsWith('./')) {
|
|
327
|
+
const relativeFile = fileRenames.get(value.slice(2))
|
|
328
|
+
if (relativeFile) {
|
|
329
|
+
return `./${relativeFile}`
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return replacePackageSelectors(value, packageRenames, false)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function isPackageSelectorPrefix(
|
|
336
|
+
value: string,
|
|
337
|
+
index: number,
|
|
338
|
+
packageName: string,
|
|
339
|
+
allowSelectorPrefix: boolean,
|
|
340
|
+
) {
|
|
341
|
+
if (index === 0 || (index === 4 && value.startsWith('npm:'))) {
|
|
342
|
+
return true
|
|
343
|
+
}
|
|
344
|
+
if (!allowSelectorPrefix) {
|
|
345
|
+
return false
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const previous = value[index - 1]
|
|
349
|
+
if (previous !== '/' && previous !== '>') {
|
|
350
|
+
return false
|
|
351
|
+
}
|
|
352
|
+
if (previous === '/' && !packageName.startsWith('@')) {
|
|
353
|
+
const previousSlash = value.lastIndexOf('/', index - 2)
|
|
354
|
+
const previousSegment = value
|
|
355
|
+
.slice(previousSlash + 1, index - 1)
|
|
356
|
+
.replace(/^npm:/, '')
|
|
357
|
+
if (previousSegment.startsWith('@')) {
|
|
358
|
+
return false
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return true
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function replacePackageSelectors(
|
|
365
|
+
value: string,
|
|
366
|
+
packageRenames: Map<string, string>,
|
|
367
|
+
allowSelectorPrefix: boolean,
|
|
368
|
+
) {
|
|
369
|
+
let cursor = 0
|
|
370
|
+
let updated = ''
|
|
371
|
+
const renames = [...packageRenames].sort(
|
|
372
|
+
([left], [right]) => right.length - left.length,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
while (cursor < value.length) {
|
|
376
|
+
let match: { index: number; name: string; replacement: string } | undefined
|
|
377
|
+
for (const [name, replacement] of renames) {
|
|
378
|
+
let index = value.indexOf(name, cursor)
|
|
379
|
+
while (index !== -1) {
|
|
380
|
+
const next = value[index + name.length]
|
|
381
|
+
if (
|
|
382
|
+
isPackageSelectorPrefix(value, index, name, allowSelectorPrefix) &&
|
|
383
|
+
(next === undefined || next === '/' || next === '@')
|
|
384
|
+
) {
|
|
385
|
+
break
|
|
386
|
+
}
|
|
387
|
+
index = value.indexOf(name, index + name.length)
|
|
388
|
+
}
|
|
389
|
+
if (
|
|
390
|
+
index !== -1 &&
|
|
391
|
+
(!match ||
|
|
392
|
+
index < match.index ||
|
|
393
|
+
(index === match.index && name.length > match.name.length))
|
|
394
|
+
) {
|
|
395
|
+
match = { index, name, replacement }
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (!match) {
|
|
400
|
+
updated += value.slice(cursor)
|
|
401
|
+
break
|
|
402
|
+
}
|
|
403
|
+
updated += value.slice(cursor, match.index)
|
|
404
|
+
updated += match.replacement
|
|
405
|
+
cursor = match.index + match.name.length
|
|
406
|
+
}
|
|
407
|
+
return updated || value
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function defineJsonProperty(target: JsonRecord, key: string, value: unknown) {
|
|
411
|
+
Object.defineProperty(target, key, {
|
|
412
|
+
configurable: true,
|
|
413
|
+
enumerable: true,
|
|
414
|
+
value,
|
|
415
|
+
writable: true,
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function createJsonRecord() {
|
|
420
|
+
return Object.create(null) as JsonRecord
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function rewriteReferenceTree(
|
|
424
|
+
value: unknown,
|
|
425
|
+
fileRenames: Map<string, string>,
|
|
426
|
+
packageRenames: Map<string, string>,
|
|
427
|
+
rewriteKeys = true,
|
|
428
|
+
): unknown {
|
|
429
|
+
if (typeof value === 'string') {
|
|
430
|
+
return replaceManifestReference(value, fileRenames, packageRenames)
|
|
431
|
+
}
|
|
432
|
+
if (Array.isArray(value)) {
|
|
433
|
+
return value.map((entry) =>
|
|
434
|
+
rewriteReferenceTree(entry, fileRenames, packageRenames, rewriteKeys),
|
|
435
|
+
)
|
|
436
|
+
}
|
|
437
|
+
if (typeof value !== 'object' || value === null) {
|
|
438
|
+
return value
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const updated = createJsonRecord()
|
|
442
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
443
|
+
const updatedKey = rewriteKeys
|
|
444
|
+
? replaceManifestReference(key, fileRenames, packageRenames)
|
|
445
|
+
: key
|
|
446
|
+
if (Object.prototype.hasOwnProperty.call(updated, updatedKey)) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`Renaming managed manifest reference ${key} conflicts with existing key ${updatedKey}`,
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
defineJsonProperty(
|
|
452
|
+
updated,
|
|
453
|
+
updatedKey,
|
|
454
|
+
rewriteReferenceTree(entry, fileRenames, packageRenames, rewriteKeys),
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
return updated
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function rewritePackageMap(
|
|
461
|
+
value: unknown,
|
|
462
|
+
packageRenames: Map<string, string>,
|
|
463
|
+
recursive: boolean,
|
|
464
|
+
): unknown {
|
|
465
|
+
if (Array.isArray(value)) {
|
|
466
|
+
return rewritePackageSelectorArray(value, packageRenames)
|
|
467
|
+
}
|
|
468
|
+
if (typeof value !== 'object' || value === null) {
|
|
469
|
+
return value
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const updated = createJsonRecord()
|
|
473
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
474
|
+
const updatedKey = replacePackageSelectors(key, packageRenames, true)
|
|
475
|
+
if (Object.prototype.hasOwnProperty.call(updated, updatedKey)) {
|
|
476
|
+
throw new Error(
|
|
477
|
+
`Renaming package reference ${key} conflicts with existing key ${updatedKey}`,
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
const updatedEntry =
|
|
481
|
+
typeof entry === 'string'
|
|
482
|
+
? replacePackageDescriptor(entry, packageRenames)
|
|
483
|
+
: recursive
|
|
484
|
+
? rewritePackageMap(entry, packageRenames, true)
|
|
485
|
+
: entry
|
|
486
|
+
defineJsonProperty(updated, updatedKey, updatedEntry)
|
|
487
|
+
}
|
|
488
|
+
return updated
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function replacePackageDescriptor(
|
|
492
|
+
value: string,
|
|
493
|
+
packageRenames: Map<string, string>,
|
|
494
|
+
) {
|
|
495
|
+
if (value.startsWith('npm:')) {
|
|
496
|
+
return replacePackageSelectors(value, packageRenames, false)
|
|
497
|
+
}
|
|
498
|
+
if (!value.startsWith('patch:')) {
|
|
499
|
+
return value
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const source = value.slice('patch:'.length)
|
|
503
|
+
const packageSeparator = source.startsWith('@')
|
|
504
|
+
? source.indexOf('@', source.indexOf('/') + 1)
|
|
505
|
+
: source.indexOf('@')
|
|
506
|
+
if (packageSeparator <= 0) {
|
|
507
|
+
return value
|
|
508
|
+
}
|
|
509
|
+
const packageName = source.slice(0, packageSeparator)
|
|
510
|
+
const replacement = packageRenames.get(packageName)
|
|
511
|
+
return replacement
|
|
512
|
+
? `patch:${replacement}${source.slice(packageSeparator)}`
|
|
513
|
+
: value
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function rewritePackageSelectorArray(
|
|
517
|
+
value: unknown,
|
|
518
|
+
packageRenames: Map<string, string>,
|
|
519
|
+
) {
|
|
520
|
+
if (!Array.isArray(value)) {
|
|
521
|
+
return value
|
|
522
|
+
}
|
|
523
|
+
return value.map((entry) =>
|
|
524
|
+
typeof entry === 'string'
|
|
525
|
+
? replacePackageSelectors(entry, packageRenames, true)
|
|
526
|
+
: entry,
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function rewritePackageSelectorKeys(
|
|
531
|
+
value: unknown,
|
|
532
|
+
packageRenames: Map<string, string>,
|
|
533
|
+
) {
|
|
534
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
535
|
+
return value
|
|
536
|
+
}
|
|
537
|
+
const updated = createJsonRecord()
|
|
538
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
539
|
+
const updatedKey = replacePackageSelectors(key, packageRenames, true)
|
|
540
|
+
if (Object.prototype.hasOwnProperty.call(updated, updatedKey)) {
|
|
541
|
+
throw new Error(
|
|
542
|
+
`Renaming package reference ${key} conflicts with existing key ${updatedKey}`,
|
|
543
|
+
)
|
|
544
|
+
}
|
|
545
|
+
defineJsonProperty(updated, updatedKey, entry)
|
|
546
|
+
}
|
|
547
|
+
return updated
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function rewritePackageExtensions(
|
|
551
|
+
value: unknown,
|
|
552
|
+
packageRenames: Map<string, string>,
|
|
553
|
+
) {
|
|
554
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
555
|
+
return value
|
|
556
|
+
}
|
|
557
|
+
const updated = createJsonRecord()
|
|
558
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
559
|
+
const updatedKey = replacePackageSelectors(key, packageRenames, true)
|
|
560
|
+
if (Object.prototype.hasOwnProperty.call(updated, updatedKey)) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
`Renaming package reference ${key} conflicts with existing key ${updatedKey}`,
|
|
563
|
+
)
|
|
564
|
+
}
|
|
565
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
566
|
+
defineJsonProperty(updated, updatedKey, entry)
|
|
567
|
+
continue
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const updatedExtension = createJsonRecord()
|
|
571
|
+
for (const [field, fieldValue] of Object.entries(entry)) {
|
|
572
|
+
defineJsonProperty(
|
|
573
|
+
updatedExtension,
|
|
574
|
+
field,
|
|
575
|
+
DEPENDENCY_FIELDS.has(field)
|
|
576
|
+
? rewritePackageMap(fieldValue, packageRenames, false)
|
|
577
|
+
: fieldValue,
|
|
578
|
+
)
|
|
579
|
+
}
|
|
580
|
+
defineJsonProperty(updated, updatedKey, updatedExtension)
|
|
581
|
+
}
|
|
582
|
+
return updated
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function rewritePnpmPeerDependencyRules(
|
|
586
|
+
value: unknown,
|
|
587
|
+
packageRenames: Map<string, string>,
|
|
588
|
+
) {
|
|
589
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
590
|
+
return value
|
|
591
|
+
}
|
|
592
|
+
const updated = createJsonRecord()
|
|
593
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
594
|
+
defineJsonProperty(
|
|
595
|
+
updated,
|
|
596
|
+
key,
|
|
597
|
+
key === 'ignoreMissing' || key === 'allowAny'
|
|
598
|
+
? rewritePackageSelectorArray(entry, packageRenames)
|
|
599
|
+
: key === 'allowedVersions'
|
|
600
|
+
? rewritePackageSelectorKeys(entry, packageRenames)
|
|
601
|
+
: entry,
|
|
602
|
+
)
|
|
603
|
+
}
|
|
604
|
+
return updated
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function rewritePnpmUpdateConfig(
|
|
608
|
+
value: unknown,
|
|
609
|
+
packageRenames: Map<string, string>,
|
|
610
|
+
) {
|
|
611
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
612
|
+
return value
|
|
613
|
+
}
|
|
614
|
+
const updated = createJsonRecord()
|
|
615
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
616
|
+
defineJsonProperty(
|
|
617
|
+
updated,
|
|
618
|
+
key,
|
|
619
|
+
key === 'ignoreDependencies'
|
|
620
|
+
? rewritePackageSelectorArray(entry, packageRenames)
|
|
621
|
+
: entry,
|
|
622
|
+
)
|
|
623
|
+
}
|
|
624
|
+
return updated
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function rewritePnpmConfig(
|
|
628
|
+
value: unknown,
|
|
629
|
+
packageRenames: Map<string, string>,
|
|
630
|
+
) {
|
|
631
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
632
|
+
return value
|
|
633
|
+
}
|
|
634
|
+
const updated = createJsonRecord()
|
|
635
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
636
|
+
let rewritten = entry
|
|
637
|
+
if (key === 'overrides') {
|
|
638
|
+
rewritten = rewritePackageMap(entry, packageRenames, true)
|
|
639
|
+
} else if (key === 'patchedDependencies') {
|
|
640
|
+
rewritten = rewritePackageSelectorKeys(entry, packageRenames)
|
|
641
|
+
} else if (key === 'packageExtensions') {
|
|
642
|
+
rewritten = rewritePackageExtensions(entry, packageRenames)
|
|
643
|
+
} else if (key === 'allowedDeprecatedVersions') {
|
|
644
|
+
rewritten = rewritePackageSelectorKeys(entry, packageRenames)
|
|
645
|
+
} else if (key === 'allowBuilds') {
|
|
646
|
+
rewritten = rewritePackageSelectorKeys(entry, packageRenames)
|
|
647
|
+
} else if (key === 'peerDependencyRules') {
|
|
648
|
+
rewritten = rewritePnpmPeerDependencyRules(entry, packageRenames)
|
|
649
|
+
} else if (key === 'updateConfig') {
|
|
650
|
+
rewritten = rewritePnpmUpdateConfig(entry, packageRenames)
|
|
651
|
+
} else if (PNPM_PACKAGE_ARRAY_FIELDS.has(key)) {
|
|
652
|
+
rewritten = rewritePackageSelectorArray(entry, packageRenames)
|
|
653
|
+
}
|
|
654
|
+
defineJsonProperty(updated, key, rewritten)
|
|
655
|
+
}
|
|
656
|
+
return updated
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function replaceScriptArgument(
|
|
660
|
+
argument: string,
|
|
661
|
+
fileRenames: Map<string, string>,
|
|
662
|
+
packageRenames: Map<string, string>,
|
|
663
|
+
) {
|
|
664
|
+
const direct = replaceManifestReference(argument, fileRenames, packageRenames)
|
|
665
|
+
if (direct !== argument) {
|
|
666
|
+
return direct
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const assignment = argument.match(
|
|
670
|
+
/^([A-Za-z_][A-Za-z0-9_]*|--?[A-Za-z0-9][A-Za-z0-9-]*)=(.+)$/,
|
|
671
|
+
)
|
|
672
|
+
if (!assignment) {
|
|
673
|
+
return argument
|
|
674
|
+
}
|
|
675
|
+
const replacement = replaceManifestReference(
|
|
676
|
+
assignment[2],
|
|
677
|
+
fileRenames,
|
|
678
|
+
packageRenames,
|
|
679
|
+
)
|
|
680
|
+
return replacement === assignment[2]
|
|
681
|
+
? argument
|
|
682
|
+
: `${assignment[1]}=${replacement}`
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function rewriteScriptCommand(
|
|
686
|
+
command: string,
|
|
687
|
+
fileRenames: Map<string, string>,
|
|
688
|
+
packageRenames: Map<string, string>,
|
|
689
|
+
) {
|
|
690
|
+
let index = 0
|
|
691
|
+
let updated = ''
|
|
692
|
+
while (index < command.length) {
|
|
693
|
+
const character = command[index]
|
|
694
|
+
if (/\s|[|&;()<>]/.test(character)) {
|
|
695
|
+
updated += character
|
|
696
|
+
index += 1
|
|
697
|
+
continue
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (character === '"' || character === "'") {
|
|
701
|
+
const quote = character
|
|
702
|
+
let end = index + 1
|
|
703
|
+
while (end < command.length) {
|
|
704
|
+
if (command[end] === quote) {
|
|
705
|
+
break
|
|
706
|
+
}
|
|
707
|
+
if (
|
|
708
|
+
quote === '"' &&
|
|
709
|
+
command[end] === '\\' &&
|
|
710
|
+
end + 1 < command.length
|
|
711
|
+
) {
|
|
712
|
+
end += 2
|
|
713
|
+
} else {
|
|
714
|
+
end += 1
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (end >= command.length) {
|
|
718
|
+
updated += command.slice(index)
|
|
719
|
+
break
|
|
720
|
+
}
|
|
721
|
+
const argument = command.slice(index + 1, end)
|
|
722
|
+
updated += `${quote}${replaceScriptArgument(
|
|
723
|
+
argument,
|
|
724
|
+
fileRenames,
|
|
725
|
+
packageRenames,
|
|
726
|
+
)}${quote}`
|
|
727
|
+
index = end + 1
|
|
728
|
+
continue
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
let end = index + 1
|
|
732
|
+
while (end < command.length && !/\s|[|&;()<>"']/.test(command[end])) {
|
|
733
|
+
end += 1
|
|
734
|
+
}
|
|
735
|
+
updated += replaceScriptArgument(
|
|
736
|
+
command.slice(index, end),
|
|
737
|
+
fileRenames,
|
|
738
|
+
packageRenames,
|
|
739
|
+
)
|
|
740
|
+
index = end
|
|
741
|
+
}
|
|
742
|
+
return updated
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function rewriteScripts(
|
|
746
|
+
value: unknown,
|
|
747
|
+
fileRenames: Map<string, string>,
|
|
748
|
+
packageRenames: Map<string, string>,
|
|
749
|
+
) {
|
|
750
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
751
|
+
return value
|
|
752
|
+
}
|
|
753
|
+
const updated = createJsonRecord()
|
|
754
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
755
|
+
defineJsonProperty(
|
|
756
|
+
updated,
|
|
757
|
+
key,
|
|
758
|
+
typeof entry === 'string'
|
|
759
|
+
? rewriteScriptCommand(entry, fileRenames, packageRenames)
|
|
760
|
+
: entry,
|
|
761
|
+
)
|
|
762
|
+
}
|
|
763
|
+
return updated
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function rewritePublishConfig(
|
|
767
|
+
value: unknown,
|
|
768
|
+
fileRenames: Map<string, string>,
|
|
769
|
+
packageRenames: Map<string, string>,
|
|
770
|
+
) {
|
|
771
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
772
|
+
return value
|
|
773
|
+
}
|
|
774
|
+
const updated = createJsonRecord()
|
|
775
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
776
|
+
defineJsonProperty(
|
|
777
|
+
updated,
|
|
778
|
+
key,
|
|
779
|
+
PACKAGE_REFERENCE_FIELDS.has(key)
|
|
780
|
+
? rewriteReferenceTree(entry, fileRenames, packageRenames)
|
|
781
|
+
: entry,
|
|
782
|
+
)
|
|
783
|
+
}
|
|
784
|
+
return updated
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function rewritePackageManifest(
|
|
788
|
+
manifest: JsonRecord,
|
|
789
|
+
fileRenames: Map<string, string>,
|
|
790
|
+
packageRenames: Map<string, string>,
|
|
791
|
+
) {
|
|
792
|
+
const updated = createJsonRecord()
|
|
793
|
+
for (const [key, entry] of Object.entries(manifest)) {
|
|
794
|
+
let rewritten = entry
|
|
795
|
+
if (key === 'name' && typeof entry === 'string') {
|
|
796
|
+
rewritten = replaceManifestReference(entry, new Map(), packageRenames)
|
|
797
|
+
} else if (PACKAGE_REFERENCE_FIELDS.has(key)) {
|
|
798
|
+
rewritten = rewriteReferenceTree(entry, fileRenames, packageRenames)
|
|
799
|
+
} else if (key === 'bin') {
|
|
800
|
+
rewritten = rewriteReferenceTree(
|
|
801
|
+
entry,
|
|
802
|
+
fileRenames,
|
|
803
|
+
packageRenames,
|
|
804
|
+
false,
|
|
805
|
+
)
|
|
806
|
+
} else if (DEPENDENCY_FIELDS.has(key)) {
|
|
807
|
+
rewritten = rewritePackageMap(entry, packageRenames, false)
|
|
808
|
+
} else if (key === 'dependenciesMeta') {
|
|
809
|
+
rewritten = rewritePackageSelectorKeys(entry, packageRenames)
|
|
810
|
+
} else if (key === 'trustedDependencies') {
|
|
811
|
+
rewritten = rewritePackageSelectorArray(entry, packageRenames)
|
|
812
|
+
} else if (key === 'bundledDependencies' || key === 'bundleDependencies') {
|
|
813
|
+
rewritten = rewritePackageMap(entry, packageRenames, false)
|
|
814
|
+
} else if (RECURSIVE_PACKAGE_KEY_FIELDS.has(key)) {
|
|
815
|
+
rewritten = rewritePackageMap(entry, packageRenames, true)
|
|
816
|
+
} else if (key === 'pnpm') {
|
|
817
|
+
rewritten = rewritePnpmConfig(entry, packageRenames)
|
|
818
|
+
} else if (key === 'scripts') {
|
|
819
|
+
rewritten = rewriteScripts(entry, fileRenames, packageRenames)
|
|
820
|
+
} else if (key === 'publishConfig') {
|
|
821
|
+
rewritten = rewritePublishConfig(entry, fileRenames, packageRenames)
|
|
822
|
+
}
|
|
823
|
+
defineJsonProperty(updated, key, rewritten)
|
|
824
|
+
}
|
|
825
|
+
return updated
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function replaceManagedTextReferences(
|
|
829
|
+
content: string,
|
|
830
|
+
fileRenames: Map<string, string>,
|
|
831
|
+
packageRenames: Map<string, string>,
|
|
832
|
+
) {
|
|
833
|
+
const references = new Map<
|
|
834
|
+
string,
|
|
835
|
+
{ replacement: string; packageName: boolean }
|
|
836
|
+
>()
|
|
837
|
+
for (const [name, replacement] of fileRenames) {
|
|
838
|
+
references.set(name, { replacement, packageName: false })
|
|
839
|
+
}
|
|
840
|
+
for (const [name, replacement] of packageRenames) {
|
|
841
|
+
references.set(name, { replacement, packageName: true })
|
|
842
|
+
}
|
|
843
|
+
if (references.size === 0) {
|
|
844
|
+
return content
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
const pattern = new RegExp(
|
|
848
|
+
[...references.keys()]
|
|
849
|
+
.sort((left, right) => right.length - left.length)
|
|
850
|
+
.map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
851
|
+
.join('|'),
|
|
852
|
+
'g',
|
|
853
|
+
)
|
|
854
|
+
return content.replace(pattern, (name: string, offset: number) => {
|
|
855
|
+
const reference = references.get(name)!
|
|
856
|
+
const previous = content[offset - 1]
|
|
857
|
+
const next = content[offset + name.length]
|
|
858
|
+
if (reference.packageName) {
|
|
859
|
+
if (previous && /[A-Za-z0-9._~@/-]/.test(previous)) {
|
|
860
|
+
return name
|
|
861
|
+
}
|
|
862
|
+
if (next && next !== '/' && /[A-Za-z0-9._~-]/.test(next)) {
|
|
863
|
+
return name
|
|
864
|
+
}
|
|
865
|
+
} else {
|
|
866
|
+
if (previous && /[A-Za-z0-9._-]/.test(previous)) {
|
|
867
|
+
return name
|
|
868
|
+
}
|
|
869
|
+
if (next && /[A-Za-z0-9._-]/.test(next)) {
|
|
870
|
+
return name
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return reference.replacement
|
|
874
|
+
})
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function isPathWithin(root: string, path: string, allowRoot: boolean) {
|
|
878
|
+
const relativePath = relative(root, path)
|
|
879
|
+
return (
|
|
880
|
+
(allowRoot && relativePath === '') ||
|
|
881
|
+
(relativePath !== '' &&
|
|
882
|
+
relativePath !== '..' &&
|
|
883
|
+
!relativePath.startsWith(`..${sep}`) &&
|
|
884
|
+
!isAbsolute(relativePath))
|
|
885
|
+
)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function assertPathWithin(
|
|
889
|
+
root: string,
|
|
890
|
+
path: string,
|
|
891
|
+
description: string,
|
|
892
|
+
allowRoot = false,
|
|
893
|
+
) {
|
|
894
|
+
if (!isPathWithin(root, path, allowRoot)) {
|
|
895
|
+
throw new Error(`${description} escapes project root ${root}: ${path}`)
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function assertRenamePathIdentity(
|
|
900
|
+
expected: string,
|
|
901
|
+
actual: string,
|
|
902
|
+
description: string,
|
|
903
|
+
) {
|
|
904
|
+
if (relative(expected, actual) === '' && relative(actual, expected) === '') {
|
|
905
|
+
return
|
|
906
|
+
}
|
|
907
|
+
const error = new Error(
|
|
908
|
+
`${description} changed after package reconciliation paths were resolved: ${expected} -> ${actual}`,
|
|
909
|
+
) as NodeJS.ErrnoException
|
|
910
|
+
error.code = 'ESTALE'
|
|
911
|
+
error.path = actual
|
|
912
|
+
throw error
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function revalidateRenameReconciliationPaths(
|
|
916
|
+
options: ReturnType<typeof applyDefaultRenameOptions>,
|
|
917
|
+
expected: ReturnType<typeof resolvePackageReconciliationPaths>,
|
|
918
|
+
) {
|
|
919
|
+
let actual: ReturnType<typeof resolvePackageReconciliationPaths>
|
|
920
|
+
try {
|
|
921
|
+
actual = resolvePackageReconciliationPaths(
|
|
922
|
+
options.cwd,
|
|
923
|
+
options.packageJsonPath,
|
|
924
|
+
[options.npmDir],
|
|
925
|
+
)
|
|
926
|
+
} catch (cause) {
|
|
927
|
+
const error = new Error(
|
|
928
|
+
'Package reconciliation paths changed after their locks were acquired',
|
|
929
|
+
{ cause },
|
|
930
|
+
) as NodeJS.ErrnoException
|
|
931
|
+
error.code = 'ESTALE'
|
|
932
|
+
error.path = expected.boundary
|
|
933
|
+
throw error
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
assertRenamePathIdentity(
|
|
937
|
+
expected.boundary,
|
|
938
|
+
actual.boundary,
|
|
939
|
+
'Package reconciliation boundary',
|
|
940
|
+
)
|
|
941
|
+
assertRenamePathIdentity(expected.cwd, actual.cwd, 'Project root')
|
|
942
|
+
assertRenamePathIdentity(
|
|
943
|
+
expected.packageRoot,
|
|
944
|
+
actual.packageRoot,
|
|
945
|
+
'Package root',
|
|
946
|
+
)
|
|
947
|
+
assertRenamePathIdentity(
|
|
948
|
+
expected.packageJsonPath,
|
|
949
|
+
actual.packageJsonPath,
|
|
950
|
+
'Package manifest',
|
|
951
|
+
)
|
|
952
|
+
if (expected.managedPaths.length !== actual.managedPaths.length) {
|
|
953
|
+
const error = new Error(
|
|
954
|
+
'Managed package paths changed after their locks were acquired',
|
|
955
|
+
) as NodeJS.ErrnoException
|
|
956
|
+
error.code = 'ESTALE'
|
|
957
|
+
error.path = actual.cwd
|
|
958
|
+
throw error
|
|
959
|
+
}
|
|
960
|
+
for (let index = 0; index < expected.managedPaths.length; index++) {
|
|
961
|
+
assertRenamePathIdentity(
|
|
962
|
+
expected.managedPaths[index],
|
|
963
|
+
actual.managedPaths[index],
|
|
964
|
+
`Managed package path ${index + 1}`,
|
|
965
|
+
)
|
|
966
|
+
}
|
|
967
|
+
return actual
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
async function canonicalizePath(path: string) {
|
|
971
|
+
let current = resolve(path)
|
|
972
|
+
const missingSegments: string[] = []
|
|
973
|
+
while (true) {
|
|
974
|
+
try {
|
|
975
|
+
return join(await realpath(current), ...missingSegments)
|
|
976
|
+
} catch (error) {
|
|
977
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
|
|
978
|
+
throw error
|
|
979
|
+
}
|
|
980
|
+
const parent = dirname(current)
|
|
981
|
+
if (parent === current) {
|
|
982
|
+
return join(current, ...missingSegments)
|
|
983
|
+
}
|
|
984
|
+
missingSegments.unshift(basename(current))
|
|
985
|
+
current = parent
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
async function resolveCanonicalRoot(
|
|
991
|
+
projectRoot: string,
|
|
992
|
+
path: string,
|
|
993
|
+
description: string,
|
|
994
|
+
) {
|
|
995
|
+
const lexicalPath = resolve(path)
|
|
996
|
+
assertPathWithin(projectRoot, lexicalPath, description, true)
|
|
997
|
+
const canonicalPath = await canonicalizePath(lexicalPath)
|
|
998
|
+
assertPathWithin(projectRoot, canonicalPath, description, true)
|
|
999
|
+
return canonicalPath
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
async function resolveCanonicalFile(
|
|
1003
|
+
projectRoot: string,
|
|
1004
|
+
path: string,
|
|
1005
|
+
description: string,
|
|
1006
|
+
) {
|
|
1007
|
+
const lexicalPath = resolve(path)
|
|
1008
|
+
assertPathWithin(projectRoot, lexicalPath, description)
|
|
1009
|
+
const canonicalParent = await canonicalizePath(dirname(lexicalPath))
|
|
1010
|
+
assertPathWithin(projectRoot, canonicalParent, `${description} parent`, true)
|
|
1011
|
+
const canonicalPath = join(canonicalParent, basename(lexicalPath))
|
|
1012
|
+
assertPathWithin(projectRoot, canonicalPath, description)
|
|
1013
|
+
if (pathExists(canonicalPath)) {
|
|
1014
|
+
assertRegularFile(canonicalPath, description)
|
|
1015
|
+
}
|
|
1016
|
+
return canonicalPath
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function resolveManagedPath(
|
|
1020
|
+
directory: string,
|
|
1021
|
+
entry: string,
|
|
1022
|
+
description: string,
|
|
1023
|
+
) {
|
|
1024
|
+
if (!entry || isAbsolute(entry)) {
|
|
1025
|
+
throw new Error(
|
|
1026
|
+
`${description} must be a non-empty relative path: ${entry}`,
|
|
1027
|
+
)
|
|
1028
|
+
}
|
|
1029
|
+
const root = resolve(directory)
|
|
1030
|
+
const path = resolve(root, entry)
|
|
1031
|
+
assertPathWithin(root, path, description)
|
|
1032
|
+
return path
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async function resolveProjectEntry(
|
|
1036
|
+
projectRoot: string,
|
|
1037
|
+
entry: string,
|
|
1038
|
+
description: string,
|
|
1039
|
+
) {
|
|
1040
|
+
const path = resolve(projectRoot, entry)
|
|
1041
|
+
if (!isPathWithin(projectRoot, path, false)) {
|
|
1042
|
+
return
|
|
1043
|
+
}
|
|
1044
|
+
return resolveCanonicalFile(projectRoot, path, description)
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
function collectManagedFileRenames(
|
|
1048
|
+
directory: string,
|
|
1049
|
+
renames: Map<string, string>,
|
|
1050
|
+
) {
|
|
1051
|
+
const operations: ManagedFileRename[] = []
|
|
1052
|
+
for (const [oldFile, newFile] of renames) {
|
|
1053
|
+
const source = resolveManagedPath(
|
|
1054
|
+
directory,
|
|
1055
|
+
oldFile,
|
|
1056
|
+
'Managed rename source',
|
|
1057
|
+
)
|
|
1058
|
+
const destination = resolveManagedPath(
|
|
1059
|
+
directory,
|
|
1060
|
+
newFile,
|
|
1061
|
+
'Managed rename destination',
|
|
1062
|
+
)
|
|
1063
|
+
if (source !== destination && pathExists(source)) {
|
|
1064
|
+
assertRegularFile(source, 'Managed rename source')
|
|
1065
|
+
operations.push({ source, destination })
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return operations
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function pathsReferToSameFile(left: string, right: string) {
|
|
1072
|
+
try {
|
|
1073
|
+
const leftStats = lstatSync(left)
|
|
1074
|
+
const rightStats = lstatSync(right)
|
|
1075
|
+
if (!leftStats.isFile() || !rightStats.isFile()) {
|
|
1076
|
+
return false
|
|
1077
|
+
}
|
|
1078
|
+
const leftPath = realpathSync.native(left)
|
|
1079
|
+
const rightPath = realpathSync.native(right)
|
|
45
1080
|
if (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
1081
|
+
leftPath === rightPath ||
|
|
1082
|
+
(process.platform === 'win32' &&
|
|
1083
|
+
leftPath.toLowerCase() === rightPath.toLowerCase())
|
|
49
1084
|
) {
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
1085
|
+
return true
|
|
1086
|
+
}
|
|
1087
|
+
return (
|
|
1088
|
+
(process.platform === 'darwin' || process.platform === 'win32') &&
|
|
1089
|
+
resolve(left).toLowerCase() === resolve(right).toLowerCase() &&
|
|
1090
|
+
leftStats.dev === rightStats.dev &&
|
|
1091
|
+
leftStats.ino === rightStats.ino
|
|
1092
|
+
)
|
|
1093
|
+
} catch (error) {
|
|
1094
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
1095
|
+
return false
|
|
53
1096
|
}
|
|
1097
|
+
throw error
|
|
54
1098
|
}
|
|
1099
|
+
}
|
|
55
1100
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
1101
|
+
function preflightManagedFileRenames(operations: ManagedFileRename[]) {
|
|
1102
|
+
const operationsBySource = new Map<string, ManagedFileRename>()
|
|
1103
|
+
const sources = new Set<string>()
|
|
1104
|
+
const destinations = new Map<string, string>()
|
|
1105
|
+
|
|
1106
|
+
for (const operation of operations) {
|
|
1107
|
+
const existingSource = operationsBySource.get(operation.source)
|
|
1108
|
+
if (existingSource) {
|
|
1109
|
+
if (existingSource.destination !== operation.destination) {
|
|
1110
|
+
throw new Error(
|
|
1111
|
+
`Managed rename source ${operation.source} has conflicting destinations ${existingSource.destination} and ${operation.destination}`,
|
|
1112
|
+
)
|
|
1113
|
+
}
|
|
1114
|
+
continue
|
|
1115
|
+
}
|
|
1116
|
+
operationsBySource.set(operation.source, operation)
|
|
1117
|
+
sources.add(operation.source)
|
|
1118
|
+
|
|
1119
|
+
const existingDestination = destinations.get(operation.destination)
|
|
1120
|
+
if (existingDestination) {
|
|
1121
|
+
throw new Error(
|
|
1122
|
+
`Managed rename destination ${operation.destination} is targeted by both ${existingDestination} and ${operation.source}`,
|
|
1123
|
+
)
|
|
1124
|
+
}
|
|
1125
|
+
destinations.set(operation.destination, operation.source)
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
const planned: PlannedManagedFileRename[] = []
|
|
1129
|
+
for (const operation of operationsBySource.values()) {
|
|
1130
|
+
const replacesSameFile =
|
|
1131
|
+
pathExists(operation.destination) &&
|
|
1132
|
+
pathsReferToSameFile(operation.source, operation.destination)
|
|
1133
|
+
if (
|
|
1134
|
+
pathExists(operation.destination) &&
|
|
1135
|
+
!sources.has(operation.destination) &&
|
|
1136
|
+
!replacesSameFile
|
|
1137
|
+
) {
|
|
1138
|
+
throw new Error(
|
|
1139
|
+
`Cannot rename managed file ${operation.source}: destination already exists: ${operation.destination}`,
|
|
1140
|
+
)
|
|
1141
|
+
}
|
|
1142
|
+
planned.push({ ...operation, replacesSameFile })
|
|
1143
|
+
}
|
|
1144
|
+
return planned
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
async function prepareManagedFileRenames(
|
|
1148
|
+
plan: RenameTransactionPlan,
|
|
1149
|
+
operations: PlannedManagedFileRename[],
|
|
1150
|
+
fileRenames: Map<string, string>,
|
|
1151
|
+
packageRenames: Map<string, string>,
|
|
1152
|
+
) {
|
|
1153
|
+
const destinations = new Set(
|
|
1154
|
+
operations.map((operation) => operation.destination),
|
|
1155
|
+
)
|
|
1156
|
+
for (const operation of operations) {
|
|
1157
|
+
const mode = fileMode(operation.source, 'Managed rename source')
|
|
1158
|
+
const content = await readFileAsync(operation.source)
|
|
1159
|
+
plan.addWrite(
|
|
1160
|
+
operation.destination,
|
|
1161
|
+
operation.source.endsWith('.wasm')
|
|
1162
|
+
? content
|
|
1163
|
+
: replaceManagedTextReferences(
|
|
1164
|
+
content.toString('utf8'),
|
|
1165
|
+
fileRenames,
|
|
1166
|
+
packageRenames,
|
|
1167
|
+
),
|
|
1168
|
+
mode,
|
|
1169
|
+
operation.replacesSameFile ? operation.source : undefined,
|
|
1170
|
+
)
|
|
1171
|
+
if (!operation.replacesSameFile && !destinations.has(operation.source)) {
|
|
1172
|
+
plan.removals.add(operation.source)
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
async function prepareManagedTextWrite(
|
|
1178
|
+
plan: RenameTransactionPlan,
|
|
1179
|
+
path: string,
|
|
1180
|
+
fileRenames: Map<string, string>,
|
|
1181
|
+
packageRenames: Map<string, string>,
|
|
1182
|
+
) {
|
|
1183
|
+
if (plan.hasWrite(path) || !pathExists(path) || path.endsWith('.wasm')) {
|
|
1184
|
+
return
|
|
1185
|
+
}
|
|
1186
|
+
const mode = fileMode(path, 'Managed text file')
|
|
1187
|
+
const content = await readFileAsync(path, 'utf8')
|
|
1188
|
+
const updated = replaceManagedTextReferences(
|
|
1189
|
+
content,
|
|
1190
|
+
fileRenames,
|
|
1191
|
+
packageRenames,
|
|
1192
|
+
)
|
|
1193
|
+
if (updated !== content) {
|
|
1194
|
+
plan.addWrite(path, updated, mode)
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function managedPackageReadme(packageName: string, target: Target) {
|
|
1199
|
+
return `# \`${packageName}-${target.platformArchABI}\`
|
|
1200
|
+
|
|
1201
|
+
This is the **${target.triple}** binary for \`${packageName}\`
|
|
1202
|
+
`
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
async function prepareManagedPackageReadme(
|
|
1206
|
+
plan: RenameTransactionPlan,
|
|
1207
|
+
directory: string,
|
|
1208
|
+
oldPackageName: string,
|
|
1209
|
+
newPackageName: string,
|
|
1210
|
+
target: Target,
|
|
1211
|
+
) {
|
|
1212
|
+
const path = resolveManagedPath(
|
|
1213
|
+
directory,
|
|
1214
|
+
'README.md',
|
|
1215
|
+
'Managed package README',
|
|
1216
|
+
)
|
|
1217
|
+
if (!pathExists(path)) {
|
|
1218
|
+
return
|
|
1219
|
+
}
|
|
1220
|
+
const mode = fileMode(path, 'Managed package README')
|
|
1221
|
+
const content = await readFileAsync(path, 'utf8')
|
|
1222
|
+
if (content === managedPackageReadme(oldPackageName, target)) {
|
|
1223
|
+
plan.addWrite(path, managedPackageReadme(newPackageName, target), mode)
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function managedRootEntries(content: string) {
|
|
1228
|
+
const firstLine = content.split(/\r?\n/, 1)[0]
|
|
1229
|
+
if (!firstLine.startsWith(WASI_ARTIFACT_METADATA_PREFIX)) {
|
|
1230
|
+
return []
|
|
1231
|
+
}
|
|
1232
|
+
try {
|
|
1233
|
+
const metadata = JSON.parse(
|
|
1234
|
+
firstLine.slice(WASI_ARTIFACT_METADATA_PREFIX.length),
|
|
1235
|
+
) as {
|
|
1236
|
+
rootEntry?: unknown
|
|
1237
|
+
managedRootEntries?: unknown
|
|
1238
|
+
}
|
|
1239
|
+
return [
|
|
1240
|
+
...(typeof metadata.rootEntry === 'string' ? [metadata.rootEntry] : []),
|
|
1241
|
+
...(Array.isArray(metadata.managedRootEntries)
|
|
1242
|
+
? metadata.managedRootEntries.filter(
|
|
1243
|
+
(entry): entry is string => typeof entry === 'string',
|
|
1244
|
+
)
|
|
1245
|
+
: []),
|
|
1246
|
+
]
|
|
1247
|
+
} catch {
|
|
1248
|
+
return []
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function asJsonRecord(value: unknown): JsonRecord | undefined {
|
|
1253
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
1254
|
+
? (value as JsonRecord)
|
|
1255
|
+
: undefined
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function updateNapiConfigRecord(record: JsonRecord, options: RenameOptions) {
|
|
1259
|
+
if (options.binaryName !== undefined) {
|
|
1260
|
+
defineJsonProperty(record, 'binaryName', options.binaryName)
|
|
1261
|
+
delete record.name
|
|
1262
|
+
}
|
|
1263
|
+
if (options.packageName !== undefined) {
|
|
1264
|
+
defineJsonProperty(record, 'packageName', options.packageName)
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
function serializeJsonLike(content: string, value: unknown) {
|
|
1269
|
+
return `${JSON.stringify(value, null, 2)}${content.endsWith('\n') ? '\n' : ''}`
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function sanitizeCargoPackageName(binaryName: string) {
|
|
1273
|
+
return binaryName.replace(/[^A-Za-z0-9_]/g, '_').toLowerCase()
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
async function executeRenameTransaction(
|
|
1277
|
+
projectRoot: string,
|
|
1278
|
+
plan: RenameTransactionPlan,
|
|
1279
|
+
) {
|
|
1280
|
+
if (plan.writes.size === 0 && plan.removals.size === 0) {
|
|
1281
|
+
return
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
const stagingRoot = await mkdtemp(join(tmpdir(), 'napi-rename-stage-'))
|
|
1285
|
+
try {
|
|
1286
|
+
const writes = []
|
|
1287
|
+
let sequence = 0
|
|
1288
|
+
for (const prepared of plan.writes.values()) {
|
|
1289
|
+
const source = join(stagingRoot, String(sequence++))
|
|
1290
|
+
await writeFile(source, prepared.content, { mode: prepared.mode })
|
|
1291
|
+
writes.push({
|
|
1292
|
+
destination: prepared.destination,
|
|
1293
|
+
mode: prepared.mode,
|
|
1294
|
+
removeBeforeWrite: prepared.removeBeforeWrite,
|
|
1295
|
+
source,
|
|
1296
|
+
})
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
await commitFileSystemTransaction(projectRoot, writes, [...plan.removals])
|
|
1300
|
+
} finally {
|
|
1301
|
+
await rm(stagingRoot, { force: true, recursive: true })
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
async function renameProjectUnlocked(
|
|
1306
|
+
userOptions: RenameOptions,
|
|
1307
|
+
resolvedPaths: ReturnType<typeof resolvePackageReconciliationPaths>,
|
|
1308
|
+
) {
|
|
1309
|
+
const options = applyDefaultRenameOptions(userOptions)
|
|
1310
|
+
const lockedPaths = revalidateRenameReconciliationPaths(
|
|
1311
|
+
options,
|
|
1312
|
+
resolvedPaths,
|
|
1313
|
+
)
|
|
1314
|
+
const projectRoot = await realpath(resolve(options.cwd))
|
|
1315
|
+
assertRenamePathIdentity(lockedPaths.cwd, projectRoot, 'Project root')
|
|
1316
|
+
const requestedPackageJsonPath = await resolveCanonicalFile(
|
|
1317
|
+
projectRoot,
|
|
1318
|
+
resolve(projectRoot, options.packageJsonPath),
|
|
1319
|
+
'package.json',
|
|
1320
|
+
)
|
|
1321
|
+
const packageJsonPath = await realpath(requestedPackageJsonPath)
|
|
1322
|
+
assertRenamePathIdentity(
|
|
1323
|
+
lockedPaths.packageJsonPath,
|
|
1324
|
+
packageJsonPath,
|
|
1325
|
+
'Package manifest',
|
|
1326
|
+
)
|
|
1327
|
+
const configPath = options.configPath
|
|
1328
|
+
? await resolveCanonicalFile(
|
|
1329
|
+
projectRoot,
|
|
1330
|
+
resolve(projectRoot, options.configPath),
|
|
1331
|
+
'NAPI config',
|
|
1332
|
+
)
|
|
1333
|
+
: undefined
|
|
1334
|
+
const npmRoot = await resolveCanonicalRoot(
|
|
1335
|
+
projectRoot,
|
|
1336
|
+
resolve(projectRoot, options.npmDir),
|
|
1337
|
+
'npm package root',
|
|
1338
|
+
)
|
|
1339
|
+
assertRenamePathIdentity(
|
|
1340
|
+
lockedPaths.managedPaths[0],
|
|
1341
|
+
npmRoot,
|
|
1342
|
+
'npm package root',
|
|
1343
|
+
)
|
|
1344
|
+
const normalizedOptions = {
|
|
1345
|
+
...options,
|
|
1346
|
+
cwd: projectRoot,
|
|
1347
|
+
packageJsonPath,
|
|
1348
|
+
configPath,
|
|
1349
|
+
npmDir: npmRoot,
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
const napiConfig = await readConfig(normalizedOptions)
|
|
1353
|
+
const oldName = napiConfig.binaryName
|
|
1354
|
+
const oldPackageName = napiConfig.packageName
|
|
1355
|
+
|
|
1356
|
+
assertSafeBinaryName(oldName, 'Configured binary name')
|
|
1357
|
+
validatePackageIdentity(
|
|
1358
|
+
oldPackageName,
|
|
1359
|
+
napiConfig.targets,
|
|
1360
|
+
'Configured package name',
|
|
1361
|
+
false,
|
|
1362
|
+
)
|
|
1363
|
+
if (options.binaryName !== undefined) {
|
|
1364
|
+
assertSafeBinaryName(options.binaryName, 'Requested binary name')
|
|
1365
|
+
}
|
|
1366
|
+
if (options.name !== undefined) {
|
|
1367
|
+
assertValidNpmPackageName(options.name, 'Requested root package name', true)
|
|
1368
|
+
}
|
|
1369
|
+
if (options.packageName !== undefined) {
|
|
1370
|
+
validatePackageIdentity(
|
|
1371
|
+
options.packageName,
|
|
1372
|
+
napiConfig.targets,
|
|
1373
|
+
'Requested package name',
|
|
1374
|
+
true,
|
|
69
1375
|
)
|
|
70
|
-
await writeFileAsync(configPath, JSON.stringify(configData, null, 2))
|
|
71
1376
|
}
|
|
72
1377
|
|
|
73
|
-
|
|
1378
|
+
const newName = options.binaryName ?? oldName
|
|
1379
|
+
const newPackageName = options.packageName ?? oldPackageName
|
|
1380
|
+
const binaryNameChanged =
|
|
1381
|
+
options.binaryName !== undefined && oldName !== options.binaryName
|
|
1382
|
+
const packageNameChanged =
|
|
1383
|
+
options.packageName !== undefined && oldPackageName !== options.packageName
|
|
1384
|
+
const managedWasiRenames = binaryNameChanged
|
|
1385
|
+
? createManagedWasiRenames(oldName, newName, napiConfig.targets)
|
|
1386
|
+
: new Map<string, string>()
|
|
1387
|
+
const managedPackageRenames = packageNameChanged
|
|
1388
|
+
? createManagedPackageRenames(
|
|
1389
|
+
oldPackageName,
|
|
1390
|
+
newPackageName,
|
|
1391
|
+
napiConfig.targets,
|
|
1392
|
+
)
|
|
1393
|
+
: new Map<string, string>()
|
|
1394
|
+
const hasManagedReferenceRenames =
|
|
1395
|
+
managedWasiRenames.size > 0 || managedPackageRenames.size > 0
|
|
1396
|
+
|
|
1397
|
+
const packageJsonMode = fileMode(packageJsonPath, 'package.json')
|
|
1398
|
+
const packageJsonContent = await readFileAsync(packageJsonPath, 'utf8')
|
|
1399
|
+
const parsedPackageJson = JSON.parse(packageJsonContent)
|
|
1400
|
+
const packageJsonData = asJsonRecord(parsedPackageJson)
|
|
1401
|
+
if (!packageJsonData) {
|
|
1402
|
+
throw new Error(
|
|
1403
|
+
`package.json must contain a JSON object: ${packageJsonPath}`,
|
|
1404
|
+
)
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
const managedRootEntryNames = new Set<string>()
|
|
1408
|
+
if (hasManagedReferenceRenames) {
|
|
1409
|
+
for (const field of ['main', 'module', 'browser', 'types'] as const) {
|
|
1410
|
+
const entry = packageJsonData[field]
|
|
1411
|
+
if (typeof entry === 'string') {
|
|
1412
|
+
managedRootEntryNames.add(entry)
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
for (const oldFile of createManagedWasiFiles(oldName, napiConfig.targets)) {
|
|
1416
|
+
if (!oldFile.endsWith('.cjs')) {
|
|
1417
|
+
continue
|
|
1418
|
+
}
|
|
1419
|
+
const loaderPath = resolveManagedPath(
|
|
1420
|
+
projectRoot,
|
|
1421
|
+
oldFile,
|
|
1422
|
+
'Managed WASI loader',
|
|
1423
|
+
)
|
|
1424
|
+
if (pathExists(loaderPath)) {
|
|
1425
|
+
assertRegularFile(loaderPath, 'Managed WASI loader')
|
|
1426
|
+
for (const entry of managedRootEntries(
|
|
1427
|
+
await readFileAsync(loaderPath, 'utf8'),
|
|
1428
|
+
)) {
|
|
1429
|
+
managedRootEntryNames.add(entry)
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
for (const [key, value] of Object.entries(
|
|
1436
|
+
omitBy(
|
|
1437
|
+
// @ts-expect-error missing fields: author and license
|
|
1438
|
+
pick(options, ['name', 'description', 'author', 'license']),
|
|
1439
|
+
isNil,
|
|
1440
|
+
),
|
|
1441
|
+
)) {
|
|
1442
|
+
defineJsonProperty(packageJsonData, key, value)
|
|
1443
|
+
}
|
|
1444
|
+
if (options.binaryName !== undefined || options.packageName !== undefined) {
|
|
1445
|
+
const napi = asJsonRecord(packageJsonData.napi) ?? createJsonRecord()
|
|
1446
|
+
updateNapiConfigRecord(napi, options)
|
|
1447
|
+
defineJsonProperty(packageJsonData, 'napi', napi)
|
|
1448
|
+
}
|
|
1449
|
+
if (options.repository) {
|
|
1450
|
+
const repository = asJsonRecord(packageJsonData.repository)
|
|
1451
|
+
if (repository) {
|
|
1452
|
+
defineJsonProperty(repository, 'url', options.repository)
|
|
1453
|
+
} else {
|
|
1454
|
+
defineJsonProperty(packageJsonData, 'repository', options.repository)
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const plan = new RenameTransactionPlan()
|
|
1459
|
+
plan.addWrite(
|
|
74
1460
|
packageJsonPath,
|
|
75
|
-
|
|
1461
|
+
serializeJsonLike(
|
|
1462
|
+
packageJsonContent,
|
|
1463
|
+
rewritePackageManifest(
|
|
1464
|
+
packageJsonData,
|
|
1465
|
+
managedWasiRenames,
|
|
1466
|
+
managedPackageRenames,
|
|
1467
|
+
),
|
|
1468
|
+
),
|
|
1469
|
+
packageJsonMode,
|
|
76
1470
|
)
|
|
77
1471
|
|
|
78
|
-
|
|
79
|
-
|
|
1472
|
+
if (configPath) {
|
|
1473
|
+
const configMode = fileMode(configPath, 'NAPI config')
|
|
1474
|
+
const configContent = await readFileAsync(configPath, 'utf8')
|
|
1475
|
+
const configData = asJsonRecord(JSON.parse(configContent))
|
|
1476
|
+
if (!configData) {
|
|
1477
|
+
throw new Error(`NAPI config must contain a JSON object: ${configPath}`)
|
|
1478
|
+
}
|
|
1479
|
+
updateNapiConfigRecord(configData, options)
|
|
1480
|
+
plan.addWrite(
|
|
1481
|
+
configPath,
|
|
1482
|
+
serializeJsonLike(configContent, configData),
|
|
1483
|
+
configMode,
|
|
1484
|
+
)
|
|
1485
|
+
}
|
|
80
1486
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
cargoToml
|
|
1487
|
+
if (binaryNameChanged) {
|
|
1488
|
+
const cargoTomlPath = await resolveCanonicalFile(
|
|
1489
|
+
projectRoot,
|
|
1490
|
+
resolve(projectRoot, options.manifestPath),
|
|
1491
|
+
'Cargo manifest',
|
|
1492
|
+
)
|
|
1493
|
+
const cargoTomlMode = fileMode(cargoTomlPath, 'Cargo manifest')
|
|
1494
|
+
const tomlContent = await readFileAsync(cargoTomlPath, 'utf8')
|
|
1495
|
+
const cargoToml = parseToml(tomlContent) as any
|
|
1496
|
+
if (cargoToml.package) {
|
|
1497
|
+
cargoToml.package.name = sanitizeCargoPackageName(newName)
|
|
1498
|
+
plan.addWrite(cargoTomlPath, stringifyToml(cargoToml), cargoTomlMode)
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
const workflowPath = join(projectRoot, '.github', 'workflows', 'CI.yml')
|
|
1502
|
+
if (pathExists(workflowPath)) {
|
|
1503
|
+
const canonicalWorkflowPath = await resolveCanonicalFile(
|
|
1504
|
+
projectRoot,
|
|
1505
|
+
workflowPath,
|
|
1506
|
+
'GitHub Actions workflow',
|
|
1507
|
+
)
|
|
1508
|
+
const workflowMode = fileMode(
|
|
1509
|
+
canonicalWorkflowPath,
|
|
1510
|
+
'GitHub Actions workflow',
|
|
1511
|
+
)
|
|
1512
|
+
const workflowContent = await readFileAsync(canonicalWorkflowPath, 'utf8')
|
|
1513
|
+
const workflowData = yamlParse(workflowContent) as any
|
|
1514
|
+
if (workflowData.env?.APP_NAME) {
|
|
1515
|
+
workflowData.env.APP_NAME = newName
|
|
1516
|
+
plan.addWrite(
|
|
1517
|
+
canonicalWorkflowPath,
|
|
1518
|
+
yamlStringify(workflowData, {
|
|
1519
|
+
lineWidth: -1,
|
|
1520
|
+
noRefs: true,
|
|
1521
|
+
sortKeys: false,
|
|
1522
|
+
}),
|
|
1523
|
+
workflowMode,
|
|
1524
|
+
)
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
90
1527
|
}
|
|
91
1528
|
|
|
92
|
-
|
|
93
|
-
const
|
|
1529
|
+
const targetDirectories: Array<{ directory: string; target: Target }> = []
|
|
1530
|
+
for (const target of napiConfig.targets) {
|
|
1531
|
+
const directory = await resolveCanonicalRoot(
|
|
1532
|
+
projectRoot,
|
|
1533
|
+
resolve(npmRoot, target.platformArchABI),
|
|
1534
|
+
`npm package directory for ${target.triple}`,
|
|
1535
|
+
)
|
|
1536
|
+
targetDirectories.push({ directory, target })
|
|
1537
|
+
}
|
|
94
1538
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
1539
|
+
const managedFileRenames = collectManagedFileRenames(
|
|
1540
|
+
projectRoot,
|
|
1541
|
+
managedWasiRenames,
|
|
1542
|
+
)
|
|
1543
|
+
for (const { target, directory } of targetDirectories) {
|
|
1544
|
+
if (!pathExists(directory)) {
|
|
1545
|
+
continue
|
|
1546
|
+
}
|
|
1547
|
+
if (target.platform === 'wasi') {
|
|
1548
|
+
managedFileRenames.push(
|
|
1549
|
+
...collectManagedFileRenames(directory, managedWasiRenames),
|
|
1550
|
+
)
|
|
1551
|
+
}
|
|
1552
|
+
if (hasManagedReferenceRenames) {
|
|
1553
|
+
const targetPackageJsonPath = resolveManagedPath(
|
|
1554
|
+
directory,
|
|
1555
|
+
'package.json',
|
|
1556
|
+
'Managed package manifest',
|
|
1557
|
+
)
|
|
1558
|
+
if (pathExists(targetPackageJsonPath)) {
|
|
1559
|
+
const manifestMode = fileMode(
|
|
1560
|
+
targetPackageJsonPath,
|
|
1561
|
+
'Managed package manifest',
|
|
110
1562
|
)
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
yamlStringify(githubActionsData, {
|
|
117
|
-
lineWidth: -1,
|
|
118
|
-
noRefs: true,
|
|
119
|
-
sortKeys: false,
|
|
120
|
-
}),
|
|
1563
|
+
const content = await readFileAsync(targetPackageJsonPath, 'utf8')
|
|
1564
|
+
const manifest = asJsonRecord(JSON.parse(content))
|
|
1565
|
+
if (!manifest) {
|
|
1566
|
+
throw new Error(
|
|
1567
|
+
`Managed package manifest must contain a JSON object: ${targetPackageJsonPath}`,
|
|
121
1568
|
)
|
|
122
1569
|
}
|
|
1570
|
+
const updatedContent = serializeJsonLike(
|
|
1571
|
+
content,
|
|
1572
|
+
rewritePackageManifest(
|
|
1573
|
+
manifest,
|
|
1574
|
+
managedWasiRenames,
|
|
1575
|
+
managedPackageRenames,
|
|
1576
|
+
),
|
|
1577
|
+
)
|
|
1578
|
+
if (updatedContent !== content) {
|
|
1579
|
+
plan.addWrite(targetPackageJsonPath, updatedContent, manifestMode)
|
|
1580
|
+
}
|
|
123
1581
|
}
|
|
124
1582
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
const plannedFileRenames = preflightManagedFileRenames(managedFileRenames)
|
|
1586
|
+
await prepareManagedFileRenames(
|
|
1587
|
+
plan,
|
|
1588
|
+
plannedFileRenames,
|
|
1589
|
+
managedWasiRenames,
|
|
1590
|
+
managedPackageRenames,
|
|
1591
|
+
)
|
|
1592
|
+
|
|
1593
|
+
if (hasManagedReferenceRenames) {
|
|
1594
|
+
for (const file of createManagedWasiFiles(newName, napiConfig.targets)) {
|
|
1595
|
+
await prepareManagedTextWrite(
|
|
1596
|
+
plan,
|
|
1597
|
+
resolveManagedPath(projectRoot, file, 'Managed WASI file'),
|
|
1598
|
+
managedWasiRenames,
|
|
1599
|
+
managedPackageRenames,
|
|
133
1600
|
)
|
|
134
1601
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
1602
|
+
|
|
1603
|
+
for (const { target, directory } of targetDirectories) {
|
|
1604
|
+
if (!pathExists(directory)) {
|
|
1605
|
+
continue
|
|
1606
|
+
}
|
|
1607
|
+
if (target.platform === 'wasi') {
|
|
1608
|
+
for (const file of createManagedWasiFiles(newName, [target])) {
|
|
1609
|
+
await prepareManagedTextWrite(
|
|
1610
|
+
plan,
|
|
1611
|
+
resolveManagedPath(directory, file, 'Managed package WASI file'),
|
|
1612
|
+
managedWasiRenames,
|
|
1613
|
+
managedPackageRenames,
|
|
1614
|
+
)
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
if (managedPackageRenames.size > 0) {
|
|
1618
|
+
await prepareManagedPackageReadme(
|
|
1619
|
+
plan,
|
|
1620
|
+
directory,
|
|
1621
|
+
oldPackageName,
|
|
1622
|
+
newPackageName,
|
|
1623
|
+
target,
|
|
1624
|
+
)
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
for (const entry of managedRootEntryNames) {
|
|
1629
|
+
const updatedEntry = replaceManifestReference(
|
|
1630
|
+
entry,
|
|
1631
|
+
managedWasiRenames,
|
|
1632
|
+
new Map(),
|
|
1633
|
+
)
|
|
1634
|
+
const path = await resolveProjectEntry(
|
|
1635
|
+
projectRoot,
|
|
1636
|
+
updatedEntry,
|
|
1637
|
+
'Managed root entry',
|
|
140
1638
|
)
|
|
1639
|
+
if (path) {
|
|
1640
|
+
await prepareManagedTextWrite(
|
|
1641
|
+
plan,
|
|
1642
|
+
path,
|
|
1643
|
+
managedWasiRenames,
|
|
1644
|
+
managedPackageRenames,
|
|
1645
|
+
)
|
|
1646
|
+
}
|
|
141
1647
|
}
|
|
142
|
-
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
1648
|
+
|
|
1649
|
+
if (managedWasiRenames.size > 0) {
|
|
1650
|
+
await prepareManagedTextWrite(
|
|
1651
|
+
plan,
|
|
1652
|
+
resolveManagedPath(
|
|
1653
|
+
projectRoot,
|
|
1654
|
+
'.gitattributes',
|
|
1655
|
+
'Managed .gitattributes file',
|
|
1656
|
+
),
|
|
1657
|
+
managedWasiRenames,
|
|
1658
|
+
new Map(),
|
|
147
1659
|
)
|
|
148
|
-
const gitAttributesData = gitAttributesContent
|
|
149
|
-
.split('\n')
|
|
150
|
-
.map((line) => {
|
|
151
|
-
return line
|
|
152
|
-
.replace(
|
|
153
|
-
`${oldName}.wasi-browser.js`,
|
|
154
|
-
`${options.binaryName}.wasi-browser.js`,
|
|
155
|
-
)
|
|
156
|
-
.replace(`${oldName}.wasi.cjs`, `${options.binaryName}.wasi.cjs`)
|
|
157
|
-
})
|
|
158
|
-
.join('\n')
|
|
159
|
-
await writeFileAsync(gitAttributesPath, gitAttributesData)
|
|
160
1660
|
}
|
|
161
1661
|
}
|
|
1662
|
+
|
|
1663
|
+
await executeRenameTransaction(projectRoot, plan)
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
export async function renameProject(userOptions: RenameOptions) {
|
|
1667
|
+
const options = applyDefaultRenameOptions(userOptions)
|
|
1668
|
+
const resolvedPaths = resolvePackageReconciliationPaths(
|
|
1669
|
+
options.cwd,
|
|
1670
|
+
options.packageJsonPath,
|
|
1671
|
+
[options.npmDir],
|
|
1672
|
+
)
|
|
1673
|
+
return withPackageFileSystemReconciliation(resolvedPaths, () =>
|
|
1674
|
+
renameProjectUnlocked(options, resolvedPaths),
|
|
1675
|
+
)
|
|
162
1676
|
}
|