@expo/repack-app 0.0.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 -0
- package/assets/Configuration.proto +216 -0
- package/assets/Resources.proto +648 -0
- package/assets/debug.keystore +0 -0
- package/bin/cli.js +3 -0
- package/build/android/Resources.types.d.ts +93 -0
- package/build/android/Resources.types.js +4 -0
- package/build/android/build-tools.d.ts +32 -0
- package/build/android/build-tools.js +157 -0
- package/build/android/index.d.ts +5 -0
- package/build/android/index.js +53 -0
- package/build/android/resources.d.ts +10 -0
- package/build/android/resources.js +170 -0
- package/build/cli.d.ts +1 -0
- package/build/cli.js +78 -0
- package/build/expo.d.ts +10 -0
- package/build/expo.js +26 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +2 -0
- package/build/ios/build-tools.d.ts +14 -0
- package/build/ios/build-tools.js +43 -0
- package/build/ios/index.d.ts +9 -0
- package/build/ios/index.js +65 -0
- package/build/ios/resources.d.ts +10 -0
- package/build/ios/resources.js +69 -0
- package/build/log.d.ts +13 -0
- package/build/log.js +23 -0
- package/build/types.d.ts +85 -0
- package/build/types.js +1 -0
- package/build/utils.d.ts +13 -0
- package/build/utils.js +27 -0
- package/package.json +44 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import spawnAsync from '@expo/turtle-spawn';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { directoryExistsAsync } from '../utils';
|
|
6
|
+
/**
|
|
7
|
+
* Unzip the IPA file.
|
|
8
|
+
*/
|
|
9
|
+
export async function unzipIpaAsync(options) {
|
|
10
|
+
const unzipWorkingDirectory = path.join(options.workingDirectory, 'unzip');
|
|
11
|
+
await spawnAsync('unzip', [options.sourceAppPath, '-d', unzipWorkingDirectory], {
|
|
12
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
13
|
+
});
|
|
14
|
+
const appWorkingDirectory = path.join(unzipWorkingDirectory, 'Payload', 'HelloWorld.app');
|
|
15
|
+
assert(await directoryExistsAsync(appWorkingDirectory));
|
|
16
|
+
return appWorkingDirectory;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Update some binary files.
|
|
20
|
+
*/
|
|
21
|
+
export async function updateFilesAsync(config, appWorkingDirectory) {
|
|
22
|
+
const parentDir = path.dirname(appWorkingDirectory);
|
|
23
|
+
const newAppWorkingDirectory = path.join(parentDir, `${config.name}.app`);
|
|
24
|
+
// [0] Update the .app directory
|
|
25
|
+
await fs.rename(path.join(parentDir, 'HelloWorld.app'), newAppWorkingDirectory);
|
|
26
|
+
// [1] Rename the executable
|
|
27
|
+
await fs.rename(path.join(newAppWorkingDirectory, 'HelloWorld'), path.join(newAppWorkingDirectory, config.name));
|
|
28
|
+
return newAppWorkingDirectory;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* From the given working .app directory, create a new .ipa file.
|
|
32
|
+
*/
|
|
33
|
+
export async function createIpaAsync(options, appWorkingDirectory) {
|
|
34
|
+
const { workingDirectory } = options;
|
|
35
|
+
await fs.mkdir(path.join(workingDirectory, 'Payload'), { recursive: true });
|
|
36
|
+
await fs.rename(appWorkingDirectory, path.join(workingDirectory, 'Payload', path.basename(appWorkingDirectory)));
|
|
37
|
+
const outputIpaPath = path.join(workingDirectory, 'repacked.ipa');
|
|
38
|
+
await spawnAsync('zip', ['-r', outputIpaPath, 'Payload'], {
|
|
39
|
+
cwd: workingDirectory,
|
|
40
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
41
|
+
});
|
|
42
|
+
return outputIpaPath;
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IosSigningOptions, Options } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Repack an iOS app.
|
|
4
|
+
*/
|
|
5
|
+
export declare function repackAppIosAsync(_options: Options): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Resign an IPA by fastlane.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resignIpaAsync(ipaPath: string, signingOptions: IosSigningOptions, options: Options): Promise<void>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getConfig } from '@expo/config';
|
|
2
|
+
import spawnAsync from '@expo/turtle-spawn';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import pico from 'picocolors';
|
|
6
|
+
import { createIpaAsync, unzipIpaAsync, updateFilesAsync } from './build-tools.js';
|
|
7
|
+
import { updateExpoPlistAsync, updateInfoPlistAsync } from './resources.js';
|
|
8
|
+
import { generateAppConfigAsync, resolveRuntimeVersionAsync } from '../expo.js';
|
|
9
|
+
import logger from '../log.js';
|
|
10
|
+
import { normalizeOptionAsync } from '../utils.js';
|
|
11
|
+
/**
|
|
12
|
+
* Repack an iOS app.
|
|
13
|
+
*/
|
|
14
|
+
export async function repackAppIosAsync(_options) {
|
|
15
|
+
const options = await normalizeOptionAsync(_options);
|
|
16
|
+
const { workingDirectory } = options;
|
|
17
|
+
await fs.mkdir(workingDirectory, { recursive: true });
|
|
18
|
+
const { exp } = getConfig(options.projectRoot, {
|
|
19
|
+
isPublicConfig: true,
|
|
20
|
+
skipSDKVersionRequirement: true,
|
|
21
|
+
});
|
|
22
|
+
const updatesRuntimeVersion = await resolveRuntimeVersionAsync(options, exp);
|
|
23
|
+
logger.log(pico.dim(`Resolved runtime version: ${updatesRuntimeVersion}`));
|
|
24
|
+
logger.time(`Unzipping IPA`);
|
|
25
|
+
let appWorkingDirectory = await unzipIpaAsync(options);
|
|
26
|
+
appWorkingDirectory = await updateFilesAsync(exp, appWorkingDirectory);
|
|
27
|
+
logger.timeEnd(`Unzipping IPA`);
|
|
28
|
+
logger.time(`Updating Info.plist`);
|
|
29
|
+
await updateInfoPlistAsync(exp, path.join(appWorkingDirectory, 'Info.plist'), options);
|
|
30
|
+
logger.timeEnd(`Updating Info.plist`);
|
|
31
|
+
logger.time(`Updating Expo.plist`);
|
|
32
|
+
await updateExpoPlistAsync(exp, path.join(appWorkingDirectory, 'Expo.plist'), updatesRuntimeVersion, options);
|
|
33
|
+
logger.timeEnd(`Updating Expo.plist`);
|
|
34
|
+
logger.time(`Generating app.config`);
|
|
35
|
+
const appConfigPath = await generateAppConfigAsync(options, exp);
|
|
36
|
+
await fs.copyFile(appConfigPath, path.join(appWorkingDirectory, 'EXConstants.bundle', 'app.config'));
|
|
37
|
+
logger.timeEnd(`Generating app.config`);
|
|
38
|
+
logger.time(`Creating updated ipa`);
|
|
39
|
+
const outputIpa = await createIpaAsync(options, appWorkingDirectory);
|
|
40
|
+
logger.timeEnd(`Creating updated ipa`);
|
|
41
|
+
await fs.rename(outputIpa, options.outputPath);
|
|
42
|
+
try {
|
|
43
|
+
await fs.rmdir(workingDirectory, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
46
|
+
return options.outputPath;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resign an IPA by fastlane.
|
|
50
|
+
*/
|
|
51
|
+
export async function resignIpaAsync(ipaPath, signingOptions, options) {
|
|
52
|
+
const args = [
|
|
53
|
+
'run',
|
|
54
|
+
'resign',
|
|
55
|
+
`ipa:${ipaPath}`,
|
|
56
|
+
`signing_identity:${signingOptions.signingIdentity}`,
|
|
57
|
+
`provisioning_profile:${signingOptions.provisioningProfile}`,
|
|
58
|
+
];
|
|
59
|
+
if (signingOptions.keychainPath) {
|
|
60
|
+
args.push(`keychain_path:${signingOptions.keychainPath}`);
|
|
61
|
+
}
|
|
62
|
+
await spawnAsync('fastlane', args, {
|
|
63
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ExpoConfig } from '@expo/config';
|
|
2
|
+
import type { NormalizedOptions } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Update the Info.plist file.
|
|
5
|
+
*/
|
|
6
|
+
export declare function updateInfoPlistAsync(config: ExpoConfig, infoPlistPath: string, options: NormalizedOptions): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Update the Expo.plist file.
|
|
9
|
+
*/
|
|
10
|
+
export declare function updateExpoPlistAsync(config: ExpoConfig, expoPlistPath: string, runtimeVersion: string, options: NormalizedOptions): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import plist from '@expo/plist';
|
|
2
|
+
import spawnAsync from '@expo/turtle-spawn';
|
|
3
|
+
import assert from 'node:assert';
|
|
4
|
+
import fs from 'node:fs/promises';
|
|
5
|
+
import { requireNotNull } from '../utils';
|
|
6
|
+
/**
|
|
7
|
+
* Update the Info.plist file.
|
|
8
|
+
*/
|
|
9
|
+
export async function updateInfoPlistAsync(config, infoPlistPath, options) {
|
|
10
|
+
const bundleIdentifier = config.ios?.bundleIdentifier;
|
|
11
|
+
assert(bundleIdentifier, 'The `bundleIdentifier` must be specified in the `ios` config.');
|
|
12
|
+
const firstScheme = Array.isArray(config.scheme) ? config.scheme[0] : config.scheme;
|
|
13
|
+
await updateBinaryPlistAsync(infoPlistPath, options, (data) => {
|
|
14
|
+
const urlTypes = data.CFBundleURLTypes.slice();
|
|
15
|
+
for (const urlType of urlTypes) {
|
|
16
|
+
if (Array.isArray(urlType.CFBundleURLSchemes)) {
|
|
17
|
+
const schemes = urlType.CFBundleURLSchemes.map((scheme) => {
|
|
18
|
+
return (scheme
|
|
19
|
+
// scheme in app.json
|
|
20
|
+
.replace(/^myapp$/g, requireNotNull(firstScheme))
|
|
21
|
+
// ios.bundleIdentifier in app.json
|
|
22
|
+
.replace(/^dev.expo.templatedefault$/g, requireNotNull(bundleIdentifier))
|
|
23
|
+
// default scheme generated from slug in app.json
|
|
24
|
+
.replace(/^exp\+expo-template-default$/g, `exp+${requireNotNull(config.slug)}`));
|
|
25
|
+
});
|
|
26
|
+
urlType.CFBundleURLSchemes = schemes;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const userActivityTypes = data.NSUserActivityTypes.map((activityType) => activityType.replace(/^dev.expo.templatedefault/g, bundleIdentifier));
|
|
30
|
+
return {
|
|
31
|
+
...data,
|
|
32
|
+
CFBundleDisplayName: config.name,
|
|
33
|
+
CFBundleName: config.name,
|
|
34
|
+
CFBundleExecutable: config.name,
|
|
35
|
+
CFBundleIdentifier: bundleIdentifier,
|
|
36
|
+
NSUserActivityTypes: userActivityTypes,
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Update the Expo.plist file.
|
|
42
|
+
*/
|
|
43
|
+
export async function updateExpoPlistAsync(config, expoPlistPath, runtimeVersion, options) {
|
|
44
|
+
await updateBinaryPlistAsync(expoPlistPath, options, (data) => {
|
|
45
|
+
const updateUrl = config.updates?.url;
|
|
46
|
+
assert(updateUrl);
|
|
47
|
+
return {
|
|
48
|
+
...data,
|
|
49
|
+
EXUpdatesEnabled: true,
|
|
50
|
+
EXUpdatesRuntimeVersion: runtimeVersion,
|
|
51
|
+
EXUpdatesURL: updateUrl,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//#region Internals
|
|
56
|
+
async function updateBinaryPlistAsync(plistPath, options, updater) {
|
|
57
|
+
await spawnAsync('plutil', ['-convert', 'xml1', plistPath], {
|
|
58
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
59
|
+
});
|
|
60
|
+
const contents = await fs.readFile(plistPath, 'utf8');
|
|
61
|
+
const data = await plist.parse(contents);
|
|
62
|
+
const updatedData = updater(data);
|
|
63
|
+
const updatedContents = plist.build(updatedData);
|
|
64
|
+
await fs.writeFile(plistPath, updatedContents);
|
|
65
|
+
await spawnAsync('plutil', ['-convert', 'binary1', plistPath], {
|
|
66
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
package/build/log.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function log(...message: string[]): void;
|
|
2
|
+
export declare function warn(...message: string[]): void;
|
|
3
|
+
export declare function error(...message: string[]): void;
|
|
4
|
+
export declare function time(label?: string): void;
|
|
5
|
+
export declare function timeEnd(label?: string): void;
|
|
6
|
+
declare const _default: {
|
|
7
|
+
log: typeof log;
|
|
8
|
+
warn: typeof warn;
|
|
9
|
+
error: typeof error;
|
|
10
|
+
time: typeof time;
|
|
11
|
+
timeEnd: typeof timeEnd;
|
|
12
|
+
};
|
|
13
|
+
export default _default;
|
package/build/log.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import pico from 'picocolors';
|
|
2
|
+
export function log(...message) {
|
|
3
|
+
console.log(...message);
|
|
4
|
+
}
|
|
5
|
+
export function warn(...message) {
|
|
6
|
+
console.warn(...message.map((value) => pico.yellow(value)));
|
|
7
|
+
}
|
|
8
|
+
export function error(...message) {
|
|
9
|
+
console.error(...message);
|
|
10
|
+
}
|
|
11
|
+
export function time(label) {
|
|
12
|
+
console.time(label);
|
|
13
|
+
}
|
|
14
|
+
export function timeEnd(label) {
|
|
15
|
+
console.timeEnd(label);
|
|
16
|
+
}
|
|
17
|
+
export default {
|
|
18
|
+
log,
|
|
19
|
+
warn,
|
|
20
|
+
error,
|
|
21
|
+
time,
|
|
22
|
+
timeEnd,
|
|
23
|
+
};
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export interface Options {
|
|
2
|
+
/**
|
|
3
|
+
* The platform to repack the app for.
|
|
4
|
+
*/
|
|
5
|
+
platform: 'android' | 'ios';
|
|
6
|
+
/**
|
|
7
|
+
* The path to the project root directory.
|
|
8
|
+
*/
|
|
9
|
+
projectRoot: string;
|
|
10
|
+
/**
|
|
11
|
+
* The prebuilt app file path that acts as a source for repacking.
|
|
12
|
+
*/
|
|
13
|
+
sourceAppPath: string;
|
|
14
|
+
/**
|
|
15
|
+
* Working directory.
|
|
16
|
+
*/
|
|
17
|
+
workingDirectory?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The path to the output app file.
|
|
20
|
+
* @default `projectRoot/repacked.apk` or `projectRoot/repacked.ipa`
|
|
21
|
+
*/
|
|
22
|
+
outputPath?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Enable verbose logging.
|
|
25
|
+
*/
|
|
26
|
+
verbose?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* The options for signing the android app.
|
|
29
|
+
*/
|
|
30
|
+
androidSigningOptions?: AndroidSigningOptions;
|
|
31
|
+
/**
|
|
32
|
+
* The path to the Android SDK build-tools directory.
|
|
33
|
+
* @default Try to find the latest build-tools directory in the `ANDROID_SDK_ROOT` directory.
|
|
34
|
+
*/
|
|
35
|
+
androidBuildToolsDir?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The options for signing the ios app.
|
|
38
|
+
*/
|
|
39
|
+
iosSigningOptions?: IosSigningOptions;
|
|
40
|
+
}
|
|
41
|
+
export interface NormalizedOptions extends Options {
|
|
42
|
+
workingDirectory: NonNullable<Options['workingDirectory']>;
|
|
43
|
+
outputPath: NonNullable<Options['outputPath']>;
|
|
44
|
+
}
|
|
45
|
+
export interface AndroidSigningOptions {
|
|
46
|
+
/**
|
|
47
|
+
* Load private key and certificate chain from the Java
|
|
48
|
+
* KeyStore initialized from the specified file.
|
|
49
|
+
*/
|
|
50
|
+
keyStorePath: string;
|
|
51
|
+
/**
|
|
52
|
+
* KeyStore password.
|
|
53
|
+
* The following formats are supported:
|
|
54
|
+
* - pass:<password> password provided inline
|
|
55
|
+
* - env:<name> password provided in the named environment variable
|
|
56
|
+
* - file:<file> password provided in the named file, as a single line
|
|
57
|
+
*/
|
|
58
|
+
keyStorePassword: string;
|
|
59
|
+
/**
|
|
60
|
+
* Alias under which the private key and certificate are stored in the KeyStore.
|
|
61
|
+
*/
|
|
62
|
+
keyAlias?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Password with which the private key is protected.
|
|
65
|
+
* The following formats are supported:
|
|
66
|
+
* - pass:<password> password provided inline
|
|
67
|
+
* - env:<name> password provided in the named environment variable
|
|
68
|
+
* - file:<file> password provided in the named file, as a single line
|
|
69
|
+
*/
|
|
70
|
+
keyPassword?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface IosSigningOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Code signing identity.
|
|
75
|
+
*/
|
|
76
|
+
signingIdentity: string;
|
|
77
|
+
/**
|
|
78
|
+
* Path to your provisioning_profile.
|
|
79
|
+
*/
|
|
80
|
+
provisioningProfile: string;
|
|
81
|
+
/**
|
|
82
|
+
* Provide a path to a keychain file that should be used by /usr/bin/codesign
|
|
83
|
+
*/
|
|
84
|
+
keychainPath?: string;
|
|
85
|
+
}
|
package/build/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/build/utils.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NormalizedOptions, Options } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Check if a directory exists.
|
|
4
|
+
*/
|
|
5
|
+
export declare function directoryExistsAsync(file: string): Promise<boolean>;
|
|
6
|
+
/**
|
|
7
|
+
* Return the non-null value.
|
|
8
|
+
*/
|
|
9
|
+
export declare function requireNotNull<T>(value: T | null | undefined): T;
|
|
10
|
+
/**
|
|
11
|
+
* Normalize the options.
|
|
12
|
+
*/
|
|
13
|
+
export declare function normalizeOptionAsync(options: Options): Promise<NormalizedOptions>;
|
package/build/utils.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Check if a directory exists.
|
|
6
|
+
*/
|
|
7
|
+
export async function directoryExistsAsync(file) {
|
|
8
|
+
return (await fs.stat(file).catch(() => null))?.isDirectory() ?? false;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Return the non-null value.
|
|
12
|
+
*/
|
|
13
|
+
export function requireNotNull(value) {
|
|
14
|
+
assert(value != null, 'Expected value to be non-null');
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Normalize the options.
|
|
19
|
+
*/
|
|
20
|
+
export async function normalizeOptionAsync(options) {
|
|
21
|
+
const fileExt = options.platform === 'android' ? '.apk' : '.ipa';
|
|
22
|
+
return {
|
|
23
|
+
...options,
|
|
24
|
+
workingDirectory: options.workingDirectory ?? (await fs.mkdtemp(path.join(require('temp-dir'), 'repack-app-'))),
|
|
25
|
+
outputPath: options.outputPath ?? path.join(options.projectRoot, `repacked${fileExt}`),
|
|
26
|
+
};
|
|
27
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@expo/repack-app",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Repacking tool for Expo apps",
|
|
5
|
+
"main": "build/index.js",
|
|
6
|
+
"module": "build/index.js",
|
|
7
|
+
"types": "build/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "expo-module build",
|
|
11
|
+
"clean": "expo-module clean",
|
|
12
|
+
"lint": "expo-module lint",
|
|
13
|
+
"prepare": "expo-module clean && tsc",
|
|
14
|
+
"prepublishOnly": "expo-module prepublishOnly",
|
|
15
|
+
"test": "expo-module test",
|
|
16
|
+
"typecheck": "expo-module typecheck"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"repack-app": "bin/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"assets",
|
|
23
|
+
"bin",
|
|
24
|
+
"build"
|
|
25
|
+
],
|
|
26
|
+
"author": "650 Industries, Inc.",
|
|
27
|
+
"license": "BUSL-1.1",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@expo/config": "^8.5.6",
|
|
30
|
+
"@expo/turtle-spawn": "^1.0.57",
|
|
31
|
+
"commander": "^12.0.0",
|
|
32
|
+
"glob": "^10.3.12",
|
|
33
|
+
"picocolors": "^1.0.0",
|
|
34
|
+
"protobufjs": "^7.2.6",
|
|
35
|
+
"resolve-from": "^5.0.0",
|
|
36
|
+
"temp-dir": "^2.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@tsconfig/node20": "^20.1.4",
|
|
40
|
+
"eslint": "^8.57.0",
|
|
41
|
+
"expo-module-scripts": "^3.4.2",
|
|
42
|
+
"typescript": "^5.4.5"
|
|
43
|
+
}
|
|
44
|
+
}
|