react-native-legal 1.2.0 → 1.3.1
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 +100 -11
- package/bare-plugin/build/android/androidCommand.js +3 -3
- package/bare-plugin/build/index.d.ts +1 -1
- package/bare-plugin/build/index.js +6 -2
- package/bare-plugin/build/ios/addSettingsBundle.js +1 -0
- package/bare-plugin/build/ios/iosCommand.js +3 -3
- package/bare-plugin/build/ios/utils.js +3 -3
- package/bare-plugin/src/android/androidCommand.ts +2 -2
- package/bare-plugin/src/index.ts +8 -3
- package/bare-plugin/src/ios/addSettingsBundle.ts +3 -0
- package/bare-plugin/src/ios/iosCommand.ts +2 -2
- package/bare-plugin/src/ios/utils.ts +1 -2
- package/bare-plugin/tsconfig.json +1 -1
- package/package.json +3 -1
- package/plugin/build/android/withAndroidLegal.js +3 -3
- package/plugin/build/ios/withIosLegal.js +3 -3
- package/plugin/src/android/withAndroidLegal.ts +2 -3
- package/plugin/src/ios/withIosLegal.ts +2 -3
- package/plugin-utils/build/android.js +2 -2
- package/plugin-utils/build/ios.js +1 -1
- package/plugin-utils/src/android.ts +2 -2
- package/plugin-utils/src/ios.ts +1 -1
- package/plugin-utils/tsconfig.json +1 -1
- package/react-native.config.js +1 -1
- package/plugin-utils/build/common.d.ts +0 -46
- package/plugin-utils/build/common.js +0 -339
- package/plugin-utils/src/common.ts +0 -438
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
import crypto from 'crypto';
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
|
|
5
|
-
const glob = require('glob');
|
|
6
|
-
|
|
7
|
-
type LicenseObj = {
|
|
8
|
-
author?: string;
|
|
9
|
-
content?: string;
|
|
10
|
-
description?: string;
|
|
11
|
-
file?: string;
|
|
12
|
-
type?: string;
|
|
13
|
-
url?: string;
|
|
14
|
-
version: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type AboutLibrariesLibraryJsonPayload = {
|
|
18
|
-
artifactVersion: string;
|
|
19
|
-
description: string;
|
|
20
|
-
developers: { name: string; organisationUrl: string }[];
|
|
21
|
-
licenses: string[];
|
|
22
|
-
name: string;
|
|
23
|
-
tag: string;
|
|
24
|
-
uniqueId: string;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
type AboutLibrariesLicenseJsonPayload = {
|
|
28
|
-
content: string;
|
|
29
|
-
hash: string;
|
|
30
|
-
name: string;
|
|
31
|
-
url: string;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type LicensePlistPayload = {
|
|
35
|
-
name: string;
|
|
36
|
-
source?: string;
|
|
37
|
-
version: string;
|
|
38
|
-
body?: string;
|
|
39
|
-
file?: string;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function compareObjects(a: unknown, b: unknown): boolean {
|
|
43
|
-
if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') {
|
|
44
|
-
return a === b;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const entriesA = Object.entries(a);
|
|
48
|
-
const entriesB = Object.entries(b);
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
entriesA.length === entriesB.length &&
|
|
52
|
-
entriesA
|
|
53
|
-
.map(([keyA, valueA]) => {
|
|
54
|
-
const entry = entriesB.find(([keyB]) => keyA === keyB);
|
|
55
|
-
|
|
56
|
-
if (!entry) {
|
|
57
|
-
return valueA === entry;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const [, valueB] = entry;
|
|
61
|
-
|
|
62
|
-
return compareObjects(valueA, valueB);
|
|
63
|
-
})
|
|
64
|
-
.reduce((acc, curr) => acc && curr, true)
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Makes a deep-check between array items and provided object, returns true if array has provided object.
|
|
70
|
-
*/
|
|
71
|
-
export function arrayIncludesObject(array?: unknown[], object?: unknown) {
|
|
72
|
-
return array?.map((item) => compareObjects(item, object)).reduce((acc, curr) => acc || curr, false);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Scans a single package and its dependencies for license information
|
|
77
|
-
*/
|
|
78
|
-
function scanPackage(
|
|
79
|
-
packageName: string,
|
|
80
|
-
version: string,
|
|
81
|
-
processedPackages: Set<string>,
|
|
82
|
-
result: Record<string, LicenseObj>,
|
|
83
|
-
) {
|
|
84
|
-
const packageKey = `${packageName}@${version}`;
|
|
85
|
-
|
|
86
|
-
// Skip if already processed to avoid circular dependencies
|
|
87
|
-
if (processedPackages.has(packageKey)) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
processedPackages.add(packageKey);
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const localPackageJsonPath = getPackageJsonPath(packageName);
|
|
95
|
-
|
|
96
|
-
if (!localPackageJsonPath) {
|
|
97
|
-
console.warn(`[react-native-legal] skipping ${packageName} could not find package.json`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const localPackageJson = require(path.resolve(localPackageJsonPath));
|
|
102
|
-
|
|
103
|
-
if (localPackageJson.private !== true) {
|
|
104
|
-
const licenseFiles = glob.sync('LICEN{S,C}E{.md,}', {
|
|
105
|
-
cwd: path.dirname(localPackageJsonPath),
|
|
106
|
-
absolute: true,
|
|
107
|
-
nocase: true,
|
|
108
|
-
nodir: true,
|
|
109
|
-
ignore: '**/{__tests__,__fixtures__,__mocks__}/**',
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
result[packageName] = {
|
|
113
|
-
author: parseAuthorField(localPackageJson),
|
|
114
|
-
content: licenseFiles?.[0] ? fs.readFileSync(licenseFiles[0], { encoding: 'utf-8' }) : undefined,
|
|
115
|
-
file: licenseFiles?.[0] ? licenseFiles[0] : undefined,
|
|
116
|
-
description: localPackageJson.description,
|
|
117
|
-
type: parseLicenseField(localPackageJson),
|
|
118
|
-
url: parseRepositoryFieldToUrl(localPackageJson),
|
|
119
|
-
version: localPackageJson.version,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const dependencies = localPackageJson.dependencies || {};
|
|
124
|
-
|
|
125
|
-
const isWorkspacePackage = version.startsWith('workspace:');
|
|
126
|
-
|
|
127
|
-
if (!isWorkspacePackage) return;
|
|
128
|
-
|
|
129
|
-
Object.entries(dependencies).forEach(([depName, depVersion]) => {
|
|
130
|
-
scanPackage(depName, depVersion as string, processedPackages, result);
|
|
131
|
-
});
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.warn(`[react-native-legal] could not process package.json for ${packageName}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Scans `package.json` and searches for all packages under `dependencies` field. Supports monorepo projects.
|
|
139
|
-
*/
|
|
140
|
-
export function scanDependencies(appPackageJsonPath: string) {
|
|
141
|
-
const appPackageJson = require(path.resolve(appPackageJsonPath));
|
|
142
|
-
const dependencies: Record<string, string> = appPackageJson.dependencies;
|
|
143
|
-
const result: Record<string, LicenseObj> = {};
|
|
144
|
-
const processedPackages = new Set<string>();
|
|
145
|
-
|
|
146
|
-
Object.entries(dependencies).forEach(([packageName, version]) => {
|
|
147
|
-
scanPackage(packageName, version, processedPackages, result);
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function needsQuoting(value: string) {
|
|
154
|
-
return (
|
|
155
|
-
value === '' || // empty string
|
|
156
|
-
/^[#:>|-]/.test(value) || // starts with special char
|
|
157
|
-
/^['"{}[\],&*#?|<>=!%@`]/.test(value) || // starts with indicator chars
|
|
158
|
-
/^[\s]|[\s]$/.test(value) || // has leading/trailing whitespace
|
|
159
|
-
/^[\d.+-]/.test(value) || // looks like a number/bool/null
|
|
160
|
-
/[\n"'\\\s]/.test(value) || // contains newlines, quotes, backslash, or spaces
|
|
161
|
-
/^(true|false|yes|no|null|on|off)$/i.test(value) // is a YAML keyword
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function formatYamlKey(key: string) {
|
|
166
|
-
return /[@/_.]/.test(key) ? `"${key}"` : key;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function formatYamlValue(value: string, indent: number) {
|
|
170
|
-
if (value.includes('\n')) {
|
|
171
|
-
const indentedValue = value
|
|
172
|
-
.split('\n')
|
|
173
|
-
.map((line) => `${' '.repeat(indent)}${line}`)
|
|
174
|
-
.join('\n');
|
|
175
|
-
|
|
176
|
-
// Return the block indicator on the same line as the content
|
|
177
|
-
return `|${indentedValue ? '\n' + indentedValue : ''}`;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (needsQuoting(value)) {
|
|
181
|
-
if (value.includes("'") && !value.includes('"')) {
|
|
182
|
-
return `"${value.replace(/["\\]/g, '\\$&')}"`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return `'${value.replace(/'/g, "''")}'`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return value;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function toYaml(obj: unknown, indent = 0): string {
|
|
192
|
-
const spaces = ' '.repeat(indent);
|
|
193
|
-
|
|
194
|
-
if (obj == null) return '';
|
|
195
|
-
|
|
196
|
-
if (Array.isArray(obj)) {
|
|
197
|
-
return obj.map((item) => `${spaces}- ${toYaml(item, indent + 2).trimStart()}`).join('\n');
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (typeof obj === 'object') {
|
|
201
|
-
return Object.entries(obj)
|
|
202
|
-
.filter(([, v]) => v != null)
|
|
203
|
-
.map(([key, value]) => {
|
|
204
|
-
const formattedKey = formatYamlKey(key);
|
|
205
|
-
const formattedValue = toYaml(value, indent + 2);
|
|
206
|
-
|
|
207
|
-
if (Array.isArray(value)) {
|
|
208
|
-
return `${spaces}${formattedKey}:\n${formattedValue}`;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (typeof value === 'object' && value !== null) {
|
|
212
|
-
return `${spaces}${formattedKey}:\n${formattedValue}`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (typeof value === 'string' && value.includes('\n')) {
|
|
216
|
-
return `${spaces}${formattedKey}: ${formattedValue}`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return `${spaces}${formattedKey}: ${formattedValue}`;
|
|
220
|
-
})
|
|
221
|
-
.join('\n');
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return typeof obj === 'string' ? formatYamlValue(obj, indent) : String(obj);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Generates LicensePlist-compatible metadata for NPM dependencies
|
|
229
|
-
*
|
|
230
|
-
* This will take scanned NPM licenses and produce following output inside iOS project's directory:
|
|
231
|
-
*
|
|
232
|
-
* | - ios
|
|
233
|
-
* | ---- myawesomeapp
|
|
234
|
-
* | ---- myawesomeapp.xcodeproj
|
|
235
|
-
* | ---- myawesomeapp.xcodeworkspace
|
|
236
|
-
* | ---- license_plist.yml <--- generated LicensePlist config with NPM dependencies
|
|
237
|
-
* | ---- Podfile
|
|
238
|
-
* | ---- Podfile.lock
|
|
239
|
-
*/
|
|
240
|
-
export function generateLicensePlistNPMOutput(licenses: Record<string, LicenseObj>, iosProjectPath: string) {
|
|
241
|
-
const renames: Record<string, string> = {};
|
|
242
|
-
const licenseEntries = Object.entries(licenses).map(([dependency, licenseObj]) => {
|
|
243
|
-
const normalizedName = normalizePackageName(dependency);
|
|
244
|
-
|
|
245
|
-
if (dependency !== normalizedName) {
|
|
246
|
-
renames[normalizedName] = dependency;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const relativeLicenseFile = licenseObj.file ? path.relative(iosProjectPath, licenseObj.file) : undefined;
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
name: normalizedName,
|
|
253
|
-
version: licenseObj.version,
|
|
254
|
-
...(licenseObj.url && { source: licenseObj.url }),
|
|
255
|
-
...(licenseObj.file
|
|
256
|
-
? { file: relativeLicenseFile }
|
|
257
|
-
: { body: licenseObj.content ?? licenseObj.type ?? 'UNKNOWN' }),
|
|
258
|
-
} as LicensePlistPayload;
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
const yamlDoc = {
|
|
262
|
-
...(Object.keys(renames).length > 0 && { rename: renames }),
|
|
263
|
-
manual: licenseEntries,
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
const yamlContent = [
|
|
267
|
-
'# BEGIN Generated NPM license entries',
|
|
268
|
-
toYaml(yamlDoc),
|
|
269
|
-
'# END Generated NPM license entries',
|
|
270
|
-
].join('\n');
|
|
271
|
-
|
|
272
|
-
fs.writeFileSync(path.join(iosProjectPath, 'license_plist.yml'), yamlContent, { encoding: 'utf-8' });
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Generates AboutLibraries-compatible metadata for NPM dependencies
|
|
277
|
-
*
|
|
278
|
-
* This will take scanned NPM licenses and produce following output inside android project's directory:
|
|
279
|
-
*
|
|
280
|
-
* | - android
|
|
281
|
-
* | ---- app
|
|
282
|
-
* | ---- config <--- generated AboutLibraries config directory
|
|
283
|
-
* | ------- libraries <--- generated directory with JSON files list of NPM dependencies
|
|
284
|
-
* | ------- licenses <--- generated directory with JSON files list of used licenses
|
|
285
|
-
* | ---- build.gradle
|
|
286
|
-
* | ---- settings.gradle
|
|
287
|
-
*/
|
|
288
|
-
export function generateAboutLibrariesNPMOutput(licenses: Record<string, LicenseObj>, androidProjectPath: string) {
|
|
289
|
-
const aboutLibrariesConfigDirPath = path.join(androidProjectPath, 'config');
|
|
290
|
-
const aboutLibrariesConfigLibrariesDirPath = path.join(aboutLibrariesConfigDirPath, 'libraries');
|
|
291
|
-
const aboutLibrariesConfigLicensesDirPath = path.join(aboutLibrariesConfigDirPath, 'licenses');
|
|
292
|
-
|
|
293
|
-
if (!fs.existsSync(aboutLibrariesConfigDirPath)) {
|
|
294
|
-
fs.mkdirSync(aboutLibrariesConfigDirPath);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (!fs.existsSync(aboutLibrariesConfigLibrariesDirPath)) {
|
|
298
|
-
fs.mkdirSync(aboutLibrariesConfigLibrariesDirPath);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (!fs.existsSync(aboutLibrariesConfigLicensesDirPath)) {
|
|
302
|
-
fs.mkdirSync(aboutLibrariesConfigLicensesDirPath);
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
Object.entries(licenses)
|
|
306
|
-
.map(([dependency, licenseObj]) => {
|
|
307
|
-
return {
|
|
308
|
-
artifactVersion: licenseObj.version,
|
|
309
|
-
content: licenseObj.content ?? '',
|
|
310
|
-
description: licenseObj.description ?? '',
|
|
311
|
-
developers: [{ name: licenseObj.author ?? '', organisationUrl: '' }],
|
|
312
|
-
licenses: [prepareAboutLibrariesLicenseField(licenseObj)],
|
|
313
|
-
name: dependency,
|
|
314
|
-
tag: '',
|
|
315
|
-
type: licenseObj.type,
|
|
316
|
-
uniqueId: normalizePackageName(dependency),
|
|
317
|
-
};
|
|
318
|
-
})
|
|
319
|
-
.map((jsonPayload) => {
|
|
320
|
-
const libraryJsonPayload: AboutLibrariesLibraryJsonPayload = {
|
|
321
|
-
artifactVersion: jsonPayload.artifactVersion,
|
|
322
|
-
description: jsonPayload.description,
|
|
323
|
-
developers: jsonPayload.developers,
|
|
324
|
-
licenses: jsonPayload.licenses,
|
|
325
|
-
name: jsonPayload.name,
|
|
326
|
-
tag: jsonPayload.tag,
|
|
327
|
-
uniqueId: jsonPayload.uniqueId,
|
|
328
|
-
};
|
|
329
|
-
const licenseJsonPayload: AboutLibrariesLicenseJsonPayload = {
|
|
330
|
-
content: jsonPayload.content,
|
|
331
|
-
hash: jsonPayload.licenses[0],
|
|
332
|
-
name: jsonPayload.type ?? '',
|
|
333
|
-
url: '',
|
|
334
|
-
};
|
|
335
|
-
const libraryJsonFilePath = path.join(
|
|
336
|
-
aboutLibrariesConfigLibrariesDirPath,
|
|
337
|
-
`${normalizePackageName(jsonPayload.name)}.json`,
|
|
338
|
-
);
|
|
339
|
-
const licenseJsonFilePath = path.join(aboutLibrariesConfigLicensesDirPath, `${licenseJsonPayload.hash}.json`);
|
|
340
|
-
|
|
341
|
-
fs.writeFileSync(libraryJsonFilePath, JSON.stringify(libraryJsonPayload));
|
|
342
|
-
|
|
343
|
-
if (!fs.existsSync(licenseJsonFilePath)) {
|
|
344
|
-
fs.writeFileSync(licenseJsonFilePath, JSON.stringify(licenseJsonPayload));
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function prepareAboutLibrariesLicenseField(license: LicenseObj) {
|
|
350
|
-
if (!license.type) {
|
|
351
|
-
return '';
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return `${license.type}_${sha512(license.content ?? license.type)}`;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function sha512(text: string) {
|
|
358
|
-
return crypto.createHash('sha512').update(text).digest('hex');
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function parseAuthorField(json: { author: string | { name: string } }) {
|
|
362
|
-
if (typeof json.author === 'object' && typeof json.author.name === 'string') {
|
|
363
|
-
return json.author.name;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (typeof json.author === 'string') {
|
|
367
|
-
return json.author;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function parseLicenseField(json: { license: string | { type: string } }) {
|
|
372
|
-
if (typeof json.license === 'object' && typeof json.license.type === 'string') {
|
|
373
|
-
return json.license.type;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (typeof json.license === 'string') {
|
|
377
|
-
return json.license;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function parseRepositoryFieldToUrl(json: { repository: string | { url?: string } }) {
|
|
382
|
-
if (typeof json.repository === 'object' && typeof json.repository.url === 'string') {
|
|
383
|
-
return normalizeRepositoryUrl(json.repository.url);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (typeof json.repository === 'string') {
|
|
387
|
-
return normalizeRepositoryUrl(json.repository);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function normalizeRepositoryUrl(url: string) {
|
|
392
|
-
return url
|
|
393
|
-
.replace('git+ssh://git@', 'git://')
|
|
394
|
-
.replace('.git', '')
|
|
395
|
-
.replace('git+https://github.com', 'https://github.com')
|
|
396
|
-
.replace('.git', '')
|
|
397
|
-
.replace('git://github.com', 'https://github.com')
|
|
398
|
-
.replace('.git', '')
|
|
399
|
-
.replace('git@github.com:', 'https://github.com/')
|
|
400
|
-
.replace('.git', '')
|
|
401
|
-
.replace('github:', 'https://github.com/')
|
|
402
|
-
.replace('.git', '');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function getPackageJsonPath(dependency: string) {
|
|
406
|
-
try {
|
|
407
|
-
return require.resolve(`${dependency}/package.json`);
|
|
408
|
-
} catch (error) {
|
|
409
|
-
return resolvePackageJsonFromEntry(dependency);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function resolvePackageJsonFromEntry(dependency: string) {
|
|
414
|
-
try {
|
|
415
|
-
const entryPath = require.resolve(dependency);
|
|
416
|
-
const packageDir = findPackageRoot(entryPath);
|
|
417
|
-
|
|
418
|
-
if (!packageDir) return null;
|
|
419
|
-
|
|
420
|
-
const packageJsonPath = path.join(packageDir, 'package.json');
|
|
421
|
-
|
|
422
|
-
return fs.existsSync(packageJsonPath) ? packageJsonPath : null;
|
|
423
|
-
} catch {
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function findPackageRoot(entryPath: string) {
|
|
429
|
-
let currentDir = path.dirname(entryPath);
|
|
430
|
-
while (currentDir !== path.dirname(currentDir)) {
|
|
431
|
-
if (fs.existsSync(path.join(currentDir, 'package.json'))) return currentDir;
|
|
432
|
-
currentDir = path.dirname(currentDir);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function normalizePackageName(packageName: string): string {
|
|
437
|
-
return packageName.replace('/', '_');
|
|
438
|
-
}
|