Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add @sapphire/node-utilities #468

Merged
merged 9 commits into from
Oct 1, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"packages/event-iterator/README.md",
"packages/fetch/README.md",
"packages/lexure/README.md",
"packages/node-utilities/README.md",
"packages/phisherman/README.md",
"packages/prettier-config/README.md",
"packages/ratelimits/README.md",
Expand Down
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/packages/event-iterator/**/*.ts @kyranet @vladfrangu @favna
/packages/fetch/**/*.ts @favna @kyranet @vladfrangu
/packages/lexure/**/*.ts @kyranet @favna @vladfrangu
/packages/node-utilities/**/*.ts @kyranet @favna @vladfrangu
/packages/phisherman/**/*.ts @undiedgamer @favna @vladfrangu
/packages/prettier-config/**/*.ts @favna
/packages/ratelimits/**/*.ts @kyranet @vladfrangu @favna
Expand Down
2 changes: 2 additions & 0 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
color: 'fbca04'
- name: packages:lexure
color: 'fbca04'
- name: packages:node-utilities
color: 'fbca04'
- name: packages:phisherman
color: 'fbca04'
- name: packages:prettier-config
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ jobs:
- event-iterator
- fetch
- lexure
- node-utilities
- phisherman
- ratelimits
- result
Expand Down
1 change: 1 addition & 0 deletions .npm-deprecaterc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package:
- '@sapphire/eslint-config'
- '@sapphire/event-iterator'
- '@sapphire/fetch'
- '@sapphire/node-utilities'
- '@sapphire/phisherman'
- '@sapphire/prettier-config'
- '@sapphire/ratelimits'
Expand Down
3 changes: 3 additions & 0 deletions packages/node-utilities/.cliff-jumperrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: node-utilities
org: sapphire
packagePath: packages/node-utilities
1 change: 1 addition & 0 deletions packages/node-utilities/.typedoc-json-parserrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
json: 'docs/api.json'
Empty file.
111 changes: 111 additions & 0 deletions packages/node-utilities/README.md

Large diffs are not rendered by default.

63 changes: 63 additions & 0 deletions packages/node-utilities/cliff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[changelog]
header = """
# Changelog

All notable changes to this project will be documented in this file.\n
"""
body = """
{% if version %}\
# [{{ version | trim_start_matches(pat="v") }}]\
{% if previous %}\
{% if previous.version %}\
(https://github.com/sapphiredev/utilities/compare/{{ previous.version }}...{{ version }})\
{% else %}\
(https://github.com/sapphiredev/utilities/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
# [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
## {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/sapphiredev/utilities/commit/{{ commit.id }}))\
{% if commit.breaking %}\
{% for breakingChange in commit.footers %}\
\n{% raw %} {% endraw %}- 💥 **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
{% endfor %}\
{% endif %}\
{% endfor %}
{% endfor %}\n
"""
trim = true
footer = ""

[git]
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "🚀 Features" },
{ message = "^fix", group = "🐛 Bug Fixes" },
{ message = "^docs", group = "📝 Documentation" },
{ message = "^perf", group = "🏃 Performance" },
{ message = "^refactor", group = "🏠 Refactor" },
{ message = "^typings", group = "⌨️ Typings" },
{ message = "^types", group = "⌨️ Typings" },
{ message = ".*deprecated", body = ".*deprecated", group = "🚨 Deprecation" },
{ message = "^revert", skip = true },
{ message = "^style", group = "🪞 Styling" },
{ message = "^test", group = "🧪 Testing" },
{ message = "^chore", skip = true },
{ message = "^ci", skip = true },
{ message = "^build", skip = true },
{ body = ".*security", group = "🛡️ Security" },
]
filter_commits = true
tag_pattern = "@sapphire/node-utilities@[0-9]*"
ignore_tags = ""
topo_order = false
sort_commits = "newest"
66 changes: 66 additions & 0 deletions packages/node-utilities/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@sapphire/node-utilities",
"version": "3.9.3",
favna marked this conversation as resolved.
Show resolved Hide resolved
"description": "Node specific JavaScript utilities for the Sapphire Community",
"author": "@sapphire",
"license": "MIT",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"sideEffects": false,
"homepage": "https://github.com/sapphiredev/utilities/tree/main/packages/utilities",
"scripts": {
"test": "vitest run",
"lint": "eslint src tests --ext ts --fix -c ../../.eslintrc",
"build": "tsup",
"docs": "typedoc-json-parser",
"prepack": "yarn build",
"bump": "cliff-jumper",
"check-update": "cliff-jumper --dry-run"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sapphiredev/utilities.git",
"directory": "packages/node-utilities"
},
"files": [
"dist/**/*.js*",
"dist/**/*.mjs*",
"dist/**/*.d*"
],
"engines": {
"node": ">=v14.0.0",
"npm": ">=7.0.0"
},
"keywords": [
"@sapphire/utilities",
"@sapphire/node-utilities",
"bot",
"typescript",
"ts",
"yarn",
"discord",
"sapphire",
"standalone"
],
"bugs": {
"url": "https://github.com/sapphiredev/utilities/issues"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@favware/cliff-jumper": "^1.8.7",
"@vitest/coverage-c8": "^0.23.4",
"tsup": "^6.2.3",
"typedoc": "^0.23.15",
"typedoc-json-parser": "^5.0.0",
"typescript": "^4.8.3",
"vitest": "^0.23.4"
}
}
1 change: 1 addition & 0 deletions packages/node-utilities/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/findFilesRecursively';
95 changes: 95 additions & 0 deletions packages/node-utilities/src/lib/findFilesRecursively.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { PathLike } from 'node:fs';
import { opendir } from 'node:fs/promises';
import { join } from 'node:path';

