@cmorales_/touch 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/README.md +103 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +125 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# @cmorales/touch
|
|
2
|
+
|
|
3
|
+
Creating complex file structures has never been easier. `@cmorales/touch` is a powerful CLI tool that allows you to generate nested directories and files using a simple and intuitive syntax.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
You can use the package directly via `npx` or install it globally.
|
|
8
|
+
|
|
9
|
+
### Using `npx` (Recommended)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @cmorales/touch <structure>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Global Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @cmorales/touch
|
|
19
|
+
# or
|
|
20
|
+
pnpm add -g @cmorales/touch
|
|
21
|
+
# or
|
|
22
|
+
bun add -g @cmorales/touch
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
The basic syntax is:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
touch <input>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Where `<input>` describes the file and folder structure you want to create.
|
|
34
|
+
|
|
35
|
+
### Key Features
|
|
36
|
+
|
|
37
|
+
#### 1. Creating Directories
|
|
38
|
+
To create a directory, simply append a `/` to the name.
|
|
39
|
+
- `app/` -> Creates a folder named `app`.
|
|
40
|
+
- `app/models/` -> Creates an `app` folder and a `models` folder inside it.
|
|
41
|
+
|
|
42
|
+
#### 2. Grouping
|
|
43
|
+
You can group multiple items at the same level using `{}`, `[]`, or `()`. All work identically and can be used interchangeably.
|
|
44
|
+
|
|
45
|
+
- **Curly Braces:** `src/{components,utils}`
|
|
46
|
+
- **Square Brackets:** `src/[components,utils]`
|
|
47
|
+
- **Parentheses:** `src/(components,utils)`
|
|
48
|
+
|
|
49
|
+
All the above commands create `src/components` and `src/utils`.
|
|
50
|
+
|
|
51
|
+
#### 3. Nesting
|
|
52
|
+
You can nest groups within groups to build complex trees.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
src/{components/{Button,Input},utils}
|
|
56
|
+
```
|
|
57
|
+
Creates:
|
|
58
|
+
- `src/components/Button`
|
|
59
|
+
- `src/components/Input`
|
|
60
|
+
- `src/utils`
|
|
61
|
+
|
|
62
|
+
#### 4. Extensions
|
|
63
|
+
You can specify extensions in two ways:
|
|
64
|
+
|
|
65
|
+
- **Per File:** Directly in the name.
|
|
66
|
+
`src/{index.ts,styles.css}`
|
|
67
|
+
|
|
68
|
+
- **Default Extension:** If you provide an extension at the end of a group, it will be applied to all files in that group that don't already have one.
|
|
69
|
+
|
|
70
|
+
`src/{components,utils}.ts`
|
|
71
|
+
Creates:
|
|
72
|
+
- `src/components.ts`
|
|
73
|
+
- `src/utils.ts`
|
|
74
|
+
|
|
75
|
+
### Examples
|
|
76
|
+
|
|
77
|
+
**React Component Structure:**
|
|
78
|
+
```bash
|
|
79
|
+
src/components/Button/{index.tsx,styles.module.css,types.ts}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**MVC Backend Structure:**
|
|
83
|
+
```bash
|
|
84
|
+
src/{controllers/,models/,routes/,services/}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Complex Project Setup:**
|
|
88
|
+
```bash
|
|
89
|
+
my-app/{src/{components/,hooks/,utils/},public/,package.json,README.md}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## About the Author
|
|
93
|
+
|
|
94
|
+
Created by **Cristian Morales**.
|
|
95
|
+
|
|
96
|
+
- **GitHub:** [Cristian-F-M](https://github.com/Cristian-F-M)
|
|
97
|
+
- **Twitter:** [@Morales_M20](https://x.com/Morales_M20)
|
|
98
|
+
|
|
99
|
+
## Support
|
|
100
|
+
|
|
101
|
+
If you find this tool useful, you can support my work!
|
|
102
|
+
|
|
103
|
+
<a href="https://www.buymeacoffee.com/cmorales" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
declare const NodeTypes: {
|
|
3
|
+
readonly DIR: "dir";
|
|
4
|
+
readonly FILE: "file";
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type NodeType = typeof NodeTypes
|
|
8
|
+
|
|
9
|
+
type Node =
|
|
10
|
+
| { type: NodeType['DIR']; name: string; children: Node[] }
|
|
11
|
+
| { type: NodeType['FILE']; name: string }
|
|
12
|
+
|
|
13
|
+
declare function build(tree: Node, base: string, defaultExt?: string): void;
|
|
14
|
+
|
|
15
|
+
declare function parse(input: string): Node;
|
|
16
|
+
declare function getExtension(input: string): string;
|
|
17
|
+
|
|
18
|
+
export { build, getExtension, parse };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/builder.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
|
|
7
|
+
// src/logger.ts
|
|
8
|
+
import pc from "picocolors";
|
|
9
|
+
var log = (msg) => console.log(pc.green(msg));
|
|
10
|
+
var warn = (msg) => console.log(pc.yellow(msg));
|
|
11
|
+
var success = (msg) => console.log(pc.bold(pc.cyan(msg)));
|
|
12
|
+
|
|
13
|
+
// src/builder.ts
|
|
14
|
+
function build(tree, base, defaultExt = "") {
|
|
15
|
+
let files = 0;
|
|
16
|
+
let dirs = 0;
|
|
17
|
+
function walk(node, current) {
|
|
18
|
+
if (node.type === "dir") {
|
|
19
|
+
const dirPath = path.join(current, node.name);
|
|
20
|
+
if (fs.existsSync(dirPath)) warn(`exists: ${dirPath}`);
|
|
21
|
+
if (!fs.existsSync(dirPath)) {
|
|
22
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
23
|
+
log(`created: ${dirPath}`);
|
|
24
|
+
dirs++;
|
|
25
|
+
}
|
|
26
|
+
node.children?.forEach((c) => walk(c, dirPath));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
let file = node.name;
|
|
30
|
+
if (!fs.existsSync(file) && defaultExt && path.extname(file).length <= 1)
|
|
31
|
+
file += defaultExt;
|
|
32
|
+
const filePath = path.join(current, file);
|
|
33
|
+
if (fs.existsSync(filePath)) return warn(`exists: ${filePath}`);
|
|
34
|
+
fs.writeFileSync(filePath, "");
|
|
35
|
+
log(`created: ${filePath}`);
|
|
36
|
+
files++;
|
|
37
|
+
}
|
|
38
|
+
walk(tree, base);
|
|
39
|
+
success(`
|
|
40
|
+
\u2714 Created ${files} files and ${dirs} folders
|
|
41
|
+
`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/constants/symbols.ts
|
|
45
|
+
var OPEN_SYMBOLS = {
|
|
46
|
+
"{": "{",
|
|
47
|
+
"(": "(",
|
|
48
|
+
"[": "["
|
|
49
|
+
};
|
|
50
|
+
var CLOSE_SYMBOLS = {
|
|
51
|
+
"}": "}",
|
|
52
|
+
")": ")",
|
|
53
|
+
"]": "]"
|
|
54
|
+
};
|
|
55
|
+
var SEPARATOR_SYMBOLS = { ",": "," };
|
|
56
|
+
var FOLDER_INDICATOR_SYMBOLS = { "/": "/", "\\": "\\" };
|
|
57
|
+
var OPEN_CLOSE_PAIRS = {
|
|
58
|
+
"(": ")",
|
|
59
|
+
"{": "}",
|
|
60
|
+
"[": "]"
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// src/parser.ts
|
|
64
|
+
function parse(input) {
|
|
65
|
+
let i = 0;
|
|
66
|
+
function walk() {
|
|
67
|
+
let name = "";
|
|
68
|
+
let char = input[i];
|
|
69
|
+
while (i < input.length && !Object.hasOwn(
|
|
70
|
+
Object.assign(
|
|
71
|
+
{},
|
|
72
|
+
OPEN_SYMBOLS,
|
|
73
|
+
CLOSE_SYMBOLS,
|
|
74
|
+
SEPARATOR_SYMBOLS,
|
|
75
|
+
FOLDER_INDICATOR_SYMBOLS
|
|
76
|
+
),
|
|
77
|
+
char
|
|
78
|
+
)) {
|
|
79
|
+
name += char;
|
|
80
|
+
char = input[++i];
|
|
81
|
+
}
|
|
82
|
+
name = name.trim();
|
|
83
|
+
if (Object.hasOwn(FOLDER_INDICATOR_SYMBOLS, char ?? "")) {
|
|
84
|
+
i++;
|
|
85
|
+
char = input[i];
|
|
86
|
+
if (!Object.hasOwn(OPEN_SYMBOLS, char ?? "")) {
|
|
87
|
+
const children = [];
|
|
88
|
+
if (i < input.length && !Object.hasOwn(SEPARATOR_SYMBOLS, input[i]) && !Object.hasOwn(CLOSE_SYMBOLS, input[i]))
|
|
89
|
+
children.push(walk());
|
|
90
|
+
return { type: "dir", name, children };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (Object.hasOwn(OPEN_SYMBOLS, char ?? "")) {
|
|
94
|
+
const opener = input[i++];
|
|
95
|
+
const closer = OPEN_CLOSE_PAIRS[OPEN_SYMBOLS[opener]];
|
|
96
|
+
const children = [];
|
|
97
|
+
char = input[i];
|
|
98
|
+
while (i < input.length && char !== closer) {
|
|
99
|
+
children.push(walk());
|
|
100
|
+
if (input[i] === ",") i++;
|
|
101
|
+
char = input[i];
|
|
102
|
+
}
|
|
103
|
+
i++;
|
|
104
|
+
return { type: "dir", name, children };
|
|
105
|
+
}
|
|
106
|
+
return { type: "file", name };
|
|
107
|
+
}
|
|
108
|
+
return walk();
|
|
109
|
+
}
|
|
110
|
+
function getExtension(input) {
|
|
111
|
+
let s = [];
|
|
112
|
+
let i = 0;
|
|
113
|
+
const closes = Object.values(CLOSE_SYMBOLS);
|
|
114
|
+
while (s.length <= 1 && i < closes.length) {
|
|
115
|
+
s = input.split(closes[i]);
|
|
116
|
+
i++;
|
|
117
|
+
}
|
|
118
|
+
const ext = s.at(-1);
|
|
119
|
+
return ext ? `${ext}` : "";
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
build,
|
|
123
|
+
getExtension,
|
|
124
|
+
parse
|
|
125
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"name": "@cmorales_/touch",
|
|
4
|
+
"module": "/dist/index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": false,
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsup src/index.ts --format esm --dts",
|
|
9
|
+
"test": "vitest"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"touch": "dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"package.json",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@biomejs/biome": "^2.3.13",
|
|
22
|
+
"@types/bun": "latest",
|
|
23
|
+
"tsup": "^8.5.1",
|
|
24
|
+
"vitest": "^4.0.18"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"typescript": "^5"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"picocolors": "^1.1.1"
|
|
31
|
+
}
|
|
32
|
+
}
|