zerostart-cli 0.0.38 → 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 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
- await gitManager.commit(projectPath, "Initial commit");
108
- spinner.succeed(chalk_1.default.green('Git repository initialized'));
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,18 +151,26 @@ 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
- }
147
- if (language === types_1.ProjectLanguage.HTMLCSS) {
148
- console.log(chalk_1.default.bold.cyan('\n Deployment:'));
149
- console.log(chalk_1.default.gray(' To deploy to Vercel, run: ') + chalk_1.default.white('zerostart deploy-vercel'));
154
+ const terminalCmds = {
155
+ [types_1.ProjectLanguage.Python]: 'start cmd /k "python"',
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"`,
157
+ [types_1.ProjectLanguage.CPP]: `start cmd /k "cd /d ${projectPath} && g++ main.cpp -o main && main"`,
158
+ };
159
+ const termCmd = terminalCmds[language];
160
+ if (termCmd) {
161
+ console.log(chalk_1.default.bold.yellow(' Opening interactive terminal...'));
162
+ (0, child_process_1.exec)(termCmd, { cwd: projectPath });
163
+ }
150
164
  }
151
165
  console.log(chalk_1.default.bold('\n Get started:'));
152
166
  console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan(`cd ${name}`));
153
167
  console.log(chalk_1.default.gray(' - ') + chalk_1.default.cyan('code .') + chalk_1.default.gray(' (or your favorite editor)'));
154
168
  console.log();
169
+ return projectPath;
155
170
  }
156
171
  catch (error) {
157
172
  spinner.fail(chalk_1.default.red('Error: ' + error.message));
173
+ return projectPath;
158
174
  }
159
175
  }
160
176
  program
@@ -564,6 +580,14 @@ shortcuts.forEach(s => {
564
580
  });
565
581
  });
