vibefast-cli 0.1.3 → 0.2.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/AUTO-DETECT-DEPS.md +607 -0
- package/CHANGELOG.md +86 -0
- package/FINAL-PACKAGE-STRATEGY.md +583 -0
- package/FINAL-SIMPLE-PLAN.md +487 -0
- package/FLOW-DIAGRAM.md +1629 -0
- package/GOTCHAS-AND-RISKS.md +801 -0
- package/IMPLEMENTATION-COMPLETE.md +477 -0
- package/IMPLEMENTATION-PLAN.md +1360 -0
- package/PRE-PUBLISH-CHECKLIST.md +558 -0
- package/PRODUCTION-READINESS.md +684 -0
- package/PRODUCTION-TEST-RESULTS.md +465 -0
- package/README.md +73 -7
- package/READY-TO-PUBLISH.md +419 -0
- package/SIMPLIFIED-PLAN.md +578 -0
- package/TEST-SUMMARY.md +261 -0
- package/USER-MODIFICATIONS.md +448 -0
- package/cloudflare-worker/worker.js +39 -11
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +192 -15
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/checklist.d.ts +3 -0
- package/dist/commands/checklist.d.ts.map +1 -0
- package/dist/commands/checklist.js +64 -0
- package/dist/commands/checklist.js.map +1 -0
- package/dist/commands/remove.d.ts.map +1 -1
- package/dist/commands/remove.js +85 -2
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +40 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/core/__tests__/fsx.test.d.ts +2 -0
- package/dist/core/__tests__/fsx.test.d.ts.map +1 -0
- package/dist/core/__tests__/fsx.test.js +79 -0
- package/dist/core/__tests__/fsx.test.js.map +1 -0
- package/dist/core/__tests__/hash.test.d.ts +2 -0
- package/dist/core/__tests__/hash.test.d.ts.map +1 -0
- package/dist/core/__tests__/hash.test.js +84 -0
- package/dist/core/__tests__/hash.test.js.map +1 -0
- package/dist/core/__tests__/journal.test.js +65 -0
- package/dist/core/__tests__/journal.test.js.map +1 -1
- package/dist/core/__tests__/prompt.test.d.ts +2 -0
- package/dist/core/__tests__/prompt.test.d.ts.map +1 -0
- package/dist/core/__tests__/prompt.test.js +56 -0
- package/dist/core/__tests__/prompt.test.js.map +1 -0
- package/dist/core/fsx.d.ts +7 -1
- package/dist/core/fsx.d.ts.map +1 -1
- package/dist/core/fsx.js +18 -3
- package/dist/core/fsx.js.map +1 -1
- package/dist/core/hash.d.ts +13 -0
- package/dist/core/hash.d.ts.map +1 -0
- package/dist/core/hash.js +69 -0
- package/dist/core/hash.js.map +1 -0
- package/dist/core/journal.d.ts +10 -1
- package/dist/core/journal.d.ts.map +1 -1
- package/dist/core/journal.js +23 -1
- package/dist/core/journal.js.map +1 -1
- package/dist/core/prompt.d.ts +11 -0
- package/dist/core/prompt.d.ts.map +1 -0
- package/dist/core/prompt.js +34 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/commands/add.ts +234 -16
- package/src/commands/checklist.ts +71 -0
- package/src/commands/remove.ts +105 -3
- package/src/commands/status.ts +47 -0
- package/src/core/__tests__/fsx.test.ts +101 -0
- package/src/core/__tests__/hash.test.ts +112 -0
- package/src/core/__tests__/journal.test.ts +76 -0
- package/src/core/__tests__/prompt.test.ts +72 -0
- package/src/core/fsx.ts +38 -5
- package/src/core/hash.ts +84 -0
- package/src/core/journal.ts +40 -2
- package/src/core/prompt.ts +40 -0
- package/src/index.ts +5 -1
- package/text.md +27 -0
package/dist/core/journal.js
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
import { readFileContent, writeFileContent, exists, ensureDir } from './fsx.js';
|
|
2
2
|
import { dirname } from 'path';
|
|
3
|
+
import { hashFile } from './hash.js';
|
|
3
4
|
export async function readJournal(journalPath) {
|
|
4
5
|
if (!(await exists(journalPath))) {
|
|
5
6
|
return { entries: [] };
|
|
6
7
|
}
|
|
7
8
|
const content = await readFileContent(journalPath);
|
|
8
|
-
|
|
9
|
+
const journal = JSON.parse(content);
|
|
10
|
+
// Migrate old format to new format
|
|
11
|
+
let needsMigration = false;
|
|
12
|
+
for (const entry of journal.entries) {
|
|
13
|
+
if (entry.files.length > 0 && typeof entry.files[0] === 'string') {
|
|
14
|
+
needsMigration = true;
|
|
15
|
+
// Old format: array of strings
|
|
16
|
+
const oldFiles = entry.files;
|
|
17
|
+
// Convert to new format with hashes
|
|
18
|
+
const newFiles = [];
|
|
19
|
+
for (const filePath of oldFiles) {
|
|
20
|
+
const hash = await hashFile(filePath);
|
|
21
|
+
newFiles.push({ path: filePath, hash });
|
|
22
|
+
}
|
|
23
|
+
entry.files = newFiles;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Save migrated journal
|
|
27
|
+
if (needsMigration) {
|
|
28
|
+
await writeJournal(journalPath, journal);
|
|
29
|
+
}
|
|
30
|
+
return journal;
|
|
9
31
|
}
|
|
10
32
|
export async function writeJournal(journalPath, journal) {
|
|
11
33
|
await ensureDir(dirname(journalPath));
|
package/dist/core/journal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/core/journal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"journal.js","sourceRoot":"","sources":["../../src/core/journal.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt user for input
|
|
3
|
+
* Returns empty string in non-interactive environments (CI/CD)
|
|
4
|
+
*/
|
|
5
|
+
export declare function promptUser(question: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Prompt user for yes/no confirmation
|
|
8
|
+
* Returns defaultValue in non-interactive environments
|
|
9
|
+
*/
|
|
10
|
+
export declare function promptYesNo(question: string, defaultYes?: boolean): boolean;
|
|
11
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/core/prompt.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAiBnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,UAAQ,GAAG,OAAO,CAUzE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import readlineSync from 'readline-sync';
|
|
2
|
+
/**
|
|
3
|
+
* Prompt user for input
|
|
4
|
+
* Returns empty string in non-interactive environments (CI/CD)
|
|
5
|
+
*/
|
|
6
|
+
export function promptUser(question) {
|
|
7
|
+
// Check if we're in a non-interactive environment
|
|
8
|
+
if (!process.stdin.isTTY) {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
// Check for CI environment variables
|
|
12
|
+
const isCI = process.env.CI === 'true' ||
|
|
13
|
+
process.env.GITHUB_ACTIONS === 'true' ||
|
|
14
|
+
process.env.GITLAB_CI === 'true' ||
|
|
15
|
+
process.env.CIRCLECI === 'true';
|
|
16
|
+
if (isCI) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
return readlineSync.question(question);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Prompt user for yes/no confirmation
|
|
23
|
+
* Returns defaultValue in non-interactive environments
|
|
24
|
+
*/
|
|
25
|
+
export function promptYesNo(question, defaultYes = false) {
|
|
26
|
+
const answer = promptUser(question);
|
|
27
|
+
// Empty answer (non-interactive or just pressed enter)
|
|
28
|
+
if (answer === '') {
|
|
29
|
+
return defaultYes;
|
|
30
|
+
}
|
|
31
|
+
const normalized = answer.toLowerCase().trim();
|
|
32
|
+
return normalized === 'y' || normalized === 'yes';
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/core/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,kDAAkD;IAClD,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,qCAAqC;IACrC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,MAAM;QACzB,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,MAAM;QACrC,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,MAAM;QAChC,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,CAAC;IAE7C,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACzC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB,EAAE,UAAU,GAAG,KAAK;IAC9D,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAEpC,uDAAuD;IACvD,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAClB,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC/C,OAAO,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,CAAC;AACpD,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import pkg from '../package.json'
|
|
3
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
4
4
|
import { addCommand } from './commands/add.js';
|
|
5
5
|
import { removeCommand } from './commands/remove.js';
|
|
6
6
|
import { listCommand } from './commands/list.js';
|
|
@@ -8,6 +8,8 @@ import { doctorCommand } from './commands/doctor.js';
|
|
|
8
8
|
import { loginCommand } from './commands/login.js';
|
|
9
9
|
import { devicesCommand } from './commands/devices.js';
|
|
10
10
|
import { logoutCommand } from './commands/logout.js';
|
|
11
|
+
import { statusCommand } from './commands/status.js';
|
|
12
|
+
import { checklistCommand } from './commands/checklist.js';
|
|
11
13
|
const program = new Command();
|
|
12
14
|
program
|
|
13
15
|
.name('vf')
|
|
@@ -20,5 +22,7 @@ program.addCommand(doctorCommand);
|
|
|
20
22
|
program.addCommand(listCommand);
|
|
21
23
|
program.addCommand(addCommand);
|
|
22
24
|
program.addCommand(removeCommand);
|
|
25
|
+
program.addCommand(statusCommand);
|
|
26
|
+
program.addCommand(checklistCommand);
|
|
23
27
|
program.parse();
|
|
24
28
|
//# 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,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,GAAG,MAAM,iBAAiB,CAAC,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,IAAI,CAAC;KACV,WAAW,CAAC,oDAAoD,CAAC;KACjE,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC;AAEnC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AAErC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibefast-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "CLI for installing VibeFast features into your monorepo",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,10 +24,12 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"commander": "^12.0.0",
|
|
26
26
|
"picocolors": "^1.0.0",
|
|
27
|
+
"readline-sync": "^1.4.10",
|
|
27
28
|
"yauzl": "^2.10.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/node": "^20.11.0",
|
|
32
|
+
"@types/readline-sync": "^1.4.8",
|
|
31
33
|
"@types/yauzl": "^2.10.3",
|
|
32
34
|
"tsx": "^4.7.0",
|
|
33
35
|
"typescript": "^5.3.3",
|
package/src/commands/add.ts
CHANGED
|
@@ -4,20 +4,42 @@ import { getPaths } from '../core/paths.js';
|
|
|
4
4
|
import { validateSignature, validateTarget } from '../core/validate.js';
|
|
5
5
|
import { getToken, getDeviceInfo } from '../core/auth.js';
|
|
6
6
|
import { fetchRecipe, downloadZip } from '../core/http.js';
|
|
7
|
-
import { addEntry, getEntry } from '../core/journal.js';
|
|
7
|
+
import { addEntry, getEntry, FileEntry } from '../core/journal.js';
|
|
8
8
|
import { copyTree, readFileContent } from '../core/fsx.js';
|
|
9
9
|
import { insertNavLinkNative, insertNavLinkWeb } from '../core/codemod.js';
|
|
10
10
|
import { join, resolve } from 'path';
|
|
11
11
|
import { ensureWithinBase } from '../core/pathGuard.js';
|
|
12
12
|
import { extractZipSafe } from '../core/archive.js';
|
|
13
|
+
import { hashFiles } from '../core/hash.js';
|
|
14
|
+
import { promptYesNo } from '../core/prompt.js';
|
|
13
15
|
|
|
14
16
|
interface RecipeManifest {
|
|
15
17
|
name: string;
|
|
16
18
|
version: string;
|
|
17
19
|
description: string;
|
|
18
20
|
copy: Array<{ from: string; to: string }>;
|
|
19
|
-
nav
|
|
21
|
+
nav?: { href: string; label: string };
|
|
20
22
|
target: 'native' | 'web';
|
|
23
|
+
dependencies?: {
|
|
24
|
+
expo?: string[];
|
|
25
|
+
npm?: string[];
|
|
26
|
+
};
|
|
27
|
+
env?: Array<{
|
|
28
|
+
key: string;
|
|
29
|
+
description: string;
|
|
30
|
+
example: string;
|
|
31
|
+
link?: string;
|
|
32
|
+
}>;
|
|
33
|
+
manualSteps?: Array<{
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
link?: string;
|
|
37
|
+
file?: string;
|
|
38
|
+
content?: string;
|
|
39
|
+
}>;
|
|
40
|
+
postInstall?: {
|
|
41
|
+
message?: string;
|
|
42
|
+
};
|
|
21
43
|
}
|
|
22
44
|
|
|
23
45
|
export const addCommand = new Command('add')
|
|
@@ -25,7 +47,8 @@ export const addCommand = new Command('add')
|
|
|
25
47
|
.argument('<feature>', 'Feature name to install')
|
|
26
48
|
.option('--target <target>', 'Target platform (native or web)', 'native')
|
|
27
49
|
.option('--dry-run', 'Preview changes without applying')
|
|
28
|
-
.option('--force', 'Overwrite existing files')
|
|
50
|
+
.option('--force', 'Overwrite existing files without asking')
|
|
51
|
+
.option('--yes', 'Answer yes to all prompts (for automation)')
|
|
29
52
|
.action(async (feature: string, options) => {
|
|
30
53
|
try {
|
|
31
54
|
const paths = getPaths();
|
|
@@ -66,10 +89,76 @@ export const addCommand = new Command('add')
|
|
|
66
89
|
});
|
|
67
90
|
|
|
68
91
|
if (!response.ok || (!response.signedUrl && !response.zipData)) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
92
|
+
const error = response.error || 'Unknown error';
|
|
93
|
+
const message = response.message || '';
|
|
94
|
+
|
|
95
|
+
log.plain('');
|
|
96
|
+
log.error('Failed to fetch recipe');
|
|
97
|
+
log.plain('');
|
|
98
|
+
|
|
99
|
+
// User-friendly error messages
|
|
100
|
+
if (error.includes('Invalid') || error.includes('token') || error.includes('license')) {
|
|
101
|
+
log.plain('❌ Invalid or expired license key');
|
|
102
|
+
log.plain('');
|
|
103
|
+
log.info('Your license key may be:');
|
|
104
|
+
log.plain(' • Incorrect or mistyped');
|
|
105
|
+
log.plain(' • Expired');
|
|
106
|
+
log.plain(' • Revoked');
|
|
107
|
+
log.plain('');
|
|
108
|
+
log.info('To fix this:');
|
|
109
|
+
log.plain(' 1. Check your license key from your purchase receipt');
|
|
110
|
+
log.plain(' 2. Run: vf logout');
|
|
111
|
+
log.plain(' 3. Run: vf login --token YOUR_CORRECT_TOKEN');
|
|
112
|
+
log.plain('');
|
|
113
|
+
log.info('Need help? Contact support@vibefast.pro');
|
|
114
|
+
} else if (error.includes('Device limit') || error.includes('device') || message.includes('device')) {
|
|
115
|
+
log.plain('❌ Device limit reached');
|
|
116
|
+
log.plain('');
|
|
117
|
+
log.info('You have reached the maximum number of devices for your license');
|
|
118
|
+
log.plain('');
|
|
119
|
+
log.info('To fix this:');
|
|
120
|
+
log.plain(' 1. Run: vf devices');
|
|
121
|
+
log.plain(' 2. Deactivate an unused device: vf devices --deactivate <device-id>');
|
|
122
|
+
log.plain(' 3. Try again: vf add ' + feature);
|
|
123
|
+
log.plain('');
|
|
124
|
+
if (message) {
|
|
125
|
+
log.plain(`Details: ${message}`);
|
|
126
|
+
log.plain('');
|
|
127
|
+
}
|
|
128
|
+
} else if (error.includes('Network') || error.includes('connect')) {
|
|
129
|
+
log.plain('❌ Network error');
|
|
130
|
+
log.plain('');
|
|
131
|
+
log.info('Could not connect to VibeFast servers');
|
|
132
|
+
log.plain('');
|
|
133
|
+
log.info('Please check:');
|
|
134
|
+
log.plain(' • Your internet connection');
|
|
135
|
+
log.plain(' • Firewall settings');
|
|
136
|
+
log.plain(' • VPN configuration');
|
|
137
|
+
log.plain('');
|
|
138
|
+
if (message) {
|
|
139
|
+
log.plain(`Details: ${message}`);
|
|
140
|
+
log.plain('');
|
|
141
|
+
}
|
|
142
|
+
} else if (error.includes('not found') || error.includes('404')) {
|
|
143
|
+
log.plain('❌ Feature not found');
|
|
144
|
+
log.plain('');
|
|
145
|
+
log.info(`The feature "${feature}" does not exist or is not available for ${target}`);
|
|
146
|
+
log.plain('');
|
|
147
|
+
log.info('To see available features:');
|
|
148
|
+
log.plain(' vf list');
|
|
149
|
+
log.plain('');
|
|
150
|
+
} else {
|
|
151
|
+
// Generic error
|
|
152
|
+
log.plain(`❌ ${error}`);
|
|
153
|
+
if (message) {
|
|
154
|
+
log.plain('');
|
|
155
|
+
log.plain(`Details: ${message}`);
|
|
156
|
+
}
|
|
157
|
+
log.plain('');
|
|
158
|
+
log.info('If this problem persists, contact support@vibefast.pro');
|
|
72
159
|
}
|
|
160
|
+
|
|
161
|
+
log.plain('');
|
|
73
162
|
process.exit(1);
|
|
74
163
|
}
|
|
75
164
|
|
|
@@ -96,8 +185,11 @@ export const addCommand = new Command('add')
|
|
|
96
185
|
|
|
97
186
|
log.info(`Installing ${manifest.name} v${manifest.version}...`);
|
|
98
187
|
|
|
99
|
-
// Copy files
|
|
188
|
+
// Copy files with interactive confirmation
|
|
100
189
|
const copiedFiles: string[] = [];
|
|
190
|
+
const allConflicts: string[] = [];
|
|
191
|
+
const allSkipped: string[] = [];
|
|
192
|
+
|
|
101
193
|
for (const copySpec of manifest.copy) {
|
|
102
194
|
const srcPath = ensureWithinBase(
|
|
103
195
|
extractRoot,
|
|
@@ -111,11 +203,35 @@ export const addCommand = new Command('add')
|
|
|
111
203
|
);
|
|
112
204
|
|
|
113
205
|
log.info(`Copying ${copySpec.from} → ${copySpec.to}`);
|
|
114
|
-
const
|
|
206
|
+
const result = await copyTree(srcPath, destPath, {
|
|
115
207
|
dryRun: options.dryRun,
|
|
116
208
|
force: options.force,
|
|
209
|
+
interactive: !options.force && !options.dryRun && !options.yes,
|
|
117
210
|
});
|
|
118
|
-
|
|
211
|
+
|
|
212
|
+
copiedFiles.push(...result.files);
|
|
213
|
+
allConflicts.push(...result.conflicts);
|
|
214
|
+
allSkipped.push(...result.skipped);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Show conflict warnings in dry-run mode
|
|
218
|
+
if (options.dryRun && allConflicts.length > 0) {
|
|
219
|
+
log.plain('');
|
|
220
|
+
log.warn(`⚠ ${allConflicts.length} file(s) will be overwritten:`);
|
|
221
|
+
allConflicts.slice(0, 5).forEach(f => {
|
|
222
|
+
const relativePath = f.replace(paths.cwd + '/', '');
|
|
223
|
+
log.plain(` • ${relativePath}`);
|
|
224
|
+
});
|
|
225
|
+
if (allConflicts.length > 5) {
|
|
226
|
+
log.plain(` ... and ${allConflicts.length - 5} more`);
|
|
227
|
+
}
|
|
228
|
+
log.warn('⚠ Make sure you have committed your changes to Git!');
|
|
229
|
+
log.plain('');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Show skipped files
|
|
233
|
+
if (allSkipped.length > 0) {
|
|
234
|
+
log.info(`ℹ Skipped ${allSkipped.length} file(s) (you chose not to overwrite)`);
|
|
119
235
|
}
|
|
120
236
|
|
|
121
237
|
// Add watermark if provided
|
|
@@ -149,29 +265,131 @@ export const addCommand = new Command('add')
|
|
|
149
265
|
}
|
|
150
266
|
}
|
|
151
267
|
|
|
152
|
-
//
|
|
268
|
+
// Hash files and update journal
|
|
153
269
|
if (!options.dryRun) {
|
|
270
|
+
log.info('Computing file hashes...');
|
|
271
|
+
const fileHashes = await hashFiles(copiedFiles, { showProgress: copiedFiles.length > 20 });
|
|
272
|
+
|
|
273
|
+
const fileEntries: FileEntry[] = Array.from(fileHashes.entries()).map(([path, hash]) => ({
|
|
274
|
+
path,
|
|
275
|
+
hash,
|
|
276
|
+
}));
|
|
277
|
+
|
|
154
278
|
await addEntry(paths.journalFile, {
|
|
155
279
|
feature: manifest.name,
|
|
156
280
|
target: manifest.target,
|
|
157
|
-
files:
|
|
281
|
+
files: fileEntries,
|
|
158
282
|
insertedNav: navInserted,
|
|
159
283
|
navHref,
|
|
160
284
|
navLabel,
|
|
161
285
|
ts: Date.now(),
|
|
286
|
+
manifest: {
|
|
287
|
+
version: manifest.version,
|
|
288
|
+
manualSteps: manifest.manualSteps,
|
|
289
|
+
env: manifest.env,
|
|
290
|
+
},
|
|
162
291
|
});
|
|
163
292
|
}
|
|
164
293
|
|
|
165
294
|
log.success(`${manifest.name} installed successfully!`);
|
|
166
295
|
log.info(`Files added: ${copiedFiles.length}`);
|
|
167
296
|
|
|
297
|
+
// Show required packages
|
|
298
|
+
if (manifest.dependencies) {
|
|
299
|
+
log.plain('');
|
|
300
|
+
log.warn('⚠ This feature requires additional packages');
|
|
301
|
+
log.plain('');
|
|
302
|
+
|
|
303
|
+
if (manifest.target === 'native' && manifest.dependencies.expo) {
|
|
304
|
+
const packages = manifest.dependencies.expo;
|
|
305
|
+
|
|
306
|
+
log.info('📦 Required packages:');
|
|
307
|
+
packages.forEach(pkg => {
|
|
308
|
+
log.plain(` • ${pkg}`);
|
|
309
|
+
});
|
|
310
|
+
log.plain('');
|
|
311
|
+
|
|
312
|
+
log.info('Install with:');
|
|
313
|
+
log.plain(` npx expo install ${packages.join(' ')}`);
|
|
314
|
+
log.plain('');
|
|
315
|
+
log.info('💡 Expo will automatically pick compatible versions');
|
|
316
|
+
|
|
317
|
+
} else if (manifest.target === 'web' && manifest.dependencies.npm) {
|
|
318
|
+
const packages = manifest.dependencies.npm;
|
|
319
|
+
|
|
320
|
+
log.info('📦 Required packages:');
|
|
321
|
+
packages.forEach(pkg => {
|
|
322
|
+
log.plain(` • ${pkg}`);
|
|
323
|
+
});
|
|
324
|
+
log.plain('');
|
|
325
|
+
|
|
326
|
+
log.info('Install with:');
|
|
327
|
+
log.plain(` pnpm add ${packages.join(' ')}`);
|
|
328
|
+
log.plain(' OR');
|
|
329
|
+
log.plain(` yarn add ${packages.join(' ')}`);
|
|
330
|
+
log.plain(` OR`);
|
|
331
|
+
log.plain(` npm install ${packages.join(' ')}`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
log.plain('');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Show manual steps
|
|
338
|
+
if (manifest.manualSteps && !options.dryRun) {
|
|
339
|
+
log.plain('');
|
|
340
|
+
log.warn('⚠ MANUAL STEPS REQUIRED:');
|
|
341
|
+
log.plain('');
|
|
342
|
+
log.plain('This feature requires some manual configuration:');
|
|
343
|
+
log.plain('');
|
|
344
|
+
|
|
345
|
+
manifest.manualSteps.forEach((step, index) => {
|
|
346
|
+
log.plain(`Step ${index + 1}: ${step.title}`);
|
|
347
|
+
log.plain(` ${step.description}`);
|
|
348
|
+
if (step.link) {
|
|
349
|
+
log.plain(` 🔗 ${step.link}`);
|
|
350
|
+
}
|
|
351
|
+
if (step.file) {
|
|
352
|
+
log.plain(` 📝 File: ${step.file}`);
|
|
353
|
+
}
|
|
354
|
+
if (step.content) {
|
|
355
|
+
log.plain(` Add: ${step.content}`);
|
|
356
|
+
}
|
|
357
|
+
log.plain('');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
log.info(`💡 Run 'vf checklist ${manifest.name}' to see these steps again`);
|
|
361
|
+
log.plain('');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Show environment variables
|
|
365
|
+
if (manifest.env && !options.dryRun) {
|
|
366
|
+
log.plain('');
|
|
367
|
+
log.warn('⚠ REQUIRED ENVIRONMENT VARIABLES:');
|
|
368
|
+
log.plain('');
|
|
369
|
+
|
|
370
|
+
manifest.env.forEach(envVar => {
|
|
371
|
+
log.plain(` ${envVar.key}`);
|
|
372
|
+
log.plain(` ${envVar.description}`);
|
|
373
|
+
log.plain(` Example: ${envVar.example}`);
|
|
374
|
+
if (envVar.link) {
|
|
375
|
+
log.plain(` Get it: ${envVar.link}`);
|
|
376
|
+
}
|
|
377
|
+
log.plain('');
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
log.info('Add these to your .env file');
|
|
381
|
+
log.plain('');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Post-install message
|
|
385
|
+
if (manifest.postInstall?.message && !options.dryRun) {
|
|
386
|
+
log.plain('');
|
|
387
|
+
log.info(manifest.postInstall.message);
|
|
388
|
+
}
|
|
389
|
+
|
|
168
390
|
if (options.dryRun) {
|
|
391
|
+
log.plain('');
|
|
169
392
|
log.warn('This was a dry run. Run without --dry-run to apply changes.');
|
|
170
|
-
} else {
|
|
171
|
-
log.info('Next steps:');
|
|
172
|
-
log.plain(` 1. Review the changes in your repo`);
|
|
173
|
-
log.plain(` 2. Run your dev server to test`);
|
|
174
|
-
log.plain(` 3. Navigate to the new feature`);
|
|
175
393
|
}
|
|
176
394
|
} catch (error: any) {
|
|
177
395
|
log.error(`Installation failed: ${error.message}`);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { log } from '../core/log.js';
|
|
3
|
+
import { getPaths } from '../core/paths.js';
|
|
4
|
+
import { getEntry } from '../core/journal.js';
|
|
5
|
+
|
|
6
|
+
export const checklistCommand = new Command('checklist')
|
|
7
|
+
.description('Show manual setup steps for an installed feature')
|
|
8
|
+
.argument('<feature>', 'Feature name')
|
|
9
|
+
.option('--target <target>', 'Target platform (native or web)', 'native')
|
|
10
|
+
.action(async (feature: string, options) => {
|
|
11
|
+
try {
|
|
12
|
+
const paths = getPaths();
|
|
13
|
+
const target = options.target as 'native' | 'web';
|
|
14
|
+
|
|
15
|
+
// Check if feature is installed
|
|
16
|
+
const entry = await getEntry(paths.journalFile, feature, target);
|
|
17
|
+
if (!entry) {
|
|
18
|
+
log.error(`${feature} is not installed for ${target}`);
|
|
19
|
+
log.info('Run "vf status" to see installed features');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Check if feature has manual steps
|
|
24
|
+
if (!entry.manifest?.manualSteps || entry.manifest.manualSteps.length === 0) {
|
|
25
|
+
log.info(`${feature} has no manual setup steps`);
|
|
26
|
+
log.success('This feature is ready to use!');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Display manual steps
|
|
31
|
+
log.info(`Manual setup steps for ${feature}:`);
|
|
32
|
+
log.plain('');
|
|
33
|
+
|
|
34
|
+
entry.manifest.manualSteps.forEach((step: any, index: number) => {
|
|
35
|
+
log.plain(`Step ${index + 1}: ${step.title}`);
|
|
36
|
+
log.plain(` ${step.description}`);
|
|
37
|
+
if (step.link) {
|
|
38
|
+
log.plain(` 🔗 ${step.link}`);
|
|
39
|
+
}
|
|
40
|
+
if (step.file) {
|
|
41
|
+
log.plain(` 📝 File: ${step.file}`);
|
|
42
|
+
}
|
|
43
|
+
if (step.content) {
|
|
44
|
+
log.plain(` Add: ${step.content}`);
|
|
45
|
+
}
|
|
46
|
+
log.plain('');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Display environment variables if any
|
|
50
|
+
if (entry.manifest.env && entry.manifest.env.length > 0) {
|
|
51
|
+
log.warn('⚠ REQUIRED ENVIRONMENT VARIABLES:');
|
|
52
|
+
log.plain('');
|
|
53
|
+
|
|
54
|
+
entry.manifest.env.forEach((envVar: any) => {
|
|
55
|
+
log.plain(` ${envVar.key}`);
|
|
56
|
+
log.plain(` ${envVar.description}`);
|
|
57
|
+
log.plain(` Example: ${envVar.example}`);
|
|
58
|
+
if (envVar.link) {
|
|
59
|
+
log.plain(` Get it: ${envVar.link}`);
|
|
60
|
+
}
|
|
61
|
+
log.plain('');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
log.info('Add these to your .env file');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
} catch (error: any) {
|
|
68
|
+
log.error(`Failed to show checklist: ${error.message}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|