Skip to content

Commit 831bcb1

Browse files
committed
fix: use cmd suffix when authenticating on windows
1 parent 8ff07e0 commit 831bcb1

11 files changed

Lines changed: 269 additions & 14 deletions

File tree

build/expo.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ function authenticate(username, password) {
2828
if (!username || !password) {
2929
return core.debug('Skipping authentication, `expo-username` and/or `expo-password` not set...');
3030
}
31-
yield cli.exec('expo', ['login', `--username=${username}`], {
31+
// github actions toolkit will handle commands with `.cmd` on windows, we need that
32+
const bin = process.platform === 'win32'
33+
? 'expo.cmd'
34+
: 'expo';
35+
yield cli.exec(bin, ['login', `--username=${username}`], {
3236
env: Object.assign(Object.assign({}, process.env), { EXPO_CLI_PASSWORD: password }),
3337
});
3438
});

build/src/expo.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use strict";
2+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4+
return new (P || (P = Promise))(function (resolve, reject) {
5+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8+
step((generator = generator.apply(thisArg, _arguments || [])).next());
9+
});
10+
};
11+
var __importStar = (this && this.__importStar) || function (mod) {
12+
if (mod && mod.__esModule) return mod;
13+
var result = {};
14+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
15+
result["default"] = mod;
16+
return result;
17+
};
18+
Object.defineProperty(exports, "__esModule", { value: true });
19+
const core = __importStar(require("@actions/core"));
20+
const cli = __importStar(require("@actions/exec"));
21+
/**
22+
* Authenticate at Expo using `expo login`.
23+
* This step is required for publishing and building new apps.
24+
* It uses the `EXPO_CLI_PASSWORD` environment variable for improved security.
25+
*/
26+
function authenticate(username, password) {
27+
return __awaiter(this, void 0, void 0, function* () {
28+
if (!username || !password) {
29+
return core.debug('Skipping authentication, `expo-username` and/or `expo-password` not set...');
30+
}
31+
// github actions toolkit will handle commands with `.cmd` on windows, we need that
32+
const bin = process.platform === 'win32'
33+
? 'expo.cmd'
34+
: 'expo';
35+
yield cli.exec(bin, ['login', `--username=${username}`], {
36+
env: Object.assign(Object.assign({}, process.env), { EXPO_CLI_PASSWORD: password }),
37+
});
38+
});
39+
}
40+
exports.authenticate = authenticate;

build/src/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"use strict";
2+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4+
return new (P || (P = Promise))(function (resolve, reject) {
5+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8+
step((generator = generator.apply(thisArg, _arguments || [])).next());
9+
});
10+
};
11+
Object.defineProperty(exports, "__esModule", { value: true });
12+
const core_1 = require("@actions/core");
13+
const expo_1 = require("./expo");
14+
const install_1 = require("./install");
15+
const system_1 = require("./system");
16+
function run() {
17+
return __awaiter(this, void 0, void 0, function* () {
18+
const path = yield install_1.install(core_1.getInput('expo-version') || 'latest', core_1.getInput('expo-packager') || 'npm');
19+
core_1.addPath(path);
20+
yield expo_1.authenticate(core_1.getInput('expo-username'), core_1.getInput('expo-password'));
21+
const shouldPatchWatchers = core_1.getInput('expo-patch-watchers') || 'true';
22+
if (shouldPatchWatchers !== 'false') {
23+
yield system_1.patchWatchers();
24+
}
25+
});
26+
}
27+
exports.run = run;
28+
run();