566
582
  async function startWizard(initialName) {
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
+ // ────────────────────────────────────────────────────────────────────────
567
591
  let step = 1;
568
592
  let name = initialName;
569
593
  let category;
@@ -573,123 +597,213 @@ async function startWizard(initialName) {
573
597
  const BACK = '< Back';
574
598
  const CAT_WEB = '🌐 Web Development (React, TS, HTML/CSS)';
575
599
  const CAT_CP = '🏆 Competitive Programming (C++, Java, Python)';
576
- while (step > 0 && step <= 4) {
600
+ while (step > 0 && step <= 6) {
601
+ // ── STEP 1: Category ────────────────────────────────────────────────
577
602
  if (step === 1) {
578
- if (!name) {
579
- const ans = await inquirer_1.default.prompt([{
580
- type: 'input',
581
- name: 'name',
582
- message: 'Project Name:',
583
- default: 'my-project'
584
- }]);
585
- name = ans.name;
586
- }
587
- step++;
588
- }
589
- else if (step === 2) {
590
- // Category Selection
591
603
  const ans = await inquirer_1.default.prompt([{
592
604
  type: 'list',
593
605
  name: 'category',
594
606
  message: 'What are you building?',
595
- choices: [CAT_WEB, CAT_CP, new inquirer_1.default.Separator(), BACK]
607
+ choices: [CAT_WEB, CAT_CP]
596
608
  }]);
597
- if (ans.category === BACK) {
598
- name = undefined;
609
+ category = ans.category;
610
+ step++;
611
+ // ── STEP 2: Language ────────────────────────────────────────────────
612
+ }
613
+ else if (step === 2) {
614
+ const langChoices = category === CAT_WEB
615
+ ? [types_1.ProjectLanguage.React, types_1.ProjectLanguage.TypeScript, types_1.ProjectLanguage.HTMLCSS, new inquirer_1.default.Separator(), BACK]
616
+ : [types_1.ProjectLanguage.CPP, types_1.ProjectLanguage.Java, types_1.ProjectLanguage.Python, new inquirer_1.default.Separator(), BACK];
617
+ const langAns = await inquirer_1.default.prompt([{
618
+ type: 'list',
619
+ name: 'language',
620
+ message: 'Select Language:',
621
+ choices: langChoices
622
+ }]);
623
+ if (langAns.language === BACK) {
599
624
  step--;
600
625
  }
601
626
  else {
602
- category = ans.category;
603
- // Sub-selection for languages
604
- const langChoices = category === CAT_WEB
605
- ? [types_1.ProjectLanguage.React, types_1.ProjectLanguage.TypeScript, types_1.ProjectLanguage.HTMLCSS, BACK]
606
- : [types_1.ProjectLanguage.CPP, types_1.ProjectLanguage.Java, types_1.ProjectLanguage.Python, BACK];
607
- const langAns = await inquirer_1.default.prompt([{
608
- type: 'list',
609
- name: 'language',
610
- message: 'Select Language:',
611
- choices: langChoices
612
- }]);
613
- if (langAns.language === BACK) {
614
- // Stay on step 2, loop back to category selection
615
- continue;
616
- }
617
- else {
618
- language = langAns.language;
619
- step++;
620
- }
627
+ language = langAns.language;
628
+ step++;
621
629
  }
630
+ // ── STEP 3: Project Name ────────────────────────────────────────────
622
631
  }
623
632
  else if (step === 3) {
624
- if (language === types_1.ProjectLanguage.React || language === types_1.ProjectLanguage.TypeScript) {
633
+ if (!name) {
634
+ const ans = await inquirer_1.default.prompt([{
635
+ type: 'input',
636
+ name: 'name',
637
+ message: 'Project Name:',
638
+ default: 'my-project',
639
+ validate: (v) => v.trim().length > 0 || 'Name cannot be empty'
640
+ }]);
641
+ name = ans.name.trim();
642
+ }
643
+ step++;
644
+ // ── STEP 4: GitHub repo? (Web Dev only) ────────────────────────────
645
+ }
646
+ else if (step === 4) {
647
+ if (category === CAT_WEB) {
625
648
  const ans = await inquirer_1.default.prompt([{
626
649
  type: 'list',
627
650
  name: 'github',
628
- message: 'Do you want to add this to GitHub?',
629
- choices: ['Yes', 'No', BACK],
630
- default: 'Yes'
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'
631
659
  }]);
632
- if (ans.github === BACK) {
660
+ if (ans.github === 'back') {
661
+ name = undefined;
633
662
  step--;
634
663
  }
635
664
  else {
636
- github = ans.github === 'Yes';
665
+ github = ans.github === 'yes';
637
666
  step++;
638
667
  }
639
668
  }
640
669
  else {
670
+ // CP projects skip straight to done
641
671
  github = false;
642
- step++;
672
+ step = 7; // jump past all web dev steps
643
673
  }
674
+ // ── STEP 5: GitHub Token ────────────────────────────────────────────
644
675
  }
645
- else if (step === 4) {
676
+ else if (step === 5) {
646
677
  if (github) {
647
678
  showGitHubTokenHelp();
648
- const ans = await inquirer_1.default.prompt([
649
- {
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([{
650
683
  type: 'password',
651
684
  name: 'token',
652
- message: 'Enter your GitHub Personal Access Token (or type "back" to return):',
653
- validate: (input) => input.length > 0 || 'Token is required'
654
- }
655
- ]);
656
- if (ans.token.toLowerCase() === 'back') {
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') {
657
695
  step--;
658
696
  }
659
697
  else {
660
- githubToken = ans.token;
661
- step++;
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
+ }
662
711
  }
663
712
  }
664
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;
665
737
  step++;
666
738
  }
667
739
  }
668
740
  }
669
- if (step > 4 && name && language) {
741
+ // ── Execute the project creation ─────────────────────────────────────────
742
+ if ((step > 6 || step === 7) && name && language) {
670
743
  let type = types_1.ProjectType.WebApp;
671
744
  if (language === types_1.ProjectLanguage.TypeScript)
672
745
  type = types_1.ProjectType.CLITool;
673
746
  if (category === CAT_CP)
674
747
  type = types_1.ProjectType.DSAPractice;
675
- await initializeProject(name, language, type, {
748
+ const projectPath = await initializeProject(name, language, type, {
676
749
  isPublic: false,
677
750
  createRemote: !!githubToken,
678
- githubToken: githubToken,
751
+ githubToken,
679
752
  authMethod: 'none'
680
753
  });
681
- if (language === types_1.ProjectLanguage.HTMLCSS) {
682
- const { deploy } = await inquirer_1.default.prompt([
683
- { type: 'confirm', name: 'deploy', message: 'Do you want to deploy to Vercel right now?', default: true }
684
- ]);
685
- if (deploy) {
686
- const vercelManager = new VercelManager_1.VercelManager();
687
- const projectPath = path.join(process.cwd(), name);
688
- if (await vercelManager.checkAuth()) {
689
- await vercelManager.deploy(projectPath, name);
690
- }
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.'));
691
785
  }
692
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();
693
807
  }
694
808
  }
695
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 });
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "zerostart": "./out/cli.js"
6
6
  },
7
7
  "description": "Create and deploy a complete project with one command.",
8
- "version": "0.0.38",
8
+ "version": "0.0.40",
9
9
  "engines": {
10
10
  "vscode": "^1.85.0"
11
11
  },
@@ -45,4 +45,4 @@
45
45
  "inquirer": "^9.3.8",
46
46
  "ora": "^5.4.1"
47
47
  }
48
- }
48
+ }