@vitrine-kit/vitrine 0.3.1 → 0.4.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.js +102 -83
- package/kit/registry/README.md +9 -0
- package/kit/registry/_index.json +74 -0
- package/kit/registry/cart/docs/cart.md +13 -0
- package/kit/registry/cart/feature.json +51 -0
- package/kit/registry/cart/files/app/(frontend)/cart/page.tsx +21 -0
- package/kit/registry/cart/files/app/api/cart/route.ts +41 -0
- package/kit/registry/cart/files/components/cart/AddToCart.tsx +40 -0
- package/kit/registry/cart/files/components/cart/CartIndicator.tsx +11 -0
- package/kit/registry/cart/files/components/cart/CartLineItem.tsx +27 -0
- package/kit/registry/cart/files/components/cart/CartSummary.tsx +31 -0
- package/kit/registry/cart/files/components/cart/CartView.tsx +31 -0
- package/kit/registry/cart/files/lib/cart/data.ts +12 -0
- package/kit/registry/cart/files/lib/cart/register.ts +10 -0
- package/kit/registry/catalog/docs/catalog.md +18 -0
- package/kit/registry/catalog/feature.json +43 -0
- package/kit/registry/catalog/files/components/catalog/CategoryNav.tsx +26 -0
- package/kit/registry/catalog/files/components/catalog/ProductCard.tsx +42 -0
- package/kit/registry/catalog/files/components/catalog/ProductGrid.tsx +25 -0
- package/kit/registry/catalog/files/lib/catalog/data.ts +24 -0
- package/kit/registry/catalog/files/lib/catalog/register.ts +8 -0
- package/kit/registry/checkout/docs/checkout.md +17 -0
- package/kit/registry/checkout/feature.json +47 -0
- package/kit/registry/checkout/files/app/api/checkout/route.ts +14 -0
- package/kit/registry/checkout/files/components/checkout/CheckoutButton.tsx +31 -0
- package/kit/registry/checkout/files/lib/checkout/fulfill.ts +72 -0
- package/kit/registry/checkout/files/lib/checkout/register.ts +8 -0
- package/kit/registry/checkout-paddle/docs/checkout-paddle.md +22 -0
- package/kit/registry/checkout-paddle/feature.json +62 -0
- package/kit/registry/checkout-paddle/files/app/api/webhooks/paddle/route.ts +22 -0
- package/kit/registry/checkout-paddle/files/lib/checkout-paddle/provider.ts +59 -0
- package/kit/registry/checkout-paddle/files/lib/checkout-paddle/register.ts +7 -0
- package/kit/registry/checkout-stripe/docs/checkout-stripe.md +17 -0
- package/kit/registry/checkout-stripe/feature.json +54 -0
- package/kit/registry/checkout-stripe/files/app/api/webhooks/stripe/route.ts +23 -0
- package/kit/registry/checkout-stripe/files/lib/checkout-stripe/provider.ts +73 -0
- package/kit/registry/checkout-stripe/files/lib/checkout-stripe/register.ts +7 -0
- package/kit/registry/checkout-yookassa/docs/checkout-yookassa.md +20 -0
- package/kit/registry/checkout-yookassa/feature.json +52 -0
- package/kit/registry/checkout-yookassa/files/app/api/webhooks/yookassa/route.ts +22 -0
- package/kit/registry/checkout-yookassa/files/lib/checkout-yookassa/provider.ts +82 -0
- package/kit/registry/checkout-yookassa/files/lib/checkout-yookassa/register.ts +7 -0
- package/kit/registry/product-page/docs/product-page.md +16 -0
- package/kit/registry/product-page/feature.json +39 -0
- package/kit/registry/product-page/files/components/product/ProductGallery.tsx +37 -0
- package/kit/registry/product-page/files/components/product/ProductView.tsx +42 -0
- package/kit/registry/product-page/files/lib/product/data.ts +10 -0
- package/kit/registry/seo/docs/seo.md +14 -0
- package/kit/registry/seo/feature.json +35 -0
- package/kit/registry/seo/files/components/seo/JsonLd.tsx +15 -0
- package/kit/registry/seo/files/lib/seo/jsonld.ts +29 -0
- package/kit/registry/seo/files/lib/seo/metadata.ts +35 -0
- package/kit/registry/stubs/route-glue.d.ts +45 -0
- package/kit/registry/tsconfig.json +21 -0
- package/kit/registry/tsconfig.routes.json +9 -0
- package/kit/templates/README.md +27 -0
- package/kit/templates/backend-payload/files/.dockerignore +8 -0
- package/kit/templates/backend-payload/files/Dockerfile +29 -0
- package/kit/templates/backend-payload/files/app/(payload)/admin/[[...segments]]/not-found.tsx +18 -0
- package/kit/templates/backend-payload/files/app/(payload)/admin/[[...segments]]/page.tsx +18 -0
- package/kit/templates/backend-payload/files/app/(payload)/admin/importMap.js +3 -0
- package/kit/templates/backend-payload/files/app/(payload)/api/[...slug]/route.ts +18 -0
- package/kit/templates/backend-payload/files/app/(payload)/api/graphql/route.ts +6 -0
- package/kit/templates/backend-payload/files/app/(payload)/api/graphql-playground/route.ts +5 -0
- package/kit/templates/backend-payload/files/app/(payload)/custom.scss +1 -0
- package/kit/templates/backend-payload/files/app/(payload)/layout.tsx +23 -0
- package/kit/templates/backend-payload/files/docker-compose.yml +35 -0
- package/kit/templates/backend-payload/files/lib/adapter/db-decision.ts +43 -0
- package/kit/templates/backend-payload/files/lib/adapter/db-ping.ts +29 -0
- package/kit/templates/backend-payload/files/lib/adapter/db.ts +27 -0
- package/kit/templates/backend-payload/files/lib/adapter/index.ts +31 -0
- package/kit/templates/backend-payload/files/lib/adapter/map.ts +112 -0
- package/kit/templates/backend-payload/files/lib/adapter/payload-catalog.ts +79 -0
- package/kit/templates/backend-payload/files/lib/adapter/payload-commerce.ts +154 -0
- package/kit/templates/backend-payload/files/lib/adapter/payload-types.ts +50 -0
- package/kit/templates/backend-payload/files/lib/seed/admin.ts +23 -0
- package/kit/templates/backend-payload/files/lib/seed/demo.ts +79 -0
- package/kit/templates/backend-payload/files/lib/seed/guards.ts +5 -0
- package/kit/templates/backend-payload/files/lib/seed/richtext.ts +26 -0
- package/kit/templates/backend-payload/files/lib/seed/run.ts +51 -0
- package/kit/templates/backend-payload/files/next.config.mjs +10 -0
- package/kit/templates/backend-payload/files/payload.config.ts +32 -0
- package/kit/templates/backend-payload/files/seed-assets/placeholder-1.svg +5 -0
- package/kit/templates/backend-payload/files/seed-assets/placeholder-2.svg +5 -0
- package/kit/templates/backend-payload/files/seed-assets/placeholder-3.svg +5 -0
- package/kit/templates/backend-payload/files/seed-assets/placeholder-4.svg +5 -0
- package/kit/templates/backend-payload/files/seed-assets/placeholder-5.svg +5 -0
- package/kit/templates/backend-vendure/files/.dockerignore +8 -0
- package/kit/templates/backend-vendure/files/Dockerfile +24 -0
- package/kit/templates/backend-vendure/files/docker-compose.yml +50 -0
- package/kit/templates/backend-vendure/files/lib/adapter/graphql.ts +27 -0
- package/kit/templates/backend-vendure/files/lib/adapter/index.ts +20 -0
- package/kit/templates/backend-vendure/files/lib/adapter/map.ts +107 -0
- package/kit/templates/backend-vendure/files/lib/adapter/vendure-catalog.ts +62 -0
- package/kit/templates/backend-vendure/files/lib/adapter/vendure-commerce.ts +76 -0
- package/kit/templates/backend-vendure/files/lib/adapter/vendure-types.ts +61 -0
- package/kit/templates/backend-vendure/files/lib/seed/populate.ts +22 -0
- package/kit/templates/backend-vendure/files/src/index.ts +15 -0
- package/kit/templates/backend-vendure/files/vendure-config.ts +56 -0
- package/kit/templates/base/files/.claude/commands/add-feature.md +18 -0
- package/kit/templates/base/files/.claude/commands/design.md +14 -0
- package/kit/templates/base/files/.claude/commands/doctor.md +15 -0
- package/kit/templates/base/files/.claude/commands/setup.md +15 -0
- package/kit/templates/base/files/.claude/commands/update.md +16 -0
- package/kit/templates/base/files/AGENTS.md +20 -0
- package/kit/templates/base/files/app/(frontend)/categories/[slug]/page.tsx +28 -0
- package/kit/templates/base/files/app/(frontend)/layout.tsx +32 -0
- package/kit/templates/base/files/app/(frontend)/page.tsx +27 -0
- package/kit/templates/base/files/app/(frontend)/products/[slug]/page.tsx +43 -0
- package/kit/templates/base/files/app/globals.css +6 -0
- package/kit/templates/base/files/components/layout/SiteFooter.tsx +15 -0
- package/kit/templates/base/files/components/layout/SiteHeader.tsx +23 -0
- package/kit/templates/base/files/design/README.md +9 -0
- package/kit/templates/base/files/lib/site.ts +10 -0
- package/kit/templates/base/files/next.config.mjs +7 -0
- package/kit/templates/base/files/postcss.config.mjs +7 -0
- package/kit/templates/base/files/public/.gitkeep +0 -0
- package/kit/templates/base/files/tailwind.config.ts +13 -0
- package/kit/templates/base/files/tsconfig.json +25 -0
- package/kit/templates/tsconfig.json +18 -0
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -121,25 +121,36 @@ function loadProject(root) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
// src/registry.ts
|
|
124
|
-
import { existsSync as
|
|
125
|
-
import { dirname as
|
|
124
|
+
import { existsSync as existsSync5 } from "fs";
|
|
125
|
+
import { dirname as dirname4, join as join5, resolve as resolve4 } from "path";
|
|
126
126
|
import { featureManifestSchema } from "@vitrine-kit/contracts";
|
|
127
127
|
|
|
128
|
+
// src/bundled-kit.ts
|
|
129
|
+
import { existsSync as existsSync3 } from "fs";
|
|
130
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
131
|
+
import { fileURLToPath } from "url";
|
|
132
|
+
function bundledKitRoot() {
|
|
133
|
+
const distDir = dirname3(fileURLToPath(import.meta.url));
|
|
134
|
+
const candidate = join3(distDir, "..", "kit");
|
|
135
|
+
if (existsSync3(join3(candidate, "registry", "_index.json"))) return candidate;
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
128
139
|
// src/cache.ts
|
|
129
|
-
import { cpSync, existsSync as
|
|
130
|
-
import { join as
|
|
140
|
+
import { cpSync, existsSync as existsSync4, rmSync } from "fs";
|
|
141
|
+
import { join as join4, resolve as resolve3 } from "path";
|
|
131
142
|
function vitrineHome() {
|
|
132
143
|
if (process.env.VITRINE_HOME) return resolve3(process.env.VITRINE_HOME);
|
|
133
144
|
const home = process.env.USERPROFILE ?? process.env.HOME;
|
|
134
145
|
if (!home) throw new Error("[vitrine] could not determine the home directory (HOME/USERPROFILE)");
|
|
135
|
-
return
|
|
146
|
+
return join4(home, ".vitrine");
|
|
136
147
|
}
|
|
137
148
|
function cachePaths(home = vitrineHome()) {
|
|
138
149
|
return {
|
|
139
150
|
root: home,
|
|
140
|
-
registry:
|
|
141
|
-
templates:
|
|
142
|
-
meta:
|
|
151
|
+
registry: join4(home, "registry"),
|
|
152
|
+
templates: join4(home, "templates"),
|
|
153
|
+
meta: join4(home, "kit.json")
|
|
143
154
|
};
|
|
144
155
|
}
|
|
145
156
|
function readKitMeta(home = vitrineHome()) {
|
|
@@ -151,7 +162,7 @@ function writeKitMeta(home, meta) {
|
|
|
151
162
|
`);
|
|
152
163
|
}
|
|
153
164
|
function readIndex(registryRoot) {
|
|
154
|
-
const file =
|
|
165
|
+
const file = join4(registryRoot, "_index.json");
|
|
155
166
|
return exists(file) ? readJson(file) : null;
|
|
156
167
|
}
|
|
157
168
|
function computeChangelog(oldIndex, newIndex) {
|
|
@@ -180,16 +191,16 @@ function formatChangelog(entries) {
|
|
|
180
191
|
}
|
|
181
192
|
function populateCache(fromDir, opts = {}) {
|
|
182
193
|
const home = opts.home ?? vitrineHome();
|
|
183
|
-
const srcRegistry =
|
|
184
|
-
const srcTemplates =
|
|
185
|
-
if (!
|
|
194
|
+
const srcRegistry = join4(fromDir, "registry");
|
|
195
|
+
const srcTemplates = join4(fromDir, "templates");
|
|
196
|
+
if (!existsSync4(join4(srcRegistry, "_index.json"))) {
|
|
186
197
|
throw new Error(`[vitrine] "${fromDir}" has no registry/_index.json \u2014 not a kit tree`);
|
|
187
198
|
}
|
|
188
199
|
const paths = cachePaths(home);
|
|
189
200
|
const oldIndex = readIndex(paths.registry);
|
|
190
201
|
rmSync(paths.registry, { recursive: true, force: true });
|
|
191
202
|
cpSync(srcRegistry, paths.registry, { recursive: true });
|
|
192
|
-
if (
|
|
203
|
+
if (existsSync4(srcTemplates)) {
|
|
193
204
|
rmSync(paths.templates, { recursive: true, force: true });
|
|
194
205
|
cpSync(srcTemplates, paths.templates, { recursive: true });
|
|
195
206
|
}
|
|
@@ -207,9 +218,9 @@ function populateCache(fromDir, opts = {}) {
|
|
|
207
218
|
function findUpRegistry(start) {
|
|
208
219
|
let dir = resolve4(start);
|
|
209
220
|
for (; ; ) {
|
|
210
|
-
const candidate =
|
|
211
|
-
if (
|
|
212
|
-
const parent =
|
|
221
|
+
const candidate = join5(dir, "registry");
|
|
222
|
+
if (existsSync5(join5(candidate, "_index.json"))) return candidate;
|
|
223
|
+
const parent = dirname4(dir);
|
|
213
224
|
if (parent === dir) return null;
|
|
214
225
|
dir = parent;
|
|
215
226
|
}
|
|
@@ -224,60 +235,62 @@ function resolveRegistryRoot(explicit) {
|
|
|
224
235
|
home = null;
|
|
225
236
|
}
|
|
226
237
|
if (home) {
|
|
227
|
-
const cache =
|
|
228
|
-
if (
|
|
238
|
+
const cache = join5(home, "registry");
|
|
239
|
+
if (existsSync5(join5(cache, "_index.json"))) return cache;
|
|
229
240
|
}
|
|
230
241
|
const dev = findUpRegistry(process.cwd());
|
|
231
242
|
if (dev) return dev;
|
|
243
|
+
const bundled = bundledKitRoot();
|
|
244
|
+
if (bundled) return join5(bundled, "registry");
|
|
232
245
|
throw new Error('[vitrine] registry not found. Run "vitrine kit update" or pass --registry.');
|
|
233
246
|
}
|
|
234
247
|
function createRegistrySource(explicitRoot) {
|
|
235
248
|
const root = resolveRegistryRoot(explicitRoot);
|
|
236
249
|
const cache = /* @__PURE__ */ new Map();
|
|
237
|
-
const featureDir = (name) =>
|
|
238
|
-
const hasFeature = (name) =>
|
|
250
|
+
const featureDir = (name) => join5(root, name);
|
|
251
|
+
const hasFeature = (name) => existsSync5(join5(featureDir(name), "feature.json"));
|
|
239
252
|
const loadManifest = (name) => {
|
|
240
253
|
const cached = cache.get(name);
|
|
241
254
|
if (cached) return cached;
|
|
242
|
-
const file =
|
|
243
|
-
if (!
|
|
255
|
+
const file = join5(featureDir(name), "feature.json");
|
|
256
|
+
if (!existsSync5(file)) throw new Error(`[vitrine] feature "${name}" not found in the registry`);
|
|
244
257
|
const manifest = featureManifestSchema.parse(JSON.parse(readText(file)));
|
|
245
258
|
cache.set(name, manifest);
|
|
246
259
|
return manifest;
|
|
247
260
|
};
|
|
248
261
|
const listFeatures2 = () => {
|
|
249
|
-
const index = JSON.parse(readText(
|
|
262
|
+
const index = JSON.parse(readText(join5(root, "_index.json")));
|
|
250
263
|
return Object.keys(index.features ?? {});
|
|
251
264
|
};
|
|
252
265
|
return { root, featureDir, hasFeature, loadManifest, listFeatures: listFeatures2 };
|
|
253
266
|
}
|
|
254
267
|
|
|
255
268
|
// src/install.ts
|
|
256
|
-
import { join as
|
|
269
|
+
import { join as join7 } from "path";
|
|
257
270
|
|
|
258
271
|
// src/transaction.ts
|
|
259
|
-
import { existsSync as
|
|
260
|
-
import { dirname as
|
|
272
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
273
|
+
import { dirname as dirname5 } from "path";
|
|
261
274
|
var FsTransaction = class {
|
|
262
275
|
backups = [];
|
|
263
276
|
snapshot(path) {
|
|
264
277
|
if (this.backups.some((b) => b.path === path)) return;
|
|
265
|
-
if (
|
|
278
|
+
if (existsSync6(path)) this.backups.push({ path, existed: true, prev: readFileSync2(path, "utf8") });
|
|
266
279
|
else this.backups.push({ path, existed: false });
|
|
267
280
|
}
|
|
268
281
|
write(path, content) {
|
|
269
282
|
this.snapshot(path);
|
|
270
|
-
mkdirSync2(
|
|
283
|
+
mkdirSync2(dirname5(path), { recursive: true });
|
|
271
284
|
writeFileSync2(path, content, "utf8");
|
|
272
285
|
}
|
|
273
286
|
remove(path) {
|
|
274
287
|
this.snapshot(path);
|
|
275
|
-
if (
|
|
288
|
+
if (existsSync6(path)) rmSync2(path);
|
|
276
289
|
}
|
|
277
290
|
rollback() {
|
|
278
291
|
for (const b of [...this.backups].reverse()) {
|
|
279
292
|
if (b.existed) writeFileSync2(b.path, b.prev, "utf8");
|
|
280
|
-
else if (
|
|
293
|
+
else if (existsSync6(b.path)) rmSync2(b.path);
|
|
281
294
|
}
|
|
282
295
|
this.backups = [];
|
|
283
296
|
}
|
|
@@ -340,9 +353,11 @@ function renderSlotsFile(features) {
|
|
|
340
353
|
const calls = withSlots.map((f) => ` register${pascalCase(f.name)}Slots();`);
|
|
341
354
|
return [
|
|
342
355
|
"// vitrine:generated \u2014 slot registration for installed features. Do not edit by hand.",
|
|
356
|
+
"import { slotRegistry } from '@vitrine-kit/core';",
|
|
343
357
|
...imports,
|
|
344
358
|
"",
|
|
345
359
|
"export function registerSlots(): void {",
|
|
360
|
+
" slotRegistry.clear();",
|
|
346
361
|
...calls.length ? calls : [" // no slot features"],
|
|
347
362
|
"}",
|
|
348
363
|
""
|
|
@@ -357,9 +372,11 @@ function renderPaymentsFile(features) {
|
|
|
357
372
|
const calls = withPayment.map((f) => ` register${pascalCase(f.name)}Provider();`);
|
|
358
373
|
return [
|
|
359
374
|
"// vitrine:generated \u2014 payment-provider registration for installed features. Do not edit by hand.",
|
|
375
|
+
"import { payments } from '@vitrine-kit/core';",
|
|
360
376
|
...imports,
|
|
361
377
|
"",
|
|
362
378
|
"export function registerPayments(): void {",
|
|
379
|
+
" payments.clear();",
|
|
363
380
|
...calls.length ? calls : [" // no payment features"],
|
|
364
381
|
"}",
|
|
365
382
|
""
|
|
@@ -448,14 +465,14 @@ function renderNeutralTheme() {
|
|
|
448
465
|
}
|
|
449
466
|
|
|
450
467
|
// src/feature-files.ts
|
|
451
|
-
import { join as
|
|
468
|
+
import { join as join6 } from "path";
|
|
452
469
|
function* eachFeatureFile(featDir, map) {
|
|
453
|
-
const src =
|
|
470
|
+
const src = join6(featDir, map.from);
|
|
454
471
|
if (!exists(src)) return;
|
|
455
472
|
const rels = isDir(src) ? walkRelFiles(src) : [""];
|
|
456
473
|
for (const rel of rels) {
|
|
457
|
-
const srcAbs = rel ?
|
|
458
|
-
const repoRel = rel ?
|
|
474
|
+
const srcAbs = rel ? join6(src, rel) : src;
|
|
475
|
+
const repoRel = rel ? join6(map.to, rel) : map.to;
|
|
459
476
|
yield { srcAbs, repoRel, toRel: toPosix(repoRel) };
|
|
460
477
|
}
|
|
461
478
|
}
|
|
@@ -491,9 +508,9 @@ function validate(name, manifest, project) {
|
|
|
491
508
|
}
|
|
492
509
|
function copyFeatureFiles(project, name, manifest, registry, tx) {
|
|
493
510
|
const featDir = registry.featureDir(name);
|
|
494
|
-
const originalsBase =
|
|
511
|
+
const originalsBase = join7(projectPaths(project.root).originals, `${name}@${manifest.kitVersion}`);
|
|
495
512
|
for (const map of manifest.files) {
|
|
496
|
-
if (!exists(
|
|
513
|
+
if (!exists(join7(featDir, map.from))) {
|
|
497
514
|
throw new Error(`[vitrine] feature "${name}": no source "${map.from}"`);
|
|
498
515
|
}
|
|
499
516
|
for (const file of eachFeatureFile(featDir, map)) {
|
|
@@ -593,7 +610,7 @@ function removeFeature(project, name, registry) {
|
|
|
593
610
|
throw new Error(`[vitrine] cannot remove "${name}": "${other}" depends on it`);
|
|
594
611
|
}
|
|
595
612
|
}
|
|
596
|
-
const originalsDir =
|
|
613
|
+
const originalsDir = join7(projectPaths(project.root).originals, `${name}@${removed.version}`);
|
|
597
614
|
const featDir = registry.featureDir(name);
|
|
598
615
|
const targets = /* @__PURE__ */ new Set();
|
|
599
616
|
if (isDir(originalsDir)) for (const rel of walkRelFiles(originalsDir)) targets.add(rel);
|
|
@@ -616,21 +633,21 @@ function removeFeature(project, name, registry) {
|
|
|
616
633
|
|
|
617
634
|
// src/design.ts
|
|
618
635
|
import { spawnSync } from "child_process";
|
|
619
|
-
import { existsSync as
|
|
620
|
-
import { delimiter, join as
|
|
636
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2 } from "fs";
|
|
637
|
+
import { delimiter, join as join8 } from "path";
|
|
621
638
|
import { TOKEN_CSS_VARS as TOKEN_CSS_VARS2 } from "@vitrine-kit/contracts";
|
|
622
639
|
var DESIGN_HEADING = "## INSTRUCTION: apply the design from /design";
|
|
623
640
|
function findClaudeBin(explicit) {
|
|
624
641
|
const pinned = explicit ?? process.env.VITRINE_CLAUDE_BIN;
|
|
625
642
|
if (pinned) {
|
|
626
|
-
if (
|
|
643
|
+
if (existsSync7(pinned)) return pinned;
|
|
627
644
|
throw new Error(`[vitrine] Claude Code not found at path "${pinned}"`);
|
|
628
645
|
}
|
|
629
646
|
const names = process.platform === "win32" ? ["claude.cmd", "claude.exe", "claude.ps1", "claude"] : ["claude"];
|
|
630
647
|
for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
|
|
631
648
|
for (const name of names) {
|
|
632
|
-
const full =
|
|
633
|
-
if (
|
|
649
|
+
const full = join8(dir, name);
|
|
650
|
+
if (existsSync7(full)) return full;
|
|
634
651
|
}
|
|
635
652
|
}
|
|
636
653
|
throw new Error(
|
|
@@ -638,8 +655,8 @@ function findClaudeBin(explicit) {
|
|
|
638
655
|
);
|
|
639
656
|
}
|
|
640
657
|
function designHasInput(root) {
|
|
641
|
-
const dir =
|
|
642
|
-
if (!
|
|
658
|
+
const dir = join8(root, "design");
|
|
659
|
+
if (!existsSync7(dir)) return false;
|
|
643
660
|
return readdirSync2(dir).some((entry) => !/^readme\.md$/i.test(entry));
|
|
644
661
|
}
|
|
645
662
|
function extractDesignInstruction(claudeMd) {
|
|
@@ -649,7 +666,7 @@ function extractDesignInstruction(claudeMd) {
|
|
|
649
666
|
return claudeMd.slice(start, next === -1 ? void 0 : next).trim();
|
|
650
667
|
}
|
|
651
668
|
function buildDesignPrompt(project) {
|
|
652
|
-
const claudeMd =
|
|
669
|
+
const claudeMd = existsSync7(join8(project.root, "CLAUDE.md")) ? readText(join8(project.root, "CLAUDE.md")) : "";
|
|
653
670
|
const instruction = extractDesignInstruction(claudeMd) ?? DESIGN_HEADING;
|
|
654
671
|
const tokens = TOKEN_CSS_VARS2.map((v) => ` ${v}`).join("\n");
|
|
655
672
|
return [
|
|
@@ -691,7 +708,7 @@ function designApply(project, opts = {}, runner = defaultRunner) {
|
|
|
691
708
|
}
|
|
692
709
|
|
|
693
710
|
// src/doctor.ts
|
|
694
|
-
import { join as
|
|
711
|
+
import { join as join9 } from "path";
|
|
695
712
|
function runDoctor(project, registry) {
|
|
696
713
|
const paths = projectPaths(project.root);
|
|
697
714
|
const issues = [];
|
|
@@ -733,7 +750,7 @@ function runDoctor(project, registry) {
|
|
|
733
750
|
const featDir = registry.featureDir(name);
|
|
734
751
|
for (const map of manifest.files) {
|
|
735
752
|
for (const file of eachFeatureFile(featDir, map)) {
|
|
736
|
-
if (!exists(
|
|
753
|
+
if (!exists(join9(project.root, file.repoRel))) {
|
|
737
754
|
add({
|
|
738
755
|
severity: "error",
|
|
739
756
|
scope,
|
|
@@ -783,7 +800,7 @@ function runDoctor(project, registry) {
|
|
|
783
800
|
|
|
784
801
|
// src/update.ts
|
|
785
802
|
import { rmSync as rmSync3 } from "fs";
|
|
786
|
-
import { join as
|
|
803
|
+
import { join as join10 } from "path";
|
|
787
804
|
|
|
788
805
|
// src/merge.ts
|
|
789
806
|
function splitLines(s) {
|
|
@@ -927,13 +944,13 @@ function planUpdate(project, name, registry) {
|
|
|
927
944
|
const fromVersion = pin.version;
|
|
928
945
|
const toVersion = manifest.kitVersion;
|
|
929
946
|
const featDir = registry.featureDir(name);
|
|
930
|
-
const originalsBase =
|
|
947
|
+
const originalsBase = join10(projectPaths(project.root).originals, `${name}@${fromVersion}`);
|
|
931
948
|
const files = [];
|
|
932
949
|
for (const map of manifest.files) {
|
|
933
950
|
for (const file of eachFeatureFile(featDir, map)) {
|
|
934
951
|
const theirs = readText(file.srcAbs);
|
|
935
|
-
const oursPath =
|
|
936
|
-
const basePath =
|
|
952
|
+
const oursPath = join10(project.root, file.repoRel);
|
|
953
|
+
const basePath = join10(originalsBase, file.repoRel);
|
|
937
954
|
const ours = exists(oursPath) ? readText(oursPath) : null;
|
|
938
955
|
const base = exists(basePath) ? readText(basePath) : null;
|
|
939
956
|
if (ours === null) {
|
|
@@ -961,8 +978,8 @@ function applyUpdate(project, plan, registry) {
|
|
|
961
978
|
const manifest = registry.loadManifest(plan.feature);
|
|
962
979
|
const featDir = registry.featureDir(plan.feature);
|
|
963
980
|
const paths = projectPaths(project.root);
|
|
964
|
-
const newOriginals =
|
|
965
|
-
const oldOriginals =
|
|
981
|
+
const newOriginals = join10(paths.originals, `${plan.feature}@${plan.toVersion}`);
|
|
982
|
+
const oldOriginals = join10(paths.originals, `${plan.feature}@${plan.fromVersion}`);
|
|
966
983
|
const tx = new FsTransaction();
|
|
967
984
|
try {
|
|
968
985
|
for (const f of plan.files) {
|
|
@@ -1037,32 +1054,34 @@ function diffFeatureCmd(name, registryRoot) {
|
|
|
1037
1054
|
|
|
1038
1055
|
// src/init.ts
|
|
1039
1056
|
import { readdirSync as readdirSync3 } from "fs";
|
|
1040
|
-
import { join as
|
|
1057
|
+
import { join as join12 } from "path";
|
|
1041
1058
|
|
|
1042
|
-
// src/kit.ts
|
|
1043
|
-
var KIT_VERSION = "0.
|
|
1059
|
+
// src/kit-versions.generated.ts
|
|
1060
|
+
var KIT_VERSION = "0.4.0";
|
|
1044
1061
|
var CONTRACTS_VERSION = "1.0.0";
|
|
1045
1062
|
var CONTRACTS_RANGE = "^1.0.0";
|
|
1046
|
-
var CORE_RANGE = "^0.
|
|
1047
|
-
var BLUEPRINT_RANGE = "^0.
|
|
1063
|
+
var CORE_RANGE = "^0.2.0";
|
|
1064
|
+
var BLUEPRINT_RANGE = "^0.2.0";
|
|
1065
|
+
|
|
1066
|
+
// src/kit.ts
|
|
1048
1067
|
var REACT_RANGE = "^18.3.1";
|
|
1049
1068
|
var CLIENT_REACT_RANGE = "^19.0.0";
|
|
1050
1069
|
var NEXT_RANGE = "^15.1.0";
|
|
1051
1070
|
var PAYLOAD_RANGE = "^3.0.0";
|
|
1052
1071
|
|
|
1053
1072
|
// src/templates.ts
|
|
1054
|
-
import { existsSync as
|
|
1055
|
-
import { dirname as
|
|
1073
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1074
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
1056
1075
|
function templatesRoot(registryRoot) {
|
|
1057
|
-
return
|
|
1076
|
+
return join11(dirname6(registryRoot), "templates");
|
|
1058
1077
|
}
|
|
1059
1078
|
function hasTemplate(root, name) {
|
|
1060
|
-
return
|
|
1079
|
+
return existsSync8(join11(root, name, "files"));
|
|
1061
1080
|
}
|
|
1062
1081
|
function copyTemplate(root, name, destRoot) {
|
|
1063
|
-
const filesDir =
|
|
1082
|
+
const filesDir = join11(root, name, "files");
|
|
1064
1083
|
const rels = walkRelFiles(filesDir);
|
|
1065
|
-
for (const rel of rels) writeText(
|
|
1084
|
+
for (const rel of rels) writeText(join11(destRoot, rel), readText(join11(filesDir, rel)));
|
|
1066
1085
|
return rels;
|
|
1067
1086
|
}
|
|
1068
1087
|
|
|
@@ -1077,7 +1096,7 @@ var PAYMENT_PROVIDER_FEATURES = [
|
|
|
1077
1096
|
];
|
|
1078
1097
|
function suggestFeatures(tier, registry, backend = defaultBackend(tier)) {
|
|
1079
1098
|
const core = ["catalog", "product-page", "seo"];
|
|
1080
|
-
const shop = backend === "vendure" ? ["cart"] : ["cart", "checkout-stripe"
|
|
1099
|
+
const shop = backend === "vendure" ? ["cart"] : ["cart", "checkout-stripe"];
|
|
1081
1100
|
const desired = tier === "catalog" ? core : [...core, ...shop];
|
|
1082
1101
|
return desired.filter((name) => registry.hasFeature(name));
|
|
1083
1102
|
}
|
|
@@ -1254,8 +1273,8 @@ ${run}
|
|
|
1254
1273
|
|
|
1255
1274
|
\`\`\`bash
|
|
1256
1275
|
vitrine list # installed + available
|
|
1257
|
-
vitrine add
|
|
1258
|
-
vitrine remove
|
|
1276
|
+
vitrine add cart # copy a feature: flag, slots, blueprint, env
|
|
1277
|
+
vitrine remove cart # remove (if the feature is removable)
|
|
1259
1278
|
vitrine design apply # style the new feature
|
|
1260
1279
|
\`\`\`
|
|
1261
1280
|
|
|
@@ -1294,10 +1313,10 @@ function scaffoldBase(opts) {
|
|
|
1294
1313
|
const backendTemplate = `backend-${backend}`;
|
|
1295
1314
|
if (hasTemplate(tRoot, backendTemplate)) copyTemplate(tRoot, backendTemplate, root);
|
|
1296
1315
|
if (!baseCopied) {
|
|
1297
|
-
writeText(
|
|
1316
|
+
writeText(join12(root, ".gitignore"), "node_modules/\n.next/\ndist/\n.env\n.env.local\n.vitrine/\n");
|
|
1298
1317
|
}
|
|
1299
1318
|
writeText(
|
|
1300
|
-
|
|
1319
|
+
join12(root, "vitrine.json"),
|
|
1301
1320
|
`${JSON.stringify(
|
|
1302
1321
|
{ kitVersion: KIT_VERSION, contracts: CONTRACTS_VERSION, backend, tier, features: {} },
|
|
1303
1322
|
null,
|
|
@@ -1306,7 +1325,7 @@ function scaffoldBase(opts) {
|
|
|
1306
1325
|
`
|
|
1307
1326
|
);
|
|
1308
1327
|
writeText(
|
|
1309
|
-
|
|
1328
|
+
join12(root, "site.config.ts"),
|
|
1310
1329
|
`import type { SiteConfig } from '@vitrine-kit/contracts';
|
|
1311
1330
|
|
|
1312
1331
|
export const siteConfig: SiteConfig = {
|
|
@@ -1327,7 +1346,7 @@ export default siteConfig;
|
|
|
1327
1346
|
`
|
|
1328
1347
|
);
|
|
1329
1348
|
writeText(
|
|
1330
|
-
|
|
1349
|
+
join12(root, "CLAUDE.md"),
|
|
1331
1350
|
`# ${name}
|
|
1332
1351
|
|
|
1333
1352
|
A Vitrine project. Backend: \`${backend}\`, tier: \`${tier}\`.
|
|
@@ -1393,12 +1412,12 @@ The step is idempotent: re-running converges and doesn't accumulate cruft.
|
|
|
1393
1412
|
- **The user makes commits** \u2014 don't run \`git commit\`/\`git push\` without an explicit request.
|
|
1394
1413
|
`
|
|
1395
1414
|
);
|
|
1396
|
-
writeText(
|
|
1397
|
-
writeText(
|
|
1398
|
-
writeText(
|
|
1399
|
-
writeText(
|
|
1400
|
-
writeText(
|
|
1401
|
-
writeText(
|
|
1415
|
+
writeText(join12(root, "README.md"), clientReadme(name, backend, tier));
|
|
1416
|
+
writeText(join12(root, "lib", "slots.ts"), renderSlotsFile([]));
|
|
1417
|
+
writeText(join12(root, "lib", "blueprint.ts"), renderBlueprintFile([]));
|
|
1418
|
+
writeText(join12(root, "theme", "client.css"), renderNeutralTheme());
|
|
1419
|
+
writeText(join12(root, ".env.example"), clientEnvExample(backend));
|
|
1420
|
+
writeText(join12(root, "package.json"), `${JSON.stringify(clientPackageJson(name, backend), null, 2)}
|
|
1402
1421
|
`);
|
|
1403
1422
|
}
|
|
1404
1423
|
function initProject(opts) {
|
|
@@ -1412,9 +1431,9 @@ function initProject(opts) {
|
|
|
1412
1431
|
|
|
1413
1432
|
// src/kit-update.ts
|
|
1414
1433
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1415
|
-
import { existsSync as
|
|
1434
|
+
import { existsSync as existsSync9, mkdtempSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
1416
1435
|
import { tmpdir } from "os";
|
|
1417
|
-
import { join as
|
|
1436
|
+
import { join as join13, resolve as resolve5 } from "path";
|
|
1418
1437
|
var REPO = "vitrine-kit/vitrine";
|
|
1419
1438
|
function hasBin(bin) {
|
|
1420
1439
|
const probe = process.platform === "win32" ? "where" : "which";
|
|
@@ -1427,7 +1446,7 @@ function acquireFromGh(version) {
|
|
|
1427
1446
|
if (!hasBin("tar")) {
|
|
1428
1447
|
throw new Error("[vitrine] unpacking needs tar, or pass --from <dir>");
|
|
1429
1448
|
}
|
|
1430
|
-
const tmp = mkdtempSync(
|
|
1449
|
+
const tmp = mkdtempSync(join13(tmpdir(), "vitrine-kit-"));
|
|
1431
1450
|
const dl = spawnSync2(
|
|
1432
1451
|
"gh",
|
|
1433
1452
|
["release", "download", ...version ? [version] : [], "--repo", REPO, "--archive=tar.gz", "--dir", tmp, "--clobber"],
|
|
@@ -1438,10 +1457,10 @@ function acquireFromGh(version) {
|
|
|
1438
1457
|
}
|
|
1439
1458
|
const tarball = readdirSync4(tmp).find((f) => f.endsWith(".tar.gz"));
|
|
1440
1459
|
if (!tarball) throw new Error("[vitrine] release tarball not found after download");
|
|
1441
|
-
if (spawnSync2("tar", ["-xzf",
|
|
1460
|
+
if (spawnSync2("tar", ["-xzf", join13(tmp, tarball), "-C", tmp], { stdio: "inherit" }).status !== 0) {
|
|
1442
1461
|
throw new Error("[vitrine] tarball extraction failed");
|
|
1443
1462
|
}
|
|
1444
|
-
const root = readdirSync4(tmp).map((f) =>
|
|
1463
|
+
const root = readdirSync4(tmp).map((f) => join13(tmp, f)).find((p2) => statSync2(p2).isDirectory() && existsSync9(join13(p2, "registry", "_index.json")));
|
|
1445
1464
|
if (!root) throw new Error("[vitrine] the unpacked tarball has no registry/_index.json");
|
|
1446
1465
|
return root;
|
|
1447
1466
|
}
|
|
@@ -1451,7 +1470,7 @@ function kitUpdate(opts = {}) {
|
|
|
1451
1470
|
}
|
|
1452
1471
|
function kitStatus(home = vitrineHome()) {
|
|
1453
1472
|
const meta = readKitMeta(home);
|
|
1454
|
-
const idxFile =
|
|
1473
|
+
const idxFile = join13(cachePaths(home).registry, "_index.json");
|
|
1455
1474
|
const idx = exists(idxFile) ? readJson(idxFile) : null;
|
|
1456
1475
|
return {
|
|
1457
1476
|
cached: meta !== null,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# registry/ — copy-in feature registry
|
|
2
|
+
|
|
3
|
+
The source for `vitrine init` / `vitrine add`. Each feature = a folder with `feature.json` (manifest) + `files/` (sources). On install the files are **copied** into the client repository (the client owns the code).
|
|
4
|
+
|
|
5
|
+
`_index.json` is the registry manifest: the list of all features + the kit version.
|
|
6
|
+
|
|
7
|
+
**Rule (spec §4, §13):** the registry holds only what varies per client (UI, sections, wiring). No critical logic — that lives in `@vitrine-kit/core`. A new feature must depend **only on the contracts**.
|
|
8
|
+
|
|
9
|
+
**Typecheck in the monorepo:** `pnpm typecheck:registry` (components + lib) and `pnpm typecheck:registry-routes` (Next API route glue via `stubs/route-glue.d.ts`). Full Next/Payload pages are checked when the client is instantiated.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../schemas/registry-index.schema.json",
|
|
3
|
+
"kitVersion": "0.4.0",
|
|
4
|
+
"contracts": "1.0.0",
|
|
5
|
+
"features": {
|
|
6
|
+
"catalog": {
|
|
7
|
+
"title": "Product catalog",
|
|
8
|
+
"kitVersion": "0.4.0",
|
|
9
|
+
"tier": [
|
|
10
|
+
"catalog",
|
|
11
|
+
"simple-store",
|
|
12
|
+
"full-store"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
"product-page": {
|
|
16
|
+
"title": "Product page",
|
|
17
|
+
"kitVersion": "0.4.0",
|
|
18
|
+
"tier": [
|
|
19
|
+
"catalog",
|
|
20
|
+
"simple-store",
|
|
21
|
+
"full-store"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"seo": {
|
|
25
|
+
"title": "SEO — metadata and JSON-LD",
|
|
26
|
+
"kitVersion": "0.4.0",
|
|
27
|
+
"tier": [
|
|
28
|
+
"catalog",
|
|
29
|
+
"simple-store",
|
|
30
|
+
"full-store"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"cart": {
|
|
34
|
+
"title": "Cart",
|
|
35
|
+
"kitVersion": "0.4.0",
|
|
36
|
+
"tier": [
|
|
37
|
+
"simple-store",
|
|
38
|
+
"full-store"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"checkout": {
|
|
42
|
+
"title": "Checkout",
|
|
43
|
+
"kitVersion": "0.4.0",
|
|
44
|
+
"tier": [
|
|
45
|
+
"simple-store",
|
|
46
|
+
"full-store"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"checkout-stripe": {
|
|
50
|
+
"title": "Payment — Stripe",
|
|
51
|
+
"kitVersion": "0.4.0",
|
|
52
|
+
"tier": [
|
|
53
|
+
"simple-store",
|
|
54
|
+
"full-store"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"checkout-paddle": {
|
|
58
|
+
"title": "Payment — Paddle",
|
|
59
|
+
"kitVersion": "0.4.0",
|
|
60
|
+
"tier": [
|
|
61
|
+
"simple-store",
|
|
62
|
+
"full-store"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"checkout-yookassa": {
|
|
66
|
+
"title": "Payment — YooKassa",
|
|
67
|
+
"kitVersion": "0.4.0",
|
|
68
|
+
"tier": [
|
|
69
|
+
"simple-store",
|
|
70
|
+
"full-store"
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Feature: cart
|
|
2
|
+
|
|
3
|
+
The cart for the `simple-store` / `full-store` tiers. Depends only on the contracts:
|
|
4
|
+
data goes through `CommerceBackend`, the arithmetic lives in `@vitrine-kit/core` (critical logic).
|
|
5
|
+
|
|
6
|
+
- **Components:** `CartView`, `CartLineItem`, `CartSummary` (hosts the `cart.summary` slot),
|
|
7
|
+
`AddToCart` (client component, `product.purchase` slot), `CartIndicator` (`global.header-actions` slot).
|
|
8
|
+
- **Routes/API (Next glue):** `/cart` (page), `POST/PATCH/DELETE /api/cart`
|
|
9
|
+
(cart id in an httpOnly cookie, mutations via `CommerceBackend`).
|
|
10
|
+
- **Slots:** `product.purchase` → `AddToCart`; `global.header-actions` → `CartIndicator`;
|
|
11
|
+
hosts `cart.summary` (where checkout-stripe places the checkout button).
|
|
12
|
+
|
|
13
|
+
Styled with tokens; the AI design step doesn't touch behavior.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../schemas/feature.schema.json",
|
|
3
|
+
"name": "cart",
|
|
4
|
+
"title": "Cart",
|
|
5
|
+
"kitVersion": "0.4.0",
|
|
6
|
+
"requiresContracts": ">=1.0.0 <2.0.0",
|
|
7
|
+
"tier": [
|
|
8
|
+
"simple-store",
|
|
9
|
+
"full-store"
|
|
10
|
+
],
|
|
11
|
+
"registryDependencies": [],
|
|
12
|
+
"corePackages": {
|
|
13
|
+
"@vitrine-kit/core": ">=0.1.0"
|
|
14
|
+
},
|
|
15
|
+
"npm": [],
|
|
16
|
+
"files": [
|
|
17
|
+
{
|
|
18
|
+
"from": "files/components/cart/",
|
|
19
|
+
"to": "components/cart/"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"from": "files/lib/cart/",
|
|
23
|
+
"to": "lib/cart/"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"from": "files/app/",
|
|
27
|
+
"to": "app/"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"config": {
|
|
31
|
+
"set": {
|
|
32
|
+
"features.cart": true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"slots": [
|
|
36
|
+
{
|
|
37
|
+
"slot": "product.purchase",
|
|
38
|
+
"component": "AddToCart",
|
|
39
|
+
"order": 10
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"slot": "global.header-actions",
|
|
43
|
+
"component": "CartIndicator",
|
|
44
|
+
"order": 20
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
"env": [],
|
|
48
|
+
"claudeDoc": "docs/cart.md",
|
|
49
|
+
"conflicts": [],
|
|
50
|
+
"removable": true
|
|
51
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Cart page. The cart id is stored in a cookie; data comes via the CommerceBackend
|
|
2
|
+
// contract (the backend adapter in lib/adapter). Not typechecked in the monorepo
|
|
3
|
+
// (Next glue), verified when the client is instantiated.
|
|
4
|
+
import { cookies } from 'next/headers';
|
|
5
|
+
import { emptyCart } from '@vitrine-kit/core';
|
|
6
|
+
import { getCommerceBackend } from '@/lib/adapter';
|
|
7
|
+
import { CartView } from '@/components/cart/CartView';
|
|
8
|
+
import { siteConfig } from '@/site.config';
|
|
9
|
+
|
|
10
|
+
export default async function CartPage() {
|
|
11
|
+
const cartId = (await cookies()).get('vitrine_cart')?.value;
|
|
12
|
+
const commerce = await getCommerceBackend();
|
|
13
|
+
const cart = cartId ? await commerce.getCart(cartId) : null;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<section className="flex flex-col gap-section">
|
|
17
|
+
<h1 className="font-heading text-fg">Cart</h1>
|
|
18
|
+
<CartView cart={cart ?? emptyCart('', siteConfig.i18n.currency)} />
|
|
19
|
+
</section>
|
|
20
|
+
);
|
|
21
|
+
}
|