@blocofy/cli 0.1.3 → 0.1.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/bin/blocofy.mjs CHANGED
@@ -15,8 +15,10 @@ import { createInterface } from "node:readline/promises";
15
15
  import { credentialsPath, loadCredentials, saveCredentials } from "../lib/credentials.mjs";
16
16
  import { startDevServer } from "../lib/dev-server.mjs";
17
17
  import { fetchDevSession, pullTheme, pushTheme } from "../lib/theme-sync.mjs";
18
+ import { hyperlink, openUrl } from "../lib/term.mjs";
19
+ import { isValidToken, isValidUrl, normalizeUrl } from "../lib/validate.mjs";
18
20
 
19
- const VERSION = "0.1.3";
21
+ const VERSION = "0.1.4";
20
22
  const args = process.argv.slice(2);
21
23
 
22
24
  /** Parse `--key value` and `--flag` (boolean) arguments. */
@@ -40,51 +42,97 @@ function parseFlags(rest) {
40
42
  function printHelp() {
41
43
  console.log(`blocofy — Blocofy theme development CLI (v${VERSION})
42
44
 
43
- Usage:
45
+ Develop your theme locally against live data, preview it three ways, and publish.
46
+
47
+ Usage
44
48
  blocofy login [--url <url>] [--token <bcf_…>]
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)
49
+ Save your platform URL + dev token to ~/.blocofy/credentials.json.
50
+ Get a token from the admin panel → Settings → Theme CLI tokens.
51
+
52
+ blocofy theme dev [dir] [--port <n>] [--no-sync]
53
+ Start a dev server and print 3 auto-reloading views — Local, live-domain
54
+ Preview, and the theme Editor. Press l / p / e to open each, q to quit.
55
+ Edit a file and save → every open view reloads. (dir defaults to cwd)
56
+ --port <n> local port (default 3030)
57
+ --no-sync local preview only (skip draft sync + remote views)
58
+
59
+ blocofy theme pull [dir]
60
+ Download the live theme to disk. (dir defaults to cwd)
61
+
62
+ blocofy theme push [dir] [--draft]
63
+ Write the local theme to the site (create/update; no delete).
64
+ --draft write to a draft theme — preview & publish it from the admin panel
65
+
53
66
  blocofy --version
54
67
  blocofy --help
55
68
 
56
- The CLI does not build assets — generate them with your own tools (npm/Vite/Tailwind);
57
- the platform serves plain Liquid + static assets.`);
69
+ Examples
70
+ blocofy login --url https://store.myblocofy.com --token bcf_xxxxxxxx
71
+ blocofy theme pull && blocofy theme dev
72
+ blocofy theme push --draft
73
+
74
+ Auth: ~/.blocofy/credentials.json (from \`login\`), or BLOCOFY_URL + BLOCOFY_TOKEN env vars.
75
+ The CLI does not build assets — bring your own (npm/Vite/Tailwind); the platform serves
76
+ plain Liquid + static assets.`);
77
+ }
78
+
79
+ /** Geçerli yanıt alana kadar sor (max `tries`), HER yanıtı anında doğrula. */
80
+ async function promptValid(rl, question, normalize, valid, hint, tries = 3) {
81
+ for (let i = 0; i < tries; i++) {
82
+ const answer = normalize(await rl.question(question));
83
+ if (valid(answer)) return answer;
84
+ console.error(` ✗ ${hint}`);
85
+ }
86
+ console.error("Too many invalid attempts.");
87
+ process.exit(1);
58
88
  }
59
89
 
60
90
  async function login(rest) {
61
91
  const flags = parseFlags(rest);
62
- let url = typeof flags.url === "string" ? flags.url : "";
63
- let token = typeof flags.token === "string" ? flags.token : "";
92
+ let url = typeof flags.url === "string" ? normalizeUrl(flags.url) : "";
93
+ let token = typeof flags.token === "string" ? flags.token.trim() : "";
94
+
95
+ // Flag ile verildiyse anında doğrula (prompt'a düşmeden).
96
+ if (url && !isValidUrl(url)) {
97
+ console.error("Invalid --url — must be a valid http(s):// URL.");
98
+ process.exit(1);
99
+ }
100
+ if (token && !isValidToken(token)) {
101
+ console.error("Invalid --token — must start with bcf_.");
102
+ process.exit(1);
103
+ }
64
104
 
65
105
  if (!url || !token) {
66
106
  const rl = createInterface({ input: process.stdin, output: process.stdout });
67
107
  try {
68
- if (!url) url = (await rl.question("Platform/site URL (e.g. https://store.myblocofy.com): ")).trim();
69
- if (!token) token = (await rl.question("Dev token (bcf_…): ")).trim();
108
+ // URL'i token'dan ÖNCE iste ve ANINDA doğrula (şema yoksa https:// eklenir);
109
+ // geçersizse aynı anda tekrar sorar — token'a geçip sonra hata vermez.
110
+ if (!url) {
111
+ url = await promptValid(
112
+ rl,
113
+ "Platform/site URL (e.g. https://store.myblocofy.com): ",
114
+ normalizeUrl,
115
+ isValidUrl,
116
+ "Enter a valid URL, e.g. https://store.myblocofy.com",
117
+ );
118
+ }
119
+ if (!token) {
120
+ token = await promptValid(
121
+ rl,
122
+ "Dev token (bcf_…): ",
123
+ (s) => s.trim(),
124
+ isValidToken,
125
+ "Token must start with bcf_ — get one from the admin panel: Settings → Theme CLI tokens.",
126
+ );
127
+ }
70
128
  } finally {
71
129
  rl.close();
72
130
  }
73
131
  }
74
132
 
75
- url = url.replace(/\/+$/, "");
76
- if (!/^https?:\/\//.test(url)) {
77
- console.error("Invalid URL — must start with http(s)://");
78
- process.exit(1);
79
- }
80
- if (!token.startsWith("bcf_")) {
81
- console.error("Invalid token — must start with bcf_ (admin panel: Settings → Theme CLI tokens).");
82
- process.exit(1);
83
- }
84
-
85
133
  saveCredentials({ url, token });
86
- console.log(`Saved credentials: ${credentialsPath()}`);
87
- console.log(`Now run, in your theme directory: blocofy theme dev`);
134
+ console.log(`✓ Saved credentials → ${credentialsPath()}`);
135
+ console.log(`Next: cd into your theme directory, then run blocofy theme dev`);
88
136
  }
89
137
 
90
138
  /** Resolve credentials (URL + token) or exit with guidance. */
@@ -154,13 +202,19 @@ async function themeDev(rest) {
154
202
  }
155
203
  }
