@flun/node-mobile-app 2.1.1 → 3.0.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/src/patcher.js CHANGED
@@ -4,26 +4,25 @@ import { writeIfChanged } from './utils.js';
4
4
 
5
5
  // 把字符串中所有非 ASCII 字符转成 \uXXXX(Java Properties 规范)
6
6
  // gradle.properties 按 ISO-8859-1 读取,无法改为 UTF-8
7
- const escapeProps = (s) =>
8
- String(s).replace(/[^\x00-\x7F]/g, (ch) => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0'));
9
-
10
- /**
11
- * 解析最终 appName:
12
- * 1. config.appName
13
- * 2. 用户项目 package.json 的 name
14
- * 3. 'MyApp'
15
- */
16
- const resolveAppName = (userProjectDir, config) => {
17
- if (config.appName) return String(config.appName);
18
- const pkgPath = path.join(userProjectDir, 'package.json');
19
- if (fs.existsSync(pkgPath)) {
20
- try {
21
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
22
- if (pkg.name) return String(pkg.name);
23
- } catch { }
24
- }
25
- return 'MyApp';
26
- },
7
+ const escapeProps = s =>
8
+ String(s).replace(/[^\x00-\x7F]/g, ch => '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0')),
9
+ /**
10
+ * 解析最终 appName:
11
+ * 1. config.appName
12
+ * 2. 用户项目 package.json 的 name
13
+ * 3. 'MyApp'
14
+ */
15
+ resolveAppName = (userProjectDir, config) => {
16
+ if (config.appName) return String(config.appName);
17
+ const pkgPath = path.join(userProjectDir, 'package.json');
18
+ if (fs.existsSync(pkgPath)) {
19
+ try {
20
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
21
+ if (pkg.name) return String(pkg.name);
22
+ } catch { }
23
+ }
24
+ return 'MyApp';
25
+ },
27
26
  /** 通用字符串转义基础:\\, ", \n, \r, \t */
28
27
  escapeStringBase = s => String(s)
29
28
  .replace(/\\/g, '\\\\')
