feat(expo): add submit executor (#17372)

This commit is contained in:
Emily Xiong 2023-06-05 10:59:01 -04:00 committed by GitHub
parent 2d76993e68
commit 1bc7965278
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 484 additions and 46 deletions

View File

@ -4808,6 +4808,14 @@
"children": [], "children": [],
"isExternal": false, "isExternal": false,
"disableCollapsible": false "disableCollapsible": false
},
{
"id": "submit",
"path": "/packages/expo/executors/submit",
"name": "submit",
"children": [],
"isExternal": false,
"disableCollapsible": false
} }
], ],
"isExternal": false, "isExternal": false,

View File

@ -817,6 +817,15 @@
"originalFilePath": "/packages/expo/src/executors/export/schema.json", "originalFilePath": "/packages/expo/src/executors/export/schema.json",
"path": "/packages/expo/executors/export", "path": "/packages/expo/executors/export",
"type": "executor" "type": "executor"
},
"/packages/expo/executors/submit": {
"description": "Submit app binary to App Store and/or Play Store",
"file": "generated/packages/expo/executors/submit.json",
"hidden": false,
"name": "submit",
"originalFilePath": "/packages/expo/src/executors/submit/schema.json",
"path": "/packages/expo/executors/submit",
"type": "executor"
} }
}, },
"generators": { "generators": {

View File

@ -804,6 +804,15 @@
"originalFilePath": "/packages/expo/src/executors/export/schema.json", "originalFilePath": "/packages/expo/src/executors/export/schema.json",
"path": "expo/executors/export", "path": "expo/executors/export",
"type": "executor" "type": "executor"
},
{
"description": "Submit app binary to App Store and/or Play Store",
"file": "generated/packages/expo/executors/submit.json",
"hidden": false,
"name": "submit",
"originalFilePath": "/packages/expo/src/executors/submit/schema.json",
"path": "expo/executors/submit",
"type": "executor"
} }
], ],
"generators": [ "generators": [

View File

@ -10,6 +10,12 @@
"title": "Expo EAS Build executor", "title": "Expo EAS Build executor",
"description": "Start an EAS build for your expo project.", "description": "Start an EAS build for your expo project.",
"type": "object", "type": "object",
"presets": [
{ "name": "Build for a specific platform", "keys": ["platform"] },
{ "name": "Build using a specific profile", "keys": ["profile"] },
{ "name": "Run build locally", "keys": ["local"] },
{ "name": "Clear cache before the build", "keys": ["clearCache"] }
],
"properties": { "properties": {
"platform": { "platform": {
"enum": ["ios", "android", "all"], "enum": ["ios", "android", "all"],
@ -28,10 +34,10 @@
"examples": ["production", "development", "preview"], "examples": ["production", "development", "preview"],
"x-priority": "important" "x-priority": "important"
}, },
"nonInteractive": { "interactive": {
"type": "boolean", "type": "boolean",
"description": "Run command in non-interactive mode", "description": "Run command in interactive mode",
"default": false "default": true
}, },
"local": { "local": {
"type": "boolean", "type": "boolean",
@ -63,8 +69,7 @@
"examples": ["production", "development", "preview"] "examples": ["production", "development", "preview"]
} }
}, },
"required": [], "required": []
"presets": []
}, },
"description": "Start an EAS build for your expo project", "description": "Start an EAS build for your expo project",
"aliases": [], "aliases": [],

View File