156
204
 
205
+ const localUrl = `http://localhost:${port}`;
206
+ const previewUrl = session ? `${session.previewUrl}&hr=${port}` : null;
207
+ const editorUrl = session ? `${session.editorUrl}&hr=${port}` : null;
208
+
157
209
  console.log(`\nblocofy theme dev — ${creds.url} (token ${creds.token.slice(0, 8)}…)\n`);
158
- console.log(` Local: http://localhost:${port}`);
159
- if (session) {
160
- console.log(` Preview: ${session.previewUrl}&hr=${port}`);
161
- console.log(` Editor: ${session.editorUrl}&hr=${port}`);
162
- }
163
- console.log(`\n Edit a theme file and save — every open view reloads automatically.\n`);
210
+ console.log(` (l) Local ${hyperlink(localUrl)}`);
211
+ if (previewUrl) console.log(` (p) Preview ${hyperlink(previewUrl)}`);
212
+ if (editorUrl) console.log(` (e) Editor ${hyperlink(editorUrl)}`);
213
+ const keys = ["l local", previewUrl && "p preview", editorUrl && "e editor", "q quit"]
214
+ .filter(Boolean)
215
+ .join(" ");
216
+ console.log(`\n Press: ${keys}`);
217
+ console.log(` Edit a theme file and save — every open view reloads automatically.\n`);
164
218
 
