forgestack-os-cli 0.3.4 → 0.3.5

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 CHANGED
@@ -30,7 +30,6 @@ forgestack-os-cli create my-app
30
30
  ```bash
31
31
  npm config get prefix
32
32
  ```
33
-
34
33
  2. Add it to PATH if needed:
35
34
  - **Windows (PowerShell):** `[System.Environment]::SetEnvironmentVariable("Path", "$env:Path;C:\Users\YourUsername\AppData\Roaming\npm", [System.EnvironmentVariableTarget]::User)`
36
35
  - **macOS/Linux:** Add `export PATH="~/.npm-global/bin:$PATH"` to `~/.bash_profile` or `~/.zshrc`
@@ -69,20 +68,20 @@ npx forgestack-os-cli create <project-name> [options]
69
68
 
70
69
  ### Available Options
71
70
 
72
- | Option | Values | Description |
73
- |--------|--------|-------------|
74
- | `--frontend` | `react-vite` \| `nextjs` \| `vue-vite` \| `sveltekit` | Frontend framework |
75
- | `--backend` | `express` \| `fastify` \| `nestjs` \| `bun-elysia` \| `go-fiber` | Backend framework |
76
- | `--auth` | `jwt` \| `clerk` \| `supabase` \| `authjs` \| `firebase` | Authentication provider |
77
- | `--database` | `postgresql` \| `mongodb` \| `mysql` \| `sqlite` \| `supabase-db` | Database |
78
- | `--api` | `rest` \| `graphql` \| `trpc` | API style |
79
- | `--preset` | `next-nest-clerk-pg` \| `react-express-jwt-mongo` \| `next-fastify-supabase-trpc` | Predefined stack |
80
- | `--stack` | JSON string | Full stack config as JSON |
81
- | `--docker` | - | Include Docker configuration |
82
- | `--no-docker` | - | Skip Docker configuration (default) |
83
- | `--multi-tenant` | - | Enable multi-tenancy scaffolding |
84
- | `--skip-install` | - | Skip npm install after project creation |
85
- | `--skip-git` | - | Skip git initialization |
71
+ | Option | Values | Description |
72
+ | ---------------- | --------------------------------------------------------------------------------- | --------------------------------------- |
73
+ | `--frontend` | `react-vite` \| `nextjs` \| `vue-vite` \| `sveltekit` | Frontend framework |
74
+ | `--backend` | `express` \| `fastify` \| `nestjs` \| `bun-elysia` \| `go-fiber` | Backend framework |
75
+ | `--auth` | `jwt` \| `clerk` \| `supabase` \| `authjs` \| `firebase` | Authentication provider |
76
+ | `--database` | `postgresql` \| `mongodb` \| `mysql` \| `sqlite` \| `supabase-db` | Database |
77
+ | `--api` | `rest` \| `graphql` \| `trpc` | API style |
78
+ | `--preset` | `next-nest-clerk-pg` \| `react-express-jwt-mongo` \| `next-fastify-supabase-trpc` | Predefined stack |
79
+ | `--stack` | JSON string | Full stack config as JSON |
80
+ | `--docker` | - | Include Docker configuration |
81
+ | `--no-docker` | - | Skip Docker configuration (default) |
82
+ | `--multi-tenant` | - | Enable multi-tenancy scaffolding |
83
+ | `--skip-install` | - | Skip npm install after project creation |
84
+ | `--skip-git` | - | Skip git initialization |
86
85
 
87
86
  ## Examples
88
87
 
@@ -97,16 +96,19 @@ You'll be guided through selecting your stack options with interactive prompts.
97
96
  ### Using Presets
98
97
 
99
98
  **Next.js + NestJS + Clerk + PostgreSQL (Full-featured):**
99
+
100
100
  ```bash
101
101
  npx forgestack-os-cli create my-enterprise --preset next-nest-clerk-pg
102
102
  ```
103
103
 
104
104
  **React + Express + JWT + MongoDB (Simple SPA):**
105
+
105
106
  ```bash
106
107
  npx forgestack-os-cli create my-app --preset react-express-jwt-mongo
107
108
  ```
108
109
 
109
110
  **Next.js + Fastify + Supabase + tRPC (Modern fullstack):**
111
+
110
112
  ```bash
111
113
  npx forgestack-os-cli create my-trpc-app --preset next-fastify-supabase-trpc
112
114
  ```
@@ -114,6 +116,7 @@ npx forgestack-os-cli create my-trpc-app --preset next-fastify-supabase-trpc
114
116
  ### Using Flags
115
117
 
116
118
  **RESTful API with React + Express:**
119
+
117
120
  ```bash
118
121
  npx forgestack-os-cli create my-rest-api \
119
122
  --frontend react-vite \
@@ -124,6 +127,7 @@ npx forgestack-os-cli create my-rest-api \
124
127
  ```
125
128
 
126
129
  **GraphQL Backend with Vue:**
130
+
127
131
  ```bash
128
132
  npx forgestack-os-cli create my-graphql-app \
129
133
  --frontend vue-vite \
@@ -135,6 +139,7 @@ npx forgestack-os-cli create my-graphql-app \
135
139
  ```
136
140
 
137
141
  **Minimal Setup (SQLite + No Docker):**
142
+
138
143
  ```bash
139
144
  npx forgestack-os-cli create my-minimal-app \
140
145
  --frontend react-vite \
@@ -161,6 +166,7 @@ npx forgestack-os-cli create my-custom-stack --stack '{
161
166
  ```
162
167
 
163
168
  **Multi-tenancy with Docker:**
