first commit

This commit is contained in:
2025-10-10 18:00:07 -04:00
commit 06b59a3a99
3786 changed files with 571590 additions and 0 deletions
Vendored
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
# .env file - Environment variables for MangaShelf server
# Copy this to .env and modify as needed
# Server port
PORT=3030
# JWT Secret - CHANGE THIS TO A RANDOM STRING IN PRODUCTION!
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
# Database path (SQLite)
DATABASE_PATH=./data/mangashelf.db
# Upload limits (in MB)
MAX_FILE_SIZE=500
# Library paths
LIBRARY_PATH=./library
COVERS_PATH=./covers
UPLOADS_PATH=./uploads
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
../color-support/bin.js
+1
View File
@@ -0,0 +1 @@
../mime/cli.js
+1
View File
@@ -0,0 +1 @@
../mkdirp/bin/cmd.js
+1
View File
@@ -0,0 +1 @@
../node-gyp/bin/node-gyp.js
+1
View File
@@ -0,0 +1 @@
../which/bin/node-which
+1
View File
@@ -0,0 +1 @@
../nodemon/bin/nodemon.js
+1
View File
@@ -0,0 +1 @@
../touch/bin/nodetouch.js
+1
View File
@@ -0,0 +1 @@
../nopt/bin/nopt.js
+1
View File
@@ -0,0 +1 @@
../prebuild-install/bin.js
Generated Vendored Symlink
+1
View File
@@ -0,0 +1 @@
../rc/cli.js
+1
View File
@@ -0,0 +1 @@
../rimraf/bin.js
+1
View File
@@ -0,0 +1 @@
../semver/bin/semver.js
+3439
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
The MIT License (MIT)
Copyright © 2020-2022 Michael Garvin
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.
+65
View File
@@ -0,0 +1,65 @@
# @gar/promisify
### Promisify an entire object or class instance
This module leverages es6 Proxy and Reflect to promisify every function in an
object or class instance.
It assumes the callback that the function is expecting is the last
parameter, and that it is an error-first callback with only one value,
i.e. `(err, value) => ...`. This mirrors node's `util.promisify` method.
In order that you can use it as a one-stop-shop for all your promisify
needs, you can also pass it a function. That function will be
promisified as normal using node's built-in `util.promisify` method.
[node's custom promisified
functions](https://nodejs.org/api/util.html#util_custom_promisified_functions)
will also be mirrored, further allowing this to be a drop-in replacement
for the built-in `util.promisify`.
### Examples
Promisify an entire object
```javascript
const promisify = require('@gar/promisify')
class Foo {
constructor (attr) {
this.attr = attr
}
double (input, cb) {
cb(null, input * 2)
}
const foo = new Foo('baz')
const promisified = promisify(foo)
console.log(promisified.attr)
console.log(await promisified.double(1024))
```
Promisify a function
```javascript
const promisify = require('@gar/promisify')
function foo (a, cb) {
if (a !== 'bad') {
return cb(null, 'ok')
}
return cb('not ok')
}
const promisified = promisify(foo)
// This will resolve to 'ok'
promisified('good')
// this will reject
promisified('bad')
```
+36
View File
@@ -0,0 +1,36 @@
'use strict'
const { promisify } = require('util')
const handler = {
get: function (target, prop, receiver) {
if (typeof target[prop] !== 'function') {
return target[prop]
}
if (target[prop][promisify.custom]) {
return function () {
return Reflect.get(target, prop, receiver)[promisify.custom].apply(target, arguments)
}
}
return function () {
return new Promise((resolve, reject) => {
Reflect.get(target, prop, receiver).apply(target, [...arguments, function (err, result) {
if (err) {
return reject(err)
}
resolve(result)
}])
})
}
}
}
module.exports = function (thingToPromisify) {
if (typeof thingToPromisify === 'function') {
return promisify(thingToPromisify)
}
if (typeof thingToPromisify === 'object') {
return new Proxy(thingToPromisify, handler)
}
throw new TypeError('Can only promisify functions or objects')
}
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@gar/promisify",
"version": "1.1.3",
"description": "Promisify an entire class or object",
"main": "index.js",
"repository": {
"type": "git",
"url": "https://github.com/wraithgar/gar-promisify.git"
},
"scripts": {
"lint": "standard",
"lint:fix": "standard --fix",
"test": "lab -a @hapi/code -t 100",
"posttest": "npm run lint"
},
"files": [
"index.js"
],
"keywords": [
"promisify",
"all",
"class",
"object"
],
"author": "Gar <gar+npm@danger.computer>",
"license": "MIT",
"devDependencies": {
"@hapi/code": "^8.0.1",
"@hapi/lab": "^24.1.0",
"standard": "^16.0.3"
}
}
+9
View File
@@ -0,0 +1,9 @@
/*
comment
*/
function isHoge(arg){
return arg;
}
export default isHoge;
+123
View File
@@ -0,0 +1,123 @@
/*
可変長引数ではないis関数詰め合わせモジュール
略称版メソッドもなし
*/
// 型・インスタンス
import isArray from './is-array.mjs';
import isBoolean from './is-boolean.mjs';
import isBuffer from './is-buffer.mjs';
import isArrayBuffer from './is-array-buffer.mjs';
import isError from './is-error.mjs';
import isFunction from './is-function.mjs';
import isAsyncFunction from './is-async-function.mjs';
import isAsyncGeneratorFunction from './is-async-generator-function.mjs';
import isGeneratorFunction from './is-generator-function.mjs';
import isNumber from './is-number.mjs';
import isRegExp from './is-reg-exp.mjs';
import isStats from './is-stats.mjs';
import isString from './is-string.mjs';
import isUndefined from './is-undefined.mjs';
import isNull from './is-null.mjs';
import isNaN from './is-nan.mjs';
import isDate from './is-date.mjs';
import isEvent from './is-event.mjs';
import isEventTarget from './is-event-target.mjs';
import isObject from './is-object.mjs';
import isPromise from './is-promise.mjs';
import isAbortController from './is-abort-controller.mjs';
import isAbortSignal from './is-abort-signal.mjs';
// Number系
import isOdd from './is-odd.mjs';
import isEven from './is-even.mjs';
import isMultiple from './is-multiple.mjs';
// String系
import isLowercase from './is-lowercase.mjs';
import isUppercase from './is-uppercase.mjs';
import isIPv4 from './is-ipv4.mjs';
import isHostname from './is-hostname.mjs';
// DOM
import isNode from './is-node.mjs';
import isTextNode from './is-text-node.mjs';
import isElement from './is-element.mjs';
import isDocumentFragment from './is-document-fragment.mjs';
// 雑多
import isTrue from './is-true.mjs';
import isFalse from './is-false.mjs';
import isTruthy from './is-truthy.mjs';
import isFalsy from './is-falsy.mjs';
import isInstance from './is-instance.mjs';
import isInstanceof from './is-instanceof.mjs';
import isInstanceOfClassName from './is-instance-of-classname.mjs';
import isNullish from './is-nullish.mjs';
import isObjectLiteral from './is-object-literal.mjs';
import isArrayLike from './is-array-like.mjs';
import isComparisonOperator from './is-comparison-operator.mjs';
import isSameDay from './is-same-day.mjs';
import isSemVer from './is-sem-ver.mjs';
import isVersion from './is-version.mjs';
import isEmpty from './is-empty.mjs';
import isLeapYear from './is-leap-year.mjs';
import isValidDate from './is-valid-date.mjs';
// 返り値
const _is = {
isArray,
isBoolean,
isBuffer,
isArrayBuffer,
isError,
isFunction,
isAsyncFunction,
isAsyncGeneratorFunction,
isGeneratorFunction,
isNumber,
isRegExp,
isStats,
isString,
isUndefined,
isNull,
isNaN,
isDate,
isEvent,
isEventTarget,
isObject,
isPromise,
isAbortController,
isAbortSignal,
isOdd,
isEven,
isMultiple,
isLowercase,
isUppercase,
isIPv4,
isHostname,
isNode,
isTextNode,
isElement,
isDocumentFragment,
isTrue,
isFalse,
isTruthy,
isFalsy,
isInstance,
isInstanceof,
isInstanceOfClassName,
isNullish,
isObjectLiteral,
isArrayLike,
isComparisonOperator,
isSameDay,
isSemVer,
isVersion,
isEmpty,
isLeapYear,
isValidDate
}
export default _is;
@@ -0,0 +1,14 @@
import isObj from './is-object.mjs';
import isFunc from './is-function.mjs';
/*
引数がAbortControllerインスタンスか
Polyfillでも機能するようにしている。
*/
function isAbortController(ac){
return isObj(ac)
&& isFunc(ac.constructor)
&& ac.constructor.name==='AbortController';
}
export default isAbortController;
+15
View File
@@ -0,0 +1,15 @@
import isObj from './is-object.mjs';
import isFunc from './is-function.mjs';
/*
引数がAbortSignalインスタンスか
Polyfillでも機能するようにしている。
*/
function isAbortSignal(abortSignal){
// const isETExtend = abortSignal instanceof EventTarget;
return isObj(abortSignal)
&& isFunc(abortSignal.constructor)
&& abortSignal.constructor.name==='AbortSignal';
}
export default isAbortSignal;
+16
View File
@@ -0,0 +1,16 @@
// Mod
import isInstance from './is-instance.mjs';
/*
ArrayBufferインスタンスか
args
1: any
result
boolean
*/
function isArrayBuffer(arg){
return isInstance(arg, ArrayBuffer);
}
export default isArrayBuffer;
+10
View File
@@ -0,0 +1,10 @@
/*
定義: truthyで、.lengthで数値を返すもの
*/
function isArrayLike(arg){
return !!arg && typeof arg.length==='number';
}
export default isArrayLike;
+5
View File
@@ -0,0 +1,5 @@
function isArray(arg){
return Array.isArray(arg);
}
export default isArray;
+14
View File
@@ -0,0 +1,14 @@
import isInstanceof from './is-instanceof.mjs';
let AsyncFunction;
function isAsyncFunction(arg){
if( !AsyncFunction ){
AsyncFunction = (async()=>0).constructor;
}
return isInstanceof(arg, AsyncFunction);
}
export default isAsyncFunction;
@@ -0,0 +1,14 @@
import isInstanceof from './is-instanceof.mjs';
let AsyncGeneratorFunction;
function isAsyncGeneratorFunction(arg){
if( !AsyncGeneratorFunction ){
AsyncGeneratorFunction = (async function*(){}).constructor;
}
return isInstanceof(arg, AsyncGeneratorFunction);
}
export default isAsyncGeneratorFunction;
+5
View File
@@ -0,0 +1,5 @@
function isBoolean(arg){
return typeof arg==='boolean';
}
export default isBoolean;
+5
View File
@@ -0,0 +1,5 @@
function isBuffer(arg){
return Buffer.isBuffer(arg);
}
export default isBuffer;
@@ -0,0 +1,22 @@
/*
有効な比較演算子の文字列か
[比較演算子 - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Comparison_Operators)
*/
// 有効な比較演算子のset
const set_co = new Set([
'<',
'>',
'<=',
'>=',
'==',
'!=',
'===',
'!=='
]);
function isComparisonOperator(arg){
return typeof arg==='string' && set_co.has(arg);
}
export default isComparisonOperator;
+5
View File
@@ -0,0 +1,5 @@
function isDate(arg){
return arg instanceof Date;
}
export default isDate;
@@ -0,0 +1,10 @@
/*
comment
*/
import isNode from './is-node.mjs';
function isDocumentFragment(arg){
return isNode(arg) && arg.nodeType===11;
}
export default isDocumentFragment;
+8
View File
@@ -0,0 +1,8 @@
/*
comment
*/
function isElement(arg){
return arg instanceof Element;
}
export default isElement;
+26
View File
@@ -0,0 +1,26 @@
/*
引数が空か
判定基準
文字列
長さが0の場合
配列
長さが0の場合
オブジェクト
keyが存在しない場合
それ以外
常にfalse
*/
function isEmpty(arg){
if(typeof arg==='string'){
return !arg.length;
}else if( Array.isArray(arg) ){
return !arg.length;
}else if(typeof arg==='object' && arg instanceof Object){
return !Object.getOwnPropertyNames(arg).length;
}else{
return false;
}
}
export default isEmpty;
+9
View File
@@ -0,0 +1,9 @@
/*
comment
*/
function isError(arg){
return arg instanceof Error;
}
export default isError;
+11
View File
@@ -0,0 +1,11 @@
// Mod
import isNumber from './is-number.mjs';
/*
偶数判定(0含む)
*/
function isEven(num){
return isNumber(num) && !(num % 2);
}
export default isEven;
+8
View File
@@ -0,0 +1,8 @@
/*
comment
*/
function isEventTarget(arg){
return arg instanceof EventTarget;
}
export default isEventTarget;
+8
View File
@@ -0,0 +1,8 @@
/*
comment
*/
function isEvent(arg){
return arg instanceof Event;
}
export default isEvent;
+9
View File
@@ -0,0 +1,9 @@
/*
comment
*/
function isFalse(arg){
return arg===false;
}
export default isFalse;
+6
View File
@@ -0,0 +1,6 @@
function isFalsy(arg){
return !arg;
}
export default isFalsy;
+5
View File
@@ -0,0 +1,5 @@
function isFunction(arg){
return typeof arg==='function';
}
export default isFunction;
@@ -0,0 +1,14 @@
import isInstanceof from './is-instanceof.mjs';
let GeneratorFunction;
function isGeneratorFunction(arg){
if( !GeneratorFunction ){
GeneratorFunction = (function*(){}).constructor;
}
return isInstanceof(arg, GeneratorFunction);
}
export default isGeneratorFunction;
+33
View File
@@ -0,0 +1,33 @@
// Var
const re_label = /[a-z0-9][a-z0-9-]*[a-z0-9]/;
const re_tld = /[a-z]{2,}|[a-z]{2}\.[a-z]{2}/;
const re_domain = new RegExp(`${re_label.source}\.${re_tld.source}`);
const re_hostname = new RegExp(`^(${re_label.source}\\.)*${re_domain.source}$`, 'i');
/*
ホスト名文字列か
厳密な仕様がよくわからないのでルーズに判定している。
大文字・小文字を区別しない。
実際には混在しているものがあるため。
dotで区切られた半角英数&ハイフン(ラベル)。
大文字・小文字は区別しない。
ハイフンは先頭・末尾NG.
ラベル毎の長さは2-63文字。
どこかで3文字以上と見たが、実際は二文字もよく振られている。
ドメイン部分は2文字もある。
合計255文字まで。
args
1: string
result
boolean
*/
function isHostname(str){
const isU256 = str.length <= 255;
return isU256 && re_hostname.test(str);
}
export default isHostname;
@@ -0,0 +1,31 @@
// mod
import isInstance from './is-instance.mjs';
import isString from './is-string.mjs';
/*
インスタンスの親クラスが指定した名称か
args
1: instance
2: string
3: op, object
return
boolean
*/
function isInstanceOfClassName(instance, str_name, options={debug:false}){
options.debug && console.log('isInstanceOfClassName()', instance, str_name);
if( !isInstance(instance) ){
throw new TypeError('arg1 not instance');
}
if( !isString(str_name) ){
throw new TypeError('arg2 not string');
}
return instance.constructor.name===str_name;
}
export default isInstanceOfClassName;
+31
View File
@@ -0,0 +1,31 @@
// Mod
import isObject from './is-object.mjs';
import isFunction from './is-function.mjs';
/*
args === 2
引数なにかのコンストラクタ関数・クラスのインスタンスか
true条件
オブジェクト
.constructorが関数
.__proto__が上記関数のprototype
args
1: any
result
boolean
*/
function isInstance(any){
if( isObject(any) ){
const parent = any.constructor;
return isFunction(parent) &&
Object.getPrototypeOf(any)===parent.prototype;
}else{
return false;
}
}
export default isInstance;
+19
View File
@@ -0,0 +1,19 @@
/*
instanceof
args
1: any
2: any
result
boolean
*/
function isInstanceof(arg, arg2){
try{
return arg instanceof arg2;
}catch(e){
return false;
}
}
export default isInstanceof;
+19
View File
@@ -0,0 +1,19 @@
// Var
const re_0to255 = /(\d|[1-9]\d|1\d{2}|2(5[0-5]|[0-4]\d))/;
const re_ipv4 = new RegExp(`^(${re_0to255.source}\\.){3}${re_0to255.source}$`);
/*
IPv4アドレス文字列か
dotで区切られた0-255が4つ。
args
1: string
result
boolean
*/
function isIPv4(str){
return re_ipv4.test(str);
}
export default isIPv4;
+36
View File
@@ -0,0 +1,36 @@
/*
閏年判定
引数
1: date or number
返り値
boolean
参考
[うるう年とは - はてなキーワード](http://d.hatena.ne.jp/keyword/%A4%A6%A4%EB%A4%A6%C7%AF)
*/
import isNumber from './is-number.mjs';
import isDate from './is-date.mjs';
function isLeapYear(arg){
if( isNumber(arg) ){
return isLeapYear_number(arg)
}else if( isDate(arg) ){
return isLeapYear_date(arg);
}else{
throw new TypeError(`Invalid arguments: ${arg}`);
}
}
// dateインスタンス用
function isLeapYear_date(date){
const year = date.getFullYear();
return isLeapYear_number(year);
}
// 数値(年)用、本体
function isLeapYear_number(year){
return year % 4 === 0 && (year % 100!==0 || year % 400===0);
}
export default isLeapYear;
+16
View File
@@ -0,0 +1,16 @@
// Mod
import isString from './is-string.mjs';
/*
引数文字列がすべて半角小文字か
*/
function isLowercase(arg){
if( !isString(arg) ){
return false;
}
return /^[a-z]+$/.test(arg);
}
export default isLowercase;
+28
View File
@@ -0,0 +1,28 @@
// Mod
import isNumber from './is-number.mjs';
/*
倍数判定
引数1が引数2の倍数か
args
1: number
2: number
result
boolean
*/
function isMultiple(arg1, arg2){
// validation
if( !isNumber(arg1) ){
throw TypeError('Invalid arguments 1');
}
if( !isNumber(arg2) ){
throw TypeError('Invalid arguments 1');
}
return !(arg1%arg2);
}
export default isMultiple;
+6
View File
@@ -0,0 +1,6 @@
// arg!=argでいい気もするがPolyfill前提のコンセプトのため
function isNaN(arg){
return Number.isNaN(arg);
}
export default isNaN;
+9
View File
@@ -0,0 +1,9 @@
// Mod
import isNumber from './is-number.mjs';
import isObject from './is-object.mjs';
function isNode(arg){
return arg instanceof Node;
}
export default isNode;
+8
View File
@@ -0,0 +1,8 @@
/*
comment
*/
function isNull(arg){
return arg===null;
}
export default isNull;
+19
View File
@@ -0,0 +1,19 @@
/*
null or undef
args
1: any
result
boolean
*/
function isNullish(arg){
if( arg===null ){
return true;
}else if( arg===undefined ){
return true;
}else{
return false;
}
}
export default isNullish;
+6
View File
@@ -0,0 +1,6 @@
function isNumber(arg){
return typeof arg==='number';
}
export default isNumber;
+12
View File
@@ -0,0 +1,12 @@
/*
const obj = {} みたいなの
arg.constructor===Object でもよさそう。
2020.11.08
Object#__proto__ => Object.getPrototypeOf()
*/
function isObjectLiteral(arg){
return Object.getPrototypeOf(arg)===Object.prototype;
}
export default isObjectLiteral;
+13
View File
@@ -0,0 +1,13 @@
/*
typeofと違ってnullは弾く
ChangeLog
2020.10.25
判定をinstanceofから以前の方法に戻した。
Object.create(null) で作られたものを検出できなかったため。
*/
function isObject(arg){
return !!arg && arg===Object(arg);
}
export default isObject;
+10
View File
@@ -0,0 +1,10 @@
/*
奇数判定(0含む)
*/
import isNumber from './is-number.mjs';
function isOdd(num){
return isNumber(num) && !!(num % 2);
}
export default isOdd;
+8
View File
@@ -0,0 +1,8 @@
/*
引数がPromiseインスタンスか
*/
function isPromise(arg){
return arg instanceof Promise;
}
export default isPromise;
+6
View File
@@ -0,0 +1,6 @@
function isRegExp(arg){
return arg instanceof RegExp;
}
export default isRegExp;
+32
View File
@@ -0,0 +1,32 @@
import isDate from './is-date.mjs';
/*
引数のdateインスタンスが同じ日か
args
1...: date
result
boolean
*/
function isSameday(...arr_date){
let str_baseDateText;
for(let date of arr_date ){
if( !isDate(date) ){
throw new TypeError(`Invalid arguments: ${index}`);
}
if( !str_baseDateText ){
str_baseDateText = date.toDateString();
}else{
const str_dateText = date.toDateString();
if(str_baseDateText!==str_dateText){
return false;
}
}
}
return true;
}
export default isSameday;
+22
View File
@@ -0,0 +1,22 @@
/*
SemVerな文字列かどうか
regexpを長々と繋げて頑張ると次弄るときに頭痛くなるから極力分割する
参考
[SemVerのv1.0.0とv2.0.0-rc.1、node-semverを見比べてみた - /var/log/kozy4324](http://kozy4324.github.io/blog/2012/12/19/semver/)
*/
function isSemVer(arg){
if( typeof arg!=='string'){
return false;
}
if( /^\d\.\d\.\d$/.test(arg) ){
return true;
}
if( /^\d\.\d\.\d-[0-9A-Za-z-.]*$/.test(arg) ){
return true;
}
}
export default isSemVer;
+12
View File
@@ -0,0 +1,12 @@
// Mod
import fs from 'fs';
function isStats(arg){
try{
return arg instanceof fs.Stats;
}catch(e){
return false;
}
}
export default isStats;
+5
View File
@@ -0,0 +1,5 @@
function isString(arg){
return typeof arg==='string';
}
export default isString;
+10
View File
@@ -0,0 +1,10 @@
/*
comment
*/
import isNode from './is-node.mjs';
function isTextNode(arg){
return isNode(arg) && arg.nodeType===3;
}
export default isTextNode;
+9
View File
@@ -0,0 +1,9 @@
/*
comment
*/
function isTrue(arg){
return arg===true;
}
export default isTrue;
+5
View File
@@ -0,0 +1,5 @@
function isTruthy(arg){
return !!arg;
}
export default isTruthy;
+6
View File
@@ -0,0 +1,6 @@
function isUndefined(arg){
return arg===undefined;
}
export default isUndefined;
+16
View File
@@ -0,0 +1,16 @@
// Mod
import isString from './is-string.mjs';
/*
引数文字列がすべて大文字か
*/
function isUppercase(arg){
if( !isString(arg) ){
return false;
}
return /^[A-Z]+$/.test(arg);
}
export default isUppercase;
+30
View File
@@ -0,0 +1,30 @@
/*
引数の日時が存在するものかをBooleanで返す
引数
1, 2, 3: number
返り値
boolean
参考
[Vanilla JavaScriptで簡単に日付が有効かどうかチェックする方法 | Rriver](https://parashuto.com/rriver/development/validate-date-using-vanilla-js)
*/
// Mod
import isNumber from './is-number.mjs';
function isValidDate(year, month, day){
if( !isNumber(year, month, day) ){
throw new TypeError(`Invalid arguments`);
}
const date = new Date(year, month-1, day);
const isSameYear = date.getFullYear()===year;
const isSameMonth = date.getMonth()===(month-1);
const isSameDay = date.getDate()===day;
const result = isSameYear && isSameMonth && isSameDay;
return result;
}
export default isValidDate;
+18
View File
@@ -0,0 +1,18 @@
/*
versionを表した数字とdotの文字列か
セーフ
1
1.2
1.2.3
1.2.3.4
アウト
1.2A
其の弐
*/
function isVersion(arg){
return typeof arg==='string' && /^\d(|[0-9.]*\d)$/.test(arg);
}
export default isVersion;
+64
View File
@@ -0,0 +1,64 @@
/*
any
引数どれか一つでも適正ならtrueを返す
*/
// Mod
import _is from './_is/index.mjs'; // 素の判定モジュール詰め合わせ
import config from './config.mjs'; // 設定
// このモジュール返り値
const any = Object.create(null);
/*
_isの関数を元に改名&可変長引数化
可変長引数
引数があれば判定関数を渡してsome、なければfalseを返す
config.variadic.ignoreと一致するメソッド名は無視する
改名
config.shortのpropertyと一致する名前のメソッドは略称版を作る
*/
for(let [key, func] of Object.entries(_is)){
// 関数名作り isFooBar => FooBar, foobar [, FB, fb]
const nameArr = [];
const name1 = key.slice(2);
const name2 = name1.toLowerCase();
nameArr.push(name1, name2);
// 略称版があれば作る
if( config.short[key] ){
const arr_alias = Array.isArray(config.short[key]) ?
config.short[key]:
[config.short[key]];
arr_alias.forEach( (name)=>{
// console.log('methodName:', name);
const str_Camel = name.slice(2); // "is"をカット
const str_lower = str_Camel.toLowerCase(); // "Method" => "method"
nameArr.push(str_Camel, str_lower);
});
}
// 可変長引数化
const method = (function(){
const isVariadic = !config.variadic.ignore.includes(key);
// console.log(`isVariadic: ${key} ${isVariadic}`);
if( isVariadic ){
return (...args)=>{
return args.length ?
args.some(func):
false;
}
}else{
return func;
}
}());
nameArr.forEach( (name)=>{
any[name] = method;
});
}
export default any;
+36
View File
@@ -0,0 +1,36 @@
/*
設定オブジェクトを返す
*/
const config = {
// 可変長引数に関する設定
variadic: {
ignore: [// 無視する関数名(非対応・元から対応などで)
'isInstanceof',
'isInstanceOfClassName',
'isMultiple',
'isValidDate',
'isSameDay'
]
},
// 別名付けるやつ
short: {
isArray: 'isArr',
isBoolean: 'isBool',
isBuffer: 'isBuf',
isArrayBuffer: 'isArrBuf',
isError: 'isErr',
isFunction: ['isFunc', 'isFn'],
isNumber: 'isNum',
isRegExp: 'isRE',
isString: 'isStr',
isObject: 'isObj',
isUndefined: 'isUndef',
isElement: 'isElm',
isDocumentFragment: 'isDF',
isVersion: 'isVer'
}
}
export default config;
+39
View File
@@ -0,0 +1,39 @@
# document
という名の制作メモ。
## 仕様
* ESM
## Mod
### devDependencies
* jsdom
- DOM関連。
## dir構成
### ./
* index.mjs
- is.mjs, not.mjs, any.mjsをまとめて返すだけ。
* is.mjs
- ./\_is/index.mjsを可変長引数化する。
* not.mjs
- is.mjsを元にnot関数を作成する。
* any.mjs
- ./\_is/index.mjsを元にany関数を作成する。
* config.mjs
- 設定オブジェクトを返すモジュール。
### ./\_is.
素の判定関数詰め合わせモジュールを返すディレクトリ。
* index.mjs
- 本体。
* ^is-[a-z-]+\.mjs$
- 個別の判定関数を返すモジュール。
### ./test
* index.mjs
- npm tで呼び出されるテストスクリプト。
+16
View File
@@ -0,0 +1,16 @@
/*
まとめるだけ
もっとスマートな書き方がありそう。
*/
import _is from './is.mjs';
import _not from './not.mjs';
import _any from './any.mjs';
export const is = _is;
export const not = _not;
export const any = _any;
export default {
is, not, any
}
+65
View File
@@ -0,0 +1,65 @@
/*
可変長引数化
*/
// 素の判定モジュール詰め合わせ
import _is from './_is/index.mjs';
// 設定
import config from './config.mjs';
// このモジュール返り値
const is = Object.create(null);
/*
_isの関数を元に改名&可変長引数化
可変長引数
引数があれば判定関数を渡してevery、なければfalseを返す
config.variadic.ignoreと一致するメソッド名は無視する
改名
config.shortのpropertyと一致する名前のメソッドは略称版を作る
*/
for(let [key, func] of Object.entries(_is)){
// 関数名作り isFooBar => FooBar, foobar [, FB, fb]
const nameArr = [];
const name1 = key.slice(2);
const name2 = name1.toLowerCase();
nameArr.push(name1, name2);
// 略称版があれば作る
if( config.short[key] ){
const arr_alias = Array.isArray(config.short[key]) ?
config.short[key]:
[config.short[key]];
arr_alias.forEach( (name)=>{
// console.log('methodName:', name);
const str_Camel = name.slice(2); // "is"をカット
const str_lower = str_Camel.toLowerCase(); // "Method" => "method"
nameArr.push(str_Camel, str_lower);
});
}
// 可変長引数化
const method = (function(){
const isVariadic = !config.variadic.ignore.includes(key);
// console.log(`isVariadic: ${key} ${isVariadic}`);
if( isVariadic ){
return (...args)=>{
return args.length ?
args.every(func):
false;
}
}else{
return func;
}
}());
nameArr.forEach( (name)=>{
is[name] = method;
});
}
export default is;
+14
View File
@@ -0,0 +1,14 @@
/*
isを元にnot作成
*/
import is from './is.mjs';
const not = Object.create(null);
for(let [key, func] of Object.entries(is) ){
not[key] = (...args)=>{
return !func(...args);
}
}
export default not;
+32
View File
@@ -0,0 +1,32 @@
{
"name": "@honeo/check",
"version": "2.9.0",
"description": "型・インスタンスなどの確認",
"main": "index.mjs",
"scripts": {
"test": "node ./test/index.mjs"
},
"author": "honeo",
"license": "MIT",
"devDependencies": {
"@azure/abort-controller": "^1.0.4",
"jsdom": "^16.5.0",
"node-abort-controller": "^3.0.0"
},
"directories": {
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/honeo/check.git"
},
"keywords": [
"check",
"type",
"instance"
],
"bugs": {
"url": "https://github.com/honeo/check/issues"
},
"homepage": "https://github.com/honeo/check#readme"
}
+271
View File
@@ -0,0 +1,271 @@
# check
* [honeo/check](https://github.com/honeo/check)
* [@honeo/check](https://www.npmjs.com/package/@honeo/check)
## なにこれ
型・インスタンス等をチェックするやつ。
## 使い方
```bash
$ npm i @honeo/check
```
```js
import {is, not, any} from '@honeo/check';
is.arr([]); // true
is.arr([], {}); // false
not.arr([], {}); // false
any.arr([], {}); // true
// single
import is from '@honeo/check/is.mjs'; // or not.mjs, any.mjs
// default export
import check from '@honeo/check';
check.is.foobar();
```
```js
// webpack v4~ webpack.config.js
{
node: {
fs: 'empty'
}
}
```
## API
is, not, any共用。
### Alias
```js
is.FooBar===is.foobar; // true
```
### Type, Instance
```js
is.array([]); // true
is.arr([], []); // true
is.boolean(false); // true
is.bool(true, false); // true
is.buffer(new Buffer('foobar')); // true
is.arraybuffer( new ArrayBuffer(0)); // true
is.error(new Error('hoge')); // true
is.function(function(){}); // true
is.func(()=>{}); // true
is.fn(_=>_); // true
is.asyncfunction(async()=>{}); // true
is.asyncgeneratorfunction(async function*(){}); // true
is.generatorfunction(function*(){}); // true
is.number(1); // true
is.num(0, 1); // true
is.regexp(/hoge/); // true
is.re(/foo/, /bar/); // true
is.string('hoge'); // true
is.str('fuga', 'piyo'); // true
is.undefined(undefined); // true
is.undef(null); // false
is.null(null); // true
is.nan(NaN); // true
is.date(new Date()); // true
is.object({}); // true
is.obj(null); // false
is.promise(new Promise(_=>_)); // true
is.stats( fs.statSync('./') ); // true
is.abortcontroller(new AbortController() ); // true
is.abortsignal(new AbortController().signal); // true
```
| name | type | varargs | description |
|:---------------------------------------------- |:---- |:-------:|:------------------------------------------------------------------------------------------------------------------- |
| Array, Arr, array, arr | any | ○ | |
| Boolean, Bool, boolean, bool | any | ○ | |
| Buffer, Buf, buffer, buf | any | ○ | |
| Error, Err, error, err | any | ○ | |
| Function, Func, Fn, function, func, fn | any | ○ | |
| AsyncFunction, asyncfunction | any | ○ | |
| AsyncGeneratorFunction, asyncgeneratorfunction | any | ○ | |
| GeneratorFunction, generatorfunction | any | ○ | |
| Number, Num, number, num | any | ○ | |
| RegExp, RE, regexp, re | any | ○ | |
| String, Str, string, str | any | ○ | |
| Undefined, Undef, undefined, undef | any | ○ | |
| Null, null | any | ○ | |
| NaN, nan | any | ○ | |
| Date, date | any | ○ | |
| Object, Obj, object, obj | any | ○ | |
| Promise, promise | any | ○ | |
| Stats, stats | any | ○ | |
| ArrayBuffer, ArrBuf, arraybuffer, arrbuf | any | ○ | |
| AbortController, abortcontroller | any | ○ | [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)インスタンスか(Polyfill対応)。 |
| AbortSignal, abortsignal | any | ○ | [AbortSignal](https://developer.mozilla.org/ja/docs/Web/API/AbortSignal)インスタンスか(Polyfill対応)。 |
### Number
```js
is.even(2); // true
is.odd(3); // true
is.multiple(8080, 80); // true
```
| name | type | varargs | description |
|:------------------ |:------ |:-------:|:---------------------- |
| Odd, odd | number | ○ | 引数がすべて奇数か。 |
| Even, even | number | ○ | 引数がすべて偶数か。 |
| Multiple, multiple | number | ✗ | 引数1が引数2の倍数か。 |
### String
```js
is.ipv4('192.168.1.1'); // true
is.hostname('www.example.com'); // true
is.lowercase('hoge'); // true
is.uppercase('FOO', 'BAR'); // true
```
| name | type | varargs | description |
|:-------------------- |:------ |:-------:|:-------------- |
| IPv4, ipv4 | string | ○ | |
| Hostname, hostname | string | ○ | やっつけ実装。 |
| Lowercase, lowercase | string | ○ | |
| Uppercase, uppercase | string | ○ | |
### DOM
```js
is.node(document.body, document.createTextNode('hoge')); // true
is.textnode(document.createTextNode('hoge')); // true
is.element(document.head, document.body); // true
is.df(document.createDocumentFragment()); // true
is.event( new Event("hoge") ); // true
is.eventtarget(document, window); // true
```
| name | type | varargs | description |
|:------------------------------------------ |:---- |:-------:|:----------- |
| Node, node | any | ○ | |
| TextNode, textnode | any | ○ | |
| Element, Elm, element, elm | any | ○ | |
| DocumentFragment, DF, documentfragment, df | any | ○ | |
| Event, event | any | ○ | |
| EventTarget, eventtarget | any | ○ | |
### その他
```js
is.true(true, !0); // true
is.false(false !1); // true
is.truthy(true, "hoge", 1, [], {}); // true
is.falsy(null, undefined, "", 0, NaN); // true
is.instance([], {}); // true
is.instance("hoge"); // false
is.instanceof(new Date(), Date); //true
is.instanceOfClassName([], "Array"); // true
is.objectliteral({}); // true
not.objectliteral([], new function(){}); // true
is.arraylike([], 'hoge'); // true
is.comparisonoperator('<='); // true
is.sameDay(new Date(), new Date()); // true
is.semver('1.2.3'); // true
is.semver('1.0.0-foo.bar'); // true
is.version('7.7.4', '2.5.0.1') // true
is.version('1.2A', 1.0); // false
is.empty('', [], {}) // true
is.empty(0, null); // false
is.leapyear(2020, new Date('2024'));
is.validdate(2019, 4, 17); // true
is.validdate(2020, 12, 32); // false
```
| name | type | varargs | description |
|:-------------------------------------------- |:------------ |:-------:|:------------------------------------------------- |
| ArrayLike(), arraylike() | any | ○ | |
| True(), true() | any | ○ | |
| False(), false() | any | ○ | |
| Truthy(), truthy() | any | ○ | |
| Falsy(), falsy() | any | ○ | |
| Empty(), empty() | any | ○ | 要素が空か。 |
| Instance(), instance() | any | ○ | 何らかのインスタンスであるオブジェクトか。 |
| Instanceof(), instanceof() | any | ✗ | 引数1が引数2のConstructor/Classのインスタンスか。 |
| InstanceOfClassName(), instanceofclassname() | instance, string | ✗ | 引数1が引数2の名称を持つClassのインスタンスか。 |
| Nullish(), nullish() | any | ○ | null or undefined |
| ObjectLiteral(), objectliteral() | any | ○ | 未継承の素のオブジェクトか。 |
| ComparisonOperator(), comparisonoperator() | any | ○ | 有効な比較演算子の文字列か。 |
| SameDay(), sameday() | date | ○ | 同じ日か |
| SemVer(), semver() | any | ○ | 有効なSemVer文字列か。 |
| Version(), Ver(), versiom(), ver() | any | ○ | 有効な数字, dotのバージョン文字列か。 |
| LeapYear(), leapyear() | number, date | ○ | 閏年か。 |
| ValidDate(year, mon, day), validdate() | number | ✗ | 存在する日付か。 |
## Breaking Changes
### v2.0.0
#### CommonJS => ES Modules
```js
// before CJS
const {is, not, any} = require('@honeo/check');
// after ESM
import {is, not, any} from '@honeo/check';
```
#### rename: instance() => instanceof()
```js
// before
is.instance([], Array);
// after
is.instanceof([], Array);
```
+759
View File
@@ -0,0 +1,759 @@
// Mod: core
import assert from 'assert';
import fs from 'fs';
import path from 'path';
import url from 'url';
// Mod: npm
import JSDOM from 'jsdom';
// Mod: local
import check from '../index.mjs';
import {is, not, any} from '../index.mjs';
import _is from '../is.mjs';
// jsdom set
global.document = JSDOM.jsdom('hogehoge');
global.head = document.head;
global.window = document.defaultView;
global.Node = window.Node;
global.Element = window.Element;
global.Event = window.Event;
// Var
//const {is, not, any} = check;
const cases = {}
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Main
assert(is===check.is, 'Hybrid export failed')
console.log('.arraybuffer');
if( !is.arraybuffer(new ArrayBuffer(0)) ){
throw new Error('failed');
}
if( !not.arrbuf('hoge') ){
throw new Error('failed');
}
console.log('.abortcontroller');
{
const ac = new AbortController();
if( is.abortcontroller(ac) ){
}else{
throw new Error('native: failed');
}
}
{
const {AbortController} = await import('@azure/abort-controller');
const ac = new AbortController();
if( is.abortcontroller(ac) ){
}else{
throw new Error('@azure/abort-controller: failed');
}
}
{
const {AbortController} = await import('node-abort-controller');
const ac = new AbortController();
if( is.abortcontroller(ac) ){
}else{
throw new Error('node-abort-controller: failed');
}
}
console.log('.abortsignal');
{
const as = new AbortController().signal;
if( is.abortsignal(as) ){
}else{
throw new Error('native: failed');
}
}
{
const {AbortController} = await import('@azure/abort-controller');
const as = new AbortController().signal;
if( is.abortsignal(as) ){
}else{
throw new Error('@azure/abort-controller: failed');
}
}
{
const {AbortController} = await import('node-abort-controller');
const as = new AbortController().signal;
if( is.abortsignal(as) ){
}else{
throw new Error('node-abort-controller: failed');
}
}
console.log('.asyncfunction');
{
const asyncFunc = async()=>{}
const bool = is.asyncfunction(asyncFunc);
if( !bool ){
throw new Error('case async: failed');
}
}
{
const bool = is.asyncfunction(_=>{});
if( bool ){
throw new Error('case sync: failed');
}
}
{
const asyncFunc = async()=>{}
const bool = is.asyncfunction(asyncFunc, asyncFunc);
if( !bool ){
throw new Error('case async2: failed');
}
}
{
const asyncFunc = async()=>{}
const bool = is.asyncfunction(asyncFunc, _=>{});
if( bool ){
throw new Error('case async, sync: failed');
}
}
console.log('.asyncgeneratorfunction');
{
const asyncGeneFunc = async function*(){}
{
const bool = is.asyncgeneratorfunction(asyncGeneFunc);
if( !bool ){
throw new Error('case asyncGene: failed');
}
}
{
const bool = is.asyncgeneratorfunction(_=>{});
if( bool ){
throw new Error('case sync: failed');
}
}
{
const bool = is.asyncgeneratorfunction(asyncGeneFunc, asyncGeneFunc);
if( !bool ){
throw new Error('case asyncGene2: failed');
}
}
{
const bool = is.asyncgeneratorfunction(asyncGeneFunc, _=>{});
if( bool ){
throw new Error('case asyncGene, sync: failed');
}
}
}
console.log('.generatorfunction');
{
const geneFunc = function*(){}
const bool = is.generatorfunction(geneFunc);
if( !bool ){
throw new Error('case gene: failed');
}
}
{
const bool = is.generatorfunction(_=>{});
if( bool ){
throw new Error('case func: failed');
}
}
{
const geneFunc = function*(){}
const bool = is.generatorfunction(geneFunc, geneFunc);
if( !bool ){
throw new Error('case gene2: failed');
}
}
{
const geneFunc = function*(){}
const bool = is.generatorfunction(_=>{}, geneFunc);
if( bool ){
throw new Error('case func, gene: failed');
}
}
console.log('isInstanceOfClassName');
{
let bool;
try{
is.instanceofclassname();
bool = false;
}catch(e){
bool = true;
}
assert(bool, 'case none arg: failed');
}
{
const bool = is.instanceofclassname(new Date(), 'Date');
assert(bool, 'case success: failed');
}
{
const bool = is.instanceofclassname({}, 'Array');
assert(!bool, 'case invalid classname: failed');
}
{
let bool;
try{
is.instanceofclassname(true, false);
bool = false;
}catch(e){
bool = true;
}
assert(bool, 'case invalid arg: failed');
}
console.log('.hostname');
if(
!is.hostname('example.com') &&
!is.hostname('www.example.com') &&
!is.hostname('foo-bar-2000.example.com') &&
!is.hostname('hoge-1234.fu.ga.pi.yo') &&
!is.hostname('Hoge.example.com') &&
not.hostname('-invalid-hostname.example.com') &&
not.hostname('very-long-hostnaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaame.example.com')
){
throw new Error('failed');
}
console.log('.ipv4');
if(
!is.ipv4('192.168.1.1') &&
!is.ipv4('0.0.0.0') &&
!is.ipv4('255.255.255.255') &&
!not.ipv4('32.64.128.256') &&
!not.ipv4('123.123.123') &&
!not.ipv4('123.123.123.123.123')
){
throw new Error('failed');
}
// multiple
console.log('.multiple');
{
assert(is.Multiple(4, 2), 'is 4, 2');
assert(is.Multiple(8080, 80), 'is 8080, 80');
assert(not.Multiple(151, 50), 'not 151, 50');
assert(is.Multiple(0, 5), '0が全ての数の倍数になっていない');
assert(is.Multiple(0, 0), '0が0の倍数になっていない');
let bool;
try{
is.Multiple('multiple!');
bool = false;
}catch(e){
bool = true;
}
assert(bool, 'validation failed');
}
console.log('.nullish');
if( !is.nullish(null) ){
throw new Error('failed');
}
if( !is.nullish(undefined) ){
throw new Error('failed');
}
if( !is.nullish(null, undefined) ){
throw new Error('failed');
}
if( !not.nullish(true, "", []) ){
throw new Error('failed');
}
if( not.nullish(null) ){
throw new Error('failed');
}
if( !any.nullish(null, true) ){
throw new Error('failed');
}
console.log('.sameday');
{
if( !is.sameday(new Date(), new Date()) ){
throw new Error('failed1');
}
const bool2 = is.sameday(
new Date(),
new Date('1999')
);
if( bool2 ){
throw new Error('failed2');
}
const bool3 = is.sameday(
new Date(),
new Date(),
new Date()
);
if( !bool3 ){
throw new Error('failed3');
}
}
/// type
// array, arr
cases.array = (arg)=>{
return !is.Array()
&& is.array([])
&& is.Arr([])
&& is.arr([], [])
&& !is.arr('array!')
&& !is.arr([], true);
}
// boolean, bool
cases.boolean = (arg)=>{
return !is.Boolean()
&& is.boolean(true)
&& is.Bool(false)
&& is.bool(true, false)
&& !is.bool('boolean!')
&& !is.bool(true, 'true');
}
// error, err
cases.error = (arg)=>{
return !is.Error()
&& is.error(new Error())
&& is.Err(new Error())
&& is.err(new Error(), new Error())
&& !is.err('error')
&& !is.err(new Error(), 'error');
}
// function, func, fn
cases.function = (arg)=>{
const f = function(){};
return !is.Function()
&& is.function(f)
&& is.Func(f)
&& is.func(f, f)
&& is.fn(f)
&& !is.func('function!')
&& !is.func(f, true);
}
// number, num
cases.number = (arg)=>{
return !is.Number()
&& is.number(123)
&& is.Num(45)
&& is.num(22, 80, 443)
&& !is.num('number!')
&& !is.num(1, true);
}
// regexp, re
cases.regexp = (arg)=>{
const r = /hoge/;
return !is.RegExp()
&& is.regexp(r)
&& is.RE(r)
&& is.re(r, r)
&& !is.re('rexexp!')
&& !is.re(r, true);
}
// string, str
cases.string = (arg)=>{
return !is.String()
&& is.string('hoge')
&& is.Str('fuga')
&& is.str('foo', 'bar')
&& !is.str(123)
&& !is.str('piyo', true);
}
// undefined
cases.undefined = (arg)=>{
return !is.Undefined()
&& is.undefined(undefined)
&& is.undefined(undefined, undefined)
&& !is.undefined(null);
}
// null
cases.null = (arg)=>{
return !is.Null()
&& is.null(null)
&& is.null(null, null)
&& !is.null(undefined);
}
// NaN
cases.nan = (arg)=>{
return !is.NaN()
&& is.nan(NaN)
&& is.nan(NaN, NaN)
&& !is.nan(undefined);
}
/// instance
// buffer
cases.buffer = ()=>{
const buffer = Buffer.from('hoge');
return is.Buffer(buffer)
&& is.Buf(buffer)
&& is.buffer(buffer)
&& is.buf(buffer)
&& !is.Buffer('hoge')
&& !is.Buffer()
&& !is.Buffer(true)
&& !is.Buffer({});
}
// date
cases.date = (arg)=>{
return !is.Date()
&& !is.date({})
&& !is.date('date object')
&& is.date(new Date());
}
// event
cases.event = (arg)=>{
return !is.Event()
&& !is.event({})
&& !is.event('event')
&& is.event( new Event('hoge') );
}
/// jsdomではEventTargetがfunctionではなくobjectなためinstanceofで判定ができないから省略
// // eventtarget
// cases.eventtarget = (arg)=>{
// return !is.eventtarget()
// && !is.eventtarget({})
// && !is.eventtarget('eventtarget')
// && is.eventtarget(window);
// }
// eventtarget
// object, obj
cases.object = (arg)=>{
return !is.Object()
&& is.object({})
&& is.Obj({})
&& is.obj( Object.create(null) )
&& is.obj({}, {})
&& !is.obj('object!')
&& !is.obj({}, true);
}
// stats
cases.stats = (arg)=>{
const stats_dir = fs.statSync('./');
const stats_file = fs.statSync(__filename);
return !is.stats()
&& is.stats(stats_dir)
&& is.stats(stats_dir, stats_file)
&& !is.stats({}, true);
}
// promise
cases.promise = (arg)=>{
const p = new Promise(_=>_);
return !is.Promise()
&& is.promise(p)
&& is.promise(p, p)
&& !is.promise('promise!')
&& !is.promise(p, true);
}
/*
Number
*/
// even
cases.even = (arg)=>{
return is.Even(0)
&& is.even(2)
&& is.even(4, 6)
&& !is.even(8, 9)
&& !is.even('evennumber!');
}
// odd
cases.odd = (arg)=>{
return is.Odd(1)
&& is.odd(3)
&& is.odd(5, 7)
&& !is.odd(9, 10)
&& !is.odd('oddnumber!');
}
/*
String系
*/
cases.lowercase = (arg)=>{
return is.Lowercase('hoge')
&& is.lowercase('foo', 'bar')
&& !is.lowercase('Fuga')
&& !is.lowercase(true);
}
cases.uppercase = (arg)=>{
return is.Uppercase('HOGE')
&& is.uppercase('FOO', 'BAR')
&& !is.uppercase('Fuga')
&& !is.uppercase(true);
}
/// DOM
const textnode = document.createTextNode('');
const element = document.createElement('div');
// node
cases.node = (arg)=>{
return !is.Node()
&& is.node(textnode)
&& is.node(element)
&& is.node(textnode, element)
&& !is.node('node!')
&& !is.node(textnode, true);
}
// textnode
cases.textnode = (arg)=>{
return !is.TextNode()
&& is.textnode(textnode)
&& is.textnode(textnode, textnode)
&& !is.textnode('textnode!')
&& !is.textnode(textnode, true);
}
// element
cases.element = (arg)=>{
return !is.Element()
&& is.Elm(element)
&& is.element(element, element)
&& !is.elm('element!')
&& !is.element(element, true);
}
// documentfragment, df
cases.documentfragment = (arg)=>{
const df = document.createDocumentFragment();
return !is.DocumentFragment()
&& is.documentfragment(df)
&& is.DF(df)
&& is.df(df, df)
&& !is.df('documentfragment!')
&& !is.df(df, true);
}
/*
Other
*/
// true
cases.true = (arg)=>{
return !is.True()
&& is.true(true)
&& is.true(true, true)
&& !is.true('true!')
&& !is.true(true, false);
}
// false
cases.false = (arg)=>{
return !is.False()
&& is.false(false)
&& is.false(false, false)
&& !is.false('false!')
&& !is.false(false, true);
}
// truthy
cases.truthy = (arg)=>{
return !is.Truthy()
&& is.truthy(true)
&& is.truthy("hoge", 123, [], {})
&& !is.truthy(null, undefined)
&& !is.truthy(true, false);
}
// falsy
cases.falsy = (arg)=>{
return !is.Falsy()
&& is.falsy(false)
&& is.falsy("", 0, null, undefined, NaN)
&& !is.falsy(true, "hoge", 123)
&& !is.falsy(true, false);
}
// isInstanceof
cases.instanceof = (arg)=>{
return !is.instanceof()
&& is.instanceof(new Date, Date)
&& !is.instanceof({}, Array)
&& !is.instanceof(true, false);
}
// objectliteral
cases.objectliteral = (arg)=>{
return !is.ObjectLiteral()
&& is.objectliteral({})
&& is.objectliteral({}, new Object({}))
&& !is.objectliteral( new function(){} )
&& !is.objectliteral({}, []);
}
// arraylike
cases.arraylike = (arg)=>{
return !is.ArrayLike()
&& is.arraylike([])
&& is.arraylike('hoge', {length: 0})
&& !is.arraylike(12345)
&& !is.arraylike([], undefined);
}
// isComparisonOperator
cases.comparisonoperator = (arg)=>{
return !is.ComparisonOperator()
&& is.comparisonoperator('<')
&& is.comparisonoperator('!==')
&& !is.comparisonoperator('&&')
&& !is.comparisonoperator(true, '>');
}
// isSemVer
cases.semver = (arg)=>{
return !is.SemVer()
&& is.semver('1.2.3')
&& is.semver('1.0.0-foo.bar', '2.2.2')
&& !is.semver('1.2.3.4')
&& !is.semver(1.0)
&& !is.semver(true, '1.0.0')
}
// isVersion
cases.version = (arg)=>{
return !is.Version()
&& is.Ver('1.2.3')
&& is.version('1.0.0.0', '7.7.7.7.7.7.7')
&& !is.ver('1.2.3.4.')
&& !is.version(1.0)
&& !is.version(true, '1.0.0A')
}
// isEmpty
cases.empty = (arg)=>{
return is.Empty({})
&& is.empty([])
&& is.empty('')
&& !is.empty()
&& !is.empty(null)
&& !is.empty({a: 1})
&& !is.empty([1])
&& !is.empty('0');
}
// isLeapYear
cases.leapyear = (arg)=>{
return is.LeapYear(2020)
&& is.leapyear(new Date('2016'))
&& !is.leapyear(2019)
&& !is.leapyear(new Date('2018'));
}
// isValidDate
cases.isvaliddate = (arg)=>{
return is.ValidDate(2020, 11, 11)
&& is.validdate(2019, 4, 17)
&& !is.validdate(1995, 13, 1)
&& !is.validdate(2010, 4, 32);
}
// 本体
for(let [key, method] of Object.entries(cases)){
if( method() ){
console.log(`${key}: success`);
}else{
throw new Error(`${key}: failed`);
}
}
/*
not
中身はほぼ一緒だから適当
*/
const resultArr = [
// string
not.String(),
!not.string('hoge'),
!not.Str('fuga'),
!not.str('foo', 'bar'),
not.str(123),
not.str(true, false),
not.str('piyo', true),
// element
not.Element(true),
!not.element(element),
// 読み込みチェック
(is===_is)
];
resultArr.forEach( (bool, index, arr)=>{
if( bool ){
console.log(`not: ${index+1}/${arr.length} success`);
}else{
throw new Error(`not: ${index}/${arr.length} failed`);
}
});
/*
any
*/
const resultArr_any = [
any.true(false, true),
any.True(false, true),
!any.Number('123', true),
!any.number('123', true),
!any.Num('123', true),
!any.num('123', true)
];
resultArr_any.forEach( (bool, index, arr)=>{
if( bool ){
console.log(`any: ${index+1}/${arr.length} success`);
}else{
throw new Error(`any: ${index}/${arr.length} failed`);
}
});
console.log('test: done');
+20
View File
@@ -0,0 +1,20 @@
<!-- This file is automatically added by @npmcli/template-oss. Do not edit. -->
ISC License
Copyright npm, Inc.
Permission to use, copy, modify, and/or distribute this
software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this
permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND NPM DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
EVENT SHALL NPM 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.
+60
View File
@@ -0,0 +1,60 @@
# @npmcli/fs
polyfills, and extensions, of the core `fs` module.
## Features
- all exposed functions return promises
- `fs.rm` polyfill for node versions < 14.14.0
- `fs.mkdir` polyfill adding support for the `recursive` and `force` options in node versions < 10.12.0
- `fs.copyFile` extended to accept an `owner` option
- `fs.mkdir` extended to accept an `owner` option
- `fs.mkdtemp` extended to accept an `owner` option
- `fs.writeFile` extended to accept an `owner` option
- `fs.withTempDir` added
- `fs.cp` polyfill for node < 16.7.0
## The `owner` option
The `copyFile`, `mkdir`, `mkdtemp`, `writeFile`, and `withTempDir` functions
all accept a new `owner` property in their options. It can be used in two ways:
- `{ owner: { uid: 100, gid: 100 } }` - set the `uid` and `gid` explicitly
- `{ owner: 100 }` - use one value, will set both `uid` and `gid` the same
The special string `'inherit'` may be passed instead of a number, which will
cause this module to automatically determine the correct `uid` and/or `gid`
from the nearest existing parent directory of the target.
## `fs.withTempDir(root, fn, options) -> Promise`
### Parameters
- `root`: the directory in which to create the temporary directory
- `fn`: a function that will be called with the path to the temporary directory
- `options`
- `tmpPrefix`: a prefix to be used in the generated directory name
### Usage
The `withTempDir` function creates a temporary directory, runs the provided
function (`fn`), then removes the temporary directory and resolves or rejects
based on the result of `fn`.
```js
const fs = require('@npmcli/fs')
const os = require('os')
// this function will be called with the full path to the temporary directory
// it is called with `await` behind the scenes, so can be async if desired.
const myFunction = async (tempPath) => {
return 'done!'
}
const main = async () => {
const result = await fs.withTempDir(os.tmpdir(), myFunction)
// result === 'done!'
}
main()
```
@@ -0,0 +1,17 @@
const url = require('url')
const node = require('../node.js')
const polyfill = require('./polyfill.js')
const useNative = node.satisfies('>=10.12.0')
const fileURLToPath = (path) => {
// the polyfill is tested separately from this module, no need to hack
// process.version to try to trigger it just for coverage
// istanbul ignore next
return useNative
? url.fileURLToPath(path)
: polyfill(path)
}
module.exports = fileURLToPath
@@ -0,0 +1,121 @@
const { URL, domainToUnicode } = require('url')
const CHAR_LOWERCASE_A = 97
const CHAR_LOWERCASE_Z = 122
const isWindows = process.platform === 'win32'
class ERR_INVALID_FILE_URL_HOST extends TypeError {
constructor (platform) {
super(`File URL host must be "localhost" or empty on ${platform}`)
this.code = 'ERR_INVALID_FILE_URL_HOST'
}
toString () {
return `${this.name} [${this.code}]: ${this.message}`
}
}
class ERR_INVALID_FILE_URL_PATH extends TypeError {
constructor (msg) {
super(`File URL path ${msg}`)
this.code = 'ERR_INVALID_FILE_URL_PATH'
}
toString () {
return `${this.name} [${this.code}]: ${this.message}`
}
}
class ERR_INVALID_ARG_TYPE extends TypeError {
constructor (name, actual) {
super(`The "${name}" argument must be one of type string or an instance ` +
`of URL. Received type ${typeof actual} ${actual}`)
this.code = 'ERR_INVALID_ARG_TYPE'
}
toString () {
return `${this.name} [${this.code}]: ${this.message}`
}
}
class ERR_INVALID_URL_SCHEME extends TypeError {
constructor (expected) {
super(`The URL must be of scheme ${expected}`)
this.code = 'ERR_INVALID_URL_SCHEME'
}
toString () {
return `${this.name} [${this.code}]: ${this.message}`
}
}
const isURLInstance = (input) => {
return input != null && input.href && input.origin
}
const getPathFromURLWin32 = (url) => {
const hostname = url.hostname
let pathname = url.pathname
for (let n = 0; n < pathname.length; n++) {
if (pathname[n] === '%') {
const third = pathname.codePointAt(n + 2) | 0x20
if ((pathname[n + 1] === '2' && third === 102) ||
(pathname[n + 1] === '5' && third === 99)) {
throw new ERR_INVALID_FILE_URL_PATH('must not include encoded \\ or / characters')
}
}
}
pathname = pathname.replace(/\//g, '\\')
pathname = decodeURIComponent(pathname)
if (hostname !== '') {
return `\\\\${domainToUnicode(hostname)}${pathname}`
}
const letter = pathname.codePointAt(1) | 0x20
const sep = pathname[2]
if (letter < CHAR_LOWERCASE_A || letter > CHAR_LOWERCASE_Z ||
(sep !== ':')) {
throw new ERR_INVALID_FILE_URL_PATH('must be absolute')
}
return pathname.slice(1)
}
const getPathFromURLPosix = (url) => {
if (url.hostname !== '') {
throw new ERR_INVALID_FILE_URL_HOST(process.platform)
}
const pathname = url.pathname
for (let n = 0; n < pathname.length; n++) {
if (pathname[n] === '%') {
const third = pathname.codePointAt(n + 2) | 0x20
if (pathname[n + 1] === '2' && third === 102) {
throw new ERR_INVALID_FILE_URL_PATH('must not include encoded / characters')
}
}
}
return decodeURIComponent(pathname)
}
const fileURLToPath = (path) => {
if (typeof path === 'string') {
path = new URL(path)
} else if (!isURLInstance(path)) {
throw new ERR_INVALID_ARG_TYPE('path', ['string', 'URL'], path)
}
if (path.protocol !== 'file:') {
throw new ERR_INVALID_URL_SCHEME('file')
}
return isWindows
? getPathFromURLWin32(path)
: getPathFromURLPosix(path)
}
module.exports = fileURLToPath
+20
View File
@@ -0,0 +1,20 @@
// given an input that may or may not be an object, return an object that has
// a copy of every defined property listed in 'copy'. if the input is not an
// object, assign it to the property named by 'wrap'
const getOptions = (input, { copy, wrap }) => {
const result = {}
if (input && typeof input === 'object') {
for (const prop of copy) {
if (input[prop] !== undefined) {
result[prop] = input[prop]
}
}
} else {
result[wrap] = input
}
return result
}
module.exports = getOptions
+9
View File
@@ -0,0 +1,9 @@
const semver = require('semver')
const satisfies = (range) => {
return semver.satisfies(process.version, range, { includePrerelease: true })
}
module.exports = {
satisfies,
}
+92
View File
@@ -0,0 +1,92 @@
const { dirname, resolve } = require('path')
const fileURLToPath = require('./file-url-to-path/index.js')
const fs = require('../fs.js')
// given a path, find the owner of the nearest parent
const find = async (path) => {
// if we have no getuid, permissions are irrelevant on this platform
if (!process.getuid) {
return {}
}
// fs methods accept URL objects with a scheme of file: so we need to unwrap
// those into an actual path string before we can resolve it
const resolved = path != null && path.href && path.origin
? resolve(fileURLToPath(path))
: resolve(path)
let stat
try {
stat = await fs.lstat(resolved)
} finally {
// if we got a stat, return its contents
if (stat) {
return { uid: stat.uid, gid: stat.gid }
}
// try the parent directory
if (resolved !== dirname(resolved)) {
return find(dirname(resolved))
}
// no more parents, never got a stat, just return an empty object
return {}
}
}
// given a path, uid, and gid update the ownership of the path if necessary
const update = async (path, uid, gid) => {
// nothing to update, just exit
if (uid === undefined && gid === undefined) {
return
}
try {
// see if the permissions are already the same, if they are we don't
// need to do anything, so return early
const stat = await fs.stat(path)
if (uid === stat.uid && gid === stat.gid) {
return
}
} catch (err) {}
try {
await fs.chown(path, uid, gid)
} catch (err) {}
}
// accepts a `path` and the `owner` property of an options object and normalizes
// it into an object with numerical `uid` and `gid`
const validate = async (path, input) => {
let uid
let gid
if (typeof input === 'string' || typeof input === 'number') {
uid = input
gid = input
} else if (input && typeof input === 'object') {
uid = input.uid
gid = input.gid
}
if (uid === 'inherit' || gid === 'inherit') {
const owner = await find(path)
if (uid === 'inherit') {
uid = owner.uid
}
if (gid === 'inherit') {
gid = owner.gid
}
}
return { uid, gid }
}
module.exports = {
find,
update,
validate,
}
+22
View File
@@ -0,0 +1,22 @@
const fs = require('./fs.js')
const getOptions = require('./common/get-options.js')
const owner = require('./common/owner.js')
const copyFile = async (src, dest, opts) => {
const options = getOptions(opts, {
copy: ['mode', 'owner'],
wrap: 'mode',
})
const { uid, gid } = await owner.validate(dest, options.owner)
// the node core method as of 16.5.0 does not support the mode being in an
// object, so we have to pass the mode value directly
const result = await fs.copyFile(src, dest, options.mode)
await owner.update(dest, uid, gid)
return result
}
module.exports = copyFile
+15
View File
@@ -0,0 +1,15 @@
(The MIT License)
Copyright (c) 2011-2017 JP Richardson
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.
+22
View File
@@ -0,0 +1,22 @@
const fs = require('../fs.js')
const getOptions = require('../common/get-options.js')
const node = require('../common/node.js')
const polyfill = require('./polyfill.js')
// node 16.7.0 added fs.cp
const useNative = node.satisfies('>=16.7.0')
const cp = async (src, dest, opts) => {
const options = getOptions(opts, {
copy: ['dereference', 'errorOnExist', 'filter', 'force', 'preserveTimestamps', 'recursive'],
})
// the polyfill is tested separately from this module, no need to hack
// process.version to try to trigger it just for coverage
// istanbul ignore next
return useNative
? fs.cp(src, dest, options)
: polyfill(src, dest, options)
}
module.exports = cp
+428
View File
@@ -0,0 +1,428 @@
// this file is a modified version of the code in node 17.2.0
// which is, in turn, a modified version of the fs-extra module on npm
// node core changes:
// - Use of the assert module has been replaced with core's error system.
// - All code related to the glob dependency has been removed.
// - Bring your own custom fs module is not currently supported.
// - Some basic code cleanup.
// changes here:
// - remove all callback related code
// - drop sync support
// - change assertions back to non-internal methods (see options.js)
// - throws ENOTDIR when rmdir gets an ENOENT for a path that exists in Windows
'use strict'
const {
ERR_FS_CP_DIR_TO_NON_DIR,
ERR_FS_CP_EEXIST,
ERR_FS_CP_EINVAL,
ERR_FS_CP_FIFO_PIPE,
ERR_FS_CP_NON_DIR_TO_DIR,
ERR_FS_CP_SOCKET,
ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY,
ERR_FS_CP_UNKNOWN,
ERR_FS_EISDIR,
ERR_INVALID_ARG_TYPE,
} = require('../errors.js')
const {
constants: {
errno: {
EEXIST,
EISDIR,
EINVAL,
ENOTDIR,
},
},
} = require('os')
const {
chmod,
copyFile,
lstat,
mkdir,
readdir,
readlink,
stat,
symlink,
unlink,
utimes,
} = require('../fs.js')
const {
dirname,
isAbsolute,
join,
parse,
resolve,
sep,
toNamespacedPath,
} = require('path')
const { fileURLToPath } = require('url')
const defaultOptions = {
dereference: false,
errorOnExist: false,
filter: undefined,
force: true,
preserveTimestamps: false,
recursive: false,
}
async function cp (src, dest, opts) {
if (opts != null && typeof opts !== 'object') {
throw new ERR_INVALID_ARG_TYPE('options', ['Object'], opts)
}
return cpFn(
toNamespacedPath(getValidatedPath(src)),
toNamespacedPath(getValidatedPath(dest)),
{ ...defaultOptions, ...opts })
}
function getValidatedPath (fileURLOrPath) {
const path = fileURLOrPath != null && fileURLOrPath.href
&& fileURLOrPath.origin
? fileURLToPath(fileURLOrPath)
: fileURLOrPath
return path
}
async function cpFn (src, dest, opts) {
// Warn about using preserveTimestamps on 32-bit node
// istanbul ignore next
if (opts.preserveTimestamps && process.arch === 'ia32') {
const warning = 'Using the preserveTimestamps option in 32-bit ' +
'node is not recommended'
process.emitWarning(warning, 'TimestampPrecisionWarning')
}
const stats = await checkPaths(src, dest, opts)
const { srcStat, destStat } = stats
await checkParentPaths(src, srcStat, dest)
if (opts.filter) {
return handleFilter(checkParentDir, destStat, src, dest, opts)
}
return checkParentDir(destStat, src, dest, opts)
}
async function checkPaths (src, dest, opts) {
const { 0: srcStat, 1: destStat } = await getStats(src, dest, opts)
if (destStat) {
if (areIdentical(srcStat, destStat)) {
throw new ERR_FS_CP_EINVAL({
message: 'src and dest cannot be the same',
path: dest,
syscall: 'cp',
errno: EINVAL,
})
}
if (srcStat.isDirectory() && !destStat.isDirectory()) {
throw new ERR_FS_CP_DIR_TO_NON_DIR({
message: `cannot overwrite directory ${src} ` +
`with non-directory ${dest}`,
path: dest,
syscall: 'cp',
errno: EISDIR,
})
}
if (!srcStat.isDirectory() && destStat.isDirectory()) {
throw new ERR_FS_CP_NON_DIR_TO_DIR({
message: `cannot overwrite non-directory ${src} ` +
`with directory ${dest}`,
path: dest,
syscall: 'cp',
errno: ENOTDIR,
})
}
}
if (srcStat.isDirectory() && isSrcSubdir(src, dest)) {
throw new ERR_FS_CP_EINVAL({
message: `cannot copy ${src} to a subdirectory of self ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
})
}
return { srcStat, destStat }
}
function areIdentical (srcStat, destStat) {
return destStat.ino && destStat.dev && destStat.ino === srcStat.ino &&
destStat.dev === srcStat.dev
}
function getStats (src, dest, opts) {
const statFunc = opts.dereference ?
(file) => stat(file, { bigint: true }) :
(file) => lstat(file, { bigint: true })
return Promise.all([
statFunc(src),
statFunc(dest).catch((err) => {
// istanbul ignore next: unsure how to cover.
if (err.code === 'ENOENT') {
return null
}
// istanbul ignore next: unsure how to cover.
throw err
}),
])
}
async function checkParentDir (destStat, src, dest, opts) {
const destParent = dirname(dest)
const dirExists = await pathExists(destParent)
if (dirExists) {
return getStatsForCopy(destStat, src, dest, opts)
}
await mkdir(destParent, { recursive: true })
return getStatsForCopy(destStat, src, dest, opts)
}
function pathExists (dest) {
return stat(dest).then(
() => true,
// istanbul ignore next: not sure when this would occur
(err) => (err.code === 'ENOENT' ? false : Promise.reject(err)))
}
// Recursively check if dest parent is a subdirectory of src.
// It works for all file types including symlinks since it
// checks the src and dest inodes. It starts from the deepest
// parent and stops once it reaches the src parent or the root path.
async function checkParentPaths (src, srcStat, dest) {
const srcParent = resolve(dirname(src))
const destParent = resolve(dirname(dest))
if (destParent === srcParent || destParent === parse(destParent).root) {
return
}
let destStat
try {
destStat = await stat(destParent, { bigint: true })
} catch (err) {
// istanbul ignore else: not sure when this would occur
if (err.code === 'ENOENT') {
return
}
// istanbul ignore next: not sure when this would occur
throw err
}
if (areIdentical(srcStat, destStat)) {
throw new ERR_FS_CP_EINVAL({
message: `cannot copy ${src} to a subdirectory of self ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
})
}
return checkParentPaths(src, srcStat, destParent)
}
const normalizePathToArray = (path) =>
resolve(path).split(sep).filter(Boolean)
// Return true if dest is a subdir of src, otherwise false.
// It only checks the path strings.
function isSrcSubdir (src, dest) {
const srcArr = normalizePathToArray(src)
const destArr = normalizePathToArray(dest)
return srcArr.every((cur, i) => destArr[i] === cur)
}
async function handleFilter (onInclude, destStat, src, dest, opts, cb) {
const include = await opts.filter(src, dest)
if (include) {
return onInclude(destStat, src, dest, opts, cb)
}
}
function startCopy (destStat, src, dest, opts) {
if (opts.filter) {
return handleFilter(getStatsForCopy, destStat, src, dest, opts)
}
return getStatsForCopy(destStat, src, dest, opts)
}
async function getStatsForCopy (destStat, src, dest, opts) {
const statFn = opts.dereference ? stat : lstat
const srcStat = await statFn(src)
// istanbul ignore else: can't portably test FIFO
if (srcStat.isDirectory() && opts.recursive) {
return onDir(srcStat, destStat, src, dest, opts)
} else if (srcStat.isDirectory()) {
throw new ERR_FS_EISDIR({
message: `${src} is a directory (not copied)`,
path: src,
syscall: 'cp',
errno: EINVAL,
})
} else if (srcStat.isFile() ||
srcStat.isCharacterDevice() ||
srcStat.isBlockDevice()) {
return onFile(srcStat, destStat, src, dest, opts)
} else if (srcStat.isSymbolicLink()) {
return onLink(destStat, src, dest)
} else if (srcStat.isSocket()) {
throw new ERR_FS_CP_SOCKET({
message: `cannot copy a socket file: ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
})
} else if (srcStat.isFIFO()) {
throw new ERR_FS_CP_FIFO_PIPE({
message: `cannot copy a FIFO pipe: ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
})
}
// istanbul ignore next: should be unreachable
throw new ERR_FS_CP_UNKNOWN({
message: `cannot copy an unknown file type: ${dest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
})
}
function onFile (srcStat, destStat, src, dest, opts) {
if (!destStat) {
return _copyFile(srcStat, src, dest, opts)
}
return mayCopyFile(srcStat, src, dest, opts)
}
async function mayCopyFile (srcStat, src, dest, opts) {
if (opts.force) {
await unlink(dest)
return _copyFile(srcStat, src, dest, opts)
} else if (opts.errorOnExist) {
throw new ERR_FS_CP_EEXIST({
message: `${dest} already exists`,
path: dest,
syscall: 'cp',
errno: EEXIST,
})
}
}
async function _copyFile (srcStat, src, dest, opts) {
await copyFile(src, dest)
if (opts.preserveTimestamps) {
return handleTimestampsAndMode(srcStat.mode, src, dest)
}
return setDestMode(dest, srcStat.mode)
}
async function handleTimestampsAndMode (srcMode, src, dest) {
// Make sure the file is writable before setting the timestamp
// otherwise open fails with EPERM when invoked with 'r+'
// (through utimes call)
if (fileIsNotWritable(srcMode)) {
await makeFileWritable(dest, srcMode)
return setDestTimestampsAndMode(srcMode, src, dest)
}
return setDestTimestampsAndMode(srcMode, src, dest)
}
function fileIsNotWritable (srcMode) {
return (srcMode & 0o200) === 0
}
function makeFileWritable (dest, srcMode) {
return setDestMode(dest, srcMode | 0o200)
}
async function setDestTimestampsAndMode (srcMode, src, dest) {
await setDestTimestamps(src, dest)
return setDestMode(dest, srcMode)
}
function setDestMode (dest, srcMode) {
return chmod(dest, srcMode)
}
async function setDestTimestamps (src, dest) {
// The initial srcStat.atime cannot be trusted
// because it is modified by the read(2) system call
// (See https://nodejs.org/api/fs.html#fs_stat_time_values)
const updatedSrcStat = await stat(src)
return utimes(dest, updatedSrcStat.atime, updatedSrcStat.mtime)
}
function onDir (srcStat, destStat, src, dest, opts) {
if (!destStat) {
return mkDirAndCopy(srcStat.mode, src, dest, opts)
}
return copyDir(src, dest, opts)
}
async function mkDirAndCopy (srcMode, src, dest, opts) {
await mkdir(dest)
await copyDir(src, dest, opts)
return setDestMode(dest, srcMode)
}
async function copyDir (src, dest, opts) {
const dir = await readdir(src)
for (let i = 0; i < dir.length; i++) {
const item = dir[i]
const srcItem = join(src, item)
const destItem = join(dest, item)
const { destStat } = await checkPaths(srcItem, destItem, opts)
await startCopy(destStat, srcItem, destItem, opts)
}
}
async function onLink (destStat, src, dest) {
let resolvedSrc = await readlink(src)
if (!isAbsolute(resolvedSrc)) {
resolvedSrc = resolve(dirname(src), resolvedSrc)
}
if (!destStat) {
return symlink(resolvedSrc, dest)
}
let resolvedDest
try {
resolvedDest = await readlink(dest)
} catch (err) {
// Dest exists and is a regular file or directory,
// Windows may throw UNKNOWN error. If dest already exists,
// fs throws error anyway, so no need to guard against it here.
// istanbul ignore next: can only test on windows
if (err.code === 'EINVAL' || err.code === 'UNKNOWN') {
return symlink(resolvedSrc, dest)
}
// istanbul ignore next: should not be possible
throw err
}
if (!isAbsolute(resolvedDest)) {
resolvedDest = resolve(dirname(dest), resolvedDest)
}
if (isSrcSubdir(resolvedSrc, resolvedDest)) {
throw new ERR_FS_CP_EINVAL({
message: `cannot copy ${resolvedSrc} to a subdirectory of self ` +
`${resolvedDest}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
})
}
// Do not copy if src is a subdir of dest since unlinking
// dest in this case would result in removing src contents
// and therefore a broken symlink would be created.
const srcStat = await stat(src)
if (srcStat.isDirectory() && isSrcSubdir(resolvedDest, resolvedSrc)) {
throw new ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY({
message: `cannot overwrite ${resolvedDest} with ${resolvedSrc}`,
path: dest,
syscall: 'cp',
errno: EINVAL,
})
}
return copyLink(resolvedSrc, dest)
}
async function copyLink (resolvedSrc, dest) {
await unlink(dest)
return symlink(resolvedSrc, dest)
}
module.exports = cp
+129
View File
@@ -0,0 +1,129 @@
'use strict'
const { inspect } = require('util')
// adapted from node's internal/errors
// https://github.com/nodejs/node/blob/c8a04049/lib/internal/errors.js
// close copy of node's internal SystemError class.
class SystemError {
constructor (code, prefix, context) {
// XXX context.code is undefined in all constructors used in cp/polyfill
// that may be a bug copied from node, maybe the constructor should use
// `code` not `errno`? nodejs/node#41104
let message = `${prefix}: ${context.syscall} returned ` +
`${context.code} (${context.message})`
if (context.path !== undefined) {
message += ` ${context.path}`
}
if (context.dest !== undefined) {
message += ` => ${context.dest}`
}
this.code = code
Object.defineProperties(this, {
name: {
value: 'SystemError',
enumerable: false,
writable: true,
configurable: true,
},
message: {
value: message,
enumerable: false,
writable: true,
configurable: true,
},
info: {
value: context,
enumerable: true,
configurable: true,
writable: false,
},
errno: {
get () {
return context.errno
},
set (value) {
context.errno = value
},
enumerable: true,
configurable: true,
},
syscall: {
get () {
return context.syscall
},
set (value) {
context.syscall = value
},
enumerable: true,
configurable: true,
},
})
if (context.path !== undefined) {
Object.defineProperty(this, 'path', {
get () {
return context.path
},
set (value) {
context.path = value
},
enumerable: true,
configurable: true,
})
}
if (context.dest !== undefined) {
Object.defineProperty(this, 'dest', {
get () {
return context.dest
},
set (value) {
context.dest = value
},
enumerable: true,
configurable: true,
})
}
}
toString () {
return `${this.name} [${this.code}]: ${this.message}`
}
[Symbol.for('nodejs.util.inspect.custom')] (_recurseTimes, ctx) {
return inspect(this, {
...ctx,
getters: true,
customInspect: false,
})
}
}
function E (code, message) {
module.exports[code] = class NodeError extends SystemError {
constructor (ctx) {
super(code, message, ctx)
}
}
}
E('ERR_FS_CP_DIR_TO_NON_DIR', 'Cannot overwrite directory with non-directory')
E('ERR_FS_CP_EEXIST', 'Target already exists')
E('ERR_FS_CP_EINVAL', 'Invalid src or dest')
E('ERR_FS_CP_FIFO_PIPE', 'Cannot copy a FIFO pipe')
E('ERR_FS_CP_NON_DIR_TO_DIR', 'Cannot overwrite non-directory with directory')
E('ERR_FS_CP_SOCKET', 'Cannot copy a socket file')
E('ERR_FS_CP_SYMLINK_TO_SUBDIRECTORY', 'Cannot overwrite symlink in subdirectory of self')
E('ERR_FS_CP_UNKNOWN', 'Cannot copy an unknown file type')
E('ERR_FS_EISDIR', 'Path is a directory')
module.exports.ERR_INVALID_ARG_TYPE = class ERR_INVALID_ARG_TYPE extends Error {
constructor (name, expected, actual) {
super()
this.code = 'ERR_INVALID_ARG_TYPE'
this.message = `The ${name} argument must be ${expected}. Received ${typeof actual}`
}
}
+8
View File
@@ -0,0 +1,8 @@
const fs = require('fs')
const promisify = require('@gar/promisify')
// this module returns the core fs module wrapped in a proxy that promisifies
// method calls within the getter. we keep it in a separate module so that the
// overridden methods have a consistent way to get to promisified fs methods
// without creating a circular dependency
module.exports = promisify(fs)
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
...require('./fs.js'),
copyFile: require('./copy-file.js'),
cp: require('./cp/index.js'),
mkdir: require('./mkdir/index.js'),
mkdtemp: require('./mkdtemp.js'),
rm: require('./rm/index.js'),
withTempDir: require('./with-temp-dir.js'),
writeFile: require('./write-file.js'),
}
+32
View File
@@ -0,0 +1,32 @@
const fs = require('../fs.js')
const getOptions = require('../common/get-options.js')
const node = require('../common/node.js')
const owner = require('../common/owner.js')
const polyfill = require('./polyfill.js')
// node 10.12.0 added the options parameter, which allows recursive and mode
// properties to be passed
const useNative = node.satisfies('>=10.12.0')
// extends mkdir with the ability to specify an owner of the new dir
const mkdir = async (path, opts) => {
const options = getOptions(opts, {
copy: ['mode', 'recursive', 'owner'],
wrap: 'mode',
})
const { uid, gid } = await owner.validate(path, options.owner)
// the polyfill is tested separately from this module, no need to hack
// process.version to try to trigger it just for coverage
// istanbul ignore next
const result = useNative
? await fs.mkdir(path, options)
: await polyfill(path, options)
await owner.update(path, uid, gid)
return result
}
module.exports = mkdir

Some files were not shown because too many files have changed in this diff Show More