@blocofy/cli 0.2.2 → 0.2.4

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
@@ -53,6 +53,7 @@ A theme is a directory of files grouped by top-level folder:
53
53
  | `asset/` | CSS and static files (`theme.css`) |
54
54
  | `pages/<slug>.json` | Page content (`/` → `pages/index.json`) |
55
55
  | `config/settings.json` | Theme settings + color schemes |
56
+ | `config/settings_schema.json` | Theme settings panel schema (synced with `pull`/`push`) |
56
57
 
57
58
  Liquid templates use the `.liquid` extension; files under `asset/` are served as-is.
58
59
 
package/bin/blocofy.mjs CHANGED
@@ -9,6 +9,7 @@
9
9
  * livereload. No monorepo required.
10
10
  */
11
11
  import { existsSync } from "node:fs";
12
+ import { createRequire } from "node:module";
12
13
  import { resolve } from "node:path";
13
14
  import { createInterface } from "node:readline/promises";
14
15
 
@@ -17,13 +18,13 @@ import { pullContent, pushContent } from "../lib/content-sync.mjs";
17
18
  import { credentialsPath, loadCredentials, saveCredentials } from "../lib/credentials.mjs";
18
19
  import { startDevServer } from "../lib/dev-server.mjs";
19
20
  import { readLocalTemplates } from "../lib/local-theme.mjs";
20
- import { githubNote, statusLine, syncScopeNote } from "../lib/messages.mjs";
21
+ import { githubNote, retryNotice, statusLine, syncScopeNote } from "../lib/messages.mjs";
21
22
  import { fetchDevSession, fetchSiteStatus, fetchWhoami, publishInstance, pullTheme, pushTheme } from "../lib/theme-sync.mjs";
22
23
  import { isAffirmative, livePushDecision } from "../lib/confirm.mjs";
23
24
  import { hyperlink, openUrl } from "../lib/term.mjs";
24
25
  import { isValidToken, isValidUrl, normalizeUrl } from "../lib/validate.mjs";
25
26
 
26
- const VERSION = "0.1.14";
27
+ const VERSION = createRequire(import.meta.url)("../package.json").version;
27
28
  const args = process.argv.slice(2);
28
29
 
29
30
  /** Human label for a resolved site: "Name (slug)" or just the slug. */
@@ -239,7 +240,14 @@ async function themePush(rest) {
239
240
  }
240
241
  }
241
242
 
