touch-all 1.1.10 → 1.2.1
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 +38 -23
- package/dist/lib/index.js +14 -68
- package/dist/lib/{index.d.ts → src/index.d.ts} +0 -1
- package/dist/slim/touch-all.js +107 -40
- package/package.json +12 -11
- package/dist/bundled/touch-all.js +0 -41483
- /package/dist/lib/{_commonErrors.d.ts → src/_commonErrors.d.ts} +0 -0
- /package/dist/lib/{_commonTypes.d.ts → src/_commonTypes.d.ts} +0 -0
- /package/dist/lib/{cli.d.ts → src/cli.d.ts} +0 -0
- /package/dist/lib/{fsGenerator.d.ts → src/fsGenerator.d.ts} +0 -0
- /package/dist/lib/{fsNormalizator.d.ts → src/fsNormalizator.d.ts} +0 -0
- /package/dist/lib/{parser.d.ts → src/parser.d.ts} +0 -0
- /package/dist/lib/{touch-all.d.ts → src/touch-all.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# touch-all
|
|
2
2
|
|
|
3
|
-
CLI tool to create folder structures from
|
|
3
|
+
CLI tool to create folder structures from Markdown tree representations.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
It behaves like `mkdir -p` and `touch` combined, creating directories and files as needed. It can be used to quickly scaffold a project structure or generate placeholder files.
|
|
6
8
|
|
|
7
9
|
## Features
|
|
8
10
|
|
|
9
11
|
- Accepts tree strings in **box-drawing** (`├──`, `└──`, `│`) or **indentation** (spaces) format
|
|
10
|
-
- Trailing `/` marks a directory; no trailing `/` marks a file
|
|
12
|
+
- Trailing slash `/` marks a directory; no trailing `/` marks a file
|
|
11
13
|
- Inline comments stripped automatically (`# ...` and `// ...`)
|
|
12
14
|
- `--dry-run` parses and validates without touching the file system
|
|
13
15
|
- `--verbose` prints every created path
|
|
@@ -20,10 +22,10 @@ Pass a tree string — drawn with box-drawing characters or plain indentation
|
|
|
20
22
|
npm install -g touch-all
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
or with `npx` without installing:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
npx touch-all "..."
|
|
28
|
+
npx touch-all@latest "..."
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
## Usage
|
|
@@ -41,37 +43,37 @@ my-project/
|
|
|
41
43
|
"
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
## Arguments
|
|
47
|
+
|
|
48
|
+
- `--path` , `-p` – specifies target directory. By default, the current working directory is used. Can be an absolute path or a path relative to the current working directory.
|
|
45
49
|
|
|
46
50
|
```bash
|
|
47
|
-
touch-all "..." --path
|
|
48
|
-
touch-all "..." -p
|
|
51
|
+
touch-all "..." --path=./my-project
|
|
52
|
+
touch-all "..." -p ~/Documents/my-project
|
|
49
53
|
```
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
- `--dry-run` , `-n` – parses and validates the tree string without creating any files or directories. Useful for testing and debugging.
|
|
52
56
|
|
|
53
57
|
```bash
|
|
54
58
|
touch-all "..." --dry-run
|
|
55
59
|
touch-all "..." -n
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
- `--verbose` , `-v` – prints every created path to the console. Useful for seeing exactly what will be created, especially with complex structures. It's an alias for `--log-level info`
|
|
59
63
|
|
|
60
64
|
```bash
|
|
61
65
|
touch-all "..." --verbose
|
|
62
66
|
touch-all "..." -v
|
|
63
67
|
```
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
- `--completions` – generates a completion script for a specific shell. Supported shells: `sh`, `bash`, `fish`, `zsh`.
|
|
70
|
+
- `--log-level` – sets the minimum log level for a command. Supported levels: `all`, `trace`, `debug`, `info`, `warning`, `error`, `fatal`, `none`. The default log level is `warning`.
|
|
71
|
+
- `--help` , `-h` – shows the help documentation for a command.
|
|
72
|
+
- `--wizard` – starts wizard mode for a command, providing an interactive step-by-step interface.
|
|
73
|
+
- `--version` – shows the version of the application.
|
|
70
74
|
|
|
71
75
|
## Tree Format
|
|
72
76
|
|
|
73
|
-
Both formats produce identical results.
|
|
74
|
-
|
|
75
77
|
### Box-drawing characters
|
|
76
78
|
|
|
77
79
|
```
|
|
@@ -100,6 +102,8 @@ my-project/
|
|
|
100
102
|
README.md
|
|
101
103
|
```
|
|
102
104
|
|
|
105
|
+
Both formats produce identical results.
|
|
106
|
+
|
|
103
107
|
### Rules
|
|
104
108
|
|
|
105
109
|
| Syntax | Meaning |
|
|
@@ -108,8 +112,8 @@ my-project/
|
|
|
108
112
|
| `name` | file |
|
|
109
113
|
| `dir/sub/` | directory at an explicit subpath |
|
|
110
114
|
| `dir/sub/file.ts` | file at an explicit subpath |
|
|
111
|
-
|
|
|
112
|
-
|
|
|
115
|
+
| `... # comment` | ignored (stripped) |
|
|
116
|
+
| `... // comment` | ignored (stripped) |
|
|
113
117
|
|
|
114
118
|
Items at the root level (no indentation / no parent) are created directly inside the target directory.
|
|
115
119
|
|
|
@@ -126,21 +130,28 @@ import {
|
|
|
126
130
|
resolveProjectPathToBase,
|
|
127
131
|
PathTraversalError,
|
|
128
132
|
FsError,
|
|
129
|
-
cli,
|
|
130
133
|
} from 'touch-all'
|
|
131
134
|
import type { ParserResult, ParserResultLineItem } from 'touch-all'
|
|
132
135
|
```
|
|
133
136
|
|
|
134
137
|
### `parserFolderStructure(tree: string): ParserResult`
|
|
135
138
|
|
|
136
|
-
Parses a tree string into a flat list of `{ path, isFile }` items. Pure function, no I/O.
|
|
139
|
+
Parses a tree string into a flat list of `type ParserResult = { path: string, isFile: boolean }` items. Pure function, no I/O.
|
|
137
140
|
|
|
138
141
|
```ts
|
|
139
142
|
const items = parserFolderStructure(`
|
|
140
143
|
src/
|
|
141
144
|
index.ts
|
|
142
145
|
`)
|
|
143
|
-
// [
|
|
146
|
+
// [
|
|
147
|
+
// {
|
|
148
|
+
// path: 'src',
|
|
149
|
+
// isFile: false
|
|
150
|
+
// }, {
|
|
151
|
+
// path: 'src/index.ts',
|
|
152
|
+
// isFile: true
|
|
153
|
+
// }
|
|
154
|
+
// ]
|
|
144
155
|
```
|
|
145
156
|
|
|
146
157
|
### `fileStructureCreator(items: ParserResult, basePath: string): Effect<void, FsError | PathTraversalError>`
|
|
@@ -151,15 +162,19 @@ Creates the parsed structure on disk under `basePath`. Returns an [Effect](https
|
|
|
151
162
|
import { Effect } from 'effect'
|
|
152
163
|
import { NodeContext, NodeRuntime } from '@effect/platform-node'
|
|
153
164
|
|
|
165
|
+
const projectDirectory = '/absolute/target/path'
|
|
154
166
|
const items = parserFolderStructure(tree)
|
|
155
167
|
|
|
156
|
-
fileStructureCreator(items,
|
|
168
|
+
fileStructureCreator(items, projectDirectory).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
|
|
157
169
|
```
|
|
158
170
|
|
|
159
171
|
### `resolveProjectPathToBase(projectPath: string, basePath: string): Effect<string, PathTraversalError>`
|
|
160
172
|
|
|
161
173
|
Resolves `projectPath` relative to `basePath` and rejects any path that would escape `basePath` (path traversal protection).
|
|
162
174
|
|
|
175
|
+
> [!CAUTION]
|
|
176
|
+
> `projectPath` cannot traverse outside of `basePath`. If `projectPath` is absolute, it treated as relative to `basePath`. If `projectPath` is relative, it is resolved against `basePath`. In either case, if the resulting path is outside of `basePath`, a `PathTraversalError` is thrown.
|
|
177
|
+
|
|
163
178
|
### Error types
|
|
164
179
|
|
|
165
180
|
| Class | `_tag` | When thrown |
|
package/dist/lib/index.js
CHANGED
|
@@ -42,31 +42,31 @@ var resolveProjectPathToBase = (projectPath, basePath) => {
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
// src/fsGenerator.ts
|
|
45
|
-
var fileStructureCreator = (items, basePath) => Effect2.gen(function* (
|
|
46
|
-
yield*
|
|
45
|
+
var fileStructureCreator = (items, basePath) => Effect2.gen(function* () {
|
|
46
|
+
yield* Effect2.logInfo(`Creating structure in: ${basePath}`);
|
|
47
47
|
for (const item of items) {
|
|
48
|
-
const fullPath = yield*
|
|
48
|
+
const fullPath = yield* resolveProjectPathToBase(item.path, basePath);
|
|
49
49
|
if (item.isFile) {
|
|
50
50
|
const dir = path.dirname(fullPath);
|
|
51
|
-
yield*
|
|
51
|
+
yield* Effect2.try({
|
|
52
52
|
try: () => fs.mkdirSync(dir, { recursive: true }),
|
|
53
53
|
catch: (error) => new FsError(`Failed to create directory ${dir}: ${String(error)}`)
|
|
54
|
-
})
|
|
55
|
-
yield*
|
|
54
|
+
});
|
|
55
|
+
yield* Effect2.try({
|
|
56
56
|
try: () => fs.writeFileSync(fullPath, ""),
|
|
57
57
|
catch: (error) => new FsError(`Failed to create file ${fullPath}: ${String(error)}`)
|
|
58
|
-
})
|
|
59
|
-
yield*
|
|
58
|
+
});
|
|
59
|
+
yield* Effect2.logInfo(` Created file: ${item.path}`);
|
|
60
60
|
} else {
|
|
61
|
-
yield*
|
|
61
|
+
yield* Effect2.try({
|
|
62
62
|
try: () => fs.mkdirSync(fullPath, { recursive: true }),
|
|
63
63
|
catch: (error) => new FsError(`Failed to create directory ${fullPath}: ${String(error)}`)
|
|
64
|
-
})
|
|
65
|
-
yield*
|
|
64
|
+
});
|
|
65
|
+
yield* Effect2.logInfo(` Created directory: ${item.path}`);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
yield*
|
|
69
|
-
✓ Structure created successfully!`)
|
|
68
|
+
yield* Effect2.logInfo(`
|
|
69
|
+
✓ Structure created successfully!`);
|
|
70
70
|
});
|
|
71
71
|
// src/parser.ts
|
|
72
72
|
var parserFolderStructure = (treeString) => {
|
|
@@ -80,13 +80,11 @@ var parserFolderStructure = (treeString) => {
|
|
|
80
80
|
const [p01 = "", _comment01] = line.split("#");
|
|
81
81
|
const [p02 = "", _comment02] = p01.split("//");
|
|
82
82
|
const p03 = p02.replace(/[│├└─\t]/g, " ");
|
|
83
|
-
const cleanLine = p03.trim();
|
|
83
|
+
const cleanLine = p03.trim().replace(/^\.\//, "");
|
|
84
84
|
if (!cleanLine)
|
|
85
85
|
continue;
|
|
86
86
|
if (cleanLine === "/")
|
|
87
87
|
continue;
|
|
88
|
-
if (cleanLine.startsWith("./"))
|
|
89
|
-
continue;
|
|
90
88
|
if (cleanLine.endsWith("../"))
|
|
91
89
|
continue;
|
|
92
90
|
const indent = countLeadingSpaces(p03);
|
|
@@ -114,62 +112,10 @@ function countLeadingSpaces(str) {
|
|
|
114
112
|
const match = str.match(/^[\s]*/);
|
|
115
113
|
return match ? match[0].length : 0;
|
|
116
114
|
}
|
|
117
|
-
// src/cli.ts
|
|
118
|
-
import { Args, Command, Options } from "@effect/cli";
|
|
119
|
-
import { Console, Effect as Effect3, Logger, LogLevel, Option } from "effect";
|
|
120
|
-
import { createInterface } from "node:readline";
|
|
121
|
-
var treeArg = Args.text({ name: "tree" }).pipe(Args.withDescription("Multiline string representing the directory tree structure"), Args.optional);
|
|
122
|
-
var pathOption = Options.directory("path").pipe(Options.withAlias("p"), Options.withDefault("."), Options.withDescription("Target folder path (defaults to current directory)"));
|
|
123
|
-
var dryRunOption = Options.boolean("dry-run").pipe(Options.withAlias("n"), Options.withDefault(false), Options.withDescription("Skip the top-level directory if there is only one"));
|
|
124
|
-
var verboseOption = Options.boolean("verbose").pipe(Options.withAlias("v"), Options.withDefault(false), Options.withDescription("Log to console extra information about creating a directory tree"));
|
|
125
|
-
var command = Command.make("touch-all", {
|
|
126
|
-
tree: treeArg,
|
|
127
|
-
path: pathOption,
|
|
128
|
-
dryRun: dryRunOption,
|
|
129
|
-
verbose: verboseOption
|
|
130
|
-
}).pipe(Command.withDescription("Create directory structure from a tree representation"), Command.withHandler(({ tree, path: targetPath, dryRun = false, verbose }) => {
|
|
131
|
-
const readStdin = Effect3.tryPromise({
|
|
132
|
-
try: () => new Promise((resolve2) => {
|
|
133
|
-
const rl = createInterface({ input: process.stdin });
|
|
134
|
-
const lines = [];
|
|
135
|
-
rl.on("line", (line) => lines.push(line));
|
|
136
|
-
rl.on("close", () => resolve2(lines.join(`
|
|
137
|
-
`)));
|
|
138
|
-
}),
|
|
139
|
-
catch: (e) => new Error(`Failed to read stdin: ${String(e)}`)
|
|
140
|
-
});
|
|
141
|
-
const program = Effect3.gen(function* (_) {
|
|
142
|
-
const treeString = Option.isSome(tree) ? tree.value : yield* _(readStdin);
|
|
143
|
-
if (dryRun) {
|
|
144
|
-
yield* _(Effect3.logInfo("Running in dry mode. No one file system node will be created."));
|
|
145
|
-
}
|
|
146
|
-
yield* _(Effect3.logInfo("Parsing tree structure..."));
|
|
147
|
-
const items = parserFolderStructure(treeString);
|
|
148
|
-
if (items.length === 0) {
|
|
149
|
-
yield* _(Console.error("No valid items found in the tree structure"));
|
|
150
|
-
yield* _(Console.error(items));
|
|
151
|
-
yield* _(Console.error(treeString));
|
|
152
|
-
return yield* _(Effect3.fail(new Error("Invalid tree structure")));
|
|
153
|
-
}
|
|
154
|
-
yield* _(Effect3.logInfo(`Found ${items.length} items to create`));
|
|
155
|
-
yield* _(Effect3.logInfo(`Found:
|
|
156
|
-
${items.map((i) => `${i.path}
|
|
157
|
-
`).join("")}`));
|
|
158
|
-
if (!dryRun) {
|
|
159
|
-
yield* _(fileStructureCreator(items, targetPath));
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
return verbose ? program.pipe(Logger.withMinimumLogLevel(LogLevel.Info)) : program;
|
|
163
|
-
}));
|
|
164
|
-
var cli = Command.run(command, {
|
|
165
|
-
name: "Touch All",
|
|
166
|
-
version: "0.0.1"
|
|
167
|
-
});
|
|
168
115
|
export {
|
|
169
116
|
resolveProjectPathToBase,
|
|
170
117
|
parserFolderStructure,
|
|
171
118
|
fileStructureCreator,
|
|
172
|
-
cli,
|
|
173
119
|
PathTraversalError,
|
|
174
120
|
FsError
|
|
175
121
|
};
|
|
@@ -3,4 +3,3 @@ export { resolveProjectPathToBase } from './fsNormalizator';
|
|
|
3
3
|
export { PathTraversalError, FsError } from './_commonErrors';
|
|
4
4
|
export type { ParserResult, ParserResultLineItem } from './_commonTypes';
|
|
5
5
|
export { parserFolderStructure } from './parser';
|
|
6
|
-
export { cli } from './cli';
|
package/dist/slim/touch-all.js
CHANGED
|
@@ -4,9 +4,9 @@ import { Effect as Effect4, Logger as Logger2, LogLevel as LogLevel2 } from "eff
|
|
|
4
4
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
|
+
import { createInterface } from "node:readline";
|
|
7
8
|
import { Args, Command, Options } from "@effect/cli";
|
|
8
9
|
import { Console, Effect as Effect3, Logger, LogLevel, Option } from "effect";
|
|
9
|
-
import { createInterface } from "node:readline";
|
|
10
10
|
|
|
11
11
|
// src/parser.ts
|
|
12
12
|
var parserFolderStructure = (treeString) => {
|
|
@@ -20,13 +20,11 @@ var parserFolderStructure = (treeString) => {
|
|
|
20
20
|
const [p01 = "", _comment01] = line.split("#");
|
|
21
21
|
const [p02 = "", _comment02] = p01.split("//");
|
|
22
22
|
const p03 = p02.replace(/[│├└─\t]/g, " ");
|
|
23
|
-
const cleanLine = p03.trim();
|
|
23
|
+
const cleanLine = p03.trim().replace(/^\.\//, "");
|
|
24
24
|
if (!cleanLine)
|
|
25
25
|
continue;
|
|
26
26
|
if (cleanLine === "/")
|
|
27
27
|
continue;
|
|
28
|
-
if (cleanLine.startsWith("./"))
|
|
29
|
-
continue;
|
|
30
28
|
if (cleanLine.endsWith("../"))
|
|
31
29
|
continue;
|
|
32
30
|
const indent = countLeadingSpaces(p03);
|
|
@@ -99,37 +97,103 @@ var resolveProjectPathToBase = (projectPath, basePath) => {
|
|
|
99
97
|
};
|
|
100
98
|
|
|
101
99
|
// src/fsGenerator.ts
|
|
102
|
-
var fileStructureCreator = (items, basePath) => Effect2.gen(function* (
|
|
103
|
-
yield*
|
|
100
|
+
var fileStructureCreator = (items, basePath) => Effect2.gen(function* () {
|
|
101
|
+
yield* Effect2.logInfo(`Creating structure in: ${basePath}`);
|
|
104
102
|
for (const item of items) {
|
|
105
|
-
const fullPath = yield*
|
|
103
|
+
const fullPath = yield* resolveProjectPathToBase(item.path, basePath);
|
|
106
104
|
if (item.isFile) {
|
|
107
105
|
const dir = path.dirname(fullPath);
|
|
108
|
-
yield*
|
|
106
|
+
yield* Effect2.try({
|
|
109
107
|
try: () => fs.mkdirSync(dir, { recursive: true }),
|
|
110
108
|
catch: (error) => new FsError(`Failed to create directory ${dir}: ${String(error)}`)
|
|
111
|
-
})
|
|
112
|
-
yield*
|
|
109
|
+
});
|
|
110
|
+
yield* Effect2.try({
|
|
113
111
|
try: () => fs.writeFileSync(fullPath, ""),
|
|
114
112
|
catch: (error) => new FsError(`Failed to create file ${fullPath}: ${String(error)}`)
|
|
115
|
-
})
|
|
116
|
-
yield*
|
|
113
|
+
});
|
|
114
|
+
yield* Effect2.logInfo(` Created file: ${item.path}`);
|
|
117
115
|
} else {
|
|
118
|
-
yield*
|
|
116
|
+
yield* Effect2.try({
|
|
119
117
|
try: () => fs.mkdirSync(fullPath, { recursive: true }),
|
|
120
118
|
catch: (error) => new FsError(`Failed to create directory ${fullPath}: ${String(error)}`)
|
|
121
|
-
})
|
|
122
|
-
yield*
|
|
119
|
+
});
|
|
120
|
+
yield* Effect2.logInfo(` Created directory: ${item.path}`);
|
|
123
121
|
}
|
|
124
122
|
}
|
|
125
|
-
yield*
|
|
126
|
-
✓ Structure created successfully!`)
|
|
123
|
+
yield* Effect2.logInfo(`
|
|
124
|
+
✓ Structure created successfully!`);
|
|
127
125
|
});
|
|
126
|
+
// package.json
|
|
127
|
+
var package_default = {
|
|
128
|
+
name: "touch-all",
|
|
129
|
+
version: "1.2.1",
|
|
130
|
+
description: "CLI tool to create folder structures from markdown tree representations",
|
|
131
|
+
keywords: [
|
|
132
|
+
"cli",
|
|
133
|
+
"folders",
|
|
134
|
+
"generator",
|
|
135
|
+
"markdown",
|
|
136
|
+
"structure"
|
|
137
|
+
],
|
|
138
|
+
license: "GPL-3.0-or-later",
|
|
139
|
+
author: "Anton Huz <anton@ahuz.dev>",
|
|
140
|
+
repository: {
|
|
141
|
+
type: "git",
|
|
142
|
+
url: "https://github.com/anton-huz/touch-all"
|
|
143
|
+
},
|
|
144
|
+
bin: {
|
|
145
|
+
"touch-all": "dist/slim/touch-all.js"
|
|
146
|
+
},
|
|
147
|
+
files: [
|
|
148
|
+
"dist/slim",
|
|
149
|
+
"dist/lib"
|
|
150
|
+
],
|
|
151
|
+
type: "module",
|
|
152
|
+
main: "dist/lib/index.js",
|
|
153
|
+
types: "dist/lib/index.d.ts",
|
|
154
|
+
exports: {
|
|
155
|
+
".": {
|
|
156
|
+
types: "./dist/lib/index.d.ts",
|
|
157
|
+
import: "./dist/lib/index.js"
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
publishConfig: {
|
|
161
|
+
access: "public",
|
|
162
|
+
provenance: true
|
|
163
|
+
},
|
|
164
|
+
scripts: {
|
|
165
|
+
build: "tsup --config .config/tsup.config.ts",
|
|
166
|
+
"check:lint": "oxlint . -c .config/.oxlintrc.json",
|
|
167
|
+
"check:format": "oxfmt . -c .config/.oxfmtrc.json --check",
|
|
168
|
+
"check:ts": "tsc --noEmit",
|
|
169
|
+
format: "oxfmt . -c .config/.oxfmtrc.json --write",
|
|
170
|
+
test: "vitest --config .config/vitest.config.ts run"
|
|
171
|
+
},
|
|
172
|
+
dependencies: {
|
|
173
|
+
"@effect/cli": "^0.75.0",
|
|
174
|
+
"@effect/platform-node": "^0.106.0",
|
|
175
|
+
effect: "^3.21.0"
|
|
176
|
+
},
|
|
177
|
+
devDependencies: {
|
|
178
|
+
"@total-typescript/tsconfig": "1.0.3",
|
|
179
|
+
"@types/bun": "1.3.11",
|
|
180
|
+
"@types/node": "25.5.0",
|
|
181
|
+
oxfmt: "0.41.0",
|
|
182
|
+
oxlint: "1.56.0",
|
|
183
|
+
tsup: "8.5.1",
|
|
184
|
+
typescript: "5.9.3",
|
|
185
|
+
vitest: "4.1.0"
|
|
186
|
+
},
|
|
187
|
+
engines: {
|
|
188
|
+
node: ">=18"
|
|
189
|
+
},
|
|
190
|
+
url: "https://github.com/anton-huz/touch-all"
|
|
191
|
+
};
|
|
128
192
|
|
|
129
193
|
// src/cli.ts
|
|
130
194
|
var treeArg = Args.text({ name: "tree" }).pipe(Args.withDescription("Multiline string representing the directory tree structure"), Args.optional);
|
|
131
195
|
var pathOption = Options.directory("path").pipe(Options.withAlias("p"), Options.withDefault("."), Options.withDescription("Target folder path (defaults to current directory)"));
|
|
132
|
-
var dryRunOption = Options.boolean("dry-run").pipe(Options.withAlias("n"), Options.withDefault(false), Options.withDescription("
|
|
196
|
+
var dryRunOption = Options.boolean("dry-run").pipe(Options.withAlias("n"), Options.withDefault(false), Options.withDescription("Parse and validate the tree without writing to the file system"));
|
|
133
197
|
var verboseOption = Options.boolean("verbose").pipe(Options.withAlias("v"), Options.withDefault(false), Options.withDescription("Log to console extra information about creating a directory tree"));
|
|
134
198
|
var command = Command.make("touch-all", {
|
|
135
199
|
tree: treeArg,
|
|
@@ -137,42 +201,45 @@ var command = Command.make("touch-all", {
|
|
|
137
201
|
dryRun: dryRunOption,
|
|
138
202
|
verbose: verboseOption
|
|
139
203
|
}).pipe(Command.withDescription("Create directory structure from a tree representation"), Command.withHandler(({ tree, path: targetPath, dryRun = false, verbose }) => {
|
|
140
|
-
const readStdin = Effect3.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
204
|
+
const readStdin = Effect3.gen(function* () {
|
|
205
|
+
if (process.stdin.isTTY) {
|
|
206
|
+
yield* Console.log("Paste your tree structure and press Ctrl+D when done:");
|
|
207
|
+
}
|
|
208
|
+
return yield* Effect3.tryPromise({
|
|
209
|
+
try: () => new Promise((resolve2) => {
|
|
210
|
+
const rl = createInterface({ input: process.stdin });
|
|
211
|
+
const lines = [];
|
|
212
|
+
rl.on("line", (line) => lines.push(line));
|
|
213
|
+
rl.on("close", () => resolve2(lines.join(`
|
|
146
214
|
`)));
|
|
147
|
-
|
|
148
|
-
|
|
215
|
+
}),
|
|
216
|
+
catch: (e) => new Error(`Failed to read stdin: ${String(e)}`)
|
|
217
|
+
});
|
|
149
218
|
});
|
|
150
|
-
const program = Effect3.gen(function* (
|
|
151
|
-
const treeString = Option.isSome(tree) ? tree.value : yield*
|
|
219
|
+
const program = Effect3.gen(function* () {
|
|
220
|
+
const treeString = Option.isSome(tree) ? tree.value : yield* readStdin;
|
|
152
221
|
if (dryRun) {
|
|
153
|
-
yield*
|
|
222
|
+
yield* Effect3.logInfo("Running in dry mode. No one file system node will be created.");
|
|
154
223
|
}
|
|
155
|
-
yield*
|
|
224
|
+
yield* Effect3.logInfo("Parsing tree structure...");
|
|
156
225
|
const items = parserFolderStructure(treeString);
|
|
157
226
|
if (items.length === 0) {
|
|
158
|
-
yield*
|
|
159
|
-
yield*
|
|
160
|
-
yield* _(Console.error(treeString));
|
|
161
|
-
return yield* _(Effect3.fail(new Error("Invalid tree structure")));
|
|
227
|
+
yield* Console.error("No valid items found in the tree structure");
|
|
228
|
+
return yield* Effect3.fail(new Error("Invalid tree structure"));
|
|
162
229
|
}
|
|
163
|
-
yield*
|
|
164
|
-
yield*
|
|
230
|
+
yield* Effect3.logInfo(`Found ${items.length} items to create`);
|
|
231
|
+
yield* Effect3.logInfo(`Found:
|
|
165
232
|
${items.map((i) => `${i.path}
|
|
166
|
-
`).join("")}`)
|
|
233
|
+
`).join("")}`);
|
|
167
234
|
if (!dryRun) {
|
|
168
|
-
yield*
|
|
235
|
+
yield* fileStructureCreator(items, targetPath);
|
|
169
236
|
}
|
|
170
237
|
});
|
|
171
238
|
return verbose ? program.pipe(Logger.withMinimumLogLevel(LogLevel.Info)) : program;
|
|
172
239
|
}));
|
|
173
240
|
var cli = Command.run(command, {
|
|
174
|
-
name:
|
|
175
|
-
version:
|
|
241
|
+
name: package_default.name,
|
|
242
|
+
version: package_default.version
|
|
176
243
|
});
|
|
177
244
|
|
|
178
245
|
// src/touch-all.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "touch-all",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "CLI tool to create folder structures from markdown tree representations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"touch-all": "dist/slim/touch-all.js"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
|
-
"dist/
|
|
22
|
+
"dist/slim",
|
|
23
23
|
"dist/lib"
|
|
24
24
|
],
|
|
25
25
|
"type": "module",
|
|
@@ -39,23 +39,24 @@
|
|
|
39
39
|
"build": "tsup --config .config/tsup.config.ts",
|
|
40
40
|
"check:lint": "oxlint . -c .config/.oxlintrc.json",
|
|
41
41
|
"check:format": "oxfmt . -c .config/.oxfmtrc.json --check",
|
|
42
|
+
"check:ts": "tsc --noEmit",
|
|
42
43
|
"format": "oxfmt . -c .config/.oxfmtrc.json --write",
|
|
43
44
|
"test": "vitest --config .config/vitest.config.ts run"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
|
-
"@effect/cli": "^0.
|
|
47
|
-
"@effect/platform-node": "^0.
|
|
48
|
-
"effect": "^3.
|
|
47
|
+
"@effect/cli": "^0.75.0",
|
|
48
|
+
"@effect/platform-node": "^0.106.0",
|
|
49
|
+
"effect": "^3.21.0"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
|
-
"@total-typescript/tsconfig": "1.0.
|
|
52
|
-
"@types/bun": "1.3.
|
|
53
|
-
"@types/node": "25.
|
|
54
|
-
"oxfmt": "0.
|
|
55
|
-
"oxlint": "1.
|
|
52
|
+
"@total-typescript/tsconfig": "1.0.3",
|
|
53
|
+
"@types/bun": "1.3.11",
|
|
54
|
+
"@types/node": "25.5.0",
|
|
55
|
+
"oxfmt": "0.41.0",
|
|
56
|
+
"oxlint": "1.56.0",
|
|
56
57
|
"tsup": "8.5.1",
|
|
57
58
|
"typescript": "5.9.3",
|
|
58
|
-
"vitest": "4.0
|
|
59
|
+
"vitest": "4.1.0"
|
|
59
60
|
},
|
|
60
61
|
"engines": {
|
|
61
62
|
"node": ">=18"
|