@zalify/cli 0.17.0 → 0.17.1
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/README.md +106 -33
- package/dist/cli.js +191 -104
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @zalify/cli
|
|
2
2
|
|
|
3
|
-
Zalify CLI
|
|
3
|
+
Zalify CLI — author a brand, launch its shop, run its ads, keep the theme
|
|
4
|
+
current.
|
|
4
5
|
|
|
5
6
|
## Install
|
|
6
7
|
|
|
@@ -10,45 +11,119 @@ npm i -g zalify
|
|
|
10
11
|
npm i -g @zalify/cli@latest
|
|
11
12
|
```
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Already installed? `zalify self-update`.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
## The command model
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
zalify login # opens the browser: pick a workspace, authorize
|
|
19
|
-
zalify login --app-url http://localhost:3000 # against a local app.zalify.com
|
|
20
|
-
zalify whoami # show the authorized workspace
|
|
21
|
-
zalify logout # delete local credentials
|
|
22
|
-
```
|
|
18
|
+
Four nouns, four objects:
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
| Noun | The object | What lives under it |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `brand` | the **data** | a brand folder: catalog, voice guide, image manifest |
|
|
23
|
+
| `shop` | the **shop** | Zalify-hosted at `<slug>.z1.shop`: repo + deployment + domain |
|
|
24
|
+
| `ads` | the **ad ops** | Autopilot brand instance: campaign intent repo (media plans, ads-as-code, policy) |
|
|
25
|
+
| `theme` | the **code** | Shopify-theme model: scaffold a copy, measure drift, upgrade |
|
|
29
26
|
|
|
30
|
-
|
|
27
|
+
Everything acting on a brand folder lives under `brand`. Brand and theme
|
|
28
|
+
commands default their directory to the cwd; `shop refresh`/`open` resolve
|
|
29
|
+
their shop from the cwd (`.zalify/site.json` in a scaffold, `store.json` in
|
|
30
|
+
a brand folder); `ads` commands run arg-free from a checkout carrying
|
|
31
|
+
`.zalify/brand-repo.json`.
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
(the same storage that backs Assets in the Zalify app):
|
|
33
|
+
## The golden path (new brand, start to live)
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
zalify
|
|
37
|
-
zalify
|
|
36
|
+
zalify brand init stores/acme # 1. author the data (no login needed)
|
|
37
|
+
zalify login # 2. authorize; then: zalify workspace set <workspace>
|
|
38
|
+
zalify shop create acme # 3. live at acme.z1.shop in ~4 minutes
|
|
39
|
+
cd stores/acme && zalify brand push --prune
|
|
40
|
+
zalify brand images generate && zalify brand images attach
|
|
41
|
+
zalify theme status # 6. stay current; `theme upgrade` keeps your edits
|
|
38
42
|
```
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
file to its asset id, public URL, and checksum. `pull` restores the binaries
|
|
42
|
-
on a fresh clone, so image masters stay out of git.
|
|
44
|
+
## Authentication
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
```bash
|
|
47
|
+
zalify login # opens the browser: authorize once
|
|
48
|
+
zalify login --app-url http://localhost:3000 # against a local app.zalify.com
|
|
49
|
+
zalify whoami # workspace, plan, and key details
|
|
50
|
+
zalify logout # delete local credentials
|
|
51
|
+
```
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
`login` runs a loopback flow (like `gh auth login`): the CLI listens on a
|
|
54
|
+
random localhost port, the browser opens `app.zalify.com/cli/auth`, and on
|
|
55
|
+
approval a **user key covering all your workspaces** is handed back and
|
|
56
|
+
stored in `~/.zalify/config.json` (0600). Keys are ordinary Zalify Public
|
|
57
|
+
API keys — list and revoke them in **Settings → Developer**. One login
|
|
58
|
+
covers every workspace; `zalify workspace set` switches between them
|
|
59
|
+
locally, no re-login.
|
|
60
|
+
|
|
61
|
+
## Command reference
|
|
62
|
+
|
|
63
|
+
### Auth & workspace
|
|
64
|
+
|
|
65
|
+
| Command | What it does |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `login [--app-url <url>]` | Browser-based authorization; stores a user key locally |
|
|
68
|
+
| `logout` | Deletes `~/.zalify/config.json` (revoke the key itself in Settings → Developer) |
|
|
69
|
+
| `whoami` | Verifies the stored key; shows workspace, plan, app origin, key prefix |
|
|
70
|
+
| `workspace` / `workspace list` | List authorized workspaces (`*` = active) |
|
|
71
|
+
| `workspace set <slug-or-id>` | Switch the active workspace (local, no re-login) |
|
|
72
|
+
|
|
73
|
+
### `brand` — the data
|
|
74
|
+
|
|
75
|
+
| Command | What it does |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `brand init [dir] [--name <name>] [--vertical <v>] [--currency <code>]` | Scaffold a brand folder: catalog, voice guide, image manifest, `AUTHORING.md` rules. No login needed |
|
|
78
|
+
| `brand validate [dir]` | Lint the brand folder against the authoring rules |
|
|
79
|
+
| `brand push [dir] [--prune] [--shop <slug>]` | Push `catalog.json` to the connected Shopify store — products, smart collections, publish; idempotent. `--prune` deletes store products (by catalog vendor) absent from the catalog; `--shop` narrows the post-push refresh to one shop |
|
|
80
|
+
| `brand images generate [dir]` | Generate the manifest's missing images on Zalify infrastructure (resumable) |
|
|
81
|
+
| `brand images attach [dir] [--shop <slug>]` | Attach generated images to their Shopify products per the manifest (skips already-attached) |
|
|
82
|
+
| `brand images push [dir]` | Upload image masters to the Zalify asset library — presigned R2 PUTs, sha256 dedup; writes `images/assets.json`, the committed index that keeps binaries out of git |
|
|
83
|
+
| `brand images pull [dir]` | Download masters listed in `images/assets.json` that are missing locally (fresh-clone restore) |
|
|
84
|
+
|
|
85
|
+
### `shop` — the hosted shop
|
|
86
|
+
|
|
87
|
+
| Command | What it does |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| `shop create <dir> [-t <theme>] [--editor] [--to <version>] [--no-install] [--no-git]` | One-command launch: provision repo + deployment + domain + storefront token, scaffold locally, push, go live at `<slug>.z1.shop`. `--editor` wires in the Zalify canvas editor (z1) |
|
|
90
|
+
| `shop` / `shop list` | List the workspace's hosted shops |
|
|
91
|
+
| `shop refresh [slug-or-dir]` | Refresh the live shop's data cache (defaults to the shop linked to cwd) |
|
|
92
|
+
| `shop open [slug-or-dir]` | Open the live shop in the browser (defaults to the shop linked to cwd) |
|
|
93
|
+
| `shop domain-sync [slug-or-dir]` | Re-point the shop at the current root domain (the old domain 308-redirects) |
|
|
94
|
+
|
|
95
|
+
`site …` remains as a hidden alias for one release and prints a pointer to
|
|
96
|
+
its new home under `shop`.
|
|
97
|
+
|
|
98
|
+
### `ads` — the ad ops
|
|
99
|
+
|
|
100
|
+
| Command | What it does |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `ads create <dir>` | Provision an Autopilot brand-instance GitHub repo (POST `/api/repos`, GitHub App), scaffold the starter locally (`identity.yaml`, `brand.yaml`, `policy/`, `campaigns/`, `media-plans/`, `signals/`, `decisions/`), and push with a short-lived repo-scoped token. Writes `.zalify/brand-repo.json` so later commands run arg-free. The directory name becomes the slug — use the workspace's store slug so the service's digest writer can find the repo |
|
|
103
|
+
| `ads` / `ads list` | List the workspace's brand instances and their provisioning status |
|
|
104
|
+
|
|
105
|
+
The engine commands — `plan` / `apply` / `creative` / `report` — join this
|
|
106
|
+
group as `@zalify/ads-engine` merges in. Until then, drafting and applying
|
|
107
|
+
happen on the Structure canvas in app.zalify.com; the instance repo is the
|
|
108
|
+
durable intent layer that work lands in.
|
|
109
|
+
|
|
110
|
+
### `theme` — the code
|
|
111
|
+
|
|
112
|
+
| Command | What it does |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `theme create <dir> [-t <template>] [--editor] [--store-domain <domain>] [--storefront-token <token>] [--to <version>] [--no-install] [--no-git] [--tarball <path>]` | Scaffold only — no Zalify hosting (the self-host path; `shop create` is the fully-hosted launch). Templates: `liquid` \| `hydrogen` \| `nextjs`. Without `--store-domain` it runs on mock.shop demo data |
|
|
115
|
+
| `theme status [dir]` | Which theme files have you edited? Checksums vs `.zalify/theme.json` |
|
|
116
|
+
| `theme upgrade [dir] [--to <version>] [--dry-run] [--install] [--tarball <path>] [--base-tarball <path>]` | Move to a newer theme version: untouched files overwritten, edited files 3-way-merged, `theme/` + `.env` never touched. `--dry-run` classifies and reports without writing |
|
|
117
|
+
|
|
118
|
+
### Maintenance
|
|
119
|
+
|
|
120
|
+
| Command | What it does |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `version` | Print the CLI version and check npm for a newer one |
|
|
123
|
+
| `self-update` | Update the CLI to the latest version (auto-detects pnpm/bun/npm) |
|
|
124
|
+
|
|
125
|
+
Every invocation also runs a passive, non-blocking update check and prints
|
|
126
|
+
a one-line notice when a newer version exists.
|
|
52
127
|
|
|
53
128
|
## Development
|
|
54
129
|
|
|
@@ -76,8 +151,6 @@ Release flow:
|
|
|
76
151
|
|
|
77
152
|
```bash
|
|
78
153
|
npm version minor --no-git-tag-version # or patch/major
|
|
79
|
-
|
|
154
|
+
# bump zalify/package.json in step and pin its @zalify/cli dependency
|
|
155
|
+
git commit -am "0.x.0" && git push
|
|
80
156
|
```
|
|
81
|
-
|
|
82
|
-
`bun run release` (bump + local `npm publish`, needs `NODE_AUTH_TOKEN` in
|
|
83
|
-
`.env`) remains as a manual fallback if CI is unavailable.
|
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 as dirname2, join as
|
|
11296
|
+
import { dirname as dirname2, join as join10 } from "node:path";
|
|
11297
11297
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
11298
11298
|
|
|
11299
11299
|
// src/auth.ts
|
|
@@ -11762,37 +11762,122 @@ async function assetsPush(storeDir) {
|
|
|
11762
11762
|
|
|
11763
11763
|
// src/self-update.ts
|
|
11764
11764
|
import { spawnSync } from "node:child_process";
|
|
11765
|
-
import { realpathSync } from "node:fs";
|
|
11766
|
-
|
|
11767
|
-
|
|
11765
|
+
import { existsSync as existsSync3, realpathSync } from "node:fs";
|
|
11766
|
+
import { delimiter, join as join3 } from "node:path";
|
|
11767
|
+
var PACKAGES = ["zalify", "@zalify/cli"];
|
|
11768
|
+
var real = (p) => {
|
|
11768
11769
|
try {
|
|
11769
|
-
|
|
11770
|
-
} catch {
|
|
11770
|
+
return realpathSync(p);
|
|
11771
|
+
} catch {
|
|
11772
|
+
return p;
|
|
11773
|
+
}
|
|
11774
|
+
};
|
|
11775
|
+
function managerFor(binPath) {
|
|
11771
11776
|
if (binPath.includes("/pnpm/") || binPath.includes("/.pnpm/")) {
|
|
11772
|
-
return {
|
|
11777
|
+
return {
|
|
11778
|
+
cmd: "pnpm",
|
|
11779
|
+
update: (pkg) => ["add", "-g", `${pkg}@latest`],
|
|
11780
|
+
remove: `pnpm rm -g ${PACKAGES.join(" ")}`
|
|
11781
|
+
};
|
|
11773
11782
|
}
|
|
11774
11783
|
if (binPath.includes("/.bun/")) {
|
|
11775
|
-
return {
|
|
11784
|
+
return {
|
|
11785
|
+
cmd: "bun",
|
|
11786
|
+
update: (pkg) => ["add", "-g", `${pkg}@latest`],
|
|
11787
|
+
remove: `bun remove -g ${PACKAGES.join(" ")}`
|
|
11788
|
+
};
|
|
11789
|
+
}
|
|
11790
|
+
return {
|
|
11791
|
+
cmd: "npm",
|
|
11792
|
+
update: (pkg) => ["install", "-g", `${pkg}@latest`],
|
|
11793
|
+
remove: `npm rm -g ${PACKAGES.join(" ")}`
|
|
11794
|
+
};
|
|
11795
|
+
}
|
|
11796
|
+
function packageFor(binPath) {
|
|
11797
|
+
return /\/node_modules\/zalify\//.test(binPath) || /\/zalify@\d/.test(binPath) ? "zalify" : "@zalify/cli";
|
|
11798
|
+
}
|
|
11799
|
+
function zalifysOnPath() {
|
|
11800
|
+
const hits = [];
|
|
11801
|
+
for (const dir2 of (process.env.PATH ?? "").split(delimiter)) {
|
|
11802
|
+
if (!dir2)
|
|
11803
|
+
continue;
|
|
11804
|
+
const candidate = join3(dir2, "zalify");
|
|
11805
|
+
if (existsSync3(candidate) && !hits.includes(candidate)) {
|
|
11806
|
+
hits.push(candidate);
|
|
11807
|
+
}
|
|
11776
11808
|
}
|
|
11777
|
-
return
|
|
11809
|
+
return hits;
|
|
11810
|
+
}
|
|
11811
|
+
function versionOf(bin) {
|
|
11812
|
+
const res = spawnSync(bin, ["--version"], { encoding: "utf8" });
|
|
11813
|
+
if (res.error || res.status !== 0)
|
|
11814
|
+
return null;
|
|
11815
|
+
return res.stdout?.trim() || null;
|
|
11778
11816
|
}
|
|
11779
|
-
|
|
11780
|
-
|
|
11817
|
+
var HASH_HINT = "If `zalify -v` still shows the old version in this terminal, the shell\n" + "has cached the old location — run `hash -r` (zsh/bash) or open a new\n" + "terminal. A CLI cannot refresh its parent shell for you.";
|
|
11818
|
+
async function selfUpdate(currentVersion) {
|
|
11819
|
+
const running = real(process.argv[1] ?? "");
|
|
11820
|
+
const manager = managerFor(running);
|
|
11821
|
+
const pkg = packageFor(running);
|
|
11781
11822
|
console.log(`Current version: ${currentVersion}`);
|
|
11782
|
-
console.log(`Updating via: ${cmd} ${
|
|
11823
|
+
console.log(`Updating via: ${manager.cmd} ${manager.update(pkg).join(" ")}
|
|
11783
11824
|
`);
|
|
11784
|
-
const result = spawnSync(cmd,
|
|
11825
|
+
const result = spawnSync(manager.cmd, manager.update(pkg), {
|
|
11826
|
+
stdio: "inherit"
|
|
11827
|
+
});
|
|
11785
11828
|
if (result.error || result.status !== 0) {
|
|
11786
11829
|
console.error(`
|
|
11787
11830
|
Update failed${result.error ? `: ${result.error.message}` : ""}.` + `
|
|
11788
|
-
Try manually: ${cmd} ${
|
|
11831
|
+
Try manually: ${manager.cmd} ${manager.update(pkg).join(" ")}`);
|
|
11789
11832
|
process.exit(result.status ?? 1);
|
|
11790
11833
|
}
|
|
11791
|
-
|
|
11792
|
-
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11834
|
+
let latest = null;
|
|
11835
|
+
try {
|
|
11836
|
+
const info = await updateNotifier({
|
|
11837
|
+
pkg: { name: "@zalify/cli", version: currentVersion }
|
|
11838
|
+
}).fetchInfo();
|
|
11839
|
+
latest = info?.latest ?? null;
|
|
11840
|
+
} catch {}
|
|
11841
|
+
const hits = zalifysOnPath();
|
|
11842
|
+
const first = hits[0];
|
|
11843
|
+
const firstVersion = first ? versionOf(first) : null;
|
|
11844
|
+
const mayBeHashed = hits.length > 1 || first && real(first) !== running;
|
|
11845
|
+
if (!first || !firstVersion) {
|
|
11846
|
+
console.log(`
|
|
11847
|
+
✓ Done`);
|
|
11848
|
+
return;
|
|
11849
|
+
}
|
|
11850
|
+
const reachedLatest = latest ? firstVersion === latest : firstVersion !== currentVersion;
|
|
11851
|
+
if (reachedLatest) {
|
|
11852
|
+
console.log(firstVersion === currentVersion ? `
|
|
11853
|
+
✓ Already the latest (${firstVersion})` : `
|
|
11854
|
+
✓ Updated ${currentVersion} → ${firstVersion}`);
|
|
11855
|
+
if (mayBeHashed)
|
|
11856
|
+
console.log(`
|
|
11857
|
+
${HASH_HINT}`);
|
|
11858
|
+
return;
|
|
11859
|
+
}
|
|
11860
|
+
if (latest) {
|
|
11861
|
+
console.log(`
|
|
11862
|
+
✗ ${pkg} is now ${latest}, but your PATH still runs`);
|
|
11863
|
+
console.log(` ${first} (${firstVersion})`);
|
|
11864
|
+
if (hits.length > 1) {
|
|
11865
|
+
console.log(`
|
|
11866
|
+
Every zalify on your PATH:`);
|
|
11867
|
+
for (const hit of hits) {
|
|
11868
|
+
console.log(` ${hit} → ${versionOf(hit) ?? "unreadable"}`);
|
|
11869
|
+
}
|
|
11870
|
+
}
|
|
11871
|
+
console.log(`
|
|
11872
|
+
Remove the stale install (${managerFor(real(first)).remove}),`);
|
|
11873
|
+
console.log("then run `hash -r` or open a new terminal.");
|
|
11874
|
+
process.exit(1);
|
|
11875
|
+
}
|
|
11876
|
+
console.log(`
|
|
11877
|
+
✓ Done (version: ${firstVersion})`);
|
|
11878
|
+
if (mayBeHashed)
|
|
11879
|
+
console.log(`
|
|
11880
|
+
${HASH_HINT}`);
|
|
11796
11881
|
}
|
|
11797
11882
|
|
|
11798
11883
|
// src/workspace.ts
|
|
@@ -11867,8 +11952,8 @@ async function workspaceSet(slugOrId) {
|
|
|
11867
11952
|
}
|
|
11868
11953
|
|
|
11869
11954
|
// src/images.ts
|
|
11870
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as
|
|
11871
|
-
import { join as
|
|
11955
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync4, rmSync as rmSync2 } from "node:fs";
|
|
11956
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
11872
11957
|
var MAX_JOBS_PER_REQUEST = 40;
|
|
11873
11958
|
var POLL_INTERVAL_MS = 5000;
|
|
11874
11959
|
var POLL_TIMEOUT_MS = 30 * 60 * 1000;
|
|
@@ -11896,7 +11981,7 @@ async function saveProduced(imagesDir, indexPath, index, file2, asset) {
|
|
|
11896
11981
|
return false;
|
|
11897
11982
|
}
|
|
11898
11983
|
const bytes = Buffer.from(await img.arrayBuffer());
|
|
11899
|
-
writeFileSync4(
|
|
11984
|
+
writeFileSync4(join4(imagesDir, file2), bytes);
|
|
11900
11985
|
index[file2] = {
|
|
11901
11986
|
assetId: asset.id,
|
|
11902
11987
|
url: asset.url,
|
|
@@ -12033,17 +12118,17 @@ async function streamingGenerate(auth, manifest, missing, imagesDir, indexPath,
|
|
|
12033
12118
|
}
|
|
12034
12119
|
async function imagesGenerate(storeDir) {
|
|
12035
12120
|
const auth = requireActive();
|
|
12036
|
-
const imagesDir =
|
|
12037
|
-
const manifestPath =
|
|
12038
|
-
if (!
|
|
12121
|
+
const imagesDir = join4(resolve2(storeDir), "images");
|
|
12122
|
+
const manifestPath = join4(imagesDir, "manifest.json");
|
|
12123
|
+
if (!existsSync4(manifestPath)) {
|
|
12039
12124
|
throw new Error(`No images/manifest.json in ${storeDir}`);
|
|
12040
12125
|
}
|
|
12041
12126
|
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
12042
|
-
const indexPath =
|
|
12043
|
-
const index =
|
|
12044
|
-
const jobStatePath =
|
|
12127
|
+
const indexPath = join4(imagesDir, "assets.json");
|
|
12128
|
+
const index = existsSync4(indexPath) ? JSON.parse(readFileSync3(indexPath, "utf8")) : {};
|
|
12129
|
+
const jobStatePath = join4(imagesDir, ".imagegen-job.json");
|
|
12045
12130
|
const counters = { produced: 0, failed: 0 };
|
|
12046
|
-
if (
|
|
12131
|
+
if (existsSync4(jobStatePath)) {
|
|
12047
12132
|
const state = JSON.parse(readFileSync3(jobStatePath, "utf8"));
|
|
12048
12133
|
console.log(`Resuming job ${state.jobId}`);
|
|
12049
12134
|
await pollJob(auth, state.jobId, state.files, imagesDir, indexPath, index, counters);
|
|
@@ -12051,7 +12136,7 @@ async function imagesGenerate(storeDir) {
|
|
|
12051
12136
|
console.log(`Done: ${counters.produced} generated (already in the asset library), ${counters.failed} failed${counters.failed ? " — re-run to retry" : ""}.`);
|
|
12052
12137
|
return;
|
|
12053
12138
|
}
|
|
12054
|
-
const missing = manifest.images.filter((e) => !
|
|
12139
|
+
const missing = manifest.images.filter((e) => !existsSync4(join4(imagesDir, e.file)));
|
|
12055
12140
|
if (missing.length === 0) {
|
|
12056
12141
|
console.log("All manifest images already exist locally.");
|
|
12057
12142
|
return;
|
|
@@ -12075,20 +12160,20 @@ async function imagesGenerate(storeDir) {
|
|
|
12075
12160
|
}
|
|
12076
12161
|
|
|
12077
12162
|
// src/shopify.ts
|
|
12078
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
12079
|
-
import { basename as basename3, join as
|
|
12163
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "node:fs";
|
|
12164
|
+
import { basename as basename3, join as join7, resolve as resolve5 } from "node:path";
|
|
12080
12165
|
|
|
12081
12166
|
// src/shop.ts
|
|
12082
12167
|
import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
|
|
12083
|
-
import { existsSync as
|
|
12084
|
-
import { basename as basename2, join as
|
|
12168
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
12169
|
+
import { basename as basename2, join as join6, resolve as resolve4 } from "node:path";
|
|
12085
12170
|
|
|
12086
12171
|
// src/theme.ts
|
|
12087
12172
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12088
12173
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
12089
12174
|
import {
|
|
12090
12175
|
cpSync,
|
|
12091
|
-
existsSync as
|
|
12176
|
+
existsSync as existsSync5,
|
|
12092
12177
|
mkdirSync as mkdirSync2,
|
|
12093
12178
|
mkdtempSync,
|
|
12094
12179
|
readdirSync as readdirSync2,
|
|
@@ -12100,7 +12185,7 @@ import {
|
|
|
12100
12185
|
writeFileSync as writeFileSync5
|
|
12101
12186
|
} from "node:fs";
|
|
12102
12187
|
import { tmpdir } from "node:os";
|
|
12103
|
-
import { basename, dirname, join as
|
|
12188
|
+
import { basename, dirname, join as join5, resolve as resolve3 } from "node:path";
|
|
12104
12189
|
var TEMPLATES_PKG = "@zalify/theme-templates";
|
|
12105
12190
|
var REGISTRY = "https://registry.npmjs.org";
|
|
12106
12191
|
var MANIFEST_PATH = ".zalify/theme.json";
|
|
@@ -12126,27 +12211,27 @@ async function downloadTarball(url) {
|
|
|
12126
12211
|
const res = await fetch(url);
|
|
12127
12212
|
if (!res.ok)
|
|
12128
12213
|
throw new Error(`tarball download ${res.status}: ${url}`);
|
|
12129
|
-
const file2 =
|
|
12214
|
+
const file2 = join5(mkdtempSync(join5(tmpdir(), "zalify-theme-")), "pkg.tgz");
|
|
12130
12215
|
writeFileSync5(file2, Buffer.from(await res.arrayBuffer()));
|
|
12131
12216
|
return file2;
|
|
12132
12217
|
}
|
|
12133
12218
|
function extractTarball(file2) {
|
|
12134
|
-
const dir2 = mkdtempSync(
|
|
12219
|
+
const dir2 = mkdtempSync(join5(tmpdir(), "zalify-theme-x-"));
|
|
12135
12220
|
const result = spawnSync2("tar", ["-xzf", resolve3(file2), "-C", dir2], {
|
|
12136
12221
|
encoding: "utf8"
|
|
12137
12222
|
});
|
|
12138
12223
|
if (result.error || result.status !== 0) {
|
|
12139
12224
|
throw new Error(`tar extract failed for ${file2}: ${result.stderr || result.error?.message}`);
|
|
12140
12225
|
}
|
|
12141
|
-
const root =
|
|
12142
|
-
if (!
|
|
12226
|
+
const root = join5(dir2, "package");
|
|
12227
|
+
if (!existsSync5(root))
|
|
12143
12228
|
throw new Error(`unexpected tarball layout in ${file2} (no package/)`);
|
|
12144
12229
|
return root;
|
|
12145
12230
|
}
|
|
12146
12231
|
async function acquireTemplatePkg(localTarball, version) {
|
|
12147
12232
|
if (localTarball) {
|
|
12148
12233
|
const root = extractTarball(localTarball);
|
|
12149
|
-
const v = JSON.parse(readFileSync4(
|
|
12234
|
+
const v = JSON.parse(readFileSync4(join5(root, "package.json"), "utf8")).version;
|
|
12150
12235
|
return { root, version: v };
|
|
12151
12236
|
}
|
|
12152
12237
|
const packument = await fetchPackument();
|
|
@@ -12157,8 +12242,8 @@ async function acquireTemplatePkg(localTarball, version) {
|
|
|
12157
12242
|
return { root: extractTarball(await downloadTarball(entry.dist.tarball)), version: target };
|
|
12158
12243
|
}
|
|
12159
12244
|
function readTemplateManifest(pkgRoot, template) {
|
|
12160
|
-
const path9 =
|
|
12161
|
-
if (!
|
|
12245
|
+
const path9 = join5(pkgRoot, "templates", template, MANIFEST_PATH);
|
|
12246
|
+
if (!existsSync5(path9)) {
|
|
12162
12247
|
throw new Error(`template "${template}" has no ${MANIFEST_PATH} — this ${TEMPLATES_PKG} version predates the upgrade contract`);
|
|
12163
12248
|
}
|
|
12164
12249
|
return JSON.parse(readFileSync4(path9, "utf8"));
|
|
@@ -12175,21 +12260,21 @@ function templateFilePath(templateDir, rel, variant) {
|
|
|
12175
12260
|
const stashed = rel.replace(/(^|\/)\.gitignore$/, "$1gitignore");
|
|
12176
12261
|
if (variant === "editor") {
|
|
12177
12262
|
for (const candidate of [rel, stashed]) {
|
|
12178
|
-
const overlay =
|
|
12179
|
-
if (
|
|
12263
|
+
const overlay = join5(templateDir, "__editor__", candidate);
|
|
12264
|
+
if (existsSync5(overlay))
|
|
12180
12265
|
return overlay;
|
|
12181
12266
|
}
|
|
12182
12267
|
}
|
|
12183
12268
|
for (const candidate of [rel, stashed]) {
|
|
12184
|
-
const path9 =
|
|
12185
|
-
if (
|
|
12269
|
+
const path9 = join5(templateDir, candidate);
|
|
12270
|
+
if (existsSync5(path9))
|
|
12186
12271
|
return path9;
|
|
12187
12272
|
}
|
|
12188
12273
|
throw new Error(`file listed in manifest but missing from template: ${rel}`);
|
|
12189
12274
|
}
|
|
12190
12275
|
function readProjectManifest(dir2) {
|
|
12191
|
-
const path9 =
|
|
12192
|
-
if (!
|
|
12276
|
+
const path9 = join5(dir2, MANIFEST_PATH);
|
|
12277
|
+
if (!existsSync5(path9)) {
|
|
12193
12278
|
throw new Error(`no ${MANIFEST_PATH} in ${dir2} — not a scaffolded Zalify storefront.
|
|
12194
12279
|
` + ` Scaffold one with \`zalify theme create\`.`);
|
|
12195
12280
|
}
|
|
@@ -12201,22 +12286,22 @@ function writeJson(path9, value) {
|
|
|
12201
12286
|
}
|
|
12202
12287
|
async function themeCreate(dir2, opts) {
|
|
12203
12288
|
const targetDir = resolve3(dir2);
|
|
12204
|
-
if (
|
|
12289
|
+
if (existsSync5(targetDir) && readdirSync2(targetDir).length > 0) {
|
|
12205
12290
|
throw new Error(`${dir2} already exists and is not empty`);
|
|
12206
12291
|
}
|
|
12207
12292
|
const { root, version } = await acquireTemplatePkg(opts.tarball, opts.to);
|
|
12208
|
-
const frameworks = JSON.parse(readFileSync4(
|
|
12293
|
+
const frameworks = JSON.parse(readFileSync4(join5(root, "templates.json"), "utf8"));
|
|
12209
12294
|
const template = opts.template ?? "nextjs";
|
|
12210
12295
|
const meta = frameworks.frameworks[template];
|
|
12211
12296
|
if (!meta) {
|
|
12212
12297
|
throw new Error(`unknown template "${template}" — expected one of: ${Object.keys(frameworks.frameworks).join(", ")}`);
|
|
12213
12298
|
}
|
|
12214
|
-
const templateDir =
|
|
12299
|
+
const templateDir = join5(root, "templates", template);
|
|
12215
12300
|
console.log(`Scaffolding ${meta.label} (${TEMPLATES_PKG}@${version}) into ${dir2}`);
|
|
12216
12301
|
mkdirSync2(targetDir, { recursive: true });
|
|
12217
12302
|
cpSync(templateDir, targetDir, { recursive: true });
|
|
12218
|
-
const overlayDir =
|
|
12219
|
-
const useEditor = Boolean(opts.editor) &&
|
|
12303
|
+
const overlayDir = join5(targetDir, "__editor__");
|
|
12304
|
+
const useEditor = Boolean(opts.editor) && existsSync5(overlayDir);
|
|
12220
12305
|
if (opts.editor && !useEditor) {
|
|
12221
12306
|
console.log(` ! --editor has no effect for ${template} — no editor overlay in this template`);
|
|
12222
12307
|
}
|
|
@@ -12225,14 +12310,14 @@ async function themeCreate(dir2, opts) {
|
|
|
12225
12310
|
rmSync3(overlayDir, { recursive: true, force: true });
|
|
12226
12311
|
(function restoreGitignores(d) {
|
|
12227
12312
|
for (const entry of readdirSync2(d)) {
|
|
12228
|
-
const full =
|
|
12313
|
+
const full = join5(d, entry);
|
|
12229
12314
|
if (statSync(full).isDirectory())
|
|
12230
12315
|
restoreGitignores(full);
|
|
12231
12316
|
else if (entry === "gitignore")
|
|
12232
|
-
renameSync(full,
|
|
12317
|
+
renameSync(full, join5(d, ".gitignore"));
|
|
12233
12318
|
}
|
|
12234
12319
|
})(targetDir);
|
|
12235
|
-
const pkgPath =
|
|
12320
|
+
const pkgPath = join5(targetDir, "package.json");
|
|
12236
12321
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
12237
12322
|
pkg.name = basename(targetDir).toLowerCase().replaceAll(/[^a-z0-9-_.]+/g, "-").replaceAll(/^[-.]+|[-.]+$/g, "") || "zalify-storefront";
|
|
12238
12323
|
writeJson(pkgPath, pkg);
|
|
@@ -12246,12 +12331,12 @@ async function themeCreate(dir2, opts) {
|
|
|
12246
12331
|
if (meta.env.sessionSecret) {
|
|
12247
12332
|
lines.push("# Session cookie signing key — unique to this project.", `${meta.env.sessionSecret}=${randomBytes2(32).toString("hex")}`);
|
|
12248
12333
|
}
|
|
12249
|
-
writeFileSync5(
|
|
12334
|
+
writeFileSync5(join5(targetDir, ".env"), lines.join(`
|
|
12250
12335
|
`) + `
|
|
12251
12336
|
`);
|
|
12252
12337
|
}
|
|
12253
12338
|
const manifest = readProjectManifest(targetDir);
|
|
12254
|
-
writeJson(
|
|
12339
|
+
writeJson(join5(targetDir, MANIFEST_PATH), resolveVariant(manifest, useEditor ? "editor" : "default"));
|
|
12255
12340
|
if (opts.git !== false) {
|
|
12256
12341
|
const git = (args) => spawnSync2("git", args, { cwd: targetDir, stdio: "ignore" });
|
|
12257
12342
|
if (git(["init", "-b", "main"]).status === 0) {
|
|
@@ -12291,8 +12376,8 @@ function themeStatus(dir2 = ".") {
|
|
|
12291
12376
|
const edited = [];
|
|
12292
12377
|
const missing = [];
|
|
12293
12378
|
for (const [rel, expected] of Object.entries(manifest.files)) {
|
|
12294
|
-
const full =
|
|
12295
|
-
if (!
|
|
12379
|
+
const full = join5(projectDir, rel);
|
|
12380
|
+
if (!existsSync5(full))
|
|
12296
12381
|
missing.push(rel);
|
|
12297
12382
|
else if (sha256(readFileSync4(full)) !== expected)
|
|
12298
12383
|
edited.push(rel);
|
|
@@ -12326,8 +12411,8 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
|
12326
12411
|
}
|
|
12327
12412
|
const oldManifest = resolveVariant(readTemplateManifest(base.root, local.template), variant);
|
|
12328
12413
|
const newManifest = resolveVariant(readTemplateManifest(target.root, local.template), variant);
|
|
12329
|
-
const oldDir =
|
|
12330
|
-
const newDir =
|
|
12414
|
+
const oldDir = join5(base.root, "templates", local.template);
|
|
12415
|
+
const newDir = join5(target.root, "templates", local.template);
|
|
12331
12416
|
console.log(`Upgrading ${local.template} theme ${local.version} → ${target.version}` + (opts.dryRun ? " (dry run)" : ""));
|
|
12332
12417
|
const counts = {
|
|
12333
12418
|
overwrite: 0,
|
|
@@ -12345,8 +12430,8 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
|
12345
12430
|
continue;
|
|
12346
12431
|
const oldHash = oldManifest.files[rel];
|
|
12347
12432
|
const newHash = newManifest.files[rel];
|
|
12348
|
-
const localPath =
|
|
12349
|
-
const localExists =
|
|
12433
|
+
const localPath = join5(projectDir, rel);
|
|
12434
|
+
const localExists = existsSync5(localPath);
|
|
12350
12435
|
const localBytes = localExists ? readFileSync4(localPath) : null;
|
|
12351
12436
|
const localHash = localBytes ? sha256(localBytes) : null;
|
|
12352
12437
|
const write = (bytes) => {
|
|
@@ -12385,9 +12470,9 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
|
12385
12470
|
console.log(` ~ would merge ${rel}`);
|
|
12386
12471
|
continue;
|
|
12387
12472
|
}
|
|
12388
|
-
const scratch = mkdtempSync(
|
|
12389
|
-
const basePath =
|
|
12390
|
-
const newPath =
|
|
12473
|
+
const scratch = mkdtempSync(join5(tmpdir(), "zalify-merge-"));
|
|
12474
|
+
const basePath = join5(scratch, "base");
|
|
12475
|
+
const newPath = join5(scratch, "new");
|
|
12391
12476
|
writeFileSync5(basePath, oldBytes);
|
|
12392
12477
|
writeFileSync5(newPath, newBytes);
|
|
12393
12478
|
const result = spawnSync2("git", ["merge-file", "-L", "current", "-L", `base (${base.version})`, "-L", `new (${target.version})`, localPath, basePath, newPath], { encoding: "utf8" });
|
|
@@ -12431,7 +12516,7 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
|
12431
12516
|
}
|
|
12432
12517
|
const depsChanged = mergePackageJson(projectDir, oldDir, newDir, opts.dryRun ?? false);
|
|
12433
12518
|
if (!opts.dryRun) {
|
|
12434
|
-
writeJson(
|
|
12519
|
+
writeJson(join5(projectDir, MANIFEST_PATH), { ...newManifest, version: target.version });
|
|
12435
12520
|
}
|
|
12436
12521
|
const summary = Object.entries(counts).filter(([, n]) => n > 0).map(([k, n]) => `${n} ${k.replace("-", " ")}`).join(", ");
|
|
12437
12522
|
console.log(`
|
|
@@ -12452,7 +12537,7 @@ ${opts.dryRun ? "Would apply" : "Done"}: ${summary || "no file changes"}.`);
|
|
|
12452
12537
|
}
|
|
12453
12538
|
}
|
|
12454
12539
|
function mergePackageJson(projectDir, oldDir, newDir, dryRun) {
|
|
12455
|
-
const read = (p) => JSON.parse(readFileSync4(
|
|
12540
|
+
const read = (p) => JSON.parse(readFileSync4(join5(p, "package.json"), "utf8"));
|
|
12456
12541
|
const localPkg = read(projectDir);
|
|
12457
12542
|
const oldPkg = read(oldDir);
|
|
12458
12543
|
const newPkg = read(newDir);
|
|
@@ -12501,7 +12586,7 @@ function mergePackageJson(projectDir, oldDir, newDir, dryRun) {
|
|
|
12501
12586
|
console.log(` ✓ package.json ${key} updated`);
|
|
12502
12587
|
}
|
|
12503
12588
|
if (changed && !dryRun) {
|
|
12504
|
-
writeFileSync5(
|
|
12589
|
+
writeFileSync5(join5(projectDir, "package.json"), JSON.stringify(localPkg, null, 2) + `
|
|
12505
12590
|
`);
|
|
12506
12591
|
}
|
|
12507
12592
|
return depsChanged;
|
|
@@ -12597,9 +12682,9 @@ async function shopCreate(dir2, opts) {
|
|
|
12597
12682
|
install: opts.install,
|
|
12598
12683
|
git: opts.git
|
|
12599
12684
|
});
|
|
12600
|
-
writeFileSync6(
|
|
12685
|
+
writeFileSync6(join6(resolve4(dir2), ".zalify", "site.json"), JSON.stringify({ id: siteId, slug: view.slug, domain: view.domain, repo: view.githubRepo }, null, 2) + `
|
|
12601
12686
|
`);
|
|
12602
|
-
writeFileSync6(
|
|
12687
|
+
writeFileSync6(join6(resolve4(dir2), "theme", "pixel.json"), JSON.stringify({ workspaceId: auth.workspaceId }, null, 2) + `
|
|
12603
12688
|
`);
|
|
12604
12689
|
if (opts.git !== false && view.githubRepo) {
|
|
12605
12690
|
const { token, repo } = await request(auth, "POST", `/api/sites/${siteId}/push-token`, {});
|
|
@@ -12640,14 +12725,14 @@ Next steps:
|
|
|
12640
12725
|
}
|
|
12641
12726
|
async function resolveSite(auth, slugOrDir) {
|
|
12642
12727
|
const fromDir = (dir2) => {
|
|
12643
|
-
const link =
|
|
12644
|
-
if (
|
|
12728
|
+
const link = join6(dir2, ".zalify", "site.json");
|
|
12729
|
+
if (existsSync6(link)) {
|
|
12645
12730
|
const parsed = JSON.parse(readFileSync5(link, "utf8"));
|
|
12646
12731
|
if (parsed.slug)
|
|
12647
12732
|
return parsed.slug;
|
|
12648
12733
|
}
|
|
12649
|
-
const storeJson =
|
|
12650
|
-
if (
|
|
12734
|
+
const storeJson = join6(dir2, "store.json");
|
|
12735
|
+
if (existsSync6(storeJson)) {
|
|
12651
12736
|
const parsed = JSON.parse(readFileSync5(storeJson, "utf8"));
|
|
12652
12737
|
if (parsed.slug)
|
|
12653
12738
|
return parsed.slug;
|
|
@@ -12655,7 +12740,7 @@ async function resolveSite(auth, slugOrDir) {
|
|
|
12655
12740
|
return null;
|
|
12656
12741
|
};
|
|
12657
12742
|
let slug = null;
|
|
12658
|
-
if (slugOrDir &&
|
|
12743
|
+
if (slugOrDir && existsSync6(resolve4(slugOrDir)))
|
|
12659
12744
|
slug = fromDir(resolve4(slugOrDir));
|
|
12660
12745
|
if (!slug && slugOrDir)
|
|
12661
12746
|
slug = slugOrDir;
|
|
@@ -12716,8 +12801,8 @@ async function maybeRefreshShops(auth, brandDir, siteSlugOverride) {
|
|
|
12716
12801
|
try {
|
|
12717
12802
|
let slug = siteSlugOverride ?? null;
|
|
12718
12803
|
if (!slug) {
|
|
12719
|
-
const storeJson =
|
|
12720
|
-
if (
|
|
12804
|
+
const storeJson = join6(resolve4(brandDir), "store.json");
|
|
12805
|
+
if (existsSync6(storeJson)) {
|
|
12721
12806
|
slug = JSON.parse(readFileSync5(storeJson, "utf8")).slug ?? null;
|
|
12722
12807
|
}
|
|
12723
12808
|
}
|
|
@@ -12772,7 +12857,7 @@ async function gql(auth, query, variables = {}) {
|
|
|
12772
12857
|
return json.data;
|
|
12773
12858
|
}
|
|
12774
12859
|
function readJson(path9, label) {
|
|
12775
|
-
if (!
|
|
12860
|
+
if (!existsSync7(path9)) {
|
|
12776
12861
|
throw new Error(`No ${label} at ${path9}`);
|
|
12777
12862
|
}
|
|
12778
12863
|
return JSON.parse(readFileSync6(path9, "utf8"));
|
|
@@ -12802,7 +12887,7 @@ async function findProductId(auth, handle) {
|
|
|
12802
12887
|
async function shopifyImport(storeDir, options = {}) {
|
|
12803
12888
|
const auth = requireActive();
|
|
12804
12889
|
const dir2 = resolve5(storeDir);
|
|
12805
|
-
const catalog = readJson(
|
|
12890
|
+
const catalog = readJson(join7(dir2, "catalog.json"), "catalog.json");
|
|
12806
12891
|
const { locations } = await gql(auth, `{ locations(first: 1) { nodes { id name } } }`);
|
|
12807
12892
|
const locationId = locations.nodes[0]?.id;
|
|
12808
12893
|
console.log(`Importing ${catalog.products.length} product(s) into the Shopify store connected to ` + `workspace "${auth.workspaceName}" (location: ${locations.nodes[0]?.name ?? "none"})`);
|
|
@@ -13011,13 +13096,13 @@ async function shopifyImport(storeDir, options = {}) {
|
|
|
13011
13096
|
async function shopifyUploadImages(storeDir, options = {}) {
|
|
13012
13097
|
const auth = requireActive();
|
|
13013
13098
|
const dir2 = resolve5(storeDir);
|
|
13014
|
-
const manifest = readJson(
|
|
13099
|
+
const manifest = readJson(join7(dir2, "images", "manifest.json"), "images/manifest.json");
|
|
13015
13100
|
console.log(`Uploading images to the Shopify store connected to workspace "${auth.workspaceName}"`);
|
|
13016
13101
|
for (const entry of manifest.images) {
|
|
13017
13102
|
if (entry.type === "banner" || !entry.handle)
|
|
13018
13103
|
continue;
|
|
13019
|
-
const filePath =
|
|
13020
|
-
if (!
|
|
13104
|
+
const filePath = join7(dir2, "images", entry.file);
|
|
13105
|
+
if (!existsSync7(filePath)) {
|
|
13021
13106
|
console.log(` - ${entry.handle}: ${entry.file} not generated yet, skipped`);
|
|
13022
13107
|
continue;
|
|
13023
13108
|
}
|
|
@@ -13092,7 +13177,7 @@ async function shopifyUploadImages(storeDir, options = {}) {
|
|
|
13092
13177
|
// src/ads.ts
|
|
13093
13178
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
13094
13179
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
13095
|
-
import { basename as basename4, join as
|
|
13180
|
+
import { basename as basename4, join as join8, resolve as resolve6 } from "node:path";
|
|
13096
13181
|
function apiError3(status, json) {
|
|
13097
13182
|
if (json.code === "SLUG_TAKEN") {
|
|
13098
13183
|
return new Error(`${json.error}
|
|
@@ -13144,8 +13229,8 @@ async function pollUntilReady2(auth, id) {
|
|
|
13144
13229
|
function scaffoldStarter(dir2, slug, workspaceId) {
|
|
13145
13230
|
const root = resolve6(dir2);
|
|
13146
13231
|
const write = (rel, content) => {
|
|
13147
|
-
const abs =
|
|
13148
|
-
mkdirSync3(
|
|
13232
|
+
const abs = join8(root, rel);
|
|
13233
|
+
mkdirSync3(join8(abs, ".."), { recursive: true });
|
|
13149
13234
|
writeFileSync7(abs, content);
|
|
13150
13235
|
};
|
|
13151
13236
|
write("identity.yaml", `# Brand instance identity — which workspace and which platform accounts
|
|
@@ -13202,8 +13287,8 @@ async function adsCreate(dir2) {
|
|
|
13202
13287
|
const view = await pollUntilReady2(auth, created.repoId);
|
|
13203
13288
|
console.log(` ✓ provisioned: ${view.githubRepo}`);
|
|
13204
13289
|
scaffoldStarter(dir2, slug, auth.workspaceId);
|
|
13205
|
-
mkdirSync3(
|
|
13206
|
-
writeFileSync7(
|
|
13290
|
+
mkdirSync3(join8(resolve6(dir2), ".zalify"), { recursive: true });
|
|
13291
|
+
writeFileSync7(join8(resolve6(dir2), ".zalify", "brand-repo.json"), JSON.stringify({ id: created.repoId, slug: view.slug, repo: view.githubRepo }, null, 2) + `
|
|
13207
13292
|
`);
|
|
13208
13293
|
if (view.githubRepo) {
|
|
13209
13294
|
const { token, repo } = await request2(auth, "POST", `/api/repos/${created.repoId}/push-token`, {});
|
|
@@ -13241,8 +13326,8 @@ async function adsList() {
|
|
|
13241
13326
|
}
|
|
13242
13327
|
|
|
13243
13328
|
// src/brand.ts
|
|
13244
|
-
import { existsSync as
|
|
13245
|
-
import { basename as basename5, join as
|
|
13329
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
13330
|
+
import { basename as basename5, join as join9, resolve as resolve7 } from "node:path";
|
|
13246
13331
|
var AUTHORING_MD = `# Brand authoring guide
|
|
13247
13332
|
|
|
13248
13333
|
The rules for filling in this folder. An author — human or AI agent —
|
|
@@ -13545,17 +13630,17 @@ var MANIFEST_JSON = JSON.stringify({
|
|
|
13545
13630
|
function brandInit(dir2 = ".", opts = {}) {
|
|
13546
13631
|
const target = resolve7(dir2);
|
|
13547
13632
|
const slug = basename5(target).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
13548
|
-
if (
|
|
13633
|
+
if (existsSync8(target) && readdirSync3(target).length > 0) {
|
|
13549
13634
|
throw new Error(`${dir2} already exists and is not empty`);
|
|
13550
13635
|
}
|
|
13551
13636
|
const name = opts.name ?? slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
13552
13637
|
const currency = (opts.currency ?? "USD").toUpperCase();
|
|
13553
|
-
mkdirSync4(
|
|
13554
|
-
writeFileSync8(
|
|
13555
|
-
writeFileSync8(
|
|
13556
|
-
writeFileSync8(
|
|
13557
|
-
writeFileSync8(
|
|
13558
|
-
writeFileSync8(
|
|
13638
|
+
mkdirSync4(join9(target, "images"), { recursive: true });
|
|
13639
|
+
writeFileSync8(join9(target, "AUTHORING.md"), AUTHORING_MD);
|
|
13640
|
+
writeFileSync8(join9(target, "brand.md"), BRAND_MD(name));
|
|
13641
|
+
writeFileSync8(join9(target, "store.json"), STORE_JSON(name, slug, opts.vertical ?? "<vertical>", currency));
|
|
13642
|
+
writeFileSync8(join9(target, "catalog.json"), CATALOG_JSON(name, currency));
|
|
13643
|
+
writeFileSync8(join9(target, "images", "manifest.json"), MANIFEST_JSON);
|
|
13559
13644
|
console.log(`Scaffolded brand data in ${dir2}:`);
|
|
13560
13645
|
for (const f of ["AUTHORING.md", "brand.md", "store.json", "catalog.json", "images/manifest.json"]) {
|
|
13561
13646
|
console.log(` + ${f}`);
|
|
@@ -13570,8 +13655,8 @@ function brandValidate(dir2 = ".") {
|
|
|
13570
13655
|
const problems = [];
|
|
13571
13656
|
const warnings = [];
|
|
13572
13657
|
const readJson2 = (rel) => {
|
|
13573
|
-
const path9 =
|
|
13574
|
-
if (!
|
|
13658
|
+
const path9 = join9(target, rel);
|
|
13659
|
+
if (!existsSync8(path9)) {
|
|
13575
13660
|
problems.push(`missing ${rel}`);
|
|
13576
13661
|
return null;
|
|
13577
13662
|
}
|
|
@@ -13582,7 +13667,7 @@ function brandValidate(dir2 = ".") {
|
|
|
13582
13667
|
return null;
|
|
13583
13668
|
}
|
|
13584
13669
|
};
|
|
13585
|
-
if (!
|
|
13670
|
+
if (!existsSync8(join9(target, "brand.md")))
|
|
13586
13671
|
problems.push("missing brand.md");
|
|
13587
13672
|
const store = readJson2("store.json");
|
|
13588
13673
|
if (store && (!store.name || !store.slug))
|
|
@@ -13758,7 +13843,7 @@ function brandValidate(dir2 = ".") {
|
|
|
13758
13843
|
// src/cli.ts
|
|
13759
13844
|
var __dirname4 = dirname2(fileURLToPath3(import.meta.url));
|
|
13760
13845
|
var require2 = createRequire2(import.meta.url);
|
|
13761
|
-
var pkg = require2(
|
|
13846
|
+
var pkg = require2(join10(__dirname4, "..", "package.json"));
|
|
13762
13847
|
try {
|
|
13763
13848
|
updateNotifier({ pkg }).notify({
|
|
13764
13849
|
isGlobal: true,
|
|
@@ -13847,7 +13932,9 @@ theme.command("status [dir]").description("Which theme files have you edited? (c
|
|
|
13847
13932
|
theme.command("upgrade [dir]").description("Move to a newer theme version: untouched files overwritten, edited files 3-way-merged, theme/ + .env never touched").option("--to <version>", "target theme 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) => {
|
|
13848
13933
|
await themeUpgrade(dir2, options);
|
|
13849
13934
|
});
|
|
13850
|
-
program2.command("self-update").description("Update the CLI to the latest version (auto-detects pnpm/bun/npm)").action(() =>
|
|
13935
|
+
program2.command("self-update").description("Update the CLI to the latest version (auto-detects pnpm/bun/npm)").action(async () => {
|
|
13936
|
+
await selfUpdate(pkg.version);
|
|
13937
|
+
});
|
|
13851
13938
|
program2.command("version").description("Print Zalify CLI version and check for updates").action(async () => {
|
|
13852
13939
|
console.log(`Zalify CLI version: ${pkg.version}`);
|
|
13853
13940
|
const isNewer = (a, b) => {
|