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/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
- console.log('A browser/device flow will open so you can log in with "gh auth login"...');
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
- console.log('Continuing with GitHub API publishing; if Git later asks for credentials, run: gh auth setup-git');
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
- console.log(dimOrange(` Note: this is named "${actualName}", not "${repoName}", because your GitHub account already had a fork of ${source}.`));
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
- CREATE TABLE IF NOT EXISTS public.site_content (id text NOT NULL, content jsonb NOT NULL, updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT site_content_pkey PRIMARY KEY (id));
4
- CREATE TABLE IF NOT EXISTS public.collections (id text NOT NULL, name text NOT NULL, description text NOT NULL, image_url text NOT NULL, item_count_label text NOT NULL, sort_order integer NOT NULL DEFAULT 0, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT collections_pkey PRIMARY KEY (id));
5
- CREATE TABLE IF NOT EXISTS public.products (id text NOT NULL, name text NOT NULL, price numeric NOT NULL CHECK (price >= 0), main_image_url text NOT NULL, gallery_image_urls text[] NOT NULL DEFAULT '{}', category_label text, description text NOT NULL, details text[] NOT NULL DEFAULT '{}', sizes text[] NOT NULL DEFAULT '{}', section text NOT NULL DEFAULT 'general' CHECK (section = ANY (ARRAY['general','new_arrivals','best_sellers'])), collection_ids text[] NOT NULL DEFAULT '{}', sort_order integer NOT NULL DEFAULT 0, is_active boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT products_pkey PRIMARY KEY (id));
6
- CREATE TABLE IF NOT EXISTS public.app_users (id uuid NOT NULL DEFAULT gen_random_uuid(), email text NOT NULL UNIQUE, first_name text NOT NULL, last_name text NOT NULL, phone text NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT app_users_pkey PRIMARY KEY (id));
7
- CREATE TABLE IF NOT EXISTS public.saved_addresses (id uuid NOT NULL DEFAULT gen_random_uuid(), user_id uuid NOT NULL, label text NOT NULL, first_name text NOT NULL, last_name text NOT NULL, phone text NOT NULL, address text NOT NULL, apartment text, city text NOT NULL, state text NOT NULL, zip_code text NOT NULL, country text NOT NULL DEFAULT 'India', is_default boolean NOT NULL DEFAULT false, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT saved_addresses_pkey PRIMARY KEY (id), CONSTRAINT saved_addresses_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.app_users(id));
8
- CREATE TABLE IF NOT EXISTS public.order_policies (id boolean NOT NULL DEFAULT true CHECK (id = true), shipping_amount numeric NOT NULL DEFAULT 15 CHECK (shipping_amount >= 0), free_shipping_threshold numeric NOT NULL DEFAULT 200 CHECK (free_shipping_threshold >= 0), tax_rate numeric NOT NULL DEFAULT 8 CHECK (tax_rate >= 0), automatic_shipping_enabled boolean NOT NULL DEFAULT false, shippo_from_name text NOT NULL DEFAULT '', shippo_from_company text NOT NULL DEFAULT '', shippo_from_street1 text NOT NULL DEFAULT '', shippo_from_street2 text NOT NULL DEFAULT '', shippo_from_city text NOT NULL DEFAULT '', shippo_from_state text NOT NULL DEFAULT '', shippo_from_zip text NOT NULL DEFAULT '', shippo_from_country text NOT NULL DEFAULT 'IN', shippo_from_phone text NOT NULL DEFAULT '', shippo_from_email text NOT NULL DEFAULT '', shippo_from_is_residential boolean NOT NULL DEFAULT false, shippo_parcel_length numeric NOT NULL DEFAULT 10 CHECK (shippo_parcel_length > 0), shippo_parcel_width numeric NOT NULL DEFAULT 10 CHECK (shippo_parcel_width > 0), shippo_parcel_height numeric NOT NULL DEFAULT 4 CHECK (shippo_parcel_height > 0), shippo_parcel_weight numeric NOT NULL DEFAULT 1 CHECK (shippo_parcel_weight > 0), shippo_parcel_distance_unit text NOT NULL DEFAULT 'in' CHECK (shippo_parcel_distance_unit = ANY (ARRAY['in','cm'])), shippo_parcel_mass_unit text NOT NULL DEFAULT 'lb' CHECK (shippo_parcel_mass_unit = ANY (ARRAY['lb','oz','g','kg'])), 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'])), active_theme_name text DEFAULT 'default', updated_at timestamptz NOT NULL DEFAULT now(), show_ticker boolean NOT NULL DEFAULT true, section_styles jsonb NOT NULL DEFAULT '{"homeHero":"video"}', CONSTRAINT order_policies_pkey PRIMARY KEY (id));
9
- CREATE TABLE IF NOT EXISTS public.coupons (code text NOT NULL, label text NOT NULL, coupon_type text NOT NULL CHECK (coupon_type = ANY (ARRAY['universal','one_time'])), discount_type text NOT NULL CHECK (discount_type = ANY (ARRAY['percent','amount'])), discount_value numeric NOT NULL CHECK (discount_value >= 0), active boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT coupons_pkey PRIMARY KEY (code));
10
- CREATE TABLE IF NOT EXISTS public.orders (id text NOT NULL, user_id uuid, user_email text NOT NULL, status text NOT NULL DEFAULT 'placed' CHECK (status = ANY (ARRAY['placed','packed','in_transit','delivered'])), payment_method text NOT NULL CHECK (payment_method = ANY (ARRAY['cod','razorpay'])), payment_verified boolean NOT NULL DEFAULT false, razorpay_payment_id text, coupon_code text, shipping_address jsonb NOT NULL, totals jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT orders_pkey PRIMARY KEY (id), CONSTRAINT orders_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.app_users(id), CONSTRAINT orders_coupon_code_fkey FOREIGN KEY (coupon_code) REFERENCES public.coupons(code));
11
- CREATE TABLE IF NOT EXISTS public.order_items (id uuid NOT NULL DEFAULT gen_random_uuid(), order_id text NOT NULL, product_id text, product_name text NOT NULL, product_image text, size text NOT NULL, quantity integer NOT NULL CHECK (quantity > 0), unit_price numeric NOT NULL CHECK (unit_price >= 0), line_total numeric NOT NULL CHECK (line_total >= 0), created_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT order_items_pkey PRIMARY KEY (id), CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id) REFERENCES public.orders(id), CONSTRAINT order_items_product_id_fkey FOREIGN KEY (product_id) REFERENCES public.products(id));
12
- CREATE TABLE IF NOT EXISTS public.themes (id uuid NOT NULL DEFAULT gen_random_uuid(), name text NOT NULL UNIQUE, label text NOT NULL, colors jsonb NOT NULL, is_active boolean NOT NULL DEFAULT true, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT themes_pkey PRIMARY KEY (id));
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
- INSERT INTO public.collections (id, name, description, image_url, item_count_label, sort_order) VALUES ('contemporary','Contemporary Collection','Modern cuts and innovative styling for the forward-thinking gentleman.','/thudarum-sky-blue-blazer.jpg','10 items',30),('evening','Evening Collection','Luxurious velvet and satin pieces designed to make a statement at formal occasions.','/thudarum-navy-velvet-blazer.jpg','6 items',40),('executive','Executive Collection','Bold, sophisticated pieces for the modern power dresser.','/thudarum-burgundy-evening-suit.jpg','12 items',10),('heritage','Heritage Collection','Classic tailoring with timeless appeal.','/thudarum-green-check-blazer.jpg','8 items',20) ON CONFLICT (id) DO NOTHING;
16
- 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 ('classic-taupe-double-breasted-suit','Classic Taupe Double-Breasted Suit',1289.00,'/thudarum-taupe-suit-hero.jpg',ARRAY['/thudarum-taupe-suit-detail.jpg'],'Suits','An impeccably tailored double-breasted suit in refined taupe wool.',ARRAY['100% Italian wool','Double-breasted closure'],ARRAY['38','40','42','44'],'new_arrivals',ARRAY['executive','contemporary'],10,true),('navy-velvet-double-breasted-jacket','Navy Velvet Double-Breasted Jacket',1195.00,'/thudarum-navy-velvet-blazer.jpg',ARRAY['/thudarum-navy-velvet-detail.jpg'],'Blazers','A luxurious navy velvet jacket with elevated texture.',ARRAY['Italian cotton velvet','Evening-ready finish'],ARRAY['38','40','42','44'],'best_sellers',ARRAY['evening'],20,true) ON CONFLICT (id) DO NOTHING;
17
- INSERT INTO public.coupons (code,label,coupon_type,discount_type,discount_value,active) VALUES ('WELCOME10','Welcome 10%','universal','percent',10.00,true) ON CONFLICT (code) DO NOTHING;
18
- INSERT INTO public.order_policies (id, shipping_amount, free_shipping_threshold, tax_rate, active_theme_name, show_ticker, section_styles) VALUES (true,15.00,200.00,8.00,'default',true,'{"homeHero":"video"}') ON CONFLICT (id) DO NOTHING;
19
- INSERT INTO public.themes (name,label,colors,is_active) VALUES ('default','Minimalist White','{"background":"oklch(0.985 0 0)","foreground":"oklch(0.145 0 0)","primary":"oklch(0.205 0 0)"}',true),('golden','Light Golden','{"background":"oklch(0.98 0.01 70)","foreground":"oklch(0.25 0.08 50)","primary":"oklch(0.70 0.18 62)"}',true),('midnight','Midnight Dark','{"background":"oklch(0.15 0.02 240)","foreground":"oklch(0.98 0.01 240)","primary":"oklch(0.6 0.15 250)"}',true) ON CONFLICT (name) DO NOTHING;
20
- INSERT INTO public.site_content (id,content) VALUES ('site','{"brandName":"FABRICA","tickerMessages":["FREE SHIPPING ON ORDERS OVER ₹200","30-DAY RETURNS","HOST YOUR OWN STORE IN MINUTES"]}'),('home','{"heroTitle":"Launch Your Store","heroSubtitle":"Discover powerful tools crafted for the modern merchant","bestSellersTitle":"Best Sellers","collectionsTitle":"Collections","newArrivalsTitle":"New Arrivals"}') ON CONFLICT (id) DO NOTHING;
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
- // ── color helpers (raw ANSI, no deps) ────────────────────────────────────────
5
- const ESC = '\x1b[';
6
- export const orange = (t) => `${ESC}38;2;255;138;0m${t}${ESC}0m`;
7
- export const dimOrange = (t) => `${ESC}38;2;182;95;0m${t}${ESC}0m`;
8
- export const bold = (t) => `${ESC}1m${t}${ESC}0m`;
9
- export const red = (t) => `${ESC}38;2;220;50;50m${t}${ESC}0m`;
10
- export const dim = (t) => `${ESC}2m${t}${ESC}0m`;
11
- export const green = (t) => `${ESC}38;2;80;200;80m${t}${ESC}0m`;
12
-
13
- // ── boxen presets ─────────────────────────────────────────────────────────────
14
- // Main section box rounded corners, orange border
15
- function sectionBox(content, title) {
16
- return boxen(content, {
17
- title: title ? bold(orange(title)) : undefined,
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: 1, right: 1 },
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
- // Sub-step box single border, indented, dim orange or red
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
- // ── section tracking (connector lines between boxes) ───────────────────────
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
- if (_sectionCount > 0) console.log(orange(' │'));
49
+ flushSection();
42
50
  _sectionCount++;
43
- console.log(sectionBox('', title));
51
+ _currentTitle = title;
52
+ _lines = [];
44
53
  }
45
54
 
46
- // Print content in a section-style box (with optional title)
47
- export function box(lines, title) {
48
- const content = lines.join('\n');
49
- console.log(sectionBox(content, title));
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
- // Print a sub-step box (indented, for raw tool output)
53
- export function subBox(lines, { isError = false } = {}) {
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
- // kv inside current flow
60
- export function kv(key, value) {
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
- // ── spinner ───────────────────────────────────────────────────────────────────
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(' ' + dimOrange('○ ') + text);
75
+ process.stdout.write(` ${dimOrange(figures.ellipsis)} ${dim(text)}`);
68
76
  return {
69
- succeed(msg) { process.stdout.write(`\r${CLEAR_LINE} ${orange('✓')} ${msg}\n`); },
70
- fail(msg) { process.stdout.write(`\r${CLEAR_LINE} ${red('✗')} ${msg}\n`); },
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
- ].map((l) => orange(l)).join('\n');
122
+ ];
123
+
124
+ const gradientArt = gradient(['#ff8a00', '#ff4500', '#ff8a00'])(art.join('\n'));
85
125
 
86
126
  const subtitle = [
87
- dimOrange('CMD OAUTH BRIDGE // SUPABASE + VERCEL DEPLOYER'),
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(`${art}\n\n${subtitle}`, {
92
- padding: { top: 0, bottom: 0, left: 1, right: 1 },
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
- ` ${bold('build')} Connect Supabase, collect secrets, deploy or run locally`,
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
- ` ${dimOrange('$')} npx fabrica-e-commerce build`,
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('Creator: SPARROW AI SOLUTION'),
178
+ dim(` Creator: SPARROW AI SOLUTION · MIT License`),
121
179
  ].join('\n');
122
180
 
123
- console.log(orange(' │'));
124
- console.log(sectionBox(content, 'Help'));
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
  }