@theholocron/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.alexignore +3 -0
- package/.editorconfig +24 -0
- package/.editorconfig-checker.json +20 -0
- package/.env +3 -0
- package/.gitattributes +12 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/dependabot.yml +6 -0
- package/.github/labeler.yml +21 -0
- package/.github/workflows/bookkeeping-pr.yml +32 -0
- package/.github/workflows/greetings.yml +32 -0
- package/.github/workflows/lint.yml +96 -0
- package/.github/workflows/publish.yml +71 -0
- package/.github/workflows/review.yml +72 -0
- package/.github/workflows/stale.yml +25 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +13 -0
- package/.husky/prepare-commit-msg +18 -0
- package/LICENSE +674 -0
- package/README.md +17 -0
- package/commitlint.config.js +9 -0
- package/eslint.config.js +9 -0
- package/media/README.md +11 -0
- package/media/error.mp3 +0 -0
- package/media/success.mp3 +0 -0
- package/media/warning.mp3 +0 -0
- package/package.json +71 -0
- package/prettier.config.js +11 -0
- package/src/cli.ts +65 -0
- package/src/commands/README.md +13 -0
- package/src/commands/bootstrap.ts +102 -0
- package/src/commands/conf/README.md +45 -0
- package/src/commands/conf/add.ts +49 -0
- package/src/commands/conf/edit.ts +12 -0
- package/src/commands/conf/view.ts +40 -0
- package/src/commands/conf.ts +10 -0
- package/src/commands/log.ts +91 -0
- package/src/const.ts +19 -0
- package/src/tasks/README.md +33 -0
- package/src/tasks/find/README.md +49 -0
- package/src/tasks/find/find-project.example.ts +23 -0
- package/src/tasks/find/find-project.ts +76 -0
- package/src/tasks/find/index.ts +1 -0
- package/src/tasks/index.ts +2 -0
- package/src/tasks/replace/README.md +61 -0
- package/src/tasks/replace/index.ts +1 -0
- package/src/tasks/replace/replace.example.ts +31 -0
- package/src/tasks/replace/replace.ts +95 -0
- package/src/ui/README.md +8 -0
- package/src/ui/index.ts +2 -0
- package/src/ui/open/README.md +58 -0
- package/src/ui/open/index.ts +7 -0
- package/src/ui/open/open-browser.ts +25 -0
- package/src/ui/open/open-editor.example.ts +14 -0
- package/src/ui/open/open-editor.ts +30 -0
- package/src/ui/prompts/README.md +36 -0
- package/src/ui/prompts/autocomplete.prompt.ts +18 -0
- package/src/ui/prompts/confirm.prompt.ts +21 -0
- package/src/ui/prompts/index.ts +17 -0
- package/src/ui/prompts/input.prompt.ts +15 -0
- package/src/ui/prompts/search.prompt.ts +33 -0
- package/src/ui/prompts/select.prompt.ts +17 -0
- package/src/ui/prompts/types.ts +8 -0
- package/src/ui/prompts/utils.ts +11 -0
- package/src/utils/$/README.md +87 -0
- package/src/utils/$/command.test.ts +48 -0
- package/src/utils/$/command.ts +21 -0
- package/src/utils/$/directory.ts +108 -0
- package/src/utils/$/file.ts +98 -0
- package/src/utils/$/index.example.ts +37 -0
- package/src/utils/$/index.ts +22 -0
- package/src/utils/$/path.ts +13 -0
- package/src/utils/$/remove.ts +47 -0
- package/src/utils/$/spawn.ts +26 -0
- package/src/utils/$/utils.ts +21 -0
- package/src/utils/README.md +15 -0
- package/src/utils/config/README.md +41 -0
- package/src/utils/config/config.ts +26 -0
- package/src/utils/config/index.ts +1 -0
- package/src/utils/config/preferences.conf.ts +45 -0
- package/src/utils/env/README.md +52 -0
- package/src/utils/env/env.example.ts +21 -0
- package/src/utils/env/env.ts +57 -0
- package/src/utils/env/index.ts +1 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/log/README.md +117 -0
- package/src/utils/log/index.ts +2 -0
- package/src/utils/log/log.example.ts +13 -0
- package/src/utils/log/log.ts +58 -0
- package/src/utils/log/logger.ts +28 -0
- package/src/utils/log/sound.ts +35 -0
- package/src/utils/node/README.md +23 -0
- package/src/utils/node/index.ts +5 -0
- package/src/utils/node/package.ts +12 -0
- package/tsconfig.json +15 -0
- package/yamllint.config.yml +20 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# $
|
|
2
|
+
|
|
3
|
+
Interact with the file system or run commands.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { $ } from "@/utils";
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const hasBrew = await $.command("brew");
|
|
12
|
+
if (hasBrew) {
|
|
13
|
+
const [err, data] = await $("brew install test");
|
|
14
|
+
|
|
15
|
+
if (err) {
|
|
16
|
+
console.error("Error:", err);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(data);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
main();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## API
|
|
28
|
+
|
|
29
|
+
### `$(command: string)`
|
|
30
|
+
|
|
31
|
+
Run a shell command. Returns a Promise that will resolve to either an error or the standard output.
|
|
32
|
+
|
|
33
|
+
#### command
|
|
34
|
+
|
|
35
|
+
The shell command to run.
|
|
36
|
+
|
|
37
|
+
Type: `string`
|
|
38
|
+
|
|
39
|
+
### `.command(command: string)`
|
|
40
|
+
|
|
41
|
+
Checks whether a bash command exists. Returns a promise resolving to true if the command is installed, false otherwise.
|
|
42
|
+
|
|
43
|
+
#### command
|
|
44
|
+
|
|
45
|
+
The shell command to check.
|
|
46
|
+
|
|
47
|
+
Type: `string`
|
|
48
|
+
|
|
49
|
+
### `.directory(path: string, maxDepth: number)`
|
|
50
|
+
|
|
51
|
+
Returns a Promise that will resolve to a tuple with either an error, or an object with `files` and the `path`.
|
|
52
|
+
|
|
53
|
+
#### path
|
|
54
|
+
|
|
55
|
+
The path to the file.
|
|
56
|
+
|
|
57
|
+
Type: `string`
|
|
58
|
+
|
|
59
|
+
#### maxDepth
|
|
60
|
+
|
|
61
|
+
The maximum levels of directories to look through.
|
|
62
|
+
|
|
63
|
+
Type: `number`
|
|
64
|
+
Default: `Infinity`
|
|
65
|
+
|
|
66
|
+
### `.file(path: string, content: string | Buffer, overwrite: boolean)`
|
|
67
|
+
|
|
68
|
+
Returns a Promise that will resolve to a tuple with either an error, or an object with `content` and the `path`.
|
|
69
|
+
|
|
70
|
+
#### path
|
|
71
|
+
|
|
72
|
+
The path to the file.
|
|
73
|
+
|
|
74
|
+
Type: `string`
|
|
75
|
+
|
|
76
|
+
#### content
|
|
77
|
+
|
|
78
|
+
The contents of the file to write or append.
|
|
79
|
+
|
|
80
|
+
Type: `string | Buffer`
|
|
81
|
+
|
|
82
|
+
#### overwrite
|
|
83
|
+
|
|
84
|
+
Whether to append to the file or overwrite them completely.
|
|
85
|
+
|
|
86
|
+
Type: `boolean`
|
|
87
|
+
Default: `false`
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { command, isInstalled } from "./command";
|
|
2
|
+
|
|
3
|
+
describe("command", () => {
|
|
4
|
+
test("execute command and return stdout", async () => {
|
|
5
|
+
const cmd = "echo Hello, world!";
|
|
6
|
+
|
|
7
|
+
const result = await command(cmd);
|
|
8
|
+
|
|
9
|
+
expect(result).toEqual([null, "Hello, world!\n", expect.any(Number)]);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("errors", () => {
|
|
13
|
+
test("return error if command fails", async () => {
|
|
14
|
+
const result = await command("{}");
|
|
15
|
+
|
|
16
|
+
expect(result[0]).toBeDefined();
|
|
17
|
+
expect(result[1]).toBeNull();
|
|
18
|
+
expect(result[2]).toEqual(expect.any(Number));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("return 'Unknown error' if error is not an instance of Error", async () => {
|
|
22
|
+
const unexpectedError = "This is not an Error object";
|
|
23
|
+
|
|
24
|
+
const result = await command("valid-command-that-will-throw-an-error", { unexpectedError });
|
|
25
|
+
|
|
26
|
+
expect(result[1]).toBeNull();
|
|
27
|
+
expect(result[2]).toEqual(expect.any(Number));
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("isInstalled", () => {
|
|
33
|
+
test("return true if command is installed", async () => {
|
|
34
|
+
const cmd = "ls"; // Assuming 'ls' command is installed on your system
|
|
35
|
+
|
|
36
|
+
const result = await isInstalled(cmd);
|
|
37
|
+
|
|
38
|
+
expect(result).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("return false if command is not installed", async () => {
|
|
42
|
+
const cmd = "nonexistent-command";
|
|
43
|
+
|
|
44
|
+
const result = await isInstalled(cmd);
|
|
45
|
+
|
|
46
|
+
expect(result).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import { type Return } from "@/types";
|
|
4
|
+
|
|
5
|
+
const asyncExec = promisify(exec);
|
|
6
|
+
|
|
7
|
+
export async function command(cmd: string, options: object = {}): Promise<Return<string>> {
|
|
8
|
+
const start = performance.now();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const { stdout } = await asyncExec(cmd, options);
|
|
12
|
+
return [null, stdout, performance.now() - start];
|
|
13
|
+
} catch (error) {
|
|
14
|
+
return [error as Error, null, performance.now() - start];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function isInstalled(cmd: string, options: object = {}): Promise<boolean> {
|
|
19
|
+
const [err] = await command(`command -v ${cmd}`, options);
|
|
20
|
+
return !err;
|
|
21
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { promises as fs, Dirent } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { HOME } from "@/const";
|
|
4
|
+
import { type Return } from "@/types";
|
|
5
|
+
import { expandTilde, isHidden, isIgnored } from "./utils";
|
|
6
|
+
|
|
7
|
+
export function getDirectoryByLevel(filepath: string, size: number = -3): string {
|
|
8
|
+
// Normalize the file path and split it into parts
|
|
9
|
+
const parts = path.normalize(filepath).split(path.sep);
|
|
10
|
+
|
|
11
|
+
// Remove the home directory path if present
|
|
12
|
+
if (filepath.startsWith(HOME)) {
|
|
13
|
+
parts.splice(0, HOME.split(path.sep).length);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Slice the last 3 directories, but if the third directory from the end is not the home directory, keep all three
|
|
17
|
+
const lastDirs = parts.slice(size);
|
|
18
|
+
|
|
19
|
+
// If three parts are kept, only return last two if the third last was home directory
|
|
20
|
+
return lastDirs.join(path.sep);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function isDirectory(pathname: string, options: object = {}): Promise<boolean> {
|
|
24
|
+
const resolvedPath = expandTilde(pathname);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const stat = await fs.stat(resolvedPath, options);
|
|
28
|
+
return stat.isDirectory();
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function readDirectory(
|
|
35
|
+
pathname: string,
|
|
36
|
+
maxDepth: number = Infinity,
|
|
37
|
+
currentDepth: number = 0,
|
|
38
|
+
options: object = {}
|
|
39
|
+
): Promise<string[]> {
|
|
40
|
+
const resolvedPath = expandTilde(pathname);
|
|
41
|
+
const files: string[] = [];
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const dirents: Dirent[] = await fs.readdir(resolvedPath, { withFileTypes: true, ...options });
|
|
45
|
+
|
|
46
|
+
for (const dirent of dirents) {
|
|
47
|
+
const fullPath = `${resolvedPath}/${dirent.name}`;
|
|
48
|
+
if (dirent.isDirectory()) {
|
|
49
|
+
if (!isHidden(dirent.name) && !isIgnored(dirent.name) && currentDepth < maxDepth) {
|
|
50
|
+
files.push(...(await readDirectory(fullPath, maxDepth, currentDepth + 1)));
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
files.push(fullPath);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return files;
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function writeDirectory(pathname: string, options: object = {}): Promise<Return<string>> {
|
|
64
|
+
const resolvedPath = expandTilde(pathname);
|
|
65
|
+
|
|
66
|
+
if (await isDirectory(resolvedPath)) {
|
|
67
|
+
return [null, resolvedPath, 0];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await fs.mkdir(resolvedPath, { recursive: true, ...options });
|
|
72
|
+
return [null, resolvedPath, 0];
|
|
73
|
+
} catch (error) {
|
|
74
|
+
return [error as Error, null, 0];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface Directory {
|
|
79
|
+
files: string[];
|
|
80
|
+
path: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function directory(pathname: string, maxDepth: number = Infinity): Promise<Return<Directory>> {
|
|
84
|
+
// if its a directory, return the resolved path and files
|
|
85
|
+
if (await isDirectory(pathname)) {
|
|
86
|
+
const files = await readDirectory(pathname, maxDepth);
|
|
87
|
+
return [
|
|
88
|
+
null,
|
|
89
|
+
{
|
|
90
|
+
files,
|
|
91
|
+
path: expandTilde(pathname),
|
|
92
|
+
},
|
|
93
|
+
0,
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// if it doesn't exist, attempt to create it and return resolved path
|
|
98
|
+
// if it's a file or something other than a directory, it will throw an error
|
|
99
|
+
const [writeErr] = await writeDirectory(pathname);
|
|
100
|
+
return [
|
|
101
|
+
writeErr,
|
|
102
|
+
{
|
|
103
|
+
files: [],
|
|
104
|
+
path: expandTilde(pathname),
|
|
105
|
+
},
|
|
106
|
+
0,
|
|
107
|
+
];
|
|
108
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as str from "@theholocron/utils-string";
|
|
3
|
+
import { type Return } from "@/types";
|
|
4
|
+
import { expandTilde } from "./utils";
|
|
5
|
+
|
|
6
|
+
const encoding = "utf-8";
|
|
7
|
+
|
|
8
|
+
export async function isFile(pathname: string, options: object = {}): Promise<boolean> {
|
|
9
|
+
const resolvedPath = expandTilde(pathname);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const stat = await fs.stat(resolvedPath, options);
|
|
13
|
+
return stat.isFile();
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface File {
|
|
20
|
+
path: string;
|
|
21
|
+
content: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function readFile(pathname: string | string[], options: object = {}): Promise<File[]> {
|
|
25
|
+
const files = str.toArray(pathname);
|
|
26
|
+
const results: File[] = [];
|
|
27
|
+
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const resolvedPath = expandTilde(file);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const content = await fs.readFile(resolvedPath, { encoding, ...options });
|
|
33
|
+
results.push({
|
|
34
|
+
content,
|
|
35
|
+
path: resolvedPath,
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
results.push({
|
|
39
|
+
content: "",
|
|
40
|
+
path: resolvedPath,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return results;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function writeFile(
|
|
49
|
+
pathname: string,
|
|
50
|
+
content?: string | Buffer,
|
|
51
|
+
overwrite: boolean = false,
|
|
52
|
+
options: object = {}
|
|
53
|
+
): Promise<Return<File>> {
|
|
54
|
+
const resolvedPath = expandTilde(pathname);
|
|
55
|
+
|
|
56
|
+
if (!content) {
|
|
57
|
+
const [data] = await readFile(resolvedPath);
|
|
58
|
+
return [null, data, 0];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
if (overwrite) {
|
|
63
|
+
await fs.writeFile(resolvedPath, content, { encoding, ...options });
|
|
64
|
+
} else {
|
|
65
|
+
await fs.appendFile(resolvedPath, content, { encoding, ...options });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const [data] = await readFile(resolvedPath);
|
|
69
|
+
return [null, data, 0];
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return [error as Error, null, 0];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// only handles single files
|
|
76
|
+
export async function file(
|
|
77
|
+
pathname: string,
|
|
78
|
+
content?: string | Buffer,
|
|
79
|
+
overwrite: boolean = false
|
|
80
|
+
): Promise<Return<File>> {
|
|
81
|
+
// if its a file and there's no content, return the resolved path and contents
|
|
82
|
+
if ((await isFile(pathname)) && !content) {
|
|
83
|
+
const [contents] = await readFile(pathname);
|
|
84
|
+
return [null, contents, 0];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// if it doesn't exist, attempt to create it and return resolved path
|
|
88
|
+
// if it's a file or something other than a directory, it will throw an error
|
|
89
|
+
const [writeErr, write] = await writeFile(pathname, content, overwrite);
|
|
90
|
+
return [
|
|
91
|
+
writeErr,
|
|
92
|
+
{
|
|
93
|
+
content: write?.content ?? "",
|
|
94
|
+
path: write?.path ?? "",
|
|
95
|
+
},
|
|
96
|
+
0,
|
|
97
|
+
];
|
|
98
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { $ } from "./index";
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const isFile = await $.file("./README.md");
|
|
5
|
+
const isNotAFile = await $.file("./");
|
|
6
|
+
const isResolvedFile = await $.file("~/Downloads/chevron-down.tsx");
|
|
7
|
+
console.log({
|
|
8
|
+
isFile,
|
|
9
|
+
isNotAFile,
|
|
10
|
+
isResolvedFile,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const isDirectory = await $.directory("./");
|
|
14
|
+
const isNotADirectory = await $.directory("./README.md");
|
|
15
|
+
const isResolvedDirectory = await $.directory("~/Downloads");
|
|
16
|
+
const isCreatedDirectory = await $.directory("./test");
|
|
17
|
+
console.log({
|
|
18
|
+
isDirectory,
|
|
19
|
+
isNotADirectory,
|
|
20
|
+
isResolvedDirectory,
|
|
21
|
+
isCreatedDirectory,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const [err, output, time] = await $("ls .");
|
|
25
|
+
if (err) {
|
|
26
|
+
console.error("Error executing command:", err);
|
|
27
|
+
} else {
|
|
28
|
+
console.log("Command output:", output);
|
|
29
|
+
console.log("Execution time:", time, "ms");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Checking if a command is installed
|
|
33
|
+
const isBrewInstalled = await $.command("brew");
|
|
34
|
+
console.log("Is brew installed?", isBrewInstalled);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Return } from "@/types";
|
|
2
|
+
import { command, isInstalled } from "./command";
|
|
3
|
+
import { directory } from "./directory";
|
|
4
|
+
import { file } from "./file";
|
|
5
|
+
import { path } from "./path";
|
|
6
|
+
import { remove } from "./remove";
|
|
7
|
+
import { spwn } from "./spawn";
|
|
8
|
+
|
|
9
|
+
export { type Directory } from "./directory";
|
|
10
|
+
export { type File } from "./file";
|
|
11
|
+
export { getDirectoryByLevel } from "./directory";
|
|
12
|
+
|
|
13
|
+
export async function $(cmd: string, options: object = {}): Promise<Return<string>> {
|
|
14
|
+
return await command(cmd, options);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
$.command = isInstalled;
|
|
18
|
+
$.directory = directory;
|
|
19
|
+
$.file = file;
|
|
20
|
+
$.path = path;
|
|
21
|
+
$.remove = remove;
|
|
22
|
+
$.spawn = spwn;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { expandTilde } from "./utils";
|
|
3
|
+
|
|
4
|
+
export async function path(pathname: string): Promise<boolean> {
|
|
5
|
+
const resolvedPath = expandTilde(pathname);
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
await fs.access(resolvedPath);
|
|
9
|
+
return true;
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import { type Return } from "@/types";
|
|
3
|
+
import { isDirectory } from "./directory";
|
|
4
|
+
import { isFile } from "./file";
|
|
5
|
+
import { expandTilde } from "./utils";
|
|
6
|
+
|
|
7
|
+
export async function removeFile(pathname: string, options: object = {}): Promise<boolean> {
|
|
8
|
+
const resolvedPath = expandTilde(pathname);
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
await fs.rm(resolvedPath, options);
|
|
12
|
+
return true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function removeDirectory(pathname: string, options: object = {}): Promise<boolean> {
|
|
19
|
+
const resolvedPath = expandTilde(pathname);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await fs.rmdir(resolvedPath, options);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function remove(pathname: string, options: object = {}): Promise<Return<boolean>> {
|
|
30
|
+
const resolvedPath = expandTilde(pathname);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (await isDirectory(resolvedPath)) {
|
|
34
|
+
await fs.rmdir(resolvedPath, { recursive: true, ...options });
|
|
35
|
+
return [null, true, 0];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (await isFile(resolvedPath)) {
|
|
39
|
+
await fs.rm(resolvedPath, options);
|
|
40
|
+
return [null, true, 0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return [new Error(`Path does not exist: ${resolvedPath}`), null, 0];
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return [error as Error, false, 0];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { type Return } from "@/types";
|
|
3
|
+
|
|
4
|
+
const asyncSpawn = (cmd: string, args: string[], options: object = {}): Promise<void> =>
|
|
5
|
+
new Promise((resolve, reject) => {
|
|
6
|
+
const process = spawn(cmd, args, options);
|
|
7
|
+
|
|
8
|
+
process.on("close", (code) => {
|
|
9
|
+
if (code !== 0) {
|
|
10
|
+
reject(new Error(`process exited with code ${code}`));
|
|
11
|
+
} else {
|
|
12
|
+
resolve();
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export async function spwn(cmd: string, args: string[], options: object = {}): Promise<Return<boolean>> {
|
|
18
|
+
const start = performance.now();
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await asyncSpawn(cmd, args, options);
|
|
22
|
+
return [null, true, performance.now() - start];
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return [error as Error, false, performance.now() - start];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { HOME, SYSTEM_IGNORED_FOLDERS, type IgnoredFolders } from "@/const";
|
|
2
|
+
import { config } from "../config";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Expands the tilde (~) in a path to the user's home directory.
|
|
6
|
+
*/
|
|
7
|
+
export function expandTilde(pathname: string): string {
|
|
8
|
+
const tilde = "~";
|
|
9
|
+
|
|
10
|
+
if (pathname.startsWith(tilde)) {
|
|
11
|
+
return pathname.replace(tilde, HOME);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return pathname;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const isHidden = (pathname: string): boolean => pathname.startsWith(".");
|
|
18
|
+
|
|
19
|
+
export const isIgnored = (pathname: string): boolean =>
|
|
20
|
+
SYSTEM_IGNORED_FOLDERS.includes(pathname as IgnoredFolders) ||
|
|
21
|
+
config.get("preferences.ignoredFolders").includes(pathname);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Utilities
|
|
2
|
+
|
|
3
|
+
A set of functions designed to be littered everywhere.
|
|
4
|
+
|
|
5
|
+
## What's Available
|
|
6
|
+
|
|
7
|
+
- [$](./$/README.md) - run a bash command in Node
|
|
8
|
+
- [config](./config/README.md) - read and write to a configuration file
|
|
9
|
+
- [env](./env/README.md) - read and write to a `.env` for use with Environment variables
|
|
10
|
+
- [log](./log/README.md) - a logger
|
|
11
|
+
- [node](./node/README.md) - interact with node packages
|
|
12
|
+
|
|
13
|
+
## Rules
|
|
14
|
+
|
|
15
|
+
- There should be no logging; all Utilities should rely on what is returned, no thrown errors, only returns
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
A wrapper around [`conf`](https://github.com/sindresorhus/conf) that provides a configuration file for storing environment variables.
|
|
4
|
+
|
|
5
|
+
The configuration will pull from a `config.json` file within the operating systems [preferred configuration locations](https://github.com/sindresorhus/env-paths?tab=readme-ov-file#pathsdata).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { config } from "@/utils";
|
|
11
|
+
|
|
12
|
+
const debugMode = "preferences.debug";
|
|
13
|
+
|
|
14
|
+
// get an item
|
|
15
|
+
const isDebugMode = config.get(debugMode);
|
|
16
|
+
|
|
17
|
+
// set an item
|
|
18
|
+
config.write(debugMode, true);
|
|
19
|
+
```
|
|
20
|
+
## API
|
|
21
|
+
|
|
22
|
+
The API is a wrapper, so look at the [docs](https://github.com/sindresorhus/conf) for a detailed list of functions. The following is the ones that are most commonly used.
|
|
23
|
+
|
|
24
|
+
### `.get(key, defaultValue?)`
|
|
25
|
+
|
|
26
|
+
Get an item or `defaultValue` if the item does not exist.
|
|
27
|
+
|
|
28
|
+
### `.set(key, value)`
|
|
29
|
+
|
|
30
|
+
Set an item.
|
|
31
|
+
|
|
32
|
+
The value must be JSON serializable. Trying to set the type `undefined`, `function`, or `symbol` will result in a `TypeError`.
|
|
33
|
+
|
|
34
|
+
### `.set(object)`
|
|
35
|
+
|
|
36
|
+
Set multiple items at once.
|
|
37
|
+
|
|
38
|
+
### `.edit()`
|
|
39
|
+
|
|
40
|
+
Open the configuration file in your preferred `$EDITOR`;
|
|
41
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Conf, { type Schema } from "conf";
|
|
2
|
+
import pkg from "@/package";
|
|
3
|
+
|
|
4
|
+
import { preferences, type PreferencesSchema } from "./preferences.conf";
|
|
5
|
+
|
|
6
|
+
interface ConfigSchema {
|
|
7
|
+
name: string;
|
|
8
|
+
preferences: PreferencesSchema;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const schema: Schema<ConfigSchema> = {
|
|
12
|
+
name: {
|
|
13
|
+
default: "holocron",
|
|
14
|
+
type: "string",
|
|
15
|
+
},
|
|
16
|
+
preferences: {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: preferences,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const config = new Conf({
|
|
23
|
+
projectName: pkg.name,
|
|
24
|
+
projectVersion: pkg.version,
|
|
25
|
+
schema,
|
|
26
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./config";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { HOME, OS } from "@/const";
|
|
3
|
+
|
|
4
|
+
export interface PreferencesSchema {
|
|
5
|
+
debug: boolean;
|
|
6
|
+
destination: string;
|
|
7
|
+
ignoredFolders: string[];
|
|
8
|
+
notifications: boolean;
|
|
9
|
+
sound: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const preferences = {
|
|
13
|
+
debug: {
|
|
14
|
+
type: "boolean",
|
|
15
|
+
default: false,
|
|
16
|
+
},
|
|
17
|
+
destination: {
|
|
18
|
+
type: "string",
|
|
19
|
+
default: `${OS}/Desktop`,
|
|
20
|
+
},
|
|
21
|
+
ignoredFolders: {
|
|
22
|
+
type: "array",
|
|
23
|
+
items: {
|
|
24
|
+
type: "string",
|
|
25
|
+
},
|
|
26
|
+
uniqueItems: true,
|
|
27
|
+
default: [".DS_Store", "holocron", "coverage", "dist", "node_modules"],
|
|
28
|
+
},
|
|
29
|
+
logs: {
|
|
30
|
+
type: "string",
|
|
31
|
+
default:
|
|
32
|
+
process.env.LOG_DIR ||
|
|
33
|
+
(OS === "win32"
|
|
34
|
+
? path.join(process.env.LOCALAPPDATA || path.join(HOME, "AppData", "Local"), "Holocron", "logs")
|
|
35
|
+
: path.join(HOME, ".holocron", "logs")),
|
|
36
|
+
},
|
|
37
|
+
notifications: {
|
|
38
|
+
type: "boolean",
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
sound: {
|
|
42
|
+
type: "boolean",
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
};
|