eoas 1.0.1
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/.eslintignore +1 -0
- package/.eslintrc.js +73 -0
- package/.prettierrc +9 -0
- package/README.md +17 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +6 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +7 -0
- package/package.json +92 -0
- package/src/commands/generate-certs.ts +95 -0
- package/src/commands/init.ts +117 -0
- package/src/commands/publish.ts +277 -0
- package/src/index.d.ts +7 -0
- package/src/lib/assets.ts +118 -0
- package/src/lib/auth.ts +67 -0
- package/src/lib/expoConfig.ts +265 -0
- package/src/lib/log.ts +122 -0
- package/src/lib/ora.ts +113 -0
- package/src/lib/package.ts +6 -0
- package/src/lib/prompts.ts +97 -0
- package/src/lib/repo.ts +62 -0
- package/src/lib/runtimeVersion.ts +177 -0
- package/src/lib/utils.ts +3 -0
- package/src/lib/vcs/README.md +1 -0
- package/src/lib/vcs/clients/git.ts +390 -0
- package/src/lib/vcs/clients/gitNoCommit.ts +45 -0
- package/src/lib/vcs/clients/noVcs.ts +23 -0
- package/src/lib/vcs/git.ts +68 -0
- package/src/lib/vcs/index.ts +25 -0
- package/src/lib/vcs/local.ts +88 -0
- package/src/lib/vcs/vcs.ts +90 -0
- package/src/lib/workflow.ts +47 -0
- package/tsconfig.json +17 -0
package/.eslintignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
node_modules
|
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
root: true,
|
|
3
|
+
extends: ['universe/node'],
|
|
4
|
+
plugins: ['node'],
|
|
5
|
+
ignorePatterns: ['bin/'],
|
|
6
|
+
rules: {
|
|
7
|
+
'no-console': 'warn',
|
|
8
|
+
'no-constant-condition': ['warn', { checkLoops: false }],
|
|
9
|
+
'sort-imports': [
|
|
10
|
+
'warn',
|
|
11
|
+
{
|
|
12
|
+
ignoreDeclarationSort: true,
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
curly: 'warn',
|
|
16
|
+
'import/no-cycle': 'error',
|
|
17
|
+
'import/no-extraneous-dependencies': [
|
|
18
|
+
'error',
|
|
19
|
+
{ devDependencies: ['**/__tests__/**/*', '**/__mocks__/**/*'] },
|
|
20
|
+
],
|
|
21
|
+
'import/no-relative-packages': 'error',
|
|
22
|
+
'no-restricted-imports': [
|
|
23
|
+
'error',
|
|
24
|
+
{
|
|
25
|
+
paths: [
|
|
26
|
+
{
|
|
27
|
+
name: 'lodash',
|
|
28
|
+
message: "Don't use lodash, it's heavy!",
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
'no-underscore-dangle': ['error', { allow: ['__typename'] }],
|
|
34
|
+
'node/no-sync': 'error',
|
|
35
|
+
},
|
|
36
|
+
overrides: [
|
|
37
|
+
{
|
|
38
|
+
files: ['*.ts', '*.d.ts'],
|
|
39
|
+
parserOptions: {
|
|
40
|
+
project: './tsconfig.json',
|
|
41
|
+
},
|
|
42
|
+
rules: {
|
|
43
|
+
'@typescript-eslint/explicit-function-return-type': [
|
|
44
|
+
'warn',
|
|
45
|
+
{
|
|
46
|
+
allowExpressions: true,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
'@typescript-eslint/prefer-nullish-coalescing': ['warn', { ignorePrimitives: true }],
|
|
50
|
+
'@typescript-eslint/no-confusing-void-expression': 'warn',
|
|
51
|
+
'@typescript-eslint/await-thenable': 'error',
|
|
52
|
+
'@typescript-eslint/no-misused-promises': [
|
|
53
|
+
'error',
|
|
54
|
+
{
|
|
55
|
+
checksVoidReturn: false,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
'@typescript-eslint/no-floating-promises': 'error',
|
|
59
|
+
'no-void': ['warn', { allowAsStatement: true }],
|
|
60
|
+
'no-return-await': 'off',
|
|
61
|
+
'@typescript-eslint/return-await': ['error', 'always'],
|
|
62
|
+
'@typescript-eslint/no-confusing-non-null-assertion': 'warn',
|
|
63
|
+
'@typescript-eslint/no-extra-non-null-assertion': 'warn',
|
|
64
|
+
'@typescript-eslint/prefer-as-const': 'warn',
|
|
65
|
+
'@typescript-eslint/prefer-includes': 'warn',
|
|
66
|
+
'@typescript-eslint/prefer-readonly': 'warn',
|
|
67
|
+
'@typescript-eslint/prefer-string-starts-ends-with': 'warn',
|
|
68
|
+
'@typescript-eslint/prefer-ts-expect-error': 'warn',
|
|
69
|
+
'@typescript-eslint/no-unnecessary-type-assertion': 'warn',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# EOAS (Expo-Open-OTA Support)
|
|
2
|
+
|
|
3
|
+
EOAS (Expo-Open-OTA Support) is a powerful helper package designed to simplify the setup and update publication process for the [expo-open-ota](https://github.com/axelmarciano/expo-open-ota) project.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
To get started with EOAS, check out the official documentation:
|
|
8
|
+
[EOAS Official Documentation](https://axelmarciano.github.io/expo-open-ota/)
|
|
9
|
+
|
|
10
|
+
## Learn More
|
|
11
|
+
For detailed information and to explore the core functionalities of expo-open-ota, visit the main repository:
|
|
12
|
+
[expo-open-ota on GitHub](https://github.com/axelmarciano/expo-open-ota)
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
Feel free to contribute, raise issues, or share feedback to help us improve EOAS!
|
|
17
|
+
|
package/bin/dev.cmd
ADDED
package/bin/dev.js
ADDED
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eoas",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc --project tsconfig.json",
|
|
7
|
+
"watch": "tsc --project tsconfig.json --watch",
|
|
8
|
+
"lint": "eslint ."
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18.0.0"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/axelmarciano/expo-open-ota/tree/main/eoas",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"expo-open-ota",
|
|
16
|
+
"expo",
|
|
17
|
+
"eas",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"author": "Axel Marciano",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"description": "A CLI tool to manage publishing and OTA updates for expo-open-OTA self-hosted server. This is not an official tool from Expo but an open-source project (https://github.com/axelmarciano/expo-open-ota)",
|
|
23
|
+
"repository": "axelmarciano/expo-open-ota",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@expo/code-signing-certificates": "^0.0.5",
|
|
26
|
+
"@expo/config": "10.0.6",
|
|
27
|
+
"@expo/config-plugins": "9.0.12",
|
|
28
|
+
"@expo/eas-build-job": "1.0.165",
|
|
29
|
+
"@expo/fingerprint": "^0.11.7",
|
|
30
|
+
"@expo/package-manager": "1.7.0",
|
|
31
|
+
"@expo/spawn-async": "1.7.2",
|
|
32
|
+
"@oclif/core": "^4.2.4",
|
|
33
|
+
"@types/node-fetch": "^2.6.12",
|
|
34
|
+
"@urql/core": "4.0.11",
|
|
35
|
+
"@urql/exchange-retry": "1.2.0",
|
|
36
|
+
"better-opn": "3.0.2",
|
|
37
|
+
"chalk": "4.1.2",
|
|
38
|
+
"fast-glob": "3.3.2",
|
|
39
|
+
"figures": "3.2.0",
|
|
40
|
+
"file-type": "^20.0.0",
|
|
41
|
+
"form-data": "^4.0.1",
|
|
42
|
+
"fs-extra": "11.2.0",
|
|
43
|
+
"getenv": "1.0.0",
|
|
44
|
+
"graphql": "16.8.1",
|
|
45
|
+
"graphql-tag": "^2.12.6",
|
|
46
|
+
"https-proxy-agent": "5.0.1",
|
|
47
|
+
"ignore": "5.3.0",
|
|
48
|
+
"joi": "17.11.0",
|
|
49
|
+
"jscodeshift": "^17.1.2",
|
|
50
|
+
"mime": "3.0.0",
|
|
51
|
+
"node-fetch": "^2.6.7",
|
|
52
|
+
"ora": "^5.1.0",
|
|
53
|
+
"prompts": "^2.4.2",
|
|
54
|
+
"recast": "^0.23.9",
|
|
55
|
+
"resolve-from": "5.0.0",
|
|
56
|
+
"semver": "7.5.4",
|
|
57
|
+
"tar": "6.2.1",
|
|
58
|
+
"terminal-link": "2.1.1",
|
|
59
|
+
"uuid": "9.0.1",
|
|
60
|
+
"eslint": "^8.57.1",
|
|
61
|
+
"prettier": "3.1.1",
|
|
62
|
+
"log-symbols": "^4.0.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@babel/parser": "^7.26.7",
|
|
66
|
+
"@babel/types": "^7.26.7",
|
|
67
|
+
"@tsconfig/node18": "^18.2.4",
|
|
68
|
+
"@types/fs-extra": "11.0.4",
|
|
69
|
+
"@types/getenv": "^1.0.0",
|
|
70
|
+
"@types/jscodeshift": "^0.12.0",
|
|
71
|
+
"@types/mime": "^3.0.4",
|
|
72
|
+
"@types/node": "^18.19.74",
|
|
73
|
+
"@types/prompts": "^2.4.9",
|
|
74
|
+
"@types/semver": "7.5.6",
|
|
75
|
+
"@types/tar": "6.1.10",
|
|
76
|
+
"@types/uuid": "9.0.7",
|
|
77
|
+
"eslint-config-universe": "^14.0.0",
|
|
78
|
+
"eslint-plugin-async-protect": "^3.1.0",
|
|
79
|
+
"eslint-plugin-node": "^11.1.0",
|
|
80
|
+
"ts-node": "10.9.2",
|
|
81
|
+
"typescript": "5.3.3"
|
|
82
|
+
},
|
|
83
|
+
"bin": {
|
|
84
|
+
"eoas": "./bin/run.js"
|
|
85
|
+
},
|
|
86
|
+
"oclif": {
|
|
87
|
+
"bin": "eoas",
|
|
88
|
+
"commands": "./dist/commands",
|
|
89
|
+
"dirname": "eoas",
|
|
90
|
+
"topicSeparator": ":"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
convertCertificateToCertificatePEM,
|
|
3
|
+
convertKeyPairToPEM,
|
|
4
|
+
generateKeyPair,
|
|
5
|
+
generateSelfSignedCodeSigningCertificate,
|
|
6
|
+
} from '@expo/code-signing-certificates';
|
|
7
|
+
import { Command } from '@oclif/core';
|
|
8
|
+
import { ensureDirSync, writeFile } from 'fs-extra';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
|
|
11
|
+
import Log from '../lib/log';
|
|
12
|
+
import { promptAsync } from '../lib/prompts';
|
|
13
|
+
|
|
14
|
+
export default class GenerateCerts extends Command {
|
|
15
|
+
static override args = {};
|
|
16
|
+
static override description = 'Generate private & public certificates for code signing';
|
|
17
|
+
static override examples = ['<%= config.bin %> <%= command.id %>'];
|
|
18
|
+
static override flags = {};
|
|
19
|
+
public async run(): Promise<void> {
|
|
20
|
+
const { certificateOutputDir } = await promptAsync({
|
|
21
|
+
message:
|
|
22
|
+
'In which directory would you like to store your code signing certificate (used by your expo app)?',
|
|
23
|
+
name: 'certificateOutputDir',
|
|
24
|
+
type: 'text',
|
|
25
|
+
initial: './keysStore',
|
|
26
|
+
validate: v => {
|
|
27
|
+
try {
|
|
28
|
+
// eslint-disable-next-line
|
|
29
|
+
ensureDirSync(path.join(process.cwd(), v));
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
const { keyOutputDir } = await promptAsync({
|
|
37
|
+
message:
|
|
38
|
+
'In which directory would you like to store your key pair (used by your OTA Server) ?. ⚠️ Those keysStore are sensitive and should be kept private.',
|
|
39
|
+
name: 'keyOutputDir',
|
|
40
|
+
type: 'text',
|
|
41
|
+
initial: './keysStore',
|
|
42
|
+
validate: v => {
|
|
43
|
+
try {
|
|
44
|
+
// eslint-disable-next-line
|
|
45
|
+
ensureDirSync(path.join(process.cwd(), v));
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const { certificateCommonName } = await promptAsync({
|
|
53
|
+
message: 'Please enter your Organization name',
|
|
54
|
+
name: 'certificateCommonName',
|
|
55
|
+
type: 'text',
|
|
56
|
+
initial: 'Your Organization Name',
|
|
57
|
+
validate: v => {
|
|
58
|
+
return !!v;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const { certificateValidityDurationYears } = await promptAsync({
|
|
62
|
+
message: 'How many years should the certificate be valid for?',
|
|
63
|
+
name: 'certificateValidityDurationYears',
|
|
64
|
+
type: 'number',
|
|
65
|
+
initial: 10,
|
|
66
|
+
validate: v => {
|
|
67
|
+
return v > 0 && Number.isInteger(v);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
const validityDurationYears = Math.floor(Number(certificateValidityDurationYears));
|
|
71
|
+
const certificateOutput = path.resolve(process.cwd(), certificateOutputDir);
|
|
72
|
+
const keyOutput = path.resolve(process.cwd(), keyOutputDir);
|
|
73
|
+
const validityNotBefore = new Date();
|
|
74
|
+
const validityNotAfter = new Date();
|
|
75
|
+
validityNotAfter.setFullYear(validityNotAfter.getFullYear() + validityDurationYears);
|
|
76
|
+
const keyPair = generateKeyPair();
|
|
77
|
+
const certificate = generateSelfSignedCodeSigningCertificate({
|
|
78
|
+
keyPair,
|
|
79
|
+
validityNotBefore,
|
|
80
|
+
validityNotAfter,
|
|
81
|
+
commonName: certificateCommonName,
|
|
82
|
+
});
|
|
83
|
+
const keyPairPEM = convertKeyPairToPEM(keyPair);
|
|
84
|
+
const certificatePEM = convertCertificateToCertificatePEM(certificate);
|
|
85
|
+
await Promise.all([
|
|
86
|
+
writeFile(path.join(keyOutput, 'public-key.pem'), keyPairPEM.publicKeyPEM),
|
|
87
|
+
writeFile(path.join(keyOutput, 'private-key.pem'), keyPairPEM.privateKeyPEM),
|
|
88
|
+
writeFile(path.join(certificateOutput, 'certificate.pem'), certificatePEM),
|
|
89
|
+
]);
|
|
90
|
+
Log.succeed(
|
|
91
|
+
`Generated public and private keys output in ${keyOutputDir}. Please follow the documentation to securely store them and do not commit them to your repository.`
|
|
92
|
+
);
|
|
93
|
+
Log.succeed(`Generated code signing certificate output in ${certificateOutputDir}.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createOrModifyExpoConfigAsync,
|
|
7
|
+
getExpoConfigUpdateUrl,
|
|
8
|
+
getPrivateExpoConfigAsync,
|
|
9
|
+
} from '../lib/expoConfig';
|
|
10
|
+
import Log from '../lib/log';
|
|
11
|
+
import { ora } from '../lib/ora';
|
|
12
|
+
import { isExpoInstalled } from '../lib/package';
|
|
13
|
+
import { confirmAsync, promptAsync } from '../lib/prompts';
|
|
14
|
+
import { isValidUpdateUrl } from '../lib/utils';
|
|
15
|
+
|
|
16
|
+
export default class Init extends Command {
|
|
17
|
+
static override args = {};
|
|
18
|
+
static override description = 'Configure your existing expo project with Expo Open OTA';
|
|
19
|
+
static override examples = ['<%= config.bin %> <%= command.id %>'];
|
|
20
|
+
static override flags = {};
|
|
21
|
+
public async run(): Promise<void> {
|
|
22
|
+
const projectDir = process.cwd();
|
|
23
|
+
const hasExpo = isExpoInstalled(projectDir);
|
|
24
|
+
if (!hasExpo) {
|
|
25
|
+
Log.error('Expo is not installed in this project. Please install Expo first.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const config = await getPrivateExpoConfigAsync(projectDir);
|
|
29
|
+
if (!config) {
|
|
30
|
+
Log.error(
|
|
31
|
+
'Could not find Expo config in this project. Please make sure you have an Expo config.'
|
|
32
|
+
);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const { updateUrl: promptedUrl } = await promptAsync({
|
|
36
|
+
message: 'Enter the URL of your update server (ex: https://customota.com)',
|
|
37
|
+
name: 'updateUrl',
|
|
38
|
+
type: 'text',
|
|
39
|
+
initial: getExpoConfigUpdateUrl(config),
|
|
40
|
+
validate: v => {
|
|
41
|
+
return !!v && isValidUpdateUrl(v);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
let manifestEndpoint = `${promptedUrl}/manifest`;
|
|
45
|
+
const updateUrl = getExpoConfigUpdateUrl(config);
|
|
46
|
+
if (updateUrl && !updateUrl.includes('expo.dev')) {
|
|
47
|
+
const confirmed = await confirmAsync({
|
|
48
|
+
message: `Expo config already has an update URL set to ${updateUrl}. Do you want to replace it?`,
|
|
49
|
+
name: 'replace',
|
|
50
|
+
type: 'confirm',
|
|
51
|
+
});
|
|
52
|
+
if (!confirmed) {
|
|
53
|
+
manifestEndpoint = updateUrl;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const confirmed = await confirmAsync({
|
|
57
|
+
message: 'Do you have already generated your certificates and keysStore for code signing?',
|
|
58
|
+
name: 'certificates',
|
|
59
|
+
type: 'confirm',
|
|
60
|
+
});
|
|
61
|
+
if (!confirmed) {
|
|
62
|
+
Log.fail('You need to generate your certificates first by using npx eoas generate-keysStore');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const { codeSigningCertificatePath } = await promptAsync({
|
|
66
|
+
message: 'Enter the path to your code signing certificate (ex: ./keysStore/certificate.pem)',
|
|
67
|
+
name: 'codeSigningCertificatePath',
|
|
68
|
+
type: 'text',
|
|
69
|
+
initial: './keysStore/certificate.pem',
|
|
70
|
+
validate: v => {
|
|
71
|
+
try {
|
|
72
|
+
const fullPath = path.resolve(projectDir, v);
|
|
73
|
+
// eslint-disable-next-line
|
|
74
|
+
const fileExists = fs.existsSync(fullPath);
|
|
75
|
+
if (!fileExists) {
|
|
76
|
+
Log.newLine();
|
|
77
|
+
Log.error('File does not exist');
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
// eslint-disable-next-line
|
|
81
|
+
const key = fs.readFileSync(fullPath, 'utf8');
|
|
82
|
+
if (!key) {
|
|
83
|
+
Log.error('Empty key');
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
const newUpdateConfig = {
|
|
93
|
+
url: manifestEndpoint,
|
|
94
|
+
codeSigningMetadata: {
|
|
95
|
+
keyid: 'main',
|
|
96
|
+
alg: 'rsa-v1_5-sha256' as const,
|
|
97
|
+
},
|
|
98
|
+
codeSigningCertificate: codeSigningCertificatePath,
|
|
99
|
+
enabled: true,
|
|
100
|
+
requestHeaders: {
|
|
101
|
+
'expo-channel-name': 'process.env.RELEASE_CHANNEL',
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const updateConfigSpinner = ora('Updating Expo config').start();
|
|
105
|
+
try {
|
|
106
|
+
await createOrModifyExpoConfigAsync(projectDir, {
|
|
107
|
+
updates: newUpdateConfig,
|
|
108
|
+
});
|
|
109
|
+
updateConfigSpinner.succeed(
|
|
110
|
+
'Expo config successfully updated do not forget to format the file with prettier or eslint'
|
|
111
|
+
);
|
|
112
|
+
} catch (e) {
|
|
113
|
+
updateConfigSpinner.fail('Failed to update Expo config');
|
|
114
|
+
Log.error(e);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|