eoas 1.0.2 → 1.0.3
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/dist/commands/generate-certs.d.ts +8 -0
- package/dist/commands/generate-certs.js +90 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +113 -0
- package/dist/commands/publish.d.ts +18 -0
- package/dist/commands/publish.js +249 -0
- package/dist/lib/assets.d.ts +19 -0
- package/dist/lib/assets.js +90 -0
- package/dist/lib/auth.d.ts +6 -0
- package/dist/lib/auth.js +57 -0
- package/dist/lib/expoConfig.d.ts +23 -0
- package/dist/lib/expoConfig.js +198 -0
- package/dist/lib/log.d.ts +40 -0
- package/dist/lib/log.js +101 -0
- package/dist/lib/ora.d.ts +9 -0
- package/dist/lib/ora.js +103 -0
- package/dist/lib/package.d.ts +1 -0
- package/dist/lib/package.js +9 -0
- package/dist/lib/prompts.d.ts +13 -0
- package/dist/lib/prompts.js +68 -0
- package/dist/lib/repo.d.ts +6 -0
- package/dist/lib/repo.js +48 -0
- package/dist/lib/runtimeVersion.d.ts +42 -0
- package/dist/lib/runtimeVersion.js +116 -0
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.js +7 -0
- package/dist/lib/vcs/clients/git.d.ts +31 -0
- package/dist/lib/vcs/clients/git.js +322 -0
- package/dist/lib/vcs/clients/gitNoCommit.d.ts +8 -0
- package/dist/lib/vcs/clients/gitNoCommit.js +42 -0
- package/dist/lib/vcs/clients/noVcs.d.ts +7 -0
- package/dist/lib/vcs/clients/noVcs.js +22 -0
- package/dist/lib/vcs/git.d.ts +13 -0
- package/dist/lib/vcs/git.js +60 -0
- package/dist/lib/vcs/index.d.ts +2 -0
- package/dist/lib/vcs/index.js +26 -0
- package/dist/lib/vcs/local.d.ts +19 -0
- package/dist/lib/vcs/local.js +85 -0
- package/dist/lib/vcs/vcs.d.ts +25 -0
- package/dist/lib/vcs/vcs.js +61 -0
- package/dist/lib/workflow.d.ts +4 -0
- package/dist/lib/workflow.js +40 -0
- package/package.json +6 -2
- package/.eslintignore +0 -1
- package/.eslintrc.js +0 -73
- package/.prettierrc +0 -9
- package/src/commands/generate-certs.ts +0 -95
- package/src/commands/init.ts +0 -117
- package/src/commands/publish.ts +0 -277
- package/src/index.d.ts +0 -7
- package/src/lib/assets.ts +0 -118
- package/src/lib/auth.ts +0 -67
- package/src/lib/expoConfig.ts +0 -265
- package/src/lib/log.ts +0 -122
- package/src/lib/ora.ts +0 -113
- package/src/lib/package.ts +0 -6
- package/src/lib/prompts.ts +0 -97
- package/src/lib/repo.ts +0 -62
- package/src/lib/runtimeVersion.ts +0 -177
- package/src/lib/utils.ts +0 -3
- package/src/lib/vcs/README.md +0 -1
- package/src/lib/vcs/clients/git.ts +0 -390
- package/src/lib/vcs/clients/gitNoCommit.ts +0 -45
- package/src/lib/vcs/clients/noVcs.ts +0 -23
- package/src/lib/vcs/git.ts +0 -68
- package/src/lib/vcs/index.ts +0 -25
- package/src/lib/vcs/local.ts +0 -88
- package/src/lib/vcs/vcs.ts +0 -90
- package/src/lib/workflow.ts +0 -47
- package/tsconfig.json +0 -17
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const code_signing_certificates_1 = require("@expo/code-signing-certificates");
|
|
5
|
+
const core_1 = require("@oclif/core");
|
|
6
|
+
const fs_extra_1 = require("fs-extra");
|
|
7
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
8
|
+
const log_1 = tslib_1.__importDefault(require("../lib/log"));
|
|
9
|
+
const prompts_1 = require("../lib/prompts");
|
|
10
|
+
class GenerateCerts extends core_1.Command {
|
|
11
|
+
static args = {};
|
|
12
|
+
static description = 'Generate private & public certificates for code signing';
|
|
13
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
14
|
+
static flags = {};
|
|
15
|
+
async run() {
|
|
16
|
+
const { certificateOutputDir } = await (0, prompts_1.promptAsync)({
|
|
17
|
+
message: 'In which directory would you like to store your code signing certificate (used by your expo app)?',
|
|
18
|
+
name: 'certificateOutputDir',
|
|
19
|
+
type: 'text',
|
|
20
|
+
initial: './certs',
|
|
21
|
+
validate: v => {
|
|
22
|
+
try {
|
|
23
|
+
// eslint-disable-next-line
|
|
24
|
+
(0, fs_extra_1.ensureDirSync)(path_1.default.join(process.cwd(), v));
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
const { keyOutputDir } = await (0, prompts_1.promptAsync)({
|
|
33
|
+
message: 'In which directory would you like to store your key pair (used by your OTA Server) ?. ⚠️ Those certss are sensitive and should be kept private.',
|
|
34
|
+
name: 'keyOutputDir',
|
|
35
|
+
type: 'text',
|
|
36
|
+
initial: './certs',
|
|
37
|
+
validate: v => {
|
|
38
|
+
try {
|
|
39
|
+
// eslint-disable-next-line
|
|
40
|
+
(0, fs_extra_1.ensureDirSync)(path_1.default.join(process.cwd(), v));
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
const { certificateCommonName } = await (0, prompts_1.promptAsync)({
|
|
49
|
+
message: 'Please enter your Organization name',
|
|
50
|
+
name: 'certificateCommonName',
|
|
51
|
+
type: 'text',
|
|
52
|
+
initial: 'Your Organization Name',
|
|
53
|
+
validate: v => {
|
|
54
|
+
return !!v;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const { certificateValidityDurationYears } = await (0, prompts_1.promptAsync)({
|
|
58
|
+
message: 'How many years should the certificate be valid for?',
|
|
59
|
+
name: 'certificateValidityDurationYears',
|
|
60
|
+
type: 'number',
|
|
61
|
+
initial: 10,
|
|
62
|
+
validate: v => {
|
|
63
|
+
return v > 0 && Number.isInteger(v);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const validityDurationYears = Math.floor(Number(certificateValidityDurationYears));
|
|
67
|
+
const certificateOutput = path_1.default.resolve(process.cwd(), certificateOutputDir);
|
|
68
|
+
const keyOutput = path_1.default.resolve(process.cwd(), keyOutputDir);
|
|
69
|
+
const validityNotBefore = new Date();
|
|
70
|
+
const validityNotAfter = new Date();
|
|
71
|
+
validityNotAfter.setFullYear(validityNotAfter.getFullYear() + validityDurationYears);
|
|
72
|
+
const keyPair = (0, code_signing_certificates_1.generateKeyPair)();
|
|
73
|
+
const certificate = (0, code_signing_certificates_1.generateSelfSignedCodeSigningCertificate)({
|
|
74
|
+
keyPair,
|
|
75
|
+
validityNotBefore,
|
|
76
|
+
validityNotAfter,
|
|
77
|
+
commonName: certificateCommonName,
|
|
78
|
+
});
|
|
79
|
+
const keyPairPEM = (0, code_signing_certificates_1.convertKeyPairToPEM)(keyPair);
|
|
80
|
+
const certificatePEM = (0, code_signing_certificates_1.convertCertificateToCertificatePEM)(certificate);
|
|
81
|
+
await Promise.all([
|
|
82
|
+
(0, fs_extra_1.writeFile)(path_1.default.join(keyOutput, 'public-key.pem'), keyPairPEM.publicKeyPEM),
|
|
83
|
+
(0, fs_extra_1.writeFile)(path_1.default.join(keyOutput, 'private-key.pem'), keyPairPEM.privateKeyPEM),
|
|
84
|
+
(0, fs_extra_1.writeFile)(path_1.default.join(certificateOutput, 'certificate.pem'), certificatePEM),
|
|
85
|
+
]);
|
|
86
|
+
log_1.default.succeed(`Generated public and private keys output in ${keyOutputDir}. Please follow the documentation to securely store them and do not commit them to your repository.`);
|
|
87
|
+
log_1.default.succeed(`Generated code signing certificate output in ${certificateOutputDir}.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.default = GenerateCerts;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const core_1 = require("@oclif/core");
|
|
5
|
+
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
const expoConfig_1 = require("../lib/expoConfig");
|
|
8
|
+
const log_1 = tslib_1.__importDefault(require("../lib/log"));
|
|
9
|
+
const ora_1 = require("../lib/ora");
|
|
10
|
+
const package_1 = require("../lib/package");
|
|
11
|
+
const prompts_1 = require("../lib/prompts");
|
|
12
|
+
const utils_1 = require("../lib/utils");
|
|
13
|
+
class Init extends core_1.Command {
|
|
14
|
+
static args = {};
|
|
15
|
+
static description = 'Configure your existing expo project with Expo Open OTA';
|
|
16
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
17
|
+
static flags = {};
|
|
18
|
+
async run() {
|
|
19
|
+
const projectDir = process.cwd();
|
|
20
|
+
const hasExpo = (0, package_1.isExpoInstalled)(projectDir);
|
|
21
|
+
if (!hasExpo) {
|
|
22
|
+
log_1.default.error('Expo is not installed in this project. Please install Expo first.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const config = await (0, expoConfig_1.getPrivateExpoConfigAsync)(projectDir);
|
|
26
|
+
if (!config) {
|
|
27
|
+
log_1.default.error('Could not find Expo config in this project. Please make sure you have an Expo config.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const { updateUrl: promptedUrl } = await (0, prompts_1.promptAsync)({
|
|
31
|
+
message: 'Enter the URL of your update server (ex: https://customota.com)',
|
|
32
|
+
name: 'updateUrl',
|
|
33
|
+
type: 'text',
|
|
34
|
+
initial: (0, expoConfig_1.getExpoConfigUpdateUrl)(config),
|
|
35
|
+
validate: v => {
|
|
36
|
+
return !!v && (0, utils_1.isValidUpdateUrl)(v);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
let manifestEndpoint = `${promptedUrl}/manifest`;
|
|
40
|
+
const updateUrl = (0, expoConfig_1.getExpoConfigUpdateUrl)(config);
|
|
41
|
+
if (updateUrl && !updateUrl.includes('expo.dev')) {
|
|
42
|
+
const confirmed = await (0, prompts_1.confirmAsync)({
|
|
43
|
+
message: `Expo config already has an update URL set to ${updateUrl}. Do you want to replace it?`,
|
|
44
|
+
name: 'replace',
|
|
45
|
+
type: 'confirm',
|
|
46
|
+
});
|
|
47
|
+
if (!confirmed) {
|
|
48
|
+
manifestEndpoint = updateUrl;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const confirmed = await (0, prompts_1.confirmAsync)({
|
|
52
|
+
message: 'Do you have already generated your certificates for code signing?',
|
|
53
|
+
name: 'certificates',
|
|
54
|
+
type: 'confirm',
|
|
55
|
+
});
|
|
56
|
+
if (!confirmed) {
|
|
57
|
+
log_1.default.fail('You need to generate your certificates first by using npx eoas generate-certs');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const { codeSigningCertificatePath } = await (0, prompts_1.promptAsync)({
|
|
61
|
+
message: 'Enter the path to your code signing certificate (ex: ./certs/certificate.pem)',
|
|
62
|
+
name: 'codeSigningCertificatePath',
|
|
63
|
+
type: 'text',
|
|
64
|
+
initial: './certs/certificate.pem',
|
|
65
|
+
validate: v => {
|
|
66
|
+
try {
|
|
67
|
+
const fullPath = path_1.default.resolve(projectDir, v);
|
|
68
|
+
// eslint-disable-next-line
|
|
69
|
+
const fileExists = fs_extra_1.default.existsSync(fullPath);
|
|
70
|
+
if (!fileExists) {
|
|
71
|
+
log_1.default.newLine();
|
|
72
|
+
log_1.default.error('File does not exist');
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
// eslint-disable-next-line
|
|
76
|
+
const key = fs_extra_1.default.readFileSync(fullPath, 'utf8');
|
|
77
|
+
if (!key) {
|
|
78
|
+
log_1.default.error('Empty key');
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const newUpdateConfig = {
|
|
89
|
+
url: manifestEndpoint,
|
|
90
|
+
codeSigningMetadata: {
|
|
91
|
+
keyid: 'main',
|
|
92
|
+
alg: 'rsa-v1_5-sha256',
|
|
93
|
+
},
|
|
94
|
+
codeSigningCertificate: codeSigningCertificatePath,
|
|
95
|
+
enabled: true,
|
|
96
|
+
requestHeaders: {
|
|
97
|
+
'expo-channel-name': 'process.env.RELEASE_CHANNEL',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
const updateConfigSpinner = (0, ora_1.ora)('Updating Expo config').start();
|
|
101
|
+
try {
|
|
102
|
+
await (0, expoConfig_1.createOrModifyExpoConfigAsync)(projectDir, {
|
|
103
|
+
updates: newUpdateConfig,
|
|
104
|
+
});
|
|
105
|
+
updateConfigSpinner.succeed('Expo config successfully updated do not forget to format the file with prettier or eslint');
|
|
106
|
+
}
|
|
107
|
+
catch (e) {
|
|
108
|
+
updateConfigSpinner.fail('Failed to update Expo config');
|
|
109
|
+
log_1.default.error(e);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.default = Init;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { Config } from '@oclif/core/lib/config';
|
|
3
|
+
import { Client } from '../lib/vcs/vcs';
|
|
4
|
+
export default class Publish extends Command {
|
|
5
|
+
vcsClient: Client;
|
|
6
|
+
constructor(argv: string[], config: Config);
|
|
7
|
+
static args: {};
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
platform: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
channel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
+
branch: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
|
+
nonInteractive: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
private sanitizeFlags;
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
5
|
+
const spawn_async_1 = tslib_1.__importDefault(require("@expo/spawn-async"));
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const form_data_1 = tslib_1.__importDefault(require("form-data"));
|
|
8
|
+
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
9
|
+
const mime_1 = tslib_1.__importDefault(require("mime"));
|
|
10
|
+
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
11
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
12
|
+
const assets_1 = require("../lib/assets");
|
|
13
|
+
const auth_1 = require("../lib/auth");
|
|
14
|
+
const expoConfig_1 = require("../lib/expoConfig");
|
|
15
|
+
const log_1 = tslib_1.__importDefault(require("../lib/log"));
|
|
16
|
+
const ora_1 = require("../lib/ora");
|
|
17
|
+
const package_1 = require("../lib/package");
|
|
18
|
+
const prompts_1 = require("../lib/prompts");
|
|
19
|
+
const repo_1 = require("../lib/repo");
|
|
20
|
+
const runtimeVersion_1 = require("../lib/runtimeVersion");
|
|
21
|
+
const vcs_1 = require("../lib/vcs");
|
|
22
|
+
const workflow_1 = require("../lib/workflow");
|
|
23
|
+
class Publish extends core_1.Command {
|
|
24
|
+
vcsClient;
|
|
25
|
+
constructor(argv, config) {
|
|
26
|
+
super(argv, config);
|
|
27
|
+
this.vcsClient = (0, vcs_1.resolveVcsClient)(false);
|
|
28
|
+
}
|
|
29
|
+
static args = {};
|
|
30
|
+
static description = 'Publish a new update to the self-hosted update server';
|
|
31
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
32
|
+
static flags = {
|
|
33
|
+
platform: core_1.Flags.string({
|
|
34
|
+
type: 'option',
|
|
35
|
+
options: Object.values(expoConfig_1.RequestedPlatform),
|
|
36
|
+
default: expoConfig_1.RequestedPlatform.All,
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
channel: core_1.Flags.string({
|
|
40
|
+
description: 'Name of the channel to publish the update to',
|
|
41
|
+
required: true,
|
|
42
|
+
}),
|
|
43
|
+
branch: core_1.Flags.string({
|
|
44
|
+
description: 'Name of the branch to point to',
|
|
45
|
+
required: true,
|
|
46
|
+
}),
|
|
47
|
+
nonInteractive: core_1.Flags.boolean({
|
|
48
|
+
description: 'Run command in non-interactive mode',
|
|
49
|
+
default: false,
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
sanitizeFlags(flags) {
|
|
53
|
+
return {
|
|
54
|
+
platform: flags.platform,
|
|
55
|
+
branch: flags.branch,
|
|
56
|
+
nonInteractive: flags.nonInteractive,
|
|
57
|
+
channel: flags.channel,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async run() {
|
|
61
|
+
const credentials = (0, auth_1.retrieveExpoCredentials)();
|
|
62
|
+
if (!credentials.token && !credentials.sessionSecret) {
|
|
63
|
+
log_1.default.error('You are not logged to eas, please run `eas login`');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const { flags } = await this.parse(Publish);
|
|
67
|
+
const { platform, nonInteractive, branch, channel } = this.sanitizeFlags(flags);
|
|
68
|
+
if (!branch) {
|
|
69
|
+
log_1.default.error('Branch name is required');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!channel) {
|
|
73
|
+
log_1.default.error('Channel name is required');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await this.vcsClient.ensureRepoExistsAsync();
|
|
77
|
+
await (0, repo_1.ensureRepoIsCleanAsync)(this.vcsClient, nonInteractive);
|
|
78
|
+
const projectDir = process.cwd();
|
|
79
|
+
const hasExpo = (0, package_1.isExpoInstalled)(projectDir);
|
|
80
|
+
if (!hasExpo) {
|
|
81
|
+
log_1.default.error('Expo is not installed in this project. Please install Expo first.');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const privateConfig = await (0, expoConfig_1.getPrivateExpoConfigAsync)(projectDir, {
|
|
85
|
+
env: {
|
|
86
|
+
RELEASE_CHANNEL: channel,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
const updateUrl = (0, expoConfig_1.getExpoConfigUpdateUrl)(privateConfig);
|
|
90
|
+
if (!updateUrl) {
|
|
91
|
+
log_1.default.error("Update url is not setup in your config. Please run 'eoas init' to setup the update url");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
let baseUrl;
|
|
95
|
+
try {
|
|
96
|
+
const parsedUrl = new URL(updateUrl);
|
|
97
|
+
baseUrl = parsedUrl.origin;
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
log_1.default.error('Invalid URL', e);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!nonInteractive) {
|
|
104
|
+
const confirmed = await (0, prompts_1.confirmAsync)({
|
|
105
|
+
message: `Is this the correct URL of your self-hosted update server? ${baseUrl}`,
|
|
106
|
+
name: 'export',
|
|
107
|
+
type: 'confirm',
|
|
108
|
+
});
|
|
109
|
+
if (!confirmed) {
|
|
110
|
+
log_1.default.error('Please run `eoas init` to setup the correct update url');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const runtimeSpinner = (0, ora_1.ora)('Resolving runtime version').start();
|
|
114
|
+
const runtimeVersions = [
|
|
115
|
+
...(!platform || platform === expoConfig_1.RequestedPlatform.All || platform === expoConfig_1.RequestedPlatform.Ios
|
|
116
|
+
? [
|
|
117
|
+
(await (0, runtimeVersion_1.resolveRuntimeVersionAsync)({
|
|
118
|
+
exp: privateConfig,
|
|
119
|
+
platform: 'ios',
|
|
120
|
+
workflow: await (0, workflow_1.resolveWorkflowAsync)(projectDir, eas_build_job_1.Platform.IOS, this.vcsClient),
|
|
121
|
+
projectDir,
|
|
122
|
+
env: {
|
|
123
|
+
RELEASE_CHANNEL: channel,
|
|
124
|
+
},
|
|
125
|
+
}))?.runtimeVersion,
|
|
126
|
+
]
|
|
127
|
+
: []),
|
|
128
|
+
...(!platform || platform === expoConfig_1.RequestedPlatform.All || platform === expoConfig_1.RequestedPlatform.Android
|
|
129
|
+
? [
|
|
130
|
+
(await (0, runtimeVersion_1.resolveRuntimeVersionAsync)({
|
|
131
|
+
exp: privateConfig,
|
|
132
|
+
platform: 'android',
|
|
133
|
+
workflow: await (0, workflow_1.resolveWorkflowAsync)(projectDir, eas_build_job_1.Platform.ANDROID, this.vcsClient),
|
|
134
|
+
projectDir,
|
|
135
|
+
env: {
|
|
136
|
+
RELEASE_CHANNEL: channel,
|
|
137
|
+
},
|
|
138
|
+
}))?.runtimeVersion,
|
|
139
|
+
]
|
|
140
|
+
: []),
|
|
141
|
+
].filter(Boolean);
|
|
142
|
+
if (!runtimeVersions.length) {
|
|
143
|
+
runtimeSpinner.fail('Could not resolve runtime versions for the requested platforms');
|
|
144
|
+
log_1.default.error('Could not resolve runtime versions for the requested platforms');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
runtimeSpinner.succeed('Runtime versions resolved');
|
|
148
|
+
const exportSpinner = (0, ora_1.ora)("Exporting project's static files").start();
|
|
149
|
+
try {
|
|
150
|
+
await (0, spawn_async_1.default)('rm', ['-rf', 'dist'], { cwd: projectDir });
|
|
151
|
+
const { stdout } = await (0, spawn_async_1.default)('npx', ['expo', 'export', '--output-dir', 'dist'], {
|
|
152
|
+
cwd: projectDir,
|
|
153
|
+
env: {
|
|
154
|
+
...process.env,
|
|
155
|
+
EXPO_NO_DOTENV: '1',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
exportSpinner.succeed('Project exported successfully');
|
|
159
|
+
log_1.default.withInfo(stdout);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
exportSpinner.fail('Failed to export the project');
|
|
163
|
+
}
|
|
164
|
+
const publicConfig = await (0, expoConfig_1.getPublicExpoConfigAsync)(projectDir, {
|
|
165
|
+
skipSDKVersionRequirement: true,
|
|
166
|
+
});
|
|
167
|
+
if (!publicConfig) {
|
|
168
|
+
log_1.default.error('Could not find Expo config in this project. Please make sure you have an Expo config.');
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// eslint-disable-next-line
|
|
172
|
+
fs_extra_1.default.writeJsonSync(path_1.default.join(projectDir, 'dist', 'expoConfig.json'), publicConfig, {
|
|
173
|
+
spaces: 2,
|
|
174
|
+
});
|
|
175
|
+
log_1.default.withInfo('expoConfig.json file created in dist directory');
|
|
176
|
+
const uploadFilesSpinner = (0, ora_1.ora)('Uploading files to the server').start();
|
|
177
|
+
const files = (0, assets_1.computeFilesRequests)(projectDir, platform || expoConfig_1.RequestedPlatform.All);
|
|
178
|
+
if (!files.length) {
|
|
179
|
+
uploadFilesSpinner.fail('No files to upload');
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
const uploadUrls = await Promise.all(runtimeVersions.map(runtimeVersion => {
|
|
183
|
+
if (!runtimeVersion) {
|
|
184
|
+
throw new Error('Runtime version is not resolved');
|
|
185
|
+
}
|
|
186
|
+
return (0, assets_1.requestUploadUrls)({
|
|
187
|
+
fileNames: files.map(file => file.path),
|
|
188
|
+
}, `${baseUrl}/requestUploadUrl/${branch}`, credentials, runtimeVersion);
|
|
189
|
+
}));
|
|
190
|
+
const allItems = uploadUrls.flat();
|
|
191
|
+
await Promise.all(allItems.map(async (itm) => {
|
|
192
|
+
const isLocalBucketFileUpload = itm.requestUploadUrl.startsWith(`${baseUrl}/uploadLocalFile`);
|
|
193
|
+
const formData = new form_data_1.default();
|
|
194
|
+
let file;
|
|
195
|
+
try {
|
|
196
|
+
file = fs_extra_1.default.createReadStream(path_1.default.join(projectDir, 'dist', itm.filePath));
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
throw new Error(`Failed to read file ${itm.filePath}`);
|
|
200
|
+
}
|
|
201
|
+
formData.append(itm.fileName, file);
|
|
202
|
+
if (isLocalBucketFileUpload) {
|
|
203
|
+
const response = await (0, node_fetch_1.default)(itm.requestUploadUrl, {
|
|
204
|
+
method: 'PUT',
|
|
205
|
+
headers: {
|
|
206
|
+
...formData.getHeaders(),
|
|
207
|
+
...(0, auth_1.getAuthExpoHeaders)(credentials),
|
|
208
|
+
},
|
|
209
|
+
body: formData,
|
|
210
|
+
});
|
|
211
|
+
if (!response.ok) {
|
|
212
|
+
log_1.default.error('Failed to upload file', await response.text());
|
|
213
|
+
throw new Error('Failed to upload file');
|
|
214
|
+
}
|
|
215
|
+
file.close();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const findFile = files.find(f => f.path === itm.filePath || f.name === itm.fileName);
|
|
219
|
+
if (!findFile) {
|
|
220
|
+
log_1.default.error(`File ${itm.filePath} not found`);
|
|
221
|
+
throw new Error(`File ${itm.filePath} not found`);
|
|
222
|
+
}
|
|
223
|
+
let contentType = mime_1.default.getType(findFile.ext);
|
|
224
|
+
if (!contentType) {
|
|
225
|
+
contentType = 'application/octet-stream';
|
|
226
|
+
}
|
|
227
|
+
const buffer = await fs_extra_1.default.readFile(path_1.default.join(projectDir, 'dist', itm.filePath));
|
|
228
|
+
const response = await (0, node_fetch_1.default)(itm.requestUploadUrl, {
|
|
229
|
+
method: 'PUT',
|
|
230
|
+
headers: {
|
|
231
|
+
'Content-Type': contentType,
|
|
232
|
+
'Cache-Control': 'max-age=31556926',
|
|
233
|
+
},
|
|
234
|
+
body: buffer,
|
|
235
|
+
});
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
log_1.default.error('Failed to upload file', await response.text());
|
|
238
|
+
throw new Error('Failed to upload file');
|
|
239
|
+
}
|
|
240
|
+
file.close();
|
|
241
|
+
}));
|
|
242
|
+
uploadFilesSpinner.succeed('Files uploaded successfully');
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
uploadFilesSpinner.fail('Failed to upload static files');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
exports.default = Publish;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Joi from 'joi';
|
|
2
|
+
import { ExpoCredentials } from './auth';
|
|
3
|
+
import { RequestedPlatform } from './expoConfig';
|
|
4
|
+
export declare const MetadataJoi: Joi.ObjectSchema<any>;
|
|
5
|
+
interface AssetToUpload {
|
|
6
|
+
path: string;
|
|
7
|
+
name: string;
|
|
8
|
+
ext: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function computeFilesRequests(projectDir: string, requestedPlatform: RequestedPlatform): AssetToUpload[];
|
|
11
|
+
export interface RequestUploadUrlItem {
|
|
12
|
+
requestUploadUrl: string;
|
|
13
|
+
fileName: string;
|
|
14
|
+
filePath: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function requestUploadUrls(body: {
|
|
17
|
+
fileNames: string[];
|
|
18
|
+
}, requestUploadUrl: string, auth: ExpoCredentials, runtimeVersion: string): Promise<RequestUploadUrlItem[]>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestUploadUrls = exports.computeFilesRequests = exports.MetadataJoi = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
6
|
+
const joi_1 = tslib_1.__importDefault(require("joi"));
|
|
7
|
+
const node_fetch_1 = tslib_1.__importDefault(require("node-fetch"));
|
|
8
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
9
|
+
const auth_1 = require("./auth");
|
|
10
|
+
const expoConfig_1 = require("./expoConfig");
|
|
11
|
+
const log_1 = tslib_1.__importDefault(require("./log"));
|
|
12
|
+
const fileMetadataJoi = joi_1.default.object({
|
|
13
|
+
assets: joi_1.default.array()
|
|
14
|
+
.required()
|
|
15
|
+
.items(joi_1.default.object({ path: joi_1.default.string().required(), ext: joi_1.default.string().required() })),
|
|
16
|
+
bundle: joi_1.default.string().required(),
|
|
17
|
+
}).optional();
|
|
18
|
+
exports.MetadataJoi = joi_1.default.object({
|
|
19
|
+
version: joi_1.default.number().required(),
|
|
20
|
+
bundler: joi_1.default.string().required(),
|
|
21
|
+
fileMetadata: joi_1.default.object({
|
|
22
|
+
android: fileMetadataJoi,
|
|
23
|
+
ios: fileMetadataJoi,
|
|
24
|
+
web: fileMetadataJoi,
|
|
25
|
+
}).required(),
|
|
26
|
+
}).required();
|
|
27
|
+
function loadMetadata(distRoot) {
|
|
28
|
+
// eslint-disable-next-line
|
|
29
|
+
const fileContent = fs_extra_1.default.readFileSync(path_1.default.join(distRoot, 'metadata.json'), 'utf8');
|
|
30
|
+
let metadata;
|
|
31
|
+
try {
|
|
32
|
+
metadata = JSON.parse(fileContent);
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
log_1.default.error(`Failed to read metadata.json: ${e.message}`);
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
const { error } = exports.MetadataJoi.validate(metadata);
|
|
39
|
+
if (error) {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
// Check version and bundler by hand (instead of with Joi) so
|
|
43
|
+
// more informative error messages can be returned.
|
|
44
|
+
if (metadata.version !== 0) {
|
|
45
|
+
throw new Error('Only bundles with metadata version 0 are supported');
|
|
46
|
+
}
|
|
47
|
+
if (metadata.bundler !== 'metro') {
|
|
48
|
+
throw new Error('Only bundles created with Metro are currently supported');
|
|
49
|
+
}
|
|
50
|
+
const platforms = Object.keys(metadata.fileMetadata);
|
|
51
|
+
if (platforms.length === 0) {
|
|
52
|
+
log_1.default.warn('No updates were exported for any platform');
|
|
53
|
+
}
|
|
54
|
+
log_1.default.debug(`Loaded ${platforms.length} platform(s): ${platforms.join(', ')}`);
|
|
55
|
+
return metadata;
|
|
56
|
+
}
|
|
57
|
+
function computeFilesRequests(projectDir, requestedPlatform) {
|
|
58
|
+
const metadata = loadMetadata(path_1.default.join(projectDir, 'dist'));
|
|
59
|
+
const assets = [
|
|
60
|
+
{ path: 'metadata.json', name: 'metadata.json', ext: 'json' },
|
|
61
|
+
{ path: 'expoConfig.json', name: 'expoConfig.json', ext: 'json' },
|
|
62
|
+
];
|
|
63
|
+
for (const platform of Object.keys(metadata.fileMetadata)) {
|
|
64
|
+
if (requestedPlatform !== expoConfig_1.RequestedPlatform.All && requestedPlatform !== platform) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const bundle = metadata.fileMetadata[platform].bundle;
|
|
68
|
+
assets.push({ path: bundle, name: path_1.default.basename(bundle), ext: 'hbc' });
|
|
69
|
+
for (const asset of metadata.fileMetadata[platform].assets) {
|
|
70
|
+
assets.push({ path: asset.path, name: path_1.default.basename(asset.path), ext: asset.ext });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return assets;
|
|
74
|
+
}
|
|
75
|
+
exports.computeFilesRequests = computeFilesRequests;
|
|
76
|
+
async function requestUploadUrls(body, requestUploadUrl, auth, runtimeVersion) {
|
|
77
|
+
const response = await (0, node_fetch_1.default)(`${requestUploadUrl}?runtimeVersion=${runtimeVersion}`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: {
|
|
80
|
+
...(0, auth_1.getAuthExpoHeaders)(auth),
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify(body),
|
|
84
|
+
});
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error(`Failed to request upload URL`);
|
|
87
|
+
}
|
|
88
|
+
return await response.json();
|
|
89
|
+
}
|
|
90
|
+
exports.requestUploadUrls = requestUploadUrls;
|
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAuthExpoHeaders = exports.retrieveExpoCredentials = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const os_1 = require("os");
|
|
6
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
|
+
function dotExpoHomeDirectory() {
|
|
8
|
+
const home = (0, os_1.homedir)();
|
|
9
|
+
if (!home) {
|
|
10
|
+
throw new Error("Can't determine your home directory; make sure your $HOME environment variable is set.");
|
|
11
|
+
}
|
|
12
|
+
let dirPath;
|
|
13
|
+
if (process.env.EXPO_STAGING) {
|
|
14
|
+
dirPath = path_1.default.join(home, '.expo-staging');
|
|
15
|
+
}
|
|
16
|
+
else if (process.env.EXPO_LOCAL) {
|
|
17
|
+
dirPath = path_1.default.join(home, '.expo-local');
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
dirPath = path_1.default.join(home, '.expo');
|
|
21
|
+
}
|
|
22
|
+
return dirPath;
|
|
23
|
+
}
|
|
24
|
+
function getStateJsonPath() {
|
|
25
|
+
return path_1.default.join(dotExpoHomeDirectory(), 'state.json');
|
|
26
|
+
}
|
|
27
|
+
function getExpoSessionData() {
|
|
28
|
+
try {
|
|
29
|
+
const stateJsonPath = getStateJsonPath();
|
|
30
|
+
const stateJson = require(stateJsonPath);
|
|
31
|
+
return stateJson['auth'] || null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function retrieveExpoCredentials() {
|
|
38
|
+
const token = process.env.EXPO_TOKEN;
|
|
39
|
+
const sessionData = getExpoSessionData();
|
|
40
|
+
const sessionSecret = sessionData?.sessionSecret;
|
|
41
|
+
return { token, sessionSecret };
|
|
42
|
+
}
|
|
43
|
+
exports.retrieveExpoCredentials = retrieveExpoCredentials;
|
|
44
|
+
function getAuthExpoHeaders(credentials) {
|
|
45
|
+
if (credentials.token) {
|
|
46
|
+
return {
|
|
47
|
+
Authorization: `Bearer ${credentials.token}`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (credentials.sessionSecret) {
|
|
51
|
+
return {
|
|
52
|
+
'expo-session': credentials.sessionSecret,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
exports.getAuthExpoHeaders = getAuthExpoHeaders;
|