Skip to content

Commit

Permalink
fix(preview): allow multiple app.json schemes (#244)
Browse files Browse the repository at this point in the history
* ci: add multiple schemes to the e2e tests of preview qr codes

* refactor: update to `@expo/config@8`

* fix(preview): allow multiple app schemes in `app.json` and add `app-scheme` as option

* chore: rebuild files

* ci: add test using custom app scheme in preview action
  • Loading branch information
byCedric authored Nov 24, 2023
1 parent 65ee055 commit 42d64cf
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 23 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ jobs:
run: |
eas init --id ${{ secrets.EXPO_PROJECT_ID }} --force --non-interactive
echo $(jq '.expo.runtimeVersion.policy = "sdkVersion"' app.json) > app.json
echo $(jq '.expo.scheme = ["expogithubaction", "ega"]' app.json) > app.json
- name: 🚀 Create preview (without comment)
if: ${{ env.hasAuth == 'true' }}
Expand Down Expand Up @@ -164,6 +165,29 @@ jobs:
throw new Error('Message output is empty');
}
- name: 🚀 Create preview (with custom app scheme)
if: ${{ env.hasAuth == 'true' }}
uses: ./preview
id: custom-app-scheme
env:
EXPO_TEST_GITHUB_PULL: 206
with:
app-scheme: thisisacustomscheme
working-directory: ./temp
command: eas update --branch test --message "This is a test"
comment: false

