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/src/github.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { commandExists, runCommand, runCommandCapture } from './system.js';
|
|
2
2
|
import { STORE_REPO } from './config.js';
|
|
3
3
|
import { choose } from './prompt.js';
|
|
4
|
-
import { dimOrange, kv, section, spinner } from './ui.js';
|
|
4
|
+
import { dimOrange, kv, section, spinner, log } from './ui.js';
|
|
5
5
|
|
|
6
6
|
export async function isGithubCliInstalled() {
|
|
7
7
|
return commandExists('gh');
|
|
@@ -27,7 +27,7 @@ export async function ensureGithubLogin() {
|
|
|
27
27
|
spin.succeed('Already logged in to GitHub');
|
|
28
28
|
} else {
|
|
29
29
|
spin.fail('Not logged in to GitHub');
|
|
30
|
-
|
|
30
|
+
log('A browser/device flow will open — log in with "gh auth login"...');
|
|
31
31
|
await runCommand('gh', ['auth', 'login', '--hostname', 'github.com', '--git-protocol', 'https', '--web']);
|
|
32
32
|
if (!(await isLoggedInToGithub())) {
|
|
33
33
|
throw new Error('GitHub login was not completed. Run "fabrica build" again after logging in with "gh auth login".');
|
|
@@ -40,7 +40,7 @@ export async function ensureGithubLogin() {
|
|
|
40
40
|
gitSpin.succeed('GitHub API credentials ready');
|
|
41
41
|
} else {
|
|
42
42
|
gitSpin.fail('Could not configure GitHub git credentials automatically');
|
|
43
|
-
|
|
43
|
+
log('Continuing — if Git asks for credentials later, run: gh auth setup-git');
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -157,7 +157,7 @@ async function forkSourceRepo(owner, repoName) {
|
|
|
157
157
|
}
|
|
158
158
|
renameSpin.fail(`Could not rename — continuing with the existing fork name`);
|
|
159
159
|
kv('GitHub repo', `https://github.com/${owner}/${actualName}`);
|
|
160
|
-
|
|
160
|
+
log(`Note: this is named "${actualName}", not "${repoName}", because your GitHub account already had a fork of ${source}.`);
|
|
161
161
|
return actualName;
|
|
162
162
|
}
|
|
163
163
|
|
package/src/sql.js
CHANGED
|
@@ -1,24 +1,234 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2
|
+
// SCHEMA
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1
4
|
export const SCHEMA_SQL = `
|
|
2
5
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
3
|
-
|
|
4
|
-
CREATE TABLE IF NOT EXISTS public.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
CREATE TABLE IF NOT EXISTS public.
|
|
12
|
-
|
|
6
|
+
|
|
7
|
+
CREATE TABLE IF NOT EXISTS public.site_content (
|
|
8
|
+
id text NOT NULL,
|
|
9
|
+
content jsonb NOT NULL,
|
|
10
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
11
|
+
CONSTRAINT site_content_pkey PRIMARY KEY (id)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE TABLE IF NOT EXISTS public.collections (
|
|
15
|
+
id text NOT NULL,
|
|
16
|
+
name text NOT NULL,
|
|
17
|
+
description text NOT NULL,
|
|
18
|
+
image_url text NOT NULL,
|
|
19
|
+
item_count_label text NOT NULL,
|
|
20
|
+
sort_order integer NOT NULL DEFAULT 0,
|
|
21
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
22
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
23
|
+
CONSTRAINT collections_pkey PRIMARY KEY (id)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE TABLE IF NOT EXISTS public.products (
|
|
27
|
+
id text NOT NULL,
|
|
28
|
+
name text NOT NULL,
|
|
29
|
+
price numeric NOT NULL CHECK (price >= 0),
|
|
30
|
+
main_image_url text NOT NULL,
|
|
31
|
+
gallery_image_urls text[] NOT NULL DEFAULT '{}',
|
|
32
|
+
category_label text,
|
|
33
|
+
description text NOT NULL,
|
|
34
|
+
details text[] NOT NULL DEFAULT '{}',
|
|
35
|
+
sizes text[] NOT NULL DEFAULT '{}',
|
|
36
|
+
section text NOT NULL DEFAULT 'general' CHECK (section = ANY (ARRAY['general','new_arrivals','best_sellers'])),
|
|
37
|
+
collection_ids text[] NOT NULL DEFAULT '{}',
|
|
38
|
+
sort_order integer NOT NULL DEFAULT 0,
|
|
39
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
40
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
41
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
42
|
+
CONSTRAINT products_pkey PRIMARY KEY (id)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
CREATE TABLE IF NOT EXISTS public.app_users (
|
|
46
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
47
|
+
email text NOT NULL UNIQUE,
|
|
48
|
+
first_name text NOT NULL,
|
|
49
|
+
last_name text NOT NULL,
|
|
50
|
+
phone text NOT NULL,
|
|
51
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
52
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
53
|
+
CONSTRAINT app_users_pkey PRIMARY KEY (id)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE IF NOT EXISTS public.saved_addresses (
|
|
57
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
58
|
+
user_id uuid NOT NULL,
|
|
59
|
+
label text NOT NULL,
|
|
60
|
+
first_name text NOT NULL,
|
|
61
|
+
last_name text NOT NULL,
|
|
62
|
+
phone text NOT NULL,
|
|
63
|
+
address text NOT NULL,
|
|
64
|
+
apartment text,
|
|
65
|
+
city text NOT NULL,
|
|
66
|
+
state text NOT NULL,
|
|
67
|
+
zip_code text NOT NULL,
|
|
68
|
+
country text NOT NULL DEFAULT 'India',
|
|
69
|
+
is_default boolean NOT NULL DEFAULT false,
|
|
70
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
71
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
72
|
+
CONSTRAINT saved_addresses_pkey PRIMARY KEY (id),
|
|
73
|
+
CONSTRAINT saved_addresses_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.app_users(id)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
CREATE TABLE IF NOT EXISTS public.order_policies (
|
|
77
|
+
id boolean NOT NULL DEFAULT true CHECK (id = true),
|
|
78
|
+
shipping_amount numeric NOT NULL DEFAULT 15 CHECK (shipping_amount >= 0),
|
|
79
|
+
free_shipping_threshold numeric NOT NULL DEFAULT 200 CHECK (free_shipping_threshold >= 0),
|
|
80
|
+
tax_rate numeric NOT NULL DEFAULT 8 CHECK (tax_rate >= 0),
|
|
81
|
+
automatic_shipping_enabled boolean NOT NULL DEFAULT false,
|
|
82
|
+
shippo_from_name text NOT NULL DEFAULT '',
|
|
83
|
+
shippo_from_company text NOT NULL DEFAULT '',
|
|
84
|
+
shippo_from_street1 text NOT NULL DEFAULT '',
|
|
85
|
+
shippo_from_street2 text NOT NULL DEFAULT '',
|
|
86
|
+
shippo_from_city text NOT NULL DEFAULT '',
|
|
87
|
+
shippo_from_state text NOT NULL DEFAULT '',
|
|
88
|
+
shippo_from_zip text NOT NULL DEFAULT '',
|
|
89
|
+
shippo_from_country text NOT NULL DEFAULT 'IN',
|
|
90
|
+
shippo_from_phone text NOT NULL DEFAULT '',
|
|
91
|
+
shippo_from_email text NOT NULL DEFAULT '',
|
|
92
|
+
shippo_from_is_residential boolean NOT NULL DEFAULT false,
|
|
93
|
+
shippo_parcel_length numeric NOT NULL DEFAULT 10 CHECK (shippo_parcel_length > 0),
|
|
94
|
+
shippo_parcel_width numeric NOT NULL DEFAULT 10 CHECK (shippo_parcel_width > 0),
|
|
95
|
+
shippo_parcel_height numeric NOT NULL DEFAULT 4 CHECK (shippo_parcel_height > 0),
|
|
96
|
+
shippo_parcel_weight numeric NOT NULL DEFAULT 1 CHECK (shippo_parcel_weight > 0),
|
|
97
|
+
shippo_parcel_distance_unit text NOT NULL DEFAULT 'in' CHECK (shippo_parcel_distance_unit = ANY (ARRAY['in','cm'])),
|
|
98
|
+
shippo_parcel_mass_unit text NOT NULL DEFAULT 'lb' CHECK (shippo_parcel_mass_unit = ANY (ARRAY['lb','oz','g','kg'])),
|
|
99
|
+
shippo_label_file_type text NOT NULL DEFAULT 'PDF_4x6' CHECK (shippo_label_file_type = ANY (ARRAY['PNG','PNG_2.3x7.5','PDF','PDF_2.3x7.5','PDF_4x6','PDF_4x8','PDF_A4','PDF_A5','PDF_A6','ZPLII'])),
|
|
100
|
+
active_theme_name text DEFAULT 'default',
|
|
101
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
102
|
+
show_ticker boolean NOT NULL DEFAULT true,
|
|
103
|
+
section_styles jsonb NOT NULL DEFAULT '{"homeHero":"video"}',
|
|
104
|
+
CONSTRAINT order_policies_pkey PRIMARY KEY (id)
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
CREATE TABLE IF NOT EXISTS public.coupons (
|
|
108
|
+
code text NOT NULL,
|
|
109
|
+
label text NOT NULL,
|
|
110
|
+
coupon_type text NOT NULL CHECK (coupon_type = ANY (ARRAY['universal','one_time'])),
|
|
111
|
+
discount_type text NOT NULL CHECK (discount_type = ANY (ARRAY['percent','amount'])),
|
|
112
|
+
discount_value numeric NOT NULL CHECK (discount_value >= 0),
|
|
113
|
+
active boolean NOT NULL DEFAULT true,
|
|
114
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
115
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
116
|
+
CONSTRAINT coupons_pkey PRIMARY KEY (code)
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
CREATE TABLE IF NOT EXISTS public.orders (
|
|
120
|
+
id text NOT NULL,
|
|
121
|
+
user_id uuid,
|
|
122
|
+
user_email text NOT NULL,
|
|
123
|
+
status text NOT NULL DEFAULT 'placed' CHECK (status = ANY (ARRAY['placed','packed','in_transit','delivered'])),
|
|
124
|
+
payment_method text NOT NULL CHECK (payment_method = ANY (ARRAY['cod','razorpay'])),
|
|
125
|
+
payment_verified boolean NOT NULL DEFAULT false,
|
|
126
|
+
razorpay_payment_id text,
|
|
127
|
+
coupon_code text,
|
|
128
|
+
shipping_address jsonb NOT NULL,
|
|
129
|
+
totals jsonb NOT NULL,
|
|
130
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
131
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
132
|
+
CONSTRAINT orders_pkey PRIMARY KEY (id),
|
|
133
|
+
CONSTRAINT orders_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.app_users(id),
|
|
134
|
+
CONSTRAINT orders_coupon_code_fkey FOREIGN KEY (coupon_code) REFERENCES public.coupons(code)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
CREATE TABLE IF NOT EXISTS public.order_items (
|
|
138
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
139
|
+
order_id text NOT NULL,
|
|
140
|
+
product_id text,
|
|
141
|
+
product_name text NOT NULL,
|
|
142
|
+
product_image text,
|
|
143
|
+
size text NOT NULL,
|
|
144
|
+
quantity integer NOT NULL CHECK (quantity > 0),
|
|
145
|
+
unit_price numeric NOT NULL CHECK (unit_price >= 0),
|
|
146
|
+
line_total numeric NOT NULL CHECK (line_total >= 0),
|
|
147
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
148
|
+
CONSTRAINT order_items_pkey PRIMARY KEY (id),
|
|
149
|
+
CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(id),
|
|
150
|
+
CONSTRAINT order_items_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.products(id)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
CREATE TABLE IF NOT EXISTS public.themes (
|
|
154
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
155
|
+
name text NOT NULL UNIQUE,
|
|
156
|
+
label text NOT NULL,
|
|
157
|
+
colors jsonb NOT NULL,
|
|
158
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
159
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
160
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
161
|
+
CONSTRAINT themes_pkey PRIMARY KEY (id)
|
|
162
|
+
);
|
|
13
163
|
`;
|
|
164
|
+
|
|
165
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
+
// SEED — full mock data from uploaded SQL files
|
|
167
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
168
|
export const SEED_SQL = `
|
|
15
|
-
|
|
16
|
-
INSERT INTO public.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
169
|
+
-- Themes
|
|
170
|
+
INSERT INTO public.themes (id, name, label, colors, is_active) VALUES
|
|
171
|
+
('36c8ea36-0aa5-4100-b0c4-d61c6b9bccd3','crimson','Crimson Elegance','{"muted":"oklch(0.96 0.02 20)","accent":"oklch(0.6 0.15 30)","border":"oklch(0.9 0.05 20)","primary":"oklch(0.5 0.18 25)","secondary":"oklch(0.95 0.03 20)","background":"oklch(0.98 0.01 20)","foreground":"oklch(0.2 0.05 20)","primary_foreground":"oklch(0.98 0.01 20)"}',true),
|
|
172
|
+
('48234595-c916-4e52-a1d7-5dc489fec14d','golden','Light Golden','{"muted":"oklch(0.88 0.08 70)","accent":"oklch(0.75 0.18 62)","border":"oklch(0.90 0.10 70)","primary":"oklch(0.70 0.18 62)","secondary":"oklch(0.85 0.12 70)","background":"oklch(0.98 0.01 70)","foreground":"oklch(0.25 0.08 50)","primary_foreground":"oklch(0.96 0.12 75)"}',true),
|
|
173
|
+
('7f33e885-6629-4f6b-8dde-382efa03698c','default','Minimalist White','{"muted":"oklch(0.97 0 0)","accent":"oklch(0.97 0 0)","border":"oklch(0.922 0 0)","primary":"oklch(0.205 0 0)","secondary":"oklch(0.97 0 0)","background":"oklch(0.985 0 0)","foreground":"oklch(0.145 0 0)","primary_foreground":"oklch(0.985 0 0)"}',true),
|
|
174
|
+
('bcf786ec-a658-4e9f-9115-0067fc313a34','peachy','Peachy Delight','{"muted":"oklch(0.901 0.012 162.023)","accent":"oklch(0.888 0.061 5.752)","border":"oklch(0.85 0.02 0)","primary":"oklch(0.816 0.086 9.042)","secondary":"oklch(0.888 0.061 5.752)","background":"oklch(0.95 0.01 162)","foreground":"oklch(0.35 0.03 0)","primary_foreground":"oklch(0.98 0.01 0)"}',true),
|
|
175
|
+
('c3f4dba4-17db-47d6-9e84-315425d78e91','midnight','Midnight Dark','{"muted":"oklch(0.2 0.03 240)","accent":"oklch(0.7 0.1 200)","border":"oklch(0.3 0.05 240)","primary":"oklch(0.6 0.15 250)","secondary":"oklch(0.25 0.05 240)","background":"oklch(0.15 0.02 240)","foreground":"oklch(0.98 0.01 240)","primary_foreground":"oklch(0.98 0.01 240)"}',true),
|
|
176
|
+
('d132bca7-872e-475c-adfc-1f8b2e9e271c','ocean','Ocean Deep','{"muted":"oklch(0.9 0.03 220)","accent":"oklch(0.6 0.12 200)","border":"oklch(0.8 0.06 220)","primary":"oklch(0.5 0.15 220)","secondary":"oklch(0.85 0.05 220)","background":"oklch(0.95 0.02 220)","foreground":"oklch(0.2 0.08 220)","primary_foreground":"oklch(0.98 0.01 220)"}',true),
|
|
177
|
+
('dab26fe5-31e9-4748-b82a-15d3b91c5af1','forest','Forest Nature','{"muted":"oklch(0.92 0.03 140)","accent":"oklch(0.5 0.12 150)","border":"oklch(0.85 0.05 140)","primary":"oklch(0.4 0.1 140)","secondary":"oklch(0.9 0.05 140)","background":"oklch(0.97 0.01 140)","foreground":"oklch(0.2 0.05 140)","primary_foreground":"oklch(0.98 0.01 140)"}',true),
|
|
178
|
+
('fd49d578-a0a1-44a9-9d83-93dcec092dc8','lavender','Lavender Mist','{"muted":"oklch(0.94 0.03 290)","accent":"oklch(0.75 0.15 300)","border":"oklch(0.88 0.06 290)","primary":"oklch(0.7 0.12 290)","secondary":"oklch(0.92 0.05 290)","background":"oklch(0.96 0.02 290)","foreground":"oklch(0.3 0.08 290)","primary_foreground":"oklch(0.98 0.01 290)"}',true)
|
|
179
|
+
ON CONFLICT (name) DO UPDATE SET label=EXCLUDED.label, colors=EXCLUDED.colors, updated_at=now();
|
|
180
|
+
|
|
181
|
+
-- Collections
|
|
182
|
+
INSERT INTO public.collections (id, name, description, image_url, item_count_label, sort_order) VALUES
|
|
183
|
+
('contemporary','Contemporary Collection','Modern cuts and innovative styling for the forward-thinking gentleman.','/thudarum-sky-blue-blazer.jpg','10 items',30),
|
|
184
|
+
('evening','Evening Collection','Luxurious velvet and satin pieces designed to make a statement at formal occasions.','/thudarum-navy-velvet-blazer.jpg','6 items',40),
|
|
185
|
+
('executive','Executive Collection','Bold, sophisticated pieces for the modern power dresser. Featuring rich textures and commanding colors.','/thudarum-burgundy-evening-suit.jpg','12 items',10),
|
|
186
|
+
('heritage','Heritage Collection','Classic tailoring with timeless appeal. Traditional patterns reimagined for contemporary elegance.','/thudarum-green-check-blazer.jpg','8 items',20)
|
|
187
|
+
ON CONFLICT (id) DO NOTHING;
|
|
188
|
+
|
|
189
|
+
-- Products
|
|
190
|
+
INSERT INTO public.products (id,name,price,main_image_url,gallery_image_urls,category_label,description,details,sizes,section,collection_ids,sort_order,is_active) VALUES
|
|
191
|
+
('burgundy-blazer-cream-trousers','Burgundy Blazer with Cream Trousers',985.00,'/thudarum-burgundy-blazer-combo.jpg',ARRAY['/thudarum-burgundy-combo-detail.jpg','/thudarum-burgundy-combo-side.jpg'],'Separates','A striking burgundy blazer combination with cream trousers for a polished statement look.',ARRAY['Premium wool construction','Double-breasted design','Gold-tone buttons','Tailored separates'],ARRAY['38','40','42','44','46','48'],'best_sellers',ARRAY['executive','evening'],10,true),
|
|
192
|
+
('camel-trench-coat','Camel Trench Coat',645.00,'/camel-trench-coat-elegant.jpg',ARRAY['/minimalist-fashion-studio-elegant-clothing.jpg'],'Outerwear','A refined camel trench coat with a versatile silhouette for smart everyday dressing.',ARRAY['Water-resistant cotton blend','Belted waist','Classic storm flap'],ARRAY['S','M','L','XL'],'general',ARRAY['heritage','contemporary'],20,true),
|
|
193
|
+
('classic-taupe-double-breasted-suit','Classic Taupe Double-Breasted Suit',1289.00,'/thudarum-taupe-suit-hero.jpg',ARRAY['/thudarum-taupe-suit-detail.jpg','/thudarum-taupe-suit-side.jpg'],'Suits','An impeccably tailored double-breasted suit in refined taupe wool with peak lapels and matching trousers.',ARRAY['100% Italian wool','Double-breasted closure','Peak lapels','Complete with trousers'],ARRAY['38','40','42','44','46','48'],'new_arrivals',ARRAY['executive','contemporary'],10,true),
|
|
194
|
+
('elegant-black-wool-trousers','Elegant Black Wool Trousers',325.00,'/elegant-black-wool-trousers.jpg',ARRAY[],'Trousers','A foundational black wool trouser with a sharp line and versatile formal appeal.',ARRAY['Wool blend','Pressed front crease','Tailored waistband'],ARRAY['30','32','34','36','38'],'general',ARRAY[],30,true),
|
|
195
|
+
('heritage-green-check-blazer','Heritage Green Check Blazer',895.00,'/thudarum-green-check-blazer.jpg',ARRAY['/thudarum-green-check-detail.jpg','/thudarum-green-check-side.jpg'],'Blazers','A distinguished houndstooth blazer with classic green patterning, refined tailoring, and gold-tone buttons.',ARRAY['Premium wool houndstooth','Double-breasted design','Gold-tone buttons','Made in England'],ARRAY['38','40','42','44','46','48'],'new_arrivals',ARRAY['heritage'],20,true),
|
|
196
|
+
('luxe-burgundy-evening-suit','Luxe Burgundy Evening Suit',1545.00,'/thudarum-burgundy-evening-suit.jpg',ARRAY['/thudarum-burgundy-suit-detail.jpg','/thudarum-burgundy-suit-side.jpg'],'Suits','A sophisticated burgundy suit designed for elevated evening occasions with a timeless tailored silhouette.',ARRAY['Fine Italian wool','Double-breasted style','Tone-on-tone buttons','Full suit ensemble'],ARRAY['38','40','42','44','46','48'],'new_arrivals',ARRAY['executive','evening'],30,true),
|
|
197
|
+
('minimalist-white-linen-shirt','Minimalist White Linen Shirt',245.00,'/minimalist-white-linen-shirt-fashion.jpg',ARRAY['/ivory-silk-blouse-minimal.jpg'],'Shirts','A clean white linen shirt designed for effortless layering and warm-weather polish.',ARRAY['Premium linen','Relaxed tailored fit','Breathable finish'],ARRAY['S','M','L','XL'],'general',ARRAY['contemporary'],10,true),
|
|
198
|
+
('modern-slate-blazer-set','Modern Slate Blazer Set',1095.00,'/thudarum-slate-blazer-set.jpg',ARRAY['/thudarum-slate-blazer-detail.jpg','/thudarum-slate-blazer-side.jpg'],'Separates','A sophisticated slate blazer set that blends timeless tailoring with modern styling.',ARRAY['Premium wool blend','Double-breasted design','Classic buttons','Made in England'],ARRAY['38','40','42','44','46','48'],'best_sellers',ARRAY['contemporary'],40,true),
|
|
199
|
+
('navy-velvet-double-breasted-jacket','Navy Velvet Double-Breasted Jacket',1195.00,'/thudarum-navy-velvet-blazer.jpg',ARRAY['/thudarum-navy-velvet-detail.jpg','/thudarum-navy-velvet-side.jpg'],'Blazers','A luxurious navy velvet jacket with elevated texture and refined eveningwear presence.',ARRAY['Italian cotton velvet','Double-breasted style','Silver-tone buttons','Evening-ready finish'],ARRAY['38','40','42','44','46','48'],'best_sellers',ARRAY['evening'],20,true),
|
|
200
|
+
('refined-gray-double-breasted-suit','Refined Gray Double-Breasted Suit',1345.00,'/thudarum-gray-suit-refined.jpg',ARRAY['/thudarum-gray-suit-detail.jpg','/thudarum-gray-suit-side.jpg'],'Suits','A modern gray double-breasted suit with a clean profile and professional finish.',ARRAY['Super 120s wool','Double-breasted closure','Peak lapels','Complete suit'],ARRAY['38','40','42','44','46','48'],'best_sellers',ARRAY['executive','contemporary'],30,true),
|
|
201
|
+
('sky-blue-textured-blazer','Sky Blue Textured Blazer',795.00,'/thudarum-sky-blue-blazer.jpg',ARRAY['/thudarum-sky-blue-detail.jpg','/thudarum-sky-blue-side.jpg'],'Blazers','A contemporary sky blue blazer with subtle texture, sharp tailoring, and distinctive button details.',ARRAY['Textured wool blend','Double-breasted cut','Gold button details','Made in Italy'],ARRAY['38','40','42','44','46','48'],'new_arrivals',ARRAY['contemporary'],40,true)
|
|
202
|
+
ON CONFLICT (id) DO NOTHING;
|
|
203
|
+
|
|
204
|
+
-- Coupons
|
|
205
|
+
INSERT INTO public.coupons (code, label, coupon_type, discount_type, discount_value, active) VALUES
|
|
206
|
+
('WELCOME10','Welcome 10%','universal','percent',10.00,true)
|
|
207
|
+
ON CONFLICT (code) DO NOTHING;
|
|
208
|
+
|
|
209
|
+
-- Order policies
|
|
210
|
+
INSERT INTO public.order_policies (id, shipping_amount, free_shipping_threshold, tax_rate, automatic_shipping_enabled, shippo_from_name, shippo_from_company, shippo_from_street1, shippo_from_street2, shippo_from_city, shippo_from_state, shippo_from_zip, shippo_from_country, shippo_from_phone, shippo_from_email, shippo_from_is_residential, shippo_parcel_length, shippo_parcel_width, shippo_parcel_height, shippo_parcel_weight, shippo_parcel_distance_unit, shippo_parcel_mass_unit, shippo_label_file_type, active_theme_name, show_ticker, section_styles) VALUES
|
|
211
|
+
(true, 15.00, 200.00, 8.00, true, 'FABRICA', 'SPARROW AI SOLUTIONS', '20 W 34th St', 'New York', 'New York', 'NY', '10001', 'US', '8852099490', 'sparrowaisolutions@gmail.com', false, 10.00, 10.00, 4.00, 1.00, 'in', 'lb', 'PDF_4x6', 'default', true, '{"homeHero":"video"}')
|
|
212
|
+
ON CONFLICT (id) DO NOTHING;
|
|
213
|
+
|
|
214
|
+
-- Site content
|
|
215
|
+
INSERT INTO public.site_content (id, content) VALUES
|
|
216
|
+
('site', '{"brandName":"FABRICA","tickerMessages":["FREE SHIPPING ON ORDERS OVER \u20b9200","30-DAY RETURNS","HOST YOUR OWN STORE IN MINUTES"]}'),
|
|
217
|
+
('home', '{"heroTitle":"Launch Your Store","heroSubtitle":"Discover powerful tools crafted for the modern merchant","heroVideoUrl":"https://www.youtube.com/embed/u9FEg5qur14?autoplay=1&mute=1&loop=1&playlist=u9FEg5qur14&controls=0&showinfo=0&rel=0&modestbranding=1&playsinline=1","footerTagline":"Contemporary commerce for the independent entrepreneur.","bestSellersTitle":"Best Sellers","collectionsTitle":"Collections","newArrivalsTitle":"New Arrivals"}'),
|
|
218
|
+
('about', '{"values":[{"title":"Craftsmanship","description":"Every feature is constructed with meticulous attention to detail by skilled engineers who take pride in their work."},{"title":"Quality","description":"We source only the finest open-source tools and frameworks, ensuring reliability and speed in every build."},{"title":"Timelessness","description":"Our designs transcend fleeting trends, offering interfaces that remain elegant and relevant season after season."}],"ctaTitle":"Experience Fabrica","heroTitle":"About Fabrica","storyTitle":"Our Story","valuesTitle":"Our Values","heroImageUrl":"/thudarum-burgundy-evening-suit.jpg","heroSubtitle":"Building seamless commerce tools for the modern entrepreneur","storyImageUrl":"/thudarum-taupe-suit-detail.jpg","ctaDescription":"Discover our latest platform of meticulously crafted storefronts and dashboards designed for the modern merchant.","storyParagraphs":["Founded with a vision to redefine modern e-commerce, Fabrica represents the perfect marriage of powerful infrastructure and intuitive design.","Every feature in our platform is meticulously crafted using proven technology and assembled by engineers who have honed their expertise over decades.","Our storefronts and admin tools are designed for the discerning merchant who appreciates quality, understands branding, and values a platform that will remain powerful and relevant for years to come."]}'),
|
|
219
|
+
('collections', '{"title":"Collections","description":"Explore our curated collections, each telling a unique story of style, craftsmanship, and modern elegance.","featuredTitle":"Crafted for Excellence","featuredDescription":"Each collection is carefully curated to offer distinctive pieces that complement your personal style."}'),
|
|
220
|
+
('shop', '{"title":"All Products"}'),
|
|
221
|
+
('page:contact', '{"body":["Reach out to our support team for help."],"title":"Contact","contact":{"email":"support@fabrica.com","phone":"+91 00000 00000","facebook":"https://facebook.com/fabricahq","whatsapp":"https://wa.me/910000000000","instagram":"https://instagram.com/fabricahq"},"description":"Contact us"}'),
|
|
222
|
+
('page:privacy', '{"body":["We respect your privacy and protect your data."],"title":"Privacy Policy","description":"Privacy information"}'),
|
|
223
|
+
('page:returns', '{"body":["30-day return policy for unworn items."],"title":"Returns","description":"Returns information"}'),
|
|
224
|
+
('page:shipping', '{"body":["Complimentary shipping rules are controlled from Admin Policies."],"title":"Shipping","description":"Shipping information"}'),
|
|
225
|
+
('page:terms', '{"body":["By using this site, you agree to our terms."],"title":"Terms of Service","description":"Terms information"}')
|
|
226
|
+
ON CONFLICT (id) DO UPDATE SET content=EXCLUDED.content, updated_at=now();
|
|
21
227
|
`;
|
|
228
|
+
|
|
229
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
230
|
+
// STORAGE POLICIES
|
|
231
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
22
232
|
export const STORAGE_POLICIES_SQL = `
|
|
23
233
|
INSERT INTO storage.buckets (id, name, public) VALUES ('pic', 'pic', true) ON CONFLICT (id) DO NOTHING;
|
|
24
234
|
DO $$ BEGIN CREATE POLICY "pic_select_all" ON storage.objects FOR SELECT USING (bucket_id = 'pic'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
@@ -26,4 +236,5 @@ DO $$ BEGIN CREATE POLICY "pic_insert_all" ON storage.objects FOR INSERT WITH CH
|
|
|
26
236
|
DO $$ BEGIN CREATE POLICY "pic_update_all" ON storage.objects FOR UPDATE USING (bucket_id = 'pic'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
27
237
|
DO $$ BEGIN CREATE POLICY "pic_delete_all" ON storage.objects FOR DELETE USING (bucket_id = 'pic'); EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
28
238
|
`;
|
|
239
|
+
|
|
29
240
|
export const FULL_SQL = `${SCHEMA_SQL}\n${SEED_SQL}\n${STORAGE_POLICIES_SQL}`;
|
package/src/ui.js
CHANGED
|
@@ -1,125 +1,197 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import boxen from 'boxen';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import gradient from 'gradient-string';
|
|
5
|
+
import figures from 'figures';
|
|
3
6
|
|
|
4
|
-
// ──
|
|
5
|
-
const
|
|
6
|
-
export const
|
|
7
|
-
export const
|
|
8
|
-
export const
|
|
9
|
-
export const
|
|
10
|
-
export const
|
|
11
|
-
export const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
// ── palette ───────────────────────────────────────────────────────────────────
|
|
8
|
+
export const orange = (t) => chalk.hex('#ff8a00')(t);
|
|
9
|
+
export const dimOrange = (t) => chalk.hex('#b65f00')(t);
|
|
10
|
+
export const bold = (t) => chalk.bold(t);
|
|
11
|
+
export const red = (t) => chalk.hex('#dc3232')(t);
|
|
12
|
+
export const dim = (t) => chalk.dim(t);
|
|
13
|
+
export const green = (t) => chalk.hex('#50c850')(t);
|
|
14
|
+
export const cyan = (t) => chalk.hex('#00c8ff')(t);
|
|
15
|
+
export const yellow = (t) => chalk.hex('#ffc846')(t);
|
|
16
|
+
export const gray = (t) => chalk.hex('#888888')(t);
|
|
17
|
+
export const white = (t) => chalk.white(t);
|
|
18
|
+
|
|
19
|
+
// strip ANSI for length calculations
|
|
20
|
+
function stripAnsi(s) { return s.replace(/\x1b\[[0-9;]*m/g, ''); }
|
|
21
|
+
|
|
22
|
+
// ── section buffer ─────────────────────────────────────────────────────────────
|
|
23
|
+
let _currentTitle = null;
|
|
24
|
+
let _lines = [];
|
|
25
|
+
let _sectionCount = 0;
|
|
26
|
+
|
|
27
|
+
function flushSection() {
|
|
28
|
+
if (_currentTitle === null) return;
|
|
29
|
+
const content = _lines.join('\n');
|
|
30
|
+
const rendered = boxen(content || ' ', {
|
|
31
|
+
title: ` ${bold(orange(_currentTitle))} `,
|
|
18
32
|
titleAlignment: 'left',
|
|
19
|
-
padding: { top: 0, bottom: 0, left:
|
|
33
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
20
34
|
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
21
35
|
borderStyle: 'round',
|
|
22
36
|
borderColor: '#ff8a00',
|
|
23
37
|
});
|
|
38
|
+
if (_sectionCount > 1) console.log(dimOrange(' │'));
|
|
39
|
+
console.log(rendered);
|
|
40
|
+
_currentTitle = null;
|
|
41
|
+
_lines = [];
|
|
24
42
|
}
|
|
25
43
|
|
|
26
|
-
|
|
27
|
-
function subStepBox(content, isError = false) {
|
|
28
|
-
return boxen(content, {
|
|
29
|
-
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
|
30
|
-
margin: { top: 0, bottom: 0, left: 4, right: 0 },
|
|
31
|
-
borderStyle: 'single',
|
|
32
|
-
borderColor: isError ? '#dc3232' : '#b65f00',
|
|
33
|
-
});
|
|
34
|
-
}
|
|
44
|
+
function addLine(text) { _lines.push(text); }
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
let _sectionCount = 0;
|
|
38
|
-
export function resetSectionCount() { _sectionCount = 0; }
|
|
46
|
+
export function resetSectionCount() { flushSection(); _sectionCount = 0; }
|
|
39
47
|
|
|
40
48
|
export function section(title) {
|
|
41
|
-
|
|
49
|
+
flushSection();
|
|
42
50
|
_sectionCount++;
|
|
43
|
-
|
|
51
|
+
_currentTitle = title;
|
|
52
|
+
_lines = [];
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
export function endSections() { flushSection(); }
|
|
56
|
+
|
|
57
|
+
export function kv(key, value) {
|
|
58
|
+
const k = bold(orange(key));
|
|
59
|
+
const arrow = dimOrange(figures.arrowRight);
|
|
60
|
+
const v = typeof value === 'string' && value.startsWith('http') ? cyan(value) : white(String(value));
|
|
61
|
+
addLine(` ${dimOrange(figures.pointer)} ${k} ${arrow} ${v}`);
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!lines || !lines.length) return;
|
|
55
|
-
const content = (isError ? lines.map((l) => red(l)) : lines.map((l) => dimOrange(l))).join('\n');
|
|
56
|
-
console.log(subStepBox(content, isError));
|
|
64
|
+
export function kvSuccess(key, value) {
|
|
65
|
+
addLine(` ${green(figures.tick)} ${bold(key)} ${dimOrange(figures.arrowRight)} ${green(String(value))}`);
|
|
57
66
|
}
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
console.log(` ${dimOrange('›')} ${bold(key)} ${dimOrange('→')} ${value}`);
|
|
68
|
+
export function kvFail(key, value) {
|
|
69
|
+
addLine(` ${red(figures.cross)} ${bold(key)} ${dimOrange(figures.arrowRight)} ${red(String(value))}`);
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
//
|
|
72
|
+
// spinner — inline stdout, then adds result line to buffer
|
|
65
73
|
const CLEAR_LINE = '\x1b[K';
|
|
66
74
|
export function spinner(text) {
|
|
67
|
-
process.stdout.write(
|
|
75
|
+
process.stdout.write(` ${dimOrange(figures.ellipsis)} ${dim(text)}`);
|
|
68
76
|
return {
|
|
69
|
-
succeed(msg) {
|
|
70
|
-
|
|
77
|
+
succeed(msg) {
|
|
78
|
+
process.stdout.write(`\r${CLEAR_LINE}`);
|
|
79
|
+
addLine(` ${green(figures.tick)} ${msg}`);
|
|
80
|
+
},
|
|
81
|
+
fail(msg) {
|
|
82
|
+
process.stdout.write(`\r${CLEAR_LINE}`);
|
|
83
|
+
addLine(` ${red(figures.cross)} ${msg}`);
|
|
84
|
+
},
|
|
71
85
|
};
|
|
72
86
|
}
|
|
73
87
|
|
|
88
|
+
export function log(text) { addLine(` ${dim(figures.line)} ${text}`); }
|
|
89
|
+
export function logInfo(text) { addLine(` ${cyan(figures.info)} ${text}`); }
|
|
90
|
+
export function logWarn(text) { addLine(` ${yellow(figures.warning)} ${yellow(text)}`); }
|
|
91
|
+
|
|
92
|
+
export function divider() { addLine(` ${dimOrange('─'.repeat(48))}`); }
|
|
93
|
+
|
|
94
|
+
// sub-step block
|
|
95
|
+
export function subBox(lines, { isError = false } = {}) {
|
|
96
|
+
if (!lines || !lines.length) return;
|
|
97
|
+
const colored = isError ? lines.map((l) => red(l)) : lines.map((l) => dim(l));
|
|
98
|
+
const content = colored.join('\n');
|
|
99
|
+
const rendered = boxen(content, {
|
|
100
|
+
padding: { top: 0, bottom: 0, left: 2, right: 2 },
|
|
101
|
+
margin: { top: 0, bottom: 0, left: 3, right: 0 },
|
|
102
|
+
borderStyle: 'single',
|
|
103
|
+
borderColor: isError ? '#dc3232' : '#b65f00',
|
|
104
|
+
});
|
|
105
|
+
for (const line of rendered.split('\n')) addLine(line);
|
|
106
|
+
}
|
|
107
|
+
|
|
74
108
|
// ── banner ────────────────────────────────────────────────────────────────────
|
|
75
109
|
export function banner() {
|
|
110
|
+
flushSection();
|
|
76
111
|
_sectionCount = 0;
|
|
112
|
+
_currentTitle = null;
|
|
113
|
+
_lines = [];
|
|
114
|
+
|
|
77
115
|
const art = [
|
|
78
|
-
'███████╗ █████╗ ██████╗ ██████╗ ██╗ ██████╗ █████╗
|
|
116
|
+
'███████╗ █████╗ ██████╗ ██████╗ ██╗ ██████╗ █████╗',
|
|
79
117
|
'██╔════╝██╔══██╗██╔══██╗██╔══██╗██║██╔════╝██╔══██╗',
|
|
80
118
|
'█████╗ ███████║██████╔╝██████╔╝██║██║ ███████║',
|
|
81
119
|
'██╔══╝ ██╔══██║██╔══██╗██╔══██╗██║██║ ██╔══██║',
|
|
82
120
|
'██║ ██║ ██║██████╔╝██║ ██║██║╚██████╗██║ ██║',
|
|
83
121
|
'╚═╝ ╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═════╝╚═╝ ╚═╝',
|
|
84
|
-
]
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
const gradientArt = gradient(['#ff8a00', '#ff4500', '#ff8a00'])(art.join('\n'));
|
|
85
125
|
|
|
86
126
|
const subtitle = [
|
|
87
|
-
|
|
88
|
-
dim('by SPARROW AI SOLUTION'),
|
|
127
|
+
cyan(' Supabase + Vercel · Deploy in minutes'),
|
|
128
|
+
dim(' by SPARROW AI SOLUTION'),
|
|
89
129
|
].join('\n');
|
|
90
130
|
|
|
91
|
-
console.log(boxen(`${
|
|
92
|
-
padding: { top:
|
|
93
|
-
margin: { top: 0, bottom: 0, left: 0, right: 0 },
|
|
131
|
+
console.log(boxen(`${gradientArt}\n\n${subtitle}`, {
|
|
132
|
+
padding: { top: 1, bottom: 1, left: 3, right: 3 },
|
|
94
133
|
borderStyle: 'double',
|
|
95
134
|
borderColor: '#ff8a00',
|
|
135
|
+
textAlignment: 'center',
|
|
96
136
|
}));
|
|
97
137
|
}
|
|
98
138
|
|
|
99
139
|
// ── help ──────────────────────────────────────────────────────────────────────
|
|
100
140
|
export function help() {
|
|
101
141
|
banner();
|
|
142
|
+
|
|
143
|
+
const commands = [
|
|
144
|
+
{ cmd: 'build', desc: 'Connect Supabase · collect secrets · deploy or run locally' },
|
|
145
|
+
{ cmd: 'list', desc: 'Show all Fabrica projects (local & cloud)' },
|
|
146
|
+
{ cmd: 'env', desc: 'View and update environment variables for any project' },
|
|
147
|
+
{ cmd: 'rerun', desc: 'Re-run or re-open an existing project' },
|
|
148
|
+
{ cmd: 'vins', desc: 'Verify & auto-install CLI dependencies (git, gh, vercel)' },
|
|
149
|
+
{ cmd: 'clean', desc: 'Remove local data, env files, or logout from Vercel / GitHub' },
|
|
150
|
+
{ cmd: 'info', desc: 'Package, bridge, repo and storage info' },
|
|
151
|
+
{ cmd: 'help', desc: 'Show this help screen' },
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
const cmdRows = commands.map(({ cmd, desc }) =>
|
|
155
|
+
` ${bold(orange(cmd.padEnd(10)))} ${dim(desc)}`
|
|
156
|
+
).join('\n');
|
|
157
|
+
|
|
158
|
+
const examples = [
|
|
159
|
+
` ${dimOrange('$')} npx fabrica-e-commerce build`,
|
|
160
|
+
` ${dimOrange('$')} npx fabrica-e-commerce vins`,
|
|
161
|
+
` ${dimOrange('$')} npx fabrica-e-commerce list`,
|
|
162
|
+
` ${dimOrange('$')} npx fabrica-e-commerce clean`,
|
|
163
|
+
` ${dimOrange('$')} npx fabrica-e-commerce env`,
|
|
164
|
+
` ${dimOrange('$')} npx fabrica-e-commerce rerun`,
|
|
165
|
+
].join('\n');
|
|
166
|
+
|
|
102
167
|
const content = [
|
|
103
|
-
bold(orange('Commands')),
|
|
168
|
+
bold(orange(' Commands')),
|
|
169
|
+
dimOrange(' ' + '─'.repeat(52)),
|
|
104
170
|
'',
|
|
105
|
-
|
|
106
|
-
` ${bold('list')} Show Fabrica projects (local & cloud)`,
|
|
107
|
-
` ${bold('env')} View and update environment variables for any project`,
|
|
108
|
-
` ${bold('rerun')} Re-run or re-open an existing project`,
|
|
109
|
-
` ${bold('vins')} Verify & auto-install CLI dependencies`,
|
|
110
|
-
` ${bold('info')} Package, bridge, repo and storage info`,
|
|
111
|
-
` ${bold('help')} Show this screen`,
|
|
171
|
+
cmdRows,
|
|
112
172
|
'',
|
|
113
|
-
bold(orange('Examples')),
|
|
173
|
+
bold(orange(' Examples')),
|
|
174
|
+
dimOrange(' ' + '─'.repeat(52)),
|
|
114
175
|
'',
|
|
115
|
-
|
|
116
|
-
` ${dimOrange('$')} npx fabrica-e-commerce env`,
|
|
117
|
-
` ${dimOrange('$')} npx fabrica-e-commerce rerun`,
|
|
118
|
-
` ${dimOrange('$')} npx fabrica-e-commerce list`,
|
|
176
|
+
examples,
|
|
119
177
|
'',
|
|
120
|
-
dim(
|
|
178
|
+
dim(` Creator: SPARROW AI SOLUTION · MIT License`),
|
|
121
179
|
].join('\n');
|
|
122
180
|
|
|
123
|
-
console.log(
|
|
124
|
-
console.log(
|
|
181
|
+
console.log(dimOrange(' │'));
|
|
182
|
+
console.log(boxen(content, {
|
|
183
|
+
title: ` ${bold(orange('Help & Commands'))} `,
|
|
184
|
+
titleAlignment: 'left',
|
|
185
|
+
padding: { top: 1, bottom: 1, left: 1, right: 1 },
|
|
186
|
+
borderStyle: 'round',
|
|
187
|
+
borderColor: '#ff8a00',
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── step progress ─────────────────────────────────────────────────────────────
|
|
192
|
+
export function stepHeader(step, total, title) {
|
|
193
|
+
endSections();
|
|
194
|
+
const badge = orange(`[${step}/${total}]`);
|
|
195
|
+
const line = `${badge} ${bold(white(title))}`;
|
|
196
|
+
console.log(`\n ${dimOrange(figures.arrowRight)} ${line}`);
|
|
125
197
|
}
|