vibefast-cli 0.7.7 → 0.7.10
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/dist/commands/add.js +209 -173
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/checklist.d.ts.map +1 -1
- package/dist/commands/checklist.js +7 -2
- package/dist/commands/checklist.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +70 -37
- package/dist/commands/init.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/add.ts +235 -196
- package/src/commands/checklist.ts +10 -2
- package/src/commands/init.ts +82 -44
package/src/commands/add.ts
CHANGED
|
@@ -536,197 +536,231 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
536
536
|
}
|
|
537
537
|
}
|
|
538
538
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
{
|
|
554
|
-
|
|
539
|
+
const fallbackZip = await getBundledRecipeZipPath(feature);
|
|
540
|
+
let attemptedFallback = false;
|
|
541
|
+
let installedManifest: (RecipeManifest & { files?: { source: string; destination: string }[] }) | null = null;
|
|
542
|
+
let installedEnvGroups: EnvGroup[] = [];
|
|
543
|
+
let installedEnvAttention: string[] = [];
|
|
544
|
+
let installedCopiedFiles: string[] = [];
|
|
545
|
+
let installedNavInserted = false;
|
|
546
|
+
let installedNavHref: string | undefined;
|
|
547
|
+
let installedNavLabel: string | undefined;
|
|
548
|
+
|
|
549
|
+
// Everything below runs inside a retry loop so we can fall back to bundled recipes
|
|
550
|
+
retry_install: while (true) {
|
|
551
|
+
try {
|
|
552
|
+
// Download and extract (or use bundled zip)
|
|
553
|
+
if (!zipPath) {
|
|
554
|
+
const result = await withSpinner(
|
|
555
|
+
'Downloading and extracting recipe...',
|
|
556
|
+
async () => {
|
|
557
|
+
const zip = response.zipData
|
|
558
|
+
? await downloadZip(response.zipData, true)
|
|
559
|
+
: await downloadZip(response.signedUrl!);
|
|
560
|
+
|
|
561
|
+
const dir = join(tmpdir(), 'vibefast', randomUUID());
|
|
562
|
+
await extractZipSafe(zip, dir);
|
|
563
|
+
|
|
564
|
+
return { zipPath: zip, extractDir: dir };
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
successText: '✓ Recipe downloaded',
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
zipPath = result.zipPath;
|
|
571
|
+
extractDir = result.extractDir;
|
|
572
|
+
} else {
|
|
573
|
+
// Bundled zip path - extract to temp location
|
|
574
|
+
extractDir = join(tmpdir(), 'vibefast', randomUUID());
|
|
575
|
+
await extractZipSafe(zipPath, extractDir);
|
|
555
576
|
}
|
|
556
|
-
);
|
|
557
|
-
zipPath = result.zipPath;
|
|
558
|
-
extractDir = result.extractDir;
|
|
559
|
-
} else {
|
|
560
|
-
// Bundled zip path - extract to temp location
|
|
561
|
-
extractDir = join(tmpdir(), 'vibefast', randomUUID());
|
|
562
|
-
await extractZipSafe(zipPath, extractDir);
|
|
563
|
-
}
|
|
564
577
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
578
|
+
// Locate manifest (some archives may nest recipe.json)
|
|
579
|
+
const findManifest = async (dir: string): Promise<string | null> => {
|
|
580
|
+
const entries = await import('fs/promises').then((m) =>
|
|
581
|
+
m.readdir(dir, { withFileTypes: true }),
|
|
582
|
+
);
|
|
583
|
+
for (const entry of entries) {
|
|
584
|
+
const full = join(dir, entry.name);
|
|
585
|
+
if (entry.isFile() && entry.name === 'recipe.json') return full;
|
|
586
|
+
if (entry.isDirectory()) {
|
|
587
|
+
const found = await findManifest(full);
|
|
588
|
+
if (found) return found;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
let manifestPath = join(extractDir, 'recipe.json');
|
|
595
|
+
if (!(await exists(manifestPath))) {
|
|
596
|
+
const found = await findManifest(extractDir);
|
|
597
|
+
if (!found) {
|
|
598
|
+
throw new Error(`recipe.json not found in ${extractDir}`);
|
|
599
|
+
}
|
|
600
|
+
manifestPath = found;
|
|
576
601
|
}
|
|
577
|
-
}
|
|
578
|
-
return null;
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
let manifestPath = join(extractDir, 'recipe.json');
|
|
582
|
-
if (!(await exists(manifestPath))) {
|
|
583
|
-
const found = await findManifest(extractDir);
|
|
584
|
-
if (!found) {
|
|
585
|
-
throw new Error(`recipe.json not found in ${extractDir}`);
|
|
586
|
-
}
|
|
587
|
-
manifestPath = found;
|
|
588
|
-
}
|
|
589
602
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
603
|
+
const manifestContent = await readFileContent(manifestPath);
|
|
604
|
+
const manifest: RecipeManifest & { files?: { source: string; destination: string }[] } = JSON.parse(manifestContent);
|
|
605
|
+
if (!manifest.copy && Array.isArray(manifest.files)) {
|
|
606
|
+
manifest.copy = manifest.files.map((file) => ({
|
|
607
|
+
from: file.source,
|
|
608
|
+
to: file.destination,
|
|
609
|
+
}));
|
|
610
|
+
}
|
|
611
|
+
if (!Array.isArray(manifest.copy)) {
|
|
612
|
+
throw new Error('recipe.json is missing a valid "copy" array');
|
|
613
|
+
}
|
|
614
|
+
const extractRoot = resolve(manifestPath, '..');
|
|
615
|
+
const repoRoot = resolve(paths.cwd);
|
|
616
|
+
if (manifest.target !== target) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`Recipe target mismatch: expected ${target}, got ${manifest.target}`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
608
621
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
622
|
+
log.info(`Installing ${manifest.name} v${manifest.version}...`);
|
|
623
|
+
const envGroups = groupEnvVars(manifest.env, paths.cwd);
|
|
624
|
+
let envAttention: string[] = [];
|
|
612
625
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
// Show conflict warnings in dry-run mode
|
|
643
|
-
if (options.dryRun && allConflicts.length > 0) {
|
|
644
|
-
log.plain('');
|
|
645
|
-
log.warn(`⚠ ${allConflicts.length} file(s) will be overwritten:`);
|
|
646
|
-
allConflicts.slice(0, 5).forEach(f => {
|
|
647
|
-
const relativePath = f.replace(paths.cwd + '/', '');
|
|
648
|
-
log.plain(` • ${relativePath}`);
|
|
649
|
-
});
|
|
650
|
-
if (allConflicts.length > 5) {
|
|
651
|
-
log.plain(` ... and ${allConflicts.length - 5} more`);
|
|
652
|
-
}
|
|
653
|
-
log.warn('⚠ Make sure you have committed your changes to Git!');
|
|
654
|
-
log.plain('');
|
|
655
|
-
}
|
|
626
|
+
// Copy files with interactive confirmation
|
|
627
|
+
const copiedFiles: string[] = [];
|
|
628
|
+
const allConflicts: string[] = [];
|
|
629
|
+
const allSkipped: string[] = [];
|
|
630
|
+
|
|
631
|
+
for (const copySpec of manifest.copy) {
|
|
632
|
+
const srcPath = ensureWithinBase(
|
|
633
|
+
extractRoot,
|
|
634
|
+
resolve(extractRoot, copySpec.from),
|
|
635
|
+
`Recipe file ${copySpec.from}`
|
|
636
|
+
);
|
|
637
|
+
const destPath = ensureWithinBase(
|
|
638
|
+
repoRoot,
|
|
639
|
+
resolve(repoRoot, copySpec.to),
|
|
640
|
+
`Destination ${copySpec.to}`
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
log.info(`Copying ${copySpec.from} → ${copySpec.to}`);
|
|
644
|
+
const result = await copyTree(srcPath, destPath, {
|
|
645
|
+
dryRun: options.dryRun,
|
|
646
|
+
force: options.force,
|
|
647
|
+
interactive: !options.force && !options.dryRun && !options.yes,
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
copiedFiles.push(...result.files);
|
|
651
|
+
allConflicts.push(...result.conflicts);
|
|
652
|
+
allSkipped.push(...result.skipped);
|
|
653
|
+
}
|
|
656
654
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
655
|
+
// Show conflict warnings in dry-run mode
|
|
656
|
+
if (options.dryRun && allConflicts.length > 0) {
|
|
657
|
+
log.plain('');
|
|
658
|
+
log.warn(`⚠ ${allConflicts.length} file(s) will be overwritten:`);
|
|
659
|
+
allConflicts.slice(0, 5).forEach(f => {
|
|
660
|
+
const relativePath = f.replace(paths.cwd + '/', '');
|
|
661
|
+
log.plain(` • ${relativePath}`);
|
|
662
|
+
});
|
|
663
|
+
if (allConflicts.length > 5) {
|
|
664
|
+
log.plain(` ... and ${allConflicts.length - 5} more`);
|
|
665
|
+
}
|
|
666
|
+
log.warn('⚠ Make sure you have committed your changes to Git!');
|
|
667
|
+
log.plain('');
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Show skipped files
|
|
671
|
+
if (allSkipped.length > 0) {
|
|
672
|
+
log.info(`ℹ Skipped ${allSkipped.length} file(s) (you chose not to overwrite)`);
|
|
673
|
+
}
|
|
661
674
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
675
|
+
// Add watermark if provided
|
|
676
|
+
if (response.watermark && !options.dryRun) {
|
|
677
|
+
const { writeFileContent, readFileContent } = await import('../core/fsx.js');
|
|
678
|
+
for (const file of copiedFiles) {
|
|
679
|
+
if (file.endsWith('.ts') || file.endsWith('.tsx') || file.endsWith('.js') || file.endsWith('.jsx')) {
|
|
680
|
+
const content = await readFileContent(file);
|
|
681
|
+
const watermarked = `// vibefast license: ${response.watermark}\n${content}`;
|
|
682
|
+
await writeFileContent(file, watermarked, { force: true });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
670
685
|
}
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
686
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
687
|
+
// Insert nav link
|
|
688
|
+
let navInserted = false;
|
|
689
|
+
let navHref: string | undefined;
|
|
690
|
+
let navLabel: string | undefined;
|
|
691
|
+
if (manifest.nav) {
|
|
692
|
+
log.info('Adding navigation link...');
|
|
693
|
+
const navFile = target === 'native' ? paths.nativeNavFile : paths.webNavFile;
|
|
694
|
+
const insertFn = target === 'native' ? insertNavLinkNative : insertNavLinkWeb;
|
|
695
|
+
navHref = manifest.nav.href;
|
|
696
|
+
navLabel = manifest.nav.label;
|
|
697
|
+
|
|
698
|
+
navInserted = await insertFn(navFile, manifest.nav, { dryRun: options.dryRun });
|
|
699
|
+
if (navInserted) {
|
|
700
|
+
log.success('Navigation link added');
|
|
701
|
+
} else {
|
|
702
|
+
log.info('Navigation link already exists');
|
|
703
|
+
}
|
|
704
|
+
}
|
|
692
705
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
706
|
+
// Hash files and update journal
|
|
707
|
+
if (!options.dryRun) {
|
|
708
|
+
log.info('Computing file hashes...');
|
|
709
|
+
const fileHashes = await hashFiles(copiedFiles, { showProgress: copiedFiles.length > 20 });
|
|
710
|
+
|
|
711
|
+
const fileEntries: FileEntry[] = Array.from(fileHashes.entries()).map(([path, hash]) => ({
|
|
712
|
+
path,
|
|
713
|
+
hash,
|
|
714
|
+
}));
|
|
715
|
+
|
|
716
|
+
await addEntry(paths.journalFile, {
|
|
717
|
+
feature: manifest.name,
|
|
718
|
+
target: manifest.target,
|
|
719
|
+
files: fileEntries,
|
|
720
|
+
insertedNav: navInserted,
|
|
721
|
+
navHref,
|
|
722
|
+
navLabel,
|
|
723
|
+
ts: Date.now(),
|
|
724
|
+
manifest: {
|
|
725
|
+
version: manifest.version,
|
|
726
|
+
manualSteps: manifest.manualSteps,
|
|
727
|
+
env: manifest.env,
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
}
|
|
718
731
|
|
|
719
|
-
|
|
720
|
-
|
|
732
|
+
// capture results for post steps outside the loop
|
|
733
|
+
installedManifest = manifest;
|
|
734
|
+
installedEnvGroups = envGroups;
|
|
735
|
+
installedEnvAttention = envAttention;
|
|
736
|
+
installedCopiedFiles = copiedFiles;
|
|
737
|
+
installedNavInserted = navInserted;
|
|
738
|
+
installedNavHref = navHref;
|
|
739
|
+
installedNavLabel = navLabel;
|
|
740
|
+
|
|
741
|
+
log.success(`${manifest.name} installed successfully!`);
|
|
742
|
+
log.info(`Files added: ${copiedFiles.length}`);
|
|
743
|
+
break retry_install;
|
|
744
|
+
} catch (err: any) {
|
|
745
|
+
if (!attemptedFallback && fallbackZip) {
|
|
746
|
+
attemptedFallback = true;
|
|
747
|
+
zipPath = fallbackZip;
|
|
748
|
+
extractDir = null;
|
|
749
|
+
log.warn(`⚠ Encountered error "${err.message}". Retrying with bundled recipe...`);
|
|
750
|
+
continue retry_install;
|
|
751
|
+
}
|
|
752
|
+
throw err;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
721
755
|
|
|
722
756
|
// Auto-install dependencies
|
|
723
|
-
if (
|
|
757
|
+
if (installedManifest && installedManifest.dependencies && !options.dryRun) {
|
|
724
758
|
log.plain('');
|
|
725
759
|
log.warn('⚠ This feature requires additional packages');
|
|
726
760
|
log.plain('');
|
|
727
761
|
|
|
728
|
-
if (
|
|
729
|
-
const packages =
|
|
762
|
+
if (installedManifest.target === 'native' && installedManifest.dependencies.expo) {
|
|
763
|
+
const packages = installedManifest.dependencies.expo;
|
|
730
764
|
|
|
731
765
|
log.info('📦 Required packages:');
|
|
732
766
|
packages.forEach(pkg => {
|
|
@@ -760,8 +794,8 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
760
794
|
log.info('💡 Expo will automatically pick compatible versions');
|
|
761
795
|
}
|
|
762
796
|
|
|
763
|
-
} else if (
|
|
764
|
-
const packages =
|
|
797
|
+
} else if (installedManifest.target === 'web' && installedManifest.dependencies.npm) {
|
|
798
|
+
const packages = installedManifest.dependencies.npm;
|
|
765
799
|
|
|
766
800
|
log.info('📦 Required packages:');
|
|
767
801
|
packages.forEach(pkg => {
|
|
@@ -808,7 +842,7 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
808
842
|
}
|
|
809
843
|
|
|
810
844
|
// Auto-setup Vosk model for wake-word
|
|
811
|
-
if (
|
|
845
|
+
if (installedManifest?.name === 'wake-word' && !options.dryRun) {
|
|
812
846
|
const { setupVoskModel } = await import('../core/vosk.js');
|
|
813
847
|
log.plain('');
|
|
814
848
|
await withSpinner(
|
|
@@ -822,20 +856,21 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
822
856
|
);
|
|
823
857
|
}
|
|
824
858
|
|
|
825
|
-
if (
|
|
826
|
-
await applyEnvConfiguration(paths,
|
|
859
|
+
if (installedManifest?.configuration?.env) {
|
|
860
|
+
await applyEnvConfiguration(paths, installedManifest.configuration.env, options);
|
|
827
861
|
}
|
|
828
862
|
|
|
829
863
|
// Show manual steps with smart detection
|
|
830
|
-
if (
|
|
864
|
+
if (installedManifest?.manualSteps && !options.dryRun) {
|
|
831
865
|
const { readFileContent, exists } = await import('../core/fsx.js');
|
|
832
866
|
const { join } = await import('path');
|
|
867
|
+
const normalize = (text: string) => text.replace(/\\n/g, '\n');
|
|
833
868
|
|
|
834
869
|
// Check which steps might already be done
|
|
835
|
-
const pendingSteps: typeof
|
|
870
|
+
const pendingSteps: typeof installedManifest.manualSteps = [];
|
|
836
871
|
const completedSteps: string[] = [];
|
|
837
872
|
|
|
838
|
-
for (const step of
|
|
873
|
+
for (const step of installedManifest.manualSteps) {
|
|
839
874
|
let alreadyDone = false;
|
|
840
875
|
|
|
841
876
|
// Check if file modification is already done
|
|
@@ -862,12 +897,10 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
862
897
|
log.plain('');
|
|
863
898
|
log.warn('⚠ MANUAL STEPS REQUIRED:');
|
|
864
899
|
log.plain('');
|
|
865
|
-
log.plain('This feature requires some manual configuration:');
|
|
866
|
-
log.plain('');
|
|
867
900
|
|
|
868
901
|
pendingSteps.forEach((step, index) => {
|
|
869
902
|
log.plain(`Step ${index + 1}: ${step.title}`);
|
|
870
|
-
log.plain(` ${step.description}`);
|
|
903
|
+
log.plain(` ${normalize(step.description || '')}`);
|
|
871
904
|
if (step.link) {
|
|
872
905
|
log.plain(` 🔗 ${step.link}`);
|
|
873
906
|
}
|
|
@@ -875,12 +908,18 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
875
908
|
log.plain(` 📝 File: ${step.file}`);
|
|
876
909
|
}
|
|
877
910
|
if (step.content) {
|
|
878
|
-
log.plain(
|
|
911
|
+
log.plain(' Add:');
|
|
912
|
+
log.plain(
|
|
913
|
+
normalize(step.content)
|
|
914
|
+
.split('\n')
|
|
915
|
+
.map(line => ` ${line}`)
|
|
916
|
+
.join('\n')
|
|
917
|
+
);
|
|
879
918
|
}
|
|
880
919
|
log.plain('');
|
|
881
920
|
});
|
|
882
921
|
|
|
883
|
-
log.info(`💡 Run 'vf checklist ${
|
|
922
|
+
log.info(`💡 Run 'vf checklist ${installedManifest.name}' to see these steps again`);
|
|
884
923
|
log.plain('');
|
|
885
924
|
}
|
|
886
925
|
|
|
@@ -890,8 +929,8 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
890
929
|
}
|
|
891
930
|
}
|
|
892
931
|
|
|
893
|
-
if (
|
|
894
|
-
const additions = await ensureEnvVarsForGroups(
|
|
932
|
+
if (installedEnvGroups.length > 0) {
|
|
933
|
+
const additions = await ensureEnvVarsForGroups(installedEnvGroups, options);
|
|
895
934
|
|
|
896
935
|
if (additions.length > 0) {
|
|
897
936
|
log.plain('');
|
|
@@ -904,29 +943,29 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
904
943
|
log.plain('');
|
|
905
944
|
}
|
|
906
945
|
|
|
907
|
-
|
|
946
|
+
installedEnvAttention = await reportEnvStatus(installedEnvGroups, options);
|
|
908
947
|
}
|
|
909
948
|
|
|
910
949
|
// Post-install message
|
|
911
|
-
if (
|
|
950
|
+
if (installedManifest?.postInstall?.message && !options.dryRun) {
|
|
912
951
|
log.plain('');
|
|
913
|
-
log.info(
|
|
952
|
+
log.info(installedManifest.postInstall.message);
|
|
914
953
|
}
|
|
915
954
|
|
|
916
955
|
// Final summary of what needs user attention
|
|
917
956
|
if (!options.dryRun) {
|
|
918
957
|
const needsAttention: string[] = [];
|
|
919
958
|
|
|
920
|
-
if (
|
|
921
|
-
needsAttention.push(...
|
|
959
|
+
if (installedEnvGroups.length > 0 && installedEnvAttention.length > 0) {
|
|
960
|
+
needsAttention.push(...installedEnvAttention);
|
|
922
961
|
}
|
|
923
962
|
|
|
924
963
|
// Check manual steps
|
|
925
|
-
if (
|
|
964
|
+
if (installedManifest?.manualSteps) {
|
|
926
965
|
const { readFileContent, exists } = await import('../core/fsx.js');
|
|
927
966
|
const { join } = await import('path');
|
|
928
967
|
|
|
929
|
-
for (const step of
|
|
968
|
+
for (const step of installedManifest.manualSteps) {
|
|
930
969
|
let alreadyDone = false;
|
|
931
970
|
|
|
932
971
|
if (step.file && step.content) {
|
|
@@ -958,7 +997,7 @@ async function applyEnvConfiguration(paths: ReturnType<typeof getPaths>, envConf
|
|
|
958
997
|
log.plain(` ${index + 1}. ${item}`);
|
|
959
998
|
});
|
|
960
999
|
log.plain('');
|
|
961
|
-
log.info(`💡 Run 'vf checklist ${
|
|
1000
|
+
log.info(`💡 Run 'vf checklist ${installedManifest?.name ?? feature}' for detailed instructions`);
|
|
962
1001
|
log.plain('');
|
|
963
1002
|
} else {
|
|
964
1003
|
log.plain('');
|
|
@@ -106,9 +106,11 @@ export const checklistCommand = new Command('checklist')
|
|
|
106
106
|
log.info(`Manual setup steps for ${feature}:`);
|
|
107
107
|
log.plain('');
|
|
108
108
|
|
|
109
|
+
const normalize = (text: string) => text.replace(/\\n/g, '\n');
|
|
110
|
+
|
|
109
111
|
entry.manifest.manualSteps.forEach((step: any, index: number) => {
|
|
110
112
|
log.plain(`Step ${index + 1}: ${step.title}`);
|
|
111
|
-
log.plain(` ${step.description}`);
|
|
113
|
+
log.plain(` ${normalize(step.description || '')}`);
|
|
112
114
|
if (step.link) {
|
|
113
115
|
log.plain(` 🔗 ${step.link}`);
|
|
114
116
|
}
|
|
@@ -116,7 +118,13 @@ export const checklistCommand = new Command('checklist')
|
|
|
116
118
|
log.plain(` 📝 File: ${step.file}`);
|
|
117
119
|
}
|
|
118
120
|
if (step.content) {
|
|
119
|
-
log.plain(
|
|
121
|
+
log.plain(' Add:');
|
|
122
|
+
log.plain(
|
|
123
|
+
normalize(step.content)
|
|
124
|
+
.split('\n')
|
|
125
|
+
.map((line: string) => ` ${line}`)
|
|
126
|
+
.join('\n')
|
|
127
|
+
);
|
|
120
128
|
}
|
|
121
129
|
log.plain('');
|
|
122
130
|
});
|