+55
.github/workflows/bundle-deploy-eas-update.yml
+55
.github/workflows/bundle-deploy-eas-update.yml
···
1
+
---
2
+
name: Bundle and Deploy EAS Update
3
+
4
+
on:
5
+
workflow_dispatch:
6
+
inputs:
7
+
runtimeVersion:
8
+
type: string
9
+
description: Runtime version (in x.x.x format) that this update is for
10
+
required: true
11
+
12
+
jobs:
13
+
bundleDeploy:
14
+
name: Bundle and Deploy EAS Update
15
+
runs-on: ubuntu-latest
16
+
steps:
17
+
- name: 🧐 Validate version
18
+
run: |
19
+
[[ "${{ github.event.inputs.runtimeVersion }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] && echo "Version is valid" || exit 1
20
+
21
+
- name: ⬇️ Checkout
22
+
uses: actions/checkout@v4
23
+
24
+
- name: 🔧 Setup Node
25
+
uses: actions/setup-node@v4
26
+
with:
27
+
node-version-file: .nvmrc
28
+
cache: yarn
29
+
30
+
- name: ⚙️ Install Dependencies
31
+
run: yarn install
32
+
33
+
- name: 🪛 Install jq
34
+
uses: dcarbone/install-jq-action@v2
35
+
36
+
- name: ⛏️ Setup Expo
37
+
run: yarn global add eas-cli-local-build-plugin
38
+
39
+
- name: 🔤 Compile Translations
40
+
run: yarn intl:build
41
+
42
+
- name: ✏️ Write environment variables
43
+
run: |
44
+
export json='${{ secrets.GOOGLE_SERVICES_TOKEN }}'
45
+
echo "${{ secrets.ENV_TOKEN }}" > .env
46
+
echo "$json" > google-services.json
47
+
48
+
- name: 🏗️ Create Bundle
49
+
run: yarn export
50
+
51
+
- name: 📦 Package Bundle and 🚀 Deploy
52
+
run: yarn make-deploy-bundle
53
+
env:
54
+
DENIS_API_KEY: ${{ secrets.DENIS_API_KEY }}
55
+
RUNTIME_VERSION: ${{ github.event.inputs.runtimeVersion }}
+18
code-signing/certificate.pem
+18
code-signing/certificate.pem
···
1
+
-----BEGIN CERTIFICATE-----
2
+
MIIC0TCCAbmgAwIBAgIJcMN2yt5KNDqTMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
3
+
BAMTB0JsdWVza3kwHhcNMjQwMzE0MDA1OTU4WhcNMzQwMzE0MDA1OTU4WjASMRAw
4
+
DgYDVQQDEwdCbHVlc2t5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
5
+
izSAWEc3wRoa3eTBEh/kE9pH0d6jhEGw9GrYfei60MHT1pSq2cTdyUM1yUZchAeW
6
+
gFFtqFxX0pfIZQyMlIZbjkaOxOqzWhB0aCsxngnhbSahFwRxkVwTAuonhqIpaLBL
7
+
hrCCCQ2IfZUpy8QeasqlTlmvmijuCC34fXxJlxNcj8SqzIZi+civ7U5PMPfIMMnD
8
+
tCDIBy1vxMk57m25X2ikcWUFW64qNVLkFAL36xEnmFTL4Ivqpz23gUcUIe1zbesY
9
+
jAgDtlwnAE7mU3oagCUDcSuOveT4POhT35Xp3Y/07I68kmXtrPxwd5k0L0zbisEm
10
+
poKZ87E2X29BitihicMpBwIDAQABoyowKDAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0l
11
+
AQH/BAwwCgYIKwYBBQUHAwMwDQYJKoZIhvcNAQELBQADggEBAED1gdMF0yr8Gy87
12
+
RgyaeVpPySwSsO0selmXXrcmOWgiPA05lubyhFEa4P5kdzBEByG2MT+pJkjGYpvK
13
+
XRnqXM5VvdS2RhYYFH0cFOIUqBKwCnzViCMuGQeoGUx4oPcKFS0PQ1WjW2d4pS75
14
+
51GBfB6LOepsCHUG0A9XEk7EAyUWc4M2ITCJsTtJh8CVn2pTks2q14ETDs86YQv4
15
+
peDaJv8nhIe8oQkeGn2o/P/ctkwJg/uBydQUsWgjjGTQZTilVjGTW1mwDr9FucAE
16
+
d5gKIk4rtR/3Zd/NDdqp8PrkoWeVM7Hwr789/mpUOeqa/j7YNkDYQh7x+M/odd1D
17
+
KY0bQEQ=
18
+
-----END CERTIFICATE-----
+3
-1
package.json
+3
-1
package.json
···
41
41
"intl:extract": "lingui extract",
42
42
"intl:compile": "lingui compile",
43
43
"nuke": "rm -rf ./node_modules && rm -rf ./ios && rm -rf ./android",
44
-
"update-extensions": "scripts/updateExtensions.sh"
44
+
"update-extensions": "bash scripts/updateExtensions.sh",
45
+
"export": "npx expo export",
46
+
"make-deploy-bundle": "bash scripts/bundleUpdate.sh"
45
47
},
46
48
"dependencies": {
47
49
"@atproto/api": "^0.12.0",
+104
scripts/bundleUpdate.js
+104
scripts/bundleUpdate.js
···
1
+
const crypto = require('crypto')
2
+
const fs = require('fs')
3
+
const fsp = fs.promises
4
+
const path = require('path')
5
+
6
+
const DIST_DIR = './dist'
7
+
const BUNDLES_DIR = '/_expo/static/js'
8
+
const IOS_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/ios')
9
+
const ANDROID_BUNDLE_DIR = path.join(DIST_DIR, BUNDLES_DIR, '/android')
10
+
const METADATA_PATH = path.join(DIST_DIR, '/metadata.json')
11
+
const DEST_DIR = './bundleTempDir'
12
+
13
+
// Weird, don't feel like figuring out _why_ it wants this
14
+
const METADATA = require(`../${METADATA_PATH}`)
15
+
const IOS_METADATA_ASSETS = METADATA.fileMetadata.ios.assets
16
+
const ANDROID_METADATA_ASSETS = METADATA.fileMetadata.android.assets
17
+
18
+
const getMd5 = async path => {
19
+
return new Promise(res => {
20
+
const hash = crypto.createHash('md5')
21
+
const rStream = fs.createReadStream(path)
22
+
rStream.on('data', data => {
23
+
hash.update(data)
24
+
})
25
+
rStream.on('end', () => {
26
+
res(hash.digest('hex'))
27
+
})
28
+
})
29
+
}
30
+
31
+
const moveFiles = async () => {
32
+
console.log('Making directory...')
33
+
await fsp.mkdir(DEST_DIR)
34
+
await fsp.mkdir(path.join(DEST_DIR, '/assets'))
35
+
36
+
console.log('Getting ios md5...')
37
+
const iosCurrPath = path.join(
38
+
IOS_BUNDLE_DIR,
39
+
(await fsp.readdir(IOS_BUNDLE_DIR))[0],
40
+
)
41
+
const iosMd5 = await getMd5(iosCurrPath)
42
+
const iosNewPath = `bundles/${iosMd5}.bundle`
43
+
44
+
console.log('Copying ios bundle...')
45
+
await fsp.cp(iosCurrPath, path.join(DEST_DIR, iosNewPath))
46
+
47
+
console.log('Getting android md5...')
48
+
const androidCurrPath = path.join(
49
+
ANDROID_BUNDLE_DIR,
50
+
(await fsp.readdir(ANDROID_BUNDLE_DIR))[0],
51
+
)
52
+
const androidMd5 = await getMd5(androidCurrPath)
53
+
const androidNewPath = `bundles/${androidMd5}.bundle`
54
+
55
+
console.log('Copying android bundle...')
56
+
await fsp.cp(androidCurrPath, path.join(DEST_DIR, androidNewPath))
57
+
58
+
const iosAssets = []
59
+
const androidAssets = []
60
+
61
+
console.log('Getting ios asset md5s and moving them...')
62
+
for (const asset of IOS_METADATA_ASSETS) {
63
+
const currPath = path.join(DIST_DIR, asset.path)
64
+
const md5 = await getMd5(currPath)
65
+
const withExtPath = `assets/${md5}.${asset.ext}`
66
+
iosAssets.push(withExtPath)
67
+
await fsp.cp(currPath, path.join(DEST_DIR, withExtPath))
68
+
}
69
+
70
+
console.log('Getting android asset md5s and moving them...')
71
+
for (const asset of ANDROID_METADATA_ASSETS) {
72
+
const currPath = path.join(DIST_DIR, asset.path)
73
+
const md5 = await getMd5(currPath)
74
+
const withExtPath = `assets/${md5}.${asset.ext}`
75
+
androidAssets.push(withExtPath)
76
+
await fsp.cp(currPath, path.join(DEST_DIR, withExtPath))
77
+
}
78
+
79
+
const result = {
80
+
version: 0,
81
+
bundler: 'metro',
82
+
fileMetadata: {
83
+
ios: {
84
+
bundle: iosNewPath,
85
+
assets: iosAssets,
86
+
},
87
+
android: {
88
+
bundle: androidNewPath,
89
+
assets: androidAssets,
90
+
},
91
+
},
92
+
}
93
+
94
+
console.log('Writing metadata...')
95
+
await fsp.writeFile(
96
+
path.join(DEST_DIR, 'metadata.json'),
97
+
JSON.stringify(result),
98
+
)
99
+
100
+
console.log('Finished!')
101
+
console.log('Metadata:', result)
102
+
}
103
+
104
+
moveFiles()
+26
scripts/bundleUpdate.sh
+26
scripts/bundleUpdate.sh
···
1
+
#!/bin/bash
2
+
set -o errexit
3
+
set -o pipefail
4
+
set -o nounset
5
+
6
+
rm -rf bundleTempDir
7
+
rm -rf bundle.tar.gz
8
+
9
+
echo "Creating tarball..."
10
+
node scripts/bundleUpdate.js
11
+
12
+
cd bundleTempDir || exit
13
+
14
+
BUNDLE_VERSION=$(date +%s)
15
+
DEPLOYMENT_URL="https://updates.bsky.app/v1/upload?runtime-version=$RUNTIME_VERSION&bundle-version=$BUNDLE_VERSION"
16
+
17
+
tar czvf bundle.tar.gz ./*
18
+
19
+
echo "Deploying to $DEPLOYMENT_URL..."
20
+
21
+
curl -o - --form "bundle=@./bundle.tar.gz" --user "bsky:$DENIS_API_KEY" --basic "$DEPLOYMENT_URL"
22
+
23
+
cd ..
24
+
25
+
rm -rf bundleTempDir
26
+
rm -rf bundle.tar.gz