Convert window-network-drives.js to typescript

Change-type: patch
This commit is contained in:
Alexis Svinartchouk 2020-01-07 14:19:37 +01:00 committed by Lorenzo Alberto Maria Ambrosi
parent 255fae3a90
commit b266a72726
5 changed files with 134 additions and 146 deletions

View File

@ -1,143 +0,0 @@
/*
* Copyright 2019 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
const Bluebird = require('bluebird')
const cp = require('child_process')
const fs = require('fs')
const _ = require('lodash')
const os = require('os')
const Path = require('path')
const process = require('process')
const { promisify } = require('util')
const { tmpFileDisposer } = require('../../../shared/utils')
const readFileAsync = promisify(fs.readFile)
const execAsync = promisify(cp.exec)
/**
* @summary Returns wmic's output for network drives
* @function
*
* @returns {Promise<String>}
*
* @example
* const output = await getWmicNetworkDrivesOutput()
*/
exports.getWmicNetworkDrivesOutput = async () => {
// Exported for tests.
// When trying to read wmic's stdout directly from node, it is encoded with the current
// console codepage (depending on the computer).
// Decoding this would require getting this codepage somehow and using iconv as node
// doesn't know how to read cp850 directly for example.
// We could also use wmic's "/output:" switch but it doesn't work when the filename
// contains a space and the os temp dir may contain spaces ("D:\Windows Temp Files" for example).
// So we just redirect to a file and read it afterwards as we know it will be ucs2 encoded.
const options = {
// Close the file once it's created
discardDescriptor: true,
// Wmic fails with "Invalid global switch" when the "/output:" switch filename contains a dash ("-")
prefix: 'tmp'
}
return Bluebird.using(tmpFileDisposer(options), async ({ path }) => {
const command = [
Path.join(process.env.SystemRoot, 'System32', 'Wbem', 'wmic'),
'path',
'Win32_LogicalDisk',
'Where',
'DriveType="4"',
'get',
'DeviceID,ProviderName',
'>',
`"${path}"`
]
await execAsync(command.join(' '), { windowsHide: true })
return readFileAsync(path, 'ucs2')
})
}
/**
* @summary returns a Map of drive letter -> network locations on Windows
* @function
*
* @returns {Promise<Map<String, String>>} - 'Z:' -> '\\\\192.168.0.1\\Public'
*
* @example
* getWindowsNetworkDrives()
* .then(console.log);
*/
const getWindowsNetworkDrives = async () => {
const result = await exports.getWmicNetworkDrivesOutput()
const couples = _.chain(result)
.split('\n')
// Remove header line
// eslint-disable-next-line no-magic-numbers
.slice(1)
// Remove extra spaces / tabs / carriage returns
.invokeMap(String.prototype.trim)
// Filter out empty lines
.compact()
.map((str) => {
const colonPosition = str.indexOf(':')
// eslint-disable-next-line no-magic-numbers
if (colonPosition === -1) {
throw new Error(`Can't parse wmic output: ${result}`)
}
// eslint-disable-next-line no-magic-numbers
return [ str.slice(0, colonPosition + 1), _.trim(str.slice(colonPosition + 1)) ]
})
// eslint-disable-next-line no-magic-numbers
.filter((couple) => couple[1].length > 0)
.value()
return new Map(couples)
}
/**
* @summary Replaces network drive letter with network drive location in the provided filePath on Windows
* @function
*
* @param {String} filePath - file path
*
* @returns {String} - updated file path
*
* @example
* replaceWindowsNetworkDriveLetter('Z:\\some-file')
* .then(console.log);
*/
exports.replaceWindowsNetworkDriveLetter = async (filePath) => {
let result = filePath
if (os.platform() === 'win32') {
const matches = /^([A-Z]+:)\\(.*)$/.exec(filePath)
if (matches !== null) {
const [ , drive, relativePath ] = matches
const drives = await getWindowsNetworkDrives()
const location = drives.get(drive)
// eslint-disable-next-line no-undefined
if (location !== undefined) {
result = `${location}\\${relativePath}`
}
}
}
return result
}

View File