169
+
164
170
  ```bash
165
171
  npx forgestack-os-cli create my-multitenant-app \
166
172
  --preset next-nest-clerk-pg \
@@ -169,14 +175,207 @@ npx forgestack-os-cli create my-multitenant-app \
169
175
  ```
170
176
 
171
177
  **Skip Dependency Installation:**
178
+
172
179
  ```bash
173
180
  npx forgestack-os-cli create my-app --preset next-nest-clerk-pg --skip-install
174
181
  cd my-app
175
182
  npm install # Install later when you're ready
176
183
  ```
177
184
 
185
+ ## Additional Commands
186
+
187
+ ### `organize` - File Organization Utility
188
+
189
+ Organize files in a folder by type or date, with optional duplicate detection. Supports MD5-based duplicate identification and automatic system folder exclusion for performance.
190
+
191
+ ```bash
192
+ npx forgestack-os-cli organize <folder-path> [options]
193
+ ```
194
+
195
+ **Options:**
196
+ - `--strategy <type>` - Organization strategy: `type` (default) or `date`
197
+ - `--duplicates` - Move duplicate files to a `Duplicates` folder
198
+
199
+ **File Categories (when using `--strategy type`):**
200
+ - **Images**: jpg, png, gif, svg, webp, bmp, ico
201
+ - **Documents**: pdf, doc, docx, txt, xlsx, csv, md
202
+ - **Videos**: mp4, mkv, avi, mov, wmv, flv
203
+ - **Audio**: mp3, wav, flac, aac, m4a, ogg
204
+ - **Code**: js, ts, py, java, cpp, go, rs, rb
205
+ - **Archives**: zip, rar, 7z, tar, gz, bz2
206
+ - **Data**: json, xml, yaml, sql, db, sqlite
207
+ - **Executables**: exe, msi, app, deb, rpm
208
+ - **Others**: All other file types
209
+
210
+ **Date Format (when using `--strategy date`):**
211
+ Files organized into folders using format: `YYYY-MM` (e.g., `2026-01`, `2025-12`)
212
+
213
+ **Features:**
214
+ - ✅ Automatically skips system folders (node_modules, .git, dist, build, .next, .env, .DS_Store, .vscode)
215
+ - ✅ MD5-based duplicate detection (prevents moving the same file twice)
216
+ - ✅ Graceful error handling for permission denied or inaccessible files
217
+ - ✅ Summary report showing total files organized and categories
218
+ - ✅ Interactive prompts if options not provided
219
+
220
+ **Examples:**
221
+
222
+ ```bash
223
+ # Organize by file type with duplicate detection
224
+ npx forgestack-os-cli organize ~/Downloads --strategy type --duplicates
225
+
226
+ # Organize photos by month
227
+ npx forgestack-os-cli organize ~/Pictures --strategy date
228
+
229
+ # Interactive mode (prompts for folder and options)
230
+ npx forgestack-os-cli organize
231
+
232
+ # Organize current directory
233
+ npx forgestack-os-cli organize .
234
+ ```
235
+
236
+ **Output Example:**
237
+ ```
238
+ ✓ Found and organized 1,250 files
239
+ ✓ Categories:
240
+ - Images: 450 files
241
+ - Documents: 280 files
242
+ - Videos: 320 files
243
+ - Code: 145 files
244
+ - Others: 55 files
245
+ ✓ Found 12 set(s) of duplicate files moved to Duplicates folder
246
+ ```
247
+
248
+ **Edge Cases Handled:**
249
+ - Empty folders: Displays "No files to organize" message
250
+ - Invalid paths: Shows clear error message with validation
251
+ - Permission errors: Skips files with access denied, continues processing
252
+ - Unreadable files: Skips during hash calculation, no crash
253
+ - Large monorepos: Excludes node_modules, .git, and build folders automatically
254
+
255
+ ---
256
+
257
+ ### `run-tasks` - Batch Task Runner
258
+
259
+ Execute shell commands from a JSON configuration file, sequentially or in parallel. Supports task-specific working directories and comprehensive error handling.
260
+
261
+ ```bash
262
+ npx forgestack-os-cli run-tasks <config-path> [options]
263
+ ```
264
+
265
+ **Options:**
266
+ - `--parallel` - Run tasks concurrently instead of sequentially (default: false)
267
+ - `--stop-on-error` - Stop execution on first task failure (default: true)
268
+
269
+ **Config File Format (tasks.json):**
270
+
271
+ ```json
272
+ {
273
+ "tasks": [
274
+ {
275
+ "name": "Build",
276
+ "command": "npm run build",
277
+ "cwd": "./"
278
+ },
279
+ {
280
+ "name": "Test",
281
+ "command": "npm test",
282
+ "cwd": "./"
283
+ }
284
+ ],
285
+ "parallel": false,
286
+ "stopOnError": true
287
+ }
288
+ ```
289
+
290
+ **Features:**
291
+ - ✅ Cross-platform shell execution (Windows CMD, Unix bash)
292
+ - ✅ Task-specific working directory support
293
+ - ✅ Sequential or parallel execution modes
294
+ - ✅ Configurable failure handling
295
+ - ✅ Comprehensive error reporting
296
+ - ✅ Interactive mode with default config file detection
297
+ - ✅ Proper exit codes for CI/CD integration
298
+
299
+ **Examples:**
300
+
301
+ ```bash
302
+ # Run tasks sequentially (stops on first error)
303
+ npx forgestack-os-cli run-tasks ./tasks.json
304
+
305
+ # Run tasks in parallel
306
+ npx forgestack-os-cli run-tasks ./tasks.json --parallel
307
+
308
+ # Continue on errors
309
+ npx forgestack-os-cli run-tasks ./tasks.json --stop-on-error false
310
+
311
+ # Interactive mode (looks for ./tasks.json automatically)
312
+ npx forgestack-os-cli run-tasks
313
+ ```
314
+
315
+ **Task Properties:**
316
+ - `name` (required) - Display name for the task (for logging)
317
+ - `command` (required) - Shell command to execute
318
+ - `cwd` (optional) - Working directory for command execution (must exist)
319
+
320
+ **Example: Monorepo Build Pipeline**
321
+
322
+ ```json
323
+ {
324
+ "tasks": [
325
+ {
326
+ "name": "Clean Build Artifacts",
327
+ "command": "rm -rf dist"
328
+ },
329
+ {
330
+ "name": "Build Frontend",
331
+ "command": "npm run build",
332
+ "cwd": "./packages/frontend"
333
+ },
334
+ {
335
+ "name": "Build Backend",
336
+ "command": "npm run build",
337
+ "cwd": "./packages/backend"
338
+ },
339
+ {
340
+ "name": "Run Tests",
341
+ "command": "npm test",
342
+ "cwd": "./packages/backend"
343
+ },
344
+ {
345
+ "name": "Generate Docs",
346
+ "command": "npm run docs"
347
+ }
348
+ ],
349
+ "parallel": false,
350
+ "stopOnError": true
351
+ }
352
+ ```
353
+
354
+ **Output Example:**
355
+ ```
356
+ ⏳ Running tasks sequentially...
357
+ ✓ Task 1/5: Clean Build Artifacts completed
358
+ ✓ Task 2/5: Build Frontend completed
359
+ ✓ Task 3/5: Build Backend completed
360
+ ✓ Task 4/5: Run Tests completed
361
+ ✓ Task 5/5: Generate Docs completed
362
+
363
+ ✓ All 5 tasks completed successfully
364
+ ```
365
+
366
+ **Error Handling:**
367
+ - **Invalid config path**: Shows clear error, no crash
368
+ - **Malformed JSON**: Displays JSON parsing error
369
+ - **Missing command**: Validates all tasks have required fields
370
+ - **Invalid working directory**: Shows warning, continues with project root
371
+ - **Task execution fails**: Respects `stopOnError` flag
372
+ - **Cross-platform compatibility**: Automatically handles Windows vs Unix paths
373
+
374
+ ---
375
+
178
376
  ## Features