build/src/install.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"use strict";
2+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4+
return new (P || (P = Promise))(function (resolve, reject) {
5+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8+
step((generator = generator.apply(thisArg, _arguments || [])).next());
9+
});
10+
};
11+
var __importStar = (this && this.__importStar) || function (mod) {
12+
if (mod && mod.__esModule) return mod;
13+
var result = {};
14+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
15+
result["default"] = mod;
16+
return result;
17+
};
18+
Object.defineProperty(exports, "__esModule", { value: true });
19+
const cache = __importStar(require("@actions/tool-cache"));
20+
const cli = __importStar(require("@actions/exec"));
21+
const io = __importStar(require("@actions/io"));
22+
const path = __importStar(require("path"));
23+
// eslint-disable-next-line @typescript-eslint/no-var-requires
24+
const registry = require('libnpm');
25+
/**
26+
* Resolve the provided semver to exact version of `expo-cli`.
27+
* This uses the npm registry and accepts latest, dist-tags or version ranges.
28+
* It's used to determine the cached version of `expo-cli`.
29+
*/
30+
function resolve(version) {
31+
return __awaiter(this, void 0, void 0, function* () {
32+
return (yield registry.manifest(`expo-cli@${version}`)).version;
33+
});
34+
}
35+
exports.resolve = resolve;
36+
/**
37+
* Install `expo-cli`, by version, using the packager.
38+
* Here you can provide any semver range or dist tag used in the registry.
39+
* It returns the path where Expo is installed.
40+
*/
41+
function install(version, packager) {
42+
return __awaiter(this, void 0, void 0, function* () {
43+
const exact = yield resolve(version);
44+
let root = yield fromCache(exact);
45+
if (!root) {
46+
root = yield fromPackager(exact, packager);
47+
root = yield toCache(exact, root);
48+
}
49+
return path.join(root, 'node_modules', '.bin');
50+
});
51+
}
52+
exports.install = install;
53+
/**
54+
* Install `expo-cli`, by version, using npm or yarn.
55+
* It creates a temporary directory to store all required files.
56+
*/
57+
function fromPackager(version, packager) {
58+
return __awaiter(this, void 0, void 0, function* () {
59+
const root = process.env['RUNNER_TEMP'] || '';
60+
const tool = yield io.which(packager);
61+
yield io.mkdirP(root);
62+
yield cli.exec(tool, ['add', `expo-cli@${version}`], { cwd: root });
63+
return root;
64+
});
65+
}
66+
exports.fromPackager = fromPackager;
67+
/**
68+
* Get the path to the `expo-cli` from cache, if any.
69+
* Note, this cache is **NOT** shared between jobs.
70+
*
71+
* @see https://github.com/actions/toolkit/issues/47
72+
*/
73+
function fromCache(version) {
74+
return __awaiter(this, void 0, void 0, function* () {
75+
return cache.find('expo-cli', version);
76+
});
77+
}
78+
exports.fromCache = fromCache;
79+
/**
80+
* Store the root of `expo-cli` in the cache, for future reuse.
81+
* Note, this cache is **NOT** shared between jobs.
82+
*
83+
* @see https://github.com/actions/toolkit/issues/47
84+
*/
85+
function toCache(version, root) {
86+
return __awaiter(this, void 0, void 0, function* () {
87+
return cache.cacheDir(root, 'expo-cli', version);
88+
});
89+
}
90+
exports.toCache = toCache;

build/src/system.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"use strict";
2+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4+
return new (P || (P = Promise))(function (resolve, reject) {
5+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8+
step((generator = generator.apply(thisArg, _arguments || [])).next());
9+
});
10+
};
11+
var __importStar = (this && this.__importStar) || function (mod) {
12+
if (mod && mod.__esModule) return mod;
13+
var result = {};
14+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
15+
result["default"] = mod;
16+
return result;
17+
};
18+
Object.defineProperty(exports, "__esModule", { value: true });
19+
const core = __importStar(require("@actions/core"));
20+
const cli = __importStar(require("@actions/exec"));
21+
/**
22+
* Try to patch the default watcher/inotify limit.
23+
* This is a limitation from GitHub Actions and might be an issue in some Expo projects.
24+
* It sets the system's `fs.inotify` limits to a more sensible setting.
25+
*
26+
* @see https://github.com/expo/expo-github-action/issues/20
27+
*/
28+
function patchWatchers() {
29+
return __awaiter(this, void 0, void 0, function* () {
30+
if (process.platform !== 'linux') {
31+
return core.debug('Skipping patch for watchers, not running on Linux...');
32+
}
33+
core.debug('Patching system watchers for the `ENOSPC` error...');
34+
try {
35+
// see https://github.com/expo/expo-cli/issues/277#issuecomment-452685177
36+
yield cli.exec('sudo sysctl fs.inotify.max_user_instances=524288');
37+
yield cli.exec('sudo sysctl fs.inotify.max_user_watches=524288');
38+
yield cli.exec('sudo sysctl fs.inotify.max_queued_events=524288');
39+
yield cli.exec('sudo sysctl -p');
40+
}
41+
catch (_a) {
42+
core.warning('Looks like we can\'t patch watchers/inotify limits, you might encouter the `ENOSPC` error.');
43+
core.warning('For more info, https://github.com/expo/expo-github-action/issues/20');
44+
}
45+
});
46+
}
47+
exports.patchWatchers = patchWatchers;