@@ -53,8 +52,7 @@ const resolveAppName = (userProjectDir, config) => {
53
52
  patchAppConfig = (buildDir, userProjectDir, config) => {
54
53
  const appName = resolveAppName(userProjectDir, config), appId = config.appId,
55
54
  versionCode = config.versionCode, versionName = config.versionName,
56
- gradleFile = path.join(buildDir, 'android', 'app', 'build.gradle'),
57
- appJsonFile = path.join(buildDir, 'app.json'),
55
+ gradleFile = path.join(buildDir, 'android', 'app', 'build.gradle'), appJsonFile = path.join(buildDir, 'app.json'),
58
56
  stringsFile = path.join(buildDir, 'android', 'app', 'src', 'main', 'res', 'values', 'strings.xml'),
59
57
  ktFiles = [
60
58
  path.join(buildDir, 'android', 'app', 'src', 'main', 'java', 'com', 'myapp', 'MainActivity.kt'),
@@ -384,8 +382,7 @@ const resolveAppName = (userProjectDir, config) => {
384
382
  console.log(` ✓ iOS 图标已应用: ${path.basename(iconAbs)}`);
385
383
  }
386
384
 
387
- // ===== 导出 =====
388
385
  export {
389
386
  resolveAppName, escapeKotlinString, escapeSwiftString, patchAppConfig, patchAndroidConfig, patchSigning,
390
387
  patchIcon, patchIosConfig, patchIosIcon
391
- };
388
+ };
@@ -1,142 +1,140 @@
1
- import path from 'path';
2
- import fs from 'fs';
3
- import { run, ensureDir } from './utils.js';
4
-
5
- const SCHEME = 'MyApp',
6
- /** iOS 相关命令只能在 macOS 上跑 */
7
- ensureMac = () => {
8
- if (process.platform !== 'darwin') throw new Error('iOS 构建只能在 macOS 上执行(当前平台: ' + process.platform + ')');
9
- },
10
- /** 检查 xcodebuild 是否存在 */
11
- checkIosTools = () => {
12
- const r = run('which', ['xcodebuild'], { stdio: 'pipe' });
13
- if (r.status !== 0) throw new Error('未找到 xcodebuild,请安装 Xcode 并运行 xcode-select --install');
14
- },
15
- /**
16
- * 执行 pod install(若 Pods/ 已存在且 Podfile.lock 未变,cocoapods 会自动跳过)
17
- */
18
- podInstall = iosDir => {
19
- console.log(' pod install ...'), run('pod', ['install'], { cwd: iosDir });
20
- },
21
- /**
22
- * 把 config.ios.signing.certificate 导入临时 keychain,供 xcodebuild 签名用
23
- * 返回 { keychainPath, keychainPassword } 或 null(未配置 / 文件缺失)
24
- */
25
- setupSigningKeychain = (buildDir, userProjectDir, config) => {
26
- const signing = config.ios && config.ios.signing;
27
- if (!signing || !signing.certificate) return null;
28
-
29
- const certPath = path.resolve(userProjectDir, signing.certificate);
30
- if (!fs.existsSync(certPath)) {
31
- console.warn(' ⚠️ iOS 签名证书不存在,跳过:', certPath);
32
- return null;
33
- }
34
-
35
- const keychainDir = path.join(buildDir, 'ios', 'build', 'signing');
36
- ensureDir(keychainDir);
37
- const keychainPath = path.join(keychainDir, 'build.keychain-db'), keychainPassword = 'nmapp' + Date.now();
38
-
39
- // 清掉同名的旧 keychain(重复构建时)
40
- if (fs.existsSync(keychainPath)) fs.rmSync(keychainPath, { force: true });
41
- console.log(' 创建临时 keychain 并导入证书 ...');
42
- run('security', ['create-keychain', '-p', keychainPassword, keychainPath]);
43
- run('security', ['set-keychain-settings', '-lut', '3600', keychainPath]);
44
- run('security', ['unlock-keychain', '-p', keychainPassword, keychainPath]);
45
- run('security', [
46
- 'import', certPath,
47
- '-k', keychainPath,
48
- '-P', signing.certificatePassword || '',
49
- '-T', '/usr/bin/codesign',
50
- '-T', '/usr/bin/security',
51
- ]);
52
- run('security', [
53
- 'set-key-partition-list',
54
- '-S', 'apple-tool:,apple:,codesign:',
55
- '-s',
56
- '-k', keychainPassword,
57
- keychainPath,
58
- ]);
59
-
60
- return { keychainPath, keychainPassword };
61
- },
62
- /**
63
- * xcodebuild archive
64
- * 返回 xcarchive 路径
65
- */
66
- xcodebuildArchive = (buildDir, config, release, userProjectDir) => {
67
- const iosDir = path.join(buildDir, 'ios'), workspace = path.join(iosDir, SCHEME + '.xcworkspace');
68
- if (!fs.existsSync(workspace)) throw new Error('未找到 ' + workspace + '(pod install 是否成功?)');
69
-
70
- const archiveDir = path.join(iosDir, 'build');
71
- ensureDir(archiveDir);
72
- const archivePath = path.join(archiveDir, SCHEME + '.xcarchive'), configName = release ? 'Release' : 'Debug',
73
- teamId = (config.ios && config.ios.signing && config.ios.signing.teamId) || '',
74
- args = [
75
- '-workspace', workspace,
76
- '-scheme', SCHEME,
77
- '-configuration', configName,
78
- '-destination', 'generic/platform=iOS',
79
- '-archivePath', archivePath,
80
- 'archive',
81
- ];
82
-
83
- if (teamId) args.push('DEVELOPMENT_TEAM=' + teamId);
84
-
85
- // 若配置了 certificate,导入临时 keychain 并让 xcodebuild 使用
86
- const keychain = setupSigningKeychain(buildDir, userProjectDir, config);
87
- if (keychain) args.push('OTHER_CODE_SIGN_FLAGS=--keychain ' + keychain.keychainPath);
88
-
89
- console.log(' xcodebuild archive ...'), run('xcodebuild', args, { cwd: iosDir });
90
- return archivePath;
91
- },
92
- /**
93
- * xcodebuild -exportArchive,导出 .ipa
94
- * 返回 .ipa 路径
95
- */
96
- xcodebuildExport = (buildDir, config, release, outDir, userProjectDir) => {
97
- const iosDir = path.join(buildDir, 'ios'), exportDir = path.join(iosDir, 'build', 'export');
98
- ensureDir(exportDir);
99
- const archivePath = path.join(iosDir, 'build', SCHEME + '.xcarchive'), method = release ? 'ad-hoc' : 'development',
100
- signing = (config.ios && config.ios.signing) || {}, teamId = signing.teamId || '',
101
- // provisioningProfile 路径 → 取 basename(去扩展名),写进 exportOptions.plist
102
- profilePath = signing.provisioningProfile ? path.resolve(userProjectDir, signing.provisioningProfile) : '',
103
- profileName = profilePath ? path.basename(profilePath).replace(/\.mobileprovision$/i, '') : '',
104
- plistPath = path.join(iosDir, 'build', 'exportOptions.plist'),
105
- plist = [
106
- '<?xml version="1.0" encoding="UTF-8"?>',
107
- '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
108
- '<plist version="1.0">',
109
- '<dict>',
110
- ' <key>method</key>',
111
- ' <string>' + method + '</string>',
112
- ' <key>stripSwiftSymbols</key>',
113
- ' <true/>',
114
- ' <key>compileBitcode</key>',
115
- ' <false/>',
116
- teamId ? ' <key>teamID</key>' : '',
117
- teamId ? ' <string>' + teamId + '</string>' : '',
118
- profileName ? ' <key>provisioningProfiles</key>' : '',
119
- profileName ? ' <dict>' : '',
120
- profileName ? ' <key>' + config.appId + '</key>' : '',
121
- profileName ? ' <string>' + profileName + '</string>' : '',
122
- profileName ? ' </dict>' : '',
123
- '</dict>',
124
- '</plist>',
125
- ].filter(Boolean).join('\n');
126
- fs.writeFileSync(plistPath, plist, 'utf-8'); // 生成 exportOptions.plist
127
-
128
- console.log(' xcodebuild -exportArchive ...');
129
- run('xcodebuild', [
130
- '-exportArchive',
131
- '-archivePath', archivePath,
132
- '-exportOptionsPlist', plistPath,
133
- '-exportPath', exportDir,
134
- ], { cwd: iosDir });
135
-
136
- // 找 .ipa
137
- const files = fs.readdirSync(exportDir).filter(f => f.endsWith('.ipa'));
138
- if (files.length === 0) throw new Error('导出目录里没有 .ipa: ' + exportDir);
139
- return path.join(exportDir, files[0]);
140
- };
141
-
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import { run, ensureDir } from './utils.js';
4
+
5
+ const SCHEME = 'MyApp',
6
+ /** iOS 相关命令只能在 macOS 上跑 */
7
+ ensureMac = () => {
8
+ if (process.platform !== 'darwin') throw new Error('iOS 构建只能在 macOS 上执行(当前平台: ' + process.platform + ')');
9
+ },
10
+ /** 检查 xcodebuild 是否存在 */
11
+ checkIosTools = () => {
12
+ const r = run('which', ['xcodebuild'], { stdio: 'pipe' });
13
+ if (r.status !== 0) throw new Error('未找到 xcodebuild,请安装 Xcode 并运行 xcode-select --install');
14
+ },
15
+ /**
16
+ * 执行 pod install(若 Pods/ 已存在且 Podfile.lock 未变,cocoapods 会自动跳过)
17
+ */
18
+ podInstall = iosDir => {
19
+ console.log(' pod install ...'), run('pod', ['install'], { cwd: iosDir, inherit: true });
20
+ },
21
+ /**
22
+ * 把 config.ios.signing.certificate 导入临时 keychain,供 xcodebuild 签名用
23
+ * 返回 { keychainPath, keychainPassword } 或 null(未配置 / 文件缺失)
24
+ */
25
+ setupSigningKeychain = (buildDir, userProjectDir, config) => {
26
+ const signing = config.ios && config.ios.signing;
27
+ if (!signing || !signing.certificate) return null;
28
+
29
+ const certPath = path.resolve(userProjectDir, signing.certificate);
30
+ if (!fs.existsSync(certPath)) {
31
+ console.warn(' ⚠️ iOS 签名证书不存在,跳过:', certPath);
32
+ return null;
33
+ }
34
+
35
+ const keychainDir = path.join(buildDir, 'ios', 'build', 'signing');
36
+ ensureDir(keychainDir);
37
+ const keychainPath = path.join(keychainDir, 'build.keychain-db'), keychainPassword = 'nmapp' + Date.now();
38
+
39
+ // 清掉同名的旧 keychain(重复构建时)
40
+ if (fs.existsSync(keychainPath)) fs.rmSync(keychainPath, { force: true });
41
+ console.log(' 创建临时 keychain 并导入证书 ...');
42
+ run('security', ['create-keychain', '-p', keychainPassword, keychainPath], { quiet: true });
43
+ run('security', ['set-keychain-settings', '-lut', '3600', keychainPath], { quiet: true });
44
+ run('security', ['unlock-keychain', '-p', keychainPassword, keychainPath], { quiet: true });
45
+ run('security', [
46
+ 'import', certPath,
47
+ '-k', keychainPath,
48
+ '-P', signing.certificatePassword || '',
49
+ '-T', '/usr/bin/codesign',
50
+ '-T', '/usr/bin/security'
51
+ ], { quiet: true });
52
+ run('security', [
53
+ 'set-key-partition-list',
54
+ '-S', 'apple-tool:,apple:,codesign:',
55
+ '-s', '-k', keychainPassword, keychainPath
56
+ ], { quiet: true });
57
+
58
+ return { keychainPath, keychainPassword };
59
+ },
60
+ /**
61
+ * xcodebuild archive
62
+ * 返回 xcarchive 路径
63
+ */
64
+ xcodebuildArchive = (buildDir, config, release, userProjectDir) => {
65
+ const iosDir = path.join(buildDir, 'ios'), workspace = path.join(iosDir, SCHEME + '.xcworkspace');
66
+ if (!fs.existsSync(workspace)) throw new Error('未找到 ' + workspace + '(pod install 是否成功?)');
67
+
68
+ const archiveDir = path.join(iosDir, 'build');
69
+ ensureDir(archiveDir);
70
+ const archivePath = path.join(archiveDir, SCHEME + '.xcarchive'), configName = release ? 'Release' : 'Debug',
71
+ teamId = (config.ios && config.ios.signing && config.ios.signing.teamId) || '',
72
+ args = [
73
+ '-workspace', workspace,
74
+ '-scheme', SCHEME,
75
+ '-configuration', configName,
76
+ '-destination', 'generic/platform=iOS',
77
+ '-archivePath', archivePath,
78
+ 'archive',
79
+ ];
80
+
81
+ if (teamId) args.push('DEVELOPMENT_TEAM=' + teamId);
82
+
83
+ // 若配置了 certificate,导入临时 keychain 并让 xcodebuild 使用
84
+ const keychain = setupSigningKeychain(buildDir, userProjectDir, config);
85
+ if (keychain) args.push('OTHER_CODE_SIGN_FLAGS=--keychain ' + keychain.keychainPath);
86
+
87
+ console.log(' xcodebuild archive ...'), run('xcodebuild', args, { cwd: iosDir, quiet: true });
88
+ return archivePath;
89
+ },
90
+ /**
91
+ * xcodebuild -exportArchive,导出 .ipa
92
+ * 返回 .ipa 路径
93
+ */
94
+ xcodebuildExport = (buildDir, config, release, outDir, userProjectDir) => {
95
+ const iosDir = path.join(buildDir, 'ios'), exportDir = path.join(iosDir, 'build', 'export');
96
+ ensureDir(exportDir);
97
+ const archivePath = path.join(iosDir, 'build', SCHEME + '.xcarchive'), method = release ? 'ad-hoc' : 'development',
98
+ signing = (config.ios && config.ios.signing) || {}, teamId = signing.teamId || '',
99
+ // provisioningProfile 路径 → 取 basename(去扩展名),写进 exportOptions.plist
100
+ profilePath = signing.provisioningProfile ? path.resolve(userProjectDir, signing.provisioningProfile) : '',
101
+ profileName = profilePath ? path.basename(profilePath).replace(/\.mobileprovision$/i, '') : '',
102
+ plistPath = path.join(iosDir, 'build', 'exportOptions.plist'),
103
+ plist = [
104
+ '<?xml version="1.0" encoding="UTF-8"?>',
105
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
106
+ '<plist version="1.0">',
107
+ '<dict>',
108
+ ' <key>method</key>',
109
+ ' <string>' + method + '</string>',
110
+ ' <key>stripSwiftSymbols</key>',
111
+ ' <true/>',
112
+ ' <key>compileBitcode</key>',
113
+ ' <false/>',
114
+ teamId ? ' <key>teamID</key>' : '',
115
+ teamId ? ' <string>' + teamId + '</string>' : '',
116
+ profileName ? ' <key>provisioningProfiles</key>' : '',
117
+ profileName ? ' <dict>' : '',
118
+ profileName ? ' <key>' + config.appId + '</key>' : '',
119
+ profileName ? ' <string>' + profileName + '</string>' : '',
120
+ profileName ? ' </dict>' : '',
121
+ '</dict>',
122
+ '</plist>',
123
+ ].filter(Boolean).join('\n');
124
+ fs.writeFileSync(plistPath, plist, 'utf-8'); // 生成 exportOptions.plist
125
+
126
+ console.log(' xcodebuild -exportArchive ...');
127
+ run('xcodebuild', [
128
+ '-exportArchive',
129
+ '-archivePath', archivePath,
130
+ '-exportOptionsPlist', plistPath,
131
+ '-exportPath', exportDir,
132
+ ], { cwd: iosDir, quiet: true });
133
+
134
+ // 找 .ipa
135
+ const files = fs.readdirSync(exportDir).filter(f => f.endsWith('.ipa'));
136
+ if (files.length === 0) throw new Error('导出目录里没有 .ipa: ' + exportDir);
137
+ return path.join(exportDir, files[0]);
138
+ };
139
+
142
140
  export { ensureMac, checkIosTools, podInstall, xcodebuildArchive, xcodebuildExport }