179
377
 
378
+ ### Project Generation
180
379
  - **150+ Stack Combinations** - Frontend (React, Next.js, Vue, Svelte) × Backend (Express, Fastify, NestJS, Bun, Go) × Auth × Database × API Style
181
380
  - **Multi-tenancy Ready** - Scaffolding support for SaaS applications
182
381
  - **Docker Compose** - Complete Docker setup with frontend and backend services
@@ -185,6 +384,10 @@ npm install # Install later when you're ready
185
384
  - **TypeScript First** - Full TypeScript support across all generated code
186
385
  - **Production Ready** - Best practices, security headers, error handling
187
386
 
387
+ ### Utility Commands
388
+ - **File Organization** (`organize`) - Sort files by type or date, with MD5-based duplicate detection and automatic system folder exclusion
389
+ - **Batch Task Runner** (`run-tasks`) - Execute complex workflows with sequential or parallel task execution, cross-platform compatibility, and comprehensive error handling
390
+
188
391
  ## Generated Project Structure
189
392
 
190
393
  ```
@@ -219,11 +422,13 @@ my-app/
219
422
  ### "command not found: forgestack-os-cli"
220
423
 
221
424
  **Using npx?** Make sure you use the full package name:
425
+
222
426
  ```bash
223
427
  npx forgestack-os-cli create my-app
