Skip to content

Commit

Permalink
@dr-js/core@0.3.0-dev.6
Browse files Browse the repository at this point in the history
notable change:
- break: use `getDefaultOpenCommandList` instead of `getDefaultOpen`
- add: `ResolveCommand` moved from `@dr-js/node`
  • Loading branch information
dr-js committed Apr 18, 2020
1 parent 817c0cb commit 9b71324
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 21 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/ci-test.yml
Expand Up @@ -6,7 +6,7 @@ jobs:
build:
strategy:
matrix:
os: [ ubuntu-latest ]
os: [ ubuntu-latest ] # [ ubuntu-latest, windows-latest, macos-latest ]
node-version: [ 12.x, 13.x ]

runs-on: ${{ matrix.os }}
Expand All @@ -29,5 +29,3 @@ jobs:
- run: npm ci

- run: npm test
env:
CI: true
8 changes: 6 additions & 2 deletions SPEC.md
Expand Up @@ -198,11 +198,13 @@
+ 📄 [source/node/server/WebSocket/function.js](source/node/server/WebSocket/function.js)
- `BUFFER_MAX_LENGTH`, `FRAME_CONFIG`, `OPCODE_TYPE`, `WEBSOCKET_EVENT`, `WEBSOCKET_VERSION`, `applyMaskQuadletBufferInPlace`, `getRequestKey`, `getRespondKey`
+ 📄 [source/node/system/DefaultOpen.js](source/node/system/DefaultOpen.js)
- `getDefaultOpen`
- `getDefaultOpenCommandList`
+ 📄 [source/node/system/ExitListener.js](source/node/system/ExitListener.js)
- `addExitListenerAsync`, `addExitListenerSync`, `clearExitListener`, `deleteExitListenerAsync`, `deleteExitListenerSync`
+ 📄 [source/node/system/Process.js](source/node/system/Process.js)
- `describeAllProcessStatusAsync`, `findProcessPidMapInfo`, `findProcessTreeInfo`, `getAllProcessStatusAsync`, `getProcessListAsync`, `isPidExist`, `killProcessInfoAsync`, `killProcessTreeInfoAsync`, `sortProcessList`, `toProcessPidMap`, `toProcessTree`
+ 📄 [source/node/system/ResolveCommand.js](source/node/system/ResolveCommand.js)
- `resolveCommand`, `resolveCommandAsync`, `resolveCommandName`, `resolveCommandNameAsync`
+ 📄 [source/node/system/Run.js](source/node/system/Run.js)
- `run`, `runSync`, `withCwd`
+ 📄 [source/node/system/Status.js](source/node/system/Status.js)
Expand Down Expand Up @@ -394,11 +396,13 @@
- `autoTestServerPort`, `getUnusedPort`, `parseCookieString`
- **System**
- **DefaultOpen**
- `getDefaultOpen`
- `getDefaultOpenCommandList`
- **ExitListener**
- `addExitListenerAsync`, `addExitListenerSync`, `clearExitListener`, `deleteExitListenerAsync`, `deleteExitListenerSync`
- **Process**
- `describeAllProcessStatusAsync`, `findProcessPidMapInfo`, `findProcessTreeInfo`, `getAllProcessStatusAsync`, `getProcessListAsync`, `isPidExist`, `killProcessInfoAsync`, `killProcessTreeInfoAsync`, `sortProcessList`, `toProcessPidMap`, `toProcessTree`
- **ResolveCommand**
- `resolveCommand`, `resolveCommandAsync`, `resolveCommandName`, `resolveCommandNameAsync`
- **Run**
- `run`, `runSync`, `withCwd`
- **Status**
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
@@ -1,7 +1,7 @@
{
"private": true,
"name": "@dr-js/core",
"version": "0.3.0-dev.5",
"version": "0.3.0-dev.6",
"description": "A collection of strange functions",
"author": "dr-js",
"license": "MIT",
Expand All @@ -28,7 +28,7 @@
"build-library-webpack-dev": "node -r @babel/register ./script/webpack development watch",
"build-module": "cross-env BABEL_ENV=module babel ./source --out-dir ./output-gitignore/module",
"// test ========================": "",
"test-dev": "cross-env BABEL_ENV=dev dr-dev --TR @babel/register -T source/common/module/AsyncLane.test.js",
"test-dev": "cross-env BABEL_ENV=dev dr-dev --TR @babel/register -T source/node/system/ResolveCommand.test.js",
"test-sanity": "cross-env BABEL_ENV=dev dr-dev --TR @babel/register -T source/common/.sanity.test/promise.test.js",
"test-source": "dr-dev --TR @babel/register -T source/env/ source/common/ source/node/ --TFS .test.js",
"test-output-library": "dr-dev -T output-gitignore/library/env/ output-gitignore/library/common/ output-gitignore/library/node/ --TFS .test.js",
Expand Down
5 changes: 3 additions & 2 deletions source-bin/index.js
Expand Up @@ -18,7 +18,7 @@ import { modifyCopy, modifyRename, modifyDelete } from '@dr-js/core/module/node/
import { autoTestServerPort } from '@dr-js/core/module/node/server/function'
import { createServerPack } from '@dr-js/core/module/node/server/Server'
import { createTCPProxyListener } from '@dr-js/core/module/node/server/Proxy'
import { getDefaultOpen } from '@dr-js/core/module/node/system/DefaultOpen'
import { getDefaultOpenCommandList } from '@dr-js/core/module/node/system/DefaultOpen'
import { runSync } from '@dr-js/core/module/node/system/Run'
import { getAllProcessStatusAsync, describeAllProcessStatusAsync } from '@dr-js/core/module/node/system/Process'
import { getSystemStatus, describeSystemStatus } from '@dr-js/core/module/node/system/Status'
Expand Down Expand Up @@ -130,7 +130,8 @@ const runMode = async (modeName, optionData) => {

case 'open': {
const uri = argumentList[ 0 ] || '.' // can be url or path
return runSync({ command: getDefaultOpen(), argList: [ uri.includes('://') ? uri : normalize(uri) ], option: { shell: true } }) // TODO: win32 quoting problem: https://github.com/sindresorhus/open#double-quotes-on-windows
const [ command, ...prefixArgList ] = getDefaultOpenCommandList()
return runSync({ command, argList: [ ...prefixArgList, uri.includes('://') ? uri : normalize(uri) ] })
}
case 'status':
return logAuto(isOutputJSON ? getSystemStatus() : describeSystemStatus())
Expand Down
39 changes: 28 additions & 11 deletions source/node/system/DefaultOpen.js
@@ -1,15 +1,32 @@
const DEFAULT_OPEN_MAP = {
linux: 'xdg-open',
win32: 'start',
darwin: 'open',
android: 'termux-open' // TODO: may have other options?
const DEFAULT_OPEN_COMMAND_LIST_MAP = {
// [ process.platform ]: [ command, ...prefixArgList ]

linux: [ 'xdg-open' ],

// better than the both `start` (cmd builtin need nasty quoting) and `explorer.exe` (standalone but fail on `http://localhost?a=1#some://url?with=special&chars=:->`)
// now the problem will be how to pass this to node on win32. check quoting problem: https://github.com/sindresorhus/open#double-quotes-on-windows
// check:
// - https://ss64.com/nt/rundll32.html
// - https://superuser.com/questions/1456314/open-url-with-windows-explorer/1456333#1456333
// - https://superuser.com/questions/36728/can-i-launch-urls-from-command-line-in-windows/36730#36730
win32: [ 'rundll32.exe', 'url.dll,OpenURL' ],

darwin: [ 'open' ],

android: [ 'termux-open' ] // TODO: may have other options?
}

// open Path or File with System Default
const getDefaultOpen = () => {
const defaultOpen = DEFAULT_OPEN_MAP[ process.platform ]
if (!defaultOpen) throw new Error(`unsupported platform: ${process.platform}`)
return defaultOpen
// open URL or File with System Default, no need `{ shell: true }` os no extra escaping needed
const getDefaultOpenCommandList = () => {
const defaultOpenCommandList = DEFAULT_OPEN_COMMAND_LIST_MAP[ process.platform ]
if (!defaultOpenCommandList) throw new Error(`unsupported platform: ${process.platform}`)
return defaultOpenCommandList // [ command, ...prefixArgList ]
}

export { getDefaultOpen }
export { getDefaultOpenCommandList }

// ultimate URL test:
// require('child_process').spawnSync('rundll32.exe', [ 'url.dll,OpenURL', 'http://localhost?a=1#some://url?with=special&chars\'\":[]{}()!@#$%^&*-=_+<>,.?/\\' ])
// require('child_process').spawnSync('open', [ 'http://localhost?a=1#some://url?with=special&chars\'\":[]{}()!@#$%^&*-=_+<>,.?/\\' ])
// require('child_process').spawnSync('xdg-open', [ 'http://localhost?a=1#some://url?with=special&chars\'\":[]{}()!@#$%^&*-=_+<>,.?/\\' ])
// require('child_process').spawnSync('termux-open', [ 'http://localhost?a=1#some://url?with=special&chars\'\":[]{}()!@#$%^&*-=_+<>,.?/\\' ])
50 changes: 50 additions & 0 deletions source/node/system/ResolveCommand.js
@@ -0,0 +1,50 @@
import { spawnSync } from 'child_process'
import { resolve } from 'path'
import { run } from './Run'

const configureWin32 = (extList = (process.env.PATHEXT || '.EXE;.BAT;.CMD').toUpperCase().split(';')) => [ // process.env.PATHEXT // '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC'
'where.exe', // search cwd // https://ss64.com/nt/where.html
(stdoutString) => stdoutString.split('\r\n').find((path) => extList.includes(path.slice(path.lastIndexOf('.')).toUpperCase())) || ''
]
const configureLinux = () => [
'which', // do not search cwd // https://ss64.com/bash/which.html
(stdoutString) => stdoutString.trim()
]

const CONFIGURE_MAP = {
linux: configureLinux,
win32: configureWin32,
darwin: configureLinux,
android: configureLinux
}

let cacheConfigure
const configureCached = () => {
if (cacheConfigure === undefined) {
const configure = CONFIGURE_MAP[ process.platform ]
if (!configure) throw new Error(`unsupported platform: ${process.platform}`)
cacheConfigure = configure()
}
return cacheConfigure
}

const resolveCommandName = (commandName, cwd) => { // if not found, result in empty string: ""
const [ checkCommand, processFunc ] = configureCached()
return processFunc(String(spawnSync(checkCommand, [ commandName ], { cwd }).stdout || ''))
}

const resolveCommandNameAsync = async (commandName, cwd) => { // if not found, result in empty string: ""
const [ checkCommand, processFunc ] = configureCached()
const { promise, stdoutPromise } = run({ command: checkCommand, argList: [ commandName ], option: { cwd }, quiet: true })
return processFunc(String(await promise.then(() => stdoutPromise, () => '')))
}

// try resolve command, fast resolve if command itself contain path sep
const resolveCommand = (command, cwd) => REGEXP_PATH_SEP.test(command) ? resolve(cwd || '', command) : resolveCommandName(command, cwd)
const resolveCommandAsync = async (command, cwd) => REGEXP_PATH_SEP.test(command) ? resolve(cwd || '', command) : resolveCommandNameAsync(command, cwd)
const REGEXP_PATH_SEP = /[\\/]/

export {
resolveCommandName, resolveCommandNameAsync,
resolveCommand, resolveCommandAsync
}
36 changes: 36 additions & 0 deletions source/node/system/ResolveCommand.test.js
@@ -0,0 +1,36 @@
import { strictEqual } from 'source/common/verify'
import {
resolveCommandName,
resolveCommandNameAsync
} from './ResolveCommand'

const { describe, it, info = console.log } = global

describe('Node.Module.ResolveCommand', () => {
const COMMAND_NAME_LIST = [
// [ commandName, isExpectResult ]
[ process.platform === 'win32' ? 'ipconfig' : 'ifconfig', true ],
[ 'npm', true ],
[ 'npx', true ],
[ 'node', true ],
[ 'git', true ],
[ 'tar', true ],
[ 'noop-0123456789', false ] // non-exist commandName should return ""
]

it('resolveCommandName()', () => {
for (const [ commandName, isExpectResult ] of COMMAND_NAME_LIST) {
const result = resolveCommandName(commandName)
info(`${JSON.stringify(commandName)} => ${JSON.stringify(result)}`)
strictEqual(Boolean(result), isExpectResult)
}
})

it('resolveCommandNameAsync()', async () => {
for (const [ commandName, isExpectResult ] of COMMAND_NAME_LIST) {
const result = await resolveCommandNameAsync(commandName)
info(`${JSON.stringify(commandName)} => ${JSON.stringify(result)}`)
strictEqual(Boolean(result), isExpectResult)
}
})
})

0 comments on commit 9b71324

Please sign in to comment.