fabrica-e-commerce 0.1.17 → 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.17",
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/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, endSections, subBox, log } 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,39 +23,54 @@ 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();
32
37
 
38
+ stepHeader(3, 5, 'Environment variables');
33
39
  const env = await collectEnv(supabase);
40
+
41
+ stepHeader(4, 5, 'Clone storefront');
34
42
  const project = await cloneRepo();
35
43
 
36
- section('Run target');
37
- const target = await choose('Where should Fabrica run this storefront?', [
38
- { name: 'Deploy on Vercel cloud', value: 'vercel' },
39
- { 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' },
40
51
  ]);
41
52
 
42
53
  if (target === 'local') {
43
54
  await runLocally(project, env);
44
- endSections();
45
55
  return;
46
56
  }
47
57
 
58
+ section('Cloud deployment — checking tools');
48
59
  await ensureDependencies({ names: ['gh', 'vercel'] });
60
+ endSections();
61
+
49
62
  await ensureVercelLogin();
50
63
  const githubRepo = await createGithubRepoFromClone(project);
51
64
  const record = await deployToVercel(project, env, githubRepo);
52
65
 
53
- section('Done');
54
- kv('Project', record.projectName);
55
- kv('GitHub repo', record.githubRepo || 'n/a');
56
- kv('URL', record.productionUrl || 'n/a');
57
- 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!'));
58
74
  endSections();
59
75
  }
60
76
 
@@ -65,28 +81,36 @@ async function list() {
65
81
 
66
82
  section('Your Fabrica projects');
67
83
  if (!projects.length) {
68
- log('No projects found. Run: fabrica build');
84
+ logInfo('No projects found');
85
+ log('Run: npx fabrica-e-commerce build');
69
86
  endSections();
70
87
  return;
71
88
  }
72
89
 
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
+
73
98
  const choices = projects.map((p) => ({
74
- name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
99
+ name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
75
100
  value: p.id,
76
101
  }));
77
102
 
78
- endSections(); // flush before interactive prompt
79
103
  const selected = await choose('Select a project to view details:', choices);
80
104
  const project = projects.find((p) => p.id === selected);
81
105
 
82
- section(`Project: ${project.projectName}`);
83
- kv('Type', project.type === 'local' ? 'local' : 'cloud');
84
- kv('Created', project.createdAt);
85
- kv('Path', project.target);
86
- kv('GitHub', project.githubRepo || 'n/a');
87
- kv('Supabase', project.supabaseUrl || 'n/a');
88
- kv('URL', project.productionUrl || 'n/a');
89
- kv('Env keys', (project.envKeys || []).join(', '));
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'));
90
114
  endSections();
91
115
  }
92
116
 
