@blocofy/cli 0.1.0 → 0.1.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 CHANGED
@@ -1,48 +1,65 @@
1
1
  # @blocofy/cli
2
2
 
3
- Blocofy tema geliştirme CLI — Shopify CLI modeli: local tema + **canlı veri** ile anında
4
- önizleme, sonra yayına alma. CLI **build almaz**; asset'leri kendi araçlarınla
5
- (npm/Vite/Tailwind) üretirsin, platform Liquid + statik asset olarak sunar.
3
+ Develop your [Blocofy](https://blocofy.com) theme locally against **live data**, see an
4
+ instant preview, and publish. The CLI does **not** build assets — generate them with your
5
+ own tools (npm/Vite/Tailwind); the platform serves plain Liquid + static assets.
6
6
 
7
7
  ```bash
8
- npx @blocofy/cli login # site URL + dev token (admin → Ayarlar → Tema CLI token'ları)
9
- cd path/to/tema
10
- npx @blocofy/cli theme dev # http://localhost:3030 — local tema + canlı veri, livereload
8
+ npx @blocofy/cli login # site URL + dev token (admin panel: Settings → Theme CLI tokens)
9
+ cd path/to/theme
10
+ npx @blocofy/cli theme dev # http://localhost:3030 — local theme + live data, livereload
11
11
  ```
12
12
 
13
- ## Komutlar
13
+ ## Commands
14
14
 
15
15
  ```bash
16
16
  blocofy login [--url <url>] [--token <bcf_…>]
17
- # Platform URL + dev token kaydet (~/.blocofy/credentials.json, 0600).
18
- blocofy theme dev [dir] # Local dev sunucusu: düzenle → tarayıcı otomatik yenilenir.
19
- # --port <n> (varsayılan 3030)
20
- blocofy theme pull [dir] # Canlı temayı diske indir.
21
- blocofy theme push [dir] # Local temayı canlı siteye yaz (create/update; silme yok).
17
+ # Save your platform URL + dev token (~/.blocofy/credentials.json, 0600).
18
+ blocofy theme dev [dir] # Local dev server: edit a file and the browser reloads automatically.
19
+ # --port <n> (default 3030)
20
+ blocofy theme pull [dir] # Download the live theme to disk.
21
+ blocofy theme push [dir] # Write the local theme to the live site (create/update; no delete).
22
22
  blocofy --version
23
23
  blocofy --help
24
24
  ```
25
25
 
26
- ## Nasıl çalışır
26
+ ## How it works
27
27
 
28
- `theme dev` kendi http sunucusunu açar; her sayfa isteğinde local tema dosyalarını okuyup
29
- platformun dev-render endpoint'ine (`/api/dev/render`) gönderir. Platform site'ın **canlı
30
- verisiyle** render edip HTML döner — yani CLI render motorunu içermez, üretimle birebir aynı
31
- çıktı görürsün. `fs.watch` ile dosya değişince tarayıcı SSE üzerinden yenilenir.
28
+ `theme dev` starts a local HTTP server. For each page request it reads your local theme
29
+ files and sends them to the platform's dev-render endpoint (`/api/dev/render`). The platform
30
+ renders them with the site's **live data** and returns HTML — so the CLI ships no rendering
31
+ engine and you see exactly the production output. A file watcher reloads the browser over
32
+ Server-Sent Events when you save.
32
33
 
33
- Kimlik bilgisi: `~/.blocofy/credentials.json` ya da `BLOCOFY_URL` + `BLOCOFY_TOKEN` ortam
34
- değişkenleri (CI/agent için).
34
+ Credentials come from `~/.blocofy/credentials.json` (written by `blocofy login`) or the
35
+ `BLOCOFY_URL` + `BLOCOFY_TOKEN` environment variables (for CI/automation).
35
36
 
36
- ## Geliştirme
37
+ ## Theme structure
37
38
 
38
- Zero-dependency (yalnız Node yerleşikleri). Test:
39
+ A theme is a directory of files grouped by top-level folder:
40
+
41
+ | Folder | Contents |
42
+ | --- | --- |
43
+ | `layout/` | Page shell (`theme.liquid`) |
44
+ | `section/` | Page sections (`Hero.liquid`, `FeaturedCards.liquid`, …) |
45
+ | `block/` | Repeatable pieces used inside sections |
46
+ | `partial/` | Shared snippets (header/footer, …) |
47
+ | `asset/` | CSS and static files (`theme.css`) |
48
+ | `pages/<slug>.json` | Page content (`/` → `pages/index.json`) |
49
+ | `config/settings.json` | Theme settings + color schemes |
50
+
51
+ Liquid templates use the `.liquid` extension; files under `asset/` are served as-is.
52
+
53
+ ## Development
54
+
55
+ Zero runtime dependencies (Node built-ins only). Run the tests with:
39
56
 
40
57
  ```bash
41
58
  node --test
42
59
  ```
43
60
 
44
- Node ≥ 18 gerekir.
61
+ Requires Node ≥ 18.
45
62
 
46
- ## Lisans
63
+ ## License
47
64
 
48
65
  [MIT](./LICENSE)
package/bin/blocofy.mjs CHANGED
@@ -1,30 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Blocofy tema CLI (#119). Shopify CLI modeli: local tema + canlı veri instant
4
- * preview, sonra yayına alma.
3
+ * Blocofy theme CLI. Develop your theme locally against live data with instant
4
+ * preview, then publish.
5
5
  *
6
- * İki mod:
7
- * - Standalone (Faz 3d): `blocofy login` ile platform URL + dev token kaydet →
8
- * `blocofy theme dev` kendi http sunucusunu açar, her isteği platformun
9
- * `/api/dev/render`'ına proxy'ler (local tema + canlı veri), fs.watch ile
10
- * livereload. Monorepo GEREKMEZ → external müşteri `npx @blocofy/cli` ile.
11
- * - Monorepo (Faz 2 v1): login yoksa, monorepo içindeyse renderer'ı THEME_DEV_DIR
12
- * ile spawn eder (fallback).
6
+ * `blocofy login` stores your platform URL + dev token, then `blocofy theme dev`
7
+ * starts a local server that proxies each request to the platform's
8
+ * `/api/dev/render` endpoint (local theme files + live data) with file-watch
9
+ * livereload. No monorepo required.
13
10
  */
14
- import { spawn } from "node:child_process";
15
11
  import { existsSync } from "node:fs";
16
- import { dirname, resolve } from "node:path";
12
+ import { resolve } from "node:path";
17
13
  import { createInterface } from "node:readline/promises";
18
- import { fileURLToPath } from "node:url";
19
14
 
20
15
  import { credentialsPath, loadCredentials, saveCredentials } from "../lib/credentials.mjs";
21
16
  import { startDevServer } from "../lib/dev-server.mjs";
22
17
  import { pullTheme, pushTheme } from "../lib/theme-sync.mjs";
23
18
 
24
- const VERSION = "0.1.0";
19
+ const VERSION = "0.1.2";
25
20
  const args = process.argv.slice(2);
26
21
 
27
- /** `--key value` ve `--flag` (boolean) ayrıştırıcı. */
22
+ /** Parse `--key value` and `--flag` (boolean) arguments. */
28
23
  function parseFlags(rest) {
29
24
  const flags = {};
30
25
  for (let i = 0; i < rest.length; i++) {
@@ -42,36 +37,24 @@ function parseFlags(rest) {
42
37
  return flags;
43
38
  }
44
39
 
45
- function findMonorepoRoot(start) {
46
- let dir = start;
47
- for (let i = 0; i < 8; i++) {
48
- if (existsSync(resolve(dir, "pnpm-workspace.yaml"))) return dir;
49
- const parent = resolve(dir, "..");
50
- if (parent === dir) break;
51
- dir = parent;
52
- }
53
- return null;
54
- }
55
-
56
40
  function printHelp() {
57
- console.log(`blocofy — Blocofy tema geliştirme CLI (v${VERSION})
41
+ console.log(`blocofy — Blocofy theme development CLI (v${VERSION})
58
42
 
59
- Kullanım:
43
+ Usage:
60
44
  blocofy login [--url <url>] [--token <bcf_…>]
61
- Platform URL + dev token kaydet (~/.blocofy/credentials.json).
62
- Token: admin → Ayarlar → Tema CLI token'ları.
63
- blocofy theme dev [dir] Local tema dev sunucusu — local tema + canlı veri instant
64
- preview. Giriş yapıldıysa standalone (monorepo gerekmez);
65
- yoksa monorepo içinde renderer'ı sarar. dir varsayılan: cwd.
66
- --port <n> (varsayılan 3030), --standalone, --monorepo
67
- blocofy theme pull [dir] Canlı temayı diske indir (giriş gerekli). dir varsayılan: cwd.
68
- blocofy theme push [dir] Local temayı canlı siteye yaz (create/update; v1 silme yok).
45
+ Save your platform URL + dev token (~/.blocofy/credentials.json).
46
+ Get a token from your admin panel: Settings → Theme CLI tokens.
47
+ blocofy theme dev [dir] Local dev server — local theme + live data with instant preview.
48
+ Edit a file and the browser reloads automatically. dir defaults to cwd.
49
+ --port <n> (default 3030)
50
+ blocofy theme pull [dir] Download the live theme to disk. dir defaults to cwd.
51
+ blocofy theme push [dir] Write the local theme to the live site (create/update; no delete).
52
+ --draft write to a draft theme instead (preview & publish from the admin panel)
69
53
  blocofy --version
70
54
  blocofy --help
71
55
 
72
- Yakında (#119): blocofy init, blocofy check.
73
- Not: CLI build ALMAZ — asset'leri kendi araçlarınla (npm/Vite/Tailwind) üretirsin;
74
- platform Liquid + statik asset olarak kalır.`);
56
+ The CLI does not build assets — generate them with your own tools (npm/Vite/Tailwind);
57
+ the platform serves plain Liquid + static assets.`);
75
58
  }
76
59
 
77
60
  async function login(rest) {
@@ -82,7 +65,7 @@ async function login(rest) {
82
65
  if (!url || !token) {
83
66
  const rl = createInterface({ input: process.stdin, output: process.stdout });
84
67
  try {
85
- if (!url) url = (await rl.question("Platform/site URL (örn. https://magaza.myblocofy.com): ")).trim();
68
+ if (!url) url = (await rl.question("Platform/site URL (e.g. https://store.myblocofy.com): ")).trim();
86
69
  if (!token) token = (await rl.question("Dev token (bcf_…): ")).trim();
87
70
  } finally {
88
71
  rl.close();
@@ -91,24 +74,24 @@ async function login(rest) {
91
74
 
92
75
  url = url.replace(/\/+$/, "");
93
76
  if (!/^https?:\/\//.test(url)) {
94
- console.error("Geçersiz URL — http(s):// ile başlamalı.");
77
+ console.error("Invalid URL — must start with http(s)://");
95
78
  process.exit(1);
96
79
  }
97
80
  if (!token.startsWith("bcf_")) {
98
- console.error("Geçersiz token — bcf_ ile başlamalı (admin → Ayarlar → Tema CLI token'ları).");
81
+ console.error("Invalid token — must start with bcf_ (admin panel: Settings → Theme CLI tokens).");
99
82
  process.exit(1);
100
83
  }
101
84
 
102
85
  saveCredentials({ url, token });
103
- console.log(`Giriş kaydedildi: ${credentialsPath()}`);
104
- console.log(`Artık tema dizininde: blocofy theme dev`);
86
+ console.log(`Saved credentials: ${credentialsPath()}`);
87
+ console.log(`Now run, in your theme directory: blocofy theme dev`);
105
88
  }
106
89
 
107
- /** Giriş bilgisi (URL+token) çöz ya da hata ver. pull/push standalone gerektirir. */
90
+ /** Resolve credentials (URL + token) or exit with guidance. */
108
91
  function requireCreds() {
109
92
  const creds = loadCredentials();
110
93
  if (!creds || !creds.url || !creds.token) {
111
- console.error("Giriş gerekli: `blocofy login` (ya da BLOCOFY_URL + BLOCOFY_TOKEN).");
94
+ console.error("Login required: run `blocofy login` (or set BLOCOFY_URL + BLOCOFY_TOKEN).");
112
95
  process.exit(1);
113
96
  }
114
97
  return creds;
@@ -119,22 +102,29 @@ async function themePull(rest) {
119
102
  const dir = resolve(positional[0] ?? process.cwd());
120
103
  const creds = requireCreds();
121
104
  const { count } = await pullTheme({ dir, url: creds.url, token: creds.token });
122
- console.log(`${count} tema dosyası indirildi → ${dir}`);
105
+ console.log(`Downloaded ${count} theme files → ${dir}`);
123
106
  }
124
107
 
125
108
  async function themePush(rest) {
109
+ const flags = parseFlags(rest);
126
110
  const positional = rest.filter((a) => !a.startsWith("--"));
127
111
  const dir = resolve(positional[0] ?? process.cwd());
128
112
  if (!existsSync(dir)) {
129
- console.error(`Tema dizini yok: ${dir}`);
113
+ console.error(`Theme directory not found: ${dir}`);
130
114
  process.exit(1);
131
115
  }
132
116
  const creds = requireCreds();
133
- const result = await pushTheme({ dir, url: creds.url, token: creds.token });
134
- const extra = result.skippedDeletes
135
- ? `, ${result.skippedDeletes} uzak dosya yerelde yok (v1: silinmedi)`
136
- : "";
137
- console.log(`Push: ${result.created} yeni, ${result.updated} güncellendi${extra}.`);
117
+ const draft = Boolean(flags.draft);
118
+ const result = await pushTheme({ dir, url: creds.url, token: creds.token, draft });
119
+ if (result.draft) {
120
+ console.log(`Pushed to draft theme #${result.instanceId} (${result.created} created, ${result.updated} updated).`);
121
+ console.log(`Preview & publish it in the admin panel: Theme → Theme library → "Open in editor".`);
122
+ } else {
123
+ const extra = result.skippedDeletes
124
+ ? `, ${result.skippedDeletes} remote file(s) absent locally (not deleted)`
125
+ : "";
126
+ console.log(`Push: ${result.created} created, ${result.updated} updated${extra}.`);
127
+ }
138
128
  }
139
129
 
140
130
  function themeDev(rest) {
@@ -143,83 +133,45 @@ function themeDev(rest) {
143
133
  const themeDir = resolve(positional[0] ?? process.cwd());
144
134
 
145
135
  if (!existsSync(themeDir)) {
146
- console.error(`Tema dizini yok: ${themeDir}`);
136
+ console.error(`Theme directory not found: ${themeDir}`);
147
137
  process.exit(1);
148
138
  }
149
139
 
150
- const creds = flags.monorepo ? null : loadCredentials();
151
- const canStandalone = Boolean(creds && creds.url && creds.token);
152
-
153
- // Standalone: giriş yapıldıysa (ya da --standalone zorlanırsa).
154
- if (canStandalone || flags.standalone) {
155
- if (!canStandalone) {
156
- console.error("Standalone için giriş gerekli: `blocofy login` (ya da BLOCOFY_URL + BLOCOFY_TOKEN).");
157
- process.exit(1);
158
- }
159
- const port = Number(flags.port) || 3030;
160
- console.log(`blocofy theme dev (standalone)`);
161
- console.log(` tema dizini : ${themeDir}`);
162
- console.log(` platform : ${creds.url}`);
163
- console.log(` token : ${creds.token.slice(0, 8)}… (${creds.source})`);
164
- console.log(` önizleme : http://localhost:${port}`);
165
- console.log(` → tema dosyasını düzenle, kaydet = tarayıcı otomatik yenilenir (livereload)`);
166
-
167
- if (flags.dry) {
168
- console.log("(--dry: sunucu başlatılmadı)");
169
- return;
170
- }
171
-
172
- const handle = startDevServer({
173
- dir: themeDir,
174
- url: creds.url,
175
- token: creds.token,
176
- port,
177
- onError: (err) => {
178
- if (err && err.code === "EADDRINUSE") {
179
- console.error(`Port ${port} kullanımda. Farklı port: blocofy theme dev --port <n>`);
180
- } else {
181
- console.error(`Sunucu hatası: ${err?.message ?? err}`);
182
- }
183
- process.exit(1);
184
- },
185
- });
186
- const shutdown = () => {
187
- handle.close();
188
- process.exit(0);
189
- };
190
- process.on("SIGINT", shutdown);
191
- process.on("SIGTERM", shutdown);
192
- return;
193
- }
194
-
195
- // Monorepo fallback (Faz 2 v1): renderer'ı THEME_DEV_DIR ile spawn et.
196
- const root = findMonorepoRoot(dirname(fileURLToPath(import.meta.url)));
197
- if (!root) {
198
- console.error(
199
- "Ne giriş yapıldı ne de monorepo bulundu.\n" +
200
- "External kullanım için: `blocofy login` (admin'den dev token al).\n" +
201
- "Monorepo içinde çalışıyorsan pnpm-workspace.yaml görünür bir dizinden çalıştır.",
202
- );
203
- process.exit(1);
204
- }
140
+ const creds = requireCreds();
141
+ const port = Number(flags.port) || 3030;
205
142
 
206
- console.log(`blocofy theme dev (monorepo)`);
207
- console.log(` tema dizini : ${themeDir}`);
208
- console.log(` renderer : ${root} (THEME_DEV_DIR ile)`);
209
- console.log(` önizleme : http://<slug>.localhost:3003 (örn. test-cafe.localhost)`);
210
- console.log(` → tema dosyasını düzenle, tarayıcıyı yenile = instant preview (gerçek içerikle)`);
143
+ console.log(`blocofy theme dev`);
144
+ console.log(` theme dir : ${themeDir}`);
145
+ console.log(` platform : ${creds.url}`);
146
+ console.log(` token : ${creds.token.slice(0, 8)}… (${creds.source})`);
147
+ console.log(` preview : http://localhost:${port}`);
148
+ console.log(` → edit a theme file and save = the browser reloads automatically (livereload)`);
211
149
 
212
150
  if (flags.dry) {
213
- console.log("(--dry: renderer başlatılmadı)");
151
+ console.log("(--dry: server not started)");
214
152
  return;
215
153
  }
216
154
 
217
- const child = spawn("pnpm", ["--filter", "renderer", "dev"], {
218
- cwd: root,
219
- env: { ...process.env, THEME_DEV_DIR: themeDir },
220
- stdio: "inherit",
155
+ const handle = startDevServer({
156
+ dir: themeDir,
157
+ url: creds.url,
158
+ token: creds.token,
159
+ port,
160
+ onError: (err) => {
161
+ if (err && err.code === "EADDRINUSE") {
162
+ console.error(`Port ${port} is in use. Try a different port: blocofy theme dev --port <n>`);
163
+ } else {
164
+ console.error(`Server error: ${err?.message ?? err}`);
165
+ }
166
+ process.exit(1);
167
+ },
221
168
  });
222
- child.on("exit", (code) => process.exit(code ?? 0));
169
+ const shutdown = () => {
170
+ handle.close();
171
+ process.exit(0);
172
+ };
173
+ process.on("SIGINT", shutdown);
174
+ process.on("SIGTERM", shutdown);
223
175
  }
224
176
 
225
177
  const [first, ...rest] = args;
@@ -246,7 +198,7 @@ if (first === "--version" || first === "-v") {
246
198
  process.exit(1);
247
199
  });
248
200
  } else {
249
- console.error(`Bilinmeyen komut: ${args.join(" ")}\n`);
201
+ console.error(`Unknown command: ${args.join(" ")}\n`);
250
202
  printHelp();
251
203
  process.exit(1);
252
204
  }
@@ -3,11 +3,11 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
5
  /**
6
- * Blocofy CLI kimlik bilgileri (#119 Faz 3d). `blocofy login` platform URL'i +
7
- * dev token'ı `~/.blocofy/credentials.json`'a (0600) yazar; `blocofy theme dev`
8
- * standalone modu bunu okur. Env (BLOCOFY_URL/BLOCOFY_TOKEN) dosyayı geçersiz
9
- * kılar (CI/agent). Token plaintext diskte tutulur (Shopify/gh CLI deseni) →
10
- * dosya izni 0600.
6
+ * Blocofy CLI credentials. `blocofy login` writes the platform URL + dev token to
7
+ * `~/.blocofy/credentials.json` (0600); `blocofy theme dev` reads it. Environment
8
+ * variables (BLOCOFY_URL/BLOCOFY_TOKEN) take precedence over the file (CI/agents).
9
+ * The token is stored in plaintext on disk (the usual dev-CLI pattern), so the file
10
+ * is locked down to 0600.
11
11
  */
12
12
 
13
13
  const DIR = join(homedir(), ".blocofy");
@@ -17,16 +17,16 @@ export function credentialsPath() {
17
17
  return FILE;
18
18
  }
19
19
 
20
- /** URL + token'ı 0600 izniyle yaz. */
20
+ /** Write URL + token with 0600 permissions. */
21
21
  export function saveCredentials({ url, token }) {
22
22
  mkdirSync(DIR, { recursive: true, mode: 0o700 });
23
23
  writeFileSync(FILE, JSON.stringify({ url, token }, null, 2) + "\n", { mode: 0o600 });
24
- chmodSync(FILE, 0o600); // dosya zaten varsa create-mode uygulanmaz → zorla
24
+ chmodSync(FILE, 0o600); // create-mode is ignored if the file already exists → enforce it
25
25
  }
26
26
 
27
27
  /**
28
- * Kimlik bilgisi çöz: önce env (tam çift), sonra dosya. `{ url, token, source }`
29
- * ya da null. URL sondaki `/`'lardan arındırılır.
28
+ * Resolve credentials: environment first (both vars), then the file. Returns
29
+ * `{ url, token, source }` or null. Trailing slashes are stripped from the URL.
30
30
  */
31
31
  export function loadCredentials() {
32
32
  const envUrl = process.env.BLOCOFY_URL;
@@ -40,7 +40,7 @@ export function loadCredentials() {
40
40
  return { url: stripSlash(data.url), token: data.token, source: "file" };
41
41
  }
42
42
  } catch {
43
- // dosya yok/bozuk → null
43
+ // missing/corrupt file → null
44
44
  }
45
45
  return null;
46
46
  }
@@ -10,13 +10,13 @@ import {
10
10
  } from "./local-theme.mjs";
11
11
 
12
12
  /**
13
- * Standalone dev sunucusu (#119 Faz 3d). Her sayfa isteğinde local tema dosyalarını
14
- * okuyup platformun `/api/dev/render`'ına yollar, dönen HTML'e livereload script'i
15
- * enjekte eder. `fs.watch` ile dosya değişince SSE üzerinden tarayıcıyı yeniler.
13
+ * Standalone dev server. For each page request it reads the local theme files and
14
+ * posts them to the platform's `/api/dev/render` endpoint, then injects a livereload
15
+ * script into the returned HTML. `fs.watch` reloads the browser over SSE on change.
16
16
  *
17
- * Asset'ler genelde `{% render 'asset/x.css' %}` ile HTML'e GÖMÜLÜ gelir (ayrı
18
- * istek yok); doğrudan `/asset/*` referansları için diskten statik servis edilir.
19
- * Görsel/JS uzak (Directus/CDN) → local servise gerek yok.
17
+ * Assets are usually inlined into the HTML via `{% render 'asset/x.css' %}` (no
18
+ * separate request); direct `/asset/*` references are served from disk. Images/JS
19
+ * are remote (Directus/CDN), so no local serving is needed for them.
20
20
  */
21
21
  export function startDevServer({ dir, url, token, port, onError }) {
22
22
  const base = url.replace(/\/+$/, "");
@@ -35,7 +35,7 @@ export function startDevServer({ dir, url, token, port, onError }) {
35
35
  try {
36
36
  message = JSON.parse(text).error ?? text;
37
37
  } catch {
38
- /* ham metin */
38
+ /* raw text */
39
39
  }
40
40
  return { status: res.status, html: errorHtml(res.status, message) };
41
41
  }
@@ -50,7 +50,7 @@ export function startDevServer({ dir, url, token, port, onError }) {
50
50
  try {
51
51
  const pathname = decodeURIComponent(new URL(req.url, "http://localhost").pathname);
52
52
 
53
- // Livereload SSE kanalı.
53
+ // Livereload SSE channel.
54
54
  if (pathname === LIVERELOAD_PATH) {
55
55
  res.writeHead(200, {
56
56
  "content-type": "text/event-stream",
@@ -63,7 +63,7 @@ export function startDevServer({ dir, url, token, port, onError }) {
63
63
  return;
64
64
  }
65
65
 
66
- // Doğrudan /asset/* referansı → diskten servis et (varsa).
66
+ // Direct /asset/* reference → serve from disk (if present).
67
67
  const assetFile = safeAssetFile(dir, pathname);
68
68
  if (assetFile) {
69
69
  res.writeHead(200, { "content-type": contentTypeFor(assetFile), "cache-control": "no-store" });
@@ -71,7 +71,7 @@ export function startDevServer({ dir, url, token, port, onError }) {
71
71
  return;
72
72
  }
73
73
 
74
- // Sayfa → platforma render ettir.
74
+ // Page → render on the platform.
75
75
  const { status, html } = await renderPath(pathname);
76
76
  res.writeHead(status, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
77
77
  res.end(injectLivereload(html));
@@ -95,7 +95,7 @@ export function startDevServer({ dir, url, token, port, onError }) {
95
95
  try {
96
96
  watcher?.close();
97
97
  } catch {
98
- /* yoksay */
98
+ /* ignore */
99
99
  }
100
100
  for (const client of clients) client.end();
101
101
  server.close();
@@ -104,8 +104,9 @@ export function startDevServer({ dir, url, token, port, onError }) {
104
104
  }
105
105
 
106
106
  /**
107
- * `dir`'i izle; recursive desteklenmiyorsa (eski Node/Linux) recursive'siz dener.
108
- * Debounce'lu `onChange` çağırır. İzlenemezse null (livereload pasif).
107
+ * Watch `dir`; if recursive watching is unsupported (older Node/Linux), fall back to
108
+ * a non-recursive watch. Calls `onChange` debounced. Returns null if unwatchable
109
+ * (livereload disabled).
109
110
  */
110
111
  function startWatch(dir, onChange) {
111
112
  let timer = null;
@@ -124,7 +125,7 @@ function startWatch(dir, onChange) {
124
125
  }
125
126
  }
126
127
 
127
- /** `/asset/*` yolunu disk dosyasına eşle (path-traversal korumalı); yoksa null. */
128
+ /** Map a `/asset/*` path to a disk file (path-traversal safe); null if missing. */
128
129
  function safeAssetFile(dir, pathname) {
129
130
  if (!pathname.startsWith("/asset/")) return null;
130
131
  const root = normalize(dir);
@@ -133,7 +134,7 @@ function safeAssetFile(dir, pathname) {
133
134
  try {
134
135
  if (existsSync(full) && statSync(full).isFile()) return full;
135
136
  } catch {
136
- /* yoksay */
137
+ /* ignore */
137
138
  }
138
139
  return null;
139
140
  }
@@ -144,12 +145,12 @@ function escapeHtml(value) {
144
145
 
145
146
  function errorHtml(status, message) {
146
147
  return (
147
- `<!doctype html><html lang="tr"><head><meta charset="utf-8">` +
148
+ `<!doctype html><html lang="en"><head><meta charset="utf-8">` +
148
149
  `<title>blocofy dev — ${status}</title></head>` +
149
150
  `<body style="font-family:system-ui,sans-serif;padding:2rem;max-width:48rem;margin:0 auto">` +
150
- `<h1 style="color:#b91c1c">Render hatası (${status})</h1>` +
151
+ `<h1 style="color:#b91c1c">Render error (${status})</h1>` +
151
152
  `<pre style="white-space:pre-wrap;background:#f4f4f5;padding:1rem;border-radius:.5rem;color:#333">${escapeHtml(message)}</pre>` +
152
- `<p style="color:#666">Tema dosyasını düzelt — kaydedince bu sayfa otomatik yenilenir.</p>` +
153
+ `<p style="color:#666">Fix the theme file — this page reloads automatically when you save.</p>` +
153
154
  `</body></html>`
154
155
  );
155
156
  }
@@ -2,25 +2,24 @@ import { readdirSync, readFileSync, statSync } from "node:fs";
2
2
  import { extname, join, relative, sep } from "node:path";
3
3
 
4
4
  /**
5
- * Local tema dosyalarını okuma + livereload — `@blocofy/cms`'in zero-dep aynası
6
- * (standalone CLI cms'i bundle edemez). `readLocalTemplates`, cms'teki
7
- * `loadLocalTemplates` (templates.ts) ile BİREBİR aynı eşlemeyi yapar:
8
- * üst-dizini THEME_DIRS olan dosyalar, key = göreli yol, `.liquid` strip'li.
5
+ * Reading local theme files + livereload helpers. `readLocalTemplates` mirrors the
6
+ * platform's own theme loader exactly: files whose top-level directory is one of
7
+ * THEME_DIRS, keyed by relative path with the `.liquid` extension stripped.
9
8
  */
10
9
 
11
10
  const THEME_DIRS = new Set(["layout", "section", "block", "partial", "asset", "template"]);
12
11
  const LIQUID_KINDS = new Set(["layout", "section", "block", "partial", "template"]);
13
12
 
14
13
  /**
15
- * Stripped key → disk yolu (cms `repoPathFor`'un aynası): liquid kind'lara `.liquid`
16
- * ekler, asset'i ham bırakır. `pull` diske yazarken kullanır (readLocalTemplates tersi).
14
+ * Stripped key → on-disk path: re-adds `.liquid` for Liquid kinds, leaves assets
15
+ * raw. Used by `pull` when writing to disk (the inverse of readLocalTemplates).
17
16
  */
18
17
  export function localPathFor(key) {
19
18
  const top = key.split("/")[0] ?? "";
20
19
  return LIQUID_KINDS.has(top) && !key.endsWith(".liquid") ? `${key}.liquid` : key;
21
20
  }
22
21
 
23
- /** Tema dizinini `{path: content}` haritasına çevir (dev-render'a gönderilir). */
22
+ /** Turn a theme directory into a `{ path: content }` map (sent to dev-render). */
24
23
  export function readLocalTemplates(dir) {
25
24
  const out = {};
26
25
  const walk = (current) => {
@@ -48,7 +47,7 @@ const LIVERELOAD_SNIPPET = `<script>
48
47
 
49
48
  export { LIVERELOAD_PATH };
50
49
 
51
- /** Render edilen HTML'e livereload script'ini ekle (</body>'den önce). */
50
+ /** Inject the livereload script into rendered HTML (before </body>). */
52
51
  export function injectLivereload(html) {
53
52
  if (typeof html !== "string") return html;
54
53
  return html.includes("</body>")
@@ -76,7 +75,7 @@ const CONTENT_TYPES = {
76
75
  ".map": "application/json",
77
76
  };
78
77
 
79
- /** Uzantıdan content-type (statik asset servisi için). */
78
+ /** Content-type from a file extension (for static asset serving). */
80
79
  export function contentTypeFor(p) {
81
80
  return CONTENT_TYPES[extname(p).toLowerCase()] ?? "application/octet-stream";
82
81
  }
@@ -4,9 +4,9 @@ import { dirname, join } from "node:path";
4
4
  import { localPathFor, readLocalTemplates } from "./local-theme.mjs";
5
5
 
6
6
  /**
7
- * `blocofy theme pull/push` (#119 Faz 3e). Platformun `/api/dev/theme` endpoint'iyle
8
- * konuşur (token Bearer). pull = GET → diske yaz (.liquid re-map); push = readLocalTemplates
9
- * → POST (create/update; v1 silme yok).
7
+ * `blocofy theme pull/push`. Talks to the platform's `/api/dev/theme` endpoint
8
+ * (Bearer token). pull = GET → write to disk (re-adding `.liquid`); push =
9
+ * readLocalTemplates → POST (create/update; no delete).
10
10
  */
11
11
 
12
12
  async function errorText(res) {
@@ -18,7 +18,7 @@ async function errorText(res) {
18
18
  }
19
19
  }
20
20
 
21
- /** Canlı temayı diske indir. `{path: content}` (stripped) → `.liquid`'li disk dosyaları. */
21
+ /** Download the live theme to disk. `{ path: content }` (stripped) → `.liquid` files. */
22
22
  export async function pullTheme({ dir, url, token }) {
23
23
  const base = url.replace(/\/+$/, "");
24
24
  const res = await fetch(`${base}/api/dev/theme`, { headers: { authorization: `Bearer ${token}` } });
@@ -34,14 +34,18 @@ export async function pullTheme({ dir, url, token }) {
34
34
  return { count };
35
35
  }
36
36
 
37
- /** Local temayı canlı siteye yaz (create/update; v1 silme yok). */
38
- export async function pushTheme({ dir, url, token }) {
37
+ /**
38
+ * Write the local theme to the site (create/update; no delete). With `draft`,
39
+ * writes to a draft theme instance instead of the live theme — preview & publish
40
+ * it from the admin panel without affecting the live site.
41
+ */
42
+ export async function pushTheme({ dir, url, token, draft = false }) {
39
43
  const base = url.replace(/\/+$/, "");
40
44
  const files = readLocalTemplates(dir);
41
45
  const res = await fetch(`${base}/api/dev/theme`, {
42
46
  method: "POST",
43
47
  headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
44
- body: JSON.stringify({ files }),
48
+ body: JSON.stringify({ files, draft }),
45
49
  });
46
50
  if (!res.ok) throw new Error(await errorText(res));
47
51
  return res.json();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blocofy/cli",
3
- "version": "0.1.0",
4
- "description": "Blocofy tema geliştirme CLI — local dev, pull, push (Shopify CLI modeli).",
3
+ "version": "0.1.2",
4
+ "description": "Develop your Blocofy theme locally against live data with instant preview — login, theme dev, pull, push.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Blocofy",
@@ -18,7 +18,7 @@
18
18
  "theme",
19
19
  "cli",
20
20
  "liquid",
21
- "shopify-cli"
21
+ "theme-development"
22
22
  ],
23
23
  "bin": {
24
24
  "blocofy": "./bin/blocofy.mjs"