224
428
  ```
225
429
 
226
430
  **Installed globally?** Try:
431
+
227
432
  1. Verify npm global bin is in PATH: `echo $PATH` (Unix) or `$env:Path` (Windows)
228
433
  2. Reinstall: `npm install -g forgestack-os-cli@latest`
229
434
  3. Check Node.js version: `node --version` (requires 18+)
@@ -255,6 +460,7 @@ npx forgestack-os-cli init my-app
255
460
  ### Preset not found
256
461
 
257
462
  Available presets:
463
+
258
464
  - `next-nest-clerk-pg` - Next.js, NestJS, Clerk, PostgreSQL
259
465
  - `react-express-jwt-mongo` - React+Vite, Express, JWT, MongoDB
260
466
  - `next-fastify-supabase-trpc` - Next.js, Fastify, Supabase, tRPC
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function organizeCommand(folderPath: string | undefined, options: Record<string, unknown>, _command?: Command): Promise<void>;
@@ -0,0 +1,126 @@
1
+ import path from 'path';
2
+ import fs from 'fs-extra';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { logger } from '../utils/logger.js';
6
+ import { organizeFilesByType, organizeFilesByDate, detectDuplicates } from '../utils/file-organizer.js';
7
+ export async function organizeCommand(folderPath, options, _command) {
8
+ try {
9
+ let targetPath = folderPath || '';
10
+ // If no folder path provided, use interactive prompt
11
+ if (!targetPath) {
12
+ const answers = await inquirer.prompt([
13
+ {
14
+ type: 'input',
15
+ name: 'path',
16
+ message: 'Enter the folder path to organize:',
17
+ default: process.cwd(),
18
+ validate: (input) => {
19
+ if (!input.trim())
20
+ return 'Path cannot be empty';
21
+ return true;
22
+ },
23
+ },
24
+ ]);
25
+ targetPath = answers.path;
26
+ }
27
+ // Resolve to absolute path
28
+ const absolutePath = path.resolve(targetPath);
29
+ // Validate folder exists
30
+ if (!(await fs.pathExists(absolutePath))) {
31
+ logger.error(`Folder does not exist: ${absolutePath}`);
32
+ process.exit(1);
33
+ }
34
+ const stats = await fs.stat(absolutePath);
35
+ if (!stats.isDirectory()) {
36
+ logger.error(`Path is not a directory: ${absolutePath}`);
37
+ process.exit(1);
38
+ }
39
+ // Get organization strategy
40
+ let strategy = options.strategy || '';
41
+ if (!['type', 'date'].includes(strategy)) {
42
+ const answers = await inquirer.prompt([
43
+ {
44
+ type: 'list',
45
+ name: 'strategy',
46
+ message: 'How would you like to organize files?',
47
+ choices: [
48
+ { name: 'By File Type (images, documents, etc.)', value: 'type' },
49
+ { name: 'By Date (YYYY-MM)', value: 'date' },
50
+ ],
51
+ },
52
+ ]);
53
+ strategy = answers.strategy;
54
+ }
55
+ // Check if user wants to handle duplicates
56
+ let handleDuplicates = !!options.duplicates;
57
+ if (!options.duplicates) {
58
+ const answers = await inquirer.prompt([
59
+ {
60
+ type: 'confirm',
61
+ name: 'duplicates',
62
+ message: 'Move duplicate files to a "Duplicates" folder?',
63
+ default: false,
64
+ },
65
+ ]);
66
+ handleDuplicates = answers.duplicates;
67
+ }
68
+ console.log('');
69
+ logger.title('📁 Organizing files...');
70
+ // Detect duplicates if needed
71
+ let duplicates = new Map();
72
+ if (handleDuplicates) {
73
+ logger.info('Detecting duplicate files...');
74
+ duplicates = await detectDuplicates(absolutePath);
75
+ if (duplicates.size === 0) {
76
+ console.log(chalk.gray('No duplicates found.'));
77
+ }
78
+ else {
79
+ console.log(chalk.gray(`Found ${duplicates.size} set(s) of duplicate files.`));
80
+ }
81
+ }
82
+ // Organize files
83
+ let result;
84
+ if (strategy === 'type') {
85
+ result = await organizeFilesByType(absolutePath, duplicates);
86
+ }
87
+ else {
88
+ result = await organizeFilesByDate(absolutePath, duplicates);
89
+ }
90
+ // Display results
91
+ console.log('');
92
+ logger.success('Organization complete!');
93
+ console.log('');
94
+ console.log(chalk.bold('Summary:'));
95
+ console.log(chalk.gray('─'.repeat(50)));
96
+ let totalFilesMoved = 0;
97
+ const categorizedEntries = Object.entries(result.categorized);
98
+ if (categorizedEntries.length === 0) {
99
+ console.log(chalk.gray('No files to organize.'));
100
+ }
101
+ else {
102
+ for (const [folder, count] of categorizedEntries) {
103
+ console.log(`${chalk.cyan('→')} ${folder}: ${chalk.bold(count)} files`);
104
+ totalFilesMoved += count;
105
+ }
106
+ }
107
+ if (result.duplicates > 0) {
108
+ console.log(`${chalk.cyan('→')} Duplicates: ${chalk.bold(result.duplicates)} files`);
109
+ totalFilesMoved += result.duplicates;
110
+ }
111
+ console.log(chalk.gray('─'.repeat(50)));
112
+ console.log(`${chalk.bold('Total files moved:')} ${chalk.green(totalFilesMoved)}`);
113
+ console.log('');
114
+ }
115
+ catch (error) {
116
+ logger.error('Failed to organize files');
117
+ if (error instanceof Error) {
118
+ console.error(chalk.red(error.message));
119
+ }
120
+ else {
121
+ console.error(error);
122
+ }
123
+ process.exit(1);
124
+ }
125
+ }
126
+ //# sourceMappingURL=organize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"organize.js","sourceRoot":"","sources":["../../src/commands/organize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { Command } from 'commander';
2
+ export interface Task {
3
+ name: string;
4
+ command: string;
5
+ cwd?: string;
6
+ }
7
+ export interface TasksConfig {
8
+ tasks: Task[];
9
+ parallel?: boolean;
10
+ stopOnError?: boolean;
11
+ }
12
+ export declare function runTasksCommand(configPath: string | undefined, options: Record<string, unknown>, _command?: Command): Promise<void>;
@@ -0,0 +1,125 @@
1
+ import path from 'path';
2
+ import fs from 'fs-extra';
3
+ import chalk from 'chalk';
4
+ import inquirer from 'inquirer';
5
+ import { logger } from '../utils/logger.js';
6
+ import { executeTasksSequentially, executeTasksInParallel } from '../utils/task-runner.js';
7
+ export async function runTasksCommand(configPath, options, _command) {
8
+ try {
9
+ let tasksFilePath = configPath || '';
10
+ // If no config path provided, use interactive prompt
11
+ if (!tasksFilePath) {
12
+ const answers = await inquirer.prompt([
13
+ {
14
+ type: 'input',
15
+ name: 'path',
16
+ message: 'Enter the path to tasks.json config file:',
17
+ default: './tasks.json',
18
+ validate: (input) => {
19
+ if (!input.trim())
20
+ return 'Path cannot be empty';
21
+ return true;
22
+ },
23
+ },
24
+ ]);
25
+ tasksFilePath = answers.path;
26
+ }
27
+ // Resolve to absolute path
28
+ const absolutePath = path.resolve(tasksFilePath);
29
+ // Validate file exists
30
+ if (!(await fs.pathExists(absolutePath))) {
31
+ logger.error(`Config file not found: ${absolutePath}`);
32
+ process.exit(1);
33
+ }
34
+ // Validate it's a file
35
+ const stats = await fs.stat(absolutePath);
36
+ if (!stats.isFile()) {
37
+ logger.error(`Path is not a file: ${absolutePath}`);
38
+ process.exit(1);
39
+ }
40
+ // Parse config file
41
+ let config;
42
+ try {
43
+ const fileContent = await fs.readFile(absolutePath, 'utf-8');
44
+ config = JSON.parse(fileContent);
45
+ }
46
+ catch (error) {
47
+ logger.error(`Failed to parse config file: ${absolutePath}`);
48
+ if (error instanceof SyntaxError) {
49
+ console.error(chalk.red(`JSON Error: ${error.message}`));
50
+ }
51
+ process.exit(1);
52
+ }
53
+ // Validate config
54
+ if (!config.tasks || !Array.isArray(config.tasks) || config.tasks.length === 0) {
55
+ logger.error('Config file must contain a "tasks" array with at least one task');
56
+ process.exit(1);
57
+ }
58
+ // Validate each task
59
+ for (const task of config.tasks) {
60
+ if (!task.name || !task.command) {
61
+ logger.error('Each task must have a "name" and "command" field');
62
+ process.exit(1);
63
+ }
64
+ if (task.cwd && !(await fs.pathExists(task.cwd))) {
65
+ logger.warning(`Working directory does not exist for task "${task.name}": ${task.cwd}`);
66
+ }
67
+ }
68
+ // Determine execution mode (CLI options override config file)
69
+ const parallel = options.parallel === true || (config.parallel === true && options.parallel !== false);
70
+ const stopOnError = options.stopOnError !== false && (config.stopOnError !== false || options.stopOnError === true);
71
+ console.log('');
72
+ logger.title('🚀 Running Tasks');
73
+ console.log(chalk.gray(`Total tasks: ${config.tasks.length}`));
74
+ console.log(chalk.gray(`Mode: ${parallel ? 'Parallel' : 'Sequential'}`));
75
+ console.log(chalk.gray(`Stop on error: ${stopOnError ? 'Yes' : 'No'}`));
76
+ console.log('');
77
+ // Execute tasks
78
+ let results;
79
+ if (parallel) {
80
+ results = await executeTasksInParallel(config.tasks, stopOnError);
81
+ }
82
+ else {
83
+ results = await executeTasksSequentially(config.tasks, stopOnError);
84
+ }
85
+ // Display results
86
+ console.log('');
87
+ logger.title('📊 Task Results');
88
+ console.log(chalk.gray('─'.repeat(60)));
89
+ let successCount = 0;
90
+ let failureCount = 0;
91
+ for (const result of results) {
92
+ const statusIcon = result.success ? chalk.green('✔') : chalk.red('✖');
93
+ const statusText = result.success ? chalk.green('SUCCESS') : chalk.red('FAILED');
94
+ console.log(`${statusIcon} ${chalk.bold(result.name)}: ${statusText}`);
95
+ if (!result.success) {
96
+ failureCount++;
97
+ if (result.error) {
98
+ console.log(chalk.red(` Error: ${result.error}`));
99
+ }
100
+ }
101
+ else {
102
+ successCount++;
103
+ }
104
+ }
105
+ console.log(chalk.gray('─'.repeat(60)));
106
+ console.log('');
107
+ console.log(chalk.bold(`Results: ${chalk.green(`${successCount} succeeded`)} / ${chalk.red(`${failureCount} failed`)}`));
108
+ console.log('');
109
+ // Exit with appropriate code
110
+ if (failureCount > 0 && stopOnError) {
111
+ process.exit(1);
112
+ }
113
+ }
114
+ catch (error) {
115
+ logger.error('Failed to run tasks');
116
+ if (error instanceof Error) {
117
+ console.error(chalk.red(error.message));
118
+ }
119
+ else {
120
+ console.error(error);
121
+ }
122
+ process.exit(1);
123
+ }
124
+ }
125
+ //# sourceMappingURL=run-tasks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-tasks.js","sourceRoot":"","sources":["../../src/commands/run-tasks.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
  import { createRequire } from 'module';
3
3
  import { Command } from 'commander';
4
4
  import { createCommand } from './commands/create.js';
5
+ import { organizeCommand } from './commands/organize.js';
6
+ import { runTasksCommand } from './commands/run-tasks.js';
5
7
  const pkgRequire = createRequire(import.meta.url);
6
8
  const { version: CLI_VERSION } = pkgRequire('../package.json');
7
9
  const program = new Command();
@@ -25,5 +27,17 @@ program
25
27
  .option('--multi-tenant', 'Enable multi-tenancy')
26
28
  .option('--skip-install', 'Skip dependency installation')
27
29
  .option('--skip-git', 'Skip Git initialization');
30
+ program
31
+ .command('organize [folder-path]')
32
+ .description('Organize files by type or date')
33
+ .action(organizeCommand)
34
+ .option('--strategy <type>', 'Organization strategy: type or date')
35
+ .option('--duplicates', 'Move duplicate files to a Duplicates folder');
36
+ program
37
+ .command('run-tasks [config-path]')
38
+ .description('Run batch tasks from a JSON config file')
39
+ .action(runTasksCommand)
40
+ .option('--parallel', 'Run tasks in parallel instead of sequentially')
41
+ .option('--stop-on-error', 'Stop execution if any task fails');
28
42
  program.parse();
29
43
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAwB,CAAC;AAEtF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACF,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,4DAA4D,CAAC;KACzE,OAAO,CAAC,WAAW,CAAC,CAAC;AAE1B,OAAO;KACF,OAAO,CAAC,uBAAuB,CAAC;KAChC,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,aAAa,CAAC;KACrB,MAAM,CAAC,wBAAwB,EAAE,oBAAoB,CAAC;KACtD,MAAM,CAAC,uBAAuB,EAAE,mBAAmB,CAAC;KACpD,MAAM,CAAC,mBAAmB,EAAE,eAAe,CAAC;KAC5C,MAAM,CAAC,iBAAiB,EAAE,UAAU,CAAC;KACrC,MAAM,CAAC,eAAe,EAAE,WAAW,CAAC;KACpC,MAAM,CAAC,iBAAiB,EAAE,+BAA+B,CAAC;KAC1D,MAAM,CAAC,gBAAgB,EAAE,mCAAmC,CAAC;KAC7D,MAAM,CAAC,UAAU,EAAE,8BAA8B,CAAC;KAClD,MAAM,CAAC,aAAa,EAAE,2BAA2B,CAAC;KAClD,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,CAAC;KAChD,MAAM,CAAC,gBAAgB,EAAE,8BAA8B,CAAC;KACxD,MAAM,CAAC,YAAY,EAAE,yBAAyB,CAAC,CAAC;AAErD,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAwB,CAAC;AAEtF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACF,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CAAC,4DAA4D,CAAC;KACzE,OAAO,CAAC,WAAW,CAAC,CAAC;AAE1B,OAAO;KACF,OAAO,CAAC,uBAAuB,CAAC;KAChC,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,aAAa,CAAC;KACrB,MAAM,CAAC,wBAAwB,EAAE,oBAAoB,CAAC;KACtD,MAAM,CAAC,uBAAuB,EAAE,mBAAmB,CAAC;KACpD,MAAM,CAAC,mBAAmB,EAAE,eAAe,CAAC;KAC5C,MAAM,CAAC,iBAAiB,EAAE,UAAU,CAAC;KACrC,MAAM,CAAC,eAAe,EAAE,WAAW,CAAC;KACpC,MAAM,CAAC,iBAAiB,EAAE,+BAA+B,CAAC;KAC1D,MAAM,CAAC,gBAAgB,EAAE,mCAAmC,CAAC;KAC7D,MAAM,CAAC,UAAU,EAAE,8BAA8B,CAAC;KAClD,MAAM,CAAC,aAAa,EAAE,2BAA2B,CAAC;KAClD,MAAM,CAAC,gBAAgB,EAAE,sBAAsB,CAAC;KAChD,MAAM,CAAC,gBAAgB,EAAE,8BAA8B,CAAC;KACxD,MAAM,CAAC,YAAY,EAAE,yBAAyB,CAAC,CAAC;AAErD,OAAO;KACF,OAAO,CAAC,wBAAwB,CAAC;KACjC,WAAW,CAAC,gCAAgC,CAAC;KAC7C,MAAM,CAAC,eAAe,CAAC;KACvB,MAAM,CAAC,mBAAmB,EAAE,qCAAqC,CAAC;KAClE,MAAM,CAAC,cAAc,EAAE,6CAA6C,CAAC,CAAC;AAE3E,OAAO;KACF,OAAO,CAAC,yBAAyB,CAAC;KAClC,WAAW,CAAC,yCAAyC,CAAC;KACtD,MAAM,CAAC,eAAe,CAAC;KACvB,MAAM,CAAC,YAAY,EAAE,+CAA+C,CAAC;KACrE,MAAM,CAAC,iBAAiB,EAAE,kCAAkC,CAAC,CAAC;AAEnE,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,17 @@
1
+ interface OrganizeResult {
2
+ categorized: Record<string, number>;
3
+ duplicates: number;
4
+ }
5
+ /**
6
+ * Detect duplicate files by hash
7
+ */
8
+ export declare function detectDuplicates(folderPath: string): Promise<Map<string, string[]>>;
9
+ /**
10
+ * Organize files by type into categorized folders
11
+ */
12
+ export declare function organizeFilesByType(folderPath: string, duplicates?: Map<string, string[]>): Promise<OrganizeResult>;
13
+ /**
14
+ * Organize files by date (YYYY-MM format)
15
+ */
16
+ export declare function organizeFilesByDate(folderPath: string, duplicates?: Map<string, string[]>): Promise<OrganizeResult>;
17
+ export {};
@@ -0,0 +1,170 @@
1
+ import path from 'path';
2
+ import fs from 'fs-extra';
3
+ import crypto from 'crypto';
4
+ // File type categories
5
+ const FILE_CATEGORIES = {
6
+ Images: ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp', '.ico', '.tiff'],
7
+ Documents: ['.pdf', '.doc', '.docx', '.txt', '.xls', '.xlsx', '.ppt', '.pptx', '.odt'],
8
+ Archives: ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.iso'],
9
+ Videos: ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm', '.m4v'],
10
+ Audio: ['.mp3', '.wav', '.flac', '.aac', '.ogg', '.wma', '.m4a'],
11
+ Code: ['.js', '.ts', '.py', '.java', '.cpp', '.c', '.go', '.rs', '.rb', '.php', '.html', '.css', '.json', '.xml', '.yaml', '.yml'],
12
+ Data: ['.csv', '.sql', '.db', '.sqlite', '.json', '.yaml'],
13
+ Executables: ['.exe', '.msi', '.app', '.bin', '.sh', '.bat'],
14
+ Others: [],
15
+ };
16
+ /**
17
+ * Calculate MD5 hash of a file
18
+ */
19
+ async function getFileHash(filePath) {
20
+ try {
21
+ const content = await fs.readFile(filePath);
22
+ return crypto.createHash('md5').update(content).digest('hex');
23
+ }
24
+ catch {
25
+ // Return empty string for unreadable files
26
+ return '';
27
+ }
28
+ }
29
+ /**
30
+ * Detect duplicate files by hash
31
+ */
32
+ export async function detectDuplicates(folderPath) {
33
+ const fileHashes = new Map();
34
+ const duplicates = new Map();
35
+ // System folders to skip
36
+ const skipFolders = new Set(['.git', '.env', 'node_modules', '.next', 'dist', 'build', '.DS_Store', '.vscode']);
37
+ async function scanDirectory(dir) {
38
+ try {
39
+ const files = await fs.readdir(dir);
40
+ for (const file of files) {
41
+ if (skipFolders.has(file) || file.startsWith('.')) {
42
+ continue;
43
+ }
44
+ const filePath = path.join(dir, file);
45
+ try {
46
+ const stats = await fs.stat(filePath);
47
+ if (stats.isDirectory()) {
48
+ await scanDirectory(filePath);
49
+ }
50
+ else if (stats.isFile() && stats.size > 0) {
51
+ try {
52
+ const hash = await getFileHash(filePath);
53
+ if (hash) {
54
+ if (!fileHashes.has(hash)) {
55
+ fileHashes.set(hash, []);
56
+ }
57
+ fileHashes.get(hash).push(filePath);
58
+ }
59
+ }
60
+ catch {
61
+ // Skip files that can't be hashed
62
+ }
63
+ }
64
+ }
65
+ catch {
66
+ // Skip files/folders that can't be accessed
67
+ }
68
+ }
69
+ }
70
+ catch {
71
+ // Skip directories that can't be read
72
+ }
73
+ }
74
+ await scanDirectory(folderPath);
75
+ // Identify actual duplicates
76
+ for (const [hash, files] of fileHashes) {
77
+ if (files.length > 1) {
78
+ duplicates.set(hash, files);
79
+ }
80
+ }
81
+ return duplicates;
82
+ }
83
+ /**
84
+ * Organize files by type into categorized folders
85
+ */
86
+ export async function organizeFilesByType(folderPath, duplicates = new Map()) {
87
+ const result = { categorized: {}, duplicates: 0 };
88
+ const duplicateSet = new Set();
89
+ // Build a set of duplicate files for quick lookup
90
+ for (const files of duplicates.values()) {
91
+ files.forEach(f => duplicateSet.add(f));
92
+ }
93
+ async function scanAndOrganize(dir) {
94
+ const files = await fs.readdir(dir);
95
+ for (const file of files) {
96
+ const filePath = path.join(dir, file);
97
+ const stats = await fs.stat(filePath);
98
+ if (stats.isDirectory()) {
99
+ // Skip special folders
100
+ if (file === 'Duplicates' || file.startsWith('.')) {
101
+ continue;
102
+ }
103
+ await scanAndOrganize(filePath);
104
+ }
105
+ else {
106
+ const ext = path.extname(file).toLowerCase();
107
+ // Find category
108
+ let category = 'Others';
109
+ for (const [cat, extensions] of Object.entries(FILE_CATEGORIES)) {
110
+ if (extensions.includes(ext)) {
111
+ category = cat;
112
+ break;
113
+ }
114
+ }
115
+ const destFolder = path.join(folderPath, duplicateSet.has(filePath) ? 'Duplicates' : category);
116
+ await fs.ensureDir(destFolder);
117
+ await fs.move(filePath, path.join(destFolder, file), { overwrite: true });
118
+ if (duplicateSet.has(filePath)) {
119
+ result.duplicates++;
120
+ }
121
+ else {
122
+ result.categorized[category] = (result.categorized[category] || 0) + 1;
123
+ }
124
+ }
125
+ }
126
+ }
127
+ await scanAndOrganize(folderPath);
128
+ return result;
129
+ }
130
+ /**
131
+ * Organize files by date (YYYY-MM format)
132
+ */
133
+ export async function organizeFilesByDate(folderPath, duplicates = new Map()) {
134
+ const result = { categorized: {}, duplicates: 0 };
135
+ const duplicateSet = new Set();
136
+ // Build a set of duplicate files
137
+ for (const files of duplicates.values()) {
138
+ files.forEach(f => duplicateSet.add(f));
139
+ }
140
+ async function scanAndOrganize(dir) {
141
+ const files = await fs.readdir(dir);
142
+ for (const file of files) {
143
+ const filePath = path.join(dir, file);
144
+ const stats = await fs.stat(filePath);
145
+ if (stats.isDirectory()) {
146
+ if (file.startsWith('.') || /^\d{4}-\d{2}$/.test(file)) {
147
+ continue;
148
+ }
149
+ await scanAndOrganize(filePath);
150
+ }
151
+ else {
152
+ // Get file modification date
153
+ const mtime = new Date(stats.mtime);
154
+ const dateFolder = mtime.toISOString().slice(0, 7); // YYYY-MM
155
+ const destFolder = path.join(folderPath, duplicateSet.has(filePath) ? 'Duplicates' : dateFolder);
156
+ await fs.ensureDir(destFolder);
157
+ await fs.move(filePath, path.join(destFolder, file), { overwrite: true });
158
+ if (duplicateSet.has(filePath)) {
159
+ result.duplicates++;
160
+ }
161
+ else {
162
+ result.categorized[dateFolder] = (result.categorized[dateFolder] || 0) + 1;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ await scanAndOrganize(folderPath);
168
+ return result;
169
+ }
170
+ //# sourceMappingURL=file-organizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-organizer.js","sourceRoot":"","sources":["../../src/utils/file-organizer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { Task } from '../commands/run-tasks.js';
2
+ export interface TaskResult {
3
+ name: string;
4
+ success: boolean;
5
+ error?: string;
6
+ }
7
+ /**
8
+ * Execute tasks sequentially (one after another)
9
+ */
10
+ export declare function executeTasksSequentially(tasks: Task[], stopOnError?: boolean): Promise<TaskResult[]>;
11
+ /**
12
+ * Execute tasks in parallel
13
+ */
14
+ export declare function executeTasksInParallel(tasks: Task[], stopOnError?: boolean): Promise<TaskResult[]>;
@@ -0,0 +1,79 @@
1
+ import { execSync } from 'child_process';
2
+ import chalk from 'chalk';
3
+ import os from 'os';
4
+ /**
5
+ * Execute a single task
6
+ */
7
+ async function executeTask(task) {
8
+ try {
9
+ console.log(`${chalk.cyan('→')} Running: ${chalk.bold(task.name)}`);
10
+ // Use shell: true for cross-platform command execution
11
+ const options = {
12
+ stdio: 'pipe',
13
+ encoding: 'utf-8',
14
+ shell: true,
15
+ };
16
+ if (task.cwd) {
17
+ options.cwd = task.cwd;
18
+ }
19
+ // Add shell option for cross-platform compatibility
20
+ if (os.platform() === 'win32') {
21
+ options.shell = 'cmd.exe';
22
+ }
23
+ try {
24
+ execSync(task.command, options);
25
+ }
26
+ catch (error) {
27
+ // execSync throws on non-zero exit code
28
+ // Extract stderr if available
29
+ if (error instanceof Error) {
30
+ throw error;
31
+ }
32
+ throw new Error(`Command failed: ${task.command}`);
33
+ }
34
+ console.log(`${chalk.green('✔')} ${task.name} completed`);
35
+ return { name: task.name, success: true };
36
+ }
37
+ catch (error) {
38
+ const errorMessage = error instanceof Error ? error.message.split('\n')[0] : String(error);
39
+ console.log(`${chalk.red('✖')} ${task.name} failed`);
40
+ return { name: task.name, success: false, error: errorMessage };
41
+ }
42
+ }
43
+ /**
44
+ * Execute tasks sequentially (one after another)
45
+ */
46
+ export async function executeTasksSequentially(tasks, stopOnError = true) {
47
+ const results = [];
48
+ for (const task of tasks) {
49
+ console.log('');
50
+ const result = await executeTask(task);
51
+ results.push(result);
52
+ if (!result.success && stopOnError) {
53
+ console.log('');
54
+ console.log(chalk.yellow('⚠ Stopping execution due to task failure'));
55
+ break;
56
+ }
57
+ }
58
+ return results;
59
+ }
60
+ /**
61
+ * Execute tasks in parallel
62
+ */
63
+ export async function executeTasksInParallel(tasks, stopOnError = true) {
64
+ console.log('');
65
+ const promises = tasks.map(task => executeTask(task)
66
+ .catch(error => ({
67
+ name: task.name,
68
+ success: false,
69
+ error: error instanceof Error ? error.message : String(error),
70
+ })));
71
+ const results = await Promise.all(promises);
72
+ // If stopOnError is true and any task failed, we should note this
73
+ if (stopOnError && results.some(r => !r.success)) {
74
+ console.log('');
75
+ console.log(chalk.yellow('⚠ Some tasks failed during parallel execution'));
76
+ }
77
+ return results;
78
+ }
79
+ //# sourceMappingURL=task-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-runner.js","sourceRoot":"","sources":["../../src/utils/task-runner.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "forgestack-os-cli",
3
- "version": "0.3.4",
4
- "description": "ForgeStack OS CLI - Generate production-ready full-stack SaaS applications",
3
+ "version": "0.3.5",
4
+ "description": "ForgeStack OS CLI - Generate production-ready full-stack SaaS applications with file organization and batch task execution utilities",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "exports": {
@@ -32,7 +32,13 @@
32
32
  "cli",
33
33
  "generator",
34
34
  "saas",
35
- "full-stack"
35
+ "full-stack",
36
+ "organize-files",
37
+ "file-organization",
38
+ "task-runner",
39
+ "batch-execution",
40
+ "duplicate-detection",
41
+ "workflow"
36
42
  ],
37
43
  "author": {
38
44
  "name": "Sumit Chauhan",
@@ -48,11 +54,12 @@
48
54
  },
49
55
  "homepage": "https://github.com/halloffame12/forgestack-os#readme",
50
56
  "engines": {
51
- "node": ">=20"
57
+ "node": ">=18"
52
58
  },
53
59
  "dependencies": {
54
60
  "chalk": "^4.1.2",
55
61
  "commander": "^13.0.0",
62
+ "crypto": "^1.0.1",
56
63
  "ejs": "^3.1.10",
57
64
  "execa": "^5.1.1",
58
65
  "fs-extra": "^11.2.0",