165
219
  if (flags.dry) {
166
220
  console.log("(--dry: server not started)");
@@ -183,11 +237,37 @@ async function themeDev(rest) {
183
237
  },
184
238
  });
185
239
  const shutdown = () => {
240
+ if (process.stdin.isTTY) {
241
+ try {
242
+ process.stdin.setRawMode(false);
243
+ } catch {
244
+ /* yoksay */
245
+ }
246
+ }
186
247
  handle.close();
187
248
  process.exit(0);
188
249
  };
189
250
  process.on("SIGINT", shutdown);
190
251
  process.on("SIGTERM", shutdown);
252
+
253
+ // Klavye kısayolları (TTY): l/p/e ilgili görünümü tarayıcıda açar, q/Ctrl-C çıkar.
254
+ // Raw mode'da SIGINT gelmez → Ctrl-C'yi () elle yakala.
255
+ if (process.stdin.isTTY) {
256
+ try {
257
+ process.stdin.setRawMode(true);
258
+ } catch {
259
+ return; // raw mode yoksa kısayolsuz devam (sunucu çalışmaya devam eder)
260
+ }
261
+ process.stdin.resume();
262
+ process.stdin.setEncoding("utf8");
263
+ process.stdin.on("data", (key) => {
264
+ const k = key.toLowerCase();
265
+ if (key === "" || k === "q") shutdown();
266
+ else if (k === "l") openUrl(localUrl);
267
+ else if (k === "p" && previewUrl) openUrl(previewUrl);
268
+ else if (k === "e" && editorUrl) openUrl(editorUrl);
269
+ });
270
+ }
191
271
  }
192
272
 
193
273
  const [first, ...rest] = args;
package/lib/term.mjs ADDED
@@ -0,0 +1,36 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ /**
4
+ * Terminal yardımcıları: URL'leri varsayılan tarayıcıda açma + tıklanabilir
5
+ * (OSC 8) hyperlink. `theme dev` 3 görünümü tıklanabilir + l/p/e kısayollarıyla açar.
6
+ */
7
+
8
+ /** Platforma göre URL açma komutu (saf → test edilebilir). */
9
+ export function openCommand(url, platform = process.platform) {
10
+ if (platform === "darwin") return { cmd: "open", args: [url] };
11
+ if (platform === "win32") return { cmd: "cmd", args: ["/c", "start", "", url] };
12
+ return { cmd: "xdg-open", args: [url] };
13
+ }
14
+
15
+ /** URL'i varsayılan tarayıcıda aç (detached, sessiz — opener yoksa yutulur). */
16
+ export function openUrl(url) {
17
+ const { cmd, args } = openCommand(url);
18
+ try {
19
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
20
+ child.on("error", () => {});
21
+ child.unref();
22
+ } catch {
23
+ /* opener yok → sessiz */
24
+ }
25
+ }
26
+
27
+ const OSC = "]8;;";
28
+ const BEL = "";
29
+
30
+ /**
31
+ * OSC 8 terminal hyperlink: destekleyen terminalde (iTerm2, GNOME Terminal, …)
32
+ * tıklanabilir; desteklemeyende `label` düz metin olarak görünür.
33
+ */
34
+ export function hyperlink(url, label = url) {
35
+ return `${OSC}${url}${BEL}${label}${OSC}${BEL}`;
36
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Login girdisi doğrulama (saf → test edilebilir). URL şema yoksa https:// eklenir
3
+ * (bare domain'e izin), sondaki `/` atılır; token `bcf_` ile başlamalı.
4
+ */
5
+
6
+ export function normalizeUrl(raw) {
7
+ let u = (raw || "").trim().replace(/\/+$/, "");
8
+ if (u && !/^https?:\/\//i.test(u)) u = `https://${u}`;
9
+ return u;
10
+ }
11
+
12
+ export function isValidUrl(u) {
13
+ return /^https?:\/\/.+/i.test(u);
14
+ }
15
+
16
+ export function isValidToken(t) {
17
+ return typeof t === "string" && t.startsWith("bcf_") && t.length >= 12;
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocofy/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.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",