zerostart-cli 0.0.39 → 0.0.40
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/out/cli.js +152 -55
- package/out/managers/GitManager.js +17 -0
- package/package.json +1 -1
package/out/cli.js
CHANGED
|
@@ -75,6 +75,7 @@ function showGitHubTokenHelp() {
|
|
|
75
75
|
openUrl(tokenUrl);
|
|
76
76
|
}
|
|
77
77
|
async function initializeProject(name, language, type, options) {
|
|
78
|
+
// Returns the project path so the wizard can continue with deploy steps
|
|
78
79
|
const cwd = process.cwd();
|
|
79
80
|
const projectPath = path.join(cwd, name);
|
|
80
81
|
if (fs.existsSync(projectPath)) {
|
|
@@ -96,17 +97,23 @@ async function initializeProject(name, language, type, options) {
|
|
|
96
97
|
const gitHubService = options.githubToken ? new GitHubServiceCLI_1.GitHubServiceCLI(options.githubToken) : null;
|
|
97
98
|
if (!await gitManager.checkGitInstalled()) {
|
|
98
99
|
spinner.fail(chalk_1.default.red('Git is not installed!'));
|
|
99
|
-
return;
|
|
100
|
+
return projectPath;
|
|
100
101
|
}
|
|
102
|
+
// ── Step 1: Create project files ────────────────────────────────────
|
|
101
103
|
spinner.text = chalk_1.default.cyan('Generating project structure...');
|
|
102
104
|
await templateManager.createProjectStructure(config);
|
|
103
105
|
spinner.succeed(chalk_1.default.green('Project structure created'));
|
|
104
106
|
if (type !== types_1.ProjectType.DSAPractice) {
|
|
107
|
+
// ── Step 2: Git init + FIRST human commit ────────────────────────
|
|
105
108
|
spinner.start(chalk_1.default.cyan('Initializing Git repository...'));
|
|
106
109
|
await gitManager.init(projectPath);
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
// First commit: just README and .gitignore (base project skeleton)
|
|
111
|
+
await gitManager.commitSelective(projectPath, ['README.md', '.gitignore', 'roadmap.md'], 'feat: initialize project with ZeroStart CLI');
|
|
112
|
+
// Second commit: everything else (source files, config, etc.)
|
|
113
|
+
await gitManager.commit(projectPath, 'chore: add project structure and configuration files');
|
|
114
|
+
spinner.succeed(chalk_1.default.green('Git repository initialized (2 commits)'));
|
|
109
115
|
}
|
|
116
|
+
// ── Step 3: Create GitHub remote if requested ─────────────────────
|
|
110
117
|
if (options.createRemote) {
|
|
111
118
|
spinner.start(chalk_1.default.cyan('Creating GitHub repository...'));
|
|
112
119
|
let repoUrl;
|
|
@@ -122,16 +129,17 @@ async function initializeProject(name, language, type, options) {
|
|
|
122
129
|
if (options.authMethod !== 'GitHub CLI')
|
|
123
130
|
await gitManager.addRemote(projectPath, repoUrl);
|
|
124
131
|
await gitManager.push(projectPath);
|
|
125
|
-
spinner.succeed(chalk_1.default.green('Pushed to GitHub'));
|
|
132
|
+
spinner.succeed(chalk_1.default.green('Pushed to GitHub ✓'));
|
|
126
133
|
}
|
|
127
134
|
else {
|
|
128
|
-
spinner.warn(chalk_1.default.yellow('GitHub repository creation failed'));
|
|
135
|
+
spinner.warn(chalk_1.default.yellow('GitHub repository creation failed — continuing locally'));
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
console.log();
|
|
132
|
-
console.log(chalk_1.default.bold.green(' Success! Your project is ready!'));
|
|
139
|
+
console.log(chalk_1.default.bold.green(' ✅ Success! Your project is ready!'));
|
|
133
140
|
console.log(chalk_1.default.gray(' Location: ') + chalk_1.default.cyan(projectPath));
|
|
134
141
|
console.log();
|
|
142
|
+
// ── CP languages: open browser + interactive terminal ─────────────
|
|
135
143
|
if ([types_1.ProjectLanguage.Python, types_1.ProjectLanguage.Java, types_1.ProjectLanguage.CPP].includes(language)) {
|
|
136
144
|
const gdbLinks = {
|
|
137
145
|
[types_1.ProjectLanguage.Python]: 'https://www.onlinegdb.com/online_python_compiler',
|
|
@@ -143,7 +151,6 @@ async function initializeProject(name, language, type, options) {
|
|
|
143
151
|
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan(link));
|
|
144
152
|
console.log(chalk_1.default.gray(' (Opening in your browser...)'));
|
|
145
153
|
openUrl(link);
|
|
146
|
-
// Open a new terminal window with the language interpreter running
|
|
147
154
|
const terminalCmds = {
|
|
148
155
|
[types_1.ProjectLanguage.Python]: 'start cmd /k "python"',
|
|
149
156
|
[types_1.ProjectLanguage.Java]: `start cmd /k "cd /d ${projectPath} && javac src/main/java/com/example/Main.java && java -cp src/main/java com.example.Main"`,
|
|
@@ -155,17 +162,15 @@ async function initializeProject(name, language, type, options) {
|
|
|
155
162
|
(0, child_process_1.exec)(termCmd, { cwd: projectPath });
|
|
156
163
|
}
|
|
157
164
|
}
|
|
158
|
-
if (language === types_1.ProjectLanguage.HTMLCSS) {
|
|
159
|
-
console.log(chalk_1.default.bold.cyan('\n Deployment:'));
|
|
160
|
-
console.log(chalk_1.default.gray(' To deploy to Vercel, run: ') + chalk_1.default.white('zerostart deploy-vercel'));
|
|
161
|
-
}
|
|
162
165
|
console.log(chalk_1.default.bold('\n Get started:'));
|
|
163
166
|
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan(`cd ${name}`));
|
|
164
167
|
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan('code .') + chalk_1.default.gray(' (or your favorite editor)'));
|
|
165
168
|
console.log();
|
|
169
|
+
return projectPath;
|
|
166
170
|
}
|
|
167
171
|
catch (error) {
|
|
168
172
|
spinner.fail(chalk_1.default.red('Error: ' + error.message));
|
|
173
|
+
return projectPath;
|
|
169
174
|
}
|
|
170
175
|
}
|
|
171
176
|
program
|
|
@@ -575,12 +580,14 @@ shortcuts.forEach(s => {
|
|
|
575
580
|
});
|
|
576
581
|
});
|
|
577
582
|
async function startWizard(initialName) {
|
|
578
|
-
// Steps
|
|
579
|
-
// 1
|
|
580
|
-
// 2
|
|
581
|
-
// 3
|
|
582
|
-
// 4
|
|
583
|
-
// 5
|
|
583
|
+
// ── Wizard Steps ────────────────────────────────────────────────────────
|
|
584
|
+
// 1 → What are you building? (Web Dev / CP)
|
|
585
|
+
// 2 → Select Language
|
|
586
|
+
// 3 → Project Name
|
|
587
|
+
// 4 → [Web Dev only] Create GitHub repo? (Yes/No)
|
|
588
|
+
// 5 → [Web Dev + GitHub=Yes] Enter GitHub token
|
|
589
|
+
// 6 → [Web Dev only] Run locally OR Deploy to Vercel?
|
|
590
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
584
591
|
let step = 1;
|
|
585
592
|
let name = initialName;
|
|
586
593
|
let category;
|
|
@@ -590,9 +597,9 @@ async function startWizard(initialName) {
|
|
|
590
597
|
const BACK = '< Back';
|
|
591
598
|
const CAT_WEB = '🌐 Web Development (React, TS, HTML/CSS)';
|
|
592
599
|
const CAT_CP = '🏆 Competitive Programming (C++, Java, Python)';
|
|
593
|
-
while (step > 0 && step <=
|
|
600
|
+
while (step > 0 && step <= 6) {
|
|
601
|
+
// ── STEP 1: Category ────────────────────────────────────────────────
|
|
594
602
|
if (step === 1) {
|
|
595
|
-
// Step 1: Category Selection — shown FIRST
|
|
596
603
|
const ans = await inquirer_1.default.prompt([{
|
|
597
604
|
type: 'list',
|
|
598
605
|
name: 'category',
|
|
@@ -601,9 +608,9 @@ async function startWizard(initialName) {
|
|
|
601
608
|
}]);
|
|
602
609
|
category = ans.category;
|
|
603
610
|
step++;
|
|
611
|
+
// ── STEP 2: Language ────────────────────────────────────────────────
|
|
604
612
|
}
|
|
605
613
|
else if (step === 2) {
|
|
606
|
-
// Step 2: Language sub-selection
|
|
607
614
|
const langChoices = category === CAT_WEB
|
|
608
615
|
? [types_1.ProjectLanguage.React, types_1.ProjectLanguage.TypeScript, types_1.ProjectLanguage.HTMLCSS, new inquirer_1.default.Separator(), BACK]
|
|
609
616
|
: [types_1.ProjectLanguage.CPP, types_1.ProjectLanguage.Java, types_1.ProjectLanguage.Python, new inquirer_1.default.Separator(), BACK];
|
|
@@ -620,93 +627,183 @@ async function startWizard(initialName) {
|
|
|
620
627
|
language = langAns.language;
|
|
621
628
|
step++;
|
|
622
629
|
}
|
|
630
|
+
// ── STEP 3: Project Name ────────────────────────────────────────────
|
|
623
631
|
}
|
|
624
632
|
else if (step === 3) {
|
|
625
|
-
// Step 3: Project Name
|
|
626
633
|
if (!name) {
|
|
627
634
|
const ans = await inquirer_1.default.prompt([{
|
|
628
635
|
type: 'input',
|
|
629
636
|
name: 'name',
|
|
630
637
|
message: 'Project Name:',
|
|
631
|
-
default: 'my-project'
|
|
638
|
+
default: 'my-project',
|
|
639
|
+
validate: (v) => v.trim().length > 0 || 'Name cannot be empty'
|
|
632
640
|
}]);
|
|
633
|
-
name = ans.name;
|
|
641
|
+
name = ans.name.trim();
|
|
634
642
|
}
|
|
635
643
|
step++;
|
|
644
|
+
// ── STEP 4: GitHub repo? (Web Dev only) ────────────────────────────
|
|
636
645
|
}
|
|
637
646
|
else if (step === 4) {
|
|
638
|
-
|
|
639
|
-
if (language === types_1.ProjectLanguage.React || language === types_1.ProjectLanguage.TypeScript) {
|
|
647
|
+
if (category === CAT_WEB) {
|
|
640
648
|
const ans = await inquirer_1.default.prompt([{
|
|
641
649
|
type: 'list',
|
|
642
650
|
name: 'github',
|
|
643
|
-
message: '
|
|
644
|
-
choices: [
|
|
645
|
-
|
|
651
|
+
message: 'Create a GitHub repository for this project?',
|
|
652
|
+
choices: [
|
|
653
|
+
{ name: '✅ Yes — push to GitHub', value: 'yes' },
|
|
654
|
+
{ name: '❌ No — keep it local', value: 'no' },
|
|
655
|
+
new inquirer_1.default.Separator(),
|
|
656
|
+
{ name: BACK, value: 'back' }
|
|
657
|
+
],
|
|
658
|
+
default: 'yes'
|
|
646
659
|
}]);
|
|
647
|
-
if (ans.github ===
|
|
648
|
-
name = undefined;
|
|
660
|
+
if (ans.github === 'back') {
|
|
661
|
+
name = undefined;
|
|
649
662
|
step--;
|
|
650
663
|
}
|
|
651
664
|
else {
|
|
652
|
-
github = ans.github === '
|
|
665
|
+
github = ans.github === 'yes';
|
|
653
666
|
step++;
|
|
654
667
|
}
|
|
655
668
|
}
|
|
656
669
|
else {
|
|
670
|
+
// CP projects skip straight to done
|
|
657
671
|
github = false;
|
|
658
|
-
step
|
|
672
|
+
step = 7; // jump past all web dev steps
|
|
659
673
|
}
|
|
674
|
+
// ── STEP 5: GitHub Token ────────────────────────────────────────────
|
|
660
675
|
}
|
|
661
676
|
else if (step === 5) {
|
|
662
|
-
// Step 5: GitHub Token (only if GitHub = Yes)
|
|
663
677
|
if (github) {
|
|
664
678
|
showGitHubTokenHelp();
|
|
665
|
-
|
|
666
|
-
|
|
679
|
+
console.log(chalk_1.default.gray(' 💡 Tip: ') + chalk_1.default.white('Your token can be saved and reused for all future ZeroStart projects.'));
|
|
680
|
+
console.log(chalk_1.default.gray(' 💡 Tip: ') + chalk_1.default.white('Scopes needed: ') + chalk_1.default.cyan('repo'));
|
|
681
|
+
console.log();
|
|
682
|
+
const tokenAns = await inquirer_1.default.prompt([{
|
|
667
683
|
type: 'password',
|
|
668
684
|
name: 'token',
|
|
669
|
-
message: '
|
|
670
|
-
validate: (input) =>
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
685
|
+
message: 'Paste your GitHub Personal Access Token:',
|
|
686
|
+
validate: (input) => {
|
|
687
|
+
if (input.toLowerCase() === 'back')
|
|
688
|
+
return true;
|
|
689
|
+
if (input.trim().length < 10)
|
|
690
|
+
return 'That doesn\'t look like a valid token';
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
}]);
|
|
694
|
+
if (tokenAns.token.toLowerCase() === 'back') {
|
|
674
695
|
step--;
|
|
675
696
|
}
|
|
676
697
|
else {
|
|
677
|
-
|
|
678
|
-
|
|
698
|
+
// Validate token immediately
|
|
699
|
+
const spinner = (0, ora_1.default)({ text: 'Validating token...', color: 'cyan' }).start();
|
|
700
|
+
const svc = new GitHubServiceCLI_1.GitHubServiceCLI(tokenAns.token);
|
|
701
|
+
const user = await svc.validateToken();
|
|
702
|
+
if (user) {
|
|
703
|
+
spinner.succeed(chalk_1.default.green(`Token valid! Logged in as @${user.login}`));
|
|
704
|
+
githubToken = tokenAns.token;
|
|
705
|
+
step++;
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
spinner.fail(chalk_1.default.red('Invalid token or no internet. Please try again.'));
|
|
709
|
+
// Stay on step 5 to retry
|
|
710
|
+
}
|
|
679
711
|
}
|
|
680
712
|
}
|
|
681
713
|
else {
|
|
714
|
+
step++; // No GitHub — skip token step
|
|
715
|
+
}
|
|
716
|
+
// ── STEP 6: Run locally or Deploy to Vercel? (Web Dev only) ────────
|
|
717
|
+
}
|
|
718
|
+
else if (step === 6) {
|
|
719
|
+
const deployAns = await inquirer_1.default.prompt([{
|
|
720
|
+
type: 'list',
|
|
721
|
+
name: 'action',
|
|
722
|
+
message: 'What do you want to do next?',
|
|
723
|
+
choices: [
|
|
724
|
+
{ name: '🚀 Deploy to Vercel (live URL in seconds)', value: 'vercel' },
|
|
725
|
+
{ name: '💻 Run locally first (I\'ll deploy later)', value: 'local' },
|
|
726
|
+
new inquirer_1.default.Separator(),
|
|
727
|
+
{ name: BACK, value: 'back' }
|
|
728
|
+
],
|
|
729
|
+
default: 'local'
|
|
730
|
+
}]);
|
|
731
|
+
if (deployAns.action === 'back') {
|
|
732
|
+
step--;
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
// Store deploy choice and break out of loop
|
|
736
|
+
startWizard._deployChoice = deployAns.action;
|
|
682
737
|
step++;
|
|
683
738
|
}
|
|
684
739
|
}
|
|
685
740
|
}
|
|
686
|
-
|
|
741
|
+
// ── Execute the project creation ─────────────────────────────────────────
|
|
742
|
+
if ((step > 6 || step === 7) && name && language) {
|
|
687
743
|
let type = types_1.ProjectType.WebApp;
|
|
688
744
|
if (language === types_1.ProjectLanguage.TypeScript)
|
|
689
745
|
type = types_1.ProjectType.CLITool;
|
|
690
746
|
if (category === CAT_CP)
|
|
691
747
|
type = types_1.ProjectType.DSAPractice;
|
|
692
|
-
await initializeProject(name, language, type, {
|
|
748
|
+
const projectPath = await initializeProject(name, language, type, {
|
|
693
749
|
isPublic: false,
|
|
694
750
|
createRemote: !!githubToken,
|
|
695
|
-
githubToken
|
|
751
|
+
githubToken,
|
|
696
752
|
authMethod: 'none'
|
|
697
753
|
});
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
754
|
+
// ── Post-creation: Web Dev deploy / local run ─────────────────────
|
|
755
|
+
const deployChoice = startWizard._deployChoice;
|
|
756
|
+
delete startWizard._deployChoice;
|
|
757
|
+
if (category === CAT_WEB && deployChoice === 'vercel') {
|
|
758
|
+
console.log();
|
|
759
|
+
console.log(chalk_1.default.bold.cyan(' 🚀 Starting Vercel deployment...'));
|
|
760
|
+
console.log(chalk_1.default.gray(' This will be tagged as a demo deployment from ZeroStart CLI'));
|
|
761
|
+
console.log();
|
|
762
|
+
const vercelManager = new VercelManager_1.VercelManager();
|
|
763
|
+
const isInstalled = await vercelManager.checkVercelInstalled();
|
|
764
|
+
if (!isInstalled) {
|
|
765
|
+
const installSpinner = (0, ora_1.default)({ text: 'Installing Vercel CLI...', color: 'cyan' }).start();
|
|
766
|
+
await vercelManager.installGlobal();
|
|
767
|
+
installSpinner.succeed(chalk_1.default.green('Vercel CLI installed'));
|
|
768
|
+
}
|
|
769
|
+
const isAuthed = await vercelManager.checkAuth();
|
|
770
|
+
if (!isAuthed) {
|
|
771
|
+
console.log(chalk_1.default.yellow(' You need to log in to Vercel first. Opening login...'));
|
|
772
|
+
await vercelManager.login();
|
|
773
|
+
}
|
|
774
|
+
const deploySpinner = (0, ora_1.default)({ text: `Deploying ${name} to Vercel...`, color: 'cyan' }).start();
|
|
775
|
+
const url = await vercelManager.deploy(projectPath, name);
|
|
776
|
+
if (url) {
|
|
777
|
+
deploySpinner.succeed(chalk_1.default.green('Deployed successfully!'));
|
|
778
|
+
console.log();
|
|
779
|
+
console.log(chalk_1.default.bold(' 🌐 Live URL: ') + chalk_1.default.cyan.underline(url));
|
|
780
|
+
console.log(chalk_1.default.gray(' (Demo deployment via ZeroStart CLI — upgrade in Vercel dashboard)'));
|
|
781
|
+
openUrl(url);
|
|
782
|
+
}
|
|
783
|
+
else {
|
|
784
|
+
deploySpinner.fail(chalk_1.default.red('Deployment failed. Run ' + chalk_1.default.white('vercel') + ' manually from project folder.'));
|
|
708
785
|
}
|
|
709
786
|
}
|
|
787
|
+
else if (category === CAT_WEB && deployChoice === 'local') {
|
|
788
|
+
console.log();
|
|
789
|
+
console.log(chalk_1.default.bold.cyan(' 💻 Run your project locally:'));
|
|
790
|
+
if (language === types_1.ProjectLanguage.React || language === types_1.ProjectLanguage.TypeScript) {
|
|
791
|
+
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan(`cd ${name}`));
|
|
792
|
+
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan('npm install'));
|
|
793
|
+
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan('npm run dev'));
|
|
794
|
+
console.log(chalk_1.default.gray('\n Then open: ') + chalk_1.default.cyan('http://localhost:5173'));
|
|
795
|
+
}
|
|
796
|
+
else if (language === types_1.ProjectLanguage.HTMLCSS) {
|
|
797
|
+
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan(`cd ${name}`));
|
|
798
|
+
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan('Open index.html in your browser'));
|
|
799
|
+
console.log(chalk_1.default.gray(' - Or use: ') + chalk_1.default.cyan('npx serve .') + chalk_1.default.gray(' for a local server'));
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
console.log();
|
|
803
|
+
console.log(chalk_1.default.bold(' Get started:'));
|
|
804
|
+
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan(`cd ${name}`));
|
|
805
|
+
console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan('code .') + chalk_1.default.gray(' (or your favorite editor)'));
|
|
806
|
+
console.log();
|
|
710
807
|
}
|
|
711
808
|
}
|
|
712
809
|
// Main wizard
|
|
@@ -67,6 +67,23 @@ class GitManager {
|
|
|
67
67
|
console.warn(`Git commit failed (might be empty): ${error.message}`);
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Stage only specific files and commit them.
|
|
72
|
+
* Used for the first human-style commit (README, .gitignore etc.) before
|
|
73
|
+
* committing the rest of the project structure as a second commit.
|
|
74
|
+
*/
|
|
75
|
+
async commitSelective(cwd, files, message) {
|
|
76
|
+
try {
|
|
77
|
+
for (const file of files) {
|
|
78
|
+
// git add is silent if the file doesn't exist — that's fine
|
|
79
|
+
await exec(`git add "${file}"`, { cwd }).catch(() => { });
|
|
80
|
+
}
|
|
81
|
+
await exec(`git commit -m "${message}" --allow-empty`, { cwd });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.warn(`Selective git commit failed: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
70
87
|
async addRemote(cwd, url) {
|
|
71
88
|
try {
|
|
72
89
|
await exec(`git remote add origin ${url}`, { cwd });
|