create-brainerce-store 1.0.0
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/dist/index.d.ts +1 -0
- package/dist/index.js +502 -0
- package/package.json +44 -0
- package/templates/nextjs/base/.env.local.ejs +3 -0
- package/templates/nextjs/base/.eslintrc.json +3 -0
- package/templates/nextjs/base/gitignore +30 -0
- package/templates/nextjs/base/next.config.ts +9 -0
- package/templates/nextjs/base/package.json.ejs +30 -0
- package/templates/nextjs/base/postcss.config.mjs +9 -0
- package/templates/nextjs/base/src/app/account/page.tsx +105 -0
- package/templates/nextjs/base/src/app/auth/callback/page.tsx +99 -0
- package/templates/nextjs/base/src/app/cart/page.tsx +263 -0
- package/templates/nextjs/base/src/app/checkout/page.tsx +463 -0
- package/templates/nextjs/base/src/app/globals.css +30 -0
- package/templates/nextjs/base/src/app/layout.tsx.ejs +33 -0
- package/templates/nextjs/base/src/app/login/page.tsx +56 -0
- package/templates/nextjs/base/src/app/order-confirmation/page.tsx +191 -0
- package/templates/nextjs/base/src/app/page.tsx +95 -0
- package/templates/nextjs/base/src/app/products/[slug]/page.tsx +346 -0
- package/templates/nextjs/base/src/app/products/page.tsx +243 -0
- package/templates/nextjs/base/src/app/register/page.tsx +66 -0
- package/templates/nextjs/base/src/app/verify-email/page.tsx +291 -0
- package/templates/nextjs/base/src/components/account/order-history.tsx +184 -0
- package/templates/nextjs/base/src/components/account/profile-section.tsx +73 -0
- package/templates/nextjs/base/src/components/auth/login-form.tsx +92 -0
- package/templates/nextjs/base/src/components/auth/oauth-buttons.tsx +134 -0
- package/templates/nextjs/base/src/components/auth/register-form.tsx +177 -0
- package/templates/nextjs/base/src/components/cart/cart-item.tsx +150 -0
- package/templates/nextjs/base/src/components/cart/cart-nudges.tsx +39 -0
- package/templates/nextjs/base/src/components/cart/cart-summary.tsx +67 -0
- package/templates/nextjs/base/src/components/cart/coupon-input.tsx +131 -0
- package/templates/nextjs/base/src/components/cart/reservation-countdown.tsx +100 -0
- package/templates/nextjs/base/src/components/checkout/checkout-form.tsx +273 -0
- package/templates/nextjs/base/src/components/checkout/payment-step.tsx +124 -0
- package/templates/nextjs/base/src/components/checkout/shipping-step.tsx +111 -0
- package/templates/nextjs/base/src/components/checkout/tax-display.tsx +62 -0
- package/templates/nextjs/base/src/components/layout/footer.tsx +35 -0
- package/templates/nextjs/base/src/components/layout/header.tsx +329 -0
- package/templates/nextjs/base/src/components/products/discount-badge.tsx +36 -0
- package/templates/nextjs/base/src/components/products/product-card.tsx +94 -0
- package/templates/nextjs/base/src/components/products/product-grid.tsx +33 -0
- package/templates/nextjs/base/src/components/products/stock-badge.tsx +34 -0
- package/templates/nextjs/base/src/components/products/variant-selector.tsx +147 -0
- package/templates/nextjs/base/src/components/shared/loading-spinner.tsx +30 -0
- package/templates/nextjs/base/src/components/shared/price-display.tsx +62 -0
- package/templates/nextjs/base/src/hooks/use-search.ts +77 -0
- package/templates/nextjs/base/src/lib/brainerce.ts.ejs +59 -0
- package/templates/nextjs/base/src/lib/utils.ts +6 -0
- package/templates/nextjs/base/src/providers/store-provider.tsx.ejs +168 -0
- package/templates/nextjs/base/tailwind.config.ts +30 -0
- package/templates/nextjs/base/tsconfig.json +23 -0
- package/templates/nextjs/themes/minimal/globals.css +30 -0
- package/templates/nextjs/themes/minimal/theme.json +23 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
10
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
|
|
29
|
+
// package.json
|
|
30
|
+
var require_package = __commonJS({
|
|
31
|
+
"package.json"(exports2, module2) {
|
|
32
|
+
module2.exports = {
|
|
33
|
+
name: "create-brainerce-store",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
description: "Scaffold a production-ready e-commerce storefront connected to Brainerce",
|
|
36
|
+
bin: {
|
|
37
|
+
"create-brainerce-store": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
files: [
|
|
40
|
+
"dist",
|
|
41
|
+
"templates"
|
|
42
|
+
],
|
|
43
|
+
scripts: {
|
|
44
|
+
build: "tsup src/index.ts --format cjs --dts --clean",
|
|
45
|
+
dev: "tsup src/index.ts --format cjs --dts --watch",
|
|
46
|
+
clean: "rimraf dist"
|
|
47
|
+
},
|
|
48
|
+
dependencies: {
|
|
49
|
+
chalk: "^4.1.2",
|
|
50
|
+
commander: "^12.1.0",
|
|
51
|
+
ejs: "^3.1.10",
|
|
52
|
+
"fs-extra": "^11.2.0",
|
|
53
|
+
ora: "^5.4.1",
|
|
54
|
+
prompts: "^2.4.2"
|
|
55
|
+
},
|
|
56
|
+
devDependencies: {
|
|
57
|
+
"@types/ejs": "^3.1.5",
|
|
58
|
+
"@types/fs-extra": "^11.0.4",
|
|
59
|
+
"@types/prompts": "^2.4.9",
|
|
60
|
+
tsup: "^8.0.0",
|
|
61
|
+
typescript: "^5.4.0"
|
|
62
|
+
},
|
|
63
|
+
engines: {
|
|
64
|
+
node: ">=18"
|
|
65
|
+
},
|
|
66
|
+
keywords: [
|
|
67
|
+
"brainerce",
|
|
68
|
+
"ecommerce",
|
|
69
|
+
"storefront",
|
|
70
|
+
"scaffold",
|
|
71
|
+
"create",
|
|
72
|
+
"nextjs"
|
|
73
|
+
],
|
|
74
|
+
license: "MIT"
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// src/index.ts
|
|
80
|
+
var import_commander = require("commander");
|
|
81
|
+
|
|
82
|
+
// src/cli.ts
|
|
83
|
+
var import_prompts = __toESM(require("prompts"));
|
|
84
|
+
|
|
85
|
+
// src/utils/validate.ts
|
|
86
|
+
function validateConnectionId(id) {
|
|
87
|
+
if (!id || typeof id !== "string") {
|
|
88
|
+
return "Connection ID is required";
|
|
89
|
+
}
|
|
90
|
+
if (!id.startsWith("vc_")) {
|
|
91
|
+
return 'Connection ID must start with "vc_"';
|
|
92
|
+
}
|
|
93
|
+
if (id.length < 6) {
|
|
94
|
+
return "Connection ID is too short";
|
|
95
|
+
}
|
|
96
|
+
if (!/^vc_[a-zA-Z0-9]+$/.test(id)) {
|
|
97
|
+
return "Connection ID contains invalid characters";
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function validateProjectName(name) {
|
|
102
|
+
if (!name || typeof name !== "string") {
|
|
103
|
+
return "Project name is required";
|
|
104
|
+
}
|
|
105
|
+
if (name.length < 1) {
|
|
106
|
+
return "Project name cannot be empty";
|
|
107
|
+
}
|
|
108
|
+
if (name.length > 100) {
|
|
109
|
+
return "Project name is too long (max 100 characters)";
|
|
110
|
+
}
|
|
111
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name)) {
|
|
112
|
+
return "Project name can only contain letters, numbers, hyphens, dots, and underscores";
|
|
113
|
+
}
|
|
114
|
+
if (name.startsWith(".") || name.startsWith("-")) {
|
|
115
|
+
return "Project name cannot start with a dot or hyphen";
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/utils/package-manager.ts
|
|
121
|
+
var import_child_process = require("child_process");
|
|
122
|
+
function detectPackageManager() {
|
|
123
|
+
const userAgent = process.env.npm_config_user_agent || "";
|
|
124
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
125
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
126
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
127
|
+
if (userAgent.startsWith("npm")) return "npm";
|
|
128
|
+
const managers = ["pnpm", "yarn", "bun", "npm"];
|
|
129
|
+
for (const manager of managers) {
|
|
130
|
+
try {
|
|
131
|
+
(0, import_child_process.execSync)(`${manager} --version`, { stdio: "ignore" });
|
|
132
|
+
return manager;
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return "npm";
|
|
137
|
+
}
|
|
138
|
+
async function installDependencies(projectDir, pkgManager) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const { spawn } = require("child_process");
|
|
141
|
+
const args = pkgManager === "yarn" ? [] : ["install"];
|
|
142
|
+
const child = spawn(pkgManager, args, {
|
|
143
|
+
cwd: projectDir,
|
|
144
|
+
stdio: "ignore",
|
|
145
|
+
shell: true
|
|
146
|
+
});
|
|
147
|
+
child.on("close", (code) => {
|
|
148
|
+
if (code === 0) {
|
|
149
|
+
resolve();
|
|
150
|
+
} else {
|
|
151
|
+
reject(new Error(`${pkgManager} install exited with code ${code}`));
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
child.on("error", (err) => {
|
|
155
|
+
reject(err);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/cli.ts
|
|
161
|
+
async function runInteractive(defaults) {
|
|
162
|
+
const questions = [];
|
|
163
|
+
if (!defaults.projectName) {
|
|
164
|
+
questions.push({
|
|
165
|
+
type: "text",
|
|
166
|
+
name: "projectName",
|
|
167
|
+
message: "Project name:",
|
|
168
|
+
initial: "my-store",
|
|
169
|
+
validate: (value) => {
|
|
170
|
+
const error = validateProjectName(value);
|
|
171
|
+
return error || true;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (!defaults.connectionId) {
|
|
176
|
+
questions.push({
|
|
177
|
+
type: "text",
|
|
178
|
+
name: "connectionId",
|
|
179
|
+
message: "Connection ID:",
|
|
180
|
+
hint: "From your Brainerce dashboard (vc_...)",
|
|
181
|
+
validate: (value) => {
|
|
182
|
+
const error = validateConnectionId(value);
|
|
183
|
+
return error || true;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
questions.push(
|
|
188
|
+
{
|
|
189
|
+
type: "select",
|
|
190
|
+
name: "framework",
|
|
191
|
+
message: "Framework:",
|
|
192
|
+
choices: [
|
|
193
|
+
{ title: "Next.js 15 (recommended)", value: "nextjs" },
|
|
194
|
+
{ title: "Vite + React (coming soon)", value: "vite", disabled: true },
|
|
195
|
+
{ title: "Remix (coming soon)", value: "remix", disabled: true }
|
|
196
|
+
],
|
|
197
|
+
initial: 0
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
type: "select",
|
|
201
|
+
name: "theme",
|
|
202
|
+
message: "Theme:",
|
|
203
|
+
choices: [
|
|
204
|
+
{
|
|
205
|
+
title: "Minimal",
|
|
206
|
+
description: "Clean, neutral design with Inter font",
|
|
207
|
+
value: "minimal"
|
|
208
|
+
},
|
|
209
|
+
{ title: "Luxury (coming soon)", value: "luxury", disabled: true },
|
|
210
|
+
{ title: "Playful (coming soon)", value: "playful", disabled: true }
|
|
211
|
+
],
|
|
212
|
+
initial: 0
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
type: "select",
|
|
216
|
+
name: "pkgManager",
|
|
217
|
+
message: "Package manager:",
|
|
218
|
+
choices: [
|
|
219
|
+
{ title: `pnpm${detectPackageManager() === "pnpm" ? " (detected)" : ""}`, value: "pnpm" },
|
|
220
|
+
{ title: `npm${detectPackageManager() === "npm" ? " (detected)" : ""}`, value: "npm" },
|
|
221
|
+
{ title: `yarn${detectPackageManager() === "yarn" ? " (detected)" : ""}`, value: "yarn" },
|
|
222
|
+
{ title: `bun${detectPackageManager() === "bun" ? " (detected)" : ""}`, value: "bun" }
|
|
223
|
+
],
|
|
224
|
+
initial: ["pnpm", "npm", "yarn", "bun"].indexOf(detectPackageManager())
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
const response = await (0, import_prompts.default)(questions, {
|
|
228
|
+
onCancel: () => {
|
|
229
|
+
throw new Error("PROMPT_CANCELLED");
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
projectName: defaults.projectName || response.projectName,
|
|
234
|
+
connectionId: defaults.connectionId || response.connectionId,
|
|
235
|
+
framework: response.framework || defaults.framework || "nextjs",
|
|
236
|
+
theme: response.theme || defaults.theme || "minimal",
|
|
237
|
+
pkgManager: response.pkgManager || defaults.pkgManager || detectPackageManager()
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/scaffold.ts
|
|
242
|
+
var import_path = __toESM(require("path"));
|
|
243
|
+
var import_fs_extra = __toESM(require("fs-extra"));
|
|
244
|
+
var import_ejs = __toESM(require("ejs"));
|
|
245
|
+
async function scaffold(options) {
|
|
246
|
+
const { projectName, framework, theme } = options;
|
|
247
|
+
const targetDir = import_path.default.resolve(process.cwd(), projectName);
|
|
248
|
+
if (await import_fs_extra.default.pathExists(targetDir)) {
|
|
249
|
+
const contents = await import_fs_extra.default.readdir(targetDir);
|
|
250
|
+
if (contents.length > 0) {
|
|
251
|
+
throw new Error(`Directory "${projectName}" already exists and is not empty.`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const templatesRoot = findTemplatesDir(__dirname);
|
|
255
|
+
const baseDir = import_path.default.join(templatesRoot, framework, "base");
|
|
256
|
+
const themeDir = import_path.default.join(templatesRoot, framework, "themes", theme);
|
|
257
|
+
if (!await import_fs_extra.default.pathExists(baseDir)) {
|
|
258
|
+
throw new Error(`Template "${framework}" not found at ${baseDir}`);
|
|
259
|
+
}
|
|
260
|
+
const templateVars = {
|
|
261
|
+
projectName: options.projectName,
|
|
262
|
+
connectionId: options.connectionId,
|
|
263
|
+
storeName: options.storeName,
|
|
264
|
+
currency: options.currency,
|
|
265
|
+
language: options.language,
|
|
266
|
+
apiBaseUrl: "https://api.brainerce.com"
|
|
267
|
+
};
|
|
268
|
+
await copyWithEjs(baseDir, targetDir, templateVars);
|
|
269
|
+
if (await import_fs_extra.default.pathExists(themeDir)) {
|
|
270
|
+
const themeCss = import_path.default.join(themeDir, "globals.css");
|
|
271
|
+
if (await import_fs_extra.default.pathExists(themeCss)) {
|
|
272
|
+
await import_fs_extra.default.copy(themeCss, import_path.default.join(targetDir, "src", "app", "globals.css"));
|
|
273
|
+
}
|
|
274
|
+
const themeJson = import_path.default.join(themeDir, "theme.json");
|
|
275
|
+
if (await import_fs_extra.default.pathExists(themeJson)) {
|
|
276
|
+
await import_fs_extra.default.copy(themeJson, import_path.default.join(targetDir, "theme.json"));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function findTemplatesDir(fromDir) {
|
|
281
|
+
let dir = fromDir;
|
|
282
|
+
for (let i = 0; i < 5; i++) {
|
|
283
|
+
const candidate = import_path.default.join(dir, "templates");
|
|
284
|
+
if (import_fs_extra.default.pathExistsSync(candidate)) {
|
|
285
|
+
return candidate;
|
|
286
|
+
}
|
|
287
|
+
dir = import_path.default.dirname(dir);
|
|
288
|
+
}
|
|
289
|
+
return import_path.default.join(fromDir, "..", "templates");
|
|
290
|
+
}
|
|
291
|
+
async function copyWithEjs(srcDir, destDir, vars) {
|
|
292
|
+
const entries = await import_fs_extra.default.readdir(srcDir, { withFileTypes: true });
|
|
293
|
+
await import_fs_extra.default.ensureDir(destDir);
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
const srcPath = import_path.default.join(srcDir, entry.name);
|
|
296
|
+
let destName = entry.name;
|
|
297
|
+
if (entry.isDirectory()) {
|
|
298
|
+
await copyWithEjs(srcPath, import_path.default.join(destDir, destName), vars);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (destName === "gitignore") {
|
|
302
|
+
destName = ".gitignore";
|
|
303
|
+
}
|
|
304
|
+
if (entry.name.endsWith(".ejs")) {
|
|
305
|
+
destName = destName.replace(/\.ejs$/, "");
|
|
306
|
+
const content = await import_fs_extra.default.readFile(srcPath, "utf-8");
|
|
307
|
+
const rendered = import_ejs.default.render(content, vars);
|
|
308
|
+
await import_fs_extra.default.writeFile(import_path.default.join(destDir, destName), rendered, "utf-8");
|
|
309
|
+
} else {
|
|
310
|
+
await import_fs_extra.default.copy(srcPath, import_path.default.join(destDir, destName));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/fetch-store-info.ts
|
|
316
|
+
var import_https = __toESM(require("https"));
|
|
317
|
+
var import_http = __toESM(require("http"));
|
|
318
|
+
async function fetchStoreInfo(connectionId, baseUrl = "https://api.brainerce.com") {
|
|
319
|
+
const url = `${baseUrl}/api/vc/${connectionId}/info`;
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
const client = url.startsWith("https") ? import_https.default : import_http.default;
|
|
322
|
+
const req = client.get(url, { timeout: 1e4 }, (res) => {
|
|
323
|
+
let data = "";
|
|
324
|
+
res.on("data", (chunk) => {
|
|
325
|
+
data += chunk;
|
|
326
|
+
});
|
|
327
|
+
res.on("end", () => {
|
|
328
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
329
|
+
if (res.statusCode === 404) {
|
|
330
|
+
reject(new Error(`Connection ID "${connectionId}" not found. Check your dashboard.`));
|
|
331
|
+
} else {
|
|
332
|
+
reject(new Error(`API returned status ${res.statusCode}`));
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
const json = JSON.parse(data);
|
|
338
|
+
resolve({
|
|
339
|
+
name: json.name || json.storeName || "My Store",
|
|
340
|
+
currency: json.currency || "USD",
|
|
341
|
+
language: json.language || "en"
|
|
342
|
+
});
|
|
343
|
+
} catch {
|
|
344
|
+
reject(new Error("Invalid response from API"));
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
req.on("error", (err) => {
|
|
349
|
+
reject(new Error(`Failed to connect to Brainerce API: ${err.message}`));
|
|
350
|
+
});
|
|
351
|
+
req.on("timeout", () => {
|
|
352
|
+
req.destroy();
|
|
353
|
+
reject(new Error("Request timed out"));
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/utils/logger.ts
|
|
359
|
+
var import_chalk = __toESM(require("chalk"));
|
|
360
|
+
var logger = {
|
|
361
|
+
banner() {
|
|
362
|
+
console.log();
|
|
363
|
+
console.log(import_chalk.default.bold.cyan(" create-brainerce-store"));
|
|
364
|
+
console.log(import_chalk.default.dim(" Scaffold a production-ready e-commerce storefront"));
|
|
365
|
+
console.log();
|
|
366
|
+
},
|
|
367
|
+
info(message) {
|
|
368
|
+
console.log(import_chalk.default.cyan(message));
|
|
369
|
+
},
|
|
370
|
+
success(message) {
|
|
371
|
+
console.log(import_chalk.default.green.bold(message));
|
|
372
|
+
},
|
|
373
|
+
warn(message) {
|
|
374
|
+
console.log(import_chalk.default.yellow(` Warning: ${message}`));
|
|
375
|
+
},
|
|
376
|
+
error(message) {
|
|
377
|
+
console.log(import_chalk.default.red.bold(` Error: ${message}`));
|
|
378
|
+
},
|
|
379
|
+
step(command) {
|
|
380
|
+
console.log(import_chalk.default.dim(" $") + " " + import_chalk.default.white(command));
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// src/utils/spinner.ts
|
|
385
|
+
var import_ora = __toESM(require("ora"));
|
|
386
|
+
function createSpinner(text) {
|
|
387
|
+
return (0, import_ora.default)({
|
|
388
|
+
text,
|
|
389
|
+
color: "cyan"
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/index.ts
|
|
394
|
+
var pkg = require_package();
|
|
395
|
+
var program = new import_commander.Command();
|
|
396
|
+
program.name("create-brainerce-store").description("Scaffold a production-ready e-commerce storefront connected to Brainerce").version(pkg.version).argument("[project-name]", "Name for the project directory").option("--connection-id <id>", "Brainerce vibe-coded connection ID (vc_*)").option("--framework <framework>", "Framework to use", "nextjs").option("--theme <theme>", "Theme to apply", "minimal").option("--pkg-manager <manager>", "Package manager (npm, pnpm, yarn, bun)").option("--no-git", "Skip git initialization").option("--no-install", "Skip dependency installation").action(async (projectNameArg, options) => {
|
|
397
|
+
try {
|
|
398
|
+
logger.banner();
|
|
399
|
+
let projectName = projectNameArg;
|
|
400
|
+
let connectionId = options.connectionId;
|
|
401
|
+
let framework = options.framework;
|
|
402
|
+
let theme = options.theme;
|
|
403
|
+
let pkgManager = options.pkgManager;
|
|
404
|
+
const skipGit = options.git === false;
|
|
405
|
+
const skipInstall = options.install === false;
|
|
406
|
+
if (!projectName || !connectionId) {
|
|
407
|
+
const answers = await runInteractive({
|
|
408
|
+
projectName,
|
|
409
|
+
connectionId,
|
|
410
|
+
framework,
|
|
411
|
+
theme,
|
|
412
|
+
pkgManager
|
|
413
|
+
});
|
|
414
|
+
projectName = answers.projectName;
|
|
415
|
+
connectionId = answers.connectionId;
|
|
416
|
+
framework = answers.framework;
|
|
417
|
+
theme = answers.theme;
|
|
418
|
+
pkgManager = answers.pkgManager;
|
|
419
|
+
}
|
|
420
|
+
const nameError = validateProjectName(projectName);
|
|
421
|
+
if (nameError) {
|
|
422
|
+
logger.error(nameError);
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
const connError = validateConnectionId(connectionId);
|
|
426
|
+
if (connError) {
|
|
427
|
+
logger.error(connError);
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
const spinner = createSpinner("Fetching store info...");
|
|
431
|
+
spinner.start();
|
|
432
|
+
let storeInfo;
|
|
433
|
+
try {
|
|
434
|
+
storeInfo = await fetchStoreInfo(connectionId);
|
|
435
|
+
spinner.succeed(
|
|
436
|
+
`Store: "${storeInfo.name}" | ${storeInfo.currency} | ${storeInfo.language}`
|
|
437
|
+
);
|
|
438
|
+
} catch (err) {
|
|
439
|
+
spinner.fail("Could not fetch store info");
|
|
440
|
+
logger.warn(
|
|
441
|
+
"Using defaults. Make sure the connection ID is correct and the Brainerce API is reachable."
|
|
442
|
+
);
|
|
443
|
+
storeInfo = { name: projectName, currency: "USD", language: "en" };
|
|
444
|
+
}
|
|
445
|
+
if (!pkgManager) {
|
|
446
|
+
pkgManager = detectPackageManager();
|
|
447
|
+
}
|
|
448
|
+
const scaffoldSpinner = createSpinner("Scaffolding project...");
|
|
449
|
+
scaffoldSpinner.start();
|
|
450
|
+
await scaffold({
|
|
451
|
+
projectName,
|
|
452
|
+
connectionId,
|
|
453
|
+
framework,
|
|
454
|
+
theme,
|
|
455
|
+
storeName: storeInfo.name,
|
|
456
|
+
currency: storeInfo.currency,
|
|
457
|
+
language: storeInfo.language
|
|
458
|
+
});
|
|
459
|
+
scaffoldSpinner.succeed(`Scaffolded into ./${projectName}`);
|
|
460
|
+
if (!skipGit) {
|
|
461
|
+
const gitSpinner = createSpinner("Initializing git...");
|
|
462
|
+
gitSpinner.start();
|
|
463
|
+
try {
|
|
464
|
+
const { execSync: execSync2 } = require("child_process");
|
|
465
|
+
execSync2("git init", { cwd: projectName, stdio: "ignore" });
|
|
466
|
+
execSync2("git add -A", { cwd: projectName, stdio: "ignore" });
|
|
467
|
+
execSync2('git commit -m "Initial commit from create-brainerce-store"', {
|
|
468
|
+
cwd: projectName,
|
|
469
|
+
stdio: "ignore"
|
|
470
|
+
});
|
|
471
|
+
gitSpinner.succeed("Git initialized");
|
|
472
|
+
} catch {
|
|
473
|
+
gitSpinner.fail("Git initialization failed (git may not be installed)");
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (!skipInstall) {
|
|
477
|
+
const installSpinner = createSpinner(`Installing dependencies with ${pkgManager}...`);
|
|
478
|
+
installSpinner.start();
|
|
479
|
+
try {
|
|
480
|
+
await installDependencies(projectName, pkgManager);
|
|
481
|
+
installSpinner.succeed("Dependencies installed");
|
|
482
|
+
} catch {
|
|
483
|
+
installSpinner.fail("Dependency installation failed");
|
|
484
|
+
logger.warn(`Run \`cd ${projectName} && ${pkgManager} install\` manually.`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
logger.success("\nYour Brainerce store is ready!\n");
|
|
488
|
+
logger.info("Next steps:");
|
|
489
|
+
logger.step(`cd ${projectName}`);
|
|
490
|
+
logger.step(`${pkgManager}${pkgManager === "npm" ? " run" : ""} dev`);
|
|
491
|
+
logger.info(`
|
|
492
|
+
Your store will be running at http://localhost:3000`);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
if (err instanceof Error && err.message === "PROMPT_CANCELLED") {
|
|
495
|
+
logger.info("\nSetup cancelled.");
|
|
496
|
+
process.exit(0);
|
|
497
|
+
}
|
|
498
|
+
logger.error(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-brainerce-store",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold a production-ready e-commerce storefront connected to Brainerce",
|
|
5
|
+
"bin": {
|
|
6
|
+
"create-brainerce-store": "dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"templates"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup src/index.ts --format cjs --dts --clean",
|
|
14
|
+
"dev": "tsup src/index.ts --format cjs --dts --watch",
|
|
15
|
+
"clean": "rimraf dist"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"chalk": "^4.1.2",
|
|
19
|
+
"commander": "^12.1.0",
|
|
20
|
+
"ejs": "^3.1.10",
|
|
21
|
+
"fs-extra": "^11.2.0",
|
|
22
|
+
"ora": "^5.4.1",
|
|
23
|
+
"prompts": "^2.4.2"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/ejs": "^3.1.5",
|
|
27
|
+
"@types/fs-extra": "^11.0.4",
|
|
28
|
+
"@types/prompts": "^2.4.9",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.4.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"brainerce",
|
|
37
|
+
"ecommerce",
|
|
38
|
+
"storefront",
|
|
39
|
+
"scaffold",
|
|
40
|
+
"create",
|
|
41
|
+
"nextjs"
|
|
42
|
+
],
|
|
43
|
+
"license": "MIT"
|
|
44
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# dependencies
|
|
2
|
+
/node_modules
|
|
3
|
+
/.pnp
|
|
4
|
+
.pnp.js
|
|
5
|
+
|
|
6
|
+
# testing
|
|
7
|
+
/coverage
|
|
8
|
+
|
|
9
|
+
# next.js
|
|
10
|
+
/.next/
|
|
11
|
+
/out/
|
|
12
|
+
|
|
13
|
+
# production
|
|
14
|
+
/build
|
|
15
|
+
|
|
16
|
+
# misc
|
|
17
|
+
.DS_Store
|
|
18
|
+
*.pem
|
|
19
|
+
|
|
20
|
+
# debug
|
|
21
|
+
npm-debug.log*
|
|
22
|
+
yarn-debug.log*
|
|
23
|
+
yarn-error.log*
|
|
24
|
+
|
|
25
|
+
# local env files
|
|
26
|
+
.env*.local
|
|
27
|
+
|
|
28
|
+
# typescript
|
|
29
|
+
*.tsbuildinfo
|
|
30
|
+
next-env.d.ts
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= projectName %>",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"brainerce": "^1.0.0",
|
|
13
|
+
"next": "^15.0.0",
|
|
14
|
+
"react": "^19.0.0",
|
|
15
|
+
"react-dom": "^19.0.0",
|
|
16
|
+
"clsx": "^2.1.0",
|
|
17
|
+
"tailwind-merge": "^2.2.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^20.0.0",
|
|
21
|
+
"@types/react": "^19.0.0",
|
|
22
|
+
"@types/react-dom": "^19.0.0",
|
|
23
|
+
"autoprefixer": "^10.4.0",
|
|
24
|
+
"eslint": "^8.57.0",
|
|
25
|
+
"eslint-config-next": "^15.0.0",
|
|
26
|
+
"postcss": "^8.4.0",
|
|
27
|
+
"tailwindcss": "^3.4.0",
|
|
28
|
+
"typescript": "^5.4.0"
|
|
29
|
+
}
|
|
30
|
+
}
|