fabrica-e-commerce 0.1.16 → 0.2.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fabrica-e-commerce",
3
- "version": "0.1.16",
4
- "description": "Orange themed CMD launcher for deploying Fabrica e-commerce stores with Supabase and Vercel.",
3
+ "version": "0.2.1",
4
+ "description": "CLI launcher for deploying Fabrica e-commerce stores Supabase + Vercel, works on Windows, macOS, Linux & Termux.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "fabrica": "bin/fabrica.js",
@@ -25,9 +25,11 @@
25
25
  "ecommerce",
26
26
  "supabase",
27
27
  "vercel",
28
- "cli"
28
+ "cli",
29
+ "storefront",
30
+ "nextjs"
29
31
  ],
30
- "author": "Fabrica",
32
+ "author": "SPARROW AI SOLUTION",
31
33
  "license": "MIT",
32
34
  "engines": {
33
35
  "node": ">=18.17"
@@ -41,6 +43,11 @@
41
43
  "url": "https://github.com/trucount/fabrica-e-c/issues"
42
44
  },
43
45
  "dependencies": {
44
- "boxen": "^8.0.1"
46
+ "boxen": "^8.0.1",
47
+ "chalk": "^5.4.1",
48
+ "cli-table3": "^0.6.5",
49
+ "figures": "^6.1.0",
50
+ "gradient-string": "^3.0.0",
51
+ "ora": "^8.2.0"
45
52
  }
46
53
  }
package/src/bridge.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BRIDGE_API_KEY, BRIDGE_ORIGIN, POLL_INTERVAL_MS, POLL_TIMEOUT_MS } from './config.js';
2
2
  import { FULL_SQL } from './sql.js';
3
3
  import { openUrl } from './system.js';