@@ -97,28 +121,29 @@ async function env() {
97
121
 
98
122
  section('Environment manager');
99
123
  if (!projects.length) {
100
- log('No projects found. Run: fabrica build');
124
+ logInfo('No projects found');
125
+ log('Run: npx fabrica-e-commerce build');
101
126
  endSections();
102
127
  return;
103
128
  }
104
129
  endSections();
105
130
 
106
131
  const typeFilter = await choose('Which projects to show?', [
107
- { name: 'All projects', value: 'all' },
108
- { name: 'Local projects only', value: 'local' },
109
- { name: 'Cloud (Vercel) projects only', value: 'cloud' },
132
+ { name: 'All projects', value: 'all' },
133
+ { name: 'Local projects only', value: 'local' },
134
+ { name: 'Cloud (Vercel) projects only', value: 'cloud' },
110
135
  ]);
111
136
 
112
137
  const filtered = typeFilter === 'all' ? projects : projects.filter((p) => p.type === typeFilter);
113
138
  if (!filtered.length) {
114
139
  section('Environment manager');
115
- log(`No ${typeFilter} projects found.`);
140
+ log(`No ${typeFilter} projects found`);
116
141
  endSections();
117
142
  return;
118
143
  }
119
144
 
120
145
  const projectId = await choose('Select project:', filtered.map((p) => ({
121
- name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
146
+ name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
122
147
  value: p.id,
123
148
  })));
124
149
  const project = filtered.find((p) => p.id === projectId);
@@ -126,7 +151,7 @@ async function env() {
126
151
  const envKeys = project.envKeys || [];
127
152
  if (!envKeys.length) {
128
153
  section('Environment manager');
129
- log('No env keys stored for this project.');
154
+ logWarn('No env keys stored for this project');
130
155
  endSections();
131
156
  return;
132
157
  }
@@ -137,7 +162,7 @@ async function env() {
137
162
 
138
163
  section('Applying update');
139
164
  await updateProjectEnv(project, key, value);
140
- kv('Updated', `${key} → ${project.type === 'local' ? '.env.local' : 'Vercel + redeployed'}`);
165
+ kvSuccess('Updated', `${key} → ${project.type === 'local' ? '.env.local' : 'Vercel + redeployed'}`);
141
166
  endSections();
142
167
  }
143
168
 
@@ -148,38 +173,39 @@ async function rerun() {
148
173
 
149
174
  section('Re-run / re-open project');
150
175
  if (!projects.length) {
151
- log('No projects found. Run: fabrica build');
176
+ logInfo('No projects found');
177
+ log('Run: npx fabrica-e-commerce build');
152
178
  endSections();
153
179
  return;
154
180
  }
155
181
  endSections();
156
182
 
157
183
  const typeFilter = await choose('Which type of project?', [
158
- { name: 'All projects', value: 'all' },
159
- { name: 'Local projects', value: 'local' },
160
- { 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' },
161
187
  ]);
162
188
 
163
189
  const filtered = typeFilter === 'all' ? projects : projects.filter((p) => p.type === typeFilter);
164
190
  if (!filtered.length) {
165
191
  section('Re-run / re-open project');
166
- log(`No ${typeFilter} projects found.`);
192
+ log(`No ${typeFilter} projects found`);
167
193
  endSections();
168
194
  return;
169
195
  }
170
196
 
171
197
  const projectId = await choose('Select project to re-run:', filtered.map((p) => ({
172
- name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
198
+ name: `${p.projectName} (${p.type === 'local' ? 'local' : 'cloud'})`,
173
199
  value: p.id,
174
200
  })));
175
201
  const project = filtered.find((p) => p.id === projectId);
176
202
 
177
- section(`Re-running: ${project.projectName}`);
203
+ section(`Re-running: ${bold(project.projectName)}`);
178
204
 
179
205
  if (project.type === 'local') {
180
206
  kv('Path', project.target);
181
207
  kv('URL', 'http://localhost:3000');
182
- log('Starting dev server — browser opens in 3s...');
208
+ log('Starting dev server — browser opens in 3s');
183
209
  endSections();
184
210
 
185
211
  const { runCommand, runCommandCapture } = await import('./system.js');
@@ -191,11 +217,11 @@ async function rerun() {
191
217
  await runCommand(devCommand[0], devCommand[1], { cwd: project.target });
192
218
  } else {
193
219
  const url = project.productionUrl || null;
194
- kv('GitHub', project.githubRepo || 'n/a');
195
- kv('URL', url || 'n/a');
196
- kv('Inspect', project.inspectUrl || 'n/a');
197
- kv('Created', project.createdAt);
198
- if (url) { log(`Opening ${url} ...`); }
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} …`);
199
225
  endSections();
200
226
  if (url) await openUrl(url);
201
227
  }
@@ -209,7 +235,8 @@ async function info() {
209
235
  kv('Bridge', BRIDGE_ORIGIN);
210
236
  kv('Store repo', STORE_REPO);
211
237
  kv('Local data', dataDir);
212
- kv('Node', process.version);
238
+ kv('Node.js', process.version);
239
+ kv('Platform', process.platform);
213
240
  kv('Creator', 'SPARROW AI SOLUTION');
214
241
  endSections();
215
242
  }
@@ -217,13 +244,16 @@ async function info() {
217
244
  // ── router ────────────────────────────────────────────────────────────────────
218
245
  export async function run(args) {
219
246
  const command = args[0] || 'help';
220
- if (command === 'build') return build();
221
- if (command === 'list') return list();
222
- if (command === 'env') return env();
223
- if (command === 'rerun') return rerun();
224
- if (command === 'info' || command === '.info') return info();
225
- if (command === 'vins' || command === '/vins') return vinsCommand();
226
- 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
+
227
257
  console.error(`Unknown command: ${command}`);
228
258
  help();
229
259
  process.exitCode = 1;