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 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,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 = Category (Web Dev / CP) — shown FIRST
580
- // 2 = Language sub-selection
581
- // 3 = Project Name
582
- // 4 = GitHub? (only for React/TS)
583
- // 5 = GitHub Token (only if GitHub = Yes)
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 <= 5) {
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
- // Step 4: GitHub? (only for React/TS)
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: 'Do you want to add this to GitHub?',
644
- choices: ['Yes', 'No', new inquirer_1.default.Separator(), BACK],
645
- 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'
646
659
  }]);
647
- if (ans.github === BACK) {
648
- name = undefined; // reset name so it's re-asked if they go back
660
+ if (ans.github === 'back') {
661
+ name = undefined;
649
662
  step--;
650
663
  }
651
664
  else {
652
- github = ans.github === 'Yes';
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
- const ans = await inquirer_1.default.prompt([
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: 'Enter your GitHub Personal Access Token (or type "back" to return):',
670
- validate: (input) => input.length > 0 || 'Token is required'
671
- }
672
- ]);
673
- 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') {
674
695
  step--;
675
696
  }
676
697
  else {
677
- githubToken = ans.token;
678
- 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
+ }
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
- if (step > 5 && name && language) {
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: githubToken,
751
+ githubToken,
696
752
  authMethod: 'none'
697
753
  });
698
- if (language === types_1.ProjectLanguage.HTMLCSS) {
699
- const { deploy } = await inquirer_1.default.prompt([
700
- { type: 'confirm', name: 'deploy', message: 'Do you want to deploy to Vercel right now?', default: true }
701
- ]);
702
- if (deploy) {
703
- const vercelManager = new VercelManager_1.VercelManager();
704
- const projectPath = path.join(process.cwd(), name);
705
- if (await vercelManager.checkAuth()) {
706
- await vercelManager.deploy(projectPath, name);
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 });
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.39",
8
+ "version": "0.0.40",
9
9
  "engines": {
10
10
  "vscode": "^1.85.0"
11
11
  },