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 +12 -5
- package/src/clean.js +184 -0
- package/src/cli.js +84 -54
- package/src/deps.js +160 -52
- package/src/sql.js +227 -16
- package/src/ui.js +100 -73
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/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
|
-
|
|
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();
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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('
|
|
54
|
-
|
|
55
|
-
kv('
|
|
56
|
-
kv('
|
|
57
|
-
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!'));
|
|
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
|
-
|
|
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}
|
|
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
|
-
|
|
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',
|
|
108
|
-
{ name: 'Local projects only',
|
|
109
|
-
{ name: 'Cloud (Vercel) projects only',
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
159
|
-
{ name: 'Local projects',
|
|
160
|
-
{ name: 'Cloud (Vercel) projects',
|
|
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}
|
|
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)
|
|
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',
|
|
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
|
-
|
|
221
|
-
if (command === '
|
|
222
|
-
if (command === '
|
|
223
|
-
if (command === '
|
|
224
|
-
if (command === '
|
|
225
|
-
if (command === '
|
|
226
|
-
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
|
+
|
|
227
257
|
console.error(`Unknown command: ${command}`);
|
|
228
258
|
help();
|
|
229
259
|
process.exitCode = 1;
|