Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: sindresorhus/open
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: cc7b781edbcee122836140a260a254aa5c64fb6a
Choose a base ref
...
head repository: sindresorhus/open
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: a9babe05b371ca414ad1c4fe3ffe83bbf40b1c4a
Choose a head ref

Commits on Apr 16, 2020

  1. Use HTTPS links

    sindresorhus committed Apr 16, 2020
    Copy the full SHA
    d542970 View commit details

Commits on May 16, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    48b6d0e View commit details
  2. Meta tweaks

    sindresorhus committed May 16, 2020
    Copy the full SHA
    96b4067 View commit details
  3. 7.0.4

    sindresorhus committed May 16, 2020
    Copy the full SHA
    72d9ddb View commit details

Commits on Jul 19, 2020

  1. Add allowNonzeroExitCode option (#176)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    peterchu999 and sindresorhus authored Jul 19, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e009765 View commit details

Commits on Jul 20, 2020

  1. 7.1.0

    sindresorhus committed Jul 20, 2020
    Copy the full SHA
    ed14bbf View commit details

Commits on Aug 21, 2020

  1. Use PowerShell on Windows for improved reliability (#188)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    tim-stasse and sindresorhus authored Aug 21, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f7ca0d3 View commit details
  2. 7.2.0

    sindresorhus committed Aug 21, 2020
    Copy the full SHA
    1022f42 View commit details

Commits on Aug 28, 2020

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9bcd285 View commit details
  2. 7.2.1

    sindresorhus committed Aug 28, 2020
    Copy the full SHA
    45e50ca View commit details

Commits on Sep 29, 2020

  1. Support WSL configuration where Windows paths are not in PATH (#195)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    Pytal and sindresorhus authored Sep 29, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    be0f794 View commit details
  2. 7.3.0

    sindresorhus committed Sep 29, 2020
    Copy the full SHA
    f3748e4 View commit details

Commits on Dec 1, 2020

  1. Move to GitHub Actions

    sindresorhus committed Dec 1, 2020
    Copy the full SHA
    003c78f View commit details

Commits on Jan 3, 2021

  1. Fix readme typo (#213)

    unzico authored Jan 3, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    fe91dea View commit details

Commits on Jan 7, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a3bbadc View commit details
  2. 7.3.1

    sindresorhus committed Jan 7, 2021
    Copy the full SHA
    3182c38 View commit details

Commits on Jan 30, 2021

  1. Remove usage of wslu (#217)

    anaisbetts authored Jan 30, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    315a480 View commit details

Commits on Feb 1, 2021

  1. 7.4.0

    sindresorhus committed Feb 1, 2021
    Copy the full SHA
    5ce319c View commit details

Commits on Feb 7, 2021

  1. WSL: Get drives mount point from wsl.conf (#219)

    Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
    kuzalekon and sindresorhus authored Feb 7, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    36e9964 View commit details

Commits on Feb 8, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    02bff01 View commit details

Commits on Feb 13, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    db8eb8f View commit details
  2. 7.4.1

    sindresorhus committed Feb 13, 2021
    Copy the full SHA
    f0533c0 View commit details

Commits on Feb 16, 2021

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    f4df68a View commit details
  2. 7.4.2

    sindresorhus committed Feb 16, 2021
    Copy the full SHA
    a9babe0 View commit details
Showing with 139 additions and 67 deletions.
  1. +23 −0 .github/workflows/main.yml
  2. +0 −5 .travis.yml
  3. +9 −4 index.d.ts
  4. +83 −51 index.js
  5. +1 −1 license
  6. +2 −2 package.json
  7. +13 −4 readme.md
  8. +8 −0 test.js
23 changes: 23 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: CI
on:
- push
- pull_request
jobs:
test:
name: Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version:
- 14
- 12
- 10
- 8
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm test
5 changes: 0 additions & 5 deletions .travis.yml

This file was deleted.

13 changes: 9 additions & 4 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -33,15 +33,20 @@ declare namespace open {
readonly app?: string | readonly string[];

/**
Uses `encodeURI` to encode the `target` before executing it.
__deprecated__
The use with targets that are not URLs is not recommended.
This option will be removed in the next major release.
*/
readonly url?: boolean;

Especially useful when dealing with the [double-quotes on Windows](https://github.com/sindresorhus/open#double-quotes-on-windows) caveat.
/**
Allow the opened app to exit with nonzero exit code when the `wait` option is `true`.
We do not recommend setting this option. The convention for success is exit code zero.
@default false
*/
readonly url?: boolean;
readonly allowNonzeroExitCode?: boolean;
}
}

134 changes: 83 additions & 51 deletions index.js
Original file line number Diff line number Diff line change
@@ -7,17 +7,55 @@ const isWsl = require('is-wsl');
const isDocker = require('is-docker');

const pAccess = promisify(fs.access);
const pExecFile = promisify(childProcess.execFile);
const pReadFile = promisify(fs.readFile);

// Path to included `xdg-open`.
const localXdgOpenPath = path.join(__dirname, 'xdg-open');

// Convert a path from WSL format to Windows format:
// `/mnt/c/Program Files/Example/MyApp.exe` → `C:\Program Files\Example\MyApp.exe`
const wslToWindowsPath = async path => {
const {stdout} = await pExecFile('wslpath', ['-w', path]);
return stdout.trim();
};
/**
Get the mount point for fixed drives in WSL.
@inner
@returns {string} The mount point.
*/
const getWslDrivesMountPoint = (() => {
// Default value for "root" param
// according to https://docs.microsoft.com/en-us/windows/wsl/wsl-config
const defaultMountPoint = '/mnt/';

let mountPoint;

return async function () {
if (mountPoint) {
// Return memoized mount point value
return mountPoint;
}

const configFilePath = '/etc/wsl.conf';

let isConfigFileExists = false;
try {
await pAccess(configFilePath, fs.constants.F_OK);
isConfigFileExists = true;
} catch (_) {}

if (!isConfigFileExists) {
return defaultMountPoint;
}

const configContent = await pReadFile(configFilePath, {encoding: 'utf8'});
const configMountPoint = /root\s*=\s*(.*)/g.exec(configContent);

if (!configMountPoint) {
return defaultMountPoint;
}

mountPoint = configMountPoint[1].trim();
mountPoint = mountPoint.endsWith('/') ? mountPoint : mountPoint + '/';

return mountPoint;
};
})();

module.exports = async (target, options) => {
if (typeof target !== 'string') {
@@ -27,29 +65,19 @@ module.exports = async (target, options) => {
options = {
wait: false,
background: false,
url: false,
allowNonzeroExitCode: false,
...options
};

let command;
let {app} = options;
let appArguments = [];
const cliArguments = [];
const childProcessOptions = {};

if (Array.isArray(options.app)) {
appArguments = options.app.slice(1);
options.app = options.app[0];
}

// Encodes the target as if it were an URL. Especially useful to get
// double-quotes through the “double-quotes on Windows caveat”, but it
// can be used on any platform.
if (options.url) {
target = encodeURI(target);

if (isWsl) {
target = target.replace(/&/g, '^&');
}
if (Array.isArray(app)) {
appArguments = app.slice(1);
app = app[0];
}

if (process.platform === 'darwin') {
@@ -63,49 +91,53 @@ module.exports = async (target, options) => {
cliArguments.push('--background');
}

if (options.app) {
cliArguments.push('-a', options.app);
if (app) {
cliArguments.push('-a', app);
}
} else if (process.platform === 'win32' || (isWsl && !isDocker())) {
command = 'cmd' + (isWsl ? '.exe' : '');
cliArguments.push('/s', '/c', 'start', '""', '/b');
const mountPoint = await getWslDrivesMountPoint();

command = isWsl ?
`${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe` :
`${process.env.SYSTEMROOT}\\System32\\WindowsPowerShell\\v1.0\\powershell`;

cliArguments.push(
'-NoProfile',
'-NonInteractive',
'–ExecutionPolicy',
'Bypass',
'-EncodedCommand'
);

if (!isWsl) {
// Always quoting target allows for URLs/paths to have spaces and unmarked characters, as `cmd.exe` will
// interpret them as plain text to be forwarded as one unique argument. Enabling `windowsVerbatimArguments`
// disables Node.js's default quotes and escapes handling (https://git.io/fjdem).
// References:
// - Issues #17, #44, #55, #77, #101, #115
// - Pull requests: #74, #98
//
// As a result, all double-quotes are stripped from the `target` and do not get to your desired destination.
target = `"${target}"`;
childProcessOptions.windowsVerbatimArguments = true;

if (options.app) {
options.app = `"${options.app}"`;
}
}

const encodedArguments = ['Start'];

if (options.wait) {
cliArguments.push('/wait');
encodedArguments.push('-Wait');
}

if (options.app) {
if (isWsl && options.app.startsWith('/mnt/')) {
const windowsPath = await wslToWindowsPath(options.app);
options.app = windowsPath;
}

cliArguments.push(options.app);
if (app) {
// Double quote with double quotes to ensure the inner quotes are passed through.
// Inner quotes are delimited for PowerShell interpretation with backticks.
encodedArguments.push(`"\`"${app}\`""`, '-ArgumentList');
appArguments.unshift(target);
} else {
encodedArguments.push(`"${target}"`);
}

if (appArguments.length > 0) {
cliArguments.push(...appArguments);
appArguments = appArguments.map(arg => `"\`"${arg}\`""`);
encodedArguments.push(appArguments.join(','));
}

// Using Base64-encoded command, accepted by PowerShell, to allow special characters.
target = Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
} else {
if (options.app) {
command = options.app;
if (app) {
command = app;
} else {
// When bundled by Webpack, there's no actual package file path and no local `xdg-open`.
const isBundled = !__dirname || __dirname === '/';
@@ -147,7 +179,7 @@ module.exports = async (target, options) => {
subprocess.once('error', reject);

subprocess.once('close', exitCode => {
if (exitCode > 0) {
if (options.allowNonzeroExitCode && exitCode > 0) {
reject(new Error(`Exited with code ${exitCode}`));
return;
}
2 changes: 1 addition & 1 deletion license
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "open",
"version": "7.0.3",
"version": "7.4.2",
"description": "Open stuff like URLs, files, executables. Cross-platform.",
"license": "MIT",
"repository": "sindresorhus/open",
"funding": "https://github.com/sponsors/sindresorhus",
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "sindresorhus.com"
"url": "https://sindresorhus.com"
},
"engines": {
"node": ">=8"
17 changes: 13 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@
This is meant to be used in command-line tools and scripts, not in the browser.

If need this for Electron, use [`shell.openItem()`](https://electronjs.org/docs/api/shell#shellopenitemfullpath) instead.
If you need this for Electron, use [`shell.openPath()`](https://www.electronjs.org/docs/api/shell#shellopenpathpath) instead.

Note: The original [`open` package](https://github.com/pwnall/node-open) was previously deprecated in favor of this package, and we got the name, so this package is now named `open` instead of `opn`. If you're upgrading from the original `open` package (`open@0.0.5` or lower), keep in mind that the API is different.

@@ -14,8 +14,8 @@ Note: The original [`open` package](https://github.com/pwnall/node-open) was pre
- Supports app arguments.
- Safer as it uses `spawn` instead of `exec`.
- Fixes most of the open original `node-open` issues.
- Includes the latest [`xdg-open` script](http://cgit.freedesktop.org/xdg/xdg-utils/commit/?id=c55122295c2a480fa721a9614f0e2d42b2949c18) for Linux.
- Supports WSL paths to Windows apps under `/mnt/*`.
- Includes the latest [`xdg-open` script](https://cgit.freedesktop.org/xdg/xdg-utils/commit/?id=c55122295c2a480fa721a9614f0e2d42b2949c18) for Linux.
- Supports WSL paths to Windows apps.

## Install

@@ -97,11 +97,20 @@ You may also pass in the app's full path. For example on WSL, this can be `/mnt/
Type: `boolean`\
Default: `false`

Uses `encodeURI` to encode the target before executing it.<br>
Uses `URL` to encode the target before executing it.<br>
We do not recommend using it on targets that are not URLs.

Especially useful when dealing with the [double-quotes on Windows](#double-quotes-on-windows) caveat.

##### allowNonzeroExitCode

Type: `boolean`\
Default: `false`

Allow the opened app to exit with nonzero exit code when the `wait` option is `true`.

We do not recommend setting this option. The convention for success is exit code zero.

## Caveats

### Double-quotes on Windows
8 changes: 8 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -78,6 +78,14 @@ test('open URL with query strings, spaces, pipes and a fragment', async () => {
await open('https://sindresorhus.com/?abc=123&def=456&ghi=w|i|t|h spaces#projects');
});

test('open URL with query strings and URL reserved characters', async () => {
await open('https://httpbin.org/get?amp=%26&colon=%3A&comma=%2C&commat=%40&dollar=%24&equals=%3D&plus=%2B&quest=%3F&semi=%3B&sol=%2F');
});

test('open URL with query strings and URL reserved characters with `url` option', async () => {
await open('https://httpbin.org/get?amp=%26&colon=%3A&comma=%2C&commat=%40&dollar=%24&equals=%3D&plus=%2B&quest=%3F&semi=%3B&sol=%2F', {url: true});
});

if (isWsl) {
test('open URL in specified Windows app given a WSL path to the app', async () => {
await open('https://sindresorhus.com', {app: firefoxWslName});