242
- const result = await pushTheme({ dir, url: creds.url, token: creds.token, draft, instance });
243
+ const result = await pushTheme({
244
+ dir,
245
+ url: creds.url,
246
+ token: creds.token,
247
+ draft,
248
+ instance,
249
+ onRetry: (info) => console.error(retryNotice(info)),
250
+ });
243
251
  if (result.instanceId) {
244
252
  console.log(`Pushed to theme ${result.instanceId} (${result.created} created, ${result.updated} updated).`);
245
253
  console.log(`Preview & publish it in the admin panel: Theme → Theme library → "Open in editor".`);
@@ -365,6 +373,7 @@ async function themeDev(rest) {
365
373
  token: creds.token,
366
374
  port,
367
375
  syncDraft: Boolean(session),
376
+ onRetry: (info) => console.error(` ${retryNotice(info)}`),
368
377
  // Her kaydetmede ne olduğunu bas — "reloaded" = watch tetiklendi; "0 views"
369
378
  // = hiçbir tarayıcı sekmesi bağlı değil (yanlış görünüme bakıyorsun); sync
370
379
  // hatası = draft güncellenemedi (preview/editör eski kalır, local yine yenilenir).
@@ -20,7 +20,7 @@ import { pushTheme } from "./theme-sync.mjs";
20
20
  * separate request); direct `/asset/*` references are served from disk. Images/JS
21
21
  * are remote (Directus/CDN), so no local serving is needed for them.
22
22
  */
23
- export function startDevServer({ dir, url, token, port, syncDraft = false, onError, onReload }) {
23
+ export function startDevServer({ dir, url, token, port, syncDraft = false, onError, onReload, onRetry }) {
24
24
  const base = url.replace(/\/+$/, "");
25
25
  const clients = new Set();
26
26
 
@@ -97,7 +97,7 @@ export function startDevServer({ dir, url, token, port, syncDraft = false, onErr
97
97
  let error = null;
98
98
  if (syncDraft) {
99
99
  try {
100
- await pushTheme({ dir, url, token, draft: true });
100
+ await pushTheme({ dir, url, token, draft: true, onRetry });
101
101
  } catch (err) {
102
102
  error = err?.message ?? String(err); // senkron hatası reload'u engellemesin
103
103
  }
@@ -108,7 +108,7 @@ export function startDevServer({ dir, url, token, port, syncDraft = false, onErr
108
108
 
109
109
  // Açılışta taslağı bir kez doldur → uzak görünümler hemen güncel local'i gösterir.
110
110
  if (syncDraft) {
111
- void pushTheme({ dir, url, token, draft: true }).catch(() => {});
111
+ void pushTheme({ dir, url, token, draft: true, onRetry }).catch(() => {});
112
112
  }
113
113
 
114
114
  server.listen(port);
@@ -31,7 +31,10 @@ export function readLocalTemplates(dir) {
31
31
  }
32
32
  const rel = relative(dir, full).split(sep).join("/");
33
33
  const top = rel.split("/")[0] ?? "";
34
- if (!THEME_DIRS.has(top)) continue;
34
+ // config/ dizininden YALNIZ settings_schema.json senkronlanır (tema-ayarları
35
+ // şeması); color_schemes.json/theme.json kendi sistemlerinde. Platform tarafıyla
36
+ // (github-sync mapRepoPath / theme-write themeWriteKind) simetrik tek-path istisnası.
37
+ if (rel !== "config/settings_schema.json" && !THEME_DIRS.has(top)) continue;
35
38
  out[rel.endsWith(".liquid") ? rel.slice(0, -7) : rel] = readFileSync(full, "utf8");
36
39
  }
37
40
  };
package/lib/messages.mjs CHANGED
@@ -14,8 +14,8 @@ import { THEME_DIRS } from "./local-theme.mjs";
14
14
  export function syncScopeNote() {
15
15
  const synced = [...THEME_DIRS].sort().map((d) => `${d}/`).join(" ");
16
16
  return [
17
- `Synced (theme code): ${synced}`,
18
- `Not synced (cloud): config/ pages/ — edit these in the admin editor`,
17
+ `Synced (theme code): ${synced} config/settings_schema.json`,
18
+ `Not synced (cloud): pages/ (other config/ files) — edit these in the admin editor`,
19
19
  ];
20
20
  }
21
21
 
@@ -31,6 +31,16 @@ export function statusLine(session) {
31
31
  return `Local files → Draft theme ${draft} · Live theme → ${live}`;
32
32
  }
33
33
 
34
+ /**
35
+ * Geçici ağ hatasında yeniden-deneme bildirimi (saf → test edilebilir). `push`
36
+ * (ve `theme dev` taslak senkronu) sessizce retry ETMESİN; stderr'e kısa bir satır
37
+ * yazılır. `onRetry({ attempt, retries, reason })` payload'ından string üretir.
38
+ */
39
+ export function retryNotice({ attempt, retries, reason }) {
40
+ const why = reason ? ` (${reason})` : "";
41
+ return `Network error${why} — retrying (${attempt}/${retries})…`;
42
+ }
43
+
34
44
  /**
35
45
  * Dev oturumuna göre "düzlem" uyarısı döndürür (string), oturum yoksa null.
36
46
  * - GitHub bağlı → repo@branch + `git pull` ipucu.
@@ -18,6 +18,49 @@ async function errorText(res) {
18
18
  }
19
19
  }
20
20
 
21
+ /** Backoff delays (ms) between attempts — index 0 = after the first failure. */
22
+ const RETRY_BACKOFF_MS = [300, 900];
23
+
24
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
25
+
26
+ /**
27
+ * fetch wrapper that retries ONLY on transient failures: a thrown fetch (network
28
+ * error / connection reset) or a 5xx / 429 response. Permanent 4xx (401/403/422 …)
29
+ * fail immediately — no retry. Max 2 retries with a short backoff (300ms, 900ms).
30
+ *
31
+ * Safe because every endpoint here is idempotent: theme push is a file upsert
32
+ * (create/update, no delete), pull/whoami/session/site are reads, publish sets a
33
+ * pointer. Re-sending the same request cannot double-apply anything.
34
+ *
35
+ * `onRetry({ attempt, retries, reason })` is called before each wait so callers can
36
+ * surface a "Network error, retrying…" line (not silent).
37
+ */
38
+ export async function fetchWithRetry(input, init, { retries = 2, onRetry, backoff = RETRY_BACKOFF_MS } = {}) {
39
+ let lastErr;
40
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
41
+ let res;
42
+ try {
43
+ res = await fetch(input, init);
44
+ } catch (err) {
45
+ // Thrown fetch = network-level failure (DNS, reset, "fetch failed"). Transient.
46
+ lastErr = err;
47
+ if (attempt === retries) throw err;
48
+ onRetry?.({ attempt: attempt + 1, retries, reason: err?.message ?? String(err) });
49
+ await sleep(backoff[attempt] ?? 0);
50
+ continue;
51
+ }
52
+ // 5xx / 429 = transient server-side; retry. Other statuses (incl. 4xx) return as-is.
53
+ if ((res.status >= 500 || res.status === 429) && attempt < retries) {
54
+ onRetry?.({ attempt: attempt + 1, retries, reason: `HTTP ${res.status}` });
55
+ await sleep(backoff[attempt] ?? 0);
56
+ continue;
57
+ }
58
+ return res;
59
+ }
60
+ // Unreachable (loop returns or throws), but keeps the type honest.
61
+ throw lastErr;
62
+ }
63
+
21
64
  /**
22
65
  * Download a theme to disk. `{ path: content }` (stripped) → `.liquid` files.
23
66
  * With `draft`, pulls the "CLI Draft" instance (what `theme dev` syncs into)
@@ -74,15 +117,19 @@ export async function fetchDevSession({ url, token }) {
74
117
  * writes to a draft theme instance instead of the live theme — preview & publish
75
118
  * it from the admin panel without affecting the live site.
76
119
  */
77
- export async function pushTheme({ dir, url, token, draft = false, instance = null }) {
120
+ export async function pushTheme({ dir, url, token, draft = false, instance = null, onRetry = null }) {
78
121
  const base = url.replace(/\/+$/, "");
79
122
  const files = readLocalTemplates(dir);
80
123
  const payload = instance ? { files, instance } : { files, draft };
81
- const res = await fetch(`${base}/api/dev/theme`, {
82
- method: "POST",
83
- headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
84
- body: JSON.stringify(payload),
85
- });
124
+ const res = await fetchWithRetry(
125
+ `${base}/api/dev/theme`,
126
+ {
127
+ method: "POST",
128
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
129
+ body: JSON.stringify(payload),
130
+ },
131
+ { onRetry },
132
+ );
86
133
  if (!res.ok) throw new Error(await errorText(res));
87
134
  return res.json();
88
135
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocofy/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
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",