@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,23 @@
|
|
|
1
|
+
import { HOME } from "@/const";
|
|
2
|
+
import { find } from "@/tasks";
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const [, , folder] = process.argv;
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const [err, data] = await find(folder, HOME, {});
|
|
9
|
+
|
|
10
|
+
if (err) {
|
|
11
|
+
console.error(err);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.log("Folders Found:\n");
|
|
16
|
+
console.table(data);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.error("An error occurred:", error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
main();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { type CLIOptions } from "@/cli";
|
|
4
|
+
import { HOME, SYSTEM_IGNORED_FOLDERS, type IgnoredFolders } from "@/const";
|
|
5
|
+
import { type Return } from "@/types";
|
|
6
|
+
import { $, config, log } from "@/utils";
|
|
7
|
+
|
|
8
|
+
export type Project = Record<string, string>;
|
|
9
|
+
export interface FindProjectsOpts extends CLIOptions {
|
|
10
|
+
maxSearchDepth?: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const FN = "find.projects";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Searches directory for any Node.js project (by detecting package.json), ignoring hidden directories.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} [location] - Directory path to search within.
|
|
19
|
+
* @param {FindProjectsOpts} [options] - Additional options for customization.
|
|
20
|
+
* @returns {Promise<Return<Project[]>>} An error or an array of locations with all of the file paths found.
|
|
21
|
+
*/
|
|
22
|
+
export async function findProjects(location?: string, options: FindProjectsOpts = {}): Promise<Return<Project[]>> {
|
|
23
|
+
log.data(FN, "arguments", { location, ...options }, options);
|
|
24
|
+
const start = performance.now();
|
|
25
|
+
const maxDepth: number = options?.maxSearchDepth || 5;
|
|
26
|
+
const results: Project[] = [];
|
|
27
|
+
const loc: string = location || HOME;
|
|
28
|
+
|
|
29
|
+
async function searchDirectory(cwd: string, depth: number = 0): Promise<void> {
|
|
30
|
+
log.process(FN, `searching in ${cwd}`, options);
|
|
31
|
+
let files: string[];
|
|
32
|
+
try {
|
|
33
|
+
files = await fs.readdir(cwd);
|
|
34
|
+
} catch {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
log.data(FN, "files", files, options);
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
if (
|
|
41
|
+
file.startsWith(".") ||
|
|
42
|
+
SYSTEM_IGNORED_FOLDERS.includes(file as IgnoredFolders) ||
|
|
43
|
+
config.get("preferences.ignoredFolders").includes(file)
|
|
44
|
+
) {
|
|
45
|
+
continue; // skip all of these files, folders
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const filePath = path.join(cwd, file);
|
|
49
|
+
log.process(FN, `checking if ${filePath} is a directory`, options);
|
|
50
|
+
const [, dir] = await $.directory(filePath);
|
|
51
|
+
// if there are no files in it, its not a folder i care about
|
|
52
|
+
if (!dir?.files.length || !dir?.path) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
log.data(FN, "isValidDirectory", dir?.path ?? "", options);
|
|
57
|
+
|
|
58
|
+
if (dir.files.includes(path.join(dir.path, "package.json"))) {
|
|
59
|
+
// Node.js project detected
|
|
60
|
+
const pkg = await fs.readFile(path.join(dir.path, "package.json"), "utf-8");
|
|
61
|
+
const packageData = JSON.parse(pkg);
|
|
62
|
+
results.push({
|
|
63
|
+
name: packageData.name,
|
|
64
|
+
value: dir.path,
|
|
65
|
+
});
|
|
66
|
+
} else if (maxDepth === -1 || depth < maxDepth) {
|
|
67
|
+
await searchDirectory(filePath, depth + 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await searchDirectory(loc);
|
|
73
|
+
|
|
74
|
+
const end = performance.now();
|
|
75
|
+
return [null, results, end - start];
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./find-project";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Find & Replace
|
|
2
|
+
|
|
3
|
+
Find a string in a file and replace it with something else.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { findReplace } from "@/tasks";
|
|
9
|
+
|
|
10
|
+
const filePaths = [
|
|
11
|
+
`/packages/developer-experience/sdk-session/src/tests/handlers.ts`,
|
|
12
|
+
];
|
|
13
|
+
const baseString: string = "const BAM_BROWSER_SDK_VERSION =";
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
const [, , version] = process.argv;
|
|
17
|
+
const [major, minor] = version.split(".");
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const [err, data, duration] = await findReplace(filePaths, baseString, `${major}.${minor}`);
|
|
21
|
+
|
|
22
|
+
if (err) {
|
|
23
|
+
console.error("Error:", err);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log("Files replaced:\n");
|
|
28
|
+
console.table(data);
|
|
29
|
+
console.log(`It took ${duration} milliseconds`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error("Error:", error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
main();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
### `findReplace(filePaths: string[], searchTerm: string, replacement: string)`
|
|
42
|
+
|
|
43
|
+
Returns an array of file paths that were modified.
|
|
44
|
+
|
|
45
|
+
#### filePaths
|
|
46
|
+
|
|
47
|
+
The file paths with which to search.
|
|
48
|
+
|
|
49
|
+
Type: `string[]`
|
|
50
|
+
|
|
51
|
+
#### searchTerm
|
|
52
|
+
|
|
53
|
+
The search term to look for and replace.
|
|
54
|
+
|
|
55
|
+
Type: `string`
|
|
56
|
+
|
|
57
|
+
#### replacement
|
|
58
|
+
|
|
59
|
+
A string to replace with
|
|
60
|
+
|
|
61
|
+
Type: `string`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./replace";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { HOME } from "@/const";
|
|
2
|
+
import { find } from "@/tasks";
|
|
3
|
+
|
|
4
|
+
const filePaths = [
|
|
5
|
+
`${HOME}/Code/disney/solo/apps/commerce/src/common/context/__browser-mocks__/handlers.ts`,
|
|
6
|
+
`${HOME}/Code/disney/solo/packages/developer-experience/sdk-session/src/tests/handlers.ts`,
|
|
7
|
+
`${HOME}/Code/disney/solo/packages/instrumentation/src/glimpse/hooks/use-fire-fed-purchase-event.test.tsx`,
|
|
8
|
+
];
|
|
9
|
+
const baseString: string = "const BAM_BROWSER_SDK_VERSION =";
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
const [, , version] = process.argv;
|
|
13
|
+
const [major, minor] = version.split(".");
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const [err, data, duration] = await find.replace(filePaths, baseString, `${major}.${minor}`);
|
|
17
|
+
|
|
18
|
+
if (err) {
|
|
19
|
+
console.error("Error:", err);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log("Files replaced:\n");
|
|
24
|
+
console.table(data);
|
|
25
|
+
console.log(`It took ${duration} milliseconds`);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("Error:", error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
main();
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type CLIOptions } from "@/cli";
|
|
2
|
+
import { type Return } from "@/types";
|
|
3
|
+
import { $, log } from "@/utils";
|
|
4
|
+
|
|
5
|
+
const FN = "find.replace";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Perform a case-sensitive replacement in a string, keeping the casing format of each found instance.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} text - The text in which to perform the replacement.
|
|
11
|
+
* @param {string} searchTerm - The term to search for, ignoring case.
|
|
12
|
+
* @param {string} replacement - The replacement term.
|
|
13
|
+
* @returns {string} - The updated text with replacements made.
|
|
14
|
+
*/
|
|
15
|
+
function replaceWithRespectToCase(text: string, searchTerm: string, replacement: string): string {
|
|
16
|
+
// Create a regex to match search term with flexible spacing/hyphens between words
|
|
17
|
+
const words = searchTerm.split(/[- ]/).map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")); // Escape special chars
|
|
18
|
+
const regex = new RegExp(words.join("[- ]?"), "gi"); // Allow for spaces or hyphens between words
|
|
19
|
+
|
|
20
|
+
return text.replace(regex, (match) => {
|
|
21
|
+
// Transform replacement to match the case of the original found match
|
|
22
|
+
const replacementWords = replacement.split(/[- ]/);
|
|
23
|
+
const transformed = match
|
|
24
|
+
.split(/[- ]/)
|
|
25
|
+
.map((word, index) => {
|
|
26
|
+
const replacementWord = replacementWords[index] || ""; // Handle possible extra words in replacement
|
|
27
|
+
return /[A-Z]/.test(word[0])
|
|
28
|
+
? replacementWord.charAt(0).toUpperCase() + replacementWord.slice(1)
|
|
29
|
+
: replacementWord.toLowerCase();
|
|
30
|
+
})
|
|
31
|
+
.join(" ");
|
|
32
|
+
|
|
33
|
+
return transformed.trim();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Update a specific string in multiple files.
|
|
39
|
+
*
|
|
40
|
+
* @param {string[]} filePaths - An array of file paths to be updated.
|
|
41
|
+
* @param {string} searchTerm - The base string to match.
|
|
42
|
+
* @param {string} replacement - The new string to replace with.
|
|
43
|
+
* @param {CLIOptions} [options={}] - CLI options for logging and additional processing.
|
|
44
|
+
* @returns {Promise<Return<string[]>>} - A promise that resolves to a tuple containing an error (if any), an object with the update status for each domain, and the time taken.
|
|
45
|
+
*/
|
|
46
|
+
export async function findReplace(
|
|
47
|
+
filePaths: string[],
|
|
48
|
+
searchTerm: string,
|
|
49
|
+
replacement: string,
|
|
50
|
+
options: CLIOptions = {}
|
|
51
|
+
): Promise<Return<string[]>> {
|
|
52
|
+
if (!filePaths || !Array.isArray(filePaths)) {
|
|
53
|
+
return [new Error("No file paths were provided!"), null, 0];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!searchTerm) {
|
|
57
|
+
return [new Error("No search term was provided!"), null, 0];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!replacement) {
|
|
61
|
+
return [new Error("No replacement was provided!"), null, 0];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const start = performance.now();
|
|
65
|
+
const output: string[] = [];
|
|
66
|
+
|
|
67
|
+
for (const filePath of filePaths) {
|
|
68
|
+
log.process(FN, `Searching ${filePath} for occurrences of "${searchTerm}"`, options);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Read the file content
|
|
72
|
+
const [fileErr, file] = await $.file(filePath);
|
|
73
|
+
if (fileErr || !file?.content) {
|
|
74
|
+
log.info(FN, `Cannot read file ${filePath}: skipping`, options);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Perform the replacement
|
|
79
|
+
const updatedData = replaceWithRespectToCase(file.content, searchTerm, replacement);
|
|
80
|
+
|
|
81
|
+
// Write back only if the content changed
|
|
82
|
+
if (updatedData !== file.content) {
|
|
83
|
+
await $.file(filePath, updatedData, true); // Ensure this function overwrites the file content
|
|
84
|
+
output.push(filePath);
|
|
85
|
+
log.process(FN, `Updated ${filePath}`, options);
|
|
86
|
+
} else {
|
|
87
|
+
log.process(FN, `No changes in ${filePath}`, options);
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
log.error(FN, `Error processing file ${filePath}: ${(error as Error).message}`, options);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return [null, output, performance.now() - start];
|
|
95
|
+
}
|
package/src/ui/README.md
ADDED
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Open an Application
|
|
2
|
+
|
|
3
|
+
Open an application after a confirmation.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { open } from "@/ui";
|
|
9
|
+
|
|
10
|
+
async function main () {
|
|
11
|
+
const [, , name, location] = process.argv;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const [err, isOpened] = await open.editor(name, location);
|
|
15
|
+
|
|
16
|
+
if (err) {
|
|
17
|
+
console.error(err);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(`${name} ${isOpened ? "was" : "was not"} opened.`);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error('An error occurred:', error);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
main();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## API
|
|
33
|
+
|
|
34
|
+
### `editor(name: string, location: string)`
|
|
35
|
+
|
|
36
|
+
Returns a Promise that resolves to a boolean.
|
|
37
|
+
|
|
38
|
+
#### name
|
|
39
|
+
|
|
40
|
+
A name of the application.
|
|
41
|
+
|
|
42
|
+
Type: `string`
|
|
43
|
+
|
|
44
|
+
#### location
|
|
45
|
+
|
|
46
|
+
A path of where the application is located.
|
|
47
|
+
|
|
48
|
+
Type: `string`
|
|
49
|
+
|
|
50
|
+
### `browser(url: string)`
|
|
51
|
+
|
|
52
|
+
Opens the URL in the users default browser.
|
|
53
|
+
|
|
54
|
+
#### url
|
|
55
|
+
|
|
56
|
+
The URL to open.
|
|
57
|
+
|
|
58
|
+
Type: `string`
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import defaultBrowser from "default-browser";
|
|
2
|
+
import open, { apps } from "open";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Opens an application if the user confirms.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} name - The name of the application.
|
|
8
|
+
* @returns {Promise<void>} - A promise that resolves to a tuple containing an error (if any), a boolean indicating if the app was opened, and the time taken to perform the operation.
|
|
9
|
+
*/
|
|
10
|
+
export async function openBrowser(url: string): Promise<void> {
|
|
11
|
+
if (!url) {
|
|
12
|
+
throw new Error("No URL was provided!");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let browser = apps.browser;
|
|
16
|
+
const { name } = await defaultBrowser();
|
|
17
|
+
const supported = ["chrome", "firefox", "edge"];
|
|
18
|
+
if (!supported.includes(name.toLowerCase())) {
|
|
19
|
+
browser = apps.chrome;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await open(url, {
|
|
23
|
+
app: { name: browser },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { open } from "@/ui";
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const [, , name, location] = process.argv;
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
await open.editor(name, location);
|
|
8
|
+
} catch (error) {
|
|
9
|
+
console.error("An error occurred:", error);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
main();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import openEditor from "open-editor";
|
|
2
|
+
import { prompt } from "@/ui";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Opens an application if the user confirms.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} name - The name of the application.
|
|
8
|
+
* @param {string} location - The file location of the application.
|
|
9
|
+
* @returns {Promise<void>}
|
|
10
|
+
*/
|
|
11
|
+
export async function openApp(location: string, name?: string, bypass: boolean = false): Promise<void> {
|
|
12
|
+
if (!location) {
|
|
13
|
+
throw new Error("No app location was provided!");
|
|
14
|
+
}
|
|
15
|
+
const open = async () =>
|
|
16
|
+
await openEditor(
|
|
17
|
+
[
|
|
18
|
+
{
|
|
19
|
+
file: location,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
{ wait: true }
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (!bypass) {
|
|
26
|
+
await prompt.confirm(`Do you want to open ${name}?`, open);
|
|
27
|
+
} else {
|
|
28
|
+
open();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Prompts
|
|
2
|
+
|
|
3
|
+
Inquirer prompts
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { prompt } from "@/ui";
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
const [, , project] = process.argv;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const filepath = await prompt.search(project, undefined, undefined, {});
|
|
15
|
+
console.log(filepath);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
console.error("Error:", error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
main();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## API
|
|
26
|
+
|
|
27
|
+
### `project(message?: string)`
|
|
28
|
+
|
|
29
|
+
Returns a string of the directory selected.
|
|
30
|
+
|
|
31
|
+
#### message
|
|
32
|
+
|
|
33
|
+
The message to display to the user.
|
|
34
|
+
|
|
35
|
+
Type: `string`
|
|
36
|
+
Default: "Enter a project:"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import autocomplete from "inquirer-autocomplete-standalone";
|
|
2
|
+
import { type Choice } from "./types";
|
|
3
|
+
|
|
4
|
+
export async function autocompletePrompt(source: Choice<string>[], msg?: string): Promise<string> {
|
|
5
|
+
return await autocomplete({
|
|
6
|
+
emptyText: "No results found. Please enter a term",
|
|
7
|
+
message: msg || "Search a term",
|
|
8
|
+
source: async (input?: string) =>
|
|
9
|
+
source.filter(({ name }) => {
|
|
10
|
+
const proj = name?.toLowerCase() ?? "";
|
|
11
|
+
if (proj.length > 0) {
|
|
12
|
+
return proj.includes(input?.toLowerCase() ?? "");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return "";
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { confirm } from "@inquirer/prompts";
|
|
2
|
+
|
|
3
|
+
type Callback = () => void | Promise<void>;
|
|
4
|
+
|
|
5
|
+
export async function confirmPrompt(
|
|
6
|
+
message: string,
|
|
7
|
+
callback: Callback,
|
|
8
|
+
error: Callback = () => {
|
|
9
|
+
/* noOp */
|
|
10
|
+
}
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const should = await confirm({
|
|
13
|
+
message,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
if (should) {
|
|
17
|
+
await callback();
|
|
18
|
+
} else {
|
|
19
|
+
await error();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { autocompletePrompt } from "./autocomplete.prompt";
|
|
2
|
+
import { confirmPrompt } from "./confirm.prompt";
|
|
3
|
+
import { inputPrompt } from "./input.prompt";
|
|
4
|
+
import { searchPrompt } from "./search.prompt";
|
|
5
|
+
import { selectPrompt } from "./select.prompt";
|
|
6
|
+
import { validate } from "./utils";
|
|
7
|
+
|
|
8
|
+
export * from "./types";
|
|
9
|
+
|
|
10
|
+
export const prompt = {
|
|
11
|
+
autocomplete: autocompletePrompt,
|
|
12
|
+
confirm: confirmPrompt,
|
|
13
|
+
input: inputPrompt,
|
|
14
|
+
search: searchPrompt,
|
|
15
|
+
select: selectPrompt,
|
|
16
|
+
validate,
|
|
17
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { input } from "@inquirer/prompts";
|
|
2
|
+
import { validate } from "./utils";
|
|
3
|
+
|
|
4
|
+
export async function inputPrompt(message: string, isRequired?: boolean = false, options): Promise<string> {
|
|
5
|
+
const opts = {
|
|
6
|
+
message,
|
|
7
|
+
...options,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
if (isRequired) {
|
|
11
|
+
opts.validate = validate.isNotEmpty;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return await input(opts);
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import autocomplete from "inquirer-autocomplete-standalone";
|
|
2
|
+
import { type CLIOptions } from "@/cli";
|
|
3
|
+
import { findProjects } from "@/tasks";
|
|
4
|
+
import { $, getDirectoryByLevel } from "@/utils";
|
|
5
|
+
|
|
6
|
+
export async function searchPrompt(location?: string, msg?: string, options: CLIOptions): Promise<string> {
|
|
7
|
+
const message = msg || "Choose a project:";
|
|
8
|
+
|
|
9
|
+
const dirname: string = await autocomplete({
|
|
10
|
+
emptyText: "No results found. Please enter the location",
|
|
11
|
+
message,
|
|
12
|
+
pageSize: 10,
|
|
13
|
+
source: async (input?: string) => {
|
|
14
|
+
const term = input?.toLowerCase() ?? "";
|
|
15
|
+
const [, results] = await findProjects(location, options);
|
|
16
|
+
const filtered = results
|
|
17
|
+
.map(({ name, value }) => ({ name: `${name} (${getDirectoryByLevel(value)})`, value }))
|
|
18
|
+
.filter(({ name }) => {
|
|
19
|
+
const proj = name?.toLowerCase() ?? "";
|
|
20
|
+
if (proj.length > 0) {
|
|
21
|
+
return proj.includes(term);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return "";
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return filtered;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const [, dir] = await $.directory(dirname);
|
|
32
|
+
return dir?.path ?? "";
|
|
33
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { checkbox } from "@inquirer/prompts";
|
|
2
|
+
import { type Choice } from "./types";
|
|
3
|
+
|
|
4
|
+
export async function selectPrompt(
|
|
5
|
+
selected: string[] | null,
|
|
6
|
+
message: string,
|
|
7
|
+
choices: Choice<string>[]
|
|
8
|
+
): Promise<string[]> {
|
|
9
|
+
if (selected) {
|
|
10
|
+
return selected;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return await checkbox({
|
|
14
|
+
message,
|
|
15
|
+
choices,
|
|
16
|
+
});
|
|
17
|
+
}
|