@zalify/cli 0.17.0 → 0.17.2
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 +192 -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,123 @@ 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: (spec) => ["add", "-g", spec],
|
|
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: (spec) => ["add", "-g", spec],
|
|
11787
|
+
remove: `bun remove -g ${PACKAGES.join(" ")}`
|
|
11788
|
+
};
|
|
11776
11789
|
}
|
|
11777
|
-
return {
|
|
11790
|
+
return {
|
|
11791
|
+
cmd: "npm",
|
|
11792
|
+
update: (spec) => ["install", "-g", spec],
|
|
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
|
+
}
|
|
11808
|
+
}
|
|
11809
|
+
return hits;
|
|
11778
11810
|
}
|
|
11779
|
-
function
|
|
11780
|
-
const
|
|
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;
|
|
11816
|
+
}
|
|
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);
|
|
11822
|
+
let latest = null;
|
|
11823
|
+
try {
|
|
11824
|
+
const info = await updateNotifier({
|
|
11825
|
+
pkg: { name: "@zalify/cli", version: currentVersion }
|
|
11826
|
+
}).fetchInfo();
|
|
11827
|
+
latest = info?.latest ?? null;
|
|
11828
|
+
} catch {}
|
|
11829
|
+
const target = latest ? `${pkg}@${latest}` : `${pkg}@latest`;
|
|
11781
11830
|
console.log(`Current version: ${currentVersion}`);
|
|
11782
|
-
console.log(`Updating via: ${cmd} ${
|
|
11831
|
+
console.log(`Updating via: ${manager.cmd} ${manager.update(target).join(" ")}
|
|
11783
11832
|
`);
|
|
11784
|
-
const result = spawnSync(cmd,
|
|
11833
|
+
const result = spawnSync(manager.cmd, manager.update(target), {
|
|
11834
|
+
stdio: "inherit"
|
|
11835
|
+
});
|
|
11785
11836
|
if (result.error || result.status !== 0) {
|
|
11786
11837
|
console.error(`
|
|
11787
11838
|
Update failed${result.error ? `: ${result.error.message}` : ""}.` + `
|
|
11788
|
-
Try manually: ${cmd} ${
|
|
11839
|
+
Try manually: ${manager.cmd} ${manager.update(target).join(" ")}`);
|
|
11789
11840
|
process.exit(result.status ?? 1);
|
|
11790
11841
|
}
|
|
11791
|
-
const
|
|
11792
|
-
const
|
|
11793
|
-
|
|
11794
|
-
|
|
11795
|
-
|
|
11842
|
+
const hits = zalifysOnPath();
|
|
11843
|
+
const first = hits[0];
|
|
11844
|
+
const firstVersion = first ? versionOf(first) : null;
|
|
11845
|
+
const mayBeHashed = hits.length > 1 || first && real(first) !== running;
|
|
11846
|
+
if (!first || !firstVersion) {
|
|
11847
|
+
console.log(`
|
|
11848
|
+
✓ Done`);
|
|
11849
|
+
return;
|
|
11850
|
+
}
|
|
11851
|
+
const reachedLatest = latest ? firstVersion === latest : firstVersion !== currentVersion;
|
|
11852
|
+
if (reachedLatest) {
|
|
11853
|
+
console.log(firstVersion === currentVersion ? `
|
|
11854
|
+
✓ Already the latest (${firstVersion})` : `
|
|
11855
|
+
✓ Updated ${currentVersion} → ${firstVersion}`);
|
|
11856
|
+
if (mayBeHashed)
|
|
11857
|
+
console.log(`
|
|
11858
|
+
${HASH_HINT}`);
|
|
11859
|
+
return;
|
|
11860
|
+
}
|
|
11861
|
+
if (latest) {
|
|
11862
|
+
console.log(`
|
|
11863
|
+
✗ ${pkg} is now ${latest}, but your PATH still runs`);
|
|
11864
|
+
console.log(` ${first} (${firstVersion})`);
|
|
11865
|
+
if (hits.length > 1) {
|
|
11866
|
+
console.log(`
|
|
11867
|
+
Every zalify on your PATH:`);
|
|
11868
|
+
for (const hit of hits) {
|
|
11869
|
+
console.log(` ${hit} → ${versionOf(hit) ?? "unreadable"}`);
|
|
11870
|
+
}
|
|
11871
|
+
}
|
|
11872
|
+
console.log(`
|
|
11873
|
+
Remove the stale install (${managerFor(real(first)).remove}),`);
|
|
11874
|
+
console.log("then run `hash -r` or open a new terminal.");
|
|
11875
|
+
process.exit(1);
|
|
11876
|
+
}
|
|
11877
|
+
console.log(`
|
|
11878
|
+
✓ Done (version: ${firstVersion})`);
|
|
11879
|
+
if (mayBeHashed)
|
|
11880
|
+
console.log(`
|
|
11881
|
+
${HASH_HINT}`);
|
|
11796
11882
|
}
|
|
11797
11883
|
|
|
11798
11884
|
// src/workspace.ts
|
|
@@ -11867,8 +11953,8 @@ async function workspaceSet(slugOrId) {
|
|
|
11867
11953
|
}
|
|
11868
11954
|
|
|
11869
11955
|
// src/images.ts
|
|
11870
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as
|
|
11871
|
-
import { join as
|
|
11956
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync4, rmSync as rmSync2 } from "node:fs";
|
|
11957
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
11872
11958
|
var MAX_JOBS_PER_REQUEST = 40;
|
|
11873
11959
|
var POLL_INTERVAL_MS = 5000;
|
|
11874
11960
|
var POLL_TIMEOUT_MS = 30 * 60 * 1000;
|
|
@@ -11896,7 +11982,7 @@ async function saveProduced(imagesDir, indexPath, index, file2, asset) {
|
|
|
11896
11982
|
return false;
|
|
11897
11983
|
}
|
|
11898
11984
|
const bytes = Buffer.from(await img.arrayBuffer());
|
|
11899
|
-
writeFileSync4(
|
|
11985
|
+
writeFileSync4(join4(imagesDir, file2), bytes);
|
|
11900
11986
|
index[file2] = {
|
|
11901
11987
|
assetId: asset.id,
|
|
11902
11988
|
url: asset.url,
|
|
@@ -12033,17 +12119,17 @@ async function streamingGenerate(auth, manifest, missing, imagesDir, indexPath,
|
|
|
12033
12119
|
}
|
|
12034
12120
|
async function imagesGenerate(storeDir) {
|
|
12035
12121
|
const auth = requireActive();
|
|
12036
|
-
const imagesDir =
|
|
12037
|
-
const manifestPath =
|
|
12038
|
-
if (!
|
|
12122
|
+
const imagesDir = join4(resolve2(storeDir), "images");
|
|
12123
|
+
const manifestPath = join4(imagesDir, "manifest.json");
|
|
12124
|
+
if (!existsSync4(manifestPath)) {
|
|
12039
12125
|
throw new Error(`No images/manifest.json in ${storeDir}`);
|
|
12040
12126
|
}
|
|
12041
12127
|
const manifest = JSON.parse(readFileSync3(manifestPath, "utf8"));
|
|
12042
|
-
const indexPath =
|
|
12043
|
-
const index =
|
|
12044
|
-
const jobStatePath =
|
|
12128
|
+
const indexPath = join4(imagesDir, "assets.json");
|
|
12129
|
+
const index = existsSync4(indexPath) ? JSON.parse(readFileSync3(indexPath, "utf8")) : {};
|
|
12130
|
+
const jobStatePath = join4(imagesDir, ".imagegen-job.json");
|
|
12045
12131
|
const counters = { produced: 0, failed: 0 };
|
|
12046
|
-
if (
|
|
12132
|
+
if (existsSync4(jobStatePath)) {
|
|
12047
12133
|
const state = JSON.parse(readFileSync3(jobStatePath, "utf8"));
|
|
12048
12134
|
console.log(`Resuming job ${state.jobId}`);
|
|
12049
12135
|
await pollJob(auth, state.jobId, state.files, imagesDir, indexPath, index, counters);
|
|
@@ -12051,7 +12137,7 @@ async function imagesGenerate(storeDir) {
|
|
|
12051
12137
|
console.log(`Done: ${counters.produced} generated (already in the asset library), ${counters.failed} failed${counters.failed ? " — re-run to retry" : ""}.`);
|
|
12052
12138
|
return;
|
|
12053
12139
|
}
|
|
12054
|
-
const missing = manifest.images.filter((e) => !
|
|
12140
|
+
const missing = manifest.images.filter((e) => !existsSync4(join4(imagesDir, e.file)));
|
|
12055
12141
|
if (missing.length === 0) {
|
|
12056
12142
|
console.log("All manifest images already exist locally.");
|
|
12057
12143
|
return;
|
|
@@ -12075,20 +12161,20 @@ async function imagesGenerate(storeDir) {
|
|
|
12075
12161
|
}
|
|
12076
12162
|
|
|
12077
12163
|
// src/shopify.ts
|
|
12078
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
12079
|
-
import { basename as basename3, join as
|
|
12164
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "node:fs";
|
|
12165
|
+
import { basename as basename3, join as join7, resolve as resolve5 } from "node:path";
|
|
12080
12166
|
|
|
12081
12167
|
// src/shop.ts
|
|
12082
12168
|
import { spawn as spawn3, spawnSync as spawnSync3 } from "node:child_process";
|
|
12083
|
-
import { existsSync as
|
|
12084
|
-
import { basename as basename2, join as
|
|
12169
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
12170
|
+
import { basename as basename2, join as join6, resolve as resolve4 } from "node:path";
|
|
12085
12171
|
|
|
12086
12172
|
// src/theme.ts
|
|
12087
12173
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12088
12174
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
12089
12175
|
import {
|
|
12090
12176
|
cpSync,
|
|
12091
|
-
existsSync as
|
|
12177
|
+
existsSync as existsSync5,
|
|
12092
12178
|
mkdirSync as mkdirSync2,
|
|
12093
12179
|
mkdtempSync,
|
|
12094
12180
|
readdirSync as readdirSync2,
|
|
@@ -12100,7 +12186,7 @@ import {
|
|
|
12100
12186
|
writeFileSync as writeFileSync5
|
|
12101
12187
|
} from "node:fs";
|
|
12102
12188
|
import { tmpdir } from "node:os";
|
|
12103
|
-
import { basename, dirname, join as
|
|
12189
|
+
import { basename, dirname, join as join5, resolve as resolve3 } from "node:path";
|
|
12104
12190
|
var TEMPLATES_PKG = "@zalify/theme-templates";
|
|
12105
12191
|
var REGISTRY = "https://registry.npmjs.org";
|
|
12106
12192
|
var MANIFEST_PATH = ".zalify/theme.json";
|
|
@@ -12126,27 +12212,27 @@ async function downloadTarball(url) {
|
|
|
12126
12212
|
const res = await fetch(url);
|
|
12127
12213
|
if (!res.ok)
|
|
12128
12214
|
throw new Error(`tarball download ${res.status}: ${url}`);
|
|
12129
|
-
const file2 =
|
|
12215
|
+
const file2 = join5(mkdtempSync(join5(tmpdir(), "zalify-theme-")), "pkg.tgz");
|
|
12130
12216
|
writeFileSync5(file2, Buffer.from(await res.arrayBuffer()));
|
|
12131
12217
|
return file2;
|
|
12132
12218
|
}
|
|
12133
12219
|
function extractTarball(file2) {
|
|
12134
|
-
const dir2 = mkdtempSync(
|
|
12220
|
+
const dir2 = mkdtempSync(join5(tmpdir(), "zalify-theme-x-"));
|
|
12135
12221
|
const result = spawnSync2("tar", ["-xzf", resolve3(file2), "-C", dir2], {
|
|
12136
12222
|
encoding: "utf8"
|
|
12137
12223
|
});
|
|
12138
12224
|
if (result.error || result.status !== 0) {
|
|
12139
12225
|
throw new Error(`tar extract failed for ${file2}: ${result.stderr || result.error?.message}`);
|
|
12140
12226
|
}
|
|
12141
|
-
const root =
|
|
12142
|
-
if (!
|
|
12227
|
+
const root = join5(dir2, "package");
|
|
12228
|
+
if (!existsSync5(root))
|
|
12143
12229
|
throw new Error(`unexpected tarball layout in ${file2} (no package/)`);
|
|
12144
12230
|
return root;
|
|
12145
12231
|
}
|
|
12146
12232
|
async function acquireTemplatePkg(localTarball, version) {
|
|
12147
12233
|
if (localTarball) {
|
|
12148
12234
|
const root = extractTarball(localTarball);
|
|
12149
|
-
const v = JSON.parse(readFileSync4(
|
|
12235
|
+
const v = JSON.parse(readFileSync4(join5(root, "package.json"), "utf8")).version;
|
|
12150
12236
|
return { root, version: v };
|
|
12151
12237
|
}
|
|
12152
12238
|
const packument = await fetchPackument();
|
|
@@ -12157,8 +12243,8 @@ async function acquireTemplatePkg(localTarball, version) {
|
|
|
12157
12243
|
return { root: extractTarball(await downloadTarball(entry.dist.tarball)), version: target };
|
|
12158
12244
|
}
|
|
12159
12245
|
function readTemplateManifest(pkgRoot, template) {
|
|
12160
|
-
const path9 =
|
|
12161
|
-
if (!
|
|
12246
|
+
const path9 = join5(pkgRoot, "templates", template, MANIFEST_PATH);
|
|
12247
|
+
if (!existsSync5(path9)) {
|
|
12162
12248
|
throw new Error(`template "${template}" has no ${MANIFEST_PATH} — this ${TEMPLATES_PKG} version predates the upgrade contract`);
|
|
12163
12249
|
}
|
|
12164
12250
|
return JSON.parse(readFileSync4(path9, "utf8"));
|
|
@@ -12175,21 +12261,21 @@ function templateFilePath(templateDir, rel, variant) {
|
|
|
12175
12261
|
const stashed = rel.replace(/(^|\/)\.gitignore$/, "$1gitignore");
|
|
12176
12262
|
if (variant === "editor") {
|
|
12177
12263
|
for (const candidate of [rel, stashed]) {
|
|
12178
|
-
const overlay =
|
|
12179
|
-
if (
|
|
12264
|
+
const overlay = join5(templateDir, "__editor__", candidate);
|
|
12265
|
+
if (existsSync5(overlay))
|
|
12180
12266
|
return overlay;
|
|
12181
12267
|
}
|
|
12182
12268
|
}
|
|
12183
12269
|
for (const candidate of [rel, stashed]) {
|
|
12184
|
-
const path9 =
|
|
12185
|
-
if (
|
|
12270
|
+
const path9 = join5(templateDir, candidate);
|
|
12271
|
+
if (existsSync5(path9))
|
|
12186
12272
|
return path9;
|
|
12187
12273
|
}
|
|
12188
12274
|
throw new Error(`file listed in manifest but missing from template: ${rel}`);
|
|
12189
12275
|
}
|
|
12190
12276
|
function readProjectManifest(dir2) {
|
|
12191
|
-
const path9 =
|
|
12192
|
-
if (!
|
|
12277
|
+
const path9 = join5(dir2, MANIFEST_PATH);
|
|
12278
|
+
if (!existsSync5(path9)) {
|
|
12193
12279
|
throw new Error(`no ${MANIFEST_PATH} in ${dir2} — not a scaffolded Zalify storefront.
|
|
12194
12280
|
` + ` Scaffold one with \`zalify theme create\`.`);
|
|
12195
12281
|
}
|
|
@@ -12201,22 +12287,22 @@ function writeJson(path9, value) {
|
|
|
12201
12287
|
}
|
|
12202
12288
|
async function themeCreate(dir2, opts) {
|
|
12203
12289
|
const targetDir = resolve3(dir2);
|
|
12204
|
-
if (
|
|
12290
|
+
if (existsSync5(targetDir) && readdirSync2(targetDir).length > 0) {
|
|
12205
12291
|
throw new Error(`${dir2} already exists and is not empty`);
|
|
12206
12292
|
}
|
|
12207
12293
|
const { root, version } = await acquireTemplatePkg(opts.tarball, opts.to);
|
|
12208
|
-
const frameworks = JSON.parse(readFileSync4(
|
|
12294
|
+
const frameworks = JSON.parse(readFileSync4(join5(root, "templates.json"), "utf8"));
|
|
12209
12295
|
const template = opts.template ?? "nextjs";
|
|
12210
12296
|
const meta = frameworks.frameworks[template];
|
|
12211
12297
|
if (!meta) {
|
|
12212
12298
|
throw new Error(`unknown template "${template}" — expected one of: ${Object.keys(frameworks.frameworks).join(", ")}`);
|
|
12213
12299
|
}
|
|
12214
|
-
const templateDir =
|
|
12300
|
+
const templateDir = join5(root, "templates", template);
|
|
12215
12301
|
console.log(`Scaffolding ${meta.label} (${TEMPLATES_PKG}@${version}) into ${dir2}`);
|
|
12216
12302
|
mkdirSync2(targetDir, { recursive: true });
|
|
12217
12303
|
cpSync(templateDir, targetDir, { recursive: true });
|
|
12218
|
-
const overlayDir =
|
|
12219
|
-
const useEditor = Boolean(opts.editor) &&
|
|
12304
|
+
const overlayDir = join5(targetDir, "__editor__");
|
|
12305
|
+
const useEditor = Boolean(opts.editor) && existsSync5(overlayDir);
|
|
12220
12306
|
if (opts.editor && !useEditor) {
|
|
12221
12307
|
console.log(` ! --editor has no effect for ${template} — no editor overlay in this template`);
|
|
12222
12308
|
}
|
|
@@ -12225,14 +12311,14 @@ async function themeCreate(dir2, opts) {
|
|
|
12225
12311
|
rmSync3(overlayDir, { recursive: true, force: true });
|
|
12226
12312
|
(function restoreGitignores(d) {
|
|
12227
12313
|
for (const entry of readdirSync2(d)) {
|
|
12228
|
-
const full =
|
|
12314
|
+
const full = join5(d, entry);
|
|
12229
12315
|
if (statSync(full).isDirectory())
|
|
12230
12316
|
restoreGitignores(full);
|
|
12231
12317
|
else if (entry === "gitignore")
|
|
12232
|
-
renameSync(full,
|
|
12318
|
+
renameSync(full, join5(d, ".gitignore"));
|
|
12233
12319
|
}
|
|
12234
12320
|
})(targetDir);
|
|
12235
|
-
const pkgPath =
|
|
12321
|
+
const pkgPath = join5(targetDir, "package.json");
|
|
12236
12322
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
12237
12323
|
pkg.name = basename(targetDir).toLowerCase().replaceAll(/[^a-z0-9-_.]+/g, "-").replaceAll(/^[-.]+|[-.]+$/g, "") || "zalify-storefront";
|
|
12238
12324
|
writeJson(pkgPath, pkg);
|
|
@@ -12246,12 +12332,12 @@ async function themeCreate(dir2, opts) {
|
|
|
12246
12332
|
if (meta.env.sessionSecret) {
|
|
12247
12333
|
lines.push("# Session cookie signing key — unique to this project.", `${meta.env.sessionSecret}=${randomBytes2(32).toString("hex")}`);
|
|
12248
12334
|
}
|
|
12249
|
-
writeFileSync5(
|
|
12335
|
+
writeFileSync5(join5(targetDir, ".env"), lines.join(`
|
|
12250
12336
|
`) + `
|
|
12251
12337
|
`);
|
|
12252
12338
|
}
|
|
12253
12339
|
const manifest = readProjectManifest(targetDir);
|
|
12254
|
-
writeJson(
|
|
12340
|
+
writeJson(join5(targetDir, MANIFEST_PATH), resolveVariant(manifest, useEditor ? "editor" : "default"));
|
|
12255
12341
|
if (opts.git !== false) {
|
|
12256
12342
|
const git = (args) => spawnSync2("git", args, { cwd: targetDir, stdio: "ignore" });
|
|
12257
12343
|
if (git(["init", "-b", "main"]).status === 0) {
|
|
@@ -12291,8 +12377,8 @@ function themeStatus(dir2 = ".") {
|
|
|
12291
12377
|
const edited = [];
|
|
12292
12378
|
const missing = [];
|
|
12293
12379
|
for (const [rel, expected] of Object.entries(manifest.files)) {
|
|
12294
|
-
const full =
|
|
12295
|
-
if (!
|
|
12380
|
+
const full = join5(projectDir, rel);
|
|
12381
|
+
if (!existsSync5(full))
|
|
12296
12382
|
missing.push(rel);
|
|
12297
12383
|
else if (sha256(readFileSync4(full)) !== expected)
|
|
12298
12384
|
edited.push(rel);
|
|
@@ -12326,8 +12412,8 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
|
12326
12412
|
}
|
|
12327
12413
|
const oldManifest = resolveVariant(readTemplateManifest(base.root, local.template), variant);
|
|
12328
12414
|
const newManifest = resolveVariant(readTemplateManifest(target.root, local.template), variant);
|
|
12329
|
-
const oldDir =
|
|
12330
|
-
const newDir =
|
|
12415
|
+
const oldDir = join5(base.root, "templates", local.template);
|
|
12416
|
+
const newDir = join5(target.root, "templates", local.template);
|
|
12331
12417
|
console.log(`Upgrading ${local.template} theme ${local.version} → ${target.version}` + (opts.dryRun ? " (dry run)" : ""));
|
|
12332
12418
|
const counts = {
|
|
12333
12419
|
overwrite: 0,
|
|
@@ -12345,8 +12431,8 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
|
12345
12431
|
continue;
|
|
12346
12432
|
const oldHash = oldManifest.files[rel];
|
|
12347
12433
|
const newHash = newManifest.files[rel];
|
|
12348
|
-
const localPath =
|
|
12349
|
-
const localExists =
|
|
12434
|
+
const localPath = join5(projectDir, rel);
|
|
12435
|
+
const localExists = existsSync5(localPath);
|
|
12350
12436
|
const localBytes = localExists ? readFileSync4(localPath) : null;
|
|
12351
12437
|
const localHash = localBytes ? sha256(localBytes) : null;
|
|
12352
12438
|
const write = (bytes) => {
|
|
@@ -12385,9 +12471,9 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
|
12385
12471
|
console.log(` ~ would merge ${rel}`);
|
|
12386
12472
|
continue;
|
|
12387
12473
|
}
|
|
12388
|
-
const scratch = mkdtempSync(
|
|
12389
|
-
const basePath =
|
|
12390
|
-
const newPath =
|
|
12474
|
+
const scratch = mkdtempSync(join5(tmpdir(), "zalify-merge-"));
|
|
12475
|
+
const basePath = join5(scratch, "base");
|
|
12476
|
+
const newPath = join5(scratch, "new");
|
|
12391
12477
|
writeFileSync5(basePath, oldBytes);
|
|
12392
12478
|
writeFileSync5(newPath, newBytes);
|
|
12393
12479
|
const result = spawnSync2("git", ["merge-file", "-L", "current", "-L", `base (${base.version})`, "-L", `new (${target.version})`, localPath, basePath, newPath], { encoding: "utf8" });
|
|
@@ -12431,7 +12517,7 @@ async function themeUpgrade(dir2 = ".", opts = {}) {
|
|
|
12431
12517
|
}
|
|
12432
12518
|
const depsChanged = mergePackageJson(projectDir, oldDir, newDir, opts.dryRun ?? false);
|
|
12433
12519
|
if (!opts.dryRun) {
|
|
12434
|
-
writeJson(
|
|
12520
|
+
writeJson(join5(projectDir, MANIFEST_PATH), { ...newManifest, version: target.version });
|
|
12435
12521
|
}
|
|
12436
12522
|
const summary = Object.entries(counts).filter(([, n]) => n > 0).map(([k, n]) => `${n} ${k.replace("-", " ")}`).join(", ");
|
|
12437
12523
|
console.log(`
|
|
@@ -12452,7 +12538,7 @@ ${opts.dryRun ? "Would apply" : "Done"}: ${summary || "no file changes"}.`);
|
|
|
12452
12538
|
}
|
|
12453
12539
|
}
|
|
12454
12540
|
function mergePackageJson(projectDir, oldDir, newDir, dryRun) {
|
|
12455
|
-
const read = (p) => JSON.parse(readFileSync4(
|
|
12541
|
+
const read = (p) => JSON.parse(readFileSync4(join5(p, "package.json"), "utf8"));
|
|
12456
12542
|
const localPkg = read(projectDir);
|
|
12457
12543
|
const oldPkg = read(oldDir);
|
|
12458
12544
|
const newPkg = read(newDir);
|
|
@@ -12501,7 +12587,7 @@ function mergePackageJson(projectDir, oldDir, newDir, dryRun) {
|
|
|
12501
12587
|
console.log(` ✓ package.json ${key} updated`);
|
|
12502
12588
|
}
|
|
12503
12589
|
if (changed && !dryRun) {
|
|
12504
|
-
writeFileSync5(
|
|
12590
|
+
writeFileSync5(join5(projectDir, "package.json"), JSON.stringify(localPkg, null, 2) + `
|
|
12505
12591
|
`);
|
|
12506
12592
|
}
|
|
12507
12593
|
return depsChanged;
|
|
@@ -12597,9 +12683,9 @@ async function shopCreate(dir2, opts) {
|
|
|
12597
12683
|
install: opts.install,
|
|
12598
12684
|
git: opts.git
|
|
12599
12685
|
});
|
|
12600
|
-
writeFileSync6(
|
|
12686
|
+
writeFileSync6(join6(resolve4(dir2), ".zalify", "site.json"), JSON.stringify({ id: siteId, slug: view.slug, domain: view.domain, repo: view.githubRepo }, null, 2) + `
|
|
12601
12687
|
`);
|
|
12602
|
-
writeFileSync6(
|
|
12688
|
+
writeFileSync6(join6(resolve4(dir2), "theme", "pixel.json"), JSON.stringify({ workspaceId: auth.workspaceId }, null, 2) + `
|
|
12603
12689
|
`);
|
|
12604
12690
|
if (opts.git !== false && view.githubRepo) {
|
|
12605
12691
|
const { token, repo } = await request(auth, "POST", `/api/sites/${siteId}/push-token`, {});
|
|
@@ -12640,14 +12726,14 @@ Next steps:
|
|
|
12640
12726
|
}
|
|
12641
12727
|
async function resolveSite(auth, slugOrDir) {
|
|
12642
12728
|
const fromDir = (dir2) => {
|
|
12643
|
-
const link =
|
|
12644
|
-
if (
|
|
12729
|
+
const link = join6(dir2, ".zalify", "site.json");
|
|
12730
|
+
if (existsSync6(link)) {
|
|
12645
12731
|
const parsed = JSON.parse(readFileSync5(link, "utf8"));
|
|
12646
12732
|
if (parsed.slug)
|
|
12647
12733
|
return parsed.slug;
|
|
12648
12734
|
}
|
|
12649
|
-
const storeJson =
|
|
12650
|
-
if (
|
|
12735
|
+
const storeJson = join6(dir2, "store.json");
|
|
12736
|
+
if (existsSync6(storeJson)) {
|
|
12651
12737
|
const parsed = JSON.parse(readFileSync5(storeJson, "utf8"));
|
|
12652
12738
|
if (parsed.slug)
|
|
12653
12739
|
return parsed.slug;
|
|
@@ -12655,7 +12741,7 @@ async function resolveSite(auth, slugOrDir) {
|
|
|
12655
12741
|
return null;
|
|
12656
12742
|
};
|
|
12657
12743
|
let slug = null;
|
|
12658
|
-
if (slugOrDir &&
|
|
12744
|
+
if (slugOrDir && existsSync6(resolve4(slugOrDir)))
|
|
12659
12745
|
slug = fromDir(resolve4(slugOrDir));
|
|
12660
12746
|
if (!slug && slugOrDir)
|
|
12661
12747
|
slug = slugOrDir;
|
|
@@ -12716,8 +12802,8 @@ async function maybeRefreshShops(auth, brandDir, siteSlugOverride) {
|
|
|
12716
12802
|
try {
|
|
12717
12803
|
let slug = siteSlugOverride ?? null;
|
|
12718
12804
|
if (!slug) {
|
|
12719
|
-
const storeJson =
|
|
12720
|
-
if (
|
|
12805
|
+
const storeJson = join6(resolve4(brandDir), "store.json");
|
|
12806
|
+
if (existsSync6(storeJson)) {
|
|
12721
12807
|
slug = JSON.parse(readFileSync5(storeJson, "utf8")).slug ?? null;
|
|
12722
12808
|
}
|
|
12723
12809
|
}
|
|
@@ -12772,7 +12858,7 @@ async function gql(auth, query, variables = {}) {
|
|
|
12772
12858
|
return json.data;
|
|
12773
12859
|
}
|
|
12774
12860
|
function readJson(path9, label) {
|
|
12775
|
-
if (!
|
|
12861
|
+
if (!existsSync7(path9)) {
|
|
12776
12862
|
throw new Error(`No ${label} at ${path9}`);
|
|
12777
12863
|
}
|
|
12778
12864
|
return JSON.parse(readFileSync6(path9, "utf8"));
|
|
@@ -12802,7 +12888,7 @@ async function findProductId(auth, handle) {
|
|
|
12802
12888
|
async function shopifyImport(storeDir, options = {}) {
|
|
12803
12889
|
const auth = requireActive();
|
|
12804
12890
|
const dir2 = resolve5(storeDir);
|
|
12805
|
-
const catalog = readJson(
|
|
12891
|
+
const catalog = readJson(join7(dir2, "catalog.json"), "catalog.json");
|
|
12806
12892
|
const { locations } = await gql(auth, `{ locations(first: 1) { nodes { id name } } }`);
|
|
12807
12893
|
const locationId = locations.nodes[0]?.id;
|
|
12808
12894
|
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 +13097,13 @@ async function shopifyImport(storeDir, options = {}) {
|
|
|
13011
13097
|
async function shopifyUploadImages(storeDir, options = {}) {
|
|
13012
13098
|
const auth = requireActive();
|
|
13013
13099
|
const dir2 = resolve5(storeDir);
|
|
13014
|
-
const manifest = readJson(
|
|
13100
|
+
const manifest = readJson(join7(dir2, "images", "manifest.json"), "images/manifest.json");
|
|
13015
13101
|
console.log(`Uploading images to the Shopify store connected to workspace "${auth.workspaceName}"`);
|
|
13016
13102
|
for (const entry of manifest.images) {
|
|
13017
13103
|
if (entry.type === "banner" || !entry.handle)
|
|
13018
13104
|
continue;
|
|
13019
|
-
const filePath =
|
|
13020
|
-
if (!
|
|
13105
|
+
const filePath = join7(dir2, "images", entry.file);
|
|
13106
|
+
if (!existsSync7(filePath)) {
|
|
13021
13107
|
console.log(` - ${entry.handle}: ${entry.file} not generated yet, skipped`);
|
|
13022
13108
|
continue;
|
|
13023
13109
|
}
|
|
@@ -13092,7 +13178,7 @@ async function shopifyUploadImages(storeDir, options = {}) {
|
|
|
13092
13178
|
// src/ads.ts
|
|
13093
13179
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
13094
13180
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync7 } from "node:fs";
|
|
13095
|
-
import { basename as basename4, join as
|
|
13181
|
+
import { basename as basename4, join as join8, resolve as resolve6 } from "node:path";
|
|
13096
13182
|
function apiError3(status, json) {
|
|
13097
13183
|
if (json.code === "SLUG_TAKEN") {
|
|
13098
13184
|
return new Error(`${json.error}
|
|
@@ -13144,8 +13230,8 @@ async function pollUntilReady2(auth, id) {
|
|
|
13144
13230
|
function scaffoldStarter(dir2, slug, workspaceId) {
|
|
13145
13231
|
const root = resolve6(dir2);
|
|
13146
13232
|
const write = (rel, content) => {
|
|
13147
|
-
const abs =
|
|
13148
|
-
mkdirSync3(
|
|
13233
|
+
const abs = join8(root, rel);
|
|
13234
|
+
mkdirSync3(join8(abs, ".."), { recursive: true });
|
|
13149
13235
|
writeFileSync7(abs, content);
|
|
13150
13236
|
};
|
|
13151
13237
|
write("identity.yaml", `# Brand instance identity — which workspace and which platform accounts
|
|
@@ -13202,8 +13288,8 @@ async function adsCreate(dir2) {
|
|
|
13202
13288
|
const view = await pollUntilReady2(auth, created.repoId);
|
|
13203
13289
|
console.log(` ✓ provisioned: ${view.githubRepo}`);
|
|
13204
13290
|
scaffoldStarter(dir2, slug, auth.workspaceId);
|
|
13205
|
-
mkdirSync3(
|
|
13206
|
-
writeFileSync7(
|
|
13291
|
+
mkdirSync3(join8(resolve6(dir2), ".zalify"), { recursive: true });
|
|
13292
|
+
writeFileSync7(join8(resolve6(dir2), ".zalify", "brand-repo.json"), JSON.stringify({ id: created.repoId, slug: view.slug, repo: view.githubRepo }, null, 2) + `
|
|
13207
13293
|
`);
|
|
13208
13294
|
if (view.githubRepo) {
|
|
13209
13295
|
const { token, repo } = await request2(auth, "POST", `/api/repos/${created.repoId}/push-token`, {});
|
|
@@ -13241,8 +13327,8 @@ async function adsList() {
|
|
|
13241
13327
|
}
|
|
13242
13328
|
|
|
13243
13329
|
// src/brand.ts
|
|
13244
|
-
import { existsSync as
|
|
13245
|
-
import { basename as basename5, join as
|
|
13330
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
13331
|
+
import { basename as basename5, join as join9, resolve as resolve7 } from "node:path";
|
|
13246
13332
|
var AUTHORING_MD = `# Brand authoring guide
|
|
13247
13333
|
|
|
13248
13334
|
The rules for filling in this folder. An author — human or AI agent —
|
|
@@ -13545,17 +13631,17 @@ var MANIFEST_JSON = JSON.stringify({
|
|
|
13545
13631
|
function brandInit(dir2 = ".", opts = {}) {
|
|
13546
13632
|
const target = resolve7(dir2);
|
|
13547
13633
|
const slug = basename5(target).toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
13548
|
-
if (
|
|
13634
|
+
if (existsSync8(target) && readdirSync3(target).length > 0) {
|
|
13549
13635
|
throw new Error(`${dir2} already exists and is not empty`);
|
|
13550
13636
|
}
|
|
13551
13637
|
const name = opts.name ?? slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
13552
13638
|
const currency = (opts.currency ?? "USD").toUpperCase();
|
|
13553
|
-
mkdirSync4(
|
|
13554
|
-
writeFileSync8(
|
|
13555
|
-
writeFileSync8(
|
|
13556
|
-
writeFileSync8(
|
|
13557
|
-
writeFileSync8(
|
|
13558
|
-
writeFileSync8(
|
|
13639
|
+
mkdirSync4(join9(target, "images"), { recursive: true });
|
|
13640
|
+
writeFileSync8(join9(target, "AUTHORING.md"), AUTHORING_MD);
|
|
13641
|
+
writeFileSync8(join9(target, "brand.md"), BRAND_MD(name));
|
|
13642
|
+
writeFileSync8(join9(target, "store.json"), STORE_JSON(name, slug, opts.vertical ?? "<vertical>", currency));
|
|
13643
|
+
writeFileSync8(join9(target, "catalog.json"), CATALOG_JSON(name, currency));
|
|
13644
|
+
writeFileSync8(join9(target, "images", "manifest.json"), MANIFEST_JSON);
|
|
13559
13645
|
console.log(`Scaffolded brand data in ${dir2}:`);
|
|
13560
13646
|
for (const f of ["AUTHORING.md", "brand.md", "store.json", "catalog.json", "images/manifest.json"]) {
|
|
13561
13647
|
console.log(` + ${f}`);
|
|
@@ -13570,8 +13656,8 @@ function brandValidate(dir2 = ".") {
|
|
|
13570
13656
|
const problems = [];
|
|
13571
13657
|
const warnings = [];
|
|
13572
13658
|
const readJson2 = (rel) => {
|
|
13573
|
-
const path9 =
|
|
13574
|
-
if (!
|
|
13659
|
+
const path9 = join9(target, rel);
|
|
13660
|
+
if (!existsSync8(path9)) {
|
|
13575
13661
|
problems.push(`missing ${rel}`);
|
|
13576
13662
|
return null;
|
|
13577
13663
|
}
|
|
@@ -13582,7 +13668,7 @@ function brandValidate(dir2 = ".") {
|
|
|
13582
13668
|
return null;
|
|
13583
13669
|
}
|
|
13584
13670
|
};
|
|
13585
|
-
if (!
|
|
13671
|
+
if (!existsSync8(join9(target, "brand.md")))
|
|
13586
13672
|
problems.push("missing brand.md");
|
|
13587
13673
|
const store = readJson2("store.json");
|
|
13588
13674
|
if (store && (!store.name || !store.slug))
|
|
@@ -13758,7 +13844,7 @@ function brandValidate(dir2 = ".") {
|
|
|
13758
13844
|
// src/cli.ts
|
|
13759
13845
|
var __dirname4 = dirname2(fileURLToPath3(import.meta.url));
|
|
13760
13846
|
var require2 = createRequire2(import.meta.url);
|
|
13761
|
-
var pkg = require2(
|
|
13847
|
+
var pkg = require2(join10(__dirname4, "..", "package.json"));
|
|
13762
13848
|
try {
|
|
13763
13849
|
updateNotifier({ pkg }).notify({
|
|
13764
13850
|
isGlobal: true,
|
|
@@ -13847,7 +13933,9 @@ theme.command("status [dir]").description("Which theme files have you edited? (c
|
|
|
13847
13933
|
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
13934
|
await themeUpgrade(dir2, options);
|
|
13849
13935
|
});
|
|
13850
|
-
program2.command("self-update").description("Update the CLI to the latest version (auto-detects pnpm/bun/npm)").action(() =>
|
|
13936
|
+
program2.command("self-update").description("Update the CLI to the latest version (auto-detects pnpm/bun/npm)").action(async () => {
|
|
13937
|
+
await selfUpdate(pkg.version);
|
|
13938
|
+
});
|
|
13851
13939
|
program2.command("version").description("Print Zalify CLI version and check for updates").action(async () => {
|
|
13852
13940
|
console.log(`Zalify CLI version: ${pkg.version}`);
|
|
13853
13941
|
const isNewer = (a, b) => {
|