@zalify/cli 0.7.1 → 0.8.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 (2) hide show
  1. package/dist/cli.js +445 -7
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -11293,7 +11293,7 @@ function updateNotifier(options) {
11293
11293
 
11294
11294
  // src/cli.ts
11295
11295
  import { createRequire as createRequire2 } from "node:module";
11296
- import { dirname, join as join5 } from "node:path";
11296
+ import { dirname as dirname2, join as join6 } from "node:path";
11297
11297
  import { fileURLToPath as fileURLToPath3 } from "node:url";
11298
11298
 
11299
11299
  // src/auth.ts
@@ -11600,8 +11600,13 @@ async function whoami() {
11600
11600
  console.log(live ? "status ✓ key verified" : `status ! not verified — ${liveError}`);
11601
11601
  }
11602
11602
 
11603
- // src/assets.ts
11603
+ // src/hash.ts
11604
11604
  import { createHash } from "node:crypto";
11605
+ function sha256(bytes) {
11606
+ return createHash("sha256").update(bytes).digest("hex");
11607
+ }
11608
+
11609
+ // src/assets.ts
11605
11610
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3, existsSync as existsSync2, readdirSync } from "node:fs";
11606
11611
  import { join as join2, resolve } from "node:path";
11607
11612
  var BATCH = 10;
@@ -11667,7 +11672,7 @@ async function assetsPush(storeDir) {
11667
11672
  }
11668
11673
  const files = readdirSync(imagesDir).filter((f) => f.endsWith(".png")).map((name) => {
11669
11674
  const bytes = readFileSync2(join2(imagesDir, name));
11670
- return { name, bytes, checksum: createHash("sha256").update(bytes).digest("hex") };
11675
+ return { name, bytes, checksum: sha256(bytes) };
11671
11676
  }).filter((f) => index[f.name]?.checksum !== f.checksum);
11672
11677
  if (!files.length) {
11673
11678
  console.log("Everything already pushed.");
@@ -11848,7 +11853,6 @@ async function workspaceSet(slugOrId) {
11848
11853
  }
11849
11854
 
11850
11855
  // src/images.ts
11851
- import { createHash as createHash2 } from "node:crypto";
11852
11856
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync3, rmSync as rmSync2 } from "node:fs";
11853
11857
  import { join as join3, resolve as resolve2 } from "node:path";
11854
11858
  var MAX_JOBS_PER_REQUEST = 40;