build/tests/utils.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
// keep track of the original one to revert the platform
4+
const originalPlatform = process.platform;
5+
/**
6+
* Change the node platform for testing purposes.
7+
* With this you can fake the `process.platform`.
8+
*/
9+
function setPlatform(platform) {
10+
Object.defineProperty(process, 'platform', { value: platform });
11+
}
12+
exports.setPlatform = setPlatform;
13+
/**
14+
* Revert the platform to the original one.
15+
*/
16+
function resetPlatform() {
17+
setPlatform(originalPlatform);
18+
}
19+
exports.resetPlatform = resetPlatform;

src/expo.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ export async function authenticate(username: string, password: string) {
1111
return core.debug('Skipping authentication, `expo-username` and/or `expo-password` not set...');
1212
}
1313

14-
await cli.exec('expo', ['login', `--username=${username}`], {
14+
// github actions toolkit will handle commands with `.cmd` on windows, we need that
15+
const bin = process.platform === 'win32'
16+
? 'expo.cmd'
17+
: 'expo';
18+
19+
await cli.exec(bin, ['login', `--username=${username}`], {
1520
env: {
1621
...process.env,
1722
EXPO_CLI_PASSWORD: password,

tests/expo.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ jest.mock('@actions/core', () => core);
55
jest.mock('@actions/exec', () => cli);
66

77
import * as expo from '../src/expo';
8+
import { setPlatform, resetPlatform } from './utils';
89

910
describe('authenticate', () => {
1011
test('skips authentication without credentials', async () => {
@@ -33,4 +34,12 @@ describe('authenticate', () => {
3334
},
3435
});
3536
});
37+
38+
test('executes login command with `.cmd` suffix on windows', async () => {
39+
setPlatform('win32');
40+
await expo.authenticate('bycedric', 'mypassword');
41+
expect(cli.exec).toBeCalled();
42+
expect(cli.exec.mock.calls[0][0]).toBe('expo.cmd');
43+
resetPlatform();
44+
});
3645
});

tests/system.test.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,15 @@ jest.mock('@actions/core', () => core);
55
jest.mock('@actions/exec', () => cli);
66

77
import * as system from '../src/system';
8+
import { setPlatform, resetPlatform } from './utils';
89

910
describe('patchWatchers', () => {
10-
const originalPlatform = process.platform;
11-
const changePlatform = (platform: NodeJS.Platform) => {
12-
Object.defineProperty(process, 'platform', { value: platform });
13-
};
14-
1511
afterEach(() => {
16-
changePlatform(originalPlatform);
12+
resetPlatform();
1713
});
1814

1915
it('increses fs inotify settings with sysctl', async () => {
20-
changePlatform('linux');
16+
setPlatform('linux');
2117
await system.patchWatchers();
2218
expect(cli.exec).toHaveBeenCalledWith('sudo sysctl fs.inotify.max_user_instances=524288');
2319
expect(cli.exec).toHaveBeenCalledWith('sudo sysctl fs.inotify.max_user_watches=524288');
@@ -28,7 +24,7 @@ describe('patchWatchers', () => {
2824
it('warns for unsuccessful patches', async () => {
2925
const error = new Error('Something went wrong');
3026
cli.exec.mockRejectedValue(error);
31-
changePlatform('linux');
27+
setPlatform('linux');
3228
await system.patchWatchers();
3329
expect(core.warning).toBeCalledWith(expect.stringContaining('can\'t patch watchers'));
3430
expect(core.warning).toBeCalledWith(
@@ -37,19 +33,19 @@ describe('patchWatchers', () => {
3733
});
3834

3935
it('skips on windows platform', async () => {
40-
changePlatform('win32');
36+
setPlatform('win32');
4137
await system.patchWatchers();
4238
expect(cli.exec).not.toHaveBeenCalled();
4339
});
4440

4541
it('skips on macos platform', async () => {
46-
changePlatform('darwin');
42+
setPlatform('darwin');
4743
await system.patchWatchers();
4844
expect(cli.exec).not.toHaveBeenCalled();
4945
});
5046

5147
it('runs on linux platform', async () => {
52-
changePlatform('linux');
48+
setPlatform('linux');
5349
await system.patchWatchers();
5450
expect(cli.exec).toHaveBeenCalled();
5551
});

tests/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// keep track of the original one to revert the platform
2+
const originalPlatform = process.platform;
3+
4+
/**
5+
* Change the node platform for testing purposes.
6+
* With this you can fake the `process.platform`.
7+
*/
8+
export function setPlatform(platform: NodeJS.Platform) {
9+
Object.defineProperty(process, 'platform', { value: platform });
10+
}
11+
12+
/**
13+
* Revert the platform to the original one.
14+
*/
15+
export function resetPlatform() {
16+
setPlatform(originalPlatform);
17+
}

0 commit comments

Comments
 (0)