vibefast-cli 0.1.4 → 0.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.
Files changed (88) hide show
  1. package/AUTO-DETECT-DEPS.md +607 -0
  2. package/CHANGELOG.md +86 -0
  3. package/FINAL-PACKAGE-STRATEGY.md +583 -0
  4. package/FINAL-SIMPLE-PLAN.md +487 -0
  5. package/FLOW-DIAGRAM.md +1629 -0
  6. package/GOTCHAS-AND-RISKS.md +801 -0
  7. package/IMPLEMENTATION-COMPLETE.md +477 -0
  8. package/IMPLEMENTATION-PLAN.md +1360 -0
  9. package/MONITORING-AND-ANNOUNCEMENT-GUIDE.md +669 -0
  10. package/PRE-PUBLISH-CHECKLIST.md +558 -0
  11. package/PRODUCTION-READINESS.md +684 -0
  12. package/PRODUCTION-TEST-RESULTS.md +465 -0
  13. package/PUBLISHED-SUCCESS.md +282 -0
  14. package/README.md +73 -7
  15. package/READY-TO-PUBLISH.md +419 -0
  16. package/SIMPLIFIED-PLAN.md +578 -0
  17. package/TEST-SUMMARY.md +261 -0
  18. package/USER-MODIFICATIONS.md +448 -0
  19. package/cloudflare-worker/worker.js +26 -6
  20. package/dist/commands/add.d.ts.map +1 -1
  21. package/dist/commands/add.js +192 -15
  22. package/dist/commands/add.js.map +1 -1
  23. package/dist/commands/checklist.d.ts +3 -0
  24. package/dist/commands/checklist.d.ts.map +1 -0
  25. package/dist/commands/checklist.js +64 -0
  26. package/dist/commands/checklist.js.map +1 -0
  27. package/dist/commands/devices.d.ts.map +1 -1
  28. package/dist/commands/devices.js +27 -1
  29. package/dist/commands/devices.js.map +1 -1
  30. package/dist/commands/list.d.ts.map +1 -1
  31. package/dist/commands/list.js +38 -1
  32. package/dist/commands/list.js.map +1 -1
  33. package/dist/commands/remove.d.ts.map +1 -1
  34. package/dist/commands/remove.js +85 -2
  35. package/dist/commands/remove.js.map +1 -1
  36. package/dist/commands/status.d.ts +3 -0
  37. package/dist/commands/status.d.ts.map +1 -0
  38. package/dist/commands/status.js +40 -0
  39. package/dist/commands/status.js.map +1 -0
  40. package/dist/core/__tests__/fsx.test.d.ts +2 -0
  41. package/dist/core/__tests__/fsx.test.d.ts.map +1 -0
  42. package/dist/core/__tests__/fsx.test.js +79 -0
  43. package/dist/core/__tests__/fsx.test.js.map +1 -0
  44. package/dist/core/__tests__/hash.test.d.ts +2 -0
  45. package/dist/core/__tests__/hash.test.d.ts.map +1 -0
  46. package/dist/core/__tests__/hash.test.js +84 -0
  47. package/dist/core/__tests__/hash.test.js.map +1 -0
  48. package/dist/core/__tests__/journal.test.js +65 -0
  49. package/dist/core/__tests__/journal.test.js.map +1 -1
  50. package/dist/core/__tests__/prompt.test.d.ts +2 -0
  51. package/dist/core/__tests__/prompt.test.d.ts.map +1 -0
  52. package/dist/core/__tests__/prompt.test.js +56 -0
  53. package/dist/core/__tests__/prompt.test.js.map +1 -0
  54. package/dist/core/fsx.d.ts +7 -1
  55. package/dist/core/fsx.d.ts.map +1 -1
  56. package/dist/core/fsx.js +18 -3
  57. package/dist/core/fsx.js.map +1 -1
  58. package/dist/core/hash.d.ts +13 -0
  59. package/dist/core/hash.d.ts.map +1 -0
  60. package/dist/core/hash.js +69 -0
  61. package/dist/core/hash.js.map +1 -0
  62. package/dist/core/journal.d.ts +10 -1
  63. package/dist/core/journal.d.ts.map +1 -1
  64. package/dist/core/journal.js +23 -1
  65. package/dist/core/journal.js.map +1 -1
  66. package/dist/core/prompt.d.ts +11 -0
  67. package/dist/core/prompt.d.ts.map +1 -0
  68. package/dist/core/prompt.js +34 -0
  69. package/dist/core/prompt.js.map +1 -0
  70. package/dist/index.js +4 -0
  71. package/dist/index.js.map +1 -1
  72. package/package.json +3 -1
  73. package/src/commands/add.ts +234 -16
  74. package/src/commands/checklist.ts +71 -0
  75. package/src/commands/devices.ts +28 -1
  76. package/src/commands/list.ts +39 -1
  77. package/src/commands/remove.ts +105 -3
  78. package/src/commands/status.ts +47 -0
  79. package/src/core/__tests__/fsx.test.ts +101 -0
  80. package/src/core/__tests__/hash.test.ts +112 -0
  81. package/src/core/__tests__/journal.test.ts +76 -0
  82. package/src/core/__tests__/prompt.test.ts +72 -0
  83. package/src/core/fsx.ts +38 -5
  84. package/src/core/hash.ts +84 -0
  85. package/src/core/journal.ts +40 -2
  86. package/src/core/prompt.ts +40 -0
  87. package/src/index.ts +4 -0
  88. package/text.md +27 -0
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { promptYesNo } from '../prompt.js';
3
+
4
+ describe('prompt', () => {
5
+ let originalIsTTY: boolean | undefined;
6
+ let originalCI: string | undefined;
7
+
8
+ beforeEach(() => {
9
+ originalIsTTY = process.stdin.isTTY;
10
+ originalCI = process.env.CI;
11
+ });
12
+
13
+ afterEach(() => {
14
+ if (originalIsTTY !== undefined) {
15
+ (process.stdin as any).isTTY = originalIsTTY;
16
+ }
17
+ if (originalCI !== undefined) {
18
+ process.env.CI = originalCI;
19
+ } else {
20
+ delete process.env.CI;
21
+ }
22
+ });
23
+
24
+ describe('promptYesNo', () => {
25
+ it('should return default value in non-TTY environment', () => {
26
+ (process.stdin as any).isTTY = false;
27
+
28
+ const result = promptYesNo('Continue?', true);
29
+ expect(result).toBe(true);
30
+
31
+ const result2 = promptYesNo('Continue?', false);
32
+ expect(result2).toBe(false);
33
+ });
34
+
35
+ it('should return default value in CI environment', () => {
36
+ process.env.CI = 'true';
37
+
38
+ const result = promptYesNo('Continue?', true);
39
+ expect(result).toBe(true);
40
+
41
+ const result2 = promptYesNo('Continue?', false);
42
+ expect(result2).toBe(false);
43
+ });
44
+
45
+ it('should detect GitHub Actions', () => {
46
+ process.env.GITHUB_ACTIONS = 'true';
47
+
48
+ const result = promptYesNo('Continue?', false);
49
+ expect(result).toBe(false);
50
+
51
+ delete process.env.GITHUB_ACTIONS;
52
+ });
53
+
54
+ it('should detect GitLab CI', () => {
55
+ process.env.GITLAB_CI = 'true';
56
+
57
+ const result = promptYesNo('Continue?', false);
58
+ expect(result).toBe(false);
59
+
60
+ delete process.env.GITLAB_CI;
61
+ });
62
+
63
+ it('should detect CircleCI', () => {
64
+ process.env.CIRCLECI = 'true';
65
+
66
+ const result = promptYesNo('Continue?', false);
67
+ expect(result).toBe(false);
68
+
69
+ delete process.env.CIRCLECI;
70
+ });
71
+ });
72
+ });
package/src/core/fsx.ts CHANGED
@@ -44,12 +44,24 @@ export async function deleteFile(path: string): Promise<void> {
44
44
  }