@ -19,7 +19,7 @@
"install": { "install": {
"type": "boolean", "type": "boolean",
"description": "Installing npm packages and CocoaPods.", "description": "Installing npm packages and CocoaPods.",
"default": false, "default": true,
"x-priority": "internal" "x-priority": "internal"
}, },
"platform": { "platform": {

View File

@ -56,7 +56,7 @@
}, },
"install": { "install": {
"type": "boolean", "type": "boolean",
"description": "Should install missing dependencies before building.", "description": "Installing npm packages and CocoaPods before building.",
"default": true "default": true
}, },
"buildCache": { "buildCache": {

View File

@ -84,6 +84,11 @@
"offline": { "offline": {
"type": "boolean", "type": "boolean",
"description": "Allows this command to run while offline" "description": "Allows this command to run while offline"
},
"sync": {
"type": "boolean",
"description": "Syncs npm dependencies to package.json (for React Native autolink).",
"default": true
} }
}, },
"examplesFile": "`project.json`:\n\n```json\n{\n \"name\": \"mobile\",\n //...\n \"targets\": {\n //...\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081\n }\n }\n //...\n }\n}\n```\n\n```shell\nnx run mobile:start\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Specify starting on platform\" %}\nThe `ios`, `android` and `web` option allows you to start the server on different platforms.\n\nOpens your app in Expo Go in a currently running iOS simulator on your computer:\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"ios\": true\n }\n }\n```\n\nOpens your app in Expo Go on a connected Android device\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"android\": true\n }\n }\n```\n\nOpens your app in a web browser:\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"web\": true\n }\n }\n```\n\n{% /tab %}\n{% tab label=\"Specify the host\" %}\nThe `host` option allows you to specify the type of host to use. `lan` uses the local network; `tunnel` ues any network by tunnel through ngrok; `localhost` connects to the dev server over localhost.\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"host\": \"localhost\"\n }\n }\n```\n\n{% /tab %}\n{% tab label=\"Starts the server with cache reset\" %}\n\nThe `clear` option allows you to remove Metro bundler cache.\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"clear\": true\n }\n }\n```\n\n{% /tab %}\n{% /tabs %}\n\n---\n", "examplesFile": "`project.json`:\n\n```json\n{\n \"name\": \"mobile\",\n //...\n \"targets\": {\n //...\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081\n }\n }\n //...\n }\n}\n```\n\n```shell\nnx run mobile:start\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Specify starting on platform\" %}\nThe `ios`, `android` and `web` option allows you to start the server on different platforms.\n\nOpens your app in Expo Go in a currently running iOS simulator on your computer:\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"ios\": true\n }\n }\n```\n\nOpens your app in Expo Go on a connected Android device\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"android\": true\n }\n }\n```\n\nOpens your app in a web browser:\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"web\": true\n }\n }\n```\n\n{% /tab %}\n{% tab label=\"Specify the host\" %}\nThe `host` option allows you to specify the type of host to use. `lan` uses the local network; `tunnel` ues any network by tunnel through ngrok; `localhost` connects to the dev server over localhost.\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"host\": \"localhost\"\n }\n }\n```\n\n{% /tab %}\n{% tab label=\"Starts the server with cache reset\" %}\n\nThe `clear` option allows you to remove Metro bundler cache.\n\n```json\n \"start\": {\n \"executor\": \"@nx/expo:start\",\n \"options\": {\n \"port\": 8081,\n \"clear\": true\n }\n }\n```\n\n{% /tab %}\n{% /tabs %}\n\n---\n",

View File

@ -0,0 +1,57 @@
{
"name": "submit",
"implementation": "/packages/expo/src/executors/submit/submit.impl.ts",
"schema": {
"$schema": "http://json-schema.org/schema",
"version": 2,
"title": "EXPO EAS Submit Executor",
"description": "Submit app binary to App Store and/or Play Store.",
"type": "object",
"presets": [
{ "name": "Submit for a specific platform", "keys": ["platform"] },
{ "name": "Submit using a specific profile", "keys": ["profile"] }
],
"properties": {
"profile": {
"type": "string",
"description": "Name of the build profile from eas.json. Defaults to \"production\" if defined in eas.json.",
"examples": ["production", "development", "preview"],
"x-priority": "important"
},
"platform": {
"enum": ["ios", "android", "all"],
"alias": "p",
"description": "The platform to build the app, example values: ios, android, all.",
"x-priority": "important"
},
"id": { "type": "string", "description": "Build ID to submit" },
"path": {
"type": "string",
"description": "Path to the .apk/.aab/.ipa file"
},
"url": {
"type": "string",
"description": "URL to the .apk/.aab/.ipa file, app archive url"
},
"latest": {
"type": "boolean",
"description": "Submit the latest build for specified platform"
},
"interactive": {
"type": "boolean",
"description": "Run command in interactive mode",
"default": true
},
"wait": {
"type": "boolean",
"description": "Wait for build(s) to complete",
"default": true
}
}
},
"description": "Submit app binary to App Store and/or Play Store",
"aliases": [],
"hidden": false,
"path": "/packages/expo/src/executors/submit/schema.json",
"type": "executor"
}