/**
*
* @param path The path in which to find files.
* @param predicate A predicate function receives the path as a parameter. Truthy values will have the path included, falsey values will have the file excluded.
*
* @return An {@link AsyncIterableIterator<string>} of all the files. To loop over these use `for await (const file of findFilesRecursively(path, predicate)) {}`
*
* @example
* ```typescript
* // With CommonJS: To find all files ending with `.ts` in the src directory:
* const path = require('node:path');
*
* for await (const file of findFilesRecursively(path.join(__dirname, 'src'), (filePath) => filePath.endsWith('.ts'))) {
* console.log(file);
* }
* ```
*
* @example
* ```typescript
* // With ESM: To find all files ending with `.ts` in the src directory:
* for await (const file of findFilesRecursively(new URL('src', import.meta.url), (filePath) => filePath.endsWith('.ts'))) {
* console.log(file);
* }
* ```
*/
export async function* findFilesRecursively(path: PathLike, predicate: (filePath: string) => boolean = () => true): AsyncIterableIterator<string> {
try {
const dir = await opendir(path);

for await (const item of dir) {
if (item.isFile() && predicate(item.name)) {
yield join(dir.path, item.name);
} else if (item.isDirectory()) {
yield* findFilesRecursively(join(dir.path, item.name), predicate);
}
}
} catch (error) {
if ((error as any).code !== 'ENOENT') {
console.error(error);
imranbarbhuiya marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

/**
*
* @param path The path in which to find files. This can be a string, buffer, or {@link URL}.
* @param fileStartsWith The string pattern with which the file name must start.
*
* Note that we do **not** support a full globby pattern using asterisk for wildcards. It has to be an exact match with {@link String.startsWith}
*
* @return An {@link AsyncIterableIterator<string>} of all the files. To loop over these use `for await (const file of findFilesRecursivelyStringStartsWith(path, fileNameEndsWith)) {}`
*/
export function findFilesRecursivelyStringStartsWith(path: PathLike, fileStartsWith: string) {
return findFilesRecursively(path, (filePath) => filePath.startsWith(fileStartsWith));
}

/**
*
* @param path The path in which to find files. This can be a string, buffer, or {@link URL}.
* @param fileEndsWith The string pattern with which the file name must end.
* Ideally this is a file extension, however you can also provide more parts of the end of the file.
*
* Note that we do **not** support a full globby pattern using asterisk for wildcards. It has to be an exact match with {@link String.endsWith}
*
* @return An {@link AsyncIterableIterator<string>} of all the files. To loop over these use `for await (const file of findFilesRecursivelyStringEndsWith(path, fileNameEndsWith)) {}`
*/
export function findFilesRecursivelyStringEndsWith(path: PathLike, fileEndsWith: string) {
return findFilesRecursively(path, (filePath) => filePath.endsWith(fileEndsWith));
}

/**
* @param path The path in which to find files. This can be a string, buffer, or {@link URL}.
* @param include The string pattern which must be present in the file name.
*
* Note that we do **not** support a full globby pattern using asterisk for wildcards. It has to be an exact match with {@link String.includes}
*
* @return An {@link AsyncIterableIterator<string>} of all the files. To loop over these use `for await (const file of findFilesRecursivelyStringIncludes(path, fileNameEndsWith)) {}`
*/
export function findFilesRecursivelyStringIncludes(path: PathLike, include: string) {
return findFilesRecursively(path, (filePath) => filePath.includes(include));
}

/**
* @param path The path in which to find files. This can be a string, buffer, or {@link URL}.
* @param regex The regex pattern that the file name must match.
*
* @return An {@link AsyncIterableIterator<string>} of all the files. To loop over these use `for await (const file of findFilesRecursivelyRegex(path, fileNameEndsWith)) {}`
*/
export function findFilesRecursivelyRegex(path: PathLike, regex: RegExp) {
return findFilesRecursively(path, (filePath) => regex.test(filePath));
}
9 changes: 9 additions & 0 deletions packages/node-utilities/src/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../ts-config/extra-strict-without-decorators.json",
"compilerOptions": {
"rootDir": "./",
"outDir": "../dist",
"incremental": false
},
"include": ["."]
}
81 changes: 81 additions & 0 deletions packages/node-utilities/tests/findFilesRecursively.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import path from 'node:path';
import {
findFilesRecursively,
findFilesRecursivelyStringEndsWith,
findFilesRecursivelyStringIncludes,
findFilesRecursivelyStringStartsWith,
findFilesRecursivelyRegex
} from '../src';

describe('findFilesRecursively', () => {
test('GIVEN a directory name THEN returns all files in that directory', async () => {
const files = [];
for await (const file of findFilesRecursively(path.join(__dirname, 'findFilesRecursivelyDemoFiles'))) {
files.push(file);
}
expect(files.length).toBe(5);
// sort is required because the order of the files is not same on all operating systems
expect(files.sort((a, b) => a.localeCompare(b))).toStrictEqual([
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'a.txt'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file1.txt'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file2.csv'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file3.xml'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'nested', 'b.txt')
]);
});
});

describe('findFilesRecursivelyStringStartsWith', () => {
test('GIVEN a directory name and a startsWith value THEN returns all files that starts with the given value', async () => {
const files = [];
for await (const file of findFilesRecursivelyStringStartsWith(path.join(__dirname, 'findFilesRecursivelyDemoFiles'), 'file')) {
files.push(file);
}
expect(files.length).toBe(3);
expect(files.sort((a, b) => a.localeCompare(b))).toStrictEqual([
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file1.txt'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file2.csv'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file3.xml')
]);
});
});

describe('findFilesRecursivelyStringEndsWith', () => {
test('GIVEN a directory name and an endsWith value THEN returns all files that ends with the given value', async () => {
const files = [];
for await (const file of findFilesRecursivelyStringEndsWith(path.join(__dirname, 'findFilesRecursivelyDemoFiles'), 'txt')) {
files.push(file);
}
expect(files.length).toBe(3);
expect(files.sort((a, b) => a.localeCompare(b))).toStrictEqual([
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'a.txt'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file1.txt'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'nested', 'b.txt')
]);
});
});

describe('findFilesRecursivelyStringIncludes', () => {
test('GIVEN a directory name and a includes value THEN returns all files that includes the given value', async () => {
const files = [];
for await (const file of findFilesRecursivelyStringIncludes(path.join(__dirname, 'findFilesRecursivelyDemoFiles'), '1')) {
files.push(file);
}
expect(files.length).toBe(1);
expect(files.sort((a, b) => a.localeCompare(b))).toStrictEqual([path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file1.txt')]);
});
});

describe('findFilesRecursivelyRegex', () => {
test('GIVEN a directory name and a regex THEN returns all files that matches the regex', async () => {
const files = [];
for await (const file of findFilesRecursivelyRegex(path.join(__dirname, 'findFilesRecursivelyDemoFiles'), /^file(?:1|2)/)) {
files.push(file);
}
expect(files.length).toBe(2);
expect(files.sort((a, b) => a.localeCompare(b))).toStrictEqual([
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file1.txt'),
path.join(__dirname, 'findFilesRecursivelyDemoFiles', 'file2.csv')
]);
});
});
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions packages/node-utilities/tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../ts-config/extra-strict-without-decorators.json",
"compilerOptions": {
"noEmit": true,
"incremental": false,
"types": ["vitest/globals"]
},
"include": ["./"]
}
8 changes: 8 additions & 0 deletions packages/node-utilities/tsconfig.eslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../ts-config/extra-strict-without-decorators.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true
},
"include": ["src", "tests"]
}
3 changes: 3 additions & 0 deletions packages/node-utilities/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createTsupConfig } from '../../scripts/tsup.config';

export default createTsupConfig({ target: 'es2019', format: ['cjs', 'esm'] });
imranbarbhuiya marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions packages/node-utilities/typedoc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"$schema": "https://typedoc.org/schema.json",
"entryPoints": ["src/index.ts"],
"json": "docs/api.json",
"tsconfig": "src/tsconfig.json"
}