45
45
  }
46
46
 
47
+ export interface CopyResult {
48
+ files: string[];
49
+ conflicts: string[];
50
+ skipped: string[];
51
+ }
52
+
47
53
  export async function copyTree(
48
54
  src: string,
49
55
  dest: string,
50
- options?: { dryRun?: boolean; force?: boolean }
51
- ): Promise<string[]> {
56
+ options?: {
57
+ dryRun?: boolean;
58
+ force?: boolean;
59
+ interactive?: boolean;
60
+ }
61
+ ): Promise<CopyResult> {
52
62
  const copied: string[] = [];
63
+ const conflicts: string[] = [];
64
+ const skipped: string[] = [];
53
65
 
54
66
  async function copyRecursive(srcPath: string, destPath: string) {
55
67
  const stats = await stat(srcPath);
@@ -64,9 +76,30 @@ export async function copyTree(
64
76
  }
65
77
  } else {
66
78
  const destExists = await exists(destPath);
67
- if (destExists && !options?.force) {
68
- throw new Error(`File exists: ${destPath}. Use --force to overwrite.`);
79
+
80
+ if (destExists) {
81
+ conflicts.push(destPath);
82
+
83
+ // If not force and not interactive, throw error
84
+ if (!options?.force && !options?.interactive) {
85
+ throw new Error(`File exists: ${destPath}. Use --force to overwrite.`);
86
+ }
87
+
88
+ // If interactive and not dry-run, ask user
89
+ if (options?.interactive && !options?.dryRun) {
90
+ const { promptYesNo } = await import('./prompt.js');
91
+ const shouldOverwrite = promptYesNo(
92
+ `Overwrite ${destPath}? (y/N): `,
93
+ false
94
+ );
95
+
96
+ if (!shouldOverwrite) {
97
+ skipped.push(destPath);
98
+ return; // Skip this file
99
+ }
100
+ }
69
101
  }
102
+
70
103
  if (!options?.dryRun) {
71
104
  await ensureDir(dirname(destPath));
72
105
  await copyFile(srcPath, destPath);
@@ -76,5 +109,5 @@ export async function copyTree(
76
109
  }
77
110
 
78
111
  await copyRecursive(src, dest);
79
- return copied;
112
+ return { files: copied, conflicts, skipped };
80
113
  }
@@ -0,0 +1,84 @@
1
+ import { createHash } from 'crypto';
2
+ import { readFile, stat } from 'fs/promises';
3
+ import { log } from './log.js';
4
+
5
+ /**
6
+ * Hash a single file using SHA-256
7
+ * Returns empty string if file doesn't exist or can't be read
8
+ */
9
+ export async function hashFile(filePath: string): Promise<string> {
10
+ try {
11
+ const content = await readFile(filePath);
12
+ return createHash('sha256').update(content).digest('hex');
13
+ } catch (error) {
14
+ // File doesn't exist or can't be read
15
+ return '';
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Hash multiple files in batches
21
+ * Skips binary files and large files for performance
22
+ */
23
+ export async function hashFiles(
24
+ filePaths: string[],
25
+ options?: { showProgress?: boolean }
26
+ ): Promise<Map<string, string>> {
27
+ const hashes = new Map<string, string>();
28
+
29
+ // Filter out files we don't need to hash
30
+ const filesToHash: string[] = [];
31
+
32
+ for (const path of filePaths) {
33
+ // Skip binary files
34
+ if (/\.(png|jpg|jpeg|gif|ico|woff|woff2|ttf|eot|svg|mp4|mp3|pdf)$/i.test(path)) {
35
+ hashes.set(path, ''); // Store empty hash for binary files
36
+ continue;
37
+ }
38
+
39
+ // Skip large files (>1MB)
40
+ try {
41
+ const stats = await stat(path);
42
+ if (stats.size > 1024 * 1024) {
43
+ hashes.set(path, ''); // Store empty hash for large files
44
+ continue;
45
+ }
46
+ } catch {
47
+ // File doesn't exist, skip
48
+ continue;
49
+ }
50
+
51
+ filesToHash.push(path);
52
+ }
53
+
54
+ // Show progress if many files
55
+ if (options?.showProgress && filesToHash.length > 20) {
56
+ log.info(`Computing file hashes for ${filesToHash.length} files...`);
57
+ }
58
+
59
+ // Process in parallel batches
60
+ const batchSize = 10;
61
+
62
+ for (let i = 0; i < filesToHash.length; i += batchSize) {
63
+ const batch = filesToHash.slice(i, i + batchSize);
64
+
65
+ await Promise.all(
66
+ batch.map(async (path) => {
67
+ const hash = await hashFile(path);
68
+ hashes.set(path, hash);
69
+ })
70
+ );
71
+
72
+ // Show progress
73
+ if (options?.showProgress && filesToHash.length > 20) {
74
+ const progress = Math.min(i + batchSize, filesToHash.length);
75
+ process.stdout.write(`\r Progress: ${progress}/${filesToHash.length}`);
76
+ }
77
+ }
78
+
79
+ if (options?.showProgress && filesToHash.length > 20) {
80
+ process.stdout.write('\n');
81
+ }
82
+
83
+ return hashes;
84
+ }
@@ -1,14 +1,25 @@
1
1
  import { readFileContent, writeFileContent, exists, ensureDir } from './fsx.js';
2
2
  import { dirname } from 'path';
3
+ import { hashFile } from './hash.js';
4
+
5
+ export interface FileEntry {
6
+ path: string;
7
+ hash: string;
8
+ }
3
9
 
4
10
  export interface JournalEntry {
5
11
  feature: string;
6
12
  target: 'native' | 'web';
7
- files: string[];
13
+ files: FileEntry[] | string[]; // Support both old and new format
8
14
  insertedNav: boolean;
9
15
  ts: number;
10
16
  navHref?: string;
11
17
  navLabel?: string;
18
+ manifest?: {
19
+ version?: string;
20
+ manualSteps?: any[];
21
+ env?: any[];
22
+ };
12
23
  }
13
24
 
14
25
  export interface Journal {
@@ -20,7 +31,34 @@ export async function readJournal(journalPath: string): Promise<Journal> {
20
31
  return { entries: [] };
21
32
  }
22
33
  const content = await readFileContent(journalPath);
23
- return JSON.parse(content);
34
+ const journal = JSON.parse(content) as Journal;
35
+
36
+ // Migrate old format to new format
37
+ let needsMigration = false;
38
+
39
+ for (const entry of journal.entries) {
40
+ if (entry.files.length > 0 && typeof entry.files[0] === 'string') {
41
+ needsMigration = true;
42
+ // Old format: array of strings
43
+ const oldFiles = entry.files as string[];
44
+
45
+ // Convert to new format with hashes
46
+ const newFiles: FileEntry[] = [];
47
+ for (const filePath of oldFiles) {
48
+ const hash = await hashFile(filePath);
49
+ newFiles.push({ path: filePath, hash });
50
+ }
51
+
52
+ entry.files = newFiles;
53
+ }
54
+ }
55
+
56
+ // Save migrated journal
57
+ if (needsMigration) {
58
+ await writeJournal(journalPath, journal);
59
+ }
60
+
61
+ return journal;
24
62
  }
25
63
 
26
64
  export async function writeJournal(journalPath: string, journal: Journal): Promise<void> {
@@ -0,0 +1,40 @@
1
+ import readlineSync from 'readline-sync';
2
+
3
+ /**
4
+ * Prompt user for input
5
+ * Returns empty string in non-interactive environments (CI/CD)
6
+ */
7
+ export function promptUser(question: string): string {
8
+ // Check if we're in a non-interactive environment
9
+ if (!process.stdin.isTTY) {
10
+ return '';
11
+ }
12
+
13
+ // Check for CI environment variables
14
+ const isCI = process.env.CI === 'true' ||
15
+ process.env.GITHUB_ACTIONS === 'true' ||
16
+ process.env.GITLAB_CI === 'true' ||
17
+ process.env.CIRCLECI === 'true';
18
+
19
+ if (isCI) {
20
+ return '';
21
+ }
22
+
23
+ return readlineSync.question(question);
24
+ }
25
+
26
+ /**
27
+ * Prompt user for yes/no confirmation
28
+ * Returns defaultValue in non-interactive environments
29
+ */
30
+ export function promptYesNo(question: string, defaultYes = false): boolean {
31
+ const answer = promptUser(question);
32
+
33
+ // Empty answer (non-interactive or just pressed enter)
34
+ if (answer === '') {
35
+ return defaultYes;
36
+ }
37
+
38
+ const normalized = answer.toLowerCase().trim();
39
+ return normalized === 'y' || normalized === 'yes';
40
+ }
package/src/index.ts CHANGED
@@ -9,6 +9,8 @@ import { doctorCommand } from './commands/doctor.js';
9
9
  import { loginCommand } from './commands/login.js';
10
10
  import { devicesCommand } from './commands/devices.js';
11
11
  import { logoutCommand } from './commands/logout.js';
12
+ import { statusCommand } from './commands/status.js';
13
+ import { checklistCommand } from './commands/checklist.js';
12
14
 
13
15
  const program = new Command();
14
16
 
@@ -24,5 +26,7 @@ program.addCommand(doctorCommand);
24
26
  program.addCommand(listCommand);
25
27
  program.addCommand(addCommand);
26
28
  program.addCommand(removeCommand);
29
+ program.addCommand(statusCommand);
30
+ program.addCommand(checklistCommand);
27
31
 
28
32
  program.parse();
package/text.md ADDED
@@ -0,0 +1,27 @@
1
+ /$$ /$$ /$$$$$$ /$$$$$$$ /$$$$$$$$ /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$
2
+ | $$ | $$|_ $$_/| $$__ $$| $$_____/| $$_____//$$__ $$ /$$__ $$|__ $$__/
3
+ | $$ | $$ | $$ | $$ \ $$| $$ | $$ | $$ \ $$| $$ \__/ | $$
4
+ | $$ / $$/ | $$ | $$$$$$$ | $$$$$ | $$$$$ | $$$$$$$$| $$$$$$ | $$
5
+ \ $$ $$/ | $$ | $$__ $$| $$__/ | $$__/ | $$__ $$ \____ $$ | $$
6
+ \ $$$/ | $$ | $$ \ $$| $$ | $$ | $$ | $$ /$$ \ $$ | $$
7
+ \ $/ /$$$$$$| $$$$$$$/| $$$$$$$$| $$ | $$ | $$| $$$$$$/ | $$
8
+ \_/ |______/|_______/ |________/|__/ |__/ |__/ \______/ |__/
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ ▖▖▄▖▄ ▄▖▄▖▄▖▄▖▄▖
17
+ ▌▌▐ ▙▘▙▖▙▖▌▌▚ ▐
18
+ ▚▘▟▖▙▘▙▖▌ ▛▌▄▌▐
19
+
20
+
21
+ ██╗ ██╗██╗██████╗ ███████╗███████╗ █████╗ ███████╗████████╗
22
+ ██║ ██║██║██╔══██╗██╔════╝██╔════╝██╔══██╗██╔════╝╚══██╔══╝
23
+ ██║ ██║██║██████╔╝█████╗ █████╗ ███████║███████╗ ██║
24
+ ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██╔══╝ ██╔══██║╚════██║ ██║
25
+ ╚████╔╝ ██║██████╔╝███████╗██║ ██║ ██║███████║ ██║
26
+ ╚═══╝ ╚═╝╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝
27
+