touch-all 1.2.1 → 2.1.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 +84 -21
- package/dist/lib/cli.d.ts +2 -0
- package/dist/lib/{src/_commonErrors.d.ts → errors.d.ts} +0 -9
- package/dist/lib/fsGenerator.d.ts +7 -0
- package/dist/lib/fsNormalizator.d.ts +20 -0
- package/dist/lib/index.d.ts +5 -0
- package/dist/lib/index.js +836 -61
- package/dist/lib/{src/parser.d.ts → parser.d.ts} +1 -1
- package/dist/lib/stdin.d.ts +2 -0
- package/dist/lib/symlinkGuard.d.ts +4 -0
- package/dist/lib/types.d.ts +12 -0
- package/dist/slim/touch-all.js +937 -117
- package/package.json +1 -2
- package/dist/lib/src/_commonTypes.d.ts +0 -5
- package/dist/lib/src/cli.d.ts +0 -2
- package/dist/lib/src/fsGenerator.d.ts +0 -4
- package/dist/lib/src/fsNormalizator.d.ts +0 -10
- package/dist/lib/src/index.d.ts +0 -5
- /package/dist/lib/{src/touch-all.d.ts → touch-all.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -10,11 +10,13 @@ It behaves like `mkdir -p` and `touch` combined, creating directories and files
|
|
|
10
10
|
|
|
11
11
|
- Accepts tree strings in **box-drawing** (`├──`, `└──`, `│`) or **indentation** (spaces) format
|
|
12
12
|
- Trailing slash `/` marks a directory; no trailing `/` marks a file
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- `--
|
|
16
|
-
-
|
|
13
|
+
- Symlink creation with `link-name -> target` syntax
|
|
14
|
+
- Inline comments stripped automatically (`# ...`, `// ...`, `<- ...`, `← ...`)
|
|
15
|
+
- `--dry-run` (`-n`) parses and validates without touching the file system
|
|
16
|
+
- Prints a summary line on success (`✓ Done. N items created.`); `--verbose` adds per-item detail
|
|
17
|
+
- Path traversal protection — no file, folder, or symlink target can escape the target directory
|
|
17
18
|
- Importable as a Node.js library with full TypeScript types
|
|
19
|
+
- Pipable interface to run as `echo tree | touch-all` in CI or scripts
|
|
18
20
|
|
|
19
21
|
## Installation
|
|
20
22
|
|
|
@@ -59,13 +61,20 @@ touch-all "..." --dry-run
|
|
|
59
61
|
touch-all "..." -n
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
- `--verbose` , `-v` – prints every created path to the console. Useful for seeing exactly what will be created, especially with complex structures.
|
|
64
|
+
- `--verbose` , `-v` – prints every created path to the console. Useful for seeing exactly what will be created, especially with complex structures. By default only a summary line is printed on success (`✓ Done. N items created.`). `--verbose` adds per-item detail.
|
|
63
65
|
|
|
64
66
|
```bash
|
|
65
67
|
touch-all "..." --verbose
|
|
66
68
|
touch-all "..." -v
|
|
67
69
|
```
|
|
68
70
|
|
|
71
|
+
- `--yes` , `-y` – skips the confirmation prompt when symlinks point outside the project root. Required in non-interactive environments (scripts, CI).
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
touch-all "..." --yes
|
|
75
|
+
touch-all "..." -y
|
|
76
|
+
```
|
|
77
|
+
|
|
69
78
|
- `--completions` – generates a completion script for a specific shell. Supported shells: `sh`, `bash`, `fish`, `zsh`.
|
|
70
79
|
- `--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
80
|
- `--help` , `-h` – shows the help documentation for a command.
|
|
@@ -112,11 +121,34 @@ Both formats produce identical results.
|
|
|
112
121
|
| `name` | file |
|
|
113
122
|
| `dir/sub/` | directory at an explicit subpath |
|
|
114
123
|
| `dir/sub/file.ts` | file at an explicit subpath |
|
|
115
|
-
| `... #
|
|
124
|
+
| `... # comment` | ignored (stripped) |
|
|
116
125
|
| `... // comment` | ignored (stripped) |
|
|
126
|
+
| `... <- comment` | ignored (stripped) |
|
|
127
|
+
| `... ← comment` | ignored (stripped) |
|
|
128
|
+
| `name -> target` | symlink pointing to `target` |
|
|
117
129
|
|
|
118
130
|
Items at the root level (no indentation / no parent) are created directly inside the target directory.
|
|
119
131
|
|
|
132
|
+
### Symlinks
|
|
133
|
+
|
|
134
|
+
Use `link-name -> target` to create a symlink. The target is passed as-is to the OS — use paths relative to the symlink's location, just as you would in a shell.
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
my-project/
|
|
138
|
+
├─ src/
|
|
139
|
+
│ ├─ index.ts
|
|
140
|
+
│ └─ utils -> ../shared/utils.ts # symlink to a sibling directory
|
|
141
|
+
└─ shared/
|
|
142
|
+
└─ utils.ts
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
If `target` ends with `/`, the symlink is created as a directory symlink (relevant on Windows). The link name's suffix is ignored.
|
|
146
|
+
|
|
147
|
+
> [!WARNING]
|
|
148
|
+
> If any symlink target resolves outside the project root (`--path`), `touch-all` will prompt for confirmation before proceeding. Use `--yes` to skip the prompt in scripts or CI.
|
|
149
|
+
>
|
|
150
|
+
> When using `fileStructureCreator` directly as a library, outside-root symlinks are rejected by default with a `PathTraversalError`. Pass `{ allowOutsideSymlinks: true }` as the third argument to allow them.
|
|
151
|
+
|
|
120
152
|
## Library API
|
|
121
153
|
|
|
122
154
|
```bash
|
|
@@ -128,36 +160,44 @@ import {
|
|
|
128
160
|
parserFolderStructure,
|
|
129
161
|
fileStructureCreator,
|
|
130
162
|
resolveProjectPathToBase,
|
|
163
|
+
isSymlinkOutsideRoot,
|
|
131
164
|
PathTraversalError,
|
|
132
|
-
FsError,
|
|
133
165
|
} from 'touch-all'
|
|
134
166
|
import type { ParserResult, ParserResultLineItem } from 'touch-all'
|
|
135
167
|
```
|
|
136
168
|
|
|
137
169
|
### `parserFolderStructure(tree: string): ParserResult`
|
|
138
170
|
|
|
139
|
-
Parses a tree string into a flat list of
|
|
171
|
+
Parses a tree string into a flat list of items. Pure function, no I/O.
|
|
172
|
+
|
|
173
|
+
Each item is one of:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
type ParserResultLineItem =
|
|
177
|
+
| { type: 'file'; path: string }
|
|
178
|
+
| { type: 'folder'; path: string }
|
|
179
|
+
| { type: 'symlink'; path: string; target: string }
|
|
180
|
+
```
|
|
140
181
|
|
|
141
182
|
```ts
|
|
142
183
|
const items = parserFolderStructure(`
|
|
143
184
|
src/
|
|
144
185
|
index.ts
|
|
186
|
+
link -> ../shared.ts
|
|
145
187
|
`)
|
|
146
188
|
// [
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// }, {
|
|
151
|
-
// path: 'src/index.ts',
|
|
152
|
-
// isFile: true
|
|
153
|
-
// }
|
|
189
|
+
// { type: 'folder', path: 'src' },
|
|
190
|
+
// { type: 'file', path: 'src/index.ts' },
|
|
191
|
+
// { type: 'symlink', path: 'src/link', target: '../shared.ts' },
|
|
154
192
|
// ]
|
|
155
193
|
```
|
|
156
194
|
|
|
157
|
-
### `fileStructureCreator(items
|
|
195
|
+
### `fileStructureCreator(items, basePath, options?): Effect<void, PathTraversalError>`
|
|
158
196
|
|
|
159
197
|
Creates the parsed structure on disk under `basePath`. Returns an [Effect](https://effect.website/).
|
|
160
198
|
|
|
199
|
+
By default, symlinks whose targets resolve outside `basePath` are rejected with `PathTraversalError`. Pass `{ allowOutsideSymlinks: true }` to allow them.
|
|
200
|
+
|
|
161
201
|
```ts
|
|
162
202
|
import { Effect } from 'effect'
|
|
163
203
|
import { NodeContext, NodeRuntime } from '@effect/platform-node'
|
|
@@ -165,22 +205,45 @@ import { NodeContext, NodeRuntime } from '@effect/platform-node'
|
|
|
165
205
|
const projectDirectory = '/absolute/target/path'
|
|
166
206
|
const items = parserFolderStructure(tree)
|
|
167
207
|
|
|
208
|
+
// Default: outside-root symlinks are rejected
|
|
168
209
|
fileStructureCreator(items, projectDirectory).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
|
|
210
|
+
|
|
211
|
+
// Opt out of symlink containment check
|
|
212
|
+
fileStructureCreator(items, projectDirectory, { allowOutsideSymlinks: true }).pipe(
|
|
213
|
+
Effect.provide(NodeContext.layer),
|
|
214
|
+
NodeRuntime.runMain
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### `isSymlinkOutsideRoot(linkPath, target, basePath, path): boolean`
|
|
219
|
+
|
|
220
|
+
Pure function that returns `true` if a symlink target resolves outside `basePath`. Useful for pre-validating items before passing them to `fileStructureCreator`.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { Path } from '@effect/platform'
|
|
224
|
+
import { Effect } from 'effect'
|
|
225
|
+
import { NodeContext } from '@effect/platform-node'
|
|
226
|
+
import { isSymlinkOutsideRoot } from 'touch-all'
|
|
227
|
+
|
|
228
|
+
Effect.gen(function* () {
|
|
229
|
+
const path = yield* Path.Path
|
|
230
|
+
const outside = isSymlinkOutsideRoot('src/link', '../../etc/passwd', '/project', path)
|
|
231
|
+
// true — target escapes /project
|
|
232
|
+
}).pipe(Effect.provide(NodeContext.layer))
|
|
169
233
|
```
|
|
170
234
|
|
|
171
235
|
### `resolveProjectPathToBase(projectPath: string, basePath: string): Effect<string, PathTraversalError>`
|
|
172
236
|
|
|
173
237
|
Resolves `projectPath` relative to `basePath` and rejects any path that would escape `basePath` (path traversal protection).
|
|
174
238
|
|
|
175
|
-
> [!
|
|
239
|
+
> [!WARNING]
|
|
176
240
|
> `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
241
|
|
|
178
242
|
### Error types
|
|
179
243
|
|
|
180
|
-
| Class | `_tag` | When thrown
|
|
181
|
-
| -------------------- | ---------------------- |
|
|
182
|
-
| `PathTraversalError` | `'PathTraversalError'` |
|
|
183
|
-
| `FsError` | `'FsError'` | `mkdirSync` or `writeFileSync` fails |
|
|
244
|
+
| Class | `_tag` | When thrown |
|
|
245
|
+
| -------------------- | ---------------------- | -------------------------------------------------------------------------- |
|
|
246
|
+
| `PathTraversalError` | `'PathTraversalError'` | A file/folder path or symlink target escapes `basePath` (unless opted out) |
|
|
184
247
|
|
|
185
248
|
## License
|
|
186
249
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
export declare const cli: (args: ReadonlyArray<string>) => Effect.Effect<void, import("./errors").PathTraversalError | import("@effect/platform/Error").PlatformError | Error | import("@effect/cli/ValidationError").ValidationError, import("@effect/cli/CliApp").CliApp.Environment>;
|
|
@@ -7,12 +7,3 @@ export declare class PathTraversalError {
|
|
|
7
7
|
constructor(path: string);
|
|
8
8
|
toString(): string;
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Error type for file system operation failures
|
|
12
|
-
*/
|
|
13
|
-
export declare class FsError {
|
|
14
|
-
readonly message: string;
|
|
15
|
-
readonly _tag = "FsError";
|
|
16
|
-
constructor(message: string);
|
|
17
|
-
toString(): string;
|
|
18
|
-
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FileSystem, Path } from '@effect/platform';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { type ParserResult } from './types';
|
|
4
|
+
import { PathTraversalError } from './errors';
|
|
5
|
+
export declare const fileStructureCreator: (items: ParserResult, basePath: string, options?: {
|
|
6
|
+
allowOutsideSymlinks?: boolean;
|
|
7
|
+
}) => Effect.Effect<void, PathTraversalError | import("@effect/platform/Error").PlatformError, Path.Path | FileSystem.FileSystem>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Path } from '@effect/platform';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { PathTraversalError } from './errors';
|
|
4
|
+
/**
|
|
5
|
+
* Returns true if the symlink target escapes the base directory.
|
|
6
|
+
*
|
|
7
|
+
* @param linkPath string - Path of the symlink relative to project root.
|
|
8
|
+
* @param target string - The symlink target value.
|
|
9
|
+
* @param basePath string - Project root (absolute or relative).
|
|
10
|
+
* @param path Path.Path - Platform path service instance.
|
|
11
|
+
*/
|
|
12
|
+
export declare const isSymlinkOutsideRoot: (linkPath: string, target: string, basePath: string, path: Path.Path) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Safely normalize a user-supplied path against a base directory.
|
|
15
|
+
* Fails with PathTraversalError if the resolved path escapes the base.
|
|
16
|
+
*
|
|
17
|
+
* @param projectPath string - Path relative to project.
|
|
18
|
+
* @param basePath string - Must be absolute.
|
|
19
|
+
*/
|
|
20
|
+
export declare const resolveProjectPathToBase: (projectPath: string, basePath: string) => Effect.Effect<string, PathTraversalError, Path.Path>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { fileStructureCreator } from './fsGenerator';
|
|
2
|
+
export { isSymlinkOutsideRoot, resolveProjectPathToBase } from './fsNormalizator';
|
|
3
|
+
export { PathTraversalError } from './errors';
|
|
4
|
+
export type { ParserResult, ParserResultLineItem } from './types';
|
|
5
|
+
export { parserFolderStructure } from './parser';
|