@ -0,0 +1,115 @@
/*
* Copyright 2019 balena.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { using } from 'bluebird';
import { exec } from 'child_process';
import { readFile } from 'fs';
import { chain, trim } from 'lodash';
import { platform } from 'os';
import { join } from 'path';
import { env } from 'process';
import { promisify } from 'util';
import { tmpFileDisposer } from '../../../shared/utils';
const readFileAsync = promisify(readFile);
const execAsync = promisify(exec);
/**
* @summary Returns wmic's output for network drives
*/
export async function getWmicNetworkDrivesOutput(): Promise<string> {
// Exported for tests.
// When trying to read wmic's stdout directly from node, it is encoded with the current
// console codepage (depending on the computer).
// Decoding this would require getting this codepage somehow and using iconv as node
// doesn't know how to read cp850 directly for example.
// We could also use wmic's "/output:" switch but it doesn't work when the filename
// contains a space and the os temp dir may contain spaces ("D:\Windows Temp Files" for example).
// So we just redirect to a file and read it afterwards as we know it will be ucs2 encoded.
const options = {
// Close the file once it's created
discardDescriptor: true,
// Wmic fails with "Invalid global switch" when the "/output:" switch filename contains a dash ("-")
prefix: 'tmp',
};
return using(tmpFileDisposer(options), async ({ path }) => {
const command = [
join(env.SystemRoot as string, 'System32', 'Wbem', 'wmic'),
'path',
'Win32_LogicalDisk',
'Where',
'DriveType="4"',
'get',
'DeviceID,ProviderName',
'>',
`"${path}"`,
];
await execAsync(command.join(' '), { windowsHide: true });
return readFileAsync(path, 'ucs2');
});
}
/**
* @summary returns a Map of drive letter -> network locations on Windows: 'Z:' -> '\\\\192.168.0.1\\Public'
*/
async function getWindowsNetworkDrives(): Promise<Map<string, string>> {
// Use getWindowsNetworkDrives from "exports." so it can be mocked in tests
const result = await exports.getWmicNetworkDrivesOutput();
const couples: Array<[string, string]> = chain(result)
.split('\n')
// Remove header line
.slice(1)
// Remove extra spaces / tabs / carriage returns
.invokeMap(String.prototype.trim)
// Filter out empty lines
.compact()
.map((str: string): [string, string] => {
const colonPosition = str.indexOf(':');
if (colonPosition === -1) {
throw new Error(`Can't parse wmic output: ${result}`);
}
return [
str.slice(0, colonPosition + 1),
trim(str.slice(colonPosition + 1)),
];
})
.filter(couple => couple[1].length > 0)
.value();
return new Map(couples);
}
/**
* @summary Replaces network drive letter with network drive location in the provided filePath on Windows
*/
export async function replaceWindowsNetworkDriveLetter(
filePath: string,
): Promise<string> {
let result = filePath;
if (platform() === 'win32') {
const matches = /^([A-Z]+:)\\(.*)$/.exec(filePath);
if (matches !== null) {
const [, drive, relativePath] = matches;
const drives = await getWindowsNetworkDrives();
const location = drives.get(drive);
if (location !== undefined) {
result = `${location}\\${relativePath}`;
}
}
}
return result;
}

20
npm-shrinkwrap.json generated
View File

@ -1193,9 +1193,9 @@
"dev": true
},
"@types/node": {
"version": "6.14.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.9.tgz",
"integrity": "sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w=="
"version": "12.12.24",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.24.tgz",
"integrity": "sha512-1Ciqv9pqwVtW6FsIUKSZNB82E5Cu1I2bBTj1xuIHXLe/1zYLl3956Nbhg2MzSYHVfl9/rmanjbQIb7LibfCnug=="
},
"@types/normalize-package-data": {
"version": "2.4.0",
@ -6054,6 +6054,13 @@
"unzip-stream": "^0.3.0",
"xxhash": "^0.3.0",
"yauzl": "^2.9.2"
},
"dependencies": {
"@types/node": {
"version": "6.14.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.9.tgz",
"integrity": "sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w=="
}
}
},
"event-emitter": {
@ -10037,6 +10044,13 @@
"@types/node": "^6.0.112",
"@types/usb": "^1.5.1",
"debug": "^3.1.0"
},
"dependencies": {
"@types/node": {
"version": "6.14.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-6.14.9.tgz",
"integrity": "sha512-leP/gxHunuazPdZaCvsCefPQxinqUDsCxCR5xaDUrY2MkYxQRFZZwU5e7GojyYsGB7QVtCi7iVEl/hoFXQYc+w=="
}
}
},
"node-releases": {

View File

@ -94,6 +94,7 @@
"@babel/plugin-proposal-function-bind": "^7.2.0",
"@babel/preset-env": "^7.6.0",
"@babel/preset-react": "^7.0.0",
"@types/node": "^12.12.24",
"@types/react-dom": "^16.8.4",
"babel-loader": "^8.0.4",
"chalk": "^1.1.3",

View File

@ -22,6 +22,7 @@ const m = require('mochainon')
const { env } = require('process')
const { promisify } = require('util')
// eslint-disable-next-line node/no-missing-require
const wnd = require('../../../lib/gui/app/os/windows-network-drives')
const readFileAsync = promisify(readFile)