Initial commit

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dinlo
2026-05-31 18:45:31 +08:00
commit e0a986eb30
1018 changed files with 615974 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 steelbrain
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:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+86
View File
@@ -0,0 +1,86 @@
# ScanDir
`sb-scandir` is a node module that supports simple file scanning with some sugar features.
## Installation
```
npm install --save sb-scandir
```
## API
```js
interface Result {
files: Array<string>
directories: Array<string>
}
interface FileSystem {
join(pathA: string, pathB: string): string
basename(path: string): string
stat(path: string): Promise<fs.Stats>
readdir(path: string): Promise<string[]>
}
type Validate = (path: string) => boolean
export const defaultFilesystem: FileSystem;
export default async function scanDirectory(
path: string,
{
recursive = true,
validate = null,
concurrency = Infinity,
fileSystem = defaultFilesystem,
}: {
recursive?: boolean
validate?: Validate | null
concurrency?: number
fileSystem?: Partial<FileSystem>
} = {},
): Promise<Result>;
```
## Examples
```js
import Path from 'path'
import scandir, { defaultFilesystem } from 'sb-scandir'
// or
const { default: scandir, defaultFilesystem } = require('sb-scandir')
// Scan all files except the dot ones
scandir(__dirname).then(function(result) {
console.log('files', result.files)
console.log('directories', result.directories)
})
// Scan all top level files except dot ones
scandir(__dirname, { recursive: false }).then(function(files) {
console.log('files', result.files)
console.log('directories', result.directories)
})
// Scan all files even the dot ones
scandir(__dirname, { recursive: true, validate(path) {
return true
}}).then(function(files) {
console.log('files', result.files)
console.log('directories', result.directories)
})
// Scan all files except in .git and node_modules
scandir(__dirname, { recursive: true, validate(path) {
const baseName = Path.basename(path)
return baseName !== '.git' && baseName !== 'node_modules'
}}).then(function(files) {
console.log('files', result.files)
console.log('directories', result.directories)
})
```
## License
This project is licensed under the terms of MIT License. See the LICENSE file for more info.
+173
View File
@@ -0,0 +1,173 @@
"use strict";
/* @flow */
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
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());
});
};
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 };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultFilesystem = void 0;
var fs_1 = __importDefault(require("fs"));
var path_1 = __importDefault(require("path"));
var assert_1 = __importDefault(require("assert"));
var sb_promise_queue_1 = require("sb-promise-queue");
exports.defaultFilesystem = {
join: function (pathA, pathB) {
return path_1.default.join(pathA, pathB);
},
basename: function (path) {
return path_1.default.basename(path);
},
stat: function (path) {
return new Promise(function (resolve, reject) {
fs_1.default.stat(path, function (err, res) {
if (err) {
reject(err);
}
else {
resolve(res);
}
});
});
},
readdir: function (path) {
return new Promise(function (resolve, reject) {
fs_1.default.readdir(path, function (err, res) {
if (err) {
reject(err);
}
else {
resolve(res);
}
});
});
},
};
function scanDirectoryInternal(_a) {
var path = _a.path, recursive = _a.recursive, validate = _a.validate, result = _a.result, fileSystem = _a.fileSystem, queue = _a.queue, reject = _a.reject;
return __awaiter(this, void 0, void 0, function () {
var itemStat, contents;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, fileSystem.stat(path)];
case 1:
itemStat = _b.sent();
if (itemStat.isFile()) {
result.files.push(path);
}
else if (itemStat.isDirectory()) {
result.directories.push(path);
}
if (!itemStat.isDirectory() || recursive === 'none') {
return [2 /*return*/];
}
return [4 /*yield*/, fileSystem.readdir(path)];
case 2:
contents = _b.sent();
contents.forEach(function (item) {
var itemPath = fileSystem.join(path, item);
if (!validate(itemPath)) {
return;
}
queue
.add(function () {
return scanDirectoryInternal({
path: itemPath,
recursive: recursive === 'shallow' ? 'none' : 'deep',
validate: validate,
result: result,
fileSystem: fileSystem,
queue: queue,
reject: reject,
});
})
.catch(reject);
});
return [2 /*return*/];
}
});
});
}
function scanDirectory(path, _a) {
var _b = _a === void 0 ? {} : _a, _c = _b.recursive, recursive = _c === void 0 ? true : _c, _d = _b.validate, validate = _d === void 0 ? null : _d, _e = _b.concurrency, concurrency = _e === void 0 ? Infinity : _e, _f = _b.fileSystem, fileSystem = _f === void 0 ? exports.defaultFilesystem : _f;
return __awaiter(this, void 0, void 0, function () {
var queue, result, mergedFileSystem;
return __generator(this, function (_g) {
switch (_g.label) {
case 0:
assert_1.default(path && typeof path === 'string', 'path must be a valid string');
assert_1.default(typeof recursive === 'boolean', 'options.recursive must be a valid boolean');
assert_1.default(validate === null || typeof validate === 'function', 'options.validate must be a valid function');
assert_1.default(typeof concurrency === 'number', 'options.concurrency must be a valid number');
assert_1.default(fileSystem !== null && typeof fileSystem === 'object', 'options.fileSystem must be a valid object');
queue = new sb_promise_queue_1.PromiseQueue({
concurrency: concurrency,
});
result = { files: [], directories: [] };
mergedFileSystem = __assign(__assign({}, exports.defaultFilesystem), fileSystem);
return [4 /*yield*/, new Promise(function (resolve, reject) {
scanDirectoryInternal({
path: path,
recursive: recursive ? 'deep' : 'shallow',
validate: validate != null ? validate : function (item) { return mergedFileSystem.basename(item).slice(0, 1) !== '.'; },
result: result,
fileSystem: mergedFileSystem,
queue: queue,
reject: reject,
})
.then(function () { return queue.waitTillIdle(); })
.then(resolve, reject);
})];
case 1:
_g.sent();
return [2 /*return*/, result];
}
});
});
}
exports.default = scanDirectory;
+94
View File
@@ -0,0 +1,94 @@
/* @flow */
import fs from 'fs';
import fsPath from 'path';
import invariant from 'assert';
import { PromiseQueue } from 'sb-promise-queue';
export const defaultFilesystem = {
join(pathA, pathB) {
return fsPath.join(pathA, pathB);
},
basename(path) {
return fsPath.basename(path);
},
stat(path) {
return new Promise((resolve, reject) => {
fs.stat(path, (err, res) => {
if (err) {
reject(err);
}
else {
resolve(res);
}
});
});
},
readdir(path) {
return new Promise((resolve, reject) => {
fs.readdir(path, (err, res) => {
if (err) {
reject(err);
}
else {
resolve(res);
}
});
});
},
};
async function scanDirectoryInternal({ path, recursive, validate, result, fileSystem, queue, reject, }) {
const itemStat = await fileSystem.stat(path);
if (itemStat.isFile()) {
result.files.push(path);
}
else if (itemStat.isDirectory()) {
result.directories.push(path);
}
if (!itemStat.isDirectory() || recursive === 'none') {
return;
}
const contents = await fileSystem.readdir(path);
contents.forEach((item) => {
const itemPath = fileSystem.join(path, item);
if (!validate(itemPath)) {
return;
}
queue
.add(() => scanDirectoryInternal({
path: itemPath,
recursive: recursive === 'shallow' ? 'none' : 'deep',
validate,
result,
fileSystem,
queue,
reject,
}))
.catch(reject);
});
}
async function scanDirectory(path, { recursive = true, validate = null, concurrency = Infinity, fileSystem = defaultFilesystem, } = {}) {
invariant(path && typeof path === 'string', 'path must be a valid string');
invariant(typeof recursive === 'boolean', 'options.recursive must be a valid boolean');
invariant(validate === null || typeof validate === 'function', 'options.validate must be a valid function');
invariant(typeof concurrency === 'number', 'options.concurrency must be a valid number');
invariant(fileSystem !== null && typeof fileSystem === 'object', 'options.fileSystem must be a valid object');
const queue = new PromiseQueue({
concurrency,
});
const result = { files: [], directories: [] };
const mergedFileSystem = { ...defaultFilesystem, ...fileSystem };
await new Promise((resolve, reject) => {
scanDirectoryInternal({
path,
recursive: recursive ? 'deep' : 'shallow',
validate: validate != null ? validate : (item) => mergedFileSystem.basename(item).slice(0, 1) !== '.',
result,
fileSystem: mergedFileSystem,
queue,
reject,
})
.then(() => queue.waitTillIdle())
.then(resolve, reject);
});
return result;
}
export default scanDirectory;
+21
View File
@@ -0,0 +1,21 @@
/// <reference types="node" />
import fs from 'fs';
interface Result {
files: Array<string>;
directories: Array<string>;
}
interface FileSystem {
join(pathA: string, pathB: string): string;
basename(path: string): string;
stat(path: string): Promise<fs.Stats>;
readdir(path: string): Promise<string[]>;
}
declare type Validate = (path: string) => boolean;
export declare const defaultFilesystem: FileSystem;
declare function scanDirectory(path: string, { recursive, validate, concurrency, fileSystem, }?: {
recursive?: boolean;
validate?: Validate | null;
concurrency?: number;
fileSystem?: Partial<FileSystem>;
}): Promise<Result>;
export default scanDirectory;
+69
View File
@@ -0,0 +1,69 @@
{
"name": "sb-scandir",
"version": "3.1.1",
"description": "File scanning module for Node.js",
"main": "lib/cjs/index.js",
"typings": "lib/typings/index.d.ts",
"module": "lib/esm/index.mjs",
"exports": {
".": {
"types": "./lib/typings/index.d.ts",
"import": "./lib/esm/index.mjs",
"require": "./lib/cjs/index.js"
},
"./package.json": "./package.json"
},
"type": "commonjs",
"scripts": {
"test": "ava",
"lint": "(tsc -p . --noEmit) && (eslint . --ext .ts) && (prettier --list-different src/*.ts)",
"prepare": "yarn build:clean ; yarn build:esm ; yarn build:cjs ; yarn build:typings",
"build:clean": "rm -rf lib",
"build:esm": "tsc --module es2015 --target es2018 --outDir lib/esm && mv lib/esm/index.js lib/esm/index.mjs",
"build:cjs": "tsc --module commonjs --target es5 --outDir lib/cjs",
"build:typings": "tsc --declaration --outDir lib/typings --emitDeclarationOnly"
},
"repository": {
"type": "git",
"url": "git+https://github.com/steelbrain/scandir.git"
},
"keywords": [
"scandir",
"steelbrain",
"files",
"scan"
],
"author": "steelbrain",
"license": "MIT",
"bugs": {
"url": "https://github.com/steelbrain/scandir/issues"
},
"files": [
"lib/*"
],
"homepage": "https://github.com/steelbrain/scandir#readme",
"devDependencies": {
"ava": "^3.11.1",
"eslint-config-steelbrain": "^10.0.0-beta2",
"ts-node": "^8.10.2",
"typescript": "^3.9.7"
},
"dependencies": {
"sb-promise-queue": "^2.1.0"
},
"ava": {
"files": [
"test/*-test.ts"
],
"extensions": [
"ts"
],
"require": [
"ts-node/register/transpile-only"
]
},
"engines": {
"node": ">= 8"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}