4
- import { spinner } from './ui.js';
4
+ import { spinner, log } from './ui.js';
5
5
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6
6
  export async function connectSupabase() {
7
7
  const spin = spinner('Posting secure schema job to Fabrica Connect');
@@ -9,7 +9,7 @@ export async function connectSupabase() {
9
9
  if (!response.ok) throw new Error(`Bridge rejected job: ${response.status} ${await response.text()}`);
10
10
  const job = await response.json();
11
11
  spin.succeed('Bridge job created');
12
- console.log(`Open this URL if your browser does not start: ${job.connectUrl}`);
12
+ log(`Open this URL if your browser does not start: ${job.connectUrl}`);
13
13
  await openUrl(job.connectUrl);
14
14
  const started = Date.now();
15
15
  const poll = spinner('Waiting for Supabase authorization and project selection');
package/src/clean.js ADDED
@@ -0,0 +1,184 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import { dataDir, buildsDir, readProjects } from './store.js';
5
+ import { runCommandCapture, runCommand } from './system.js';
6
+ import { section, endSections, kv, kvSuccess, kvFail, log, logInfo, logWarn, divider, spinner, orange, dimOrange, bold, dim, green, red, cyan } from './ui.js';
7
+ import { choose as choosePrompt } from './prompt.js';
8
+
9
+ async function pathExists(p) {
10
+ try { await fs.access(p); return true; } catch { return false; }
11
+ }
12
+
13
+ async function safeRm(p) {
14
+ try { await fs.rm(p, { recursive: true, force: true }); return true; } catch { return false; }
15
+ }
16
+
17
+ // ── clean local project data ──────────────────────────────────────────────────
18
+ async function cleanProjectData() {
19
+ section('Clean local project data');
20
+
21
+ const projectsFile = path.join(dataDir, 'projects.json');
22
+ const projectsExist = await pathExists(projectsFile);
23
+ const buildsExist = await pathExists(buildsDir);
24
+
25
+ if (!projectsExist && !buildsExist) {
26
+ log('Nothing to clean — no local data found');
27
+ endSections();
28
+ return;
29
+ }
30
+
31
+ kv('Projects file', projectsExist ? projectsFile : dim('not found'));
32
+ kv('Builds folder', buildsExist ? buildsDir : dim('not found'));
33
+ logWarn('This will delete all locally tracked project records and cloned builds');
34
+ endSections();
35
+
36
+ const confirm = await choosePrompt(red('Are you sure you want to delete all local data?'), [
37
+ { name: 'Yes, delete everything', value: 'yes' },
38
+ { name: 'No, cancel', value: 'no' },
39
+ ]);
40
+
41
+ if (confirm !== 'yes') {
42
+ section('Clean cancelled');
43
+ log('No changes made');
44
+ endSections();
45
+ return;
46
+ }
47
+
48
+ section('Removing local data');
49
+ if (projectsExist) {
50
+ const ok = await safeRm(projectsFile);
51
+ ok ? kvSuccess('projects.json', 'Deleted') : kvFail('projects.json', 'Could not delete');
52
+ }
53
+ if (buildsExist) {
54
+ const ok = await safeRm(buildsDir);
55
+ ok ? kvSuccess('builds/', 'Deleted') : kvFail('builds/', 'Could not delete');
56
+ }
57
+ log(green('Local data cleaned successfully'));
58
+ endSections();
59
+ }
60
+
61
+ // ── clean env files ───────────────────────────────────────────────────────────
62
+ async function cleanEnvFiles() {
63
+ section('Clean .env files from local projects');
64
+ const projects = await readProjects();
65
+ const localProjects = projects.filter((p) => p.type === 'local');
66
+
67
+ if (!localProjects.length) {
68
+ log('No local projects found with env files');
69
+ endSections();
70
+ return;
71
+ }
72
+
73
+ for (const project of localProjects) {
74
+ const envPath = path.join(project.target, '.env.local');
75
+ if (await pathExists(envPath)) {
76
+ const ok = await safeRm(envPath);
77
+ ok ? kvSuccess(project.projectName, `.env.local deleted`) : kvFail(project.projectName, `Could not delete .env.local`);
78
+ } else {
79
+ log(`${project.projectName} — no .env.local found`);
80
+ }
81
+ }
82
+ endSections();
83
+ }
84
+
85
+ // ── logout Vercel ─────────────────────────────────────────────────────────────
86
+ async function logoutVercel() {
87
+ section('Vercel logout');
88
+ const check = await runCommandCapture('npx', ['--yes', 'vercel@latest', 'whoami']);
89
+ if (check.code !== 0) {
90
+ log(dim('Not currently logged in to Vercel'));
91
+ endSections();
92
+ return;
93
+ }
94
+ const spin = spinner('Logging out of Vercel…');
95
+ const result = await runCommandCapture('npx', ['--yes', 'vercel@latest', 'logout']);
96
+ if (result.code === 0) spin.succeed('Logged out of Vercel');
97
+ else spin.fail('Vercel logout failed — you may need to run: npx vercel logout');
98
+ endSections();
99
+ }
100
+
101
+ // ── logout GitHub ─────────────────────────────────────────────────────────────
102
+ async function logoutGitHub() {
103
+ section('GitHub logout');
104
+ const check = await runCommandCapture('gh', ['auth', 'status']);
105
+ if (check.code !== 0) {
106
+ log(dim('Not currently logged in to GitHub CLI'));
107
+ endSections();
108
+ return;
109
+ }
110
+ const spin = spinner('Logging out of GitHub CLI…');
111
+ const result = await runCommandCapture('gh', ['auth', 'logout', '--hostname', 'github.com']);
112
+ if (result.code === 0) spin.succeed('Logged out of GitHub CLI');
113
+ else spin.fail('GitHub logout failed — you may need to run: gh auth logout');
114
+ endSections();
115
+ }
116
+
117
+ // ── main clean command ────────────────────────────────────────────────────────
118
+ export async function cleanCommand() {
119
+ section('Clean — what do you want to remove?');
120
+ logInfo('Choose what to clean:');
121
+ endSections();
122
+
123
+ const choice = await choosePrompt('Select clean mode:', [
124
+ { name: 'Project data — delete local project records & cloned builds', value: 'project' },
125
+ { name: 'Total — project data + env files + logout (Vercel & GitHub)', value: 'total' },
126
+ { name: 'Vercel logout — log out from Vercel CLI only', value: 'vercel' },
127
+ { name: 'GitHub logout — log out from GitHub CLI only', value: 'github' },
128
+ { name: 'Cancel', value: 'cancel' },
129
+ ]);
130
+
131
+ if (choice === 'cancel') {
132
+ section('Clean');
133
+ log('Cancelled — no changes made');
134
+ endSections();
135
+ return;
136
+ }
137
+
138
+ if (choice === 'project') {
139
+ await cleanProjectData();
140
+ return;
141
+ }
142
+
143
+ if (choice === 'vercel') {
144
+ await logoutVercel();
145
+ return;
146
+ }
147
+
148
+ if (choice === 'github') {
149
+ await logoutGitHub();
150
+ return;
151
+ }
152
+
153
+ if (choice === 'total') {
154
+ section('Total clean — this will:');
155
+ log('1. Delete all local project records & cloned builds');
156
+ log('2. Delete all .env.local files from local projects');
157
+ log('3. Log out from Vercel CLI');
158
+ log('4. Log out from GitHub CLI');
159
+ logWarn('This is a full reset of the Fabrica CLI environment');
160
+ endSections();
161
+
162
+ const confirm = await choosePrompt(red('Proceed with total clean?'), [
163
+ { name: 'Yes, reset everything', value: 'yes' },
164
+ { name: 'No, cancel', value: 'no' },
165
+ ]);
166
+
167
+ if (confirm !== 'yes') {
168
+ section('Total clean cancelled');
169
+ log('No changes made');
170
+ endSections();
171
+ return;
172
+ }
173
+
174
+ await cleanProjectData();
175
+ await cleanEnvFiles();
176
+ await logoutVercel();
177
+ await logoutGitHub();
178
+
179
+ section('Total clean complete');
180
+ log(green('All Fabrica local data and sessions have been cleared'));
181
+ log('Run fabrica build to start fresh');
182
+ endSections();
183
+ }
184
+ }
package/src/cli.js CHANGED
@@ -6,10 +6,11 @@ import { connectSupabase } from './bridge.js';
6
6
  import { collectEnv, cloneRepo, deployToVercel, updateProjectEnv, ensureVercelLogin, runLocally } from './deploy.js';
7
7
  import { createGithubRepoFromClone } from './github.js';
8
8
  import { ensureDependencies, vinsCommand } from './deps.js';
9
+ import { cleanCommand } from './clean.js';
9
10
  import { BRIDGE_ORIGIN, STORE_REPO } from './config.js';
10
11
  import { dataDir, readProjects } from './store.js';
11
12
  import { choose, ask } from './prompt.js';
12
- import { banner, help, kv, section, subBox, resetSectionCount } from './ui.js';
13
+ import { banner, help, kv, kvSuccess, kvFail, section, endSections, subBox, log, logInfo, logWarn, divider, stepHeader, orange, dimOrange, bold, dim, green, cyan } from './ui.js';
13
14
  import { openUrl } from './system.js';
14
15
 
15
16
  async function packageVersion() {
@@ -22,20 +23,31 @@ async function packageVersion() {
22
23
  async function build() {
23
24
  banner();
24
25
 
26
+ stepHeader(1, 5, 'Dependency check');
25
27
  section('Dependency check');
26
28
  await ensureDependencies({ names: ['git'] });
29
+ endSections();
27
30
 
28
- section('Supabase Connect');
29
- kv('BRIDGE', 'ONLINE');
30
- kv('SQL', 'Prepared securely (hidden from UI)');
31
+ stepHeader(2, 5, 'Supabase connect');
32
+ section('Supabase connect');
33
+ kv('Bridge', 'ONLINE');
34
+ kv('SQL', 'Prepared securely (hidden from UI)');
31
35
  const supabase = await connectSupabase();
36
+ endSections();
37
+
38
+ stepHeader(3, 5, 'Environment variables');
32
39
  const env = await collectEnv(supabase);
40
+
41
+ stepHeader(4, 5, 'Clone storefront');
33
42
  const project = await cloneRepo();
34
43
 
35
- section('Run target');
36
- const target = await choose('Where should Fabrica run this storefront?', [
37
- { name: 'Deploy on Vercel cloud', value: 'vercel' },
38
- { name: 'Run locally on this computer', value: 'local' },
44
+ stepHeader(5, 5, 'Deploy target');
45
+ section('Where should Fabrica deploy?');
46
+ endSections();
47
+
48
+ const target = await choose('Select deployment target:', [
49
+ { name: '☁ Deploy on Vercel (cloud, shareable URL)', value: 'vercel' },
50
+ { name: '💻 Run locally on this computer', value: 'local' },
39
51
  ]);
40
52
 
41
53
  if (target === 'local') {
@@ -43,164 +55,175 @@ async function build() {
43
55
  return;
44
56
  }
45
57
 
58
+ section('Cloud deployment — checking tools');
46
59
  await ensureDependencies({ names: ['gh', 'vercel'] });
60
+ endSections();
61
+
47
62
  await ensureVercelLogin();
48
63
  const githubRepo = await createGithubRepoFromClone(project);
49
64
  const record = await deployToVercel(project, env, githubRepo);
50
65
 
51
- section('Done');
52
- kv('Project', record.projectName);
53
- kv('GitHub repo', record.githubRepo || 'n/a');
54
- kv('URL', record.productionUrl || 'n/a');
55
- kv('Path', record.target);
66
+ section('✓ Deployment complete');
67
+ divider();
68
+ kv('Project', record.projectName);
69
+ kv('GitHub', record.githubRepo || dim('n/a'));
70
+ kv('URL', record.productionUrl || dim('n/a'));
71
+ kv('Local path', record.target);
72
+ divider();
73
+ log(green('Your Fabrica store is live!'));
74
+ endSections();
56
75
  }
57
76
 
58
77
  // ── list ──────────────────────────────────────────────────────────────────────
59
78
  async function list() {
60
79
  banner();
61
80
  const projects = await readProjects();
81
+
82
+ section('Your Fabrica projects');
62
83
  if (!projects.length) {
63
- console.log(' No projects found. Run: fabrica build');
84
+ logInfo('No projects found');
85
+ log('Run: npx fabrica-e-commerce build');
86
+ endSections();
64
87
  return;
65
88
  }
66
89
 
67
- section('Your Fabrica projects');
90
+ kv('Total projects', String(projects.length));
91
+ divider();
92
+ for (const p of projects) {
93
+ const type = p.type === 'local' ? cyan('local') : green('cloud');
94
+ log(`${bold(p.projectName)} ${type} ${dim(p.createdAt?.slice(0,10) || '')}`);
95
+ }
96
+ endSections();
97
+
68
98
  const choices = projects.map((p) => ({
69
- name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
99
+ name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
70
100
  value: p.id,
71
101
  }));
72
102
 
73
103
  const selected = await choose('Select a project to view details:', choices);
74
104
  const project = projects.find((p) => p.id === selected);
75
105
 
76
- section(`Project: ${project.projectName}`);
77
- const lines = [
78
- `Name: ${project.projectName}`,
79
- `Type: ${project.type === 'local' ? 'local' : 'cloud'}`,
80
- `Created: ${project.createdAt}`,
81
- `Path: ${project.target}`,
82
- `GitHub: ${project.githubRepo || 'n/a'}`,
83
- `Supabase: ${project.supabaseUrl || 'n/a'}`,
84
- `URL: ${project.productionUrl || 'n/a'}`,
85
- `Env keys: ${(project.envKeys || []).join(', ')}`,
86
- ];
87
- subBox(lines);
106
+ section(`Project: ${bold(project.projectName)}`);
107
+ kv('Type', project.type === 'local' ? cyan('local') : green('cloud'));
108
+ kv('Created', project.createdAt || dim('unknown'));
109
+ kv('Path', project.target || dim('n/a'));
110
+ kv('GitHub', project.githubRepo || dim('n/a'));
111
+ kv('Supabase', project.supabaseUrl || dim('n/a'));
112
+ kv('URL', project.productionUrl || dim('n/a'));
113
+ kv('Env keys', (project.envKeys || []).join(', ') || dim('none'));
114
+ endSections();
88
115
  }
89
116
 
90
117
  // ── env ───────────────────────────────────────────────────────────────────────
91
118
  async function env() {
92
119
  banner();
93
120
  const projects = await readProjects();
121
+
122
+ section('Environment manager');
94
123
  if (!projects.length) {
95
- console.log(' No projects found. Run: fabrica build');
124
+ logInfo('No projects found');
125
+ log('Run: npx fabrica-e-commerce build');
126
+ endSections();
96
127
  return;
97
128
  }
129
+ endSections();
98
130
 
99
- section('Environment manager');
100
-
101
- // Step 1 — pick project type filter
102
131
  const typeFilter = await choose('Which projects to show?', [
103
- { name: 'All projects', value: 'all' },
104
- { name: 'Local projects only', value: 'local' },
132
+ { name: 'All projects', value: 'all' },
133
+ { name: 'Local projects only', value: 'local' },
105
134
  { name: 'Cloud (Vercel) projects only', value: 'cloud' },
106
135
  ]);
107
136
 
108
137
  const filtered = typeFilter === 'all' ? projects : projects.filter((p) => p.type === typeFilter);
109
138
  if (!filtered.length) {
110
- console.log(` No ${typeFilter} projects found.`);
139
+ section('Environment manager');
140
+ log(`No ${typeFilter} projects found`);
141
+ endSections();
111
142
  return;
112
143
  }
113
144
 
114
- // Step 2 — pick project
115
145
  const projectId = await choose('Select project:', filtered.map((p) => ({
116
- name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
146
+ name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
117
147
  value: p.id,
118
148
  })));
119
149
  const project = filtered.find((p) => p.id === projectId);
120
150
 
121
- // Step 3 — pick env key
122
151
  const envKeys = project.envKeys || [];
123
152
  if (!envKeys.length) {
124
- console.log(' No env keys stored for this project.');
153
+ section('Environment manager');
154
+ logWarn('No env keys stored for this project');
155
+ endSections();
125
156
  return;
126
157
  }
127
158
 
128
159
  const key = await choose('Select env variable to update:', envKeys.map((k) => ({ name: k, value: k })));
129
-
130
- // Step 4 — new value
131
160
  const currentVal = (project.env || {})[key];
132
161
  const value = await ask(`New value for ${key}`, currentVal || '');
133
162
 
134
163
  section('Applying update');
135
164
  await updateProjectEnv(project, key, value);
136
- kv('Updated', `${key} → ${project.type === 'local' ? '.env.local' : 'Vercel + redeployed'}`);
165
+ kvSuccess('Updated', `${key} → ${project.type === 'local' ? '.env.local' : 'Vercel + redeployed'}`);
166
+ endSections();
137
167
  }
138
168
 
139
169
  // ── rerun ─────────────────────────────────────────────────────────────────────
140
170
  async function rerun() {
141
171
  banner();
142
172
  const projects = await readProjects();
173
+
174
+ section('Re-run / re-open project');
143
175
  if (!projects.length) {
144
- console.log(' No projects found. Run: fabrica build');
176
+ logInfo('No projects found');
177
+ log('Run: npx fabrica-e-commerce build');
178
+ endSections();
145
179
  return;
146
180
  }
181
+ endSections();
147
182
 
148
- section('Re-run / re-open project');
149
-
150
- // Step 1 — local or cloud
151
183
  const typeFilter = await choose('Which type of project?', [
152
- { name: 'All projects', value: 'all' },
153
- { name: 'Local projects', value: 'local' },
154
- { name: 'Cloud (Vercel) projects', value: 'cloud' },
184
+ { name: 'All projects', value: 'all' },
185
+ { name: 'Local projects', value: 'local' },
186
+ { name: 'Cloud (Vercel) projects', value: 'cloud' },
155
187
  ]);
156
188
 
157
189
  const filtered = typeFilter === 'all' ? projects : projects.filter((p) => p.type === typeFilter);
158
190
  if (!filtered.length) {
159
- console.log(` No ${typeFilter} projects found.`);
191
+ section('Re-run / re-open project');
192
+ log(`No ${typeFilter} projects found`);
193
+ endSections();
160
194
  return;
161
195
  }
162
196
 
163
- // Step 2 — pick project
164
197
  const projectId = await choose('Select project to re-run:', filtered.map((p) => ({
165
- name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
198
+ name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
166
199
  value: p.id,
167
200
  })));
168
201
  const project = filtered.find((p) => p.id === projectId);
169
202
 
170
- section(`Re-running: ${project.projectName}`);
203
+ section(`Re-running: ${bold(project.projectName)}`);
171
204
 
172
205
  if (project.type === 'local') {
173
- // Re-run local dev server
174
206
  kv('Path', project.target);
175
- kv('URL', 'http://localhost:3000');
207
+ kv('URL', 'http://localhost:3000');
208
+ log('Starting dev server — browser opens in 3s…');
209
+ endSections();
176
210
 
177
- const { runCommand } = await import('./system.js');
211
+ const { runCommand, runCommandCapture } = await import('./system.js');
178
212
  const { access } = await import('node:fs/promises');
179
213
  const hasPnpmLock = await access(path.join(project.target, 'pnpm-lock.yaml')).then(() => true, () => false);
180
- const { runCommandCapture } = await import('./system.js');
181
214
  const pnpmAvailable = (await runCommandCapture('pnpm', ['--version'])).code === 0;
182
215
  const devCommand = hasPnpmLock && pnpmAvailable ? ['pnpm', ['run', 'dev']] : ['npm', ['run', 'dev']];
183
-
184
- console.log(' Starting dev server... auto-opening browser in 3s');
185
216
  setTimeout(() => openUrl('http://localhost:3000'), 3000);
186
217
  await runCommand(devCommand[0], devCommand[1], { cwd: project.target });
187
218
  } else {
188
- // Cloud: show info + open URL
189
219
  const url = project.productionUrl || null;
190
- const lines = [
191
- `Project: ${project.projectName}`,
192
- `GitHub: ${project.githubRepo || 'n/a'}`,
193
- `URL: ${url || 'n/a'}`,
194
- `Inspect: ${project.inspectUrl || 'n/a'}`,
195
- `Created: ${project.createdAt}`,
196
- ];
197
- subBox(lines);
198
- if (url) {
199
- kv('Opening', url);
200
- await openUrl(url);
201
- } else {
202
- console.log(' No URL found for this project.');
203
- }
220
+ kv('GitHub', project.githubRepo || dim('n/a'));
221
+ kv('URL', url || dim('n/a'));
222
+ kv('Inspect', project.inspectUrl || dim('n/a'));
223
+ kv('Created', project.createdAt || dim('unknown'));
224
+ if (url) log(`Opening ${url} …`);
225
+ endSections();
226
+ if (url) await openUrl(url);
204
227
  }
205
228
  }
206
229
 
@@ -208,28 +231,29 @@ async function rerun() {
208
231
  async function info() {
209
232
  banner();
210
233
  section('Package info');
211
- const lines = [
212
- `Package: fabrica-e-commerce v${await packageVersion()}`,
213
- `Bridge: ${BRIDGE_ORIGIN}`,
214
- `Store repo: ${STORE_REPO}`,
215
- `Local data: ${dataDir}`,
216
- `Node: ${process.version}`,
217
- '',
218
- 'Creator: SPARROW AI SOLUTION',
219
- ];
220
- subBox(lines);
234
+ kv('Package', `fabrica-e-commerce v${await packageVersion()}`);
235
+ kv('Bridge', BRIDGE_ORIGIN);
236
+ kv('Store repo', STORE_REPO);
237
+ kv('Local data', dataDir);
238
+ kv('Node.js', process.version);
239
+ kv('Platform', process.platform);
240
+ kv('Creator', 'SPARROW AI SOLUTION');
241
+ endSections();
221
242
  }
222
243
 
223
244
  // ── router ────────────────────────────────────────────────────────────────────
224
245
  export async function run(args) {
225
246
  const command = args[0] || 'help';
226
- if (command === 'build') return build();
227
- if (command === 'list') return list();
228
- if (command === 'env') return env();
229
- if (command === 'rerun') return rerun();
230
- if (command === 'info' || command === '.info') return info();
231
- if (command === 'vins' || command === '/vins') return vinsCommand();
232
- if (command === 'help' || command === '--help' || command === '-h') return help();
247
+
248
+ if (command === 'build') return build();
249
+ if (command === 'list') return list();
250
+ if (command === 'env') return env();
251
+ if (command === 'rerun') return rerun();
252
+ if (command === 'clean') return cleanCommand();
253
+ if (command === 'info' || command === '.info') return info();
254
+ if (command === 'vins' || command === '/vins') return vinsCommand();
255
+ if (command === 'help' || command === '--help' || command === '-h') return help();
256
+
233
257
  console.error(`Unknown command: ${command}`);
234
258
  help();
235
259
  process.exitCode = 1;