Comparing version 0.2.5 to 0.3.0-0
@@ -16,1 +16,5 @@ #### v0.2.4 | ||
- __修复__: 隐藏文件夹跳过处理 | ||
#### 20201216 | ||
- __优化__: 优化交互式删除二级目录选择步骤 |
389
lib/index.js
@@ -1,225 +0,176 @@ | ||
const fs = require("fs-extra"); | ||
const path = require("path"); | ||
const warning = require("./warning"); | ||
const execa = require("execa"); | ||
const log = require("./log"); | ||
const chalk = require("chalk"); | ||
const { | ||
checkLinkExist, | ||
checkDirectory, | ||
checkFindLevels, | ||
checkLevels, | ||
getLinkPath, | ||
getDirBasePath | ||
} = require('./utils'); | ||
const getExts = require('./exts'); | ||
const updateSourceAndDest = require("./updateSourceAndDest"); | ||
const configPath = require("./configPath"); | ||
const inquirer = require('inquirer'); | ||
const resolvePath = path.resolve; | ||
function getRealDestPath(sourcePath, destPath, saveLevels, startPath, s) { | ||
const relativePath = path | ||
.relative(startPath, path.resolve(sourcePath)) || s.replace(path.extname(s), ''); | ||
return path.resolve( | ||
destPath, | ||
relativePath | ||
.split(path.sep) | ||
.slice(-saveLevels) | ||
.join(path.sep) | ||
); | ||
return destPath; | ||
} | ||
function getSource(answers) { | ||
const isSecondDir = answers.sourcePath === '二级目录'; | ||
const finalSourceDir = isSecondDir ? path.join(answers.sourcePathSecond, answers.secondDir) : answers.sourcePath; | ||
const sourceDir = isSecondDir ? path.dirname(finalSourceDir) : finalSourceDir; | ||
return [finalSourceDir, sourceDir] | ||
} | ||
function parseInput(input) { | ||
if (input.length === 1) { | ||
return [resolvePath(process.cwd()), resolvePath(input[0])] | ||
} else if (input.length >= 2) { | ||
return [resolvePath(input[0]), resolvePath(input[1])] | ||
} | ||
} | ||
async function hardLink(input, options) { | ||
const { s, e, m, d } = options; | ||
let source = false; | ||
let dest = false; | ||
const isDelete = !!d; | ||
let deleteDir = false; | ||
let isSecondDir = false; | ||
let sourceDir = false; | ||
if (!isDelete) { | ||
warning(!input.length, "必须指定目标地址"); | ||
const parsedPaths = parseInput(input); | ||
source = parsedPaths[0] | ||
sourceDir = parsedPaths[0] | ||
dest = parsedPaths[1] | ||
} else { | ||
const mapJsonNotExist = !fs.existsSync(configPath.mapJson) | ||
const noSaveRecord = !mapJsonNotExist && !Object.keys(fs.readJSONSync(configPath.mapJson)).length; | ||
warning((mapJsonNotExist || noSaveRecord) && !input.length, "你没有创建记录,你必须手动指定目标地址及源地址来进行删除操作"); | ||
const answerDeleteMode = await inquirer.prompt([{ | ||
type: 'rawlist', | ||
message: '请选择删除模式?', | ||
name: 'deleteDir', | ||
choices: ['仅仅删除文件', '删除硬链文件以及其所在目录'], | ||
default: '删除硬链文件以及其所在目录' | ||
}]) | ||
deleteDir = answerDeleteMode.deleteDir === '删除硬链文件以及其所在目录'; | ||
if (input.length) { | ||
const parsedPaths = parseInput(input); | ||
source = parsedPaths[0] | ||
sourceDir = parsedPaths[0] | ||
dest = parsedPaths[1] | ||
} else { | ||
const pathsMap = fs.readJSONSync(configPath.mapJson); | ||
const answers = await inquirer.prompt([{ | ||
type: 'rawlist', | ||
name: 'sourcePath', | ||
message: '请选择需要删除硬链的源地址', | ||
choices: Object.keys(pathsMap).concat('二级目录') | ||
}, { | ||
message: '你需要选择哪个文件夹的二级目录', | ||
type: 'rawlist', | ||
name: 'sourcePathSecond', | ||
when: (answer) => { | ||
return answer.sourcePath === '二级目录' | ||
}, | ||
choices: Object.keys(pathsMap).map(s => ({ | ||
value: s, | ||
name: s + '的二级目录' | ||
})) | ||
}, { | ||
type: 'rawlist', | ||
name: 'secondDir', | ||
message: "请选择一个二级目录作为源地址", | ||
when: (answer) => { | ||
return !!answer.sourcePathSecond | ||
}, | ||
choices: (answer) => { | ||
const file = fs.readdirSync(answer.sourcePathSecond).filter((s) => { | ||
return fs.statSync(path.join(answer.sourcePathSecond, s)).isDirectory() | ||
}) | ||
return file | ||
} | ||
}, { | ||
type: 'rawlist', | ||
name: 'destDir', | ||
message: "该源地址对应两个目标地址,请选择一个", | ||
when: (answer) => { | ||
const choiceDest = pathsMap[getSource(answer)[1]]; | ||
return choiceDest && choiceDest.length > 1; | ||
}, | ||
choices: (answer) => { | ||
return pathsMap[getSource(answer)[1]] | ||
} | ||
}]) | ||
const [realSource, createdSourceDir] = getSource(answers); | ||
source = realSource; | ||
sourceDir = createdSourceDir | ||
dest = answers.destDir || pathsMap[createdSourceDir][0]; | ||
isSecondDir = answers.sourcePath === '二级目录' | ||
} | ||
} | ||
checkDirectory(source, dest); | ||
const exts = e.split(","); | ||
const saveLevels = +s; | ||
const maxFindLevels = +m; | ||
checkLevels(saveLevels); | ||
checkFindLevels(maxFindLevels); | ||
const messageMap = { | ||
e: " 包含的后缀有:", | ||
m: " 源地址最大查找层级为:", | ||
s: " 硬链保存模式:" | ||
}; | ||
if (!isDelete) { | ||
log.info("开始创建硬链..."); | ||
log.info("当前配置为:"); | ||
Object.keys(messageMap).forEach(k => { | ||
if(options[k]) { | ||
log.info(`${messageMap[k]}${chalk.cyanBright(options[k])}`); | ||
} | ||
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
} else { | ||
log.info("开始删除硬链...") | ||
} | ||
function start(sourcePath, currentLevel = 1) { | ||
if (currentLevel > maxFindLevels) { | ||
return; | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (_) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
} | ||
const sourceDirContent = fs.readdirSync(sourcePath); | ||
sourceDirContent.forEach(async s => { | ||
const extname = path.extname(s).replace(".", ""); | ||
const filePath = resolvePath(sourcePath, s); | ||
if (fs.lstatSync(filePath).isDirectory()) { | ||
if (!s.startsWith(".")) { | ||
await start(filePath, currentLevel + 1); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var fs_extra_1 = __importDefault(require("fs-extra")); | ||
var path_1 = __importDefault(require("path")); | ||
var utils_1 = require("./utils"); | ||
var execa_1 = __importDefault(require("execa")); | ||
var chalk_1 = __importDefault(require("chalk")); | ||
var saveRecord_1 = __importDefault(require("./config/saveRecord")); | ||
var parse_1 = __importDefault(require("./utils/parse")); | ||
var HlinkError_1 = __importDefault(require("./utils/HlinkError")); | ||
var resolvePath = path_1.default.resolve; | ||
function hardLink(input, options) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
function start(currentDir, currentLevel) { | ||
var _this = this; | ||
if (currentLevel === void 0) { currentLevel = 1; } | ||
if (currentLevel > maxFindLevel) { | ||
return; | ||
} | ||
var currentDirContents = fs_extra_1.default.readdirSync(currentDir); | ||
currentDirContents.forEach(function (name) { return __awaiter(_this, void 0, void 0, function () { | ||
var extname, fileFullPath, realDestPath, linkPaths, sourceNameForMessage, destNameForMessage; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: | ||
extname = path_1.default | ||
.extname(name) | ||
.replace('.', '') | ||
.toLowerCase(); | ||
fileFullPath = resolvePath(currentDir, name); | ||
if (!fs_extra_1.default.lstatSync(fileFullPath).isDirectory()) return [3 /*break*/, 3]; | ||
if (!!name.startsWith('.')) return [3 /*break*/, 2]; | ||
return [4 /*yield*/, start(fileFullPath, currentLevel + 1)]; | ||
case 1: | ||
_a.sent(); | ||
_a.label = 2; | ||
case 2: return [3 /*break*/, 4]; | ||
case 3: | ||
if (isWhiteList | ||
? exts.indexOf(extname) > -1 | ||
: excludeExts.indexOf(extname) === -1) { | ||
totalCount += 1; | ||
realDestPath = utils_1.getRealDestPath(fileFullPath, source, dest, saveMode); | ||
if (isDelete) { | ||
// 删除硬链接 | ||
try { | ||
linkPaths = utils_1.getLinkPath(fileFullPath, dest, isDeleteDir); | ||
if (linkPaths.length) { | ||
linkPaths.forEach(function (removePath) { | ||
execa_1.default.sync('rm', ['-r', removePath]); | ||
var deletePathMessage = chalk_1.default.cyan(utils_1.getDirBasePath(dest, removePath)); | ||
utils_1.log.info(isDeleteDir ? '目录' : '硬链', deletePathMessage, '删除成功'); | ||
successCount += 1; | ||
}); | ||
} | ||
else { | ||
jumpCount += 1; | ||
utils_1.log.warn('没找到', chalk_1.default.yellow(utils_1.getDirBasePath(dest, fileFullPath)), '相关硬链, 跳过'); | ||
} | ||
} | ||
catch (e) { | ||
if (e.message === 'ALREADY_DELETE') { | ||
utils_1.log.warn('目录', chalk_1.default.yellow(utils_1.getDirBasePath(dest, realDestPath)), '已删除, 跳过'); | ||
jumpCount += 1; | ||
} | ||
else { | ||
utils_1.log.error(e); | ||
failCount += 1; | ||
} | ||
} | ||
} | ||
else { | ||
sourceNameForMessage = chalk_1.default.yellow(utils_1.getDirBasePath(source, fileFullPath)); | ||
destNameForMessage = chalk_1.default.cyan(utils_1.getDirBasePath(dest, path_1.default.join(realDestPath, name))); | ||
try { | ||
if (utils_1.checkLinkExist(fileFullPath, dest)) { | ||
throw new HlinkError_1.default('File exists'); | ||
} | ||
else { | ||
fs_extra_1.default.ensureDirSync(realDestPath); | ||
} | ||
execa_1.default.sync('ln', [fileFullPath, realDestPath]); | ||
utils_1.log.success('源地址', sourceNameForMessage, '硬链成功, 硬链地址为', destNameForMessage); | ||
successCount += 1; | ||
} | ||
catch (e) { | ||
if (!e.stderr || e.stderr.indexOf('File exists') === -1) { | ||
utils_1.log.error(e); | ||
failCount += 1; | ||
} | ||
else { | ||
utils_1.log.warn('源地址', sourceNameForMessage, '硬链已存在, 跳过创建'); | ||
jumpCount += 1; | ||
} | ||
} | ||
} | ||
} | ||
else { | ||
totalCount += 1; | ||
utils_1.log.warn('当前文件', chalk_1.default.yellow(name), '不满足配置条件, 跳过创建'); | ||
jumpCount += 1; | ||
} | ||
_a.label = 4; | ||
case 4: return [2 /*return*/]; | ||
} | ||
}); | ||
}); }); | ||
} | ||
// 地址继续循环 | ||
} else if (getExts(exts).indexOf(extname.toLowerCase()) > -1) { | ||
const realDestPath = getRealDestPath( | ||
sourcePath, | ||
dest, | ||
saveLevels, | ||
source, | ||
s, | ||
); | ||
if (isDelete) { | ||
// 删除硬链接 | ||
try { | ||
const linkPaths = getLinkPath(filePath, dest, deleteDir) | ||
linkPaths.map((removePath) => { | ||
execa.sync('rm', ['-r', removePath]) | ||
const deletePathMessage = chalk.cyan(getDirBasePath(dest, removePath)); | ||
log.info(`${deleteDir ? '目录' : '硬链'} ${deletePathMessage} 已删除`) | ||
}) | ||
// } | ||
} catch (e) { | ||
if (e.message === 'ALREADY_DELETE') { | ||
log.warn(`目录 ${chalk.cyan(getDirBasePath(dest, realDestPath))} 已删除`) | ||
var isSecondDir, _a, source, saveMode, dest, isDelete, maxFindLevel, exts, excludeExts, sourceDir, isDeleteDir, isWhiteList, successCount, jumpCount, failCount, totalCount; | ||
return __generator(this, function (_b) { | ||
switch (_b.label) { | ||
case 0: | ||
isSecondDir = false; | ||
return [4 /*yield*/, parse_1.default(input, options)]; | ||
case 1: | ||
_a = _b.sent(), source = _a.source, saveMode = _a.saveMode, dest = _a.dest, isDelete = _a.isDelete, maxFindLevel = _a.maxFindLevel, exts = _a.exts, excludeExts = _a.excludeExts, sourceDir = _a.sourceDir, isDeleteDir = _a.isDeleteDir; | ||
isWhiteList = !!exts.length; | ||
utils_1.startLog({ | ||
extname: isWhiteList ? exts : excludeExts, | ||
maxLevel: maxFindLevel, | ||
saveMode: saveMode, | ||
source: source, | ||
dest: dest | ||
}, isWhiteList, isDelete); | ||
successCount = 0; | ||
jumpCount = 0; | ||
failCount = 0; | ||
totalCount = 0; | ||
start(source); | ||
saveRecord_1.default(sourceDir, dest, isDelete && !isSecondDir); | ||
utils_1.endLog(successCount, failCount, jumpCount, totalCount, isDelete); | ||
return [2 /*return*/]; | ||
} | ||
} | ||
} else { | ||
// 做硬链接 | ||
const sourceNameForMessage = chalk.yellow(getDirBasePath(source, filePath)); | ||
const destNameForMessage = chalk.cyan(getDirBasePath(dest, path.join(realDestPath, s))); | ||
try { | ||
if (checkLinkExist(filePath, dest)) { | ||
throw { stderr: 'File exists' } | ||
} else { | ||
fs.ensureDirSync(realDestPath); | ||
} | ||
execa.sync("ln", [filePath, realDestPath]); | ||
log.success( | ||
`源地址 ${sourceNameForMessage} 硬链成功, 硬链地址为 ${destNameForMessage}` | ||
); | ||
} catch (e) { | ||
if (!e.stderr) { | ||
console.log(e); | ||
process.exit(0); | ||
} | ||
if (e.stderr.indexOf("File exists") === -1) { | ||
console.log(e); | ||
} else { | ||
log.warn( | ||
`源地址"${sourceNameForMessage}"硬链已存在, 跳过创建` | ||
); | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}); | ||
} | ||
start(source); | ||
updateSourceAndDest(sourceDir, dest, isDelete && !isSecondDir) | ||
} | ||
module.exports = hardLink; | ||
exports.default = hardLink; |
{ | ||
"name": "hlink", | ||
"version": "0.2.5", | ||
"version": "0.3.0-0", | ||
"description": "hlink", | ||
@@ -12,3 +12,3 @@ "license": "MIT", | ||
"bin": { | ||
"hlink": "cli.js" | ||
"hlink": "./lib/cli.js" | ||
}, | ||
@@ -20,6 +20,12 @@ "engines": { | ||
"test": "xo && ava", | ||
"start": "./cli.js ./sourceDir ./destDir" | ||
"start": "hlink ./sourceDir ./destDir", | ||
"dev": "tsc -w", | ||
"clean": "rm -rf lib", | ||
"cp": "cp src/config/hlink.config.tpl lib/config/hlink.config.tpl", | ||
"build": "npm run clean && tsc && npm run cp", | ||
"prepublishOnly": "npm run build", | ||
"np": "np --no-cleanup --no-tests", | ||
"np:beta": "np --tag=beta --any-branch --no-cleanup --no-tests" | ||
}, | ||
"files": [ | ||
"cli.js", | ||
"lib" | ||
@@ -34,2 +40,5 @@ ], | ||
"dependencies": { | ||
"@types/fs-extra": "^9.0.6", | ||
"@types/inquirer": "^7.3.1", | ||
"@types/node": "^14.14.25", | ||
"chalk": "^4.1.0", | ||
@@ -42,5 +51,14 @@ "execa": "^2.1.0", | ||
"devDependencies": { | ||
"@types/meow": "^5.0.0", | ||
"@typescript-eslint/eslint-plugin": "^4.14.2", | ||
"ava": "^2.1.0", | ||
"eslint": "^7.15.0", | ||
"eslint-config-standard-with-typescript": "^20.0.0", | ||
"eslint-plugin-import": "^2.22.1", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-promise": "^4.2.1", | ||
"np": "^7.4.0", | ||
"typescript": "^4.1.5", | ||
"xo": "^0.24.0" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
50186
15
874
8
11
9
1
+ Added@types/fs-extra@^9.0.6
+ Added@types/inquirer@^7.3.1
+ Added@types/node@^14.14.25
+ Added@types/fs-extra@9.0.13(transitive)
+ Added@types/inquirer@7.3.3(transitive)
+ Added@types/node@14.18.63(transitive)
+ Added@types/through@0.0.33(transitive)