@@ -11882,7 +11886,7 @@ async function saveProduced(imagesDir, indexPath, index, file2, asset) {
11882
11886
  index[file2] = {
11883
11887
  assetId: asset.id,
11884
11888
  url: asset.url,
11885
- checksum: createHash2("sha256").update(bytes).digest("hex")
11889
+ checksum: sha256(bytes)
11886
11890
  };
11887
11891
  writeFileSync4(indexPath, JSON.stringify(index, null, 2) + `
11888
11892
  `);
@@ -12313,10 +12317,434 @@ async function shopifyUploadImages(storeDir) {
12313
12317
  console.log("Done.");
12314
12318
  }
12315
12319
 
12320
+ // src/theme.ts
12321
+ import { spawnSync as spawnSync2 } from "node:child_process";
12322
+ import { randomBytes as randomBytes2 } from "node:crypto";
12323
+ import {
12324
+ cpSync,
12325
+ existsSync as existsSync5,
12326
+ mkdirSync as mkdirSync2,
12327
+ mkdtempSync,
12328
+ readdirSync as readdirSync2,
12329
+ readFileSync as readFileSync5,
12330
+ renameSync,
12331
+ rmSync as rmSync3,
12332
+ statSync,
12333
+ unlinkSync,
12334
+ writeFileSync as writeFileSync5
12335
+ } from "node:fs";
12336
+ import { tmpdir } from "node:os";
12337
+ import { basename as basename2, dirname, join as join5, resolve as resolve4 } from "node:path";
12338
+ var TEMPLATES_PKG = "@zalify/theme-templates";
12339
+ var REGISTRY = "https://registry.npmjs.org";
12340
+ var MANIFEST_PATH = ".zalify/theme.json";
12341
+ var PROTECTED_DIRS = ["theme/", ".git/", ".zalify/", "node_modules/", ".next/"];
12342
+ function isProtected(rel) {
12343
+ if (rel === ".env" || rel.startsWith(".env.") && rel !== ".env.example")
12344
+ return true;
12345
+ return PROTECTED_DIRS.some((p) => rel === p.slice(0, -1) || rel.startsWith(p));
12346
+ }
12347
+ async function fetchPackument() {
12348
+ const res = await fetch(`${REGISTRY}/${TEMPLATES_PKG}`, {
12349
+ headers: { Accept: "application/vnd.npm.install-v1+json" }
12350
+ });
12351
+ if (res.status === 404) {
12352
+ throw new Error(`${TEMPLATES_PKG} not found on npm — it may not be published yet.
12353
+ ` + ` For local testing pass --tarball <path-to-.tgz>.`);
12354
+ }
12355
+ if (!res.ok)
12356
+ throw new Error(`npm registry ${res.status} for ${TEMPLATES_PKG}`);
12357
+ return await res.json();
12358
+ }
12359
+ async function downloadTarball(url) {
12360
+ const res = await fetch(url);
12361
+ if (!res.ok)
12362
+ throw new Error(`tarball download ${res.status}: ${url}`);
12363
+ const file2 = join5(mkdtempSync(join5(tmpdir(), "zalify-theme-")), "pkg.tgz");
12364
+ writeFileSync5(file2, Buffer.from(await res.arrayBuffer()));
12365
+ return file2;
12366
+ }
12367
+ function extractTarball(file2) {
12368
+ const dir2 = mkdtempSync(join5(tmpdir(), "zalify-theme-x-"));
12369
+ const result = spawnSync2("tar", ["-xzf", resolve4(file2), "-C", dir2], {
12370
+ encoding: "utf8"
12371
+ });
12372
+ if (result.error || result.status !== 0) {
12373
+ throw new Error(`tar extract failed for ${file2}: ${result.stderr || result.error?.message}`);
12374
+ }
12375
+ const root = join5(dir2, "package");
12376
+ if (!existsSync5(root))
12377
+ throw new Error(`unexpected tarball layout in ${file2} (no package/)`);
12378
+ return root;
12379
+ }
12380
+ async function acquireTemplatePkg(localTarball, version) {
12381
+ if (localTarball) {
12382
+ const root = extractTarball(localTarball);
12383
+ const v = JSON.parse(readFileSync5(join5(root, "package.json"), "utf8")).version;
12384
+ return { root, version: v };
12385
+ }
12386
+ const packument = await fetchPackument();
12387
+ const target = version ?? packument["dist-tags"].latest;
12388
+ const entry = packument.versions[target];
12389
+ if (!entry)
12390
+ throw new Error(`${TEMPLATES_PKG}@${target} does not exist on npm`);
12391
+ return { root: extractTarball(await downloadTarball(entry.dist.tarball)), version: target };
12392
+ }
12393
+ function readTemplateManifest(pkgRoot, template) {
12394
+ const path9 = join5(pkgRoot, "templates", template, MANIFEST_PATH);
12395
+ if (!existsSync5(path9)) {
12396
+ throw new Error(`template "${template}" has no ${MANIFEST_PATH} — this ${TEMPLATES_PKG} version predates the upgrade contract`);
12397
+ }
12398
+ return JSON.parse(readFileSync5(path9, "utf8"));
12399
+ }
12400
+ function resolveVariant(manifest, variant) {
12401
+ const resolved = { ...manifest, files: { ...manifest.files }, variant };
12402
+ if (variant === "editor" && manifest.editorFiles) {
12403
+ Object.assign(resolved.files, manifest.editorFiles);
12404
+ }
12405
+ delete resolved.editorFiles;
12406
+ return resolved;
12407
+ }
12408
+ function templateFilePath(templateDir, rel, variant) {
12409
+ const stashed = rel.replace(/(^|\/)\.gitignore$/, "$1gitignore");
12410
+ if (variant === "editor") {
12411
+ for (const candidate of [rel, stashed]) {
12412
+ const overlay = join5(templateDir, "__editor__", candidate);
12413
+ if (existsSync5(overlay))
12414
+ return overlay;
12415
+ }
12416
+ }
12417
+ for (const candidate of [rel, stashed]) {
12418
+ const path9 = join5(templateDir, candidate);
12419
+ if (existsSync5(path9))
12420
+ return path9;
12421
+ }
12422
+ throw new Error(`file listed in manifest but missing from template: ${rel}`);
12423
+ }
12424
+ function readProjectManifest(dir2) {
12425
+ const path9 = join5(dir2, MANIFEST_PATH);
12426
+ if (!existsSync5(path9)) {
12427
+ throw new Error(`no ${MANIFEST_PATH} in ${dir2} — not a scaffolded Zalify storefront.
12428
+ ` + ` Scaffold one with \`zalify theme create\`.`);
12429
+ }
12430
+ return JSON.parse(readFileSync5(path9, "utf8"));
12431
+ }
12432
+ function writeJson(path9, value) {
12433
+ writeFileSync5(path9, JSON.stringify(value, null, 2) + `
12434
+ `);
12435
+ }
12436
+ async function themeCreate(dir2, opts) {
12437
+ const targetDir = resolve4(dir2);
12438
+ if (existsSync5(targetDir) && readdirSync2(targetDir).length > 0) {
12439
+ throw new Error(`${dir2} already exists and is not empty`);
12440
+ }
12441
+ const { root, version } = await acquireTemplatePkg(opts.tarball, opts.to);
12442
+ const frameworks = JSON.parse(readFileSync5(join5(root, "templates.json"), "utf8"));
12443
+ const template = opts.template ?? "nextjs";
12444
+ const meta = frameworks.frameworks[template];
12445
+ if (!meta) {
12446
+ throw new Error(`unknown template "${template}" — expected one of: ${Object.keys(frameworks.frameworks).join(", ")}`);
12447
+ }
12448
+ const templateDir = join5(root, "templates", template);
12449
+ console.log(`Scaffolding ${meta.label} (${TEMPLATES_PKG}@${version}) into ${dir2}`);
12450
+ mkdirSync2(targetDir, { recursive: true });
12451
+ cpSync(templateDir, targetDir, { recursive: true });
12452
+ const overlayDir = join5(targetDir, "__editor__");
12453
+ const useEditor = Boolean(opts.editor) && existsSync5(overlayDir);
12454
+ if (opts.editor && !useEditor) {
12455
+ console.log(` ! --editor has no effect for ${template} — no editor overlay in this template`);
12456
+ }
12457
+ if (useEditor)
12458
+ cpSync(overlayDir, targetDir, { recursive: true });
12459
+ rmSync3(overlayDir, { recursive: true, force: true });
12460
+ (function restoreGitignores(d) {
12461
+ for (const entry of readdirSync2(d)) {
12462
+ const full = join5(d, entry);
12463
+ if (statSync(full).isDirectory())
12464
+ restoreGitignores(full);
12465
+ else if (entry === "gitignore")
12466
+ renameSync(full, join5(d, ".gitignore"));
12467
+ }
12468
+ })(targetDir);
12469
+ const pkgPath = join5(targetDir, "package.json");
12470
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
12471
+ pkg.name = basename2(targetDir).toLowerCase().replaceAll(/[^a-z0-9-_.]+/g, "-").replaceAll(/^[-.]+|[-.]+$/g, "") || "zalify-storefront";
12472
+ writeJson(pkgPath, pkg);
12473
+ if (meta.env) {
12474
+ const lines = [
12475
+ "# Leave the store variables empty to run against https://mock.shop",
12476
+ "# demo data. Fill them in to connect your Shopify store.",
12477
+ `${meta.env.domain}=${opts.storeDomain ?? ""}`,
12478
+ `${meta.env.token}=${opts.storefrontToken ?? ""}`
12479
+ ];
12480
+ if (meta.env.sessionSecret) {
12481
+ lines.push("# Session cookie signing key — unique to this project.", `${meta.env.sessionSecret}=${randomBytes2(32).toString("hex")}`);
12482
+ }
12483
+ writeFileSync5(join5(targetDir, ".env"), lines.join(`
12484
+ `) + `
12485
+ `);
12486
+ }
12487
+ const manifest = readProjectManifest(targetDir);
12488
+ writeJson(join5(targetDir, MANIFEST_PATH), resolveVariant(manifest, useEditor ? "editor" : "default"));
12489
+ if (opts.git !== false) {
12490
+ const git = (args) => spawnSync2("git", args, { cwd: targetDir, stdio: "ignore" });
12491
+ if (git(["init", "-b", "main"]).status === 0) {
12492
+ git(["add", "-A"]);
12493
+ git(["commit", "-m", "Initial commit from zalify theme create"]);
12494
+ console.log(" ✓ initialized git repository");
12495
+ } else {
12496
+ console.log(" ! git init failed — skipping repository setup");
12497
+ }
12498
+ }
12499
+ if (opts.install !== false) {
12500
+ const pm = spawnSync2("pnpm", ["--version"], { stdio: "ignore" }).status === 0 ? "pnpm" : "npm";
12501
+ console.log(` Installing dependencies with ${pm}…`);
12502
+ const result = spawnSync2(pm, ["install"], { cwd: targetDir, stdio: "inherit" });
12503
+ if (result.status !== 0) {
12504
+ console.log(` ! ${pm} install failed — run it manually inside the project`);
12505
+ }
12506
+ }
12507
+ console.log(`
12508
+ Done. Next steps:`);
12509
+ console.log(` cd ${dir2}`);
12510
+ if (opts.install === false)
12511
+ console.log(" pnpm install");
12512
+ for (const step of meta.nextSteps)
12513
+ console.log(` ${step}`);
12514
+ if (meta.env) {
12515
+ console.log(`
12516
+ Runs on mock.shop demo data out of the box — edit .env to connect your store.`);
12517
+ }
12518
+ console.log("Your theme customizations live in theme/ — see theme/README.md.");
12519
+ }
12520
+ function themeStatus(dir2 = ".") {
12521
+ const projectDir = resolve4(dir2);
12522
+ const manifest = readProjectManifest(projectDir);
12523
+ console.log(`${manifest.template} theme, ${TEMPLATES_PKG}@${manifest.version}` + (manifest.variant === "editor" ? " (editor variant)" : ""));
12524
+ let clean = 0;
12525
+ const edited = [];
12526
+ const missing = [];
12527
+ for (const [rel, expected] of Object.entries(manifest.files)) {
12528
+ const full = join5(projectDir, rel);
12529
+ if (!existsSync5(full))
12530
+ missing.push(rel);
12531
+ else if (sha256(readFileSync5(full)) !== expected)
12532
+ edited.push(rel);
12533
+ else
12534
+ clean++;
12535
+ }
12536
+ for (const rel of edited)
12537
+ console.log(` ! edited ${rel}`);
12538
+ for (const rel of missing)
12539
+ console.log(` ✗ missing ${rel}`);
12540
+ console.log(`${clean} clean, ${edited.length} edited, ${missing.length} missing ` + `of ${Object.keys(manifest.files).length} theme-owned files`);
12541
+ if (edited.length) {
12542
+ console.log("Edited files are 3-way-merged on `zalify theme upgrade` (conflicts marked).");
12543
+ }
12544
+ }
12545
+ function looksBinary(...buffers) {
12546
+ return buffers.some((b) => b.subarray(0, 8000).includes(0));
12547
+ }
12548
+ async function themeUpgrade(dir2 = ".", opts = {}) {
12549
+ const projectDir = resolve4(dir2);
12550
+ const local = readProjectManifest(projectDir);
12551
+ const variant = local.variant ?? "default";
12552
+ const target = await acquireTemplatePkg(opts.tarball, opts.to);
12553
+ if (target.version === local.version && !opts.tarball) {
12554
+ console.log(`Already at ${TEMPLATES_PKG}@${local.version} — nothing to do.`);
12555
+ return;
12556
+ }
12557
+ const base = await acquireTemplatePkg(opts.baseTarball, local.version);
12558
+ if (base.version !== local.version) {
12559
+ console.log(` ! base tarball is ${base.version}, project manifest says ${local.version} — using it anyway`);
12560
+ }
12561
+ const oldManifest = resolveVariant(readTemplateManifest(base.root, local.template), variant);
12562
+ const newManifest = resolveVariant(readTemplateManifest(target.root, local.template), variant);
12563
+ const oldDir = join5(base.root, "templates", local.template);
12564
+ const newDir = join5(target.root, "templates", local.template);
12565
+ console.log(`Upgrading ${local.template} theme ${local.version} → ${target.version}` + (opts.dryRun ? " (dry run)" : ""));
12566
+ const counts = {
12567
+ overwrite: 0,
12568
+ add: 0,
12569
+ merge: 0,
12570
+ conflict: 0,
12571
+ delete: 0,
12572
+ "keep-edited": 0,
12573
+ "keep-warn": 0
12574
+ };
12575
+ const conflicts = [];
12576
+ const allPaths = [...new Set([...Object.keys(oldManifest.files), ...Object.keys(newManifest.files)])].sort();
12577
+ for (const rel of allPaths) {
12578
+ if (isProtected(rel))
12579
+ continue;
12580
+ const oldHash = oldManifest.files[rel];
12581
+ const newHash = newManifest.files[rel];
12582
+ const localPath = join5(projectDir, rel);
12583
+ const localExists = existsSync5(localPath);
12584
+ const localBytes = localExists ? readFileSync5(localPath) : null;
12585
+ const localHash = localBytes ? sha256(localBytes) : null;
12586
+ const write = (bytes) => {
12587
+ if (opts.dryRun)
12588
+ return;
12589
+ mkdirSync2(dirname(localPath), { recursive: true });
12590
+ writeFileSync5(localPath, bytes);
12591
+ };
12592
+ if (oldHash && newHash) {
12593
+ if (oldHash === newHash) {
12594
+ if (!localExists)
12595
+ console.log(` ! missing ${rel} (theme file was deleted locally; unchanged upstream — leaving as is)`);
12596
+ continue;
12597
+ }
12598
+ const newBytes = readFileSync5(templateFilePath(newDir, rel, variant));
12599
+ if (!localExists) {
12600
+ write(newBytes);
12601
+ counts.add++;
12602
+ console.log(` + ${opts.dryRun ? "would restore" : "restored"} ${rel}`);
12603
+ } else if (localHash === oldHash) {
12604
+ write(newBytes);
12605
+ counts.overwrite++;
12606
+ console.log(` ✓ ${opts.dryRun ? "would update" : "updated"} ${rel}`);
12607
+ } else if (localHash === newHash) {} else {
12608
+ const oldBytes = readFileSync5(templateFilePath(oldDir, rel, variant));
12609
+ if (looksBinary(localBytes, oldBytes, newBytes)) {
12610
+ if (!opts.dryRun)
12611
+ writeFileSync5(`${localPath}.new`, newBytes);
12612
+ counts.conflict++;
12613
+ conflicts.push(rel);
12614
+ console.log(` ✗ conflict ${rel} (binary — new version written to ${rel}.new)`);
12615
+ continue;
12616
+ }
12617
+ if (opts.dryRun) {
12618
+ counts.merge++;
12619
+ console.log(` ~ would merge ${rel}`);
12620
+ continue;
12621
+ }
12622
+ const scratch = mkdtempSync(join5(tmpdir(), "zalify-merge-"));
12623
+ const basePath = join5(scratch, "base");
12624
+ const newPath = join5(scratch, "new");
12625
+ writeFileSync5(basePath, oldBytes);
12626
+ writeFileSync5(newPath, newBytes);
12627
+ const result = spawnSync2("git", ["merge-file", "-L", "current", "-L", `base (${base.version})`, "-L", `new (${target.version})`, localPath, basePath, newPath], { encoding: "utf8" });
12628
+ rmSync3(scratch, { recursive: true, force: true });
12629
+ if (result.error || result.status === null || result.status < 0) {
12630
+ writeFileSync5(`${localPath}.new`, newBytes);
12631
+ counts.conflict++;
12632
+ conflicts.push(rel);
12633
+ console.log(` ✗ conflict ${rel} (merge unavailable — new version written to ${rel}.new)`);
12634
+ } else if (result.status > 0) {
12635
+ counts.conflict++;
12636
+ conflicts.push(rel);
12637
+ console.log(` ✗ conflict ${rel} (${result.status} conflict marker(s) left in file)`);
12638
+ } else {
12639
+ counts.merge++;
12640
+ console.log(` ✓ merged ${rel} (kept your edits)`);
12641
+ }
12642
+ }
12643
+ } else if (oldHash && !newHash) {
12644
+ if (!localExists)
12645
+ continue;
12646
+ if (localHash === oldHash) {
12647
+ if (!opts.dryRun)
12648
+ unlinkSync(localPath);
12649
+ counts.delete++;
12650
+ console.log(` - ${opts.dryRun ? "would remove" : "removed"} ${rel} (deleted upstream)`);
12651
+ } else {
12652
+ counts["keep-warn"]++;
12653
+ console.log(` ! kept ${rel} (deleted upstream, but you edited it)`);
12654
+ }
12655
+ } else if (!oldHash && newHash) {
12656
+ if (!localExists) {
12657
+ write(readFileSync5(templateFilePath(newDir, rel, variant)));
12658
+ counts.add++;
12659
+ console.log(` + ${opts.dryRun ? "would add" : "added"} ${rel}`);
12660
+ } else if (localHash !== newHash) {
12661
+ counts["keep-warn"]++;
12662
+ console.log(` ! kept ${rel} (upstream added a file where yours exists)`);
12663
+ }
12664
+ }
12665
+ }
12666
+ const depsChanged = mergePackageJson(projectDir, oldDir, newDir, opts.dryRun ?? false);
12667
+ if (!opts.dryRun) {
12668
+ writeJson(join5(projectDir, MANIFEST_PATH), { ...newManifest, version: target.version });
12669
+ }
12670
+ const summary = Object.entries(counts).filter(([, n]) => n > 0).map(([k, n]) => `${n} ${k.replace("-", " ")}`).join(", ");
12671
+ console.log(`
12672
+ ${opts.dryRun ? "Would apply" : "Done"}: ${summary || "no file changes"}.`);
12673
+ if (conflicts.length) {
12674
+ console.log(`Resolve ${conflicts.length} conflict(s), then re-run \`zalify theme status\`:`);
12675
+ for (const rel of conflicts)
12676
+ console.log(` ${rel}`);
12677
+ }
12678
+ if (depsChanged && !opts.dryRun) {
12679
+ if (opts.install) {
12680
+ const pm = spawnSync2("pnpm", ["--version"], { stdio: "ignore" }).status === 0 ? "pnpm" : "npm";
12681
+ console.log(`Dependencies changed — running ${pm} install…`);
12682
+ spawnSync2(pm, ["install"], { cwd: projectDir, stdio: "inherit" });
12683
+ } else {
12684
+ console.log("Dependencies changed — run `pnpm install`.");
12685
+ }
12686
+ }
12687
+ }
12688
+ function mergePackageJson(projectDir, oldDir, newDir, dryRun) {
12689
+ const read = (p) => JSON.parse(readFileSync5(join5(p, "package.json"), "utf8"));
12690
+ const localPkg = read(projectDir);
12691
+ const oldPkg = read(oldDir);
12692
+ const newPkg = read(newDir);
12693
+ const eq = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
12694
+ let changed = false;
12695
+ let depsChanged = false;
12696
+ const mergeSection = (section) => {
12697
+ const localS = localPkg[section] ?? {};
12698
+ const oldS = oldPkg[section] ?? {};
12699
+ const newS = newPkg[section] ?? {};
12700
+ for (const key of new Set([...Object.keys(oldS), ...Object.keys(newS)])) {
12701
+ if (eq(oldS[key], newS[key]))
12702
+ continue;
12703
+ if (!eq(localS[key], oldS[key]) && key in localS) {
12704
+ if (!eq(localS[key], newS[key])) {
12705
+ console.log(` ! kept package.json ${section}.${key} = ${JSON.stringify(localS[key])} (template wants ${JSON.stringify(newS[key] ?? "removed")})`);
12706
+ }
12707
+ continue;
12708
+ }
12709
+ changed = true;
12710
+ if (section === "dependencies" || section === "devDependencies")
12711
+ depsChanged = true;
12712
+ if (key in newS) {
12713
+ localS[key] = newS[key];
12714
+ console.log(` ✓ package.json ${section}.${key} → ${JSON.stringify(newS[key])}`);
12715
+ } else {
12716
+ delete localS[key];
12717
+ console.log(` - package.json ${section}.${key} removed`);
12718
+ }
12719
+ }
12720
+ if (Object.keys(localS).length)
12721
+ localPkg[section] = localS;
12722
+ };
12723
+ for (const section of ["dependencies", "devDependencies", "scripts"])
12724
+ mergeSection(section);
12725
+ for (const key of ["engines", "packageManager", "type"]) {
12726
+ if (eq(oldPkg[key], newPkg[key]))
12727
+ continue;
12728
+ if (!eq(localPkg[key], oldPkg[key]))
12729
+ continue;
12730
+ changed = true;
12731
+ if (key in newPkg)
12732
+ localPkg[key] = newPkg[key];
12733
+ else
12734
+ delete localPkg[key];
12735
+ console.log(` ✓ package.json ${key} updated`);
12736
+ }
12737
+ if (changed && !dryRun) {
12738
+ writeFileSync5(join5(projectDir, "package.json"), JSON.stringify(localPkg, null, 2) + `
12739
+ `);
12740
+ }
12741
+ return depsChanged;
12742
+ }
12743
+
12316
12744
  // src/cli.ts
12317
- var __dirname4 = dirname(fileURLToPath3(import.meta.url));
12745
+ var __dirname4 = dirname2(fileURLToPath3(import.meta.url));
12318
12746
  var require2 = createRequire2(import.meta.url);
12319
- var pkg = require2(join5(__dirname4, "..", "package.json"));
12747
+ var pkg = require2(join6(__dirname4, "..", "package.json"));
12320
12748
  try {
12321
12749
  updateNotifier({ pkg }).notify({
12322
12750
  isGlobal: true,
@@ -12371,6 +12799,16 @@ shopify.command("import <store-dir>").description("Import <store-dir>/catalog.js
12371
12799
  shopify.command("upload-images <store-dir>").description("Upload <store-dir>/images/* to matching products per images/manifest.json (skips files already attached)").action(async (storeDir) => {
12372
12800
  await shopifyUploadImages(storeDir);
12373
12801
  });
12802
+ var theme = program2.command("theme").description("Scaffold and upgrade standalone Zalify storefronts (Shopify-theme model)");
12803
+ theme.command("create <dir>").description("Scaffold a standalone storefront from the published theme templates").option("-t, --template <name>", "liquid | hydrogen | nextjs", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--store-domain <domain>", "your-store.myshopify.com (default: mock.shop demo data)").option("--storefront-token <token>", "public Storefront API access token").option("--to <version>", "template version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init + initial commit").option("--tarball <path>", "use a local @zalify/theme-templates .tgz instead of npm").action(async (dir2, options) => {
12804
+ await themeCreate(dir2, options);
12805
+ });
12806
+ theme.command("status [dir]").description("List theme-owned files edited since scaffolding (.zalify/theme.json checksums)").action((dir2) => {
12807
+ themeStatus(dir2);
12808
+ });
12809
+ theme.command("upgrade [dir]").description("Upgrade to a newer theme version: untouched files overwritten, edited files 3-way-merged, theme/ + .env never touched").option("--to <version>", "target template version (default: latest)").option("--dry-run", "classify and report without writing anything").option("--install", "run install when dependencies changed").option("--tarball <path>", "target version as a local .tgz (testing)").option("--base-tarball <path>", "base version as a local .tgz (testing)").action(async (dir2, options) => {
12810
+ await themeUpgrade(dir2, options);
12811
+ });
12374
12812
  var assets = program2.command("assets").description("Sync images with the Zalify asset library");
12375
12813
  assets.command("push <store-dir>").description("Upload <store-dir>/images/*.png (sha256 dedup, writes assets.json)").action(async (storeDir) => {
12376
12814
  await assetsPush(storeDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalify/cli",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "Zalify CLI - command-line interface for Zalify",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",