View File

@ -10,6 +10,10 @@
"title": "Expo EAS Update executor", "title": "Expo EAS Update executor",
"description": "Start an EAS update for your expo project.", "description": "Start an EAS update for your expo project.",
"type": "object", "type": "object",
"presets": [
{ "name": "Update for a specific platform", "keys": ["platform"] },
{ "name": "Update from a specific branch", "keys": ["branch"] }
],
"properties": { "properties": {
"branch": { "branch": {
"type": "string", "type": "string",
@ -52,14 +56,13 @@
"type": "string", "type": "string",
"description": "File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named \"private-key.pem\" in the certificate's directory." "description": "File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named \"private-key.pem\" in the certificate's directory."
}, },
"nonInteractive": { "interactive": {
"type": "boolean", "type": "boolean",
"description": "Run command in non-interactive mode", "description": "Run command in interactive mode",
"default": false "default": true
} }
}, },
"required": [], "required": []
"presets": []
}, },
"description": "Start an EAS update for your expo project", "description": "Start an EAS update for your expo project",
"aliases": [], "aliases": [],

View File

@ -54,6 +54,11 @@
"implementation": "./src/executors/export/export.impl", "implementation": "./src/executors/export/export.impl",
"schema": "./src/executors/export/schema.json", "schema": "./src/executors/export/schema.json",
"description": "Export the JavaScript and assets for your app using Metro/webpack bundler" "description": "Export the JavaScript and assets for your app using Metro/webpack bundler"
},
"submit": {
"implementation": "./src/executors/submit/submit.impl",
"schema": "./src/executors/submit/schema.json",
"description": "Submit app binary to App Store and/or Play Store"
} }
}, },
"builders": { "builders": {
@ -111,6 +116,11 @@
"implementation": "./src/executors/export/compat", "implementation": "./src/executors/export/compat",
"schema": "./src/executors/export/schema.json", "schema": "./src/executors/export/schema.json",
"description": "Export the JavaScript and assets for your app using Metro/webpack bundler" "description": "Export the JavaScript and assets for your app using Metro/webpack bundler"
},
"submit": {
"implementation": "./src/executors/submit/compat",
"schema": "./src/executors/submit/schema.json",
"description": "Submit app binary to App Store and/or Play Store"
} }
} }
} }

View File

@ -817,6 +817,27 @@
"alwaysAddToPackageJson": false "alwaysAddToPackageJson": false
} }
} }
},
"16.2.2": {
"version": "16.2.2-beta.0",
"packages": {
"expo": {
"version": "^48.0.17",
"alwaysAddToPackageJson": false
},
"eas-cli": {
"version": "~3.13.2",
"alwaysAddToPackageJson": false
},
"react-native": {
"version": "0.71.8",
"alwaysAddToPackageJson": false
},
"@types/react-native": {
"version": "0.71.7",
"alwaysAddToPackageJson": false
}
}
} }
} }
} }

View File