- name: 🧪 Comment has custom app scheme
if: ${{ env.hasAuth == 'true' }}
uses: actions/github-script@v7
with:
script: |
const comment = `${{ steps.custom-app-scheme.outputs.comment }}`
if (!comment || !comment.includes('thisisacustomscheme')) {
console.log({ comment });
throw new Error('Message output does not include the app scheme from action input');
}
preview-comment:
runs-on: ubuntu-latest
steps:
Expand Down
32 changes: 25 additions & 7 deletions build/preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41838,7 +41838,7 @@ try {
"use strict";

Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.createSummary = exports.getVariables = exports.previewAction = exports.previewInput = exports.MESSAGE_ID = void 0;
exports.createSummary = exports.getSchemeFromConfig = exports.getVariables = exports.previewAction = exports.previewInput = exports.MESSAGE_ID = void 0;
const core_1 = __nccwpck_require__(2186);
const eas_1 = __nccwpck_require__(3251);
const github_1 = __nccwpck_require__(978);
Expand All @@ -41853,6 +41853,7 @@ function previewInput() {
commentId: (0, core_1.getInput)('comment-id') || exports.MESSAGE_ID,
workingDirectory: (0, core_1.getInput)('working-directory'),
githubToken: (0, core_1.getInput)('github-token'),
appScheme: (0, core_1.getInput)('app-scheme'),
};
}
exports.previewInput = previewInput;
Expand All @@ -41874,7 +41875,7 @@ async function previewAction(input = previewInput()) {
if (!config.extra?.eas?.projectId) {
return (0, core_1.setFailed)(`Missing 'extra.eas.projectId' in app.json or app.config.js.`);
}
const variables = getVariables(config, updates);
const variables = getVariables(config, updates, input);
const messageId = (0, utils_1.template)(input.commentId, variables);
const messageBody = createSummary(updates, variables);
if (!input.shouldComment) {
Expand Down Expand Up @@ -41922,21 +41923,22 @@ function sanitizeCommand(input) {
/**
* Generate useful variables for the message body, and as step outputs.
*/
function getVariables(config, updates) {
function getVariables(config, updates, input) {
const projectId = config.extra?.eas?.projectId;
const android = updates.find(update => update.platform === 'android');
const ios = updates.find(update => update.platform === 'ios');
const appScheme = input.appScheme || getSchemeFromConfig(config) || '';
return {
// EAS / Expo specific
projectId,
projectName: config.name,
projectSlug: config.slug,
projectScheme: config.scheme || '',
projectScheme: appScheme,
// Shared update properties
// Note, only use these properties when the update groups are identical
groupId: updates[0].group,
runtimeVersion: updates[0].runtimeVersion,
qr: (0, eas_1.getUpdateGroupQr)({ projectId, updateGroupId: updates[0].group, appScheme: config.scheme }),
qr: (0, eas_1.getUpdateGroupQr)({ projectId, updateGroupId: updates[0].group, appScheme }),
link: (0, eas_1.getUpdateGroupWebsite)({ projectId, updateGroupId: updates[0].group }),
// These are safe to access regardless of the update groups
branchName: updates[0].branch,
Expand All @@ -41949,19 +41951,35 @@ function getVariables(config, updates) {
androidBranchName: android?.branch || '',
androidMessage: android?.message || '',
androidRuntimeVersion: android?.runtimeVersion || '',
androidQR: android ? (0, eas_1.getUpdateGroupQr)({ projectId, updateGroupId: android.group, appScheme: config.scheme }) : '',
androidQR: android ? (0, eas_1.getUpdateGroupQr)({ projectId, updateGroupId: android.group, appScheme }) : '',
androidLink: android ? (0, eas_1.getUpdateGroupWebsite)({ projectId, updateGroupId: android.group }) : '',
// iOS update
iosId: ios?.id || '',
iosGroupId: ios?.group || '',
iosBranchName: ios?.branch || '',
iosMessage: ios?.message || '',
iosRuntimeVersion: ios?.runtimeVersion || '',
iosQR: ios ? (0, eas_1.getUpdateGroupQr)({ projectId, updateGroupId: ios.group, appScheme: config.scheme }) : '',
iosQR: ios ? (0, eas_1.getUpdateGroupQr)({ projectId, updateGroupId: ios.group, appScheme }) : '',
iosLink: ios ? (0, eas_1.getUpdateGroupWebsite)({ projectId, updateGroupId: ios.group }) : '',
};
}
exports.getVariables = getVariables;
/**
* Retrieve the app scheme from project config, using the following criteria:
* - If the scheme is a string, return that.
* - If the scheme is an array, return the longest scheme.
*/
function getSchemeFromConfig(config) {
if (typeof config.scheme === 'string') {
return config.scheme;
}
if (Array.isArray(config.scheme) && config.scheme.length > 0) {
const longestToShortest = config.scheme.sort((a, b) => b.length - a.length);
return longestToShortest[0];
}
return null;
}
exports.getSchemeFromConfig = getSchemeFromConfig;
/**
* Generate the message body for a single update.
* Note, this is not configurable, but you can use the variables used to construct your own.
Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"uuid": "^8.3.2"
},
"devDependencies": {
"@expo/config": "^7.0.3",
"@expo/config": "^8.1.2",
"@expo/fingerprint": "^0.1.0",
"@expo/spawn-async": "^1.7.2",
"@semantic-release/changelog": "^6.0.2",
Expand Down
1 change: 1 addition & 0 deletions preview/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Here is a summary of all the input options you can use.
| --------------------- | --------------------------- | ---------------------------------------------------------------------------------------------- |
| **command** | - | EAS CLI command to run when creating updates |
| **working-directory** | - | The relative directory of your Expo app |
| **app-scheme** | - | The (custom) app scheme to use when creating the preview QR code |
| **comment** | `true` | If the action should summarize the EAS Update information as comment on a pull request |
| **comment-id** | _[see code][code-defaults]_ | unique id template to prevent duplicate comments ([read more](#preventing-duplicate-comments)) |
| **github-token** | `github.token` | GitHub token to use when commenting on PR ([read more](#github-tokens)) |
Expand Down
3 changes: 3 additions & 0 deletions preview/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ inputs:
command:
description: EAS CLI command to run when creating updates
required: true
app-scheme:
description: The (custom) app scheme to use when creating the preview QR code
required: false
comment:
description: If the action should summarize the EAS Update information as comment on a pull request
required: false
Expand Down
33 changes: 27 additions & 6 deletions src/actions/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function previewInput() {
commentId: getInput('comment-id') || MESSAGE_ID,
workingDirectory: getInput('working-directory'),
githubToken: getInput('github-token'),
appScheme: getInput('app-scheme'),
};
}

Expand All @@ -42,7 +43,7 @@ export async function previewAction(input = previewInput()) {
return setFailed(`Missing 'extra.eas.projectId' in app.json or app.config.js.`);
}

const variables = getVariables(config, updates);
const variables = getVariables(config, updates, input);
const messageId = template(input.commentId, variables);
const messageBody = createSummary(updates, variables);

Expand Down Expand Up @@ -95,22 +96,24 @@ function sanitizeCommand(input: string): string {
/**
* Generate useful variables for the message body, and as step outputs.
*/
export function getVariables(config: ExpoConfig, updates: EasUpdate[]) {
export function getVariables(config: ExpoConfig, updates: EasUpdate[], input: ReturnType<typeof previewInput>) {
const projectId: string = config.extra?.eas?.projectId;
const android = updates.find(update => update.platform === 'android');
const ios = updates.find(update => update.platform === 'ios');

const appScheme = input.appScheme || getSchemeFromConfig(config) || '';

return {
// EAS / Expo specific
projectId,
projectName: config.name,
projectSlug: config.slug,
projectScheme: config.scheme || '',
projectScheme: appScheme,
// Shared update properties
// Note, only use these properties when the update groups are identical
groupId: updates[0].group,
runtimeVersion: updates[0].runtimeVersion,
qr: getUpdateGroupQr({ projectId, updateGroupId: updates[0].group, appScheme: config.scheme }),
qr: getUpdateGroupQr({ projectId, updateGroupId: updates[0].group, appScheme }),
link: getUpdateGroupWebsite({ projectId, updateGroupId: updates[0].group }),
// These are safe to access regardless of the update groups
branchName: updates[0].branch,
Expand All @@ -123,19 +126,37 @@ export function getVariables(config: ExpoConfig, updates: EasUpdate[]) {
androidBranchName: android?.branch || '',
androidMessage: android?.message || '',
androidRuntimeVersion: android?.runtimeVersion || '',
androidQR: android ? getUpdateGroupQr({ projectId, updateGroupId: android.group, appScheme: config.scheme }) : '',
androidQR: android ? getUpdateGroupQr({ projectId, updateGroupId: android.group, appScheme }) : '',
androidLink: android ? getUpdateGroupWebsite({ projectId, updateGroupId: android.group }) : '',
// iOS update
iosId: ios?.id || '',
iosGroupId: ios?.group || '',
iosBranchName: ios?.branch || '',
iosMessage: ios?.message || '',
iosRuntimeVersion: ios?.runtimeVersion || '',
iosQR: ios ? getUpdateGroupQr({ projectId, updateGroupId: ios.group, appScheme: config.scheme }) : '',
iosQR: ios ? getUpdateGroupQr({ projectId, updateGroupId: ios.group, appScheme }) : '',
iosLink: ios ? getUpdateGroupWebsite({ projectId, updateGroupId: ios.group }) : '',
};
}

/**
* Retrieve the app scheme from project config, using the following criteria:
* - If the scheme is a string, return that.
* - If the scheme is an array, return the longest scheme.
*/
export function getSchemeFromConfig(config: ExpoConfig) {
if (typeof config.scheme === 'string') {
return config.scheme;
}

if (Array.isArray(config.scheme) && config.scheme.length > 0) {
const longestToShortest = config.scheme.sort((a, b) => b.length - a.length);
return longestToShortest[0];
}

return null;
}

/**
* Generate the message body for a single update.
* Note, this is not configurable, but you can use the variables used to construct your own.
Expand Down
53 changes: 44 additions & 9 deletions tests/actions/preview.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { ExpoConfig } from '@expo/config';

import { createSummary, getVariables } from '../../src/actions/preview';
import { createSummary, getSchemeFromConfig, getVariables, previewInput } from '../../src/actions/preview';
import { EasUpdate } from '../../src/eas';

const fakeInput = {} as unknown as ReturnType<typeof previewInput>;

const fakeExpoConfig = {
slug: 'fake-project',
extra: {
Expand Down Expand Up @@ -37,10 +39,25 @@ const fakeUpdatesSingle: EasUpdate[] = [

const fakeUpdatesMultiple = fakeUpdatesSingle.map(update => ({ ...update, group: `fake-group-${update.id}` }));

describe(getSchemeFromConfig, () => {
it('returns `null` when not defined', () => {
expect(getSchemeFromConfig({} as ExpoConfig)).toBeNull();
});

it('returns scheme when defined as string', () => {
expect(getSchemeFromConfig({ scheme: 'ega' } as ExpoConfig)).toBe('ega');
});

it('returns longest scheme when defined as array', () => {
expect(getSchemeFromConfig({ scheme: ['ega', 'expogithubaction'] } as ExpoConfig)).toBe('expogithubaction');
});
});

describe(createSummary, () => {
describe('single update group', () => {
it('returns expected message for both platforms', () => {
expect(createSummary(fakeUpdatesSingle, getVariables(fakeExpoConfig, fakeUpdatesSingle))).toMatchInlineSnapshot(`
expect(createSummary(fakeUpdatesSingle, getVariables(fakeExpoConfig, fakeUpdatesSingle, fakeInput)))
.toMatchInlineSnapshot(`
"🚀 Expo preview is ready!
- Project → **fake-project**
Expand All @@ -55,8 +72,26 @@ describe(createSummary, () => {
});

it('returns expected message for both platforms with custom app scheme', () => {
const customSchemeConfig = { ...fakeExpoConfig, scheme: 'expogithubaction' };
expect(createSummary(fakeUpdatesSingle, getVariables(customSchemeConfig, fakeUpdatesSingle)))
const customSchemeConfig = { ...fakeExpoConfig, scheme: ['ega', 'expogithubaction'] };
expect(createSummary(fakeUpdatesSingle, getVariables(customSchemeConfig, fakeUpdatesSingle, fakeInput)))
.toMatchInlineSnapshot(`
"🚀 Expo preview is ready!
- Project → **fake-project**
- Platforms → **android**, **ios**
- Scheme → **expogithubaction**
- Runtime Version → **exposdk:47.0.0**
- **[More info](https://expo.dev/projects/fake-project-id/updates/fake-group-id)**
<a href="https://qr.expo.dev/eas-update?appScheme=expogithubaction&projectId=fake-project-id&groupId=fake-group-id"><img src="https://qr.expo.dev/eas-update?appScheme=expogithubaction&projectId=fake-project-id&groupId=fake-group-id" width="250px" height="250px" /></a>
> Learn more about [𝝠 Expo Github Action](https://github.com/expo/expo-github-action/tree/main/preview#example-workflows)"
`);
});

it('returns expected message for both platforms with overwriten app scheme', () => {
const customInput = { ...fakeInput, appScheme: 'expogithubaction' };
expect(createSummary(fakeUpdatesSingle, getVariables(fakeExpoConfig, fakeUpdatesSingle, customInput)))
.toMatchInlineSnapshot(`
"🚀 Expo preview is ready!
Expand All @@ -74,7 +109,7 @@ describe(createSummary, () => {

it('returns expected message for android only', () => {
const fakeUpdate = fakeUpdatesSingle.filter(update => update.platform === 'android');
expect(createSummary(fakeUpdate, getVariables(fakeExpoConfig, fakeUpdate))).toMatchInlineSnapshot(`
expect(createSummary(fakeUpdate, getVariables(fakeExpoConfig, fakeUpdate, fakeInput))).toMatchInlineSnapshot(`
"🚀 Expo preview is ready!
- Project → **fake-project**
Expand All @@ -90,7 +125,7 @@ describe(createSummary, () => {

it('returns expected message for ios only', () => {
const fakeUpdate = fakeUpdatesSingle.filter(update => update.platform === 'ios');
expect(createSummary(fakeUpdate, getVariables(fakeExpoConfig, fakeUpdate))).toMatchInlineSnapshot(`
expect(createSummary(fakeUpdate, getVariables(fakeExpoConfig, fakeUpdate, fakeInput))).toMatchInlineSnapshot(`
"🚀 Expo preview is ready!
- Project → **fake-project**
Expand All @@ -107,7 +142,7 @@ describe(createSummary, () => {

describe('mutliple update groups', () => {
it('returns expected message for both platforms', () => {
expect(createSummary(fakeUpdatesMultiple, getVariables(fakeExpoConfig, fakeUpdatesMultiple)))
expect(createSummary(fakeUpdatesMultiple, getVariables(fakeExpoConfig, fakeUpdatesMultiple, fakeInput)))
.toMatchInlineSnapshot(`
"🚀 Expo preview is ready!
Expand All @@ -124,7 +159,7 @@ describe(createSummary, () => {

it('returns expected message for android only', () => {
const fakeUpdate = fakeUpdatesSingle.filter(update => update.platform === 'android');
expect(createSummary(fakeUpdate, getVariables(fakeExpoConfig, fakeUpdate))).toMatchInlineSnapshot(`
expect(createSummary(fakeUpdate, getVariables(fakeExpoConfig, fakeUpdate, fakeInput))).toMatchInlineSnapshot(`
"🚀 Expo preview is ready!
- Project → **fake-project**
Expand All @@ -140,7 +175,7 @@ describe(createSummary, () => {

it('returns expected message for ios only', () => {
const fakeUpdate = fakeUpdatesSingle.filter(update => update.platform === 'ios');
expect(createSummary(fakeUpdate, getVariables(fakeExpoConfig, fakeUpdate))).toMatchInlineSnapshot(`
expect(createSummary(fakeUpdate, getVariables(fakeExpoConfig, fakeUpdate, fakeInput))).toMatchInlineSnapshot(`
"🚀 Expo preview is ready!
- Project → **fake-project**
Expand Down

0 comments on commit 42d64cf

Please sign in to comment.