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.
Files changed (58) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/LICENSE +21 -0
  3. package/README.md +372 -0
  4. package/bin/local-expo-build.js +2 -0
  5. package/dist/cli.js +33 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/build.js +121 -0
  8. package/dist/commands/build.js.map +1 -0
  9. package/dist/commands/doctor.js +606 -0
  10. package/dist/commands/doctor.js.map +1 -0
  11. package/dist/commands/init.js +125 -0
  12. package/dist/commands/init.js.map +1 -0
  13. package/dist/commands/keystore.js +47 -0
  14. package/dist/commands/keystore.js.map +1 -0
  15. package/dist/core/bumpVersion.js +70 -0
  16. package/dist/core/bumpVersion.js.map +1 -0
  17. package/dist/core/easLink.js +64 -0
  18. package/dist/core/easLink.js.map +1 -0
  19. package/dist/core/expoConfig.js +71 -0
  20. package/dist/core/expoConfig.js.map +1 -0
  21. package/dist/core/gradleRun.js +31 -0
  22. package/dist/core/gradleRun.js.map +1 -0
  23. package/dist/core/keystore/easFetch.js +109 -0
  24. package/dist/core/keystore/easFetch.js.map +1 -0
  25. package/dist/core/keystore/existing.js +135 -0
  26. package/dist/core/keystore/existing.js.map +1 -0
  27. package/dist/core/keystore/generate.js +72 -0
  28. package/dist/core/keystore/generate.js.map +1 -0
  29. package/dist/core/keystore/index.js +62 -0
  30. package/dist/core/keystore/index.js.map +1 -0
  31. package/dist/core/keystore/rehydrate.js +88 -0
  32. package/dist/core/keystore/rehydrate.js.map +1 -0
  33. package/dist/core/pinGradle.js +50 -0
  34. package/dist/core/pinGradle.js.map +1 -0
  35. package/dist/core/prebuild.js +16 -0
  36. package/dist/core/prebuild.js.map +1 -0
  37. package/dist/core/sdkDetect.js +26 -0
  38. package/dist/core/sdkDetect.js.map +1 -0
  39. package/dist/core/setupSigning.js +168 -0
  40. package/dist/core/setupSigning.js.map +1 -0
  41. package/dist/core/syncEasVersion.js +97 -0
  42. package/dist/core/syncEasVersion.js.map +1 -0
  43. package/dist/core/writeCredentialsJson.js +51 -0
  44. package/dist/core/writeCredentialsJson.js.map +1 -0
  45. package/dist/util/ctx.js +17 -0
  46. package/dist/util/ctx.js.map +1 -0
  47. package/dist/util/gitignore.js +23 -0
  48. package/dist/util/gitignore.js.map +1 -0
  49. package/dist/util/log.js +16 -0
  50. package/dist/util/log.js.map +1 -0
  51. package/package.json +64 -0
  52. package/templates/keystore.properties.example +4 -0
  53. package/templates/scripts/build.js +79 -0
  54. package/templates/scripts/bump-version.js +65 -0
  55. package/templates/scripts/pin-gradle.js +45 -0
  56. package/templates/scripts/print-artifact.js +36 -0
  57. package/templates/scripts/setup-signing.js +137 -0
  58. 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"}
@@ -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"}
@@ -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,4 @@
1
+ storeFile=release.jks
2
+ storePassword=FILL_IN
3
+ keyAlias=release
4
+ keyPassword=FILL_IN
@@ -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}"`);