@ -43,7 +43,7 @@
"@nx/webpack": "file:../webpack" "@nx/webpack": "file:../webpack"
}, },
"peerDependencies": { "peerDependencies": {
"expo": "^48.0.16" "expo": "^48.0.17"
}, },
"builders": "./executors.json", "builders": "./executors.json",
"ng-update": { "ng-update": {

View File

@ -81,6 +81,11 @@ function createBuildOptions(options: ExpoEasBuildOptions) {
return Object.keys(options).reduce((acc, k) => { return Object.keys(options).reduce((acc, k) => {
const v = options[k]; const v = options[k];
if (typeof v === 'boolean') { if (typeof v === 'boolean') {
if (k === 'interactive') {
if (v === false) {
acc.push('--non-interactive'); // when is false, the flag is --non-interactive
}
}
if (v === true) { if (v === true) {
// when true, does not need to pass the value true, just need to pass the flag in kebob case // when true, does not need to pass the value true, just need to pass the flag in kebob case
acc.push(`--${names(k).fileName}`); acc.push(`--${names(k).fileName}`);

View File

@ -3,7 +3,7 @@
export interface ExpoEasBuildOptions { export interface ExpoEasBuildOptions {
platform: 'ios' | 'android' | 'all'; platform: 'ios' | 'android' | 'all';
profile?: string; profile?: string;
nonInteractive: boolean; // default is false interactive: boolean; // default is true
local: boolean; // default is false local: boolean; // default is false
output?: string; output?: string;
wait: boolean; // default is true wait: boolean; // default is true

View File

@ -7,6 +7,24 @@
"title": "Expo EAS Build executor", "title": "Expo EAS Build executor",
"description": "Start an EAS build for your expo project.", "description": "Start an EAS build for your expo project.",
"type": "object", "type": "object",
"presets": [
{
"name": "Build for a specific platform",
"keys": ["platform"]
},
{
"name": "Build using a specific profile",
"keys": ["profile"]
},
{
"name": "Run build locally",
"keys": ["local"]
},
{
"name": "Clear cache before the build",
"keys": ["clearCache"]
}
],
"properties": { "properties": {
"platform": { "platform": {
"enum": ["ios", "android", "all"], "enum": ["ios", "android", "all"],
@ -25,10 +43,10 @@
"examples": ["production", "development", "preview"], "examples": ["production", "development", "preview"],
"x-priority": "important" "x-priority": "important"
}, },
"nonInteractive": { "interactive": {
"type": "boolean", "type": "boolean",
"description": "Run command in non-interactive mode", "description": "Run command in interactive mode",
"default": false "default": true
}, },
"local": { "local": {
"type": "boolean", "type": "boolean",

View File

@ -25,12 +25,10 @@ export default async function* prebuildExecutor(
await prebuildAsync(context.root, projectRoot, options); await prebuildAsync(context.root, projectRoot, options);
if (options.install) { if (options.install) {
await installAsync(context.root, { fix: true }); await installAsync(context.root, {});
if (options.platform === 'ios') { if (options.platform === 'ios') {
await podInstall(join(context.root, projectRoot, 'ios')); await podInstall(join(context.root, projectRoot, 'ios'));
} }
} else {
await installAsync(context.root, {});
} }
yield { yield {

View File

@ -16,7 +16,7 @@
"install": { "install": {
"type": "boolean", "type": "boolean",
"description": "Installing npm packages and CocoaPods.", "description": "Installing npm packages and CocoaPods.",
"default": false, "default": true,
"x-priority": "internal" "x-priority": "internal"
}, },
"platform": { "platform": {

View File

@ -49,17 +49,12 @@ export default async function* runExecutor(
clean: options.clean, clean: options.clean,
}); });
} }
if (options.install) { if (options.install) {
await installAsync(context.root, { await installAsync(context.root, {});
fix: true,
});
if (options.platform === 'ios') { if (options.platform === 'ios') {
await podInstall(join(context.root, projectRoot, 'ios')); await podInstall(join(context.root, projectRoot, 'ios'));
} }
} else {
await installAsync(context.root, {
check: true,
});
} }
try { try {

View File

@ -53,7 +53,7 @@
}, },
"install": { "install": {
"type": "boolean", "type": "boolean",
"description": "Should install missing dependencies before building.", "description": "Installing npm packages and CocoaPods before building.",
"default": true "default": true
}, },
"buildCache": { "buildCache": {

View File

@ -19,4 +19,7 @@ export interface ExpoStartOptions {
localhost?: boolean; localhost?: boolean;
tunnel?: boolean; tunnel?: boolean;
offline?: boolean; offline?: boolean;
// nx options
sync?: boolean; // default is true
} }

View File

@ -87,6 +87,11 @@
"offline": { "offline": {
"type": "boolean", "type": "boolean",
"description": "Allows this command to run while offline" "description": "Allows this command to run while offline"
},
"sync": {
"type": "boolean",
"description": "Syncs npm dependencies to package.json (for React Native autolink).",
"default": true
} }
}, },
"examplesFile": "../../../docs/start-examples.md" "examplesFile": "../../../docs/start-examples.md"

View File

@ -5,6 +5,10 @@ import { join } from 'path';
import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink'; import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink';
import { ExpoStartOptions } from './schema'; import { ExpoStartOptions } from './schema';
import {
displayNewlyAddedDepsMessage,
syncDeps,
} from '../sync-deps/sync-deps.impl';
export interface ExpoStartOutput { export interface ExpoStartOutput {
baseUrl?: string; baseUrl?: string;
@ -20,6 +24,17 @@ export default async function* startExecutor(
const projectRoot = const projectRoot =
context.projectsConfigurations.projects[context.projectName].root; context.projectsConfigurations.projects[context.projectName].root;
ensureNodeModulesSymlink(context.root, projectRoot); ensureNodeModulesSymlink(context.root, projectRoot);
if (options.sync) {
displayNewlyAddedDepsMessage(
context.projectName,
await syncDeps(
context.projectName,
projectRoot,
context.root,
context.projectGraph
)
);
}
try { try {
const baseUrl = `http://localhost:${options.port}`; const baseUrl = `http://localhost:${options.port}`;
@ -68,12 +83,16 @@ function startAsync(
} }
// options from https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/start/index.ts // options from https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/start/index.ts
const nxOptions = ['sync'];
function createStartOptions(options: ExpoStartOptions) { function createStartOptions(options: ExpoStartOptions) {
return Object.keys(options).reduce((acc, k) => { return Object.keys(options).reduce((acc, k) => {
if (nxOptions.includes(k)) {
return acc;
}
const v = options[k]; const v = options[k];
if (k === 'dev') { if (k === 'dev') {
if (v === false) { if (v === false) {
acc.push(`--no-dev`); acc.push(`--no-dev`); // only no-dev flag is supported
} }
} else { } else {
if (typeof v === 'boolean') { if (typeof v === 'boolean') {

View File

@ -0,0 +1,5 @@
import { convertNxExecutor } from '@nx/devkit';
import submitExecutor from './submit.impl';
export default convertNxExecutor(submitExecutor);

View File

@ -0,0 +1,12 @@
// command to run https://github.com/expo/eas-cli/tree/main#eas-submit
// options from https://github.com/expo/eas-cli/blob/main/packages/eas-cli/src/commands/submit.ts
export interface SubmitExecutorSchema {
profile?: string;
platform?: 'ios' | 'android' | 'all';
id?: string;
latest?: boolean;
interactive: boolean; // default is true
path?: string;
url?: string;
wait: boolean; // default is true
}

View File

@ -0,0 +1,57 @@
{
"$schema": "http://json-schema.org/schema",
"version": 2,
"title": "EXPO EAS Submit Executor",
"description": "Submit app binary to App Store and/or Play Store.",
"type": "object",
"presets": [
{
"name": "Submit for a specific platform",
"keys": ["platform"]
},
{
"name": "Submit using a specific profile",
"keys": ["profile"]
}
],
"properties": {
"profile": {
"type": "string",
"description": "Name of the build profile from eas.json. Defaults to \"production\" if defined in eas.json.",
"examples": ["production", "development", "preview"],
"x-priority": "important"
},
"platform": {
"enum": ["ios", "android", "all"],
"alias": "p",
"description": "The platform to build the app, example values: ios, android, all.",
"x-priority": "important"
},
"id": {
"type": "string",
"description": "Build ID to submit"
},
"path": {
"type": "string",
"description": "Path to the .apk/.aab/.ipa file"
},
"url": {
"type": "string",
"description": "URL to the .apk/.aab/.ipa file, app archive url"
},
"latest": {
"type": "boolean",
"description": "Submit the latest build for specified platform"
},
"interactive": {
"type": "boolean",
"description": "Run command in interactive mode",
"default": true
},
"wait": {
"type": "boolean",
"description": "Wait for build(s) to complete",
"default": true
}
}
}

View File

@ -0,0 +1,89 @@
import { ExecutorContext, names } from '@nx/devkit';
import { join } from 'path';
import { ChildProcess, fork } from 'child_process';
import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink';
import { SubmitExecutorSchema } from './schema';
export interface ReactNativeSubmitOutput {
success: boolean;
}
let childProcess: ChildProcess;
export default async function* submitExecutor(
options: SubmitExecutorSchema,
context: ExecutorContext
): AsyncGenerator<ReactNativeSubmitOutput> {
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
ensureNodeModulesSymlink(context.root, projectRoot);
try {
await runCliSubmit(context.root, projectRoot, options);
yield { success: true };
} finally {
if (childProcess) {
childProcess.kill();
}
}
}
function runCliSubmit(
workspaceRoot: string,
projectRoot: string,
options: SubmitExecutorSchema
) {
return new Promise((resolve, reject) => {
childProcess = fork(
join(workspaceRoot, './node_modules/eas-cli/bin/run'),
['submit', ...createSubmitOptions(options)],
{
cwd: join(workspaceRoot, projectRoot),
env: process.env,
}
);
// Ensure the child process is killed when the parent exits
process.on('exit', () => childProcess.kill());
process.on('SIGTERM', () => childProcess.kill());
childProcess.on('error', (err) => {
reject(err);
});
childProcess.on('exit', (code) => {
if (code === 0) {
resolve(code);
} else {
reject(code);
}
});
});
}
function createSubmitOptions(options: SubmitExecutorSchema) {
return Object.keys(options).reduce((acc, k) => {
const v = options[k];
if (typeof v === 'boolean') {
if (k === 'interactive') {
if (v === false) {
acc.push('--non-interactive'); // when is false, the flag is --non-interactive
}
} else if (k === 'wait') {
if (v === false) {
acc.push('--no-wait'); // when is false, the flag is --no-wait
} else {
acc.push('--wait');
}
} else if (v === true) {
// when true, does not need to pass the value true, just need to pass the flag in kebob case
acc.push(`--${names(k).fileName}`);
}
} else {
acc.push(`--${names(k).fileName}`, v);
}
return acc;
}, []);
}

View File

@ -11,5 +11,5 @@ export interface ExpoEasUpdateOptions {
json: boolean; // default is false json: boolean; // default is false
auto: boolean; // default is false auto: boolean; // default is false
privateKeyPath?: string; privateKeyPath?: string;
nonInteractive: boolean; // default is false interactive: boolean; // default is false
} }

View File

@ -7,6 +7,16 @@
"title": "Expo EAS Update executor", "title": "Expo EAS Update executor",
"description": "Start an EAS update for your expo project.", "description": "Start an EAS update for your expo project.",
"type": "object", "type": "object",
"presets": [
{
"name": "Update for a specific platform",
"keys": ["platform"]
},
{
"name": "Update from a specific branch",
"keys": ["branch"]
}
],
"properties": { "properties": {
"branch": { "branch": {
"type": "string", "type": "string",
@ -55,10 +65,10 @@
"type": "string", "type": "string",
"description": "File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named \"private-key.pem\" in the certificate's directory." "description": "File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named \"private-key.pem\" in the certificate's directory."
}, },
"nonInteractive": { "interactive": {
"type": "boolean", "type": "boolean",
"description": "Run command in non-interactive mode", "description": "Run command in interactive mode",
"default": false "default": true
} }
}, },
"required": [] "required": []

View File

@ -5,6 +5,11 @@ import { ChildProcess, fork } from 'child_process';
import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink'; import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink';
import { ExpoEasUpdateOptions } from './schema'; import { ExpoEasUpdateOptions } from './schema';
import {
displayNewlyAddedDepsMessage,
syncDeps,
} from '../sync-deps/sync-deps.impl';
import { installAsync } from '../install/install.impl';
export interface ReactNativeUpdateOutput { export interface ReactNativeUpdateOutput {
success: boolean; success: boolean;
@ -18,6 +23,17 @@ export default async function* buildExecutor(
): AsyncGenerator<ReactNativeUpdateOutput> { ): AsyncGenerator<ReactNativeUpdateOutput> {
const projectRoot = const projectRoot =
context.projectsConfigurations.projects[context.projectName].root; context.projectsConfigurations.projects[context.projectName].root;
await installAsync(context.root, { packages: ['expo-updates'] });
displayNewlyAddedDepsMessage(
context.projectName,
await syncDeps(
context.projectName,
projectRoot,
context.root,
context.projectGraph,
['expo-updates']
)
);
ensureNodeModulesSymlink(context.root, projectRoot); ensureNodeModulesSymlink(context.root, projectRoot);
try { try {
@ -63,7 +79,11 @@ function createUpdateOptions(options: ExpoEasUpdateOptions) {
return Object.keys(options).reduce((acc, k) => { return Object.keys(options).reduce((acc, k) => {
const v = options[k]; const v = options[k];
if (typeof v === 'boolean') { if (typeof v === 'boolean') {
if (v === true) { if (k === 'interactive') {
if (v === false) {
acc.push('--non-interactive');
}
} else if (v === true) {
// when true, does not need to pass the value true, just need to pass the flag in kebob case // when true, does not need to pass the value true, just need to pass the flag in kebob case
acc.push(`--${names(k).fileName}`); acc.push(`--${names(k).fileName}`);
} }

View File

@ -60,6 +60,11 @@ function getTargets(options: NormalizedSchema) {
options: {}, options: {},
}; };
architect['submit'] = {
executor: '@nx/expo:submit',
options: {},
};
architect['build-list'] = { architect['build-list'] = {
executor: '@nx/expo:build-list', executor: '@nx/expo:build-list',
options: {}, options: {},

View File

@ -12,7 +12,10 @@ export default async function update(tree: Tree) {
const projects = getProjects(tree); const projects = getProjects(tree);
for (const [name, config] of projects.entries()) { for (const [name, config] of projects.entries()) {
if (config.targets?.['start']?.executor === '@nrwl/expo:start') { if (
config.targets?.['start']?.executor === '@nrwl/expo:start' ||
config.targets?.['start']?.executor === '@nx/expo:start'
) {
const jestConfigPath = config.targets?.test?.options?.jestConfig; const jestConfigPath = config.targets?.test?.options?.jestConfig;
if (!jestConfigPath || !tree.exists(jestConfigPath)) return; if (!jestConfigPath || !tree.exists(jestConfigPath)) return;
try { try {

View File

@ -12,7 +12,10 @@ export default async function update(tree: Tree) {
const projects = getProjects(tree); const projects = getProjects(tree);
for (const [name, config] of projects.entries()) { for (const [name, config] of projects.entries()) {
if (config.targets?.['start']?.executor === '@nrwl/expo:start') { if (
config.targets?.['start']?.executor === '@nrwl/expo:start' ||
config.targets?.['start']?.executor === '@nx/expo:start'
) {
const targetsToDelete = [ const targetsToDelete = [
'build-ios', 'build-ios',
'build-android', 'build-android',
@ -28,9 +31,8 @@ export default async function update(tree: Tree) {
delete config.targets[target]; delete config.targets[target];
} }
}); });
updateProjectConfiguration(tree, name, config);
} }
updateProjectConfiguration(tree, name, config);
} }
await formatFiles(tree); await formatFiles(tree);

View File

@ -7,7 +7,10 @@ export default async function update(tree: Tree) {
const projects = getProjects(tree); const projects = getProjects(tree);
projects.forEach((config) => { projects.forEach((config) => {
if (config.targets?.['start']?.executor === '@nrwl/expo:start') { if (
config.targets?.['start']?.executor === '@nrwl/expo:start' ||
config.targets?.['start']?.executor === '@nx/expo:start'
) {
updateJson(tree, `${config.root}/app.json`, (json) => { updateJson(tree, `${config.root}/app.json`, (json) => {
if (!json.expo.plugins) { if (!json.expo.plugins) {
json.expo.plugins = []; json.expo.plugins = [];

View File

@ -22,7 +22,10 @@ export default function update(tree: Tree) {
}; };
for (const [name, config] of projects.entries()) { for (const [name, config] of projects.entries()) {
if (config.targets?.['start']?.executor === '@nrwl/expo:start') { if (
config.targets?.['start']?.executor === '@nrwl/expo:start' ||
config.targets?.['start']?.executor === '@nx/expo:start'
) {
try { try {
addEasScripts(tree); addEasScripts(tree);
updateJson(tree, join(config.root, 'package.json'), (packageJson) => { updateJson(tree, join(config.root, 'package.json'), (packageJson) => {

View File

@ -0,0 +1,31 @@
import { addProjectConfiguration, getProjects, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import update from './add-submit-target';
describe('add-submit-target', () => {
let tree: Tree;
beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
addProjectConfiguration(tree, 'product', {
root: 'apps/product',
sourceRoot: 'apps/product/src',
targets: {
start: {
executor: '@nx/expo:start',
},
},
});
});
it(`should update project.json with target submit`, async () => {
await update(tree);
getProjects(tree).forEach((project) => {
expect(project.targets['submit']).toEqual({
executor: '@nx/expo:submit',
options: {},
});
});
});
});

View File

@ -0,0 +1,27 @@
import {
Tree,
formatFiles,
getProjects,
updateProjectConfiguration,
} from '@nx/devkit';
/**
* Add new submit target
*/
export default async function update(tree: Tree) {
const projects = getProjects(tree);
for (const [name, config] of projects.entries()) {
if (config.targets?.['start']?.executor === '@nx/expo:start') {
if (!config.targets['submit']) {
config.targets['submit'] = {
executor: '@nx/expo:submit',
options: {},
};
updateProjectConfiguration(tree, name, config);
}
}
}
await formatFiles(tree);
}

View File

@ -1,11 +1,12 @@
export const nxVersion = require('../../package.json').version; export const nxVersion = require('../../package.json').version;
export const expoVersion = '48.0.16'; export const expoVersion = '48.0.17';
export const expoMetroConfigVersion = '0.7.1'; export const expoMetroConfigVersion = '0.7.1';
export const expoSplashScreenVersion = '~0.18.2'; export const expoSplashScreenVersion = '~0.18.2';
export const expoStatusBarVersion = '~1.4.4'; export const expoStatusBarVersion = '~1.4.4';
export const expoUpdatesVersion = '~0.16.4';
export const expoCliVersion = '~0.7.1'; // @expo/cli export const expoCliVersion = '~0.7.1'; // @expo/cli
export const easCliVersion = '~3.12.0'; export const easCliVersion = '~3.13.2';
export const babelPresetExpoVersion = '~9.3.2'; export const babelPresetExpoVersion = '~9.3.2';
export const reactVersion = '18.2.0'; export const reactVersion = '18.2.0';
@ -13,8 +14,8 @@ export const reactDomVersion = '18.2.0';
export const reactTestRendererVersion = '18.2.0'; export const reactTestRendererVersion = '18.2.0';
export const typesReactVersion = '18.0.28'; export const typesReactVersion = '18.0.28';
export const reactNativeVersion = '0.71.7'; export const reactNativeVersion = '0.71.8';
export const typesReactNativeVersion = '0.71.6'; export const typesReactNativeVersion = '0.71.7';
export const reactNativeWebVersion = '~0.18.12'; export const reactNativeWebVersion = '~0.18.12';
export const reactNativeSvgTransformerVersion = '1.0.0'; export const reactNativeSvgTransformerVersion = '1.0.0';

View File

@ -1,4 +1,4 @@
import { ExecutorContext, logger, names } from '@nx/devkit'; import { ExecutorContext } from '@nx/devkit';
import { join } from 'path'; import { join } from 'path';
import { ChildProcess, fork } from 'child_process'; import { ChildProcess, fork } from 'child_process';
import { platform } from 'os'; import { platform } from 'os';