mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
signed script
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
"start": "expo start",
|
||||
"android": "expo run:android",
|
||||
"android:release": "expo run:android --variant release",
|
||||
"build:signed:aab": "tsx scripts/signed-build.ts",
|
||||
"build:signed:apk": "tsx scripts/signed-build.ts --format apk",
|
||||
"ios": "expo run:ios",
|
||||
"web": "expo start --web",
|
||||
"prebuild": "expo prebuild",
|
||||
@@ -59,6 +61,7 @@
|
||||
"devDependencies": {
|
||||
"@epic-web/config": "^1.21.3",
|
||||
"@types/react": "~19.1.12",
|
||||
"cmd-ts": "^0.14.1",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-config-expo": "~10.0.0",
|
||||
"jiti": "^2.5.1",
|
||||
|
||||
43
apps/mobile/scripts/script-lib.ts
Normal file
43
apps/mobile/scripts/script-lib.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import * as child from 'child_process';
|
||||
import * as path from 'path';
|
||||
|
||||
export const cmd = (
|
||||
command: string,
|
||||
options: { relativeCwd?: string; stdio?: child.StdioOptions } = {},
|
||||
) =>
|
||||
new Promise<{ exitCode: number; stdout: string; stderr: string }>(
|
||||
(resolve, reject) => {
|
||||
console.log(`cmd: ${command}`);
|
||||
const proc = child.spawn(command, {
|
||||
shell: true,
|
||||
stdio: options.stdio ?? 'inherit',
|
||||
cwd: options.relativeCwd
|
||||
? path.resolve(process.cwd(), options.relativeCwd)
|
||||
: process.cwd(),
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
proc.stdout?.on('data', (data) => {
|
||||
stdout += data;
|
||||
});
|
||||
proc.stderr?.on('data', (data) => {
|
||||
stderr += data;
|
||||
});
|
||||
|
||||
process.once('SIGTERM', () => {
|
||||
proc.kill('SIGTERM');
|
||||
});
|
||||
process.once('SIGINT', () => {
|
||||
proc.kill('SIGINT');
|
||||
});
|
||||
proc.on('close', (code) => {
|
||||
console.log(`cmd: ${command} closed with code ${code}`);
|
||||
resolve({ exitCode: code ?? 0, stdout, stderr });
|
||||
});
|
||||
proc.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
},
|
||||
);
|
||||
157
apps/mobile/scripts/signed-build.ts
Normal file
157
apps/mobile/scripts/signed-build.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* https://docs.expo.dev/guides/local-app-production/
|
||||
*/
|
||||
import * as fsp from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { command, run, option, oneOf } from 'cmd-ts';
|
||||
import { z } from 'zod';
|
||||
import { cmd } from './script-lib';
|
||||
|
||||
async function getSecrets(): Promise<{
|
||||
keystoreBase64: string;
|
||||
keystoreAlias: string;
|
||||
keystorePassword: string;
|
||||
}> {
|
||||
const { stdout: rawBwItemString } = await cmd(
|
||||
`bw get item "fressh keystore" --raw`,
|
||||
{
|
||||
stdio: 'pipe',
|
||||
},
|
||||
);
|
||||
const rawBwItem = JSON.parse(rawBwItemString);
|
||||
const bwItemSchema = z.looseObject({
|
||||
login: z.looseObject({
|
||||
username: z.string(),
|
||||
password: z.string(),
|
||||
}),
|
||||
fields: z.array(
|
||||
z.looseObject({
|
||||
name: z.string(),
|
||||
value: z.string(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
const bwItem = bwItemSchema.parse(rawBwItem, {
|
||||
reportInput: true,
|
||||
});
|
||||
const keystoreBase64 = bwItem.fields.find(
|
||||
(field) => field.name === 'keystore',
|
||||
)?.value;
|
||||
if (!keystoreBase64) throw new Error('Keystore not found');
|
||||
return {
|
||||
keystoreBase64,
|
||||
keystoreAlias: bwItem.login.username,
|
||||
keystorePassword: bwItem.login.password,
|
||||
};
|
||||
}
|
||||
|
||||
const signedBuildCommand = command({
|
||||
name: 'signed-build',
|
||||
description: 'Build a signed release build of the app',
|
||||
args: {
|
||||
format: option({
|
||||
long: 'format',
|
||||
type: oneOf(['aab', 'apk']),
|
||||
short: 'f',
|
||||
description: 'The format of the build to build',
|
||||
defaultValue: () => 'aab',
|
||||
}),
|
||||
},
|
||||
handler: async ({ format }) => {
|
||||
{
|
||||
const secrets = await getSecrets();
|
||||
await cmd(`pnpm run prebuild:clean`);
|
||||
|
||||
// Ensure keystore is in the right place
|
||||
// https://docs.expo.dev/guides/local-app-production/#create-an-upload-key
|
||||
// Generated with:
|
||||
// sudo keytool -genkey -v -keystore fressh-upload-key.keystore -alias fressh-key-alias -keyalg RSA -keysize 2048 -validity 10000
|
||||
const keystorePath = `./android/app/fressh-upload-key.keystore`;
|
||||
const keystoreFileName = path.basename(keystorePath);
|
||||
// const bufferShouldEqual = await fsp.readFile(keystoreFileName, 'base64');
|
||||
// await fsp.writeFile(
|
||||
// './debug.log',
|
||||
// JSON.stringify(
|
||||
// {
|
||||
// ...secrets,
|
||||
// bufferShouldEqual,
|
||||
// },
|
||||
// null,
|
||||
// 2,
|
||||
// ),
|
||||
// );
|
||||
await fsp.writeFile(
|
||||
keystorePath,
|
||||
Buffer.from(secrets.keystoreBase64, 'base64'),
|
||||
'base64',
|
||||
);
|
||||
console.log(`Keystore written to ${keystorePath}`);
|
||||
|
||||
// Ensure gradle.properties is configured
|
||||
// https://docs.expo.dev/guides/local-app-production/#update-gradle-variables
|
||||
const gradlePropertiesSuffix = `
|
||||
FRESSH_UPLOAD_STORE_FILE=${keystoreFileName}
|
||||
FRESSH_UPLOAD_KEY_ALIAS=${secrets.keystoreAlias}
|
||||
FRESSH_UPLOAD_STORE_PASSWORD=${secrets.keystorePassword}
|
||||
FRESSH_UPLOAD_KEY_PASSWORD=${secrets.keystorePassword}
|
||||
`;
|
||||
const currentGradleProperties = await fsp.readFile(
|
||||
'./android/gradle.properties',
|
||||
'utf8',
|
||||
);
|
||||
|
||||
if (!currentGradleProperties.includes(gradlePropertiesSuffix.trim())) {
|
||||
await fsp.writeFile(
|
||||
'./android/gradle.properties',
|
||||
`${currentGradleProperties}\n\n${gradlePropertiesSuffix}`,
|
||||
);
|
||||
console.log(`Gradle properties written to ./android/gradle.properties`);
|
||||
}
|
||||
|
||||
// Ensure there is a release signing config in android/app/build.gradle
|
||||
// https://docs.expo.dev/guides/local-app-production/#add-signing-config-to-buildgradle
|
||||
const releaseSigningConfig = `
|
||||
release {
|
||||
if (project.hasProperty('FRESSH_UPLOAD_STORE_FILE')) {
|
||||
storeFile file(FRESSH_UPLOAD_STORE_FILE)
|
||||
storePassword FRESSH_UPLOAD_STORE_PASSWORD
|
||||
keyAlias FRESSH_UPLOAD_KEY_ALIAS
|
||||
keyPassword FRESSH_UPLOAD_KEY_PASSWORD
|
||||
}
|
||||
}`;
|
||||
const currentBuildGradle = await fsp.readFile(
|
||||
'./android/app/build.gradle',
|
||||
'utf8',
|
||||
);
|
||||
if (!currentBuildGradle.includes(releaseSigningConfig.trim())) {
|
||||
const newBuildGradle = currentBuildGradle
|
||||
.replace(
|
||||
/signingConfigs \{([\s\S]*?)\}/, // Modify existing signingConfigs without removing debug
|
||||
(match) => {
|
||||
if (/release \{/.test(match)) {
|
||||
return match.replace(
|
||||
/release \{([\s\S]*?)\}/,
|
||||
releaseSigningConfig,
|
||||
);
|
||||
}
|
||||
return match.trim() + releaseSigningConfig;
|
||||
},
|
||||
)
|
||||
.replace(
|
||||
/buildTypes \{([\s\S]*?)release \{([\s\S]*?)signingConfig signingConfigs\.debug/, // Ensure release config uses signingConfigs.release
|
||||
`buildTypes { $1release { $2signingConfig signingConfigs.release`,
|
||||
);
|
||||
await fsp.writeFile('./android/app/build.gradle', newBuildGradle);
|
||||
console.log(`Build gradle written to ./android/app/build.gradle`);
|
||||
}
|
||||
|
||||
const bundleCommand =
|
||||
format === 'aab' ? 'bundleRelease' : 'assembleRelease';
|
||||
await cmd(`./gradlew app:${bundleCommand}`, {
|
||||
relativeCwd: './android',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
void run(signedBuildCommand, process.argv.slice(2));
|
||||
Reference in New Issue
Block a user