local-expo-build 0.2.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/CHANGELOG.md +99 -0
- package/LICENSE +21 -0
- package/README.md +372 -0
- package/bin/local-expo-build.js +2 -0
- package/dist/cli.js +33 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/build.js +121 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/doctor.js +606 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +125 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/keystore.js +47 -0
- package/dist/commands/keystore.js.map +1 -0
- package/dist/core/bumpVersion.js +70 -0
- package/dist/core/bumpVersion.js.map +1 -0
- package/dist/core/easLink.js +64 -0
- package/dist/core/easLink.js.map +1 -0
- package/dist/core/expoConfig.js +71 -0
- package/dist/core/expoConfig.js.map +1 -0
- package/dist/core/gradleRun.js +31 -0
- package/dist/core/gradleRun.js.map +1 -0
- package/dist/core/keystore/easFetch.js +109 -0
- package/dist/core/keystore/easFetch.js.map +1 -0
- package/dist/core/keystore/existing.js +135 -0
- package/dist/core/keystore/existing.js.map +1 -0
- package/dist/core/keystore/generate.js +72 -0
- package/dist/core/keystore/generate.js.map +1 -0
- package/dist/core/keystore/index.js +62 -0
- package/dist/core/keystore/index.js.map +1 -0
- package/dist/core/keystore/rehydrate.js +88 -0
- package/dist/core/keystore/rehydrate.js.map +1 -0
- package/dist/core/pinGradle.js +50 -0
- package/dist/core/pinGradle.js.map +1 -0
- package/dist/core/prebuild.js +16 -0
- package/dist/core/prebuild.js.map +1 -0
- package/dist/core/sdkDetect.js +26 -0
- package/dist/core/sdkDetect.js.map +1 -0
- package/dist/core/setupSigning.js +168 -0
- package/dist/core/setupSigning.js.map +1 -0
- package/dist/core/syncEasVersion.js +97 -0
- package/dist/core/syncEasVersion.js.map +1 -0
- package/dist/core/writeCredentialsJson.js +51 -0
- package/dist/core/writeCredentialsJson.js.map +1 -0
- package/dist/util/ctx.js +17 -0
- package/dist/util/ctx.js.map +1 -0
- package/dist/util/gitignore.js +23 -0
- package/dist/util/gitignore.js.map +1 -0
- package/dist/util/log.js +16 -0
- package/dist/util/log.js.map +1 -0
- package/package.json +64 -0
- package/templates/keystore.properties.example +4 -0
- package/templates/scripts/build.js +79 -0
- package/templates/scripts/bump-version.js +65 -0
- package/templates/scripts/pin-gradle.js +45 -0
- package/templates/scripts/print-artifact.js +36 -0
- package/templates/scripts/setup-signing.js +137 -0
- package/templates/scripts/sync-eas-version.js +59 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readKeystoreProps = readKeystoreProps;
|
|
7
|
+
exports.writeKeystoreProps = writeKeystoreProps;
|
|
8
|
+
exports.setupSigning = setupSigning;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const log_1 = require("../util/log");
|
|
12
|
+
/**
|
|
13
|
+
* `expo prebuild` (especially with --clean or after accepting the "android
|
|
14
|
+
* project is malformed, clear and reinitialize?" prompt) wipes the entire
|
|
15
|
+
* android/ directory, including any .jks we previously copied into
|
|
16
|
+
* android/app/. Before injecting the release signingConfig, we make sure the
|
|
17
|
+
* keystore actually exists at android/app/<storeFile> by copying it back from
|
|
18
|
+
* a stable source location outside android/.
|
|
19
|
+
*
|
|
20
|
+
* Recovery priority:
|
|
21
|
+
* 1. credentials.json's keystorePath (most authoritative)
|
|
22
|
+
* 2. <cwd>/credentials/android/<storeFile> (EAS download default)
|
|
23
|
+
* 3. <cwd>/credentials/android/keystore.jks (EAS download fallback name)
|
|
24
|
+
* 4. <cwd>/<storeFile> (project-root convention)
|
|
25
|
+
*/
|
|
26
|
+
function ensureKeystoreInAndroidApp(cwd, storeFile) {
|
|
27
|
+
const destDir = path_1.default.join(cwd, 'android', 'app');
|
|
28
|
+
const dest = path_1.default.join(destDir, storeFile);
|
|
29
|
+
if (isNonEmptyFile(dest))
|
|
30
|
+
return;
|
|
31
|
+
const candidates = [];
|
|
32
|
+
const credPath = path_1.default.join(cwd, 'credentials.json');
|
|
33
|
+
if (fs_1.default.existsSync(credPath)) {
|
|
34
|
+
try {
|
|
35
|
+
const cred = JSON.parse(fs_1.default.readFileSync(credPath, 'utf8'));
|
|
36
|
+
const ksPath = cred?.android?.keystore?.keystorePath;
|
|
37
|
+
if (typeof ksPath === 'string' && ksPath.trim()) {
|
|
38
|
+
candidates.push(path_1.default.resolve(cwd, ksPath));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// ignore malformed credentials.json — other candidates may still work
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
candidates.push(path_1.default.join(cwd, 'credentials', 'android', storeFile));
|
|
46
|
+
candidates.push(path_1.default.join(cwd, 'credentials', 'android', 'keystore.jks'));
|
|
47
|
+
candidates.push(path_1.default.join(cwd, storeFile));
|
|
48
|
+
const found = candidates.find((p) => {
|
|
49
|
+
const abs = path_1.default.resolve(p);
|
|
50
|
+
return abs !== path_1.default.resolve(dest) && isNonEmptyFile(abs);
|
|
51
|
+
});
|
|
52
|
+
if (!found) {
|
|
53
|
+
const tried = candidates
|
|
54
|
+
.map((p) => ' - ' + path_1.default.relative(cwd, p).replace(/\\/g, '/'))
|
|
55
|
+
.join('\n');
|
|
56
|
+
throw new Error(`Keystore file ${path_1.default.relative(cwd, dest).replace(/\\/g, '/')} not found, ` +
|
|
57
|
+
`and no recovery source available.\nTried:\n${tried}\n\n` +
|
|
58
|
+
`If your android/ directory was just wiped by \`expo prebuild --clean\`, ` +
|
|
59
|
+
`re-run \`npx local-expo-build keystore rehydrate\` (if you have credentials.json) ` +
|
|
60
|
+
`or \`npx local-expo-build keystore import <path-to-jks>\` to restore it.`);
|
|
61
|
+
}
|
|
62
|
+
fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
63
|
+
fs_1.default.copyFileSync(found, dest);
|
|
64
|
+
log_1.log.ok(`Restored keystore: ${path_1.default.relative(cwd, found).replace(/\\/g, '/')} → ` +
|
|
65
|
+
`${path_1.default.relative(cwd, dest).replace(/\\/g, '/')}`);
|
|
66
|
+
}
|
|
67
|
+
function isNonEmptyFile(p) {
|
|
68
|
+
try {
|
|
69
|
+
return fs_1.default.existsSync(p) && fs_1.default.statSync(p).size > 0;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function readKeystoreProps(cwd) {
|
|
76
|
+
const p = path_1.default.join(cwd, 'keystore.properties');
|
|
77
|
+
if (!fs_1.default.existsSync(p))
|
|
78
|
+
return null;
|
|
79
|
+
const props = {};
|
|
80
|
+
fs_1.default.readFileSync(p, 'utf8')
|
|
81
|
+
.split('\n')
|
|
82
|
+
.forEach((line) => {
|
|
83
|
+
const t = line.trim();
|
|
84
|
+
if (!t || t.startsWith('#'))
|
|
85
|
+
return;
|
|
86
|
+
const i = t.indexOf('=');
|
|
87
|
+
if (i === -1)
|
|
88
|
+
return;
|
|
89
|
+
props[t.slice(0, i).trim()] = t.slice(i + 1).trim();
|
|
90
|
+
});
|
|
91
|
+
const missing = ['storeFile', 'storePassword', 'keyAlias', 'keyPassword'].filter((k) => !props[k] || props[k] === 'FILL_IN');
|
|
92
|
+
if (missing.length)
|
|
93
|
+
return null;
|
|
94
|
+
return props;
|
|
95
|
+
}
|
|
96
|
+
function writeKeystoreProps(cwd, props) {
|
|
97
|
+
const lines = [
|
|
98
|
+
`storeFile=${props.storeFile}`,
|
|
99
|
+
`storePassword=${props.storePassword}`,
|
|
100
|
+
`keyAlias=${props.keyAlias}`,
|
|
101
|
+
`keyPassword=${props.keyPassword}`,
|
|
102
|
+
];
|
|
103
|
+
fs_1.default.writeFileSync(path_1.default.join(cwd, 'keystore.properties'), lines.join('\n') + '\n', 'utf8');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Injects the release signingConfig into android/app/build.gradle.
|
|
107
|
+
* Idempotent: if `signingConfigs.release` is already present, it's a no-op.
|
|
108
|
+
*/
|
|
109
|
+
function setupSigning({ cwd }) {
|
|
110
|
+
const props = readKeystoreProps(cwd);
|
|
111
|
+
if (!props) {
|
|
112
|
+
throw new Error(`keystore.properties missing or incomplete at ${cwd}. ` +
|
|
113
|
+
`Run "local-expo-build keystore create|import|fetch" first.`);
|
|
114
|
+
}
|
|
115
|
+
const gradlePath = path_1.default.join(cwd, 'android', 'app', 'build.gradle');
|
|
116
|
+
if (!fs_1.default.existsSync(gradlePath))
|
|
117
|
+
throw new Error(`${gradlePath} not found — run prebuild first.`);
|
|
118
|
+
// Survive `expo prebuild --clean` wiping android/: restore .jks from a stable source.
|
|
119
|
+
ensureKeystoreInAndroidApp(cwd, props.storeFile);
|
|
120
|
+
let gradle = fs_1.default.readFileSync(gradlePath, 'utf8');
|
|
121
|
+
if (gradle.includes('signingConfigs.release')) {
|
|
122
|
+
log_1.log.ok('Release signing config already present — skipping.');
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const DEFAULT_SIGNING_BLOCK = ` signingConfigs {
|
|
126
|
+
debug {
|
|
127
|
+
storeFile file('debug.keystore')
|
|
128
|
+
storePassword 'android'
|
|
129
|
+
keyAlias 'androiddebugkey'
|
|
130
|
+
keyPassword 'android'
|
|
131
|
+
}
|
|
132
|
+
}`;
|
|
133
|
+
const NEW_SIGNING_BLOCK = ` signingConfigs {
|
|
134
|
+
debug {
|
|
135
|
+
storeFile file('debug.keystore')
|
|
136
|
+
storePassword 'android'
|
|
137
|
+
keyAlias 'androiddebugkey'
|
|
138
|
+
keyPassword 'android'
|
|
139
|
+
}
|
|
140
|
+
release {
|
|
141
|
+
storeFile file('${props.storeFile}')
|
|
142
|
+
storePassword '${props.storePassword}'
|
|
143
|
+
keyAlias '${props.keyAlias}'
|
|
144
|
+
keyPassword '${props.keyPassword}'
|
|
145
|
+
}
|
|
146
|
+
}`;
|
|
147
|
+
if (!gradle.includes(DEFAULT_SIGNING_BLOCK)) {
|
|
148
|
+
throw new Error('Could not find the default signingConfigs block in build.gradle.\n' +
|
|
149
|
+
'The file may have been manually edited; add the release signing config by hand.');
|
|
150
|
+
}
|
|
151
|
+
gradle = gradle.replace(DEFAULT_SIGNING_BLOCK, NEW_SIGNING_BLOCK);
|
|
152
|
+
const TOKEN = 'signingConfig signingConfigs.debug';
|
|
153
|
+
const parts = gradle.split(TOKEN);
|
|
154
|
+
if (parts.length >= 3) {
|
|
155
|
+
gradle =
|
|
156
|
+
parts[0] +
|
|
157
|
+
TOKEN +
|
|
158
|
+
parts[1] +
|
|
159
|
+
'signingConfig signingConfigs.release' +
|
|
160
|
+
parts.slice(2).join(TOKEN);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
log_1.log.warn('Could not locate release buildType signing line — check build.gradle manually.');
|
|
164
|
+
}
|
|
165
|
+
fs_1.default.writeFileSync(gradlePath, gradle, 'utf8');
|
|
166
|
+
log_1.log.ok(`Release signing config injected (alias=${props.keyAlias})`);
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=setupSigning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupSigning.js","sourceRoot":"","sources":["../../src/core/setupSigning.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.syncEasVersion = syncEasVersion;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const https_1 = __importDefault(require("https"));
|
|
11
|
+
const log_1 = require("../util/log");
|
|
12
|
+
const EAS_API = 'api.expo.dev';
|
|
13
|
+
function getSessionSecret() {
|
|
14
|
+
if (process.env.EXPO_TOKEN)
|
|
15
|
+
return { token: process.env.EXPO_TOKEN };
|
|
16
|
+
const statePath = path_1.default.join(os_1.default.homedir(), '.expo', 'state.json');
|
|
17
|
+
if (!fs_1.default.existsSync(statePath)) {
|
|
18
|
+
throw new Error('No EXPO_TOKEN env var and no ~/.expo/state.json found. Run `eas login` first.');
|
|
19
|
+
}
|
|
20
|
+
const state = JSON.parse(fs_1.default.readFileSync(statePath, 'utf8'));
|
|
21
|
+
const secret = state?.auth?.sessionSecret;
|
|
22
|
+
if (!secret)
|
|
23
|
+
throw new Error('No sessionSecret in ~/.expo/state.json. Run `eas login` first.');
|
|
24
|
+
return { sessionSecret: secret };
|
|
25
|
+
}
|
|
26
|
+
async function syncEasVersion({ cwd }) {
|
|
27
|
+
const gradlePath = path_1.default.join(cwd, 'android', 'app', 'build.gradle');
|
|
28
|
+
const appJsonPath = path_1.default.join(cwd, 'app.json');
|
|
29
|
+
if (!fs_1.default.existsSync(gradlePath))
|
|
30
|
+
throw new Error(`${gradlePath} not found`);
|
|
31
|
+
if (!fs_1.default.existsSync(appJsonPath))
|
|
32
|
+
throw new Error(`${appJsonPath} not found`);
|
|
33
|
+
const gradle = fs_1.default.readFileSync(gradlePath, 'utf8');
|
|
34
|
+
const m = gradle.match(/\bversionCode\s+(\d+)/);
|
|
35
|
+
if (!m)
|
|
36
|
+
throw new Error('Could not find versionCode in build.gradle');
|
|
37
|
+
const versionCode = m[1];
|
|
38
|
+
const appJson = JSON.parse(fs_1.default.readFileSync(appJsonPath, 'utf8'));
|
|
39
|
+
const projectId = appJson.expo?.extra?.eas?.projectId;
|
|
40
|
+
const applicationId = appJson.expo?.android?.package;
|
|
41
|
+
const storeVersion = appJson.expo?.version ?? '1.0.0';
|
|
42
|
+
if (!projectId) {
|
|
43
|
+
log_1.log.warn('No expo.extra.eas.projectId in app.json — skipping EAS version sync.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!applicationId)
|
|
47
|
+
throw new Error('Missing expo.android.package in app.json');
|
|
48
|
+
const auth = getSessionSecret();
|
|
49
|
+
const mutation = `
|
|
50
|
+
mutation CreateAppVersionMutation($appVersionInput: AppVersionInput!) {
|
|
51
|
+
appVersion {
|
|
52
|
+
createAppVersion(appVersionInput: $appVersionInput) { id }
|
|
53
|
+
}
|
|
54
|
+
}`;
|
|
55
|
+
const variables = {
|
|
56
|
+
appVersionInput: {
|
|
57
|
+
appId: projectId,
|
|
58
|
+
platform: 'ANDROID',
|
|
59
|
+
applicationIdentifier: applicationId,
|
|
60
|
+
storeVersion,
|
|
61
|
+
buildVersion: String(versionCode),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const body = JSON.stringify({ query: mutation, variables });
|
|
65
|
+
const headers = {
|
|
66
|
+
'Content-Type': 'application/json',
|
|
67
|
+
'Content-Length': String(Buffer.byteLength(body)),
|
|
68
|
+
'expo-client-info': JSON.stringify({ appVersion: '0.0.0', sdkVersion: '0.0.0' }),
|
|
69
|
+
};
|
|
70
|
+
if (auth.sessionSecret)
|
|
71
|
+
headers['expo-session'] = auth.sessionSecret;
|
|
72
|
+
if (auth.token)
|
|
73
|
+
headers['authorization'] = `Bearer ${auth.token}`;
|
|
74
|
+
log_1.log.info(`Syncing EAS versionCode → ${versionCode} (appId: ${projectId})`);
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
const req = https_1.default.request({ hostname: EAS_API, path: '/graphql', method: 'POST', headers }, (res) => {
|
|
77
|
+
let data = '';
|
|
78
|
+
res.on('data', (c) => (data += c));
|
|
79
|
+
res.on('end', () => {
|
|
80
|
+
try {
|
|
81
|
+
const json = JSON.parse(data);
|
|
82
|
+
if (json.errors)
|
|
83
|
+
return reject(new Error(`EAS API: ${JSON.stringify(json.errors)}`));
|
|
84
|
+
log_1.log.ok(`EAS remote versionCode set to ${versionCode}`);
|
|
85
|
+
resolve();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
reject(new Error(`Failed to parse EAS response: ${data}`));
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
req.on('error', reject);
|
|
93
|
+
req.write(body);
|
|
94
|
+
req.end();
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=syncEasVersion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncEasVersion.js","sourceRoot":"","sources":["../../src/core/syncEasVersion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeCredentialsJson = writeCredentialsJson;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const log_1 = require("../util/log");
|
|
10
|
+
/**
|
|
11
|
+
* Scaffolds (or updates) credentials.json at the project root from the values
|
|
12
|
+
* in keystore.properties. Used by EAS submit / cloud builds when
|
|
13
|
+
* credentialsSource is "local" — see
|
|
14
|
+
* https://docs.expo.dev/app-signing/local-credentials/
|
|
15
|
+
*
|
|
16
|
+
* - Path points at `android/app/<storeFile>` to stay in lock-step with
|
|
17
|
+
* keystore.properties (the Gradle release signingConfig reads the same file).
|
|
18
|
+
* - Merges into an existing credentials.json so any `ios` block is preserved.
|
|
19
|
+
* - Overwrites a malformed credentials.json (we can't safely merge garbage).
|
|
20
|
+
* - Idempotent: writing the same values twice is a no-op on disk content.
|
|
21
|
+
*
|
|
22
|
+
* SECURITY: credentials.json contains plaintext keystore passwords. The
|
|
23
|
+
* keystore setup flow adds it to .gitignore; do NOT commit it.
|
|
24
|
+
*/
|
|
25
|
+
function writeCredentialsJson(cwd, props) {
|
|
26
|
+
const credPath = path_1.default.join(cwd, 'credentials.json');
|
|
27
|
+
const keystorePath = `android/app/${props.storeFile}`.replace(/\\/g, '/');
|
|
28
|
+
let existing = {};
|
|
29
|
+
if (fs_1.default.existsSync(credPath)) {
|
|
30
|
+
try {
|
|
31
|
+
existing = JSON.parse(fs_1.default.readFileSync(credPath, 'utf8'));
|
|
32
|
+
if (existing === null || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
33
|
+
existing = {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
log_1.log.warn('credentials.json was malformed — overwriting.');
|
|
38
|
+
existing = {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
existing.android = existing.android || {};
|
|
42
|
+
existing.android.keystore = {
|
|
43
|
+
keystorePath,
|
|
44
|
+
keystorePassword: props.storePassword,
|
|
45
|
+
keyAlias: props.keyAlias,
|
|
46
|
+
keyPassword: props.keyPassword,
|
|
47
|
+
};
|
|
48
|
+
fs_1.default.writeFileSync(credPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
|
|
49
|
+
log_1.log.ok(`Wrote credentials.json (keystorePath=${keystorePath})`);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=writeCredentialsJson.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writeCredentialsJson.js","sourceRoot":"","sources":["../../src/core/writeCredentialsJson.ts"],"names":[],"mappings":";;;;;AAoBA,oDA2BC;AA/CD,4CAAoB;AACpB,gDAAwB;AAExB,qCAAkC;AAElC;;;;;;;;;;;;;;GAcG;AACH,SAAgB,oBAAoB,CAAC,GAAW,EAAE,KAAoB;IACpE,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;IACpD,MAAM,YAAY,GAAG,eAAe,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAE1E,IAAI,QAAQ,GAAQ,EAAE,CAAC;IACvB,IAAI,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;YACzD,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjF,QAAQ,GAAG,EAAE,CAAC;YAChB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,SAAG,CAAC,IAAI,CAAC,+CAA+C,CAAC,CAAC;YAC1D,QAAQ,GAAG,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,OAAO,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,QAAQ,GAAG;QAC1B,YAAY;QACZ,gBAAgB,EAAE,KAAK,CAAC,aAAa;QACrC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,WAAW,EAAE,KAAK,CAAC,WAAW;KAC/B,CAAC;IAEF,YAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7E,SAAG,CAAC,EAAE,CAAC,wCAAwC,YAAY,GAAG,CAAC,CAAC;AAClE,CAAC"}
|
package/dist/util/ctx.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getCtx = getCtx;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
function getCtx(cmd) {
|
|
9
|
+
const opts = cmd.optsWithGlobals();
|
|
10
|
+
const cwd = path_1.default.resolve(opts.cwd || process.cwd());
|
|
11
|
+
return {
|
|
12
|
+
cwd,
|
|
13
|
+
verbose: Boolean(opts.verbose),
|
|
14
|
+
dryRun: Boolean(opts.dryRun),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=ctx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ctx.js","sourceRoot":"","sources":["../../src/util/ctx.ts"],"names":[],"mappings":";;;;;AASA,wBAQC;AAjBD,gDAAwB;AASxB,SAAgB,MAAM,CAAC,GAAY;IACjC,MAAM,IAAI,GAAG,GAAG,CAAC,eAAe,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpD,OAAO;QACL,GAAG;QACH,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QAC9B,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC;KAC7B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureGitignoreEntries = ensureGitignoreEntries;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const log_1 = require("./log");
|
|
10
|
+
function ensureGitignoreEntries(cwd, entries) {
|
|
11
|
+
const p = path_1.default.join(cwd, '.gitignore');
|
|
12
|
+
let content = fs_1.default.existsSync(p) ? fs_1.default.readFileSync(p, 'utf8') : '';
|
|
13
|
+
const lines = new Set(content.split(/\r?\n/).map((l) => l.trim()));
|
|
14
|
+
const toAdd = entries.filter((e) => !lines.has(e));
|
|
15
|
+
if (!toAdd.length)
|
|
16
|
+
return;
|
|
17
|
+
if (content.length && !content.endsWith('\n'))
|
|
18
|
+
content += '\n';
|
|
19
|
+
content += '\n# local-expo-build\n' + toAdd.join('\n') + '\n';
|
|
20
|
+
fs_1.default.writeFileSync(p, content, 'utf8');
|
|
21
|
+
log_1.log.ok(`.gitignore: added ${toAdd.join(', ')}`);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=gitignore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitignore.js","sourceRoot":"","sources":["../../src/util/gitignore.ts"],"names":[],"mappings":";;;;;AAIA,wDAUC;AAdD,4CAAoB;AACpB,gDAAwB;AACxB,+BAA4B;AAE5B,SAAgB,sBAAsB,CAAC,GAAW,EAAE,OAAiB;IACnE,MAAM,CAAC,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACvC,IAAI,OAAO,GAAG,YAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjE,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO;IAC1B,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,IAAI,CAAC;IAC/D,OAAO,IAAI,wBAAwB,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC9D,YAAE,CAAC,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACrC,SAAG,CAAC,EAAE,CAAC,qBAAqB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAClD,CAAC"}
|
package/dist/util/log.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.log = void 0;
|
|
7
|
+
const kleur_1 = __importDefault(require("kleur"));
|
|
8
|
+
exports.log = {
|
|
9
|
+
info: (msg) => console.log(kleur_1.default.cyan('› ') + msg),
|
|
10
|
+
step: (msg) => console.log('\n' + kleur_1.default.bold().cyan('▸ ') + kleur_1.default.bold(msg)),
|
|
11
|
+
ok: (msg) => console.log(kleur_1.default.green('✓ ') + msg),
|
|
12
|
+
warn: (msg) => console.warn(kleur_1.default.yellow('! ') + msg),
|
|
13
|
+
error: (msg) => console.error(kleur_1.default.red('✗ ') + msg),
|
|
14
|
+
dim: (msg) => console.log(kleur_1.default.gray(msg)),
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/util/log.ts"],"names":[],"mappings":";;;;;;AAAA,kDAA0B;AAEb,QAAA,GAAG,GAAG;IACjB,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IAC1D,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,eAAK,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpF,EAAE,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IACzD,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,eAAK,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,eAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC;IAC5D,GAAG,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;CACnD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "local-expo-build",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "One-stop CLI for local Expo Android APK / AAB builds. Bypasses EAS cloud, handles signing, scaffolds credentials.json + keystore.properties, and keeps EAS versionCode in sync.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"expo",
|
|
7
|
+
"android",
|
|
8
|
+
"build",
|
|
9
|
+
"aab",
|
|
10
|
+
"apk",
|
|
11
|
+
"eas",
|
|
12
|
+
"gradle",
|
|
13
|
+
"keystore",
|
|
14
|
+
"signing",
|
|
15
|
+
"react-native",
|
|
16
|
+
"cli"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "Nikhil Dhawan",
|
|
21
|
+
"email": "nikhild64@gmail.com"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/nikhild64/local-expo-build#readme",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/nikhild64/local-expo-build.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/nikhild64/local-expo-build/issues"
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"local-expo-build": "bin/local-expo-build.js"
|
|
33
|
+
},
|
|
34
|
+
"main": "dist/cli.js",
|
|
35
|
+
"files": [
|
|
36
|
+
"bin",
|
|
37
|
+
"dist",
|
|
38
|
+
"templates",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"CHANGELOG.md"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -p tsconfig.json",
|
|
45
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
46
|
+
"prepublishOnly": "npm run build",
|
|
47
|
+
"start": "node bin/local-expo-build.js"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@inquirer/prompts": "^7.2.1",
|
|
54
|
+
"commander": "^12.1.0",
|
|
55
|
+
"execa": "^9.5.2",
|
|
56
|
+
"kleur": "^4.1.5",
|
|
57
|
+
"semver": "^7.6.3"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^20.17.10",
|
|
61
|
+
"@types/semver": "^7.5.8",
|
|
62
|
+
"typescript": "^5.7.2"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Orchestrates the full local Expo Android build pipeline.
|
|
4
|
+
* Replaces the long `&&` chain in package.json with a single Node entry point.
|
|
5
|
+
* Generated by local-expo-build.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node scripts/build.js (apk|aab) [--dry-run]
|
|
8
|
+
* --dry-run prints each step without executing it (great for screenshots / sanity checks).
|
|
9
|
+
*/
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const target = (args.find((a) => !a.startsWith('--')) || 'aab').toLowerCase();
|
|
15
|
+
const dryRun = args.includes('--dry-run');
|
|
16
|
+
|
|
17
|
+
if (target !== 'apk' && target !== 'aab') {
|
|
18
|
+
console.error('Usage: node scripts/build.js (apk|aab) [--dry-run]');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
23
|
+
const isWin = process.platform === 'win32';
|
|
24
|
+
const gradlew = isWin ? 'gradlew.bat' : './gradlew';
|
|
25
|
+
const gradleTask = target === 'aab' ? 'bundleRelease' : 'assembleRelease';
|
|
26
|
+
|
|
27
|
+
const steps = [
|
|
28
|
+
{ name: 'expo prebuild', cmd: 'npx expo prebuild --platform android', cwd: ROOT },
|
|
29
|
+
{ name: 'pin Gradle wrapper', cmd: 'node scripts/pin-gradle.js', cwd: ROOT },
|
|
30
|
+
{ name: 'bump version', cmd: 'node scripts/bump-version.js', cwd: ROOT },
|
|
31
|
+
{ name: 'setup signing (restores .jks)', cmd: 'node scripts/setup-signing.js', cwd: ROOT },
|
|
32
|
+
{ name: `gradle ${gradleTask}`, cmd: `${gradlew} ${gradleTask}`, cwd: path.join(ROOT, 'android') },
|
|
33
|
+
{ name: 'sync EAS versionCode', cmd: 'node scripts/sync-eas-version.js', cwd: ROOT, allowFail: true },
|
|
34
|
+
{ name: 'print artifact', cmd: `node scripts/print-artifact.js ${target}`, cwd: ROOT },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const dim = (s) => '\x1b[2m' + s + '\x1b[0m';
|
|
38
|
+
const bold = (s) => '\x1b[1m' + s + '\x1b[0m';
|
|
39
|
+
|
|
40
|
+
console.log('\n' + bold(`local-expo-build · ${target.toUpperCase()} · ${steps.length} steps${dryRun ? ' · DRY RUN' : ''}`));
|
|
41
|
+
console.log(dim('Local build · saves an EAS cloud build credit · first run is slowest (~5 min)'));
|
|
42
|
+
if (dryRun) {
|
|
43
|
+
console.log(dim('No commands will be executed. Each step prints what it would run.\n'));
|
|
44
|
+
} else {
|
|
45
|
+
console.log('');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
for (let i = 0; i < steps.length; i++) {
|
|
50
|
+
const s = steps[i];
|
|
51
|
+
console.log('\n' + bold(`▸ [${i + 1}/${steps.length}] ${s.name}`));
|
|
52
|
+
if (dryRun) {
|
|
53
|
+
const relCwd = path.relative(ROOT, s.cwd) || '.';
|
|
54
|
+
console.log(dim(` [dry-run] cwd=${relCwd}`));
|
|
55
|
+
console.log(dim(` [dry-run] ${s.cmd}`));
|
|
56
|
+
if (s.allowFail) console.log(dim(' [dry-run] (non-fatal on failure)'));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
// execSync always uses a shell; omit the option so Node picks the platform default
|
|
61
|
+
// (cmd.exe on Windows, /bin/sh on Unix/macOS).
|
|
62
|
+
execSync(s.cmd, { cwd: s.cwd, stdio: 'inherit' });
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (s.allowFail) {
|
|
65
|
+
console.warn(dim(`! ${s.name} failed (non-fatal): ${err.message || err}`));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
console.error('\n\x1b[31m✗\x1b[0m Build failed at step ' + (i + 1) + ': ' + s.name);
|
|
69
|
+
process.exit(typeof err.status === 'number' ? err.status : 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (dryRun) {
|
|
74
|
+
console.log(bold(`\nDRY RUN complete — ${steps.length} steps shown for ${target.toUpperCase()}.`));
|
|
75
|
+
console.log(dim('Re-run without --dry-run to actually build.\n'));
|
|
76
|
+
} else {
|
|
77
|
+
const mins = ((Date.now() - started) / 60000).toFixed(1);
|
|
78
|
+
console.log(dim(`\nTotal: ${mins} min\n`));
|
|
79
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Bumps patch version in app.json + package.json, fetches versionCode from EAS,
|
|
4
|
+
* increments it, and writes both into android/app/build.gradle.
|
|
5
|
+
* Generated by local-expo-build.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node scripts/bump-version.js [profile] (default profile: production)
|
|
8
|
+
*/
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const APP_JSON = path.resolve(__dirname, '../app.json');
|
|
14
|
+
const PKG_JSON = path.resolve(__dirname, '../package.json');
|
|
15
|
+
const GRADLE = path.resolve(__dirname, '../android/app/build.gradle');
|
|
16
|
+
const PROFILE = process.argv[2] || 'production';
|
|
17
|
+
|
|
18
|
+
const appJson = JSON.parse(fs.readFileSync(APP_JSON, 'utf8'));
|
|
19
|
+
const current = appJson.expo.version;
|
|
20
|
+
const parts = current.split('.');
|
|
21
|
+
if (parts.length !== 3) {
|
|
22
|
+
console.error(`Unexpected version: "${current}"`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
parts[2] = String(parseInt(parts[2], 10) + 1);
|
|
26
|
+
const next = parts.join('.');
|
|
27
|
+
appJson.expo.version = next;
|
|
28
|
+
fs.writeFileSync(APP_JSON, JSON.stringify(appJson, null, 2) + '\n');
|
|
29
|
+
console.log(`app.json: ${current} → ${next}`);
|
|
30
|
+
|
|
31
|
+
const pkg = JSON.parse(fs.readFileSync(PKG_JSON, 'utf8'));
|
|
32
|
+
pkg.version = next;
|
|
33
|
+
fs.writeFileSync(PKG_JSON, JSON.stringify(pkg, null, 2) + '\n');
|
|
34
|
+
|
|
35
|
+
let nextCode = null;
|
|
36
|
+
if (appJson.expo.extra?.eas?.projectId) {
|
|
37
|
+
try {
|
|
38
|
+
const out = execSync(
|
|
39
|
+
`eas build:version:get --platform android --profile ${PROFILE} --non-interactive`,
|
|
40
|
+
{ cwd: path.resolve(__dirname, '..'), encoding: 'utf8' }
|
|
41
|
+
);
|
|
42
|
+
const m = out.match(/Android versionCode\s*[-–]\s*(\d+)/i);
|
|
43
|
+
if (m) {
|
|
44
|
+
nextCode = parseInt(m[1], 10) + 1;
|
|
45
|
+
console.log(`EAS versionCode: ${m[1]} → ${nextCode}`);
|
|
46
|
+
}
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.warn('EAS version fetch failed:', e.message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!fs.existsSync(GRADLE)) {
|
|
53
|
+
console.error('android/app/build.gradle missing — run prebuild first.');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
let gradle = fs.readFileSync(GRADLE, 'utf8');
|
|
57
|
+
if (nextCode == null) {
|
|
58
|
+
const m = gradle.match(/\bversionCode\s+(\d+)/);
|
|
59
|
+
nextCode = m ? parseInt(m[1], 10) + 1 : 1;
|
|
60
|
+
console.log(`Local versionCode bump → ${nextCode}`);
|
|
61
|
+
}
|
|
62
|
+
gradle = gradle.replace(/(\bversionCode\s+)\d+/, `$1${nextCode}`);
|
|
63
|
+
gradle = gradle.replace(/(\bversionName\s+")[^"]*"/, `$1${next}"`);
|
|
64
|
+
fs.writeFileSync(GRADLE, gradle);
|
|
65
|
+
console.log(`build.gradle: versionCode=${nextCode}, versionName="${next}"`);
|