Comparing version 1.0.3 to 1.0.4
279
cjs/index.js
@@ -13,22 +13,38 @@ 'use strict'; | ||
async function makeFileInfo(root, relative) { | ||
const absolute = path__default['default'].join(root, relative); | ||
const { base, name, ext } = path__default['default'].parse(absolute); | ||
const info = await fs__default['default'].promises.stat(absolute); | ||
const { size, birthtimeMs: created, mtimeMs: modified } = info; | ||
const isDirectory = info.isDirectory(); | ||
return { | ||
isDirectory, | ||
hidden: name.startsWith('.'), | ||
relative, | ||
absolute, | ||
size: isDirectory ? 0 : size, | ||
base, | ||
name, | ||
ext, | ||
created, | ||
modified, | ||
}; | ||
function isDictionary(obj) { | ||
return obj !== null && typeof obj === 'object' && Array.isArray(obj) === false; | ||
} | ||
isDictionary.TYPE_NAME = 'Dictionary'; | ||
function isFunction(obj) { | ||
return typeof obj === 'function'; | ||
} | ||
isFunction.TYPE_NAME = 'UnknownFunction'; | ||
function isBoolean(obj) { | ||
return typeof obj === 'boolean'; | ||
} | ||
isBoolean.TYPE_NAME = 'boolean'; | ||
function isString(obj) { | ||
return typeof obj === 'string'; | ||
} | ||
isString.TYPE_NAME = 'string'; | ||
function isNumber(obj) { | ||
return typeof obj === 'number'; | ||
} | ||
isNumber.TYPE_NAME = 'number'; | ||
function isIndex(obj) { | ||
return typeof obj === 'number' || typeof obj === 'string'; | ||
} | ||
isIndex.TYPE_NAME = 'Index'; | ||
function isPrimitive(obj) { | ||
return typeof obj === 'number' || typeof obj === 'string' || typeof obj === 'boolean'; | ||
} | ||
isPrimitive.TYPE_NAME = 'Primitive'; | ||
function isIndexable(obj) { | ||
return obj !== null && typeof obj === 'object'; | ||
} | ||
isIndexable.TYPE_NAME = 'Indexable'; | ||
function isArray(obj) { | ||
return Array.isArray(obj); | ||
} | ||
isArray.TYPE_NAME = 'unknown[]'; | ||
function isNullish(obj) { | ||
@@ -43,2 +59,66 @@ return obj === null || typeof obj === 'undefined'; | ||
function isJSONValue(val) { | ||
switch (typeof val) { | ||
case 'string': | ||
case 'number': | ||
case 'boolean': | ||
return true; | ||
case 'object': { | ||
if (val === null) { | ||
return true; | ||
} | ||
if (Array.isArray(val)) { | ||
return val.every(isJSONValue); | ||
} | ||
return Object.values(val).every(isJSONValue); | ||
} | ||
} | ||
return false; | ||
} | ||
isJSONValue.TYPE_NAME = 'JSONValue'; | ||
function isJSONArray(val) { | ||
return Array.isArray(val); | ||
} | ||
isJSONArray.TYPE_NAME = 'JSONArray'; | ||
function isJSONObject(val) { | ||
return val !== null && typeof val === 'object' && !Array.isArray(val); | ||
} | ||
isJSONObject.TYPE_NAME = 'JSONObject'; | ||
function stringifyDefinition(value) { | ||
if (typeof value === 'string') { | ||
return value; | ||
} | ||
const elements = Object.entries(value); | ||
if (elements.length === 0) { | ||
return '{}'; | ||
} | ||
return `{ ${elements.map(([k, v]) => `${k}: ${stringifyDefinition(v)}`).join(', ')} }`; | ||
} | ||
function inspectType(value, opts = {}) { | ||
const { maxDepth = 3 } = opts; | ||
if (typeof value !== 'object') { | ||
return typeof value; | ||
} | ||
if (value === null) { | ||
return 'null'; | ||
} | ||
if (Array.isArray(value)) { | ||
return 'Array'; | ||
} | ||
if (value.constructor.name !== 'Object') { | ||
return value.constructor.name; | ||
} | ||
if (maxDepth <= 0) { | ||
return 'Dictionary'; | ||
} | ||
const result = {}; | ||
const nestedOptions = { ...opts, maxDepth: maxDepth - 1 }; | ||
for (const [k, v] of Object.entries(value)) { | ||
result[k] = inspectType(v, nestedOptions); | ||
} | ||
return stringifyDefinition(result); | ||
} | ||
function asDefined(obj, fallback) { | ||
@@ -51,42 +131,5 @@ if (isDefined(obj)) { | ||
} | ||
throw new Error("Unable to cast " + typeof obj + " to NonNullable<unknown>"); | ||
throw new Error(`Unable to cast ${inspectType(obj)} to NonNullable<unknown>`); | ||
} | ||
/*! ***************************************************************************** | ||
Copyright (c) Microsoft Corporation. | ||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted. | ||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH | ||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY | ||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, | ||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM | ||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR | ||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR | ||
PERFORMANCE OF THIS SOFTWARE. | ||
***************************************************************************** */ | ||
/* global Reflect, Promise */ | ||
var extendStatics = function(d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
function __extends(d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
} | ||
((function (_super) { | ||
__extends(TypeAssertion, _super); | ||
function TypeAssertion() { | ||
return _super !== null && _super.apply(this, arguments) || this; | ||
} | ||
return TypeAssertion; | ||
})(Error)); | ||
function deferred() { | ||
@@ -110,5 +153,5 @@ let resolve; | ||
while (pending.length > 0) { | ||
const next = asDefined(pending.shift()); | ||
const [info, depth] = asDefined(pending.shift()); | ||
try { | ||
await fn(next); | ||
await fn(info, depth); | ||
} | ||
@@ -120,3 +163,3 @@ catch (e) { | ||
try { | ||
await recover(e, next[0]); | ||
await recover(e, info.relative); | ||
recovered = true; | ||
@@ -142,9 +185,9 @@ } | ||
return { | ||
add(location, depth) { | ||
add(info, depth) { | ||
if (stopped) { | ||
return; | ||
} | ||
pending.push([location, depth]); | ||
pending.push([info, depth]); | ||
if (running < concurrency) { | ||
runThread(); | ||
void runThread(); | ||
} | ||
@@ -160,4 +203,38 @@ }, | ||
async function makeFileInfo(root, relative) { | ||
const absolute = path__default["default"].join(root, relative); | ||
const { base, name, ext } = path__default["default"].parse(absolute); | ||
const info = await fs__default["default"].promises.stat(absolute); | ||
const { size, birthtimeMs: created, mtimeMs: modified } = info; | ||
const isDirectory = info.isDirectory(); | ||
return { | ||
isDirectory, | ||
hidden: name.startsWith('.'), | ||
relative, | ||
absolute, | ||
size: isDirectory ? 0 : size, | ||
base, | ||
name, | ||
ext, | ||
created, | ||
modified, | ||
}; | ||
} | ||
async function extendFileInfo(entry) { | ||
const { base, name, ext } = path__default["default"].parse(entry.absolute); | ||
const info = await fs__default["default"].promises.stat(entry.absolute); | ||
const { size, birthtimeMs: created, mtimeMs: modified } = info; | ||
return { | ||
...entry, | ||
size: entry.isDirectory ? 0 : size, | ||
base, | ||
name, | ||
ext, | ||
created, | ||
modified, | ||
}; | ||
} | ||
function createInspector(options = {}) { | ||
const { exclude, filter, map, concurrency = 8, maxDepth = Infinity, catch: recover, includeFolders = false, includeHidden = false } = options; | ||
const { exclude, filter, map, concurrency = 8, maxDepth = Infinity, minDepth = 0, catch: recover, type, includeFolders, includeHidden = false } = options; | ||
if (!isValidCount(concurrency)) { | ||
@@ -169,35 +246,75 @@ throw new Error(`Invalid concurrency value ${concurrency}. Expected either a positive non-zero integer, or Infinity.`); | ||
} | ||
if (isDefined(type) && isDefined(includeFolders)) { | ||
throw new Error('Clashing arguments "type" and "includeFolder" specified. Use "type: all" to include files and folders in your output.'); | ||
} | ||
let includeTypes = type ?? 'files'; | ||
if (includeFolders) { | ||
includeTypes = 'all'; | ||
} | ||
if (minDepth !== 0 && !isValidCount(minDepth)) { | ||
throw new Error(`Invalid minDepth value ${minDepth}. Expected either a positive integer, or Infinity.`); | ||
} | ||
if (minDepth > maxDepth) { | ||
throw new Error('Invalid depth range. Expected minDepth to be less than or equal to maxDepth.'); | ||
} | ||
return { | ||
async search(location) { | ||
const results = []; | ||
const root = path__default['default'].resolve(location); | ||
const processEntry = async ([relative, depth]) => { | ||
const info = await makeFileInfo(root, relative); | ||
if (info.hidden && !includeHidden) { | ||
const processEntry = async (basicInfo, depth) => { | ||
let fullInfo = null; | ||
if (basicInfo.hidden && !includeHidden) { | ||
return; | ||
} | ||
if (info.isDirectory) { | ||
if (depth < maxDepth) { | ||
if (exclude && await exclude(info)) { | ||
if (basicInfo.isDirectory) { | ||
if (exclude) { | ||
fullInfo = await extendFileInfo(basicInfo); | ||
if (await exclude(fullInfo)) { | ||
return; | ||
} | ||
for (const entry of await fs__default['default'].promises.readdir(info.absolute)) { | ||
add(path__default['default'].join(relative, entry), depth + 1); | ||
} | ||
if (depth < maxDepth) { | ||
for (const entry of await fs__default["default"].promises.readdir(basicInfo.absolute, { withFileTypes: true })) { | ||
const child = { | ||
isDirectory: entry.isDirectory(), | ||
hidden: entry.name.startsWith('.'), | ||
relative: path__default["default"].join(basicInfo.relative, entry.name), | ||
absolute: path__default["default"].join(basicInfo.absolute, entry.name), | ||
}; | ||
add(child, depth + 1); | ||
} | ||
} | ||
if (!includeFolders) { | ||
if (includeTypes === 'files') { | ||
return; | ||
} | ||
} | ||
if (!filter || await filter(info)) { | ||
if (map) { | ||
results.push(await map(info)); | ||
else if (includeTypes === 'folders') { | ||
return; | ||
} | ||
if (depth < minDepth) { | ||
return; | ||
} | ||
if (fullInfo === null) { | ||
fullInfo = await extendFileInfo(basicInfo); | ||
} | ||
if (filter) { | ||
if (!await filter(fullInfo)) { | ||
return; | ||
} | ||
else { | ||
results.push(info); | ||
} | ||
} | ||
const output = map ? await map(fullInfo) : fullInfo; | ||
results.push(output); | ||
}; | ||
const { add, complete } = queue({ concurrency, fn: processEntry, recover }); | ||
add('', 0); | ||
let rootEntry; | ||
try { | ||
rootEntry = await makeFileInfo(path__default["default"].resolve(location), ''); | ||
} | ||
catch (e) { | ||
if (recover) { | ||
await recover(e, ''); | ||
return []; | ||
} | ||
throw e; | ||
} | ||
add(rootEntry, 0); | ||
await complete; | ||
@@ -204,0 +321,0 @@ return results; |
{ | ||
"name": "fs-inspect", | ||
"version": "1.0.3", | ||
"version": "1.0.4", | ||
"description": "directory tree scanner for Node.js", | ||
@@ -9,17 +9,17 @@ "main": "cjs/index.js", | ||
"devDependencies": { | ||
"@rollup/plugin-node-resolve": "^11.2.0", | ||
"@types/jest": "^26.0.14", | ||
"@typescript-eslint/eslint-plugin": "^4.3.0", | ||
"@typescript-eslint/parser": "^4.3.0", | ||
"eslint": "^7.10.0", | ||
"jest": "^26.5.0", | ||
"rollup": "^2.32.0", | ||
"rollup-plugin-typescript2": "^0.28.0", | ||
"ts-jest": "^26.4.1", | ||
"ts-runtime-typecheck": "^2.2.0", | ||
"typescript": "^4.1.2" | ||
"@rollup/plugin-node-resolve": "^13.3.0", | ||
"@types/jest": "^28.1.1", | ||
"@typescript-eslint/eslint-plugin": "^5.28.0", | ||
"@typescript-eslint/parser": "^5.28.0", | ||
"eslint": "^8.17.0", | ||
"jest": "^28.1.1", | ||
"rollup": "^2.75.6", | ||
"rollup-plugin-typescript2": "^0.32.1", | ||
"ts-jest": "^28.0.5", | ||
"ts-runtime-typecheck": "^2.6.0", | ||
"typescript": "^4.7.3" | ||
}, | ||
"scripts": { | ||
"test": "jest --verbose --coverage", | ||
"lint": "eslint", | ||
"lint": "eslint src", | ||
"build": "rollup -c && cp ./package.json ./dist/package.json && cp ./README.md ./dist/README.md" | ||
@@ -26,0 +26,0 @@ }, |
@@ -39,3 +39,3 @@ # fs-inspect | ||
Before each item is added to the results it can optionally be omitted using a 'filter' function. If 'includeFolders' is true then folders will be passed to the filter function, but the filter will not prevent the children of the folder from being visited. | ||
Before each item is added to the results it can optionally be omitted using a 'filter' function. If folders are included in the output then they will be passed to the filter function, but the filter will not prevent the children of the folder from being visited. | ||
@@ -59,3 +59,3 @@ ```typescript | ||
When a folder is visited an optional 'exclude' function can be used to indicate that the children of the folder should be skipped, which has obvious performance advantages if you wish to skip large parts of the file tree. If 'includeFolders' is true then excluded folders will not be included in the results, so you will not have to use 'filter' to skip them. | ||
When a folder is visited an optional 'exclude' function can be used to indicate that the children of the folder should be skipped, which has obvious performance advantages if you wish to skip large parts of the file tree. If folders are included in the output then excluded folders will not be included in the results, so you will not have to use 'filter' to skip them. | ||
@@ -124,4 +124,6 @@ ```typescript | ||
If you have a very deep file tree and you are only interested in the top couple of levels you can specify a depth limit with the `maxDepth` option. By default there is no limit (Infinity). The minimum value is 1, which is _just_ the direct children of the target folder. | ||
If you have a very deep file tree and you are only interested in the top couple of levels you can specify a depth limit with the `maxDepth` option. By default there is no limit (Infinity). The minimum value is 1, which is *just* the direct children of the target folder. | ||
Additionally if you are only interested in the results inside a subfolder then `minDepth` can be used to filter out results from the top levels. It defaults to 0 which indicates the inclusion of the root entry. | ||
## Hidden files/folders | ||
@@ -133,7 +135,7 @@ | ||
Folders are not added to the output by default, you can include them using the `includeFolders` option. | ||
Folders are not added to the output by default, you can include them by setting the `type` option to `all` or `folders`. These include files and folders, or just folders respectively. | ||
## Root entry | ||
If the target location for the search is a file, then only that file will be included in the output. Providing that it passes any filters you have specified. If it's a folder and `includeFolders` is set then it will be included in the output. In both cases the relative path will be an empty string, indicating that it is the root of the tree. | ||
If the target location for the search is a file, then only that file will be included in the output. Providing that it passes any filters you have specified. If it's a folder and are included in the output normally then it will be included in the output. In both cases the relative path will be an empty string, indicating that it is the root of the tree. | ||
@@ -181,6 +183,10 @@ ## Error recovery | ||
- ### InspectorOptions.includeFolders | ||
- ### InspectorOptions.type | ||
An optional boolean flag that causes folders to be included in the output. Default value is false. | ||
An optional string which indicates if `'files'`, `'folders'` or `'all'` should be included in the output. Default value is `'files'`. | ||
- ### InspectorOptions.includeFolders | ||
**Depreciated** optional boolean flag that indicates that folders should be included in the output. Use `type: 'all'` instead. | ||
- ### InspectorOptions.concurrency | ||
@@ -194,2 +200,6 @@ | ||
- ### InspectorOptions.minDepth | ||
An optional positive integer which specified the minimum search depth through the folder tree. Infinity indicates no limit. 0 indicates all entries should be included. Default value is 0. | ||
- ### InspectorOptions.exclude | ||
@@ -196,0 +206,0 @@ |
Sorry, the diff of this file is not supported yet
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
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
34847
624
256
4