@vitrine-kit/vitrine 0.3.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -0
  3. package/dist/index.js +1610 -0
  4. package/package.json +31 -0
package/dist/index.js ADDED
@@ -0,0 +1,1610 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { readFileSync as readFileSync3 } from "fs";
5
+ import { resolve as resolve6 } from "path";
6
+ import { Command } from "commander";
7
+ import * as p from "@clack/prompts";
8
+
9
+ // src/project.ts
10
+ import { existsSync as existsSync2 } from "fs";
11
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
12
+ import { vitrineLockSchema } from "@vitrine-kit/contracts";
13
+
14
+ // src/util.ts
15
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
16
+ import { dirname, isAbsolute, join, relative, resolve } from "path";
17
+ function preflightNode(min = 20) {
18
+ const major = Number(process.versions.node.split(".")[0]);
19
+ if (Number.isFinite(major) && major < min) {
20
+ throw new Error(
21
+ `[vitrine] \u043D\u0443\u0436\u0435\u043D Node >= ${min} (\u0442\u0435\u043A\u0443\u0449\u0438\u0439 ${process.versions.node}). \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 Node ${min} LTS: https://nodejs.org`
22
+ );
23
+ }
24
+ }
25
+ function exists(p2) {
26
+ return existsSync(p2);
27
+ }
28
+ function isDir(p2) {
29
+ return existsSync(p2) && statSync(p2).isDirectory();
30
+ }
31
+ function readText(p2) {
32
+ return readFileSync(p2, "utf8");
33
+ }
34
+ function readJson(p2) {
35
+ return JSON.parse(readText(p2));
36
+ }
37
+ function writeText(p2, content) {
38
+ mkdirSync(dirname(p2), { recursive: true });
39
+ writeFileSync(p2, content, "utf8");
40
+ }
41
+ function toPosix(p2) {
42
+ return p2.split("\\").join("/");
43
+ }
44
+ function safeJoin(root, ...segs) {
45
+ const base = resolve(root);
46
+ const target = resolve(base, ...segs);
47
+ const rel = relative(base, target);
48
+ if (rel.startsWith("..") || isAbsolute(rel)) {
49
+ throw new Error(`[vitrine] \u043F\u0443\u0442\u044C "${join(...segs)}" \u0432\u044B\u0445\u043E\u0434\u0438\u0442 \u0437\u0430 \u043F\u0440\u0435\u0434\u0435\u043B\u044B "${root}"`);
50
+ }
51
+ return target;
52
+ }
53
+ function parseEnvKeys(text2) {
54
+ return new Set(
55
+ text2.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#") && l.includes("=")).map((l) => l.split("=")[0]?.trim() ?? "").filter(Boolean)
56
+ );
57
+ }
58
+ function walkRelFiles(dir) {
59
+ const out = [];
60
+ const walk = (current) => {
61
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
62
+ const abs = join(current, entry.name);
63
+ if (entry.isDirectory()) walk(abs);
64
+ else out.push(toPosix(relative(dir, abs)));
65
+ }
66
+ };
67
+ walk(dir);
68
+ return out;
69
+ }
70
+ function pascalCase(s) {
71
+ return s.replace(/(^|[-_/])(\w)/g, (_m, _sep, ch) => ch.toUpperCase());
72
+ }
73
+ function sortKeys(obj) {
74
+ const out = {};
75
+ for (const k of Object.keys(obj).sort()) out[k] = obj[k];
76
+ return out;
77
+ }
78
+ function parseNpmSpec(spec) {
79
+ const at = spec.lastIndexOf("@");
80
+ if (at > 0) return { name: spec.slice(0, at), range: spec.slice(at + 1) };
81
+ return { name: spec, range: "latest" };
82
+ }
83
+ function replaceBetween(content, startMarker, endMarker, replacement) {
84
+ const si = content.indexOf(startMarker);
85
+ const ei = content.indexOf(endMarker);
86
+ if (si === -1 || ei === -1 || ei < si) {
87
+ throw new Error(`[vitrine] \u043C\u0430\u0440\u043A\u0435\u0440\u044B "${startMarker}"/"${endMarker}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u044B`);
88
+ }
89
+ const afterStartLine = content.indexOf("\n", si) + 1;
90
+ const endLineStart = content.lastIndexOf("\n", ei) + 1;
91
+ return `${content.slice(0, afterStartLine)}${replacement}
92
+ ${content.slice(endLineStart)}`;
93
+ }
94
+
95
+ // src/project.ts
96
+ function projectPaths(root) {
97
+ return {
98
+ config: join2(root, "site.config.ts"),
99
+ slots: join2(root, "lib", "slots.ts"),
100
+ payments: join2(root, "lib", "payments.ts"),
101
+ blueprint: join2(root, "lib", "blueprint.ts"),
102
+ claude: join2(root, "CLAUDE.md"),
103
+ env: join2(root, ".env.example"),
104
+ pkg: join2(root, "package.json"),
105
+ lock: join2(root, "vitrine.json"),
106
+ originals: join2(root, ".vitrine", "originals")
107
+ };
108
+ }
109
+ function findProjectRoot(start = process.cwd()) {
110
+ let dir = resolve2(start);
111
+ for (; ; ) {
112
+ if (existsSync2(join2(dir, "vitrine.json"))) return dir;
113
+ const parent = dirname2(dir);
114
+ if (parent === dir) return null;
115
+ dir = parent;
116
+ }
117
+ }
118
+ function loadProject(root) {
119
+ const lock = vitrineLockSchema.parse(JSON.parse(readText(join2(root, "vitrine.json"))));
120
+ return { root, lock };
121
+ }
122
+
123
+ // src/registry.ts
124
+ import { existsSync as existsSync4 } from "fs";
125
+ import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
126
+ import { featureManifestSchema } from "@vitrine-kit/contracts";
127
+
128
+ // src/cache.ts
129
+ import { cpSync, existsSync as existsSync3, rmSync } from "fs";
130
+ import { join as join3, resolve as resolve3 } from "path";
131
+ function vitrineHome() {
132
+ if (process.env.VITRINE_HOME) return resolve3(process.env.VITRINE_HOME);
133
+ const home = process.env.USERPROFILE ?? process.env.HOME;
134
+ if (!home) throw new Error("[vitrine] \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u0434\u043E\u043C\u0430\u0448\u043D\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433 (HOME/USERPROFILE)");
135
+ return join3(home, ".vitrine");
136
+ }
137
+ function cachePaths(home = vitrineHome()) {
138
+ return {
139
+ root: home,
140
+ registry: join3(home, "registry"),
141
+ templates: join3(home, "templates"),
142
+ meta: join3(home, "kit.json")
143
+ };
144
+ }
145
+ function readKitMeta(home = vitrineHome()) {
146
+ const file = cachePaths(home).meta;
147
+ return exists(file) ? readJson(file) : null;
148
+ }
149
+ function writeKitMeta(home, meta) {
150
+ writeText(cachePaths(home).meta, `${JSON.stringify(meta, null, 2)}
151
+ `);
152
+ }
153
+ function readIndex(registryRoot) {
154
+ const file = join3(registryRoot, "_index.json");
155
+ return exists(file) ? readJson(file) : null;
156
+ }
157
+ function computeChangelog(oldIndex, newIndex) {
158
+ const oldF = oldIndex?.features ?? {};
159
+ const newF = newIndex?.features ?? {};
160
+ const entries = [];
161
+ for (const name of Object.keys(newF).sort()) {
162
+ if (!(name in oldF)) {
163
+ entries.push({ kind: "added", name, to: newF[name]?.kitVersion });
164
+ } else if (oldF[name]?.kitVersion !== newF[name]?.kitVersion) {
165
+ entries.push({ kind: "changed", name, from: oldF[name]?.kitVersion, to: newF[name]?.kitVersion });
166
+ }
167
+ }
168
+ for (const name of Object.keys(oldF).sort()) {
169
+ if (!(name in newF)) entries.push({ kind: "removed", name, from: oldF[name]?.kitVersion });
170
+ }
171
+ return entries;
172
+ }
173
+ function formatChangelog(entries) {
174
+ if (entries.length === 0) return "\u0431\u0435\u0437 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0430\u0431\u043E\u0440\u0430 \u0444\u0438\u0447";
175
+ return entries.map((e) => {
176
+ if (e.kind === "added") return `+ ${e.name}${e.to ? ` ${e.to}` : ""}`;
177
+ if (e.kind === "removed") return `- ${e.name}`;
178
+ return `~ ${e.name} ${e.from ?? "?"}\u2192${e.to ?? "?"}`;
179
+ }).join("\n");
180
+ }
181
+ function populateCache(fromDir, opts = {}) {
182
+ const home = opts.home ?? vitrineHome();
183
+ const srcRegistry = join3(fromDir, "registry");
184
+ const srcTemplates = join3(fromDir, "templates");
185
+ if (!existsSync3(join3(srcRegistry, "_index.json"))) {
186
+ throw new Error(`[vitrine] \u0432 "${fromDir}" \u043D\u0435\u0442 registry/_index.json \u2014 \u044D\u0442\u043E \u043D\u0435 \u0434\u0435\u0440\u0435\u0432\u043E kit`);
187
+ }
188
+ const paths = cachePaths(home);
189
+ const oldIndex = readIndex(paths.registry);
190
+ rmSync(paths.registry, { recursive: true, force: true });
191
+ cpSync(srcRegistry, paths.registry, { recursive: true });
192
+ if (existsSync3(srcTemplates)) {
193
+ rmSync(paths.templates, { recursive: true, force: true });
194
+ cpSync(srcTemplates, paths.templates, { recursive: true });
195
+ }
196
+ const newIndex = readIndex(paths.registry);
197
+ const kitVersion = newIndex?.kitVersion ?? "0.0.0";
198
+ writeKitMeta(home, {
199
+ kitVersion,
200
+ channel: opts.channel ?? "stable",
201
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
202
+ });
203
+ return { kitVersion, changelog: computeChangelog(oldIndex, newIndex) };
204
+ }
205
+
206
+ // src/registry.ts
207
+ function findUpRegistry(start) {
208
+ let dir = resolve4(start);
209
+ for (; ; ) {
210
+ const candidate = join4(dir, "registry");
211
+ if (existsSync4(join4(candidate, "_index.json"))) return candidate;
212
+ const parent = dirname3(dir);
213
+ if (parent === dir) return null;
214
+ dir = parent;
215
+ }
216
+ }
217
+ function resolveRegistryRoot(explicit) {
218
+ if (explicit) return resolve4(explicit);
219
+ if (process.env.VITRINE_REGISTRY) return resolve4(process.env.VITRINE_REGISTRY);
220
+ let home;
221
+ try {
222
+ home = vitrineHome();
223
+ } catch {
224
+ home = null;
225
+ }
226
+ if (home) {
227
+ const cache = join4(home, "registry");
228
+ if (existsSync4(join4(cache, "_index.json"))) return cache;
229
+ }
230
+ const dev = findUpRegistry(process.cwd());
231
+ if (dev) return dev;
232
+ throw new Error('[vitrine] \u0440\u0435\u0435\u0441\u0442\u0440 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D. \u0417\u0430\u043F\u0443\u0441\u0442\u0438 "vitrine kit update" \u0438\u043B\u0438 \u0443\u043A\u0430\u0436\u0438 --registry.');
233
+ }
234
+ function createRegistrySource(explicitRoot) {
235
+ const root = resolveRegistryRoot(explicitRoot);
236
+ const cache = /* @__PURE__ */ new Map();
237
+ const featureDir = (name) => join4(root, name);
238
+ const hasFeature = (name) => existsSync4(join4(featureDir(name), "feature.json"));
239
+ const loadManifest = (name) => {
240
+ const cached = cache.get(name);
241
+ if (cached) return cached;
242
+ const file = join4(featureDir(name), "feature.json");
243
+ if (!existsSync4(file)) throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435`);
244
+ const manifest = featureManifestSchema.parse(JSON.parse(readText(file)));
245
+ cache.set(name, manifest);
246
+ return manifest;
247
+ };
248
+ const listFeatures2 = () => {
249
+ const index = JSON.parse(readText(join4(root, "_index.json")));
250
+ return Object.keys(index.features ?? {});
251
+ };
252
+ return { root, featureDir, hasFeature, loadManifest, listFeatures: listFeatures2 };
253
+ }
254
+
255
+ // src/install.ts
256
+ import { join as join6 } from "path";
257
+
258
+ // src/transaction.ts
259
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
260
+ import { dirname as dirname4 } from "path";
261
+ var FsTransaction = class {
262
+ backups = [];
263
+ snapshot(path) {
264
+ if (this.backups.some((b) => b.path === path)) return;
265
+ if (existsSync5(path)) this.backups.push({ path, existed: true, prev: readFileSync2(path, "utf8") });
266
+ else this.backups.push({ path, existed: false });
267
+ }
268
+ write(path, content) {
269
+ this.snapshot(path);
270
+ mkdirSync2(dirname4(path), { recursive: true });
271
+ writeFileSync2(path, content, "utf8");
272
+ }
273
+ remove(path) {
274
+ this.snapshot(path);
275
+ if (existsSync5(path)) rmSync2(path);
276
+ }
277
+ rollback() {
278
+ for (const b of [...this.backups].reverse()) {
279
+ if (b.existed) writeFileSync2(b.path, b.prev, "utf8");
280
+ else if (existsSync5(b.path)) rmSync2(b.path);
281
+ }
282
+ this.backups = [];
283
+ }
284
+ commit() {
285
+ this.backups = [];
286
+ }
287
+ };
288
+
289
+ // src/generate.ts
290
+ import { TOKEN_CSS_VARS } from "@vitrine-kit/contracts";
291
+ function collectFeatureFlags(features) {
292
+ const flags = {};
293
+ for (const f of features) {
294
+ for (const [key, value] of Object.entries(f.manifest.config?.set ?? {})) {
295
+ if (key.startsWith("features.")) flags[key.slice("features.".length)] = value;
296
+ }
297
+ }
298
+ return flags;
299
+ }
300
+ function renderFeaturesRegion(features) {
301
+ const flags = collectFeatureFlags(features);
302
+ const keys = Object.keys(flags).sort();
303
+ if (keys.length === 0) return " features: {},";
304
+ const body = keys.map((k) => ` ${JSON.stringify(k)}: ${flags[k]},`).join("\n");
305
+ return ` features: {
306
+ ${body}
307
+ },`;
308
+ }
309
+ function activePaymentProvider(features) {
310
+ const providers = /* @__PURE__ */ new Set();
311
+ for (const f of features) {
312
+ if (f.manifest.payment) providers.add(f.manifest.payment.provider);
313
+ }
314
+ return [...providers].sort()[0];
315
+ }
316
+ function renderIntegrationsRegion(features) {
317
+ const payments = activePaymentProvider(features);
318
+ if (!payments) return " integrations: {},";
319
+ return ` integrations: {
320
+ payments: ${JSON.stringify(payments)},
321
+ },`;
322
+ }
323
+ function assertNoPascalCollisions(features) {
324
+ const byPascal = /* @__PURE__ */ new Map();
325
+ for (const f of features) {
326
+ const id = pascalCase(f.name);
327
+ const prev = byPascal.get(id);
328
+ if (prev && prev !== f.name) {
329
+ throw new Error(`[vitrine] \u0444\u0438\u0447\u0438 "${prev}" \u0438 "${f.name}" \u0434\u0430\u044E\u0442 \u043E\u0434\u0438\u043D \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440 "${id}" \u2014 \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u0443\u0439\u0442\u0435 \u043E\u0434\u043D\u0443`);
330
+ }
331
+ byPascal.set(id, f.name);
332
+ }
333
+ }
334
+ function renderSlotsFile(features) {
335
+ const withSlots = features.filter((f) => (f.manifest.slots?.length ?? 0) > 0);
336
+ assertNoPascalCollisions(withSlots);
337
+ const imports = withSlots.map(
338
+ (f) => `import { register${pascalCase(f.name)}Slots } from './${f.name}/register.js';`
339
+ );
340
+ const calls = withSlots.map((f) => ` register${pascalCase(f.name)}Slots();`);
341
+ return [
342
+ "// vitrine:generated \u2014 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F \u0441\u043B\u043E\u0442\u043E\u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0444\u0438\u0447. \u041D\u0435 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E.",
343
+ ...imports,
344
+ "",
345
+ "export function registerSlots(): void {",
346
+ ...calls.length ? calls : [" // \u043D\u0435\u0442 \u0444\u0438\u0447 \u0441\u043E \u0441\u043B\u043E\u0442\u0430\u043C\u0438"],
347
+ "}",
348
+ ""
349
+ ].join("\n");
350
+ }
351
+ function renderPaymentsFile(features) {
352
+ const withPayment = features.filter((f) => f.manifest.payment);
353
+ assertNoPascalCollisions(withPayment);
354
+ const imports = withPayment.map(
355
+ (f) => `import { register${pascalCase(f.name)}Provider } from './${f.name}/register.js';`
356
+ );
357
+ const calls = withPayment.map((f) => ` register${pascalCase(f.name)}Provider();`);
358
+ return [
359
+ "// vitrine:generated \u2014 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044F \u043F\u043B\u0430\u0442\u0451\u0436\u043D\u044B\u0445 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u043E\u0432 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0444\u0438\u0447. \u041D\u0435 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E.",
360
+ ...imports,
361
+ "",
362
+ "export function registerPayments(): void {",
363
+ ...calls.length ? calls : [" // \u043D\u0435\u0442 \u043F\u043B\u0430\u0442\u0451\u0436\u043D\u044B\u0445 \u0444\u0438\u0447"],
364
+ "}",
365
+ ""
366
+ ].join("\n");
367
+ }
368
+ function renderBlueprintFile(features) {
369
+ const withBp = features.filter((f) => f.manifest.blueprint);
370
+ assertNoPascalCollisions(withBp);
371
+ const imports = withBp.map(
372
+ (f) => `import { extend${pascalCase(f.name)}Blueprint } from './${f.name}/blueprint.js';`
373
+ );
374
+ const calls = withBp.map((f) => ` extend${pascalCase(f.name)}Blueprint(blueprint);`);
375
+ return [
376
+ "// vitrine:generated \u2014 blueprint \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0445 \u0444\u0438\u0447. \u041D\u0435 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0440\u0443\u0447\u043D\u0443\u044E.",
377
+ "import { createBlueprint } from '@vitrine-kit/payload-blueprint';",
378
+ ...imports,
379
+ "",
380
+ "const blueprint = createBlueprint();",
381
+ ...calls,
382
+ "",
383
+ "export const collections = blueprint.build();",
384
+ ""
385
+ ].join("\n");
386
+ }
387
+ function renderClaudeFeaturesTable(features) {
388
+ if (features.length === 0) return "_\u0424\u0438\u0447\u0438 \u0435\u0449\u0451 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B._";
389
+ const rows = features.slice().sort((a, b) => a.name.localeCompare(b.name)).map((f) => `| \`${f.name}\` | ${f.manifest.title} | ${f.version} |`).join("\n");
390
+ return ["| \u0424\u0438\u0447\u0430 | \u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435 | \u0412\u0435\u0440\u0441\u0438\u044F |", "|---|---|---|", rows].join("\n");
391
+ }
392
+ function mergeEnvExample(existing, features) {
393
+ const present = parseEnvKeys(existing);
394
+ const additions = [];
395
+ for (const f of features) {
396
+ for (const e of f.manifest.env ?? []) {
397
+ if (!present.has(e.key)) {
398
+ additions.push(`${e.key}=`);
399
+ present.add(e.key);
400
+ }
401
+ }
402
+ }
403
+ if (additions.length === 0) return existing;
404
+ return `${existing.trimEnd()}
405
+
406
+ ${additions.join("\n")}
407
+ `;
408
+ }
409
+ function mergePackageDeps(pkg2, features) {
410
+ const deps = { ...pkg2.dependencies ?? {} };
411
+ for (const f of features) {
412
+ for (const [name, range] of Object.entries(f.manifest.corePackages ?? {})) deps[name] = String(range);
413
+ for (const spec of f.manifest.npm ?? []) {
414
+ const { name, range } = parseNpmSpec(spec);
415
+ deps[name] = range;
416
+ }
417
+ }
418
+ return { ...pkg2, dependencies: sortKeys(deps) };
419
+ }
420
+ function renderNeutralTheme() {
421
+ const neutral = {
422
+ "--vt-color-bg": "#ffffff",
423
+ "--vt-color-fg": "#111111",
424
+ "--vt-color-muted": "#f4f4f5",
425
+ "--vt-color-muted-fg": "#6b7280",
426
+ "--vt-color-surface": "#ffffff",
427
+ "--vt-color-surface-fg": "#111111",
428
+ "--vt-color-border": "#e5e7eb",
429
+ "--vt-color-input": "#e5e7eb",
430
+ "--vt-color-ring": "#111111",
431
+ "--vt-color-primary": "#111111",
432
+ "--vt-color-primary-fg": "#ffffff",
433
+ "--vt-color-price": "#111111",
434
+ "--vt-radius-base": "0.5rem",
435
+ "--vt-space-unit": "0.25rem",
436
+ "--vt-space-container-max": "72rem",
437
+ "--vt-space-container-padding": "1rem",
438
+ "--vt-space-section-gap": "2rem"
439
+ };
440
+ const lines = TOKEN_CSS_VARS.map((v) => ` ${v}: ${neutral[v] ?? "initial"};`).join("\n");
441
+ return [
442
+ "/* vitrine: \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u043A\u043B\u0438\u0435\u043D\u0442\u0430. \u0414\u0438\u0437\u0430\u0439\u043D-\u0448\u0430\u0433 (vitrine design apply) \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u044D\u0442\u043E. */",
443
+ ":root {",
444
+ lines,
445
+ "}",
446
+ ""
447
+ ].join("\n");
448
+ }
449
+
450
+ // src/feature-files.ts
451
+ import { join as join5 } from "path";
452
+ function* eachFeatureFile(featDir, map) {
453
+ const src = join5(featDir, map.from);
454
+ if (!exists(src)) return;
455
+ const rels = isDir(src) ? walkRelFiles(src) : [""];
456
+ for (const rel of rels) {
457
+ const srcAbs = rel ? join5(src, rel) : src;
458
+ const repoRel = rel ? join5(map.to, rel) : map.to;
459
+ yield { srcAbs, repoRel, toRel: toPosix(repoRel) };
460
+ }
461
+ }
462
+
463
+ // src/install.ts
464
+ function resolveOrder(names, registry) {
465
+ const order = [];
466
+ const seen = /* @__PURE__ */ new Set();
467
+ const visit = (name) => {
468
+ if (seen.has(name)) return;
469
+ if (!registry.hasFeature(name)) {
470
+ throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435`);
471
+ }
472
+ seen.add(name);
473
+ const manifest = registry.loadManifest(name);
474
+ for (const dep of manifest.registryDependencies ?? []) visit(dep);
475
+ order.push(name);
476
+ };
477
+ for (const name of names) visit(name);
478
+ return order;
479
+ }
480
+ function validate(name, manifest, project) {
481
+ if (!manifest.tier.includes(project.lock.tier)) {
482
+ throw new Error(
483
+ `[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0443\u0440\u043E\u0432\u0435\u043D\u044C "${project.lock.tier}" (\u0442\u043E\u043B\u044C\u043A\u043E ${manifest.tier.join(", ")})`
484
+ );
485
+ }
486
+ for (const conflict of manifest.conflicts ?? []) {
487
+ if (project.lock.features[conflict]) {
488
+ throw new Error(`[vitrine] \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442: "${name}" \u043D\u0435\u0441\u043E\u0432\u043C\u0435\u0441\u0442\u0438\u043C\u0430 \u0441 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u0439 "${conflict}"`);
489
+ }
490
+ }
491
+ }
492
+ function copyFeatureFiles(project, name, manifest, registry, tx) {
493
+ const featDir = registry.featureDir(name);
494
+ const originalsBase = join6(projectPaths(project.root).originals, `${name}@${manifest.kitVersion}`);
495
+ for (const map of manifest.files) {
496
+ if (!exists(join6(featDir, map.from))) {
497
+ throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}": \u043D\u0435\u0442 \u0438\u0441\u0442\u043E\u0447\u043D\u0438\u043A\u0430 "${map.from}"`);
498
+ }
499
+ for (const file of eachFeatureFile(featDir, map)) {
500
+ const content = readText(file.srcAbs);
501
+ tx.write(safeJoin(project.root, file.repoRel), content);
502
+ tx.write(safeJoin(originalsBase, file.repoRel), content);
503
+ }
504
+ }
505
+ }
506
+ function regenerateDerived(project, registry, tx) {
507
+ const paths = projectPaths(project.root);
508
+ const states = Object.entries(project.lock.features).map(([name, pin]) => ({
509
+ name,
510
+ version: pin.version,
511
+ manifest: registry.loadManifest(name)
512
+ }));
513
+ const config = readText(paths.config);
514
+ const withFeatures = replaceBetween(
515
+ config,
516
+ "// vitrine:features:start",
517
+ "// vitrine:features:end",
518
+ renderFeaturesRegion(states)
519
+ );
520
+ tx.write(
521
+ paths.config,
522
+ replaceBetween(
523
+ withFeatures,
524
+ "// vitrine:integrations:start",
525
+ "// vitrine:integrations:end",
526
+ renderIntegrationsRegion(states)
527
+ )
528
+ );
529
+ tx.write(paths.slots, renderSlotsFile(states));
530
+ tx.write(paths.payments, renderPaymentsFile(states));
531
+ tx.write(paths.blueprint, renderBlueprintFile(states));
532
+ tx.write(paths.env, mergeEnvExample(exists(paths.env) ? readText(paths.env) : "", states));
533
+ const pkg2 = readJson(paths.pkg);
534
+ tx.write(paths.pkg, `${JSON.stringify(mergePackageDeps(pkg2, states), null, 2)}
535
+ `);
536
+ const claude = readText(paths.claude);
537
+ tx.write(
538
+ paths.claude,
539
+ replaceBetween(
540
+ claude,
541
+ "<!-- vitrine:features:start -->",
542
+ "<!-- vitrine:features:end -->",
543
+ renderClaudeFeaturesTable(states)
544
+ )
545
+ );
546
+ tx.write(paths.lock, `${JSON.stringify(project.lock, null, 2)}
547
+ `);
548
+ }
549
+ function installFeatures(project, names, registry) {
550
+ const order = resolveOrder(names, registry);
551
+ const installedNames = new Set(Object.keys(project.lock.features));
552
+ const toInstall = order.filter((name) => {
553
+ const manifest = registry.loadManifest(name);
554
+ const pinned = project.lock.features[name];
555
+ return !pinned || pinned.version !== manifest.kitVersion;
556
+ });
557
+ if (toInstall.length === 0) {
558
+ return { installed: [], skipped: order };
559
+ }
560
+ const tx = new FsTransaction();
561
+ try {
562
+ for (const name of toInstall) {
563
+ const manifest = registry.loadManifest(name);
564
+ validate(name, manifest, project);
565
+ copyFeatureFiles(project, name, manifest, registry, tx);
566
+ project.lock.features[name] = { version: manifest.kitVersion };
567
+ }
568
+ regenerateDerived(project, registry, tx);
569
+ tx.commit();
570
+ } catch (error) {
571
+ tx.rollback();
572
+ for (const name of toInstall) {
573
+ if (!installedNames.has(name)) delete project.lock.features[name];
574
+ }
575
+ throw error;
576
+ }
577
+ return {
578
+ installed: toInstall,
579
+ skipped: order.filter((n) => !toInstall.includes(n))
580
+ };
581
+ }
582
+ function removeFeature(project, name, registry) {
583
+ const removed = project.lock.features[name];
584
+ if (!removed) throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430`);
585
+ const manifest = registry.loadManifest(name);
586
+ if (!manifest.removable) {
587
+ throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u0443\u0434\u0430\u043B\u044F\u0435\u043C\u0430 (removable: false)`);
588
+ }
589
+ for (const other of Object.keys(project.lock.features)) {
590
+ if (other === name) continue;
591
+ const deps = registry.loadManifest(other).registryDependencies ?? [];
592
+ if (deps.includes(name)) {
593
+ throw new Error(`[vitrine] \u043D\u0435\u043B\u044C\u0437\u044F \u0443\u0434\u0430\u043B\u0438\u0442\u044C "${name}": \u043E\u0442 \u043D\u0435\u0451 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 "${other}"`);
594
+ }
595
+ }
596
+ const originalsDir = join6(projectPaths(project.root).originals, `${name}@${removed.version}`);
597
+ const featDir = registry.featureDir(name);
598
+ const targets = /* @__PURE__ */ new Set();
599
+ if (isDir(originalsDir)) for (const rel of walkRelFiles(originalsDir)) targets.add(rel);
600
+ for (const map of manifest.files) for (const file of eachFeatureFile(featDir, map)) targets.add(file.toRel);
601
+ const tx = new FsTransaction();
602
+ try {
603
+ for (const rel of targets) {
604
+ tx.remove(safeJoin(project.root, rel));
605
+ tx.remove(safeJoin(originalsDir, rel));
606
+ }
607
+ delete project.lock.features[name];
608
+ regenerateDerived(project, registry, tx);
609
+ tx.commit();
610
+ } catch (error) {
611
+ tx.rollback();
612
+ project.lock.features[name] = removed;
613
+ throw error;
614
+ }
615
+ }
616
+
617
+ // src/design.ts
618
+ import { spawnSync } from "child_process";
619
+ import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
620
+ import { delimiter, join as join7 } from "path";
621
+ import { TOKEN_CSS_VARS as TOKEN_CSS_VARS2 } from "@vitrine-kit/contracts";
622
+ var DESIGN_HEADING = "## \u0418\u041D\u0421\u0422\u0420\u0423\u041A\u0426\u0418\u042F: \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u0438\u0437 /design";
623
+ function findClaudeBin(explicit) {
624
+ const pinned = explicit ?? process.env.VITRINE_CLAUDE_BIN;
625
+ if (pinned) {
626
+ if (existsSync6(pinned)) return pinned;
627
+ throw new Error(`[vitrine] Claude Code \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u043F\u043E \u043F\u0443\u0442\u0438 "${pinned}"`);
628
+ }
629
+ const names = process.platform === "win32" ? ["claude.cmd", "claude.exe", "claude.ps1", "claude"] : ["claude"];
630
+ for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
631
+ for (const name of names) {
632
+ const full = join7(dir, name);
633
+ if (existsSync6(full)) return full;
634
+ }
635
+ }
636
+ throw new Error(
637
+ "[vitrine] Claude Code CLI \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u0432 PATH. \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u0435 \u0435\u0433\u043E (npm i -g @anthropic-ai/claude-code) \u0438\u043B\u0438 \u0443\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0443\u0442\u044C \u0447\u0435\u0440\u0435\u0437 --bin / VITRINE_CLAUDE_BIN. design apply \u2014 \u043E\u0431\u0451\u0440\u0442\u043A\u0430 \u043D\u0430\u0434 Claude Code, \u0441\u0432\u043E\u0435\u0433\u043E \u043A\u043B\u044E\u0447\u0430 Anthropic \u043D\u0435 \u0442\u0440\u0435\u0431\u0443\u0435\u0442."
638
+ );
639
+ }
640
+ function designHasInput(root) {
641
+ const dir = join7(root, "design");
642
+ if (!existsSync6(dir)) return false;
643
+ return readdirSync2(dir).some((entry) => !/^readme\.md$/i.test(entry));
644
+ }
645
+ function extractDesignInstruction(claudeMd) {
646
+ const start = claudeMd.indexOf(DESIGN_HEADING);
647
+ if (start === -1) return null;
648
+ const next = claudeMd.indexOf("\n## ", start + DESIGN_HEADING.length);
649
+ return claudeMd.slice(start, next === -1 ? void 0 : next).trim();
650
+ }
651
+ function buildDesignPrompt(project) {
652
+ const claudeMd = existsSync6(join7(project.root, "CLAUDE.md")) ? readText(join7(project.root, "CLAUDE.md")) : "";
653
+ const instruction = extractDesignInstruction(claudeMd) ?? DESIGN_HEADING;
654
+ const tokens = TOKEN_CSS_VARS2.map((v) => ` ${v}`).join("\n");
655
+ return [
656
+ instruction,
657
+ "",
658
+ "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442 \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u0438\u044F:",
659
+ "- \u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A \u0434\u0438\u0437\u0430\u0439\u043D\u0430: \u043F\u0430\u043F\u043A\u0430 `design/` (\u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0438\u0437 Claude Design).",
660
+ "- \u0415\u0434\u0438\u043D\u0441\u0442\u0432\u0435\u043D\u043D\u044B\u0439 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u0443\u0435\u043C\u044B\u0439 \u0444\u0430\u0439\u043B: `theme/client.css` \u2014 \u0437\u0430\u0434\u0430\u0439\u0442\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F CSS-\u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445.",
661
+ "- \u0417\u0430\u043C\u043A\u043D\u0443\u0442\u044B\u0439 \u043D\u0430\u0431\u043E\u0440 \u0438\u043C\u0451\u043D \u0442\u043E\u043A\u0435\u043D\u043E\u0432 (\u0434\u0440\u0443\u0433\u0438\u0445 \u043D\u0435 \u0432\u0432\u043E\u0434\u0438\u0442\u044C):",
662
+ tokens,
663
+ "",
664
+ "\u041D\u0415 \u0440\u0435\u0434\u0430\u043A\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C: \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u044B, \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u044B, \u0440\u043E\u0443\u0442\u044B, site.config, lib/*. \u0422\u043E\u043B\u044C\u043A\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F",
665
+ "\u043F\u0435\u0440\u0435\u043C\u0435\u043D\u043D\u044B\u0445 \u0432 theme/client.css. \u0418\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C: \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u043D\u0435 \u043D\u0430\u043A\u0430\u043F\u043B\u0438\u0432\u0430\u0435\u0442 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u044F."
666
+ ].join("\n");
667
+ }
668
+ var defaultRunner = ({ bin, args, cwd }) => {
669
+ const res = spawnSync(bin, args, { cwd, stdio: "inherit" });
670
+ if (res.error) throw res.error;
671
+ return res.status ?? 0;
672
+ };
673
+ function planDesignApply(project, opts = {}) {
674
+ const bin = findClaudeBin(opts.bin);
675
+ const prompt = buildDesignPrompt(project);
676
+ const args = ["-p", prompt, "--permission-mode", "acceptEdits", ...opts.extraArgs ?? []];
677
+ return { bin, args, cwd: project.root, prompt };
678
+ }
679
+ function designApply(project, opts = {}, runner = defaultRunner) {
680
+ if (!designHasInput(project.root)) {
681
+ throw new Error(
682
+ "[vitrine] \u043F\u0430\u043F\u043A\u0430 design/ \u043F\u0443\u0441\u0442\u0430 \u2014 \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435 \u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0438\u0437 Claude Design \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435."
683
+ );
684
+ }
685
+ const cmd = planDesignApply(project, opts);
686
+ if (opts.dryRun) {
687
+ console.log(`[vitrine] dry-run: ${cmd.bin} -p <\u043F\u0440\u043E\u043C\u043F\u0442 ${cmd.prompt.length} \u0441\u0438\u043C\u0432.> --permission-mode acceptEdits`);
688
+ return 0;
689
+ }
690
+ return runner(cmd);
691
+ }
692
+
693
+ // src/doctor.ts
694
+ import { join as join8 } from "path";
695
+ function runDoctor(project, registry) {
696
+ const paths = projectPaths(project.root);
697
+ const issues = [];
698
+ const add = (i) => void issues.push(i);
699
+ const pkg2 = exists(paths.pkg) ? readJson(paths.pkg) : {};
700
+ const deps = pkg2.dependencies ?? {};
701
+ const env = exists(paths.env) ? parseEnvKeys(readText(paths.env)) : /* @__PURE__ */ new Set();
702
+ const configText = exists(paths.config) ? readText(paths.config) : "";
703
+ const slotsText = exists(paths.slots) ? readText(paths.slots) : "";
704
+ const paymentsText = exists(paths.payments) ? readText(paths.payments) : "";
705
+ for (const core of ["@vitrine-kit/contracts", "@vitrine-kit/core"]) {
706
+ if (!deps[core]) {
707
+ add({ severity: "error", scope: "packages", message: `\u043D\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 ${core}`, fix: "\u0434\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0432 package.json" });
708
+ }
709
+ }
710
+ if (exists(paths.claude) && !readText(paths.claude).includes("\u0418\u041D\u0421\u0422\u0420\u0423\u041A\u0426\u0418\u042F: \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D")) {
711
+ add({
712
+ severity: "warn",
713
+ scope: "design",
714
+ message: "\u0432 CLAUDE.md \u043D\u0435\u0442 \u0431\u043B\u043E\u043A\u0430 \u0434\u0438\u0437\u0430\u0439\u043D-\u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u0438",
715
+ fix: "\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u0435 CLAUDE.md (kit update \u043F\u0440\u0438\u043D\u043E\u0441\u0438\u0442 \u0441\u0432\u0435\u0436\u0443\u044E \u0438\u043D\u0441\u0442\u0440\u0443\u043A\u0446\u0438\u044E)"
716
+ });
717
+ }
718
+ for (const [name, pin] of Object.entries(project.lock.features)) {
719
+ const scope = `feature:${name}`;
720
+ if (!registry.hasFeature(name)) {
721
+ add({ severity: "error", scope, message: `\u0444\u0438\u0447\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435`, fix: "vitrine kit update" });
722
+ continue;
723
+ }
724
+ const manifest = registry.loadManifest(name);
725
+ if (pin.version !== manifest.kitVersion) {
726
+ add({
727
+ severity: "warn",
728
+ scope,
729
+ message: `\u0432\u0435\u0440\u0441\u0438\u044F \u0432 \u0440\u0435\u043F\u043E ${pin.version}, \u0440\u0435\u0435\u0441\u0442\u0440 \u043F\u0440\u0435\u0434\u043B\u0430\u0433\u0430\u0435\u0442 ${manifest.kitVersion}`,
730
+ fix: `vitrine update ${name}`
731
+ });
732
+ }
733
+ const featDir = registry.featureDir(name);
734
+ for (const map of manifest.files) {
735
+ for (const file of eachFeatureFile(featDir, map)) {
736
+ if (!exists(join8(project.root, file.repoRel))) {
737
+ add({
738
+ severity: "error",
739
+ scope,
740
+ message: `\u043D\u0435\u0442 \u0444\u0430\u0439\u043B\u0430 "${file.toRel}"`,
741
+ fix: `vitrine add ${name} (\u043F\u0435\u0440\u0435\u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442)`
742
+ });
743
+ }
744
+ }
745
+ }
746
+ for (const e of manifest.env ?? []) {
747
+ if (!env.has(e.key)) {
748
+ add({
749
+ severity: e.required ? "error" : "warn",
750
+ scope,
751
+ message: `\u043D\u0435\u0442 \u043A\u043B\u044E\u0447\u0430 env "${e.key}"${e.required ? " (\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D)" : ""}`,
752
+ fix: "\u0434\u043E\u0431\u0430\u0432\u044C\u0442\u0435 \u0432 .env.example/.env"
753
+ });
754
+ }
755
+ }
756
+ const need = [...Object.keys(manifest.corePackages ?? {}), ...(manifest.npm ?? []).map((s) => parseNpmSpec(s).name)];
757
+ for (const dep of need) {
758
+ if (!deps[dep]) {
759
+ add({ severity: "error", scope, message: `\u043D\u0435\u0442 \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438 ${dep}`, fix: `vitrine add ${name} (\u0434\u043E\u043C\u0435\u0440\u0436\u0438\u0442 deps)` });
760
+ }
761
+ }
762
+ if ((manifest.slots?.length ?? 0) > 0) {
763
+ const fn = `register${pascalCase(name)}Slots`;
764
+ if (!slotsText.includes(fn)) {
765
+ add({ severity: "error", scope, message: `lib/slots.ts \u043D\u0435 \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442 ${fn}`, fix: `vitrine add ${name} (\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u0442 slots)` });
766
+ }
767
+ }
768
+ if (!configText.includes(`"${name}": true`)) {
769
+ add({ severity: "warn", scope, message: `\u0432 site.config \u043D\u0435\u0442 \u0444\u043B\u0430\u0433\u0430 features.${name}`, fix: `vitrine add ${name} (\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u0442 \u0444\u043B\u0430\u0433\u0438)` });
770
+ }
771
+ if (manifest.payment) {
772
+ const fn = `register${pascalCase(name)}Provider`;
773
+ if (!paymentsText.includes(fn)) {
774
+ add({ severity: "error", scope, message: `lib/payments.ts \u043D\u0435 \u0432\u044B\u0437\u044B\u0432\u0430\u0435\u0442 ${fn}`, fix: `vitrine add ${name} (\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u0442 payments)` });
775
+ }
776
+ if (!configText.includes(`payments: ${JSON.stringify(manifest.payment.provider)}`)) {
777
+ add({ severity: "warn", scope, message: `\u0432 site.config integrations.payments \u2260 "${manifest.payment.provider}"`, fix: `vitrine add ${name} (\u0440\u0435\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u0442 integrations)` });
778
+ }
779
+ }
780
+ }
781
+ return { ok: !issues.some((i) => i.severity === "error"), issues };
782
+ }
783
+
784
+ // src/update.ts
785
+ import { rmSync as rmSync3 } from "fs";
786
+ import { join as join9 } from "path";
787
+
788
+ // src/merge.ts
789
+ function splitLines(s) {
790
+ return s.split("\n");
791
+ }
792
+ function lcsMatches(a, b) {
793
+ const n = a.length;
794
+ const m = b.length;
795
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
796
+ for (let i2 = n - 1; i2 >= 0; i2--) {
797
+ const row = dp[i2];
798
+ const next = dp[i2 + 1];
799
+ for (let j2 = m - 1; j2 >= 0; j2--) {
800
+ row[j2] = a[i2] === b[j2] ? next[j2 + 1] + 1 : Math.max(next[j2], row[j2 + 1]);
801
+ }
802
+ }
803
+ const pairs = [];
804
+ let i = 0;
805
+ let j = 0;
806
+ while (i < n && j < m) {
807
+ if (a[i] === b[j]) {
808
+ pairs.push([i, j]);
809
+ i++;
810
+ j++;
811
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
812
+ i++;
813
+ } else {
814
+ j++;
815
+ }
816
+ }
817
+ return pairs;
818
+ }
819
+ function changeHunks(base, other) {
820
+ const matches = lcsMatches(base, other);
821
+ const hunks = [];
822
+ let bi = 0;
823
+ let oi = 0;
824
+ const flush = (bEnd, oEnd) => {
825
+ if (bEnd > bi || oEnd > oi) hunks.push({ baseStart: bi, baseEnd: bEnd, lines: other.slice(oi, oEnd) });
826
+ };
827
+ for (const [mb, mo] of matches) {
828
+ flush(mb, mo);
829
+ bi = mb + 1;
830
+ oi = mo + 1;
831
+ }
832
+ flush(base.length, other.length);
833
+ return hunks;
834
+ }
835
+ function applyHunks(base, start, end, hunks) {
836
+ const res = [];
837
+ let i = start;
838
+ for (const h of hunks) {
839
+ for (; i < h.baseStart; i++) res.push(base[i]);
840
+ res.push(...h.lines);
841
+ i = h.baseEnd;
842
+ }
843
+ for (; i < end; i++) res.push(base[i]);
844
+ return res;
845
+ }
846
+ function eq(a, b) {
847
+ return a.length === b.length && a.every((x, i) => x === b[i]);
848
+ }
849
+ var MAX_LCS_CELLS = 4e6;
850
+ function merge3(base, ours, theirs, labels = {}) {
851
+ const B = splitLines(base);
852
+ const O = splitLines(ours);
853
+ const T = splitLines(theirs);
854
+ const ourLabel = labels.ours ?? "ours (\u0440\u0435\u043F\u043E \u043A\u043B\u0438\u0435\u043D\u0442\u0430)";
855
+ const theirLabel = labels.theirs ?? "theirs (\u0440\u0435\u0435\u0441\u0442\u0440)";
856
+ if (B.length * Math.max(O.length, T.length) > MAX_LCS_CELLS) {
857
+ if (ours === theirs || theirs === base) return { text: ours, clean: true, conflicts: 0 };
858
+ if (ours === base) return { text: theirs, clean: true, conflicts: 0 };
859
+ const text2 = [`<<<<<<< ${ourLabel}`, ours, "=======", theirs, `>>>>>>> ${theirLabel}`].join("\n");
860
+ return { text: text2, clean: false, conflicts: 1 };
861
+ }
862
+ const oh = changeHunks(B, O);
863
+ const th = changeHunks(B, T);
864
+ const out = [];
865
+ let conflicts = 0;
866
+ let p2 = 0;
867
+ let oi = 0;
868
+ let ti = 0;
869
+ const N = B.length;
870
+ const startOf = (hunks, idx) => idx < hunks.length ? hunks[idx].baseStart : Infinity;
871
+ while (p2 < N || oi < oh.length || ti < th.length) {
872
+ const nextChange = Math.min(startOf(oh, oi), startOf(th, ti));
873
+ if (p2 < nextChange) {
874
+ const upto = Math.min(nextChange, N);
875
+ for (; p2 < upto; p2++) out.push(B[p2]);
876
+ if (p2 >= N && oi >= oh.length && ti >= th.length) break;
877
+ continue;
878
+ }
879
+ const groupO = [];
880
+ const groupT = [];
881
+ let end = p2;
882
+ let grew = true;
883
+ while (grew) {
884
+ grew = false;
885
+ while (oi + groupO.length < oh.length) {
886
+ const h = oh[oi + groupO.length];
887
+ if (h.baseStart < end || h.baseStart === p2) {
888
+ groupO.push(h);
889
+ end = Math.max(end, h.baseEnd);
890
+ grew = true;
891
+ } else break;
892
+ }
893
+ while (ti + groupT.length < th.length) {
894
+ const h = th[ti + groupT.length];
895
+ if (h.baseStart < end || h.baseStart === p2) {
896
+ groupT.push(h);
897
+ end = Math.max(end, h.baseEnd);
898
+ grew = true;
899
+ } else break;
900
+ }
901
+ }
902
+ const ourText = applyHunks(B, p2, end, groupO);
903
+ const theirText = applyHunks(B, p2, end, groupT);
904
+ oi += groupO.length;
905
+ ti += groupT.length;
906
+ if (eq(ourText, theirText)) {
907
+ out.push(...ourText);
908
+ } else if (groupT.length === 0) {
909
+ out.push(...ourText);
910
+ } else if (groupO.length === 0) {
911
+ out.push(...theirText);
912
+ } else {
913
+ conflicts++;
914
+ out.push(`<<<<<<< ${ourLabel}`, ...ourText, "=======", ...theirText, `>>>>>>> ${theirLabel}`);
915
+ }
916
+ p2 = end;
917
+ }
918
+ return { text: out.join("\n"), clean: conflicts === 0, conflicts };
919
+ }
920
+
921
+ // src/update.ts
922
+ function planUpdate(project, name, registry) {
923
+ const pin = project.lock.features[name];
924
+ if (!pin) throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u0430`);
925
+ if (!registry.hasFeature(name)) throw new Error(`[vitrine] \u0444\u0438\u0447\u0430 "${name}" \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u0432 \u0440\u0435\u0435\u0441\u0442\u0440\u0435`);
926
+ const manifest = registry.loadManifest(name);
927
+ const fromVersion = pin.version;
928
+ const toVersion = manifest.kitVersion;
929
+ const featDir = registry.featureDir(name);
930
+ const originalsBase = join9(projectPaths(project.root).originals, `${name}@${fromVersion}`);
931
+ const files = [];
932
+ for (const map of manifest.files) {
933
+ for (const file of eachFeatureFile(featDir, map)) {
934
+ const theirs = readText(file.srcAbs);
935
+ const oursPath = join9(project.root, file.repoRel);
936
+ const basePath = join9(originalsBase, file.repoRel);
937
+ const ours = exists(oursPath) ? readText(oursPath) : null;
938
+ const base = exists(basePath) ? readText(basePath) : null;
939
+ if (ours === null) {
940
+ files.push({ to: file.toRel, status: "new", merged: theirs, conflicts: 0 });
941
+ } else if (theirs === base || theirs === ours) {
942
+ files.push({ to: file.toRel, status: "unchanged", merged: ours, conflicts: 0 });
943
+ } else if (base === null) {
944
+ files.push({ to: file.toRel, status: "conflict", merged: ours, conflicts: 1 });
945
+ } else {
946
+ const res = merge3(base, ours, theirs);
947
+ files.push({ to: file.toRel, status: res.clean ? "clean" : "conflict", merged: res.text, conflicts: res.conflicts });
948
+ }
949
+ }
950
+ }
951
+ return {
952
+ feature: name,
953
+ fromVersion,
954
+ toVersion,
955
+ files,
956
+ hasConflicts: files.some((f) => f.status === "conflict"),
957
+ changed: toVersion !== fromVersion || files.some((f) => f.status !== "unchanged")
958
+ };
959
+ }
960
+ function applyUpdate(project, plan, registry) {
961
+ const manifest = registry.loadManifest(plan.feature);
962
+ const featDir = registry.featureDir(plan.feature);
963
+ const paths = projectPaths(project.root);
964
+ const newOriginals = join9(paths.originals, `${plan.feature}@${plan.toVersion}`);
965
+ const oldOriginals = join9(paths.originals, `${plan.feature}@${plan.fromVersion}`);
966
+ const tx = new FsTransaction();
967
+ try {
968
+ for (const f of plan.files) {
969
+ if (f.status !== "unchanged") tx.write(safeJoin(project.root, f.to), f.merged);
970
+ }
971
+ for (const map of manifest.files) {
972
+ for (const file of eachFeatureFile(featDir, map)) {
973
+ tx.write(safeJoin(newOriginals, file.repoRel), readText(file.srcAbs));
974
+ }
975
+ }
976
+ project.lock.features[plan.feature] = { version: plan.toVersion };
977
+ regenerateDerived(project, registry, tx);
978
+ tx.commit();
979
+ } catch (error) {
980
+ tx.rollback();
981
+ throw error;
982
+ }
983
+ if (plan.fromVersion !== plan.toVersion) {
984
+ rmSync3(oldOriginals, { recursive: true, force: true });
985
+ }
986
+ }
987
+ function renderPlan(plan) {
988
+ const head = plan.fromVersion === plan.toVersion ? `${plan.feature} @ ${plan.toVersion}` : `${plan.feature} ${plan.fromVersion} \u2192 ${plan.toVersion}`;
989
+ const lines = plan.files.filter((f) => f.status !== "unchanged").map((f) => {
990
+ const mark = f.status === "conflict" ? "\u2717 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442" : f.status === "new" ? "+ \u043D\u043E\u0432\u044B\u0439 " : "~ \u0441\u043B\u0438\u044F\u043D\u0438\u0435";
991
+ return ` ${mark} ${f.to}${f.conflicts ? ` (\u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432: ${f.conflicts})` : ""}`;
992
+ });
993
+ return lines.length === 0 ? `${head}: \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 \u043D\u0435\u0442` : [head, ...lines].join("\n");
994
+ }
995
+
996
+ // src/commands.ts
997
+ function requireProject() {
998
+ const root = findProjectRoot();
999
+ if (!root) {
1000
+ throw new Error("[vitrine] \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D vitrine.json \u2014 \u044D\u0442\u043E \u043D\u0435 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043A\u043B\u0438\u0435\u043D\u0442\u0430 Vitrine");
1001
+ }
1002
+ return loadProject(root);
1003
+ }
1004
+ function addFeatures(names, registryRoot) {
1005
+ return installFeatures(requireProject(), names, createRegistrySource(registryRoot));
1006
+ }
1007
+ function removeFeatureCmd(name, registryRoot) {
1008
+ removeFeature(requireProject(), name, createRegistrySource(registryRoot));
1009
+ }
1010
+ function listFeatures(registryRoot) {
1011
+ const project = requireProject();
1012
+ const registry = createRegistrySource(registryRoot);
1013
+ const installed = Object.keys(project.lock.features);
1014
+ const available = registry.listFeatures().filter((name) => !installed.includes(name));
1015
+ return { installed, available };
1016
+ }
1017
+ function designApplyCmd(opts = {}) {
1018
+ return designApply(requireProject(), { bin: opts.bin, dryRun: opts.dryRun });
1019
+ }
1020
+ function doctorCmd(registryRoot) {
1021
+ return runDoctor(requireProject(), createRegistrySource(registryRoot));
1022
+ }
1023
+ function updateFeaturesCmd(names, registryRoot, opts = {}) {
1024
+ const project = requireProject();
1025
+ const registry = createRegistrySource(registryRoot);
1026
+ const targets = names.length > 0 ? names : Object.keys(project.lock.features);
1027
+ return targets.map((name) => {
1028
+ const plan = planUpdate(project, name, registry);
1029
+ const applied = plan.changed && !opts.dryRun;
1030
+ if (applied) applyUpdate(project, plan, registry);
1031
+ return { plan, applied };
1032
+ });
1033
+ }
1034
+ function diffFeatureCmd(name, registryRoot) {
1035
+ return planUpdate(requireProject(), name, createRegistrySource(registryRoot));
1036
+ }
1037
+
1038
+ // src/init.ts
1039
+ import { readdirSync as readdirSync3 } from "fs";
1040
+ import { join as join11 } from "path";
1041
+
1042
+ // src/kit.ts
1043
+ var KIT_VERSION = "0.0.0";
1044
+ var CONTRACTS_VERSION = "1.0.0";
1045
+ var CONTRACTS_RANGE = "^1.0.0";
1046
+ var CORE_RANGE = "^0.1.0";
1047
+ var BLUEPRINT_RANGE = "^0.1.0";
1048
+ var REACT_RANGE = "^18.3.1";
1049
+ var CLIENT_REACT_RANGE = "^19.0.0";
1050
+ var NEXT_RANGE = "^15.1.0";
1051
+ var PAYLOAD_RANGE = "^3.0.0";
1052
+
1053
+ // src/templates.ts
1054
+ import { existsSync as existsSync7 } from "fs";
1055
+ import { dirname as dirname5, join as join10 } from "path";
1056
+ function templatesRoot(registryRoot) {
1057
+ return join10(dirname5(registryRoot), "templates");
1058
+ }
1059
+ function hasTemplate(root, name) {
1060
+ return existsSync7(join10(root, name, "files"));
1061
+ }
1062
+ function copyTemplate(root, name, destRoot) {
1063
+ const filesDir = join10(root, name, "files");
1064
+ const rels = walkRelFiles(filesDir);
1065
+ for (const rel of rels) writeText(join10(destRoot, rel), readText(join10(filesDir, rel)));
1066
+ return rels;
1067
+ }
1068
+
1069
+ // src/init.ts
1070
+ function defaultBackend(tier) {
1071
+ return tier === "full-store" ? "vendure" : "payload";
1072
+ }
1073
+ var PAYMENT_PROVIDER_FEATURES = [
1074
+ "checkout-stripe",
1075
+ "checkout-paddle",
1076
+ "checkout-yookassa"
1077
+ ];
1078
+ function suggestFeatures(tier, registry, backend = defaultBackend(tier)) {
1079
+ const core = ["catalog", "product-page", "seo"];
1080
+ const shop = backend === "vendure" ? ["cart"] : ["cart", "checkout-stripe", "reviews"];
1081
+ const desired = tier === "catalog" ? core : [...core, ...shop];
1082
+ return desired.filter((name) => registry.hasFeature(name));
1083
+ }
1084
+ function clientPackageJson(name, backend) {
1085
+ const dependencies = {
1086
+ "@vitrine-kit/contracts": CONTRACTS_RANGE,
1087
+ "@vitrine-kit/core": CORE_RANGE
1088
+ };
1089
+ const devDependencies = {
1090
+ "@types/node": "^20.17.0",
1091
+ typescript: "^5.7.2"
1092
+ };
1093
+ let scripts = {};
1094
+ if (backend === "payload") {
1095
+ Object.assign(dependencies, {
1096
+ "@vitrine-kit/payload-blueprint": BLUEPRINT_RANGE,
1097
+ "@payloadcms/db-postgres": PAYLOAD_RANGE,
1098
+ "@payloadcms/db-sqlite": PAYLOAD_RANGE,
1099
+ "@payloadcms/next": PAYLOAD_RANGE,
1100
+ "@payloadcms/richtext-lexical": PAYLOAD_RANGE,
1101
+ graphql: "^16.9.0",
1102
+ next: NEXT_RANGE,
1103
+ payload: PAYLOAD_RANGE,
1104
+ react: CLIENT_REACT_RANGE,
1105
+ "react-dom": CLIENT_REACT_RANGE,
1106
+ sharp: "^0.33.5"
1107
+ });
1108
+ Object.assign(devDependencies, {
1109
+ "@types/react": "^19.0.0",
1110
+ "@types/react-dom": "^19.0.0",
1111
+ autoprefixer: "^10.4.20",
1112
+ postcss: "^8.4.49",
1113
+ tailwindcss: "^3.4.17"
1114
+ });
1115
+ scripts = {
1116
+ dev: "next dev",
1117
+ build: "next build",
1118
+ start: "next start",
1119
+ "generate:types": "payload generate:types",
1120
+ payload: "payload"
1121
+ };
1122
+ } else if (backend === "vendure") {
1123
+ Object.assign(dependencies, {
1124
+ "@vendure/asset-server-plugin": "^3.0.0",
1125
+ "@vendure/core": "^3.0.0",
1126
+ "better-sqlite3": "^11.0.0",
1127
+ graphql: "^16.9.0",
1128
+ next: NEXT_RANGE,
1129
+ pg: "^8.13.0",
1130
+ react: CLIENT_REACT_RANGE,
1131
+ "react-dom": CLIENT_REACT_RANGE
1132
+ });
1133
+ Object.assign(devDependencies, {
1134
+ "@types/react": "^19.0.0",
1135
+ "@types/react-dom": "^19.0.0",
1136
+ autoprefixer: "^10.4.20",
1137
+ postcss: "^8.4.49",
1138
+ tailwindcss: "^3.4.17",
1139
+ tsx: "^4.19.2"
1140
+ });
1141
+ scripts = {
1142
+ dev: "next dev",
1143
+ build: "next build",
1144
+ start: "next start",
1145
+ vendure: "tsx src/index.ts"
1146
+ };
1147
+ } else {
1148
+ dependencies.react = REACT_RANGE;
1149
+ }
1150
+ return {
1151
+ name,
1152
+ version: "0.1.0",
1153
+ private: true,
1154
+ type: "module",
1155
+ scripts,
1156
+ dependencies: sortKeys(dependencies),
1157
+ devDependencies: sortKeys(devDependencies)
1158
+ };
1159
+ }
1160
+ function clientEnvExample(backend) {
1161
+ if (backend === "vendure") {
1162
+ return [
1163
+ "# \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.",
1164
+ "",
1165
+ "# \u0411\u0414. \u041F\u0443\u0441\u0442\u043E \u0432 dev \u2014 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 SQLite (.vitrine/vendure.sqlite).",
1166
+ "DATABASE_URL=",
1167
+ "",
1168
+ "# Vendure Shop API (\u0432\u0438\u0442\u0440\u0438\u043D\u0430 \u2192 \u0441\u0435\u0440\u0432\u0435\u0440).",
1169
+ "VENDURE_SHOP_API_URL=http://localhost:3001/shop-api",
1170
+ "",
1171
+ "# \u0421\u0443\u043F\u0435\u0440\u0430\u0434\u043C\u0438\u043D Vendure (dev-\u0434\u0435\u0444\u043E\u043B\u0442 superadmin/superadmin; \u0441\u043C\u0435\u043D\u0438\u0442\u0435 \u0434\u043B\u044F \u043F\u0440\u043E\u0434\u0430).",
1172
+ "VENDURE_SUPERADMIN_USERNAME=",
1173
+ "VENDURE_SUPERADMIN_PASSWORD=",
1174
+ "VENDURE_COOKIE_SECRET=",
1175
+ "",
1176
+ "# \u0411\u0430\u0437\u043E\u0432\u044B\u0439 URL \u0432\u0438\u0442\u0440\u0438\u043D\u044B.",
1177
+ "NEXT_PUBLIC_SITE_URL=http://localhost:3000",
1178
+ ""
1179
+ ].join("\n");
1180
+ }
1181
+ if (backend !== "payload") {
1182
+ return "# \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.\nDATABASE_URL=\n";
1183
+ }
1184
+ return [
1185
+ "# \u041E\u043A\u0440\u0443\u0436\u0435\u043D\u0438\u0435 \u043F\u0440\u043E\u0435\u043A\u0442\u0430.",
1186
+ "",
1187
+ "# \u0411\u0414. \u0414\u043B\u044F \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0433\u043E dev \u043C\u043E\u0436\u043D\u043E \u043E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043F\u0443\u0441\u0442\u044B\u043C \u2014 \u0431\u0443\u0434\u0435\u0442 SQLite-fallback (.vitrine/dev.sqlite).",
1188
+ "DATABASE_URL=",
1189
+ "",
1190
+ "# \u0421\u0435\u043A\u0440\u0435\u0442 Payload (\u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D; \u0441\u0433\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0439\u0442\u0435 \u0441\u043B\u0443\u0447\u0430\u0439\u043D\u044B\u0439 \u0434\u043B\u044F \u043F\u0440\u043E\u0434\u0430).",
1191
+ "PAYLOAD_SECRET=",
1192
+ "",
1193
+ "# Dev-\u0430\u0434\u043C\u0438\u043D (\u0441\u043E\u0437\u0434\u0430\u0451\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u0432 dev \u043F\u0440\u0438 \u043F\u0443\u0441\u0442\u043E\u0439 \u0411\u0414; \u043F\u0430\u0440\u043E\u043B\u044C \u043F\u0435\u0447\u0430\u0442\u0430\u0435\u0442\u0441\u044F \u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u044C).",
1194
+ "DEV_ADMIN_EMAIL=",
1195
+ "DEV_ADMIN_PASSWORD=",
1196
+ "",
1197
+ "# \u0411\u0430\u0437\u043E\u0432\u044B\u0439 URL \u0441\u0430\u0439\u0442\u0430 (canonical, OG).",
1198
+ "NEXT_PUBLIC_SITE_URL=http://localhost:3000",
1199
+ "",
1200
+ "# \u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C SQLite-fallback \u0434\u0430\u0436\u0435 \u0432 dev (\u043B\u043E\u0432\u0438\u0442\u044C \u043E\u043F\u0435\u0447\u0430\u0442\u043A\u0438 \u043A\u043E\u043D\u0444\u0438\u0433\u0430):",
1201
+ "# VITRINE_DB_STRICT=1",
1202
+ ""
1203
+ ].join("\n");
1204
+ }
1205
+ function clientReadme(name, backend, tier) {
1206
+ const run = backend === "vendure" ? [
1207
+ "```bash",
1208
+ "pnpm install",
1209
+ "cp .env.example .env",
1210
+ "pnpm vendure # Vendure-\u0441\u0435\u0440\u0432\u0435\u0440 (Shop API \u043D\u0430 :3001) \u2014 \u0432 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E\u043C \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0435",
1211
+ "pnpm dev # \u0432\u0438\u0442\u0440\u0438\u043D\u0430 \u043D\u0430 :3000",
1212
+ "```",
1213
+ "",
1214
+ "\u0411\u0435\u0437 Postgres dev \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 SQLite (`.vitrine/vendure.sqlite`) \u0438",
1215
+ "populate-\u0441\u0438\u0434. \u0421\u0443\u043F\u0435\u0440\u0430\u0434\u043C\u0438\u043D \u2014 \u0438\u0437 `VENDURE_SUPERADMIN_*` (dev-\u0434\u0435\u0444\u043E\u043B\u0442",
1216
+ "superadmin/superadmin; \u0441\u043C\u0435\u043D\u0438\u0442\u0435 \u0434\u043B\u044F \u043F\u0440\u043E\u0434\u0430)."
1217
+ ].join("\n") : [
1218
+ "```bash",
1219
+ "pnpm install",
1220
+ "cp .env.example .env",
1221
+ "pnpm dev",
1222
+ "```",
1223
+ "",
1224
+ "- \u0412\u0438\u0442\u0440\u0438\u043D\u0430: http://localhost:3000",
1225
+ "- \u0410\u0434\u043C\u0438\u043D\u043A\u0430: http://localhost:3000/admin",
1226
+ "",
1227
+ "\u0411\u0435\u0437 Postgres dev \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0432\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 SQLite (`.vitrine/dev.sqlite`), \u0437\u0430\u043F\u043E\u043B\u043D\u044F\u0435\u0442",
1228
+ "\u0434\u0435\u043C\u043E-\u043A\u0430\u0442\u0430\u043B\u043E\u0433 (5 \u0442\u043E\u0432\u0430\u0440\u043E\u0432, 2 \u043A\u0430\u0442\u0435\u0433\u043E\u0440\u0438\u0438) \u0438 \u0437\u0430\u0432\u043E\u0434\u0438\u0442 dev-\u0430\u0434\u043C\u0438\u043D\u0430 (\u043B\u043E\u0433\u0438\u043D/\u043F\u0430\u0440\u043E\u043B\u044C \u043F\u0435\u0447\u0430\u0442\u0430\u044E\u0442\u0441\u044F",
1229
+ "\u0432 \u043A\u043E\u043D\u0441\u043E\u043B\u044C \u043E\u0434\u0438\u043D \u0440\u0430\u0437). \u041E\u0442\u043A\u043B\u044E\u0447\u0438\u0442\u044C fallback \u0438 \u0432 dev \u2014 `VITRINE_DB_STRICT=1`."
1230
+ ].join("\n");
1231
+ const deploySecret = backend === "vendure" ? "export VENDURE_COOKIE_SECRET=... # \u0441\u0435\u043A\u0440\u0435\u0442 cookie Vendure" : "export PAYLOAD_SECRET=... # \u0441\u043B\u0443\u0447\u0430\u0439\u043D\u044B\u0439 \u0441\u0435\u043A\u0440\u0435\u0442 Payload";
1232
+ return `# ${name}
1233
+
1234
+ \u041A\u043B\u0438\u0435\u043D\u0442\u0441\u043A\u0438\u0439 \u043F\u0440\u043E\u0435\u043A\u0442 \u043D\u0430 Vitrine. Backend: \`${backend}\`, \u0443\u0440\u043E\u0432\u0435\u043D\u044C: \`${tier}\`.
1235
+ Next.js + Tailwind; \u0444\u0438\u0447\u0438 \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u044B \u0438\u0437 \u0440\u0435\u0435\u0441\u0442\u0440\u0430 Vitrine \u2014 \u0432\u044B \u0432\u043B\u0430\u0434\u0435\u0435\u0442\u0435 \u043A\u043E\u0434\u043E\u043C \u0438
1236
+ \u0441\u0442\u0438\u043B\u0438\u0437\u0443\u0435\u0442\u0435 \u0435\u0433\u043E \u0442\u043E\u043A\u0435\u043D\u0430\u043C\u0438 (\`theme/client.css\`), \u043D\u0435 \u043C\u0435\u043D\u044F\u044F \u043B\u043E\u0433\u0438\u043A\u0443.
1237
+
1238
+ ## 1. \u041F\u0440\u0435\u0434\u0443\u0441\u043B\u043E\u0432\u0438\u044F
1239
+
1240
+ Node >= 20 (LTS) \u0438 \`pnpm\`. \u041F\u0430\u043A\u0435\u0442\u044B \`@vitrine-kit/*\` \u043F\u0443\u0431\u043B\u0438\u0447\u043D\u044B \u0432 npm \u2014
1241
+ \u0442\u043E\u043A\u0435\u043D \u0434\u043B\u044F \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043A\u0438 \u043D\u0435 \u043D\u0443\u0436\u0435\u043D.
1242
+
1243
+ ## 2. \u041B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0437\u0430\u043F\u0443\u0441\u043A (zero-config)
1244
+
1245
+ ${run}
1246
+
1247
+ ## 3. \u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u043A\u043B\u0438\u0435\u043D\u0442\u0430
1248
+
1249
+ 1. \u041F\u043E\u043B\u043E\u0436\u0438\u0442\u0435 \u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0431\u0440\u0435\u043D\u0434\u0430 (Figma export, \u0441\u043A\u0440\u0438\u043D\u0448\u043E\u0442\u044B, \u0430\u0441\u0441\u0435\u0442\u044B) \u0432 \`/design\`.
1250
+ 2. \`vitrine design apply\` \u2014 \u0418\u0418 \u0437\u0430\u0434\u0430\u0451\u0442 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0432 \`theme/client.css\`
1251
+ (\u043B\u043E\u0433\u0438\u043A\u0443/\u0434\u0430\u043D\u043D\u044B\u0435/\u0440\u043E\u0443\u0442\u0438\u043D\u0433/a11y \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442). \u0428\u0430\u0433 \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u0435\u043D.
1252
+
1253
+ ## 4. \u0424\u0438\u0447\u0438: \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C, \u0443\u0431\u0440\u0430\u0442\u044C, \u043F\u043E\u0441\u043C\u043E\u0442\u0440\u0435\u0442\u044C
1254
+
1255
+ \`\`\`bash
1256
+ vitrine list # \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 + \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435
1257
+ vitrine add reviews # \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0438\u0447\u0443: \u0444\u043B\u0430\u0433, \u0441\u043B\u043E\u0442\u044B, blueprint, env
1258
+ vitrine remove reviews # \u0443\u0431\u0440\u0430\u0442\u044C (\u0435\u0441\u043B\u0438 \u0444\u0438\u0447\u0430 removable)
1259
+ vitrine design apply # \u0441\u0442\u0438\u043B\u0438\u0437\u043E\u0432\u0430\u0442\u044C \u043D\u043E\u0432\u0443\u044E \u0444\u0438\u0447\u0443
1260
+ \`\`\`
1261
+
1262
+ \`add\` \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u0435\u043D \u0438 \u0442\u0440\u0430\u043D\u0437\u0430\u043A\u0446\u0438\u043E\u043D\u0435\u043D (\u043E\u0442\u043A\u0430\u0442 \u043F\u0440\u0438 \u043E\u0448\u0438\u0431\u043A\u0435); \u043E\u0440\u0438\u0433\u0438\u043D\u0430\u043B\u044B \u0432\u0435\u0440\u0441\u0438\u0439 \u043F\u0438\u0448\u0443\u0442\u0441\u044F \u0432
1263
+ \`.vitrine/originals/\` \u2014 \u043E\u0441\u043D\u043E\u0432\u0430 \u0434\u043B\u044F 3-way merge \u043F\u0440\u0438 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0438.
1264
+
1265
+ ## 5. \u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u0438 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430
1266
+
1267
+ \`\`\`bash
1268
+ vitrine kit update # \u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u044D\u0448 \u0440\u0435\u0435\u0441\u0442\u0440\u0430/\u0448\u0430\u0431\u043B\u043E\u043D\u043E\u0432 \u0441 GitHub
1269
+ vitrine diff <feature> # \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u0444\u0438\u0447\u0438
1270
+ vitrine update [feature] # 3-way merge \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0444\u0438\u0447\u0438 (\u0431\u0430\u0437\u0430 = \u0432\u0430\u0448 \u0441\u043D\u0430\u043F\u0448\u043E\u0442)
1271
+ vitrine doctor # \u043A\u043E\u043D\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C vitrine.json \u2194 \u0444\u0430\u0439\u043B\u044B \u2194 \u043F\u0430\u043A\u0435\u0442\u044B \u2194 env
1272
+ \`\`\`
1273
+
1274
+ \u041F\u0430\u043A\u0435\u0442\u044B \`@vitrine-kit/*\` \u0432\u0435\u0440\u0441\u0438\u043E\u043D\u0438\u0440\u0443\u044E\u0442\u0441\u044F \u043D\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E: \u0444\u0438\u043A\u0441 \u0432 \`core\` \u043F\u043E\u0434\u043D\u0438\u043C\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E
1275
+ \`@vitrine-kit/core\`, \u0430 \`@vitrine-kit/contracts\` \u043E\u0441\u0442\u0430\u0451\u0442\u0441\u044F \u043D\u0430 \u0441\u0432\u043E\u0435\u0439 \u0441\u0442\u0430\u0431\u0438\u043B\u044C\u043D\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u2014
1276
+ \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0439\u0442\u0435 \u0432\u0435\u0440\u0441\u0438\u0438 \u0432 \`package.json\` \u0442\u043E\u0447\u0435\u0447\u043D\u043E.
1277
+
1278
+ ## 6. \u0414\u0435\u043F\u043B\u043E\u0439 (VPS + Docker)
1279
+
1280
+ \`\`\`bash
1281
+ ${deploySecret}
1282
+ docker compose up --build
1283
+ \`\`\`
1284
+
1285
+ Production \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0440\u0435\u0430\u043B\u044C\u043D\u044B\u0439 \`DATABASE_URL\` \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0441\u0442\u0430\u0440\u0442 \u043F\u0440\u0435\u0440\u044B\u0432\u0430\u0435\u0442\u0441\u044F
1286
+ (SQLite-fallback \u0442\u043E\u043B\u044C\u043A\u043E \u0432 dev).
1287
+ `;
1288
+ }
1289
+ function scaffoldBase(opts) {
1290
+ const { root, name, backend, tier } = opts;
1291
+ const tRoot = templatesRoot(opts.registry.root);
1292
+ const baseCopied = hasTemplate(tRoot, "base");
1293
+ if (baseCopied) copyTemplate(tRoot, "base", root);
1294
+ const backendTemplate = `backend-${backend}`;
1295
+ if (hasTemplate(tRoot, backendTemplate)) copyTemplate(tRoot, backendTemplate, root);
1296
+ if (!baseCopied) {
1297
+ writeText(join11(root, ".gitignore"), "node_modules/\n.next/\ndist/\n.env\n.env.local\n.vitrine/\n");
1298
+ }
1299
+ writeText(
1300
+ join11(root, "vitrine.json"),
1301
+ `${JSON.stringify(
1302
+ { kitVersion: KIT_VERSION, contracts: CONTRACTS_VERSION, backend, tier, features: {} },
1303
+ null,
1304
+ 2
1305
+ )}
1306
+ `
1307
+ );
1308
+ writeText(
1309
+ join11(root, "site.config.ts"),
1310
+ `import type { SiteConfig } from '@vitrine-kit/contracts';
1311
+
1312
+ export const siteConfig: SiteConfig = {
1313
+ backend: ${JSON.stringify(backend)},
1314
+ tier: ${JSON.stringify(tier)},
1315
+ // vitrine:features:start
1316
+ features: {},
1317
+ // vitrine:features:end
1318
+ layout: { sections: [] },
1319
+ theme: { name: 'default', cssFile: 'theme/client.css' },
1320
+ // vitrine:integrations:start
1321
+ integrations: {},
1322
+ // vitrine:integrations:end
1323
+ i18n: { defaultLocale: 'ru', locales: ['ru'], currency: 'RUB' },
1324
+ };
1325
+
1326
+ export default siteConfig;
1327
+ `
1328
+ );
1329
+ writeText(
1330
+ join11(root, "CLAUDE.md"),
1331
+ `# ${name}
1332
+
1333
+ \u041F\u0440\u043E\u0435\u043A\u0442 \u043D\u0430 Vitrine. Backend: \`${backend}\`, \u0443\u0440\u043E\u0432\u0435\u043D\u044C: \`${tier}\`.
1334
+ \u042D\u0442\u043E\u0442 \u0444\u0430\u0439\u043B \u2014 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0439 \u0433\u0430\u0439\u0434 \u0434\u043B\u044F \u0418\u0418-\u0430\u0433\u0435\u043D\u0442\u0430 (Claude Code) \u0438 \u0440\u0430\u0437\u0440\u0430\u0431\u043E\u0442\u0447\u0438\u043A\u0430. \u0412\u0441\u0435 \u043E\u043F\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043E
1335
+ \u0441\u0442\u0430\u0440\u0442\u0435\u0440-\u043A\u0438\u0442\u043E\u043C \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u044E\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 CLI \`vitrine\`; \u0433\u043E\u0442\u043E\u0432\u044B\u0435 \u043F\u043E\u0442\u043E\u043A\u0438 \u2014 \u0441\u043B\u044D\u0448-\u043A\u043E\u043C\u0430\u043D\u0434\u0430\u043C\u0438 \u0432 \`.claude/commands/\`.
1336
+
1337
+ ## \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0444\u0438\u0447\u0438
1338
+ <!-- vitrine:features:start -->
1339
+ _\u0424\u0438\u0447\u0438 \u0435\u0449\u0451 \u043D\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B._
1340
+ <!-- vitrine:features:end -->
1341
+
1342
+ ## \u041A\u043E\u043C\u0430\u043D\u0434\u044B vitrine CLI
1343
+
1344
+ | \u041A\u043E\u043C\u0430\u043D\u0434\u0430 | \u041D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 | \u041A\u043E\u0433\u0434\u0430 \u0437\u0432\u0430\u0442\u044C | \u0424\u043B\u0430\u0433\u0438 |
1345
+ |---|---|---|---|
1346
+ | \`vitrine list\` | \u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0438 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u0444\u0438\u0447\u0438 | \u043F\u0435\u0440\u0435\u0434 \u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0444\u0438\u0447\u0438 | \u2014 |
1347
+ | \`vitrine add <features\u2026>\` | \u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0444\u0438\u0447\u0443(\u0438): \u0444\u0430\u0439\u043B\u044B, \u0444\u043B\u0430\u0433, \u0441\u043B\u043E\u0442\u044B, blueprint, env, deps | \xAB\u0434\u043E\u0431\u0430\u0432\u044C \u0444\u0438\u0447\u0443 X\xBB | \`--registry\` |
1348
+ | \`vitrine remove <feature>\` | \u0423\u0431\u0440\u0430\u0442\u044C \u0444\u0438\u0447\u0443 (\u0435\u0441\u043B\u0438 \`removable\`) | \xAB\u0443\u0431\u0435\u0440\u0438 \u0444\u0438\u0447\u0443 X\xBB | \`--registry\` |
1349
+ | \`vitrine update [features\u2026]\` | \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0444\u0438\u0447\u0438 3-way merge (\u0431\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u2014 \u0432\u0441\u0435) | \u043F\u043E\u0441\u043B\u0435 \`kit update\` | \`--dry-run\`, \`--registry\` |
1350
+ | \`vitrine diff <feature>\` | \u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F (\u0431\u0435\u0437 \u0437\u0430\u043F\u0438\u0441\u0438) | \u043F\u0435\u0440\u0435\u0434 \`update\` | \`--registry\` |
1351
+ | \`vitrine doctor\` | \u041A\u043E\u043D\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C: \`vitrine.json\` \u2194 \u0444\u0430\u0439\u043B\u044B \u2194 \u043F\u0430\u043A\u0435\u0442\u044B \u2194 env | \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0430\u0432\u043E\u043A, \u043F\u0440\u0438 \u0441\u043E\u043C\u043D\u0435\u043D\u0438\u044F\u0445 | \`--registry\` |
1352
+ | \`vitrine design apply\` | \u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u0438\u0437 \`/design\` \u043A \u0442\u043E\u043A\u0435\u043D\u0430\u043C (\u0447\u0435\u0440\u0435\u0437 Claude Code) | \u043F\u043E\u0441\u043B\u0435 \`add\` \u0438\u043B\u0438 \u0441\u043C\u0435\u043D\u044B \u0431\u0440\u0435\u043D\u0434\u0430 | \`--bin\`, \`--dry-run\` |
1353
+ | \`vitrine kit update\` | \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u044D\u0448 \u0440\u0435\u0435\u0441\u0442\u0440\u0430/\u0448\u0430\u0431\u043B\u043E\u043D\u043E\u0432 \u0441 GitHub | \u043F\u0435\u0440\u0435\u0434 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435\u043C \u0444\u0438\u0447 | \`--from\`, \`--version\`, \`--channel\` |
1354
+ | \`vitrine kit status\` | \u0412\u0435\u0440\u0441\u0438\u044F \u043A\u044D\u0448\u0430 vs \u043E\u0436\u0438\u0434\u0430\u0435\u043C\u0430\u044F CLI | \u0434\u0438\u0430\u0433\u043D\u043E\u0441\u0442\u0438\u043A\u0430 | \u2014 |
1355
+ | \`vitrine self-update\` | \u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0430\u043C CLI | \u0440\u0435\u0434\u043A\u043E | \`--dry-run\` |
1356
+
1357
+ \`init\` \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u043E\u0434\u0438\u043D \u0440\u0430\u0437 \u043F\u0440\u0438 \u0441\u043E\u0437\u0434\u0430\u043D\u0438\u0438 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u044F (\u0432\u0438\u0437\u0430\u0440\u0434 \`vitrine init\`). \`add\`/\`update\`
1358
+ \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u043D\u044B \u0438 \u0442\u0440\u0430\u043D\u0437\u0430\u043A\u0446\u0438\u043E\u043D\u043D\u044B (\u043E\u0442\u043A\u0430\u0442 \u043F\u0440\u0438 \u043E\u0448\u0438\u0431\u043A\u0435); \u043E\u0440\u0438\u0433\u0438\u043D\u0430\u043B\u044B \u0432\u0435\u0440\u0441\u0438\u0439 \u043F\u0438\u0448\u0443\u0442\u0441\u044F \u0432 \`.vitrine/originals/\`
1359
+ \u2014 \u0431\u0430\u0437\u0430 \u0434\u043B\u044F 3-way merge.
1360
+
1361
+ ## \u0422\u0438\u043F\u043E\u0432\u044B\u0435 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0438
1362
+
1363
+ - **\u041D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430 \u043F\u0440\u043E\u0435\u043A\u0442\u0430** \u2192 \`/setup\`: \u0437\u0430\u0432\u0438\u0441\u0438\u043C\u043E\u0441\u0442\u0438, GitHub PAT, \`.env\`, \u0437\u0430\u043F\u0443\u0441\u043A dev-\u0441\u0435\u0440\u0432\u0435\u0440\u0430.
1364
+ - **\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0438 \u0441\u0442\u0438\u043B\u0438\u0437\u043E\u0432\u0430\u0442\u044C \u0444\u0438\u0447\u0443** \u2192 \`/add-feature <\u0438\u043C\u044F>\`: \`list\` \u2192 \`add\` \u2192 \`design apply\` \u2192 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430.
1365
+ - **\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C/\u043E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D** \u2192 \`/design\`: \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u044C \u044D\u043A\u0441\u043F\u043E\u0440\u0442 \u0432 \`/design\`, \`design apply\`.
1366
+ - **\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0444\u0438\u0447\u0438** \u2192 \`/update\`: \`kit update\` \u2192 \`diff\` \u2192 \`update\` \u2192 \u0440\u0430\u0437\u0440\u0443\u043B\u0438\u0442\u044C \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u044B \u2192 \`doctor\`.
1367
+ - **\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043A\u043E\u043D\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C** \u2192 \`/doctor\`.
1368
+
1369
+ \u041F\u043E\u043B\u043D\u044B\u0439 \u0433\u0430\u0439\u0434 \u0434\u043B\u044F \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u2014 \u0432 \`README.md\`.
1370
+
1371
+ ## \u0418\u041D\u0421\u0422\u0420\u0423\u041A\u0426\u0418\u042F: \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u0438\u0437 /design
1372
+ \u0412\u0445\u043E\u0434: \u0432\u0441\u0451 \u0432 \`/design\`.
1373
+ \u0417\u0430\u0434\u0430\u0447\u0430: \u0438\u0437\u0432\u043B\u0435\u0447\u044C \u0432\u0438\u0437\u0443\u0430\u043B\u044C\u043D\u044B\u0439 \u044F\u0437\u044B\u043A (\u043F\u0430\u043B\u0438\u0442\u0440\u0430, \u0442\u0438\u043F\u043E\u0433\u0440\u0430\u0444\u0438\u043A\u0430, \u043E\u0442\u0441\u0442\u0443\u043F\u044B, \u0440\u0430\u0434\u0438\u0443\u0441\u044B, \u0442\u0435\u043D\u0438,
1374
+ \u0432\u0438\u0434 \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0445 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432) \u0438 \u043F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u043A \u043F\u0440\u043E\u0435\u043A\u0442\u0443.
1375
+ \u041F\u0440\u0438\u043C\u0435\u043D\u044F\u0442\u044C \u0442\u0430\u043A:
1376
+ 1) \u0437\u0430\u0434\u0430\u0442\u044C \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u043E\u0432 \u0432 \`theme/client.css\` \u2014 \u043E\u0441\u043D\u043E\u0432\u043D\u043E\u0439 \u0440\u044B\u0447\u0430\u0433;
1377
+ 2) \u0442\u043E\u043B\u044C\u043A\u043E \u0435\u0441\u043B\u0438 \u0442\u043E\u043A\u0435\u043D \u043D\u0435 \u0432\u044B\u0440\u0430\u0436\u0430\u0435\u0442 \u043D\u0443\u0436\u043D\u043E\u0435 \u2014 \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u0435\u0437\u0435\u043D\u0442\u0430\u0446\u0438\u043E\u043D\u043D\u044B\u0435 \u043A\u043B\u0430\u0441\u0441\u044B
1378
+ \u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u043E\u043C\u0443 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u0443, \u041D\u0415 \u043C\u0435\u043D\u044F\u044F \u0435\u0433\u043E \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443.
1379
+ \u041D\u0415 \u043C\u0435\u043D\u044F\u0442\u044C: \u043B\u043E\u0433\u0438\u043A\u0443 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432, \u0432\u044B\u0431\u043E\u0440\u043A\u0443 \u0434\u0430\u043D\u043D\u044B\u0445, \u0432\u044B\u0437\u043E\u0432\u044B \u0430\u0434\u0430\u043F\u0442\u0435\u0440\u0430, \u0440\u043E\u0443\u0442\u0438\u043D\u0433,
1380
+ a11y-\u0440\u043E\u043B\u0438/\u043B\u0435\u0439\u0431\u043B\u044B, \u043F\u0443\u0431\u043B\u0438\u0447\u043D\u044B\u0435 \u043F\u0440\u043E\u043F\u0441\u044B. \u0422\u043E\u043A\u0435\u043D\u044B \u2014 \u044D\u0442\u043E \u0438\u043D\u0442\u0435\u0440\u0444\u0435\u0439\u0441.
1381
+ \u0415\u0441\u043B\u0438 \u0434\u0438\u0437\u0430\u0439\u043D \u0442\u0440\u0435\u0431\u0443\u0435\u0442 \u0438\u043D\u043E\u0439 \u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u044B \u0441\u0435\u043A\u0446\u0438\u0438 \u2014 \u0441\u043E\u0437\u0434\u0430\u0442\u044C override \u0441\u0435\u043A\u0446\u0438\u0438 \u0432 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0438
1382
+ (\u043A\u043E\u043C\u043F\u043E\u0437\u0438\u0446\u0438\u044F), \u0430 \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043E\u0431\u0449\u0438\u0439 wireframe.
1383
+ \u0428\u0430\u0433 \u0438\u0434\u0435\u043C\u043F\u043E\u0442\u0435\u043D\u0442\u0435\u043D: \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u044B\u0439 \u043F\u0440\u043E\u0433\u043E\u043D \u0441\u0445\u043E\u0434\u0438\u0442\u0441\u044F, \u043D\u0435 \u043D\u0430\u043A\u0430\u043F\u043B\u0438\u0432\u0430\u0435\u0442 \u043C\u0443\u0441\u043E\u0440.
1384
+
1385
+ ## \u0413\u0440\u0430\u043D\u0438\u0446\u044B (\u0447\u0442\u043E \u0430\u0433\u0435\u043D\u0442\u0443 \u043D\u0435\u043B\u044C\u0437\u044F \u0442\u0440\u043E\u0433\u0430\u0442\u044C)
1386
+ - **\u0413\u0435\u043D\u0435\u0440\u0438\u0440\u0443\u0435\u043C\u044B\u0435/\u0443\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u043C\u044B\u0435 \u0444\u0430\u0439\u043B\u044B \u2014 \u0440\u0443\u043A\u0430\u043C\u0438 \u043D\u0435 \u043F\u0440\u0430\u0432\u0438\u0442\u044C** (CLI \u043F\u0435\u0440\u0435\u0437\u0430\u0442\u0440\u0451\u0442 \u0438\u0437 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F):
1387
+ \`lib/slots.ts\`, \`lib/payments.ts\`, \`lib/blueprint.ts\`, \u0443\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u043C\u044B\u0435 \u0440\u0435\u0433\u0438\u043E\u043D\u044B \`site.config.ts\`
1388
+ (\`features\`/\`integrations\`), \`vitrine.json\`, \u0442\u0430\u0431\u043B\u0438\u0446\u0430 \u0444\u0438\u0447 \u0432 \u044D\u0442\u043E\u043C \`CLAUDE.md\`, \`.env*\`.
1389
+ \u041D\u0430\u0431\u043E\u0440 \u0444\u0438\u0447/\u0438\u043D\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0439 \u043C\u0435\u043D\u044F\u0435\u0442\u0441\u044F \u0447\u0435\u0440\u0435\u0437 \`vitrine add/remove\`, \u0430 \u043D\u0435 \u043F\u0440\u0430\u0432\u043A\u043E\u0439 \u0444\u0430\u0439\u043B\u043E\u0432.
1390
+ - **\u0414\u0438\u0437\u0430\u0439\u043D \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u044F \u0442\u043E\u043A\u0435\u043D\u043E\u0432** \u0432 \`theme/client.css\` (\u0447\u0435\u0440\u0435\u0437 \`vitrine design apply\`):
1391
+ \u043B\u043E\u0433\u0438\u043A\u0443/\u0434\u0430\u043D\u043D\u044B\u0435/\u0440\u043E\u0443\u0442\u0438\u043D\u0433/a11y/\u0441\u0442\u0440\u0443\u043A\u0442\u0443\u0440\u0443 \u043A\u043E\u043C\u043F\u043E\u043D\u0435\u043D\u0442\u043E\u0432 \u043D\u0435 \u043C\u0435\u043D\u044F\u0442\u044C.
1392
+ - **\u041A\u043E\u043D\u0442\u0440\u0430\u043A\u0442\u044B \u0440\u0430\u0441\u0448\u0438\u0440\u044F\u044E\u0442\u0441\u044F \u0430\u0434\u0434\u0438\u0442\u0438\u0432\u043D\u043E** (\`@vitrine-kit/contracts\`): \u043B\u043E\u043C\u0430\u0442\u044C \u0444\u043E\u0440\u043C\u0443 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044E\u0449\u0438\u0445 \u043F\u043E\u043B\u0435\u0439 \u043D\u0435\u043B\u044C\u0437\u044F.
1393
+ - **\u041A\u043E\u043C\u043C\u0438\u0442\u044B \u0434\u0435\u043B\u0430\u0435\u0442 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C** \u2014 \u043D\u0435 \u0437\u0430\u043F\u0443\u0441\u043A\u0430\u0439 \`git commit\`/\`git push\` \u0431\u0435\u0437 \u044F\u0432\u043D\u043E\u0439 \u043F\u0440\u043E\u0441\u044C\u0431\u044B.
1394
+ `
1395
+ );
1396
+ writeText(join11(root, "README.md"), clientReadme(name, backend, tier));
1397
+ writeText(join11(root, "lib", "slots.ts"), renderSlotsFile([]));
1398
+ writeText(join11(root, "lib", "blueprint.ts"), renderBlueprintFile([]));
1399
+ writeText(join11(root, "theme", "client.css"), renderNeutralTheme());
1400
+ writeText(join11(root, ".env.example"), clientEnvExample(backend));
1401
+ writeText(join11(root, "package.json"), `${JSON.stringify(clientPackageJson(name, backend), null, 2)}
1402
+ `);
1403
+ }
1404
+ function initProject(opts) {
1405
+ if (exists(opts.root) && readdirSync3(opts.root).length > 0) {
1406
+ throw new Error(`[vitrine] \u043A\u0430\u0442\u0430\u043B\u043E\u0433 "${opts.root}" \u043D\u0435 \u043F\u0443\u0441\u0442\u043E\u0439`);
1407
+ }
1408
+ scaffoldBase(opts);
1409
+ const project = loadProject(opts.root);
1410
+ return installFeatures(project, opts.features, opts.registry);
1411
+ }
1412
+
1413
+ // src/kit-update.ts
1414
+ import { spawnSync as spawnSync2 } from "child_process";
1415
+ import { existsSync as existsSync8, mkdtempSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
1416
+ import { tmpdir } from "os";
1417
+ import { join as join12, resolve as resolve5 } from "path";
1418
+ var REPO = "vitrine-kit/vitrine";
1419
+ function hasBin(bin) {
1420
+ const probe = process.platform === "win32" ? "where" : "which";
1421
+ return spawnSync2(probe, [bin], { stdio: "ignore" }).status === 0;
1422
+ }
1423
+ function acquireFromGh(version) {
1424
+ if (!hasBin("gh")) {
1425
+ throw new Error("[vitrine] \u0434\u043B\u044F \u0441\u0435\u0442\u0435\u0432\u043E\u0433\u043E \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u044F \u043D\u0443\u0436\u0435\u043D gh (GitHub CLI), \u043B\u0438\u0431\u043E \u0443\u043A\u0430\u0436\u0438\u0442\u0435 --from <dir>");
1426
+ }
1427
+ if (!hasBin("tar")) {
1428
+ throw new Error("[vitrine] \u0434\u043B\u044F \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u043A\u0438 \u043D\u0443\u0436\u0435\u043D tar, \u043B\u0438\u0431\u043E \u0443\u043A\u0430\u0436\u0438\u0442\u0435 --from <dir>");
1429
+ }
1430
+ const tmp = mkdtempSync(join12(tmpdir(), "vitrine-kit-"));
1431
+ const dl = spawnSync2(
1432
+ "gh",
1433
+ ["release", "download", ...version ? [version] : [], "--repo", REPO, "--archive=tar.gz", "--dir", tmp, "--clobber"],
1434
+ { stdio: "inherit" }
1435
+ );
1436
+ if (dl.status !== 0) {
1437
+ throw new Error("[vitrine] gh release download \u043D\u0435 \u0443\u0434\u0430\u043B\u0441\u044F (\u043F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u0434\u043E\u0441\u0442\u0443\u043F/\u0430\u0432\u0442\u043E\u0440\u0438\u0437\u0430\u0446\u0438\u044E: gh auth status, \u043F\u0440\u0438 \u043D\u0435\u043E\u0431\u0445\u043E\u0434\u0438\u043C\u043E\u0441\u0442\u0438 gh auth login)");
1438
+ }
1439
+ const tarball = readdirSync4(tmp).find((f) => f.endsWith(".tar.gz"));
1440
+ if (!tarball) throw new Error("[vitrine] release-tarball \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D \u043F\u043E\u0441\u043B\u0435 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438");
1441
+ if (spawnSync2("tar", ["-xzf", join12(tmp, tarball), "-C", tmp], { stdio: "inherit" }).status !== 0) {
1442
+ throw new Error("[vitrine] \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u043A\u0430 tarball \u043D\u0435 \u0443\u0434\u0430\u043B\u0430\u0441\u044C");
1443
+ }
1444
+ const root = readdirSync4(tmp).map((f) => join12(tmp, f)).find((p2) => statSync2(p2).isDirectory() && existsSync8(join12(p2, "registry", "_index.json")));
1445
+ if (!root) throw new Error("[vitrine] \u0432 \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u0430\u043D\u043D\u043E\u043C tarball \u043D\u0435\u0442 registry/_index.json");
1446
+ return root;
1447
+ }
1448
+ function kitUpdate(opts = {}) {
1449
+ const source = opts.from ? resolve5(opts.from) : acquireFromGh(opts.version);
1450
+ return populateCache(source, { home: opts.home, channel: opts.channel ?? "stable" });
1451
+ }
1452
+ function kitStatus(home = vitrineHome()) {
1453
+ const meta = readKitMeta(home);
1454
+ const idxFile = join12(cachePaths(home).registry, "_index.json");
1455
+ const idx = exists(idxFile) ? readJson(idxFile) : null;
1456
+ return {
1457
+ cached: meta !== null,
1458
+ kitVersion: meta?.kitVersion,
1459
+ channel: meta?.channel,
1460
+ updatedAt: meta?.updatedAt,
1461
+ featureCount: idx ? Object.keys(idx.features ?? {}).length : void 0,
1462
+ cliKitVersion: KIT_VERSION
1463
+ };
1464
+ }
1465
+ function selfUpdate(opts = {}) {
1466
+ const args = ["install", "-g", "@vitrine-kit/vitrine@latest"];
1467
+ if (opts.dryRun) {
1468
+ console.log(`[vitrine] npm ${args.join(" ")}`);
1469
+ return 0;
1470
+ }
1471
+ const res = spawnSync2("npm", args, { stdio: "inherit" });
1472
+ if (res.error) throw res.error;
1473
+ return res.status ?? 0;
1474
+ }
1475
+
1476
+ // src/index.ts
1477
+ var pkg = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
1478
+ var program = new Command();
1479
+ program.name("vitrine").description("Vitrine CLI").version(pkg.version);
1480
+ program.command("add").description("\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0444\u0438\u0447\u0443(\u0438) \u0432 \u0442\u0435\u043A\u0443\u0449\u0438\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439").argument("<features...>", "\u0438\u043C\u0435\u043D\u0430 \u0444\u0438\u0447").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((features, opts) => {
1481
+ const res = addFeatures(features, opts.registry);
1482
+ console.log(res.installed.length ? `\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E: ${res.installed.join(", ")}` : "\u0423\u0436\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u2014 \u043D\u0435\u0442 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439.");
1483
+ });
1484
+ program.command("remove").description("\u0423\u0434\u0430\u043B\u0438\u0442\u044C \u0444\u0438\u0447\u0443 (\u0435\u0441\u043B\u0438 removable)").argument("<feature>", "\u0438\u043C\u044F \u0444\u0438\u0447\u0438").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((feature, opts) => {
1485
+ removeFeatureCmd(feature, opts.registry);
1486
+ console.log(`\u0423\u0434\u0430\u043B\u0435\u043D\u043E: ${feature}`);
1487
+ });
1488
+ program.command("update").description("\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0443\u044E \u0444\u0438\u0447\u0443 (3-way merge); \u0431\u0435\u0437 \u0430\u0440\u0433\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u2014 \u0432\u0441\u0435").argument("[features...]", "\u0438\u043C\u0435\u043D\u0430 \u0444\u0438\u0447").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").option("--dry-run", "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043F\u043B\u0430\u043D \u0431\u0435\u0437 \u0437\u0430\u043F\u0438\u0441\u0438").action((features, opts) => {
1489
+ const outcomes = updateFeaturesCmd(features, opts.registry, { dryRun: opts.dryRun });
1490
+ let conflicts = 0;
1491
+ for (const { plan, applied } of outcomes) {
1492
+ console.log(renderPlan(plan));
1493
+ if (applied) console.log(" \u2192 \u043F\u0440\u0438\u043C\u0435\u043D\u0435\u043D\u043E");
1494
+ conflicts += plan.files.reduce((n, f) => n + f.conflicts, 0);
1495
+ }
1496
+ if (conflicts > 0) {
1497
+ console.log(`
1498
+ \u26A0 \u043A\u043E\u043D\u0444\u043B\u0438\u043A\u0442\u043E\u0432: ${conflicts}. \u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 git-\u043C\u0430\u0440\u043A\u0435\u0440\u044B (<<<<<<< / >>>>>>>) \u0432\u0440\u0443\u0447\u043D\u0443\u044E.`);
1499
+ }
1500
+ });
1501
+ program.command("diff").description("\u041F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0439 update (\u0431\u0435\u0437 \u0437\u0430\u043F\u0438\u0441\u0438)").argument("<feature>", "\u0438\u043C\u044F \u0444\u0438\u0447\u0438").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((feature, opts) => {
1502
+ console.log(renderPlan(diffFeatureCmd(feature, opts.registry)));
1503
+ });
1504
+ program.command("list").description("\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0438 \u0434\u043E\u0441\u0442\u0443\u043F\u043D\u044B\u0435 \u0444\u0438\u0447\u0438").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((opts) => {
1505
+ const { installed, available } = listFeatures(opts.registry);
1506
+ console.log("\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u044B:", installed.join(", ") || "\u2014");
1507
+ console.log("\u0414\u043E\u0441\u0442\u0443\u043F\u043D\u044B: ", available.join(", ") || "\u2014");
1508
+ });
1509
+ var design = program.command("design").description("\u0418\u0418-\u0448\u0430\u0433 \u0434\u0438\u0437\u0430\u0439\u043D\u0430 (\u043E\u0431\u0451\u0440\u0442\u043A\u0430 \u043D\u0430\u0434 Claude Code)");
1510
+ design.command("apply").description("\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C \u0434\u0438\u0437\u0430\u0439\u043D \u0438\u0437 /design \u043A \u0442\u043E\u043A\u0435\u043D\u0430\u043C (\u0447\u0435\u0440\u0435\u0437 Claude Code)").option("--bin <path>", "\u043F\u0443\u0442\u044C \u043A Claude Code (\u0438\u043D\u0430\u0447\u0435 VITRINE_CLAUDE_BIN / PATH)").option("--dry-run", "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0431\u0435\u0437 \u0437\u0430\u043F\u0443\u0441\u043A\u0430").action((opts) => {
1511
+ const code = designApplyCmd({ bin: opts.bin, dryRun: opts.dryRun });
1512
+ if (code !== 0) process.exit(code);
1513
+ });
1514
+ var kit = program.command("kit").description("\u041B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430\u0440\u0438\u0439 (\u043A\u044D\u0448 \u0440\u0435\u0435\u0441\u0442\u0440\u0430 ~/.vitrine)");
1515
+ kit.command("update").description("\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u043B\u043E\u043A\u0430\u043B\u044C\u043D\u044B\u0439 \u043A\u044D\u0448 \u0440\u0435\u0435\u0441\u0442\u0440\u0430/\u0448\u0430\u0431\u043B\u043E\u043D\u043E\u0432 \u0441 GitHub (\u0438\u043B\u0438 --from <dir>)").option("--from <path>", "\u043B\u043E\u043A\u0430\u043B\u044C\u043D\u043E\u0435 \u0434\u0435\u0440\u0435\u0432\u043E kit (\u043A\u043B\u043E\u043D / \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u0430\u043D\u043D\u044B\u0439 tarball) \u0432\u043C\u0435\u0441\u0442\u043E \u0441\u0435\u0442\u0438").option("--version <tag>", "\u043A\u043E\u043D\u043A\u0440\u0435\u0442\u043D\u044B\u0439 \u0440\u0435\u043B\u0438\u0437").option("--channel <channel>", "stable | main", "stable").action((opts) => {
1516
+ const res = kitUpdate({ from: opts.from, version: opts.version, channel: opts.channel });
1517
+ console.log(`\u041A\u044D\u0448 \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D \u0434\u043E kit ${res.kitVersion}.`);
1518
+ console.log(formatChangelog(res.changelog));
1519
+ });
1520
+ kit.command("status").description("\u0412\u0435\u0440\u0441\u0438\u044F \u043A\u044D\u0448\u0430 \u0438 \u0447\u0442\u043E \u043D\u043E\u0432\u0435\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u043E\u0433\u043E CLI").action(() => {
1521
+ const s = kitStatus();
1522
+ if (!s.cached) {
1523
+ console.log('\u041A\u044D\u0448 \u043F\u0443\u0441\u0442. \u0417\u0430\u043F\u0443\u0441\u0442\u0438 "vitrine kit update".');
1524
+ return;
1525
+ }
1526
+ console.log(`\u041A\u044D\u0448: kit ${s.kitVersion} (${s.channel}), \u0444\u0438\u0447: ${s.featureCount ?? "\u2014"}, \u043E\u0431\u043D\u043E\u0432\u043B\u0451\u043D ${s.updatedAt}.`);
1527
+ console.log(`CLI \u043E\u0436\u0438\u0434\u0430\u0435\u0442 kit ${s.cliKitVersion}.`);
1528
+ });
1529
+ program.command("self-update").description("\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C \u0441\u0430\u043C CLI (@vitrine-kit/vitrine)").option("--dry-run", "\u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443 \u0431\u0435\u0437 \u0437\u0430\u043F\u0443\u0441\u043A\u0430").action((opts) => {
1530
+ const code = selfUpdate({ dryRun: opts.dryRun });
1531
+ if (code !== 0) process.exit(code);
1532
+ });
1533
+ program.command("doctor").description("\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u043A\u043E\u043D\u0441\u0438\u0441\u0442\u0435\u043D\u0442\u043D\u043E\u0441\u0442\u044C: vitrine.json \u2194 \u0444\u0430\u0439\u043B\u044B \u2194 \u043F\u0430\u043A\u0435\u0442\u044B \u2194 env").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").action((opts) => {
1534
+ const report = doctorCmd(opts.registry);
1535
+ if (report.issues.length === 0) {
1536
+ console.log("\u2713 \u041F\u0440\u043E\u0431\u043B\u0435\u043C \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E.");
1537
+ return;
1538
+ }
1539
+ for (const i of report.issues) {
1540
+ const tag = i.severity === "error" ? "\u041E\u0428\u0418\u0411\u041A\u0410" : "\u043F\u0440\u0435\u0434\u0443\u043F\u0440.";
1541
+ console.log(`[${tag}] ${i.scope}: ${i.message}${i.fix ? ` \u2192 ${i.fix}` : ""}`);
1542
+ }
1543
+ if (!report.ok) process.exit(1);
1544
+ });
1545
+ program.command("init").description("\u0421\u043E\u0437\u0434\u0430\u0442\u044C \u043D\u043E\u0432\u044B\u0439 \u0440\u0435\u043F\u043E\u0437\u0438\u0442\u043E\u0440\u0438\u0439 \u043A\u043B\u0438\u0435\u043D\u0442\u0430").argument("[name]", "\u0438\u043C\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430").option("--dir <path>", "\u0440\u043E\u0434\u0438\u0442\u0435\u043B\u044C\u0441\u043A\u0438\u0439 \u043A\u0430\u0442\u0430\u043B\u043E\u0433", process.cwd()).option("--tier <tier>", "catalog | simple-store | full-store").option("--backend <backend>", "payload | vendure").option("--features <list>", "\u0441\u043F\u0438\u0441\u043E\u043A \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E").option("--registry <path>", "\u043F\u0443\u0442\u044C \u043A \u0440\u0435\u0435\u0441\u0442\u0440\u0443").option("--yes", "\u0431\u0435\u0437 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0445 \u0432\u043E\u043F\u0440\u043E\u0441\u043E\u0432").action(async (nameArg, opts) => {
1546
+ const registry = createRegistrySource(opts.registry);
1547
+ let name = nameArg;
1548
+ let tier = opts.tier;
1549
+ let features = opts.features ? String(opts.features).split(",") : void 0;
1550
+ if (!opts.yes && (!name || !tier)) {
1551
+ p.intro("vitrine init");
1552
+ if (!name) name = String(await p.text({ message: "\u0418\u043C\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430", placeholder: "my-shop" }));
1553
+ if (!tier) {
1554
+ tier = await p.select({
1555
+ message: "\u0423\u0440\u043E\u0432\u0435\u043D\u044C",
1556
+ options: [
1557
+ { value: "catalog", label: "\u041A\u0430\u0442\u0430\u043B\u043E\u0433" },
1558
+ { value: "simple-store", label: "\u041F\u0440\u043E\u0441\u0442\u043E\u0439 \u043C\u0430\u0433\u0430\u0437\u0438\u043D" },
1559
+ { value: "full-store", label: "\u041F\u043E\u043B\u043D\u044B\u0439 \u043C\u0430\u0433\u0430\u0437\u0438\u043D" }
1560
+ ]
1561
+ });
1562
+ }
1563
+ const suggested = suggestFeatures(tier, registry);
1564
+ const baseline = suggested.filter((f) => !PAYMENT_PROVIDER_FEATURES.includes(f));
1565
+ const picked = await p.multiselect({
1566
+ message: "\u0424\u0438\u0447\u0438",
1567
+ options: baseline.map((f) => ({ value: f, label: f })),
1568
+ initialValues: baseline,
1569
+ required: false
1570
+ });
1571
+ features = Array.isArray(picked) ? picked : baseline;
1572
+ const wizardBackend = opts.backend ?? defaultBackend(tier);
1573
+ if (tier !== "catalog" && wizardBackend === "payload") {
1574
+ const labels = {
1575
+ "checkout-stripe": "Stripe",
1576
+ "checkout-paddle": "Paddle",
1577
+ "checkout-yookassa": "\u042EKassa"
1578
+ };
1579
+ const available = PAYMENT_PROVIDER_FEATURES.filter((f) => registry.hasFeature(f));
1580
+ if (available.length > 0) {
1581
+ const provider = await p.select({
1582
+ message: "\u041F\u043B\u0430\u0442\u0451\u0436\u043D\u044B\u0439 \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440",
1583
+ options: [
1584
+ { value: "none", label: "\u041D\u0435\u0442 (\u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u043E\u0437\u0436\u0435: vitrine add checkout-<provider>)" },
1585
+ ...available.map((f) => ({ value: f, label: labels[f] ?? f }))
1586
+ ],
1587
+ initialValue: available[0]
1588
+ });
1589
+ if (typeof provider === "string" && provider !== "none") features.push(provider);
1590
+ }
1591
+ }
1592
+ p.outro("\u041F\u043E\u0435\u0445\u0430\u043B\u0438");
1593
+ }
1594
+ if (!name) throw new Error("[vitrine] \u043D\u0443\u0436\u043D\u043E \u0438\u043C\u044F \u043F\u0440\u043E\u0435\u043A\u0442\u0430");
1595
+ const finalTier = tier ?? "catalog";
1596
+ const backend = opts.backend ?? defaultBackend(finalTier);
1597
+ const finalFeatures = features ?? suggestFeatures(finalTier, registry, backend);
1598
+ const root = resolve6(String(opts.dir ?? process.cwd()), name);
1599
+ const res = initProject({ root, name, backend, tier: finalTier, features: finalFeatures, registry });
1600
+ console.log(`\u0421\u043E\u0437\u0434\u0430\u043D \u043F\u0440\u043E\u0435\u043A\u0442 "${name}" \u2192 ${root}`);
1601
+ console.log(`\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E: ${res.installed.join(", ") || "\u2014"}`);
1602
+ });
1603
+ async function main() {
1604
+ preflightNode();
1605
+ await program.parseAsync(process.argv);
1606
+ }
1607
+ main().catch((error) => {
1608
+ console.error(error instanceof Error ? error.message : String(error));
1609
+ process.exit(1);
1610
+ });