@blocofy/cli 0.2.4 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,13 +17,29 @@ blocofy login [--url <url>] [--token <bcf_…>]
17
17
  # Save your platform URL + dev token (~/.blocofy/credentials.json, 0600).
18
18
  blocofy theme dev [dir] # Local dev server. Prints 3 views — local preview, a live-domain
19
19
  # preview link, and the theme editor — all auto-reloading on save.
20
- # --port <n> (default 3030), --no-sync (local preview only)
20
+ # --port <n> (default 3030), --no-sync (local preview only),
21
+ # --name <name> (name the draft when first created)
21
22
  blocofy theme pull [dir] # Download the live theme to disk.
22
- blocofy theme push [dir] # Write the local theme to the live site (create/update; no delete).
23
+ blocofy theme push [dir] # Write the local theme to a DRAFT by default (create/update; no
24
+ # delete) — preview & publish from the admin panel. --live writes
25
+ # to the live site immediately (asks to confirm; add --yes for CI).
26
+ # --name <name> names the new draft (draft mode only).
27
+ blocofy theme rename <handle> <new name>
28
+ # Rename a theme (label only). Works on any theme, live included.
29
+ # Handle from the panel theme card or `blocofy status`.
23
30
  blocofy --version
24
31
  blocofy --help
25
32
  ```
26
33
 
34
+ ## Changelog
35
+
36
+ - **0.4.0** — `theme push` / `theme dev` accept `--name <name>` to name a new draft (applied
37
+ only when the draft is first created; reuse ignores it). New `theme rename <handle> <new name>`
38
+ renames a theme (the name is a label; works on any theme, including the live one).
39
+ - **0.3.0** — `theme push` now writes to a **draft** by default; use `--live` for the old
40
+ immediate-live behavior (with a confirmation prompt; `--yes` to skip it in CI). `blocofy
41
+ status` now names the exact pages at 404 risk when a theme's pages are split off the live theme.
42
+
27
43
  ## How it works
28
44
 
29
45
  `theme dev` starts a local HTTP server. For each page request it reads your local theme
package/bin/blocofy.mjs CHANGED
@@ -19,8 +19,8 @@ import { credentialsPath, loadCredentials, saveCredentials } from "../lib/creden
19
19
  import { startDevServer } from "../lib/dev-server.mjs";
20
20
  import { readLocalTemplates } from "../lib/local-theme.mjs";
21
21
  import { githubNote, retryNotice, statusLine, syncScopeNote } from "../lib/messages.mjs";
22
- import { fetchDevSession, fetchSiteStatus, fetchWhoami, publishInstance, pullTheme, pushTheme } from "../lib/theme-sync.mjs";
23
- import { isAffirmative, livePushDecision } from "../lib/confirm.mjs";
22
+ import { fetchDevSession, fetchSiteStatus, fetchWhoami, publishInstance, pullTheme, pushTheme, renameInstance } from "../lib/theme-sync.mjs";
23
+ import { isAffirmative, livePushDecision, resolvePushMode } from "../lib/confirm.mjs";
24
24
  import { hyperlink, openUrl } from "../lib/term.mjs";
25
25
  import { isValidToken, isValidUrl, normalizeUrl } from "../lib/validate.mjs";
26
26
 
@@ -43,13 +43,14 @@ Usage
43
43
  Save your platform URL + dev token to ~/.blocofy/credentials.json.
44
44
  Get a token from the admin panel → Settings → Theme CLI tokens.
45
45
 
46
- blocofy theme dev [dir] [--port <n>] [--no-sync]
46
+ blocofy theme dev [dir] [--port <n>] [--no-sync] [--name <name>]
47
47
  Start a dev server and print 3 auto-reloading views — Local, live-domain
48
48
  Preview, and the theme Editor. Press l / p / e to open each, q to quit.
49
49
  Edit a file and save → every open view reloads. Saves sync to a DRAFT theme
50
50
  only (never the live site). (dir defaults to cwd)
51
51
  --port <n> local port (default 3030)
52
52
  --no-sync local preview only (skip draft sync + remote views)
53
+ --name <name> name the draft when it is first created (ignored if it already exists)
53
54
 
54
55
  blocofy theme pull [dir] [--draft] [--instance <handle>]
55
56
  Download the live theme to disk. (dir defaults to cwd)
@@ -57,14 +58,21 @@ Usage
57
58
  --instance <handle> pull a specific theme by its handle (from the admin
58
59
  panel theme card, or \`blocofy status\`)
59
60
 
60
- blocofy theme push [dir] [--draft] [--yes] [--instance <handle>]
61
- Write the local theme to the LIVE site IMMEDIATELY (create/update; no delete,
62
- no preview). Asks for confirmation first; non-interactive shells must pass --yes.
63
- --draft write to a draft theme instead — preview & publish it from the
64
- admin panel (recommended; never touches the live site)
65
- --yes confirm the live push without prompting (for CI / agents)
61
+ blocofy theme push [dir] [--live] [--yes] [--instance <handle>]
62
+ By DEFAULT writes to a DRAFT theme (create/update; no delete) — preview & publish
63
+ it from the admin panel, never touching the live site. Publish it with
64
+ 'blocofy theme publish'.
65
+ --live write to the LIVE site IMMEDIATELY (no preview). Asks for
66
+ confirmation first; non-interactive shells must add --yes.
67
+ --draft explicit draft (same as the default; safe)
68
+ --yes confirm a --live push without prompting (for CI / agents)
66
69
  --instance <handle> push to a specific theme by its handle (safe targeted
67
70
  write — no live-confirmation prompt)
71
+ --name <name> name the NEW draft (draft mode only; ignored on --live/--instance)
72
+
73
+ blocofy theme rename <handle> <new name>
74
+ Rename a theme (the name is just a label). Works on any of your themes,
75
+ including the live one. Handle comes from the panel theme card or 'blocofy status'.
68
76
 
69
77
  blocofy theme publish [--instance <handle>]
70
78
  Publish a draft theme to the LIVE site. With no flag, publishes the draft that
@@ -90,7 +98,7 @@ Usage
90
98
  Examples
91
99
  blocofy login --url https://store.myblocofy.com --token bcf_xxxxxxxx
92
100
  blocofy theme pull && blocofy theme dev
93
- blocofy theme push --draft && blocofy theme publish
101
+ blocofy theme push && blocofy theme publish
94
102
  blocofy status
95
103
 
96
104
  Auth: ~/.blocofy/credentials.json (from \`login\`), or BLOCOFY_URL + BLOCOFY_TOKEN env vars.
@@ -196,8 +204,17 @@ async function themePush(rest) {
196
204
  process.exit(1);
197
205
  }
198
206
  const creds = requireCreds();
199
- const draft = Boolean(flags.draft);
200
- const instance = typeof flags.instance === "string" ? flags.instance : null;
207
+ const instanceFlag = typeof flags.instance === "string" ? flags.instance : null;
208
+ const name = typeof flags.name === "string" ? flags.name : null;
209
+
210
+ // Yeni varsayılan hedef: DRAFT (güvenli). `--live` eski anında-canlı davranışını
211
+ // açıkça geri getirir; `--instance` belirli bir temayı adresler. Sadece "live"
212
+ // modu canlıya yazar ve onay gerektirir.
213
+ const { mode, instance } = resolvePushMode({
214
+ live: Boolean(flags.live),
215
+ draft: Boolean(flags.draft),
216
+ instance: instanceFlag,
217
+ });
201
218
 
202
219
  // Hedef tenant'ı çöz ve GÖSTER — site sunucuda TOKEN'dan çözülür (URL kozmetik),
203
220
  // yanlış-tenant'a yazımı görünür kılar ("klarosa sandım, ksc'ye yazdım"). whoami
@@ -209,21 +226,26 @@ async function themePush(rest) {
209
226
  /* best-effort */
210
227
  }
211
228
  const target = site ? siteLabel(site) : creds.url;
212
- if (site) {
213
- console.log(`→ ${draft ? "Pushing to a draft" : "Pushing to the LIVE theme"} of ${target}`);
229
+ if (mode === "instance") {
230
+ console.log(`→ Pushing to theme ${instance}${site ? ` of ${target}` : ""}`);
231
+ } else if (mode === "live") {
232
+ console.log(`→ Pushing to the LIVE theme of ${target}`);
233
+ } else {
234
+ console.log(`→ Pushing to a draft${site ? ` of ${target}` : ""}`);
214
235
  }
215
236
 
216
- // Canlı push (flag'siz) ANINDA canlı temayı değiştirir (önizleme yok). Agent/CI
217
- // kazara canlıya basmasın diye açık onay şart (#431 L2).
237
+ // Canlı push (`--live`) ANINDA canlı temayı değiştirir (önizleme yok). Agent/CI
238
+ // kazara canlıya basmasın diye açık onay şart (#431 L2). Draft/instance modu
239
+ // güvenli → otomatik onay (prompt yok).
218
240
  const decision = livePushDecision({
219
- draft: draft || Boolean(instance),
241
+ draft: mode !== "live",
220
242
  yes: Boolean(flags.yes),
221
243
  confirm: Boolean(flags.confirm),
222
244
  isTTY: Boolean(process.stdin.isTTY),
223
245
  });
224
246
  if (decision.mustAbort) {
225
- console.error(`⚠ 'theme push' writes to the LIVE theme of ${target} immediately (no preview).`);
226
- console.error(` Non-interactive shell: pass --yes to confirm, or use --draft for a safe draft.`);
247
+ console.error(`⚠ 'theme push --live' writes to the LIVE theme of ${target} immediately (no preview).`);
248
+ console.error(` Non-interactive shell: pass --live --yes to confirm, or omit --live to push to a safe draft.`);
227
249
  process.exit(1);
228
250
  }
229
251
  if (decision.needsPrompt) {
@@ -235,22 +257,40 @@ async function themePush(rest) {
235
257
  rl.close();
236
258
  }
237
259
  if (!isAffirmative(answer)) {
238
- console.error("Aborted. Use `--draft` to push to a draft, or `--yes` to confirm the live push.");
260
+ console.error("Aborted. Omit `--live` to push to a safe draft, or pass `--live --yes` to confirm.");
239
261
  process.exit(1);
240
262
  }
241
263
  }
242
264
 
265
+ // `--name` yalnızca YENİ taslak yaratırken (draft modu) anlamlı — canlıya/mevcut
266
+ // instance'a yazarken ad kaydedilmez, sessizce kaybolmasın diye açıkça uyar.
267
+ if (name && mode !== "draft") {
268
+ console.error("Note: --name yalnız yeni taslak yaratırken (varsayılan push) geçerli, yok sayıldı.");
269
+ }
270
+
243
271
  const result = await pushTheme({
244
272
  dir,
245
273
  url: creds.url,
246
274
  token: creds.token,
247
- draft,
248
- instance,
275
+ draft: mode === "draft",
276
+ instance: mode === "instance" ? instance : null,
277
+ name: mode === "draft" ? name : null,
249
278
  onRetry: (info) => console.error(retryNotice(info)),
250
279
  });
280
+
281
+ // Sunucu canlı-yazımı bildirdiyse (yeni alan; eski sunucuda yok) belirgin uyar.
282
+ if (result.warning === "live_write" && result.message) {
283
+ console.error(`\n⚠ ${result.message}\n`);
284
+ }
285
+
251
286
  if (result.instanceId) {
252
287
  console.log(`Pushed to theme ${result.instanceId} (${result.created} created, ${result.updated} updated).`);
253
- console.log(`Preview & publish it in the admin panel: Theme → Theme library → "Open in editor".`);
288
+ if (mode === "draft") {
289
+ console.log(`Preview & publish it in the admin panel: Theme → Theme library → "Open in editor".`);
290
+ console.log(`Publish it live with: blocofy theme publish`);
291
+ } else {
292
+ console.log(`Preview & publish it in the admin panel: Theme → Theme library → "Open in editor".`);
293
+ }
254
294
  } else {
255
295
  const extra = result.skippedDeletes
256
296
  ? `, ${result.skippedDeletes} remote file(s) absent locally (not deleted)`
@@ -306,7 +346,8 @@ async function themeDev(rest) {
306
346
  let session = null;
307
347
  if (!flags["no-sync"]) {
308
348
  try {
309
- session = await fetchDevSession({ url: creds.url, token: creds.token });
349
+ const name = typeof flags.name === "string" ? flags.name : null;
350
+ session = await fetchDevSession({ url: creds.url, token: creds.token, name });
310
351
  } catch (error) {
311
352
  console.warn(
312
353
  `Warning: dev session unavailable (${error?.message ?? error}). ` +
@@ -444,6 +485,20 @@ async function themePublish(rest) {
444
485
  );
445
486
  }
446
487
 
488
+ async function themeRename(rest) {
489
+ const { flags, positionals } = parseArgs(rest);
490
+ const handle = positionals[0];
491
+ const name = positionals.slice(1).join(" ") || (typeof flags.name === "string" ? flags.name : null);
492
+ if (!handle || !name) {
493
+ console.error("Usage: blocofy theme rename <handle> <new name>");
494
+ console.error(" Rename a theme (handle from the panel theme card or `blocofy status`).");
495
+ process.exit(1);
496
+ }
497
+ const creds = requireCreds();
498
+ const result = await renameInstance({ url: creds.url, token: creds.token, instance: handle, name });
499
+ console.log(`✓ Renamed to "${result.name}" (${result.id}).`);
500
+ }
501
+
447
502
  async function status() {
448
503
  const creds = requireCreds();
449
504
  const s = await fetchSiteStatus({ url: creds.url, token: creds.token });
@@ -455,12 +510,23 @@ async function status() {
455
510
  : `Live theme: none`,
456
511
  );
457
512
  console.log(`Health: ${s.health}`);
513
+ // orphan_missing_slugs yeni sunucu alanı (PR #661). Eski/deploy-edilmemiş sunucuda
514
+ // yok → null; o durumda eski (yumuşatılmış) sayaç satırına düş.
515
+ const missing = Array.isArray(s.orphan_missing_slugs) ? s.orphan_missing_slugs : null;
458
516
  if (s.health === "live_instance_empty") {
459
517
  console.error(
460
518
  ` ⚠ The live theme has no pages — the site will 404. Publish a theme with content: blocofy theme publish`,
461
519
  );
462
520
  } else if (s.health === "pages_split") {
463
- console.warn(` ⚠ ${s.orphaned_pages} page(s) live on a non-live instance (orphan).`);
521
+ if (missing && missing.length) {
522
+ console.warn(
523
+ ` ⚠ These pages are published only on a non-live theme, so visitors can't reach them (404 risk): ` +
524
+ `${missing.join(", ")}\n` +
525
+ ` Fix: publish the theme that has them — blocofy theme publish`,
526
+ );
527
+ } else {
528
+ console.warn(` ⚠ ${s.orphaned_pages} page(s) published on a non-live theme.`);
529
+ }
464
530
  }
