@zalify/cli 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +635 -20
- 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
|
|
11296
|
+
import { dirname as dirname2, join as join7 } 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/
|
|
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:
|
|
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:
|
|
11889
|
+
checksum: sha256(bytes)
|
|
11886
11890
|
};
|
|
11887
11891
|
writeFileSync4(indexPath, JSON.stringify(index, null, 2) + `
|
|
11888
11892
|
`);
|
|
@@ -12057,8 +12061,597 @@ async function imagesGenerate(storeDir) {
|
|
|
12057
12061
|
}
|
|
12058
12062
|
|
|
12059
12063
|
// src/shopify.ts
|
|
12060
|
-
import { readFileSync as
|
|
12061
|
-
import { basename, join as
|
|
12064
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "node:fs";
|
|
12065
|
+
import { basename as basename3, join as join6, resolve as resolve5 } from "node:path";
|
|
12066
|
+
|
|
12067
|
+
// src/storefront.ts
|
|
12068
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12069
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
12070
|
+
import { basename as basename2, join as join5, resolve as resolve4 } from "node:path";
|
|
12071
|
+
|
|
12072
|
+
// src/theme.ts
|
|
12073
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12074
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
12075
|
+
import {
|
|
12076
|
+
cpSync,
|
|
12077
|
+
existsSync as existsSync4,
|
|
12078
|
+
mkdirSync as mkdirSync2,
|
|
12079
|
+
mkdtempSync,
|
|
12080
|
+
readdirSync as readdirSync2,
|
|
12081
|
+
readFileSync as readFileSync4,
|
|
12082
|
+
renameSync,
|
|
12083
|
+
rmSync as rmSync3,
|
|
12084
|
+
statSync,
|
|
12085
|
+
unlinkSync,
|
|
12086
|
+
writeFileSync as writeFileSync5
|
|
12087
|
+
} from "node:fs";
|
|
12088
|
+
import { tmpdir } from "node:os";
|
|
12089
|
+
import { basename, dirname, join as join4, resolve as resolve3 } from "node:path";
|
|
12090
|
+
var TEMPLATES_PKG = "@zalify/theme-templates";
|
|
12091
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
12092
|
+
var MANIFEST_PATH = ".zalify/theme.json";
|
|
12093
|
+
var PROTECTED_DIRS = ["theme/", ".git/", ".zalify/", "node_modules/", ".next/"];
|
|
12094
|
+
function isProtected(rel) {
|
|
12095
|
+
if (rel === ".env" || rel.startsWith(".env.") && rel !== ".env.example")
|
|
12096
|
+
return true;
|
|
12097
|
+
return PROTECTED_DIRS.some((p) => rel === p.slice(0, -1) || rel.startsWith(p));
|
|
12098
|
+
}
|
|
12099
|
+
async function fetchPackument() {
|
|
12100
|
+
const res = await fetch(`${REGISTRY}/${TEMPLATES_PKG}`, {
|
|
12101
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" }
|
|
12102
|
+
});
|
|
12103
|
+
if (res.status === 404) {
|
|
12104
|
+
throw new Error(`${TEMPLATES_PKG} not found on npm — it may not be published yet.
|
|
12105
|
+
` + ` For local testing pass --tarball <path-to-.tgz>.`);
|
|
12106
|
+
}
|
|
12107
|
+
if (!res.ok)
|
|
12108
|
+
throw new Error(`npm registry ${res.status} for ${TEMPLATES_PKG}`);
|
|
12109
|
+
return await res.json();
|
|
12110
|
+
}
|
|
12111
|
+
async function downloadTarball(url) {
|
|
12112
|
+
const res = await fetch(url);
|
|
12113
|
+
if (!res.ok)
|
|
12114
|
+
throw new Error(`tarball download ${res.status}: ${url}`);
|
|
12115
|
+
const file2 = join4(mkdtempSync(join4(tmpdir(), "zalify-theme-")), "pkg.tgz");
|
|
12116
|
+
writeFileSync5(file2, Buffer.from(await res.arrayBuffer()));
|
|
12117
|
+
return file2;
|
|
12118
|
+
}
|
|
12119
|
+
function extractTarball(file2) {
|
|
12120
|
+
const dir2 = mkdtempSync(join4(tmpdir(), "zalify-theme-x-"));
|
|
12121
|
+
const result = spawnSync2("tar", ["-xzf", resolve3(file2), "-C", dir2], {
|
|
12122
|
+
encoding: "utf8"
|
|
12123
|
+
});
|
|
12124
|
+
if (result.error || result.status !== 0) {
|
|
12125
|
+
throw new Error(`tar extract failed for ${file2}: ${result.stderr || result.error?.message}`);
|
|
12126
|
+
}
|
|
12127
|
+
const root = join4(dir2, "package");
|
|
12128
|
+
if (!existsSync4(root))
|
|
12129
|
+
throw new Error(`unexpected tarball layout in ${file2} (no package/)`);
|
|
12130
|
+
return root;
|
|
12131
|
+
}
|
|
12132
|
+
async function acquireTemplatePkg(localTarball, version) {
|
|
12133
|
+
if (localTarball) {
|
|
12134
|
+
const root = extractTarball(localTarball);
|
|
12135
|
+
const v = JSON.parse(readFileSync4(join4(root, "package.json"), "utf8")).version;
|
|
12136
|
+
return { root, version: v };
|
|
12137
|
+
}
|
|
12138
|
+
const packument = await fetchPackument();
|
|
12139
|
+
const target = version ?? packument["dist-tags"].latest;
|
|
12140
|
+
const entry = packument.versions[target];
|
|
12141
|
+
if (!entry)
|
|
12142
|
+
throw new Error(`${TEMPLATES_PKG}@${target} does not exist on npm`);
|
|
12143
|
+
return { root: extractTarball(await downloadTarball(entry.dist.tarball)), version: target };
|
|
12144
|
+
}
|
|
12145
|
+
function readTemplateManifest(pkgRoot, template) {
|
|
12146
|
+
const path9 = join4(pkgRoot, "templates", template, MANIFEST_PATH);
|
|
12147
|
+
if (!existsSync4(path9)) {
|
|
12148
|
+
throw new Error(`template "${template}" has no ${MANIFEST_PATH} — this ${TEMPLATES_PKG} version predates the upgrade contract`);
|
|
12149
|
+
}
|
|
12150
|
+
return JSON.parse(readFileSync4(path9, "utf8"));
|
|
12151
|
+
}
|
|
12152
|
+
function resolveVariant(manifest, variant) {
|
|
12153
|
+
const resolved = { ...manifest, files: { ...manifest.files }, variant };
|
|
12154
|
+
if (variant === "editor" && manifest.editorFiles) {
|
|
12155
|
+
Object.assign(resolved.files, manifest.editorFiles);
|
|
12156
|
+
}
|
|
12157
|
+
delete resolved.editorFiles;
|
|
12158
|
+
return resolved;
|
|
12159
|
+
}
|
|
12160
|
+
function templateFilePath(templateDir, rel, variant) {
|
|
12161
|
+
const stashed = rel.replace(/(^|\/)\.gitignore$/, "$1gitignore");
|
|
12162
|
+
if (variant === "editor") {
|
|
12163
|
+
for (const candidate of [rel, stashed]) {
|
|
12164
|
+
const overlay = join4(templateDir, "__editor__", candidate);
|
|
12165
|
+
if (existsSync4(overlay))
|
|
12166
|
+
return overlay;
|
|
12167
|
+
}
|
|
12168
|
+
}
|
|
12169
|
+
for (const candidate of [rel, stashed]) {
|
|
12170
|
+
const path9 = join4(templateDir, candidate);
|
|
12171
|
+
if (existsSync4(path9))
|
|
12172
|
+
return path9;
|
|
12173
|
+
}
|
|
12174
|
+
throw new Error(`file listed in manifest but missing from template: ${rel}`);
|
|
12175
|
+
}
|
|
12176
|
+
function readProjectManifest(dir2) {
|
|
12177
|
+
const path9 = join4(dir2, MANIFEST_PATH);
|
|
12178
|
+
if (!existsSync4(path9)) {
|
|
12179
|
+
throw new Error(`no ${MANIFEST_PATH} in ${dir2} — not a scaffolded Zalify storefront.
|
|
12180
|
+
` + ` Scaffold one with \`zalify theme create\`.`);
|
|
12181
|
+
}
|
|
12182
|
+
return JSON.parse(readFileSync4(path9, "utf8"));
|
|
12183
|
+
}
|
|
12184
|
+
function writeJson(path9, value) {
|
|
12185
|
+
writeFileSync5(path9, JSON.stringify(value, null, 2) + `
|
|
12186
|
+
`);
|
|
12187
|
+
}
|
|
12188
|
+
async function themeCreate(dir2, opts) {
|
|
12189
|
+
const targetDir = resolve3(dir2);
|
|
12190
|
+
if (existsSync4(targetDir) && readdirSync2(targetDir).length > 0) {
|
|
12191
|
+
throw new Error(`${dir2} already exists and is not empty`);
|
|
12192
|
+
}
|
|
12193
|
+
const { root, version } = await acquireTemplatePkg(opts.tarball, opts.to);
|
|
12194
|
+
const frameworks = JSON.parse(readFileSync4(join4(root, "templates.json"), "utf8"));
|
|
12195
|
+
const template = opts.template ?? "nextjs";
|
|
12196
|
+
const meta = frameworks.frameworks[template];
|
|
12197
|
+
if (!meta) {
|
|
12198
|
+
throw new Error(`unknown template "${template}" — expected one of: ${Object.keys(frameworks.frameworks).join(", ")}`);
|
|
12199
|
+
}
|
|
12200
|
+
const templateDir = join4(root, "templates", template);
|
|
12201
|
+
console.log(`Scaffolding ${meta.label} (${TEMPLATES_PKG}@${version}) into ${dir2}`);
|
|
12202
|
+
mkdirSync2(targetDir, { recursive: true });
|
|
12203
|
+
cpSync(templateDir, targetDir, { recursive: true });
|
|
12204
|
+
const overlayDir = join4(targetDir, "__editor__");
|
|
12205
|
+
const useEditor = Boolean(opts.editor) && existsSync4(overlayDir);
|
|
12206
|
+
if (opts.editor && !useEditor) {
|
|
12207
|
+
console.log(` ! --editor has no effect for ${template} — no editor overlay in this template`);
|
|
12208
|
+
}
|
|
12209
|
+
if (useEditor)
|
|
12210
|
+
cpSync(overlayDir, targetDir, { recursive: true });
|
|
12211
|
+
rmSync3(overlayDir, { recursive: true, force: true });
|
|
12212
|
+
(function restoreGitignores(d) {
|
|
12213
|
+
for (const entry of readdirSync2(d)) {
|
|
12214
|
+
const full = join4(d, entry);
|
|
12215
|
+
if (statSync(full).isDirectory())
|
|
12216
|
+
restoreGitignores(full);
|
|
12217
|
+
else if (entry === "gitignore")
|
|
12218
|
+
renameSync(full, join4(d, ".gitignore"));
|
|
12219
|
+
}
|
|
12220
|
+
})(targetDir);
|
|
12221
|
+
const pkgPath = join4(targetDir, "package.json");
|
|
12222
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
12223
|
+
pkg.name = basename(targetDir).toLowerCase().replaceAll(/[^a-z0-9-_.]+/g, "-").replaceAll(/^[-.]+|[-.]+$/g, "") || "zalify-storefront";
|
|
12224
|
+
writeJson(pkgPath, pkg);
|
|
12225
|
+
if (meta.env) {
|
|
12226
|
+
const lines = [
|
|
12227
|
+
"# Leave the store variables empty to run against https://mock.shop",
|
|
12228
|
+
"# demo data. Fill them in to connect your Shopify store.",
|
|
12229
|
+
`${meta.env.domain}=${opts.storeDomain ?? ""}`,
|
|
12230
|
+
`${meta.env.token}=${opts.storefrontToken ?? ""}`
|
|
12231
|
+
];
|
|
12232
|
+
if (meta.env.sessionSecret) {
|
|
12233
|
+
lines.push("# Session cookie signing key — unique to this project.", `${meta.env.sessionSecret}=${randomBytes2(32).toString("hex")}`);
|
|
12234
|
+
}
|
|
12235
|
+
writeFileSync5(join4(targetDir, ".env"), lines.join(`
|
|
12236
|
+
`) + `
|
|
12237
|
+
`);
|
|
12238
|
+
}
|
|
12239
|
+
const manifest = readProjectManifest(targetDir);
|
|
12240
|
+
writeJson(join4(targetDir, MANIFEST_PATH), resolveVariant(manifest, useEditor ? "editor" : "default"));
|
|
12241
|
+
if (opts.git !== false) {
|
|
12242
|
+
const git = (args) => spawnSync2("git", args, { cwd: targetDir, stdio: "ignore" });
|
|
12243
|
+
if (git(["init", "-b", "main"]).status === 0) {
|
|
12244
|
+
git(["add", "-A"]);
|
|
12245
|
+
git(["commit", "-m", "Initial commit from zalify theme create"]);
|
|
12246
|
+
console.log(" ✓ initialized git repository");
|
|
12247
|
+
} else {
|
|
12248
|
+
console.log(" ! git init failed — skipping repository setup");
|
|
12249
|
+
}
|
|
12250
|
+
}
|
|
12251
|
+
if (opts.install !== false) {
|
|
12252
|
+
const pm = spawnSync2("pnpm", ["--version"], { stdio: "ignore" }).status === 0 ? "pnpm" : "npm";
|
|
12253
|
+
console.log(` Installing dependencies with ${pm}…`);
|
|
12254
|
+
const result = spawnSync2(pm, ["install"], { cwd: targetDir, stdio: "inherit" });
|
|
12255
|
+
if (result.status !== 0) {
|
|
12256
|
+
console.log(` ! ${pm} install failed — run it manually inside the project`);
|
|
12257
|
+
}
|
|
12258
|
+
}
|
|
12259
|
+
console.log(`
|
|
12260
|
+
Done. Next steps:`);
|
|
12261
|
+
console.log(` cd ${dir2}`);
|
|
12262
|
+
if (opts.install === false)
|
|
12263
|
+
console.log(" pnpm install");
|
|
12264
|
+
for (const step of meta.nextSteps)
|
|
12265
|
+
console.log(` ${step}`);
|
|
12266
|
+
if (meta.env) {
|
|
12267
|
+
console.log(`
|
|
12268
|
+
Runs on mock.shop demo data out of the box — edit .env to connect your store.`);
|
|
12269
|
+
}
|
|
12270
|
+
console.log("Your theme customizations live in theme/ — see theme/README.md.");
|
|
12271
|
+
}
|
|
12272
|
+
function themeStatus(dir2 = ".") {
|
|
12273
|
+
const projectDir = resolve3(dir2);
|
|
12274
|
+
const manifest = readProjectManifest(projectDir);
|
|
12275
|
+
console.log(`${manifest.template} theme, ${TEMPLATES_PKG}@${manifest.version}` + (manifest.variant === "editor" ? " (editor variant)" : ""));
|
|
12276
|
+
let clean = 0;
|
|
12277
|
+
const edited = [];
|
|
12278
|
+
const missing = [];
|
|
12279
|
+
for (const [rel, expected] of Object.entries(manifest.files)) {
|
|
12280
|
+
const full = join4(projectDir, rel);
|
|
12281
|
+
if (!existsSync4(full))
|
|
12282
|
+
missing.push(rel);
|
|
12283
|
+
else if (sha256(readFileSync4(full)) !== expected)
|
|
12284
|
+
edited.push(rel);
|
|
12285
|
+
else
|
|
12286
|
+
clean++;
|
|
12287
|
+
}
|
|
12288
|
+
for (const rel of edited)
|
|
12289
|
+
console.log(` ! edited ${rel}`);
|
|
12290
|
+
for (const rel of missing)
|
|
12291
|
+
console.log(` ✗ missing ${rel}`);
|
|
12292
|
+
console.log(`${clean} clean, ${edited.length} edited, ${missing.length} missing ` + `of ${Object.keys(manifest.files).length} theme-owned files`);
|
|
12293
|
+
if (edited.length) {
|
|
12294
|
+
console.log("Edited files are 3-way-merged on `zalify theme upgrade` (conflicts marked).");
|
|
12295
|
+
}
|
|
12296
|
+
}
|
|
12297
|
+
function looksBinary(...buffers) {
|
|
12298
|
+
return buffers.some((b) => b.subarray(0, 8000).includes(0));
|
|
12299
|
+
}
|
|
12300
|
+
async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
12301
|
+
const projectDir = resolve3(dir2);
|
|
12302
|
+
const local = readProjectManifest(projectDir);
|
|
12303
|
+
const variant = local.variant ?? "default";
|
|
12304
|
+
const target = await acquireTemplatePkg(opts.tarball, opts.to);
|
|
12305
|
+
if (target.version === local.version && !opts.tarball) {
|
|
12306
|
+
console.log(`Already at ${TEMPLATES_PKG}@${local.version} — nothing to do.`);
|
|
12307
|
+
return;
|
|
12308
|
+
}
|
|
12309
|
+
const base = await acquireTemplatePkg(opts.baseTarball, local.version);
|
|
12310
|
+
if (base.version !== local.version) {
|
|
12311
|
+
console.log(` ! base tarball is ${base.version}, project manifest says ${local.version} — using it anyway`);
|
|
12312
|
+
}
|
|
12313
|
+
const oldManifest = resolveVariant(readTemplateManifest(base.root, local.template), variant);
|
|
12314
|
+
const newManifest = resolveVariant(readTemplateManifest(target.root, local.template), variant);
|
|
12315
|
+
const oldDir = join4(base.root, "templates", local.template);
|
|
12316
|
+
const newDir = join4(target.root, "templates", local.template);
|
|
12317
|
+
console.log(`Upgrading ${local.template} theme ${local.version} → ${target.version}` + (opts.dryRun ? " (dry run)" : ""));
|
|
12318
|
+
const counts = {
|
|
12319
|
+
overwrite: 0,
|
|
12320
|
+
add: 0,
|
|
12321
|
+
merge: 0,
|
|
12322
|
+
conflict: 0,
|
|
12323
|
+
delete: 0,
|
|
12324
|
+
"keep-edited": 0,
|
|
12325
|
+
"keep-warn": 0
|
|
12326
|
+
};
|
|
12327
|
+
const conflicts = [];
|
|
12328
|
+
const allPaths = [...new Set([...Object.keys(oldManifest.files), ...Object.keys(newManifest.files)])].sort();
|
|
12329
|
+
for (const rel of allPaths) {
|
|
12330
|
+
if (isProtected(rel))
|
|
12331
|
+
continue;
|
|
12332
|
+
const oldHash = oldManifest.files[rel];
|
|
12333
|
+
const newHash = newManifest.files[rel];
|
|
12334
|
+
const localPath = join4(projectDir, rel);
|
|
12335
|
+
const localExists = existsSync4(localPath);
|
|
12336
|
+
const localBytes = localExists ? readFileSync4(localPath) : null;
|
|
12337
|
+
const localHash = localBytes ? sha256(localBytes) : null;
|
|
12338
|
+
const write = (bytes) => {
|
|
12339
|
+
if (opts.dryRun)
|
|
12340
|
+
return;
|
|
12341
|
+
mkdirSync2(dirname(localPath), { recursive: true });
|
|
12342
|
+
writeFileSync5(localPath, bytes);
|
|
12343
|
+
};
|
|
12344
|
+
if (oldHash && newHash) {
|
|
12345
|
+
if (oldHash === newHash) {
|
|
12346
|
+
if (!localExists)
|
|
12347
|
+
console.log(` ! missing ${rel} (theme file was deleted locally; unchanged upstream — leaving as is)`);
|
|
12348
|
+
continue;
|
|
12349
|
+
}
|
|
12350
|
+
const newBytes = readFileSync4(templateFilePath(newDir, rel, variant));
|
|
12351
|
+
if (!localExists) {
|
|
12352
|
+
write(newBytes);
|
|
12353
|
+
counts.add++;
|
|
12354
|
+
console.log(` + ${opts.dryRun ? "would restore" : "restored"} ${rel}`);
|
|
12355
|
+
} else if (localHash === oldHash) {
|
|
12356
|
+
write(newBytes);
|
|
12357
|
+
counts.overwrite++;
|
|
12358
|
+
console.log(` ✓ ${opts.dryRun ? "would update" : "updated"} ${rel}`);
|
|
12359
|
+
} else if (localHash === newHash) {} else {
|
|
12360
|
+
const oldBytes = readFileSync4(templateFilePath(oldDir, rel, variant));
|
|
12361
|
+
if (looksBinary(localBytes, oldBytes, newBytes)) {
|
|
12362
|
+
if (!opts.dryRun)
|
|
12363
|
+
writeFileSync5(`${localPath}.new`, newBytes);
|
|
12364
|
+
counts.conflict++;
|
|
12365
|
+
conflicts.push(rel);
|
|
12366
|
+
console.log(` ✗ conflict ${rel} (binary — new version written to ${rel}.new)`);
|
|
12367
|
+
continue;
|
|
12368
|
+
}
|
|
12369
|
+
if (opts.dryRun) {
|
|
12370
|
+
counts.merge++;
|
|
12371
|
+
console.log(` ~ would merge ${rel}`);
|
|
12372
|
+
continue;
|
|
12373
|
+
}
|
|
12374
|
+
const scratch = mkdtempSync(join4(tmpdir(), "zalify-merge-"));
|
|
12375
|
+
const basePath = join4(scratch, "base");
|
|
12376
|
+
const newPath = join4(scratch, "new");
|
|
12377
|
+
writeFileSync5(basePath, oldBytes);
|
|
12378
|
+
writeFileSync5(newPath, newBytes);
|
|
12379
|
+
const result = spawnSync2("git", ["merge-file", "-L", "current", "-L", `base (${base.version})`, "-L", `new (${target.version})`, localPath, basePath, newPath], { encoding: "utf8" });
|
|
12380
|
+
rmSync3(scratch, { recursive: true, force: true });
|
|
12381
|
+
if (result.error || result.status === null || result.status < 0) {
|
|
12382
|
+
writeFileSync5(`${localPath}.new`, newBytes);
|
|
12383
|
+
counts.conflict++;
|
|
12384
|
+
conflicts.push(rel);
|
|
12385
|
+
console.log(` ✗ conflict ${rel} (merge unavailable — new version written to ${rel}.new)`);
|
|
12386
|
+
} else if (result.status > 0) {
|
|
12387
|
+
counts.conflict++;
|
|
12388
|
+
conflicts.push(rel);
|
|
12389
|
+
console.log(` ✗ conflict ${rel} (${result.status} conflict marker(s) left in file)`);
|
|
12390
|
+
} else {
|
|
12391
|
+
counts.merge++;
|
|
12392
|
+
console.log(` ✓ merged ${rel} (kept your edits)`);
|
|
12393
|
+
}
|
|
12394
|
+
}
|
|
12395
|
+
} else if (oldHash && !newHash) {
|
|
12396
|
+
if (!localExists)
|
|
12397
|
+
continue;
|
|
12398
|
+
if (localHash === oldHash) {
|
|
12399
|
+
if (!opts.dryRun)
|
|
12400
|
+
unlinkSync(localPath);
|
|
12401
|
+
counts.delete++;
|
|
12402
|
+
console.log(` - ${opts.dryRun ? "would remove" : "removed"} ${rel} (deleted upstream)`);
|
|
12403
|
+
} else {
|
|
12404
|
+
counts["keep-warn"]++;
|
|
12405
|
+
console.log(` ! kept ${rel} (deleted upstream, but you edited it)`);
|
|
12406
|
+
}
|
|
12407
|
+
} else if (!oldHash && newHash) {
|
|
12408
|
+
if (!localExists) {
|
|
12409
|
+
write(readFileSync4(templateFilePath(newDir, rel, variant)));
|
|
12410
|
+
counts.add++;
|
|
12411
|
+
console.log(` + ${opts.dryRun ? "would add" : "added"} ${rel}`);
|
|
12412
|
+
} else if (localHash !== newHash) {
|
|
12413
|
+
counts["keep-warn"]++;
|
|
12414
|
+
console.log(` ! kept ${rel} (upstream added a file where yours exists)`);
|
|
12415
|
+
}
|
|
12416
|
+
}
|
|
12417
|
+
}
|
|
12418
|
+
const depsChanged = mergePackageJson(projectDir, oldDir, newDir, opts.dryRun ?? false);
|
|
12419
|
+
if (!opts.dryRun) {
|
|
12420
|
+
writeJson(join4(projectDir, MANIFEST_PATH), { ...newManifest, version: target.version });
|
|
12421
|
+
}
|
|
12422
|
+
const summary = Object.entries(counts).filter(([, n]) => n > 0).map(([k, n]) => `${n} ${k.replace("-", " ")}`).join(", ");
|
|
12423
|
+
console.log(`
|
|
12424
|
+
${opts.dryRun ? "Would apply" : "Done"}: ${summary || "no file changes"}.`);
|
|
12425
|
+
if (conflicts.length) {
|
|
12426
|
+
console.log(`Resolve ${conflicts.length} conflict(s), then re-run \`zalify theme status\`:`);
|
|
12427
|
+
for (const rel of conflicts)
|
|
12428
|
+
console.log(` ${rel}`);
|
|
12429
|
+
}
|
|
12430
|
+
if (depsChanged && !opts.dryRun) {
|
|
12431
|
+
if (opts.install) {
|
|
12432
|
+
const pm = spawnSync2("pnpm", ["--version"], { stdio: "ignore" }).status === 0 ? "pnpm" : "npm";
|
|
12433
|
+
console.log(`Dependencies changed — running ${pm} install…`);
|
|
12434
|
+
spawnSync2(pm, ["install"], { cwd: projectDir, stdio: "inherit" });
|
|
12435
|
+
} else {
|
|
12436
|
+
console.log("Dependencies changed — run `pnpm install`.");
|
|
12437
|
+
}
|
|
12438
|
+
}
|
|
12439
|
+
}
|
|
12440
|
+
function mergePackageJson(projectDir, oldDir, newDir, dryRun) {
|
|
12441
|
+
const read = (p) => JSON.parse(readFileSync4(join4(p, "package.json"), "utf8"));
|
|
12442
|
+
const localPkg = read(projectDir);
|
|
12443
|
+
const oldPkg = read(oldDir);
|
|
12444
|
+
const newPkg = read(newDir);
|
|
12445
|
+
const eq = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
12446
|
+
let changed = false;
|
|
12447
|
+
let depsChanged = false;
|
|
12448
|
+
const mergeSection = (section) => {
|
|
12449
|
+
const localS = localPkg[section] ?? {};
|
|
12450
|
+
const oldS = oldPkg[section] ?? {};
|
|
12451
|
+
const newS = newPkg[section] ?? {};
|
|
12452
|
+
for (const key of new Set([...Object.keys(oldS), ...Object.keys(newS)])) {
|
|
12453
|
+
if (eq(oldS[key], newS[key]))
|
|
12454
|
+
continue;
|
|
12455
|
+
if (!eq(localS[key], oldS[key]) && key in localS) {
|
|
12456
|
+
if (!eq(localS[key], newS[key])) {
|
|
12457
|
+
console.log(` ! kept package.json ${section}.${key} = ${JSON.stringify(localS[key])} (template wants ${JSON.stringify(newS[key] ?? "removed")})`);
|
|
12458
|
+
}
|
|
12459
|
+
continue;
|
|
12460
|
+
}
|
|
12461
|
+
changed = true;
|
|
12462
|
+
if (section === "dependencies" || section === "devDependencies")
|
|
12463
|
+
depsChanged = true;
|
|
12464
|
+
if (key in newS) {
|
|
12465
|
+
localS[key] = newS[key];
|
|
12466
|
+
console.log(` ✓ package.json ${section}.${key} → ${JSON.stringify(newS[key])}`);
|
|
12467
|
+
} else {
|
|
12468
|
+
delete localS[key];
|
|
12469
|
+
console.log(` - package.json ${section}.${key} removed`);
|
|
12470
|
+
}
|
|
12471
|
+
}
|
|
12472
|
+
if (Object.keys(localS).length)
|
|
12473
|
+
localPkg[section] = localS;
|
|
12474
|
+
};
|
|
12475
|
+
for (const section of ["dependencies", "devDependencies", "scripts"])
|
|
12476
|
+
mergeSection(section);
|
|
12477
|
+
for (const key of ["engines", "packageManager", "type"]) {
|
|
12478
|
+
if (eq(oldPkg[key], newPkg[key]))
|
|
12479
|
+
continue;
|
|
12480
|
+
if (!eq(localPkg[key], oldPkg[key]))
|
|
12481
|
+
continue;
|
|
12482
|
+
changed = true;
|
|
12483
|
+
if (key in newPkg)
|
|
12484
|
+
localPkg[key] = newPkg[key];
|
|
12485
|
+
else
|
|
12486
|
+
delete localPkg[key];
|
|
12487
|
+
console.log(` ✓ package.json ${key} updated`);
|
|
12488
|
+
}
|
|
12489
|
+
if (changed && !dryRun) {
|
|
12490
|
+
writeFileSync5(join4(projectDir, "package.json"), JSON.stringify(localPkg, null, 2) + `
|
|
12491
|
+
`);
|
|
12492
|
+
}
|
|
12493
|
+
return depsChanged;
|
|
12494
|
+
}
|
|
12495
|
+
|
|
12496
|
+
// src/storefront.ts
|
|
12497
|
+
function apiError2(status, json, appUrl) {
|
|
12498
|
+
if (status === 402) {
|
|
12499
|
+
return new Error(`${json.error ?? "Upgrade required"}
|
|
12500
|
+
` + ` Storefront hosting is a paid feature — upgrade the workspace in ${appUrl}`);
|
|
12501
|
+
}
|
|
12502
|
+
if (json.code === "NO_CONNECTION") {
|
|
12503
|
+
return new Error(`${json.error}
|
|
12504
|
+
Connect the workspace to its Shopify store first, then re-run.`);
|
|
12505
|
+
}
|
|
12506
|
+
if (json.code === "SLUG_TAKEN") {
|
|
12507
|
+
return new Error(`${json.error}
|
|
12508
|
+
Pick a different directory/slug name.`);
|
|
12509
|
+
}
|
|
12510
|
+
if (status === 404) {
|
|
12511
|
+
return new Error("The storefronts API is not deployed yet (404). Update app.zalify.com first.");
|
|
12512
|
+
}
|
|
12513
|
+
if (status === 503) {
|
|
12514
|
+
return new Error(json.error ?? "Storefront provisioning is not configured on the server.");
|
|
12515
|
+
}
|
|
12516
|
+
return new Error(`storefronts API ${status}: ${JSON.stringify(json)}`);
|
|
12517
|
+
}
|
|
12518
|
+
async function post(auth, path9, body) {
|
|
12519
|
+
const res = await fetch(`${auth.appUrl}${path9}`, {
|
|
12520
|
+
method: "POST",
|
|
12521
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${auth.key}` },
|
|
12522
|
+
body: JSON.stringify({ workspaceId: auth.workspaceId, ...body })
|
|
12523
|
+
});
|
|
12524
|
+
const json = await res.json().catch(() => ({}));
|
|
12525
|
+
if (!res.ok)
|
|
12526
|
+
throw apiError2(res.status, json, auth.appUrl);
|
|
12527
|
+
return json;
|
|
12528
|
+
}
|
|
12529
|
+
async function get(auth, path9) {
|
|
12530
|
+
const sep = path9.includes("?") ? "&" : "?";
|
|
12531
|
+
const res = await fetch(`${auth.appUrl}${path9}${sep}workspaceId=${auth.workspaceId}`, {
|
|
12532
|
+
headers: { Authorization: `Bearer ${auth.key}` }
|
|
12533
|
+
});
|
|
12534
|
+
const json = await res.json().catch(() => ({}));
|
|
12535
|
+
if (!res.ok)
|
|
12536
|
+
throw apiError2(res.status, json, auth.appUrl);
|
|
12537
|
+
return json;
|
|
12538
|
+
}
|
|
12539
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
12540
|
+
async function pollUntilReady(auth, id) {
|
|
12541
|
+
let lastMessage = "";
|
|
12542
|
+
const deadline = Date.now() + 10 * 60000;
|
|
12543
|
+
while (Date.now() < deadline) {
|
|
12544
|
+
const view = await get(auth, `/api/storefronts/${id}`);
|
|
12545
|
+
const message = view.statusMessage ?? view.status;
|
|
12546
|
+
if (message !== lastMessage) {
|
|
12547
|
+
console.log(` … ${message}`);
|
|
12548
|
+
lastMessage = message;
|
|
12549
|
+
}
|
|
12550
|
+
if (view.status === "READY")
|
|
12551
|
+
return view;
|
|
12552
|
+
if (view.status === "FAILED") {
|
|
12553
|
+
throw new Error(`Provisioning failed: ${view.statusMessage ?? "unknown reason"}`);
|
|
12554
|
+
}
|
|
12555
|
+
await sleep2(3000);
|
|
12556
|
+
}
|
|
12557
|
+
throw new Error("Provisioning timed out after 10 minutes — check the Temporal UI.");
|
|
12558
|
+
}
|
|
12559
|
+
async function storefrontCreate(dir2, opts) {
|
|
12560
|
+
const auth = requireActive();
|
|
12561
|
+
const slug = basename2(resolve4(dir2)).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "") || "storefront";
|
|
12562
|
+
console.log(`Provisioning storefront "${slug}" (workspace "${auth.workspaceName}")`);
|
|
12563
|
+
const { storefrontId } = await post(auth, "/api/storefronts", {
|
|
12564
|
+
slug,
|
|
12565
|
+
theme: opts.theme ?? "nextjs",
|
|
12566
|
+
...opts.to ? { themeVersion: opts.to } : {}
|
|
12567
|
+
});
|
|
12568
|
+
const view = await pollUntilReady(auth, storefrontId);
|
|
12569
|
+
console.log(` ✓ provisioned: ${view.githubRepo} → https://${view.domain}`);
|
|
12570
|
+
await themeCreate(dir2, {
|
|
12571
|
+
template: view.theme,
|
|
12572
|
+
editor: opts.editor,
|
|
12573
|
+
storeDomain: view.storeDomain ?? "",
|
|
12574
|
+
storefrontToken: view.storefrontToken ?? "",
|
|
12575
|
+
to: opts.to,
|
|
12576
|
+
install: opts.install,
|
|
12577
|
+
git: opts.git
|
|
12578
|
+
});
|
|
12579
|
+
if (opts.git !== false && view.githubRepo) {
|
|
12580
|
+
const { token, repo } = await post(auth, `/api/storefronts/${storefrontId}/push-token`, {});
|
|
12581
|
+
const run = (args) => spawnSync3("git", args, { cwd: resolve4(dir2), stdio: "ignore" });
|
|
12582
|
+
run(["remote", "add", "origin", `https://github.com/${repo}.git`]);
|
|
12583
|
+
const push = spawnSync3("git", ["push", "-u", `https://x-access-token:${token}@github.com/${repo}.git`, "main"], { cwd: resolve4(dir2), stdio: "inherit" });
|
|
12584
|
+
if (push.status !== 0) {
|
|
12585
|
+
throw new Error(`git push to ${repo} failed — resolve and push manually.`);
|
|
12586
|
+
}
|
|
12587
|
+
run(["branch", "--set-upstream-to=origin/main", "main"]);
|
|
12588
|
+
console.log(` ✓ pushed to github.com/${repo} — Vercel deploys on push`);
|
|
12589
|
+
}
|
|
12590
|
+
if (view.domain) {
|
|
12591
|
+
console.log(` Waiting for https://${view.domain} …`);
|
|
12592
|
+
const deadline = Date.now() + 5 * 60000;
|
|
12593
|
+
while (Date.now() < deadline) {
|
|
12594
|
+
try {
|
|
12595
|
+
const res = await fetch(`https://${view.domain}/`, { redirect: "manual" });
|
|
12596
|
+
if (res.ok) {
|
|
12597
|
+
console.log(`
|
|
12598
|
+
Live: https://${view.domain}`);
|
|
12599
|
+
break;
|
|
12600
|
+
}
|
|
12601
|
+
} catch {}
|
|
12602
|
+
await sleep2(1e4);
|
|
12603
|
+
}
|
|
12604
|
+
}
|
|
12605
|
+
console.log(`
|
|
12606
|
+
Next steps:
|
|
12607
|
+
` + ` - author your theme in ${dir2}/theme/ (see theme/README.md), commit + push
|
|
12608
|
+
` + ` - zalify shopify import stores/<slug> --prune # catalog (auto-flushes the site cache)
|
|
12609
|
+
` + ` - zalify theme status ${dir2} # drift check anytime`);
|
|
12610
|
+
}
|
|
12611
|
+
async function storefrontList() {
|
|
12612
|
+
const auth = requireActive();
|
|
12613
|
+
const { storefronts } = await get(auth, "/api/storefronts");
|
|
12614
|
+
if (!storefronts.length) {
|
|
12615
|
+
console.log(`No storefronts in workspace "${auth.workspaceName}".`);
|
|
12616
|
+
return;
|
|
12617
|
+
}
|
|
12618
|
+
for (const s of storefronts) {
|
|
12619
|
+
const site = s.domain ? `https://${s.domain}` : "-";
|
|
12620
|
+
console.log(`${s.status === "READY" ? "✓" : s.status === "FAILED" ? "✗" : "…"} ${s.slug} ${site} (${s.status.toLowerCase()}${s.githubRepo ? `, ${s.githubRepo}` : ""})`);
|
|
12621
|
+
}
|
|
12622
|
+
}
|
|
12623
|
+
function resolveSlug(slugOrDir) {
|
|
12624
|
+
const storeJson = join5(resolve4(slugOrDir), "store.json");
|
|
12625
|
+
if (existsSync5(storeJson)) {
|
|
12626
|
+
const parsed = JSON.parse(readFileSync5(storeJson, "utf8"));
|
|
12627
|
+
if (parsed.slug)
|
|
12628
|
+
return parsed.slug;
|
|
12629
|
+
}
|
|
12630
|
+
return basename2(slugOrDir);
|
|
12631
|
+
}
|
|
12632
|
+
async function storefrontRevalidate(slugOrDir) {
|
|
12633
|
+
const auth = requireActive();
|
|
12634
|
+
const slug = resolveSlug(slugOrDir);
|
|
12635
|
+
const { storefronts } = await get(auth, "/api/storefronts");
|
|
12636
|
+
const match = storefronts.find((s) => s.slug === slug);
|
|
12637
|
+
if (!match)
|
|
12638
|
+
throw new Error(`No storefront "${slug}" in workspace "${auth.workspaceName}".`);
|
|
12639
|
+
await post(auth, `/api/storefronts/${match.id}/revalidate`, {});
|
|
12640
|
+
console.log(`✓ flushed cache on https://${match.domain}`);
|
|
12641
|
+
}
|
|
12642
|
+
async function maybeRevalidateStorefront(auth, storeDir) {
|
|
12643
|
+
try {
|
|
12644
|
+
const slug = resolveSlug(storeDir);
|
|
12645
|
+
const { storefronts } = await get(auth, "/api/storefronts");
|
|
12646
|
+
const match = storefronts.find((s) => s.slug === slug && s.status === "READY");
|
|
12647
|
+
if (!match)
|
|
12648
|
+
return;
|
|
12649
|
+
await post(auth, `/api/storefronts/${match.id}/revalidate`, {});
|
|
12650
|
+
console.log(`✓ site cache flushed (https://${match.domain})`);
|
|
12651
|
+
} catch {}
|
|
12652
|
+
}
|
|
12653
|
+
|
|
12654
|
+
// src/shopify.ts
|
|
12062
12655
|
function proxyError(auth, status, json) {
|
|
12063
12656
|
if (json.code === "SCOPES_MISSING") {
|
|
12064
12657
|
const granted = new Set(json.grantedScopes ?? []);
|
|
@@ -12099,10 +12692,10 @@ async function gql(auth, query, variables = {}) {
|
|
|
12099
12692
|
return json.data;
|
|
12100
12693
|
}
|
|
12101
12694
|
function readJson(path9, label) {
|
|
12102
|
-
if (!
|
|
12695
|
+
if (!existsSync6(path9)) {
|
|
12103
12696
|
throw new Error(`No ${label} at ${path9}`);
|
|
12104
12697
|
}
|
|
12105
|
-
return JSON.parse(
|
|
12698
|
+
return JSON.parse(readFileSync6(path9, "utf8"));
|
|
12106
12699
|
}
|
|
12107
12700
|
function joinErrors(errs) {
|
|
12108
12701
|
return errs.map((e) => e.message).join("; ");
|
|
@@ -12128,8 +12721,8 @@ async function findProductId(auth, handle) {
|
|
|
12128
12721
|
}
|
|
12129
12722
|
async function shopifyImport(storeDir, options = {}) {
|
|
12130
12723
|
const auth = requireActive();
|
|
12131
|
-
const dir2 =
|
|
12132
|
-
const catalog = readJson(
|
|
12724
|
+
const dir2 = resolve5(storeDir);
|
|
12725
|
+
const catalog = readJson(join6(dir2, "catalog.json"), "catalog.json");
|
|
12133
12726
|
const { locations } = await gql(auth, `{ locations(first: 1) { nodes { id name } } }`);
|
|
12134
12727
|
const locationId = locations.nodes[0]?.id;
|
|
12135
12728
|
console.log(`Importing ${catalog.products.length} product(s) into the Shopify store connected to ` + `workspace "${auth.workspaceName}" (location: ${locations.nodes[0]?.name ?? "none"})`);
|
|
@@ -12234,15 +12827,16 @@ async function shopifyImport(storeDir, options = {}) {
|
|
|
12234
12827
|
console.warn(` ! could not publish to Online Store (write_publications scope missing?): ${e instanceof Error ? e.message : String(e)}`);
|
|
12235
12828
|
}
|
|
12236
12829
|
console.log("Done.");
|
|
12830
|
+
await maybeRevalidateStorefront(auth, dir2);
|
|
12237
12831
|
}
|
|
12238
12832
|
async function shopifyUploadImages(storeDir) {
|
|
12239
12833
|
const auth = requireActive();
|
|
12240
|
-
const dir2 =
|
|
12241
|
-
const manifest = readJson(
|
|
12834
|
+
const dir2 = resolve5(storeDir);
|
|
12835
|
+
const manifest = readJson(join6(dir2, "images", "manifest.json"), "images/manifest.json");
|
|
12242
12836
|
console.log(`Uploading images to the Shopify store connected to workspace "${auth.workspaceName}"`);
|
|
12243
12837
|
for (const entry of manifest.images) {
|
|
12244
|
-
const filePath =
|
|
12245
|
-
if (!
|
|
12838
|
+
const filePath = join6(dir2, "images", entry.file);
|
|
12839
|
+
if (!existsSync6(filePath)) {
|
|
12246
12840
|
console.log(` - ${entry.handle}: ${entry.file} not generated yet, skipped`);
|
|
12247
12841
|
continue;
|
|
12248
12842
|
}
|
|
@@ -12253,13 +12847,13 @@ async function shopifyUploadImages(storeDir) {
|
|
|
12253
12847
|
console.error(` ✗ ${entry.handle}: product not found in store`);
|
|
12254
12848
|
continue;
|
|
12255
12849
|
}
|
|
12256
|
-
const stem =
|
|
12850
|
+
const stem = basename3(entry.file, ".png");
|
|
12257
12851
|
const already = product.media.nodes.some((m) => m.image?.url?.includes(stem) || entry.alt && m.alt === entry.alt);
|
|
12258
12852
|
if (already) {
|
|
12259
12853
|
console.log(` - ${entry.file}: already attached, skipped`);
|
|
12260
12854
|
continue;
|
|
12261
12855
|
}
|
|
12262
|
-
const bytes =
|
|
12856
|
+
const bytes = readFileSync6(filePath);
|
|
12263
12857
|
const staged = await gql(auth, `mutation($input: [StagedUploadInput!]!) {
|
|
12264
12858
|
stagedUploadsCreate(input: $input) {
|
|
12265
12859
|
stagedTargets { url resourceUrl parameters { name value } }
|
|
@@ -12269,7 +12863,7 @@ async function shopifyUploadImages(storeDir) {
|
|
|
12269
12863
|
input: [
|
|
12270
12864
|
{
|
|
12271
12865
|
resource: "IMAGE",
|
|
12272
|
-
filename:
|
|
12866
|
+
filename: basename3(entry.file),
|
|
12273
12867
|
mimeType: "image/png",
|
|
12274
12868
|
httpMethod: "PUT",
|
|
12275
12869
|
fileSize: String(bytes.length)
|
|
@@ -12311,12 +12905,13 @@ async function shopifyUploadImages(storeDir) {
|
|
|
12311
12905
|
console.log(` ✓ ${entry.file}`);
|
|
12312
12906
|
}
|
|
12313
12907
|
console.log("Done.");
|
|
12908
|
+
await maybeRevalidateStorefront(auth, dir2);
|
|
12314
12909
|
}
|
|
12315
12910
|
|
|
12316
12911
|
// src/cli.ts
|
|
12317
|
-
var __dirname4 =
|
|
12912
|
+
var __dirname4 = dirname2(fileURLToPath3(import.meta.url));
|
|
12318
12913
|
var require2 = createRequire2(import.meta.url);
|
|
12319
|
-
var pkg = require2(
|
|
12914
|
+
var pkg = require2(join7(__dirname4, "..", "package.json"));
|
|
12320
12915
|
try {
|
|
12321
12916
|
updateNotifier({ pkg }).notify({
|
|
12322
12917
|
isGlobal: true,
|
|
@@ -12371,6 +12966,26 @@ shopify.command("import <store-dir>").description("Import <store-dir>/catalog.js
|
|
|
12371
12966
|
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
12967
|
await shopifyUploadImages(storeDir);
|
|
12373
12968
|
});
|
|
12969
|
+
var storefront = program2.command("storefront").description("Provision and manage fully-hosted standalone storefronts (repo + Vercel via Zalify)");
|
|
12970
|
+
storefront.command("create <dir>").description("One-command launch: Zalify provisions the GitHub repo + Vercel project (env, domain, storefront token), then scaffolds locally and pushes").option("-t, --theme <name>", "theme", "nextjs").option("--editor", "include the Zalify canvas-editor (z1) wiring").option("--to <version>", "template version (default: latest)").option("--no-install", "skip dependency install").option("--no-git", "skip git init/push").action(async (dir2, options) => {
|
|
12971
|
+
await storefrontCreate(dir2, options);
|
|
12972
|
+
});
|
|
12973
|
+
storefront.command("list", { isDefault: true }).description("List the workspace's provisioned storefronts").action(async () => {
|
|
12974
|
+
await storefrontList();
|
|
12975
|
+
});
|
|
12976
|
+
storefront.command("revalidate <slug-or-store-dir>").description("Flush the storefront site's data cache (secret stays server-side)").action(async (slugOrDir) => {
|
|
12977
|
+
await storefrontRevalidate(slugOrDir);
|
|
12978
|
+
});
|
|
12979
|
+
var theme = program2.command("theme").description("Scaffold and upgrade standalone Zalify storefronts (Shopify-theme model)");
|
|
12980
|
+
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) => {
|
|
12981
|
+
await themeCreate(dir2, options);
|
|
12982
|
+
});
|
|
12983
|
+
theme.command("status [dir]").description("List theme-owned files edited since scaffolding (.zalify/theme.json checksums)").action((dir2) => {
|
|
12984
|
+
themeStatus(dir2);
|
|
12985
|
+
});
|
|
12986
|
+
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) => {
|
|
12987
|
+
await themeUpgrade(dir2, options);
|
|
12988
|
+
});
|
|
12374
12989
|
var assets = program2.command("assets").description("Sync images with the Zalify asset library");
|
|
12375
12990
|
assets.command("push <store-dir>").description("Upload <store-dir>/images/*.png (sha256 dedup, writes assets.json)").action(async (storeDir) => {
|
|
12376
12991
|
await assetsPush(storeDir);
|