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 +12 -5
- package/src/bridge.js +2 -2
- package/src/clean.js +184 -0
- package/src/cli.js +115 -91
- package/src/deploy.js +62 -64
- package/src/deps.js +173 -126
- package/src/github.js +4 -4
- package/src/sql.js +227 -16
- package/src/ui.js +140 -68
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fabrica-e-commerce",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "
|
|
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": "
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
kv('
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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('
|
|
52
|
-
|
|
53
|
-
kv('
|
|
54
|
-
kv('
|
|
55
|
-
kv('
|
|
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
|
-
|
|
84
|
+
logInfo('No projects found');
|
|
85
|
+
log('Run: npx fabrica-e-commerce build');
|
|
86
|
+
endSections();
|
|
64
87
|
return;
|
|
65
88
|
}
|
|
66
89
|
|
|
67
|
-
|
|
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}
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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',
|
|
104
|
-
{ name: 'Local projects only',
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
153
|
-
{ name: 'Local projects',
|
|
154
|
-
{ name: 'Cloud (Vercel) projects',
|
|
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
|
-
|
|
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}
|
|
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',
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
''
|
|
218
|
-
|
|
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
|
-
|
|
227
|
-
if (command === '
|
|
228
|
-
if (command === '
|
|
229
|
-
if (command === '
|
|
230
|
-
if (command === '
|
|
231
|
-
if (command === '
|
|
232
|
-
if (command === '
|
|
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;
|