465
531
  if (Array.isArray(s.drafts) && s.drafts.length > 0) {
466
532
  console.log(`Drafts: ${s.drafts.map((d) => `${d.id}${d.name ? ` ${d.name}` : ""}`).join(", ")}`);
@@ -499,6 +565,11 @@ if (first === "--version" || first === "-v") {
499
565
  console.error(error?.message ?? error);
500
566
  process.exit(1);
501
567
  });
568
+ } else if (first === "theme" && rest[0] === "rename") {
569
+ themeRename(rest.slice(1)).catch((error) => {
570
+ console.error(error?.message ?? error);
571
+ process.exit(1);
572
+ });
502
573
  } else if (first === "status") {
503
574
  status().catch((error) => {
504
575
  console.error(error?.message ?? error);
package/lib/args.mjs CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  /** Sonraki token'ı değer olarak almayan flag'ler. */
8
- const BOOLEAN_FLAGS = new Set(["draft", "yes", "confirm", "no-sync", "dry", "help", "version"]);
8
+ const BOOLEAN_FLAGS = new Set(["draft", "live", "yes", "confirm", "no-sync", "dry", "help", "version"]);
9
9
 
10
10
  export function parseArgs(rest) {
11
11
  const flags = {};
package/lib/confirm.mjs CHANGED
@@ -15,6 +15,22 @@ export function livePushDecision({ draft, yes, confirm, isTTY }) {
15
15
  return { autoApproved: false, needsPrompt: true, mustAbort: false };
16
16
  }
17
17
 
18
+ /**
19
+ * `theme push` hedefini seç (saf, yan etkisiz). Yeni varsayılan: DRAFT (güvenli).
20
+ * `--live` eski "anında canlı" davranışını açıkça geri getirir.
21
+ * - `instance` verilmişse → { mode: "instance", instance } (açık-hedef; `--live`
22
+ * olsa bile instance KAZANIR — kullanıcı belirli bir temayı adresledi).
23
+ * - `--live` (instance yok) → { mode: "live" } (onay gerektiren tek yol).
24
+ * - aksi halde → { mode: "draft" } (varsayılan; açık `--draft` dahil).
25
+ * - `--live` + `--draft` birlikte → draft (güvenli olan kazanır).
26
+ */
27
+ export function resolvePushMode({ live, draft, instance } = {}) {
28
+ if (instance) return { mode: "instance", instance };
29
+ if (draft) return { mode: "draft" };
30
+ if (live) return { mode: "live" };
31
+ return { mode: "draft" };
32
+ }
33
+
18
34
  /** y/N yanıtını yorumla (yes/y → onay). */
19
35
  export function isAffirmative(answer) {
20
36
  const a = String(answer ?? "").trim().toLowerCase();
@@ -103,9 +103,10 @@ export async function fetchWhoami({ url, token }) {
103
103
  * Dev session bilgisi (#119 `theme dev`): platform draft instance'ı hazırlar ve
104
104
  * 3 görünümün URL'lerini döner — `{ draftInstanceId, previewUrl, editorUrl, site }`.
105
105
  */
106
- export async function fetchDevSession({ url, token }) {
106
+ export async function fetchDevSession({ url, token, name = null }) {
107
107
  const base = url.replace(/\/+$/, "");
108
- const res = await fetch(`${base}/api/dev/session`, {
108
+ const query = name ? `?name=${encodeURIComponent(name)}` : "";
109
+ const res = await fetch(`${base}/api/dev/session${query}`, {
109
110
  headers: { authorization: `Bearer ${token}` },
110
111
  });
111
112
  if (!res.ok) throw new Error(await errorText(res));
@@ -117,10 +118,10 @@ export async function fetchDevSession({ url, token }) {
117
118
  * writes to a draft theme instance instead of the live theme — preview & publish
118
119
  * it from the admin panel without affecting the live site.
119
120
  */
120
- export async function pushTheme({ dir, url, token, draft = false, instance = null, onRetry = null }) {
121
+ export async function pushTheme({ dir, url, token, draft = false, instance = null, name = null, onRetry = null }) {
121
122
  const base = url.replace(/\/+$/, "");
122
123
  const files = readLocalTemplates(dir);
123
- const payload = instance ? { files, instance } : { files, draft };
124
+ const payload = instance ? { files, instance } : name ? { files, draft, name } : { files, draft };
124
125
  const res = await fetchWithRetry(
125
126
  `${base}/api/dev/theme`,
126
127
  {
@@ -150,6 +151,22 @@ export async function publishInstance({ url, token, instanceId }) {
150
151
  return res.json();
151
152
  }
152
153
 
154
+ /**
155
+ * Bir tema instance'ının adını değiştir (`POST /api/dev/theme/rename`). Ad yalnızca
156
+ * bir etiket — canlı instance dahil sahip olunan her instance yeniden adlandırılabilir.
157
+ * `{ ok, id, name }` döner.
158
+ */
159
+ export async function renameInstance({ url, token, instance, name }) {
160
+ const base = url.replace(/\/+$/, "");
161
+ const res = await fetch(`${base}/api/dev/theme/rename`, {
162
+ method: "POST",
163
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
164
+ body: JSON.stringify({ instance, name }),
165
+ });
166
+ if (!res.ok) throw new Error(await errorText(res));
167
+ return res.json();
168
+ }
169
+
153
170
  /**
154
171
  * Site sağlık/durum özeti (`GET /api/dev/site`) — `blocofy status`. Canlı tema instance'ı,
155
172
  * instance-başına sayfa dağılımı, taslaklar ve health döner.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocofy/cli",
3
- "version": "0.2.4",
3
+ "version": "0.4.0",
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",