@bobfrankston/tswalk 1.0.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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(ls:*)",
5
+ "Bash(npm run build:*)"
6
+ ],
7
+ "deny": []
8
+ }
9
+ }
package/.editorconfig ADDED
@@ -0,0 +1,9 @@
1
+ # EditorConfig helps maintain consistent coding styles
2
+ root = true
3
+
4
+ [*]
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ charset = utf-8
8
+ indent_style = space
9
+ indent_size = 2
package/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Normalize line endings in repo
2
+ * text=auto eol=lf
@@ -0,0 +1,32 @@
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "name": "Launch Program",
9
+ "program": "${workspaceFolder}/app.js",
10
+ "request": "launch",
11
+ "skipFiles": [
12
+ "<node_internals>/**"
13
+ ],
14
+ "type": "node"
15
+ },
16
+ {
17
+ "type": "node",
18
+ "request": "launch",
19
+ "name": "u:",
20
+ "program": "${workspaceFolder}/index.js",
21
+ "skipFiles": [
22
+ "<node_internals>/**"
23
+ ],
24
+ "outFiles": [
25
+ "${workspaceFolder}/**/*.js"
26
+ ],
27
+ "args": [
28
+ "u:",
29
+ ]
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "workbench.colorCustomizations": {
3
+ "activityBar.activeBackground": "#f4fd05",
4
+ "activityBar.background": "#f4fd05",
5
+ "activityBar.foreground": "#15202b",
6
+ "activityBar.inactiveForeground": "#15202b99",
7
+ "activityBarBadge.background": "#02b7be",
8
+ "activityBarBadge.foreground": "#15202b",
9
+ "commandCenter.border": "#15202b99",
10
+ "sash.hoverBorder": "#f4fd05",
11
+ "statusBar.background": "#c6cd02",
12
+ "statusBar.foreground": "#15202b",
13
+ "statusBarItem.hoverBackground": "#959a02",
14
+ "statusBarItem.remoteBackground": "#c6cd02",
15
+ "statusBarItem.remoteForeground": "#15202b",
16
+ "titleBar.activeBackground": "#c6cd02",
17
+ "titleBar.activeForeground": "#15202b",
18
+ "titleBar.inactiveBackground": "#c6cd0299",
19
+ "titleBar.inactiveForeground": "#15202b99"
20
+ },
21
+ "peacock.color": "#c6cd02"
22
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ // Template 1.0 y:\x\bin\MakeCodeTemplates\vscode\
3
+ // See https://go.microsoft.com/fwlink/?LinkId=733558
4
+ // for the documentation about the tasks.json format
5
+ "version": "2.0.0",
6
+ "tasks": [
7
+ {
8
+ "type": "typescript",
9
+ "tsconfig": "tsconfig.json",
10
+ "option": "watch",
11
+ "runOptions": {
12
+ "runOn": "folderOpen"
13
+ },
14
+ "problemMatcher": [
15
+ "$tsc-watch"
16
+ ],
17
+ "group": {
18
+ "kind": "build",
19
+ "isDefault": true
20
+ }
21
+ }
22
+ ]
23
+ }
package/bin/tswalk.js ADDED
File without changes
package/diskwalker.js ADDED
@@ -0,0 +1,240 @@
1
+ import * as fp from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { styleText } from 'node:util';
5
+ // Error types to suppress unless verbose
6
+ const suppressedErrors = ['ENOENT', 'EPERM', 'EACCES'];
7
+ export class DiskWalker {
8
+ topPath;
9
+ static defaultOptions = {
10
+ depth: 1,
11
+ threshold: 0,
12
+ cmd: '',
13
+ verbose: false,
14
+ showCompressed: false,
15
+ finding: null,
16
+ forceAcl: false,
17
+ quiet: false,
18
+ sizeUnit: 'MB'
19
+ };
20
+ options;
21
+ walker;
22
+ lastProgressTime = 0;
23
+ constructor(topPath = '.', options = {}) {
24
+ this.topPath = topPath;
25
+ this.options = { ...DiskWalker.defaultOptions, ...options };
26
+ this.topPath = path.resolve(topPath);
27
+ this.walker = new WalkTree(this.topPath, this.options, this.showProgress.bind(this));
28
+ }
29
+ truncatePath(fullPath, maxLength = 64) {
30
+ if (fullPath.length <= maxLength)
31
+ return fullPath;
32
+ const half = Math.floor((maxLength - 3) / 2);
33
+ const start = fullPath.slice(0, half);
34
+ const end = fullPath.slice(-half);
35
+ return `${start}...${end}`;
36
+ }
37
+ showProgress(walker, currentPath) {
38
+ const now = Date.now();
39
+ if (now - this.lastProgressTime > 1000 && !this.options.quiet) {
40
+ const truncatedPath = this.truncatePath(currentPath);
41
+ process.stdout.write('\x1b[2K\r'); // Clear entire line and return to start
42
+ const line = styleText(['blue'], `${walker.formatSize().padStart(13)} ${this.options.sizeUnit} ${truncatedPath}`);
43
+ process.stdout.write(line);
44
+ this.lastProgressTime = now;
45
+ }
46
+ }
47
+ async walk() {
48
+ try {
49
+ await this.walker.walk();
50
+ if (!this.options.quiet) {
51
+ process.stdout.write('\x1b[2K\r'); // Clear progress line
52
+ }
53
+ console.log(`${this.walker.formatSize()} ${this.options.sizeUnit} ${this.topPath}`);
54
+ }
55
+ catch (err) {
56
+ console.error('Failed:', err.message);
57
+ }
58
+ }
59
+ }
60
+ class WalkTree {
61
+ dirPath;
62
+ options;
63
+ progressCallback;
64
+ atDepth;
65
+ length = 0;
66
+ compressedLength = 0;
67
+ fileCount = 0;
68
+ dirCount = 0;
69
+ totalFileCount = 0;
70
+ totalDirCount = 0;
71
+ otherCount = 0;
72
+ prefix;
73
+ nextTitleUpdate = new Date(0);
74
+ seenErrors = new Set();
75
+ constructor(dirPath, options, progressCallback, atDepth = 0) {
76
+ this.dirPath = dirPath;
77
+ this.options = options;
78
+ this.progressCallback = progressCallback;
79
+ this.atDepth = atDepth;
80
+ this.prefix = ' '.repeat(5 + atDepth * 5);
81
+ if (options.verbose) {
82
+ this.prefix = atDepth.toString().padStart(2, '0') + ' ' + this.prefix;
83
+ }
84
+ }
85
+ formatSize() {
86
+ const divisor = this.options.sizeUnit === 'GB' ? (1024 * 1024 * 1024) : (1024 * 1024);
87
+ const size = this.length / divisor;
88
+ const formatted = size.toLocaleString(undefined, { minimumFractionDigits: 2 });
89
+ if (this.options.showCompressed && this.length !== this.compressedLength) {
90
+ const compressedSize = this.compressedLength / divisor;
91
+ return `${formatted}/${compressedSize.toLocaleString(undefined, { minimumFractionDigits: 2 })}`;
92
+ }
93
+ return formatted;
94
+ }
95
+ logError(context, err) {
96
+ if (!this.options.verbose &&
97
+ (this.options.quiet || suppressedErrors.some(e => err.message.includes(e)))) {
98
+ return;
99
+ }
100
+ const errorKey = `${context}:${err.message}:${this.dirPath}`;
101
+ if (!this.seenErrors.has(errorKey)) {
102
+ console.error(styleText(['red'], `Error processing ${context}: ${err.message}`));
103
+ this.seenErrors.add(errorKey);
104
+ }
105
+ }
106
+ async isSymlink(testPath) {
107
+ try {
108
+ const stats = await fp.lstat(testPath);
109
+ return stats.isSymbolicLink();
110
+ }
111
+ catch {
112
+ return false;
113
+ }
114
+ }
115
+ async processDirectory() {
116
+ try {
117
+ if (Date.now() > this.nextTitleUpdate.getTime()) {
118
+ process.title = this.dirPath;
119
+ this.nextTitleUpdate = new Date(Date.now() + 500);
120
+ }
121
+ const files = await fp.readdir(this.dirPath, { withFileTypes: true });
122
+ for (const file of files) {
123
+ if (!file.isDirectory()) {
124
+ this.fileCount++;
125
+ this.totalFileCount++;
126
+ try {
127
+ const stats = await fp.stat(path.join(this.dirPath, file.name));
128
+ this.length += stats.size;
129
+ this.compressedLength += stats.size;
130
+ }
131
+ catch (err) {
132
+ this.logError(file.name, err);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ catch (err) {
138
+ this.logError('directory', err);
139
+ }
140
+ }
141
+ async executeCommand() {
142
+ if (!this.options.cmd)
143
+ return;
144
+ const currentDir = process.cwd();
145
+ try {
146
+ process.chdir(this.dirPath);
147
+ await new Promise((resolve, reject) => {
148
+ const proc = spawn(this.options.cmd, [], { shell: true });
149
+ proc.on('close', resolve);
150
+ proc.on('error', reject);
151
+ });
152
+ }
153
+ catch (err) {
154
+ this.logError('command execution', err);
155
+ }
156
+ finally {
157
+ process.chdir(currentDir);
158
+ }
159
+ }
160
+ async findFiles() {
161
+ if (!this.options.finding)
162
+ return;
163
+ try {
164
+ for (const pattern of this.options.finding) {
165
+ const files = await fp.readdir(this.dirPath);
166
+ for (const file of files) {
167
+ if (file.match(pattern)) {
168
+ try {
169
+ const stats = await fp.stat(path.join(this.dirPath, file));
170
+ const size = stats.size / 1024;
171
+ process.stdout.write('\x1b[2K\r'); // Clear progress line
172
+ console.log(`${stats.mtime.toISOString().slice(0, 19).replace('T', ' ')} ` +
173
+ `${size.toFixed(1).padStart(8)}KB ${this.prefix} ${path.join(this.dirPath, file)}`);
174
+ }
175
+ catch (err) {
176
+ this.logError(file, err);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ catch (err) {
183
+ this.logError('file search', err);
184
+ }
185
+ }
186
+ async walk() {
187
+ try {
188
+ const isSymlink = await this.isSymlink(this.dirPath);
189
+ if (isSymlink)
190
+ return;
191
+ this.progressCallback(this, this.dirPath);
192
+ if (this.options.cmd || this.options.finding || this.options.forceAcl) {
193
+ if (this.options.forceAcl) {
194
+ // ACL operations not implemented
195
+ }
196
+ if (this.options.cmd) {
197
+ await this.executeCommand();
198
+ }
199
+ if (this.options.finding) {
200
+ await this.findFiles();
201
+ }
202
+ }
203
+ else {
204
+ await this.processDirectory();
205
+ }
206
+ const entries = await fp.readdir(this.dirPath, { withFileTypes: true });
207
+ for (const entry of entries) {
208
+ if (entry.isDirectory()) {
209
+ const subPath = path.join(this.dirPath, entry.name);
210
+ const walker = new WalkTree(subPath, this.options, this.progressCallback, this.atDepth + 1);
211
+ try {
212
+ await walker.walk();
213
+ this.dirCount++;
214
+ this.length += walker.length;
215
+ this.compressedLength += walker.compressedLength;
216
+ this.totalFileCount += walker.totalFileCount;
217
+ this.totalDirCount += walker.totalDirCount + 1;
218
+ const divisor = this.options.sizeUnit === 'GB' ? (1024 * 1024 * 1024) : (1024 * 1024);
219
+ const size = walker.length / divisor;
220
+ if (size >= this.options.threshold && this.atDepth < this.options.depth) {
221
+ if (!this.options.quiet) {
222
+ process.stdout.write('\x1b[2K\r'); // Clear progress line
223
+ }
224
+ console.log(`${this.prefix} ${walker.formatSize().padStart(13)} ${this.options.sizeUnit} ` +
225
+ `${walker.totalDirCount.toString().padStart(5)} ` +
226
+ `${walker.totalFileCount.toString().padStart(6)} ${entry.name}`);
227
+ }
228
+ }
229
+ catch (err) {
230
+ this.logError(entry.name, err);
231
+ }
232
+ }
233
+ }
234
+ }
235
+ catch (err) {
236
+ this.logError('directory walk', err);
237
+ }
238
+ }
239
+ }
240
+ //# sourceMappingURL=diskwalker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diskwalker.js","sourceRoot":"","sources":["diskwalker.ts"],"names":[],"mappings":"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"}
package/diskwalker.ts ADDED
@@ -0,0 +1,271 @@
1
+ import * as fs from 'fs';
2
+ import * as fp from 'fs/promises'
3
+ import * as path from 'path';
4
+ import { spawn } from 'child_process';
5
+ import { styleText } from 'node:util';
6
+
7
+ interface WalkOptions {
8
+ depth: number;
9
+ threshold: number;
10
+ cmd: string;
11
+ verbose: boolean;
12
+ showCompressed: boolean;
13
+ finding: string[] | null;
14
+ forceAcl: boolean;
15
+ quiet: boolean;
16
+ sizeUnit: 'MB' | 'GB';
17
+ }
18
+
19
+ type ProgressCallback = (walker: WalkTree, currentPath: string) => void;
20
+
21
+ // Error types to suppress unless verbose
22
+ const suppressedErrors = ['ENOENT', 'EPERM', 'EACCES'];
23
+
24
+ export class DiskWalker {
25
+ private static defaultOptions: WalkOptions = {
26
+ depth: 1,
27
+ threshold: 0,
28
+ cmd: '',
29
+ verbose: false,
30
+ showCompressed: false,
31
+ finding: null,
32
+ forceAcl: false,
33
+ quiet: false,
34
+ sizeUnit: 'MB'
35
+ };
36
+
37
+ private options: WalkOptions;
38
+ private walker: WalkTree;
39
+ private lastProgressTime: number = 0;
40
+
41
+ constructor(private topPath: string = '.', options: Partial<WalkOptions> = {}) {
42
+ this.options = { ...DiskWalker.defaultOptions, ...options };
43
+ this.topPath = path.resolve(topPath);
44
+ this.walker = new WalkTree(this.topPath, this.options, this.showProgress.bind(this));
45
+ }
46
+
47
+ private truncatePath(fullPath: string, maxLength: number = 64): string {
48
+ if (fullPath.length <= maxLength) return fullPath;
49
+ const half = Math.floor((maxLength - 3) / 2);
50
+ const start = fullPath.slice(0, half);
51
+ const end = fullPath.slice(-half);
52
+ return `${start}...${end}`;
53
+ }
54
+
55
+ private showProgress(walker: WalkTree, currentPath: string): void {
56
+ const now = Date.now();
57
+ if (now - this.lastProgressTime > 1000 && !this.options.quiet) {
58
+ const truncatedPath = this.truncatePath(currentPath);
59
+ process.stdout.write('\x1b[2K\r'); // Clear entire line and return to start
60
+ const line = styleText(['blue'], `${walker.formatSize().padStart(13)} ${this.options.sizeUnit} ${truncatedPath}`);
61
+ process.stdout.write(line);
62
+
63
+ this.lastProgressTime = now;
64
+ }
65
+ }
66
+
67
+ async walk(): Promise<void> {
68
+ try {
69
+ await this.walker.walk();
70
+ if (!this.options.quiet) {
71
+ process.stdout.write('\x1b[2K\r'); // Clear progress line
72
+ }
73
+ console.log(`${this.walker.formatSize()} ${this.options.sizeUnit} ${this.topPath}`);
74
+ } catch (err) {
75
+ console.error('Failed:', (err as Error).message);
76
+ }
77
+ }
78
+ }
79
+
80
+ class WalkTree {
81
+ private length: number = 0;
82
+ private compressedLength: number = 0;
83
+ private fileCount: number = 0;
84
+ private dirCount: number = 0;
85
+ private totalFileCount: number = 0;
86
+ private totalDirCount: number = 0;
87
+ private otherCount: number = 0;
88
+ private prefix: string;
89
+ private nextTitleUpdate: Date = new Date(0);
90
+ private seenErrors = new Set<string>();
91
+
92
+ constructor(
93
+ private dirPath: string,
94
+ private options: WalkOptions,
95
+ private progressCallback: ProgressCallback,
96
+ private atDepth: number = 0
97
+ ) {
98
+ this.prefix = ' '.repeat(5 + atDepth * 5);
99
+ if (options.verbose) {
100
+ this.prefix = atDepth.toString().padStart(2, '0') + ' ' + this.prefix;
101
+ }
102
+ }
103
+
104
+ public formatSize(): string {
105
+ const divisor = this.options.sizeUnit === 'GB' ? (1024 * 1024 * 1024) : (1024 * 1024);
106
+ const size = this.length / divisor;
107
+ const formatted = size.toLocaleString(undefined, { minimumFractionDigits: 2 });
108
+
109
+ if (this.options.showCompressed && this.length !== this.compressedLength) {
110
+ const compressedSize = this.compressedLength / divisor;
111
+ return `${formatted}/${compressedSize.toLocaleString(undefined, { minimumFractionDigits: 2 })}`;
112
+ }
113
+
114
+ return formatted;
115
+ }
116
+
117
+ private logError(context: string, err: Error): void {
118
+ if (!this.options.verbose &&
119
+ (this.options.quiet || suppressedErrors.some(e => err.message.includes(e)))) {
120
+ return;
121
+ }
122
+
123
+ const errorKey = `${context}:${err.message}:${this.dirPath}`;
124
+ if (!this.seenErrors.has(errorKey)) {
125
+ console.error(styleText(['red'], `Error processing ${context}: ${err.message}`));
126
+ this.seenErrors.add(errorKey);
127
+ }
128
+ }
129
+
130
+ private async isSymlink(testPath: string): Promise<boolean> {
131
+ try {
132
+ const stats = await fp.lstat(testPath);
133
+ return stats.isSymbolicLink();
134
+ } catch {
135
+ return false;
136
+ }
137
+ }
138
+
139
+ private async processDirectory(): Promise<void> {
140
+ try {
141
+ if (Date.now() > this.nextTitleUpdate.getTime()) {
142
+ process.title = this.dirPath;
143
+ this.nextTitleUpdate = new Date(Date.now() + 500);
144
+ }
145
+
146
+ const files = await fp.readdir(this.dirPath, { withFileTypes: true });
147
+
148
+ for (const file of files) {
149
+ if (!file.isDirectory()) {
150
+ this.fileCount++;
151
+ this.totalFileCount++;
152
+
153
+ try {
154
+ const stats = await fp.stat(path.join(this.dirPath, file.name));
155
+ this.length += stats.size;
156
+ this.compressedLength += stats.size;
157
+ } catch (err) {
158
+ this.logError(file.name, err as Error);
159
+ }
160
+ }
161
+ }
162
+ } catch (err) {
163
+ this.logError('directory', err as Error);
164
+ }
165
+ }
166
+
167
+ private async executeCommand(): Promise<void> {
168
+ if (!this.options.cmd) return;
169
+
170
+ const currentDir = process.cwd();
171
+ try {
172
+ process.chdir(this.dirPath);
173
+ await new Promise((resolve, reject) => {
174
+ const proc = spawn(this.options.cmd, [], { shell: true });
175
+ proc.on('close', resolve);
176
+ proc.on('error', reject);
177
+ });
178
+ } catch (err) {
179
+ this.logError('command execution', err as Error);
180
+ } finally {
181
+ process.chdir(currentDir);
182
+ }
183
+ }
184
+
185
+ private async findFiles(): Promise<void> {
186
+ if (!this.options.finding) return;
187
+
188
+ try {
189
+ for (const pattern of this.options.finding) {
190
+ const files = await fp.readdir(this.dirPath);
191
+ for (const file of files) {
192
+ if (file.match(pattern)) {
193
+ try {
194
+ const stats = await fp.stat(path.join(this.dirPath, file));
195
+ const size = stats.size / 1024;
196
+ process.stdout.write('\x1b[2K\r'); // Clear progress line
197
+ console.log(
198
+ `${stats.mtime.toISOString().slice(0, 19).replace('T', ' ')} ` +
199
+ `${size.toFixed(1).padStart(8)}KB ${this.prefix} ${path.join(this.dirPath, file)}`
200
+ );
201
+ } catch (err) {
202
+ this.logError(file, err as Error);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ } catch (err) {
208
+ this.logError('file search', err as Error);
209
+ }
210
+ }
211
+
212
+ public async walk(): Promise<void> {
213
+ try {
214
+ const isSymlink = await this.isSymlink(this.dirPath);
215
+ if (isSymlink) return;
216
+
217
+ this.progressCallback(this, this.dirPath);
218
+
219
+ if (this.options.cmd || this.options.finding || this.options.forceAcl) {
220
+ if (this.options.forceAcl) {
221
+ // ACL operations not implemented
222
+ }
223
+ if (this.options.cmd) {
224
+ await this.executeCommand();
225
+ }
226
+ if (this.options.finding) {
227
+ await this.findFiles();
228
+ }
229
+ } else {
230
+ await this.processDirectory();
231
+ }
232
+
233
+ const entries = await fp.readdir(this.dirPath, { withFileTypes: true });
234
+
235
+ for (const entry of entries) {
236
+ if (entry.isDirectory()) {
237
+ const subPath = path.join(this.dirPath, entry.name);
238
+ const walker = new WalkTree(subPath, this.options, this.progressCallback, this.atDepth + 1);
239
+
240
+ try {
241
+ await walker.walk();
242
+ this.dirCount++;
243
+ this.length += walker.length;
244
+ this.compressedLength += walker.compressedLength;
245
+ this.totalFileCount += walker.totalFileCount;
246
+ this.totalDirCount += walker.totalDirCount + 1;
247
+
248
+ const divisor = this.options.sizeUnit === 'GB' ? (1024 * 1024 * 1024) : (1024 * 1024);
249
+ const size = walker.length / divisor;
250
+ if (size >= this.options.threshold && this.atDepth < this.options.depth) {
251
+ if (!this.options.quiet) {
252
+ process.stdout.write('\x1b[2K\r'); // Clear progress line
253
+ }
254
+ console.log(
255
+ `${this.prefix} ${walker.formatSize().padStart(13)} ${this.options.sizeUnit} ` +
256
+ `${walker.totalDirCount.toString().padStart(5)} ` +
257
+ `${walker.totalFileCount.toString().padStart(6)} ${entry.name}`
258
+ );
259
+ }
260
+ } catch (err) {
261
+ this.logError(entry.name, err as Error);
262
+ }
263
+ }
264
+ }
265
+ } catch (err) {
266
+ this.logError('directory walk', err as Error);
267
+ }
268
+ }
269
+ }
270
+
271
+ export { WalkOptions };