@blocofy/cli 0.1.14 → 0.2.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/bin/blocofy.mjs +36 -49
- package/lib/args.mjs +33 -0
- package/lib/messages.mjs +2 -2
- package/lib/theme-sync.mjs +6 -4
- package/package.json +1 -1
package/bin/blocofy.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { existsSync } from "node:fs";
|
|
|
12
12
|
import { resolve } from "node:path";
|
|
13
13
|
import { createInterface } from "node:readline/promises";
|
|
14
14
|
|
|
15
|
+
import { parseArgs } from "../lib/args.mjs";
|
|
15
16
|
import { pullContent, pushContent } from "../lib/content-sync.mjs";
|
|
16
17
|
import { credentialsPath, loadCredentials, saveCredentials } from "../lib/credentials.mjs";
|
|
17
18
|
import { startDevServer } from "../lib/dev-server.mjs";
|
|
@@ -31,24 +32,6 @@ function siteLabel(site) {
|
|
|
31
32
|
return site.name ? `${site.name} (${site.slug})` : site.slug;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
/** Parse `--key value` and `--flag` (boolean) arguments. */
|
|
35
|
-
function parseFlags(rest) {
|
|
36
|
-
const flags = {};
|
|
37
|
-
for (let i = 0; i < rest.length; i++) {
|
|
38
|
-
const arg = rest[i];
|
|
39
|
-
if (!arg.startsWith("--")) continue;
|
|
40
|
-
const key = arg.slice(2);
|
|
41
|
-
const next = rest[i + 1];
|
|
42
|
-
if (next && !next.startsWith("--")) {
|
|
43
|
-
flags[key] = next;
|
|
44
|
-
i++;
|
|
45
|
-
} else {
|
|
46
|
-
flags[key] = true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return flags;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
35
|
function printHelp() {
|
|
53
36
|
console.log(`blocofy — Blocofy theme development CLI (v${VERSION})
|
|
54
37
|
|
|
@@ -70,6 +53,8 @@ Usage
|
|
|
70
53
|
blocofy theme pull [dir] [--draft]
|
|
71
54
|
Download the live theme to disk. (dir defaults to cwd)
|
|
72
55
|
--draft pull the draft theme (what 'theme dev' syncs into) instead of live
|
|
56
|
+
--instance <handle> pull a specific theme by its handle (from the admin
|
|
57
|
+
panel theme card, or \`blocofy status\`)
|
|
73
58
|
|
|
74
59
|
blocofy theme push [dir] [--draft] [--yes]
|
|
75
60
|
Write the local theme to the LIVE site IMMEDIATELY (create/update; no delete,
|
|
@@ -77,12 +62,14 @@ Usage
|
|
|
77
62
|
--draft write to a draft theme instead — preview & publish it from the
|
|
78
63
|
admin panel (recommended; never touches the live site)
|
|
79
64
|
--yes confirm the live push without prompting (for CI / agents)
|
|
65
|
+
--instance <handle> push to a specific theme by its handle (safe targeted
|
|
66
|
+
write — no live-confirmation prompt)
|
|
80
67
|
|
|
81
|
-
blocofy theme publish [--instance <
|
|
68
|
+
blocofy theme publish [--instance <handle>]
|
|
82
69
|
Publish a draft theme to the LIVE site. With no flag, publishes the draft that
|
|
83
70
|
'theme dev' / 'theme push --draft' writes into. The server refuses to publish a
|
|
84
71
|
theme that has no pages (it would 404) — so publishing is always safe.
|
|
85
|
-
--instance <
|
|
72
|
+
--instance <handle> publish a specific theme (handle from the panel / status)
|
|
86
73
|
|
|
87
74
|
blocofy status
|
|
88
75
|
Show the live theme, page distribution per instance, drafts, and a health flag
|
|
@@ -122,7 +109,7 @@ async function promptValid(rl, question, normalize, valid, hint, tries = 3) {
|
|
|
122
109
|
}
|
|
123
110
|
|
|
124
111
|
async function login(rest) {
|
|
125
|
-
const flags =
|
|
112
|
+
const { flags } = parseArgs(rest);
|
|
126
113
|
let url = typeof flags.url === "string" ? normalizeUrl(flags.url) : "";
|
|
127
114
|
let token = typeof flags.token === "string" ? flags.token.trim() : "";
|
|
128
115
|
|
|
@@ -190,25 +177,26 @@ function requireCreds() {
|
|
|
190
177
|
}
|
|
191
178
|
|
|
192
179
|
async function themePull(rest) {
|
|
193
|
-
const flags =
|
|
194
|
-
const
|
|
195
|
-
const dir = resolve(positional[0] ?? process.cwd());
|
|
180
|
+
const { flags, positionals } = parseArgs(rest);
|
|
181
|
+
const dir = resolve(positionals[0] ?? process.cwd());
|
|
196
182
|
const creds = requireCreds();
|
|
197
183
|
const draft = Boolean(flags.draft);
|
|
198
|
-
const
|
|
199
|
-
|
|
184
|
+
const instance = typeof flags.instance === "string" ? flags.instance : null;
|
|
185
|
+
const { count } = await pullTheme({ dir, url: creds.url, token: creds.token, draft, instance });
|
|
186
|
+
const what = instance ? `instance ${instance}` : draft ? "draft" : "live";
|
|
187
|
+
console.log(`Downloaded ${count} ${what} theme files → ${dir}`);
|
|
200
188
|
}
|
|
201
189
|
|
|
202
190
|
async function themePush(rest) {
|
|
203
|
-
const flags =
|
|
204
|
-
const
|
|
205
|
-
const dir = resolve(positional[0] ?? process.cwd());
|
|
191
|
+
const { flags, positionals } = parseArgs(rest);
|
|
192
|
+
const dir = resolve(positionals[0] ?? process.cwd());
|
|
206
193
|
if (!existsSync(dir)) {
|
|
207
194
|
console.error(`Theme directory not found: ${dir}`);
|
|
208
195
|
process.exit(1);
|
|
209
196
|
}
|
|
210
197
|
const creds = requireCreds();
|
|
211
198
|
const draft = Boolean(flags.draft);
|
|
199
|
+
const instance = typeof flags.instance === "string" ? flags.instance : null;
|
|
212
200
|
|
|
213
201
|
// Hedef tenant'ı çöz ve GÖSTER — site sunucuda TOKEN'dan çözülür (URL kozmetik),
|
|
214
202
|
// yanlış-tenant'a yazımı görünür kılar ("klarosa sandım, ksc'ye yazdım"). whoami
|
|
@@ -227,7 +215,7 @@ async function themePush(rest) {
|
|
|
227
215
|
// Canlı push (flag'siz) ANINDA canlı temayı değiştirir (önizleme yok). Agent/CI
|
|
228
216
|
// kazara canlıya basmasın diye açık onay şart (#431 L2).
|
|
229
217
|
const decision = livePushDecision({
|
|
230
|
-
draft,
|
|
218
|
+
draft: draft || Boolean(instance),
|
|
231
219
|
yes: Boolean(flags.yes),
|
|
232
220
|
confirm: Boolean(flags.confirm),
|
|
233
221
|
isTTY: Boolean(process.stdin.isTTY),
|
|
@@ -251,9 +239,9 @@ async function themePush(rest) {
|
|
|
251
239
|
}
|
|
252
240
|
}
|
|
253
241
|
|
|
254
|
-
const result = await pushTheme({ dir, url: creds.url, token: creds.token, draft });
|
|
255
|
-
if (result.
|
|
256
|
-
console.log(`Pushed to
|
|
242
|
+
const result = await pushTheme({ dir, url: creds.url, token: creds.token, draft, instance });
|
|
243
|
+
if (result.instanceId) {
|
|
244
|
+
console.log(`Pushed to theme ${result.instanceId} (${result.created} created, ${result.updated} updated).`);
|
|
257
245
|
console.log(`Preview & publish it in the admin panel: Theme → Theme library → "Open in editor".`);
|
|
258
246
|
} else {
|
|
259
247
|
const extra = result.skippedDeletes
|
|
@@ -264,8 +252,8 @@ async function themePush(rest) {
|
|
|
264
252
|
}
|
|
265
253
|
|
|
266
254
|
async function contentPush(scope, rest) {
|
|
267
|
-
const
|
|
268
|
-
const dir = resolve(
|
|
255
|
+
const { positionals } = parseArgs(rest);
|
|
256
|
+
const dir = resolve(positionals[0] ?? process.cwd());
|
|
269
257
|
if (!existsSync(dir)) {
|
|
270
258
|
console.error(`Directory not found: ${dir}`);
|
|
271
259
|
process.exit(1);
|
|
@@ -286,17 +274,16 @@ async function contentPush(scope, rest) {
|
|
|
286
274
|
}
|
|
287
275
|
|
|
288
276
|
async function contentPull(scope, rest) {
|
|
289
|
-
const
|
|
290
|
-
const dir = resolve(
|
|
277
|
+
const { positionals } = parseArgs(rest);
|
|
278
|
+
const dir = resolve(positionals[0] ?? process.cwd());
|
|
291
279
|
const creds = requireCreds();
|
|
292
280
|
const { count } = await pullContent({ dir, url: creds.url, token: creds.token, scope });
|
|
293
281
|
console.log(`Downloaded ${count} ${scope === "settings" ? "settings" : "page"} file(s) → ${dir}`);
|
|
294
282
|
}
|
|
295
283
|
|
|
296
284
|
async function themeDev(rest) {
|
|
297
|
-
const flags =
|
|
298
|
-
const
|
|
299
|
-
const themeDir = resolve(positional[0] ?? process.cwd());
|
|
285
|
+
const { flags, positionals } = parseArgs(rest);
|
|
286
|
+
const themeDir = resolve(positionals[0] ?? process.cwd());
|
|
300
287
|
|
|
301
288
|
if (!existsSync(themeDir)) {
|
|
302
289
|
console.error(`Theme directory not found: ${themeDir}`);
|
|
@@ -434,17 +421,17 @@ async function themeDev(rest) {
|
|
|
434
421
|
}
|
|
435
422
|
|
|
436
423
|
async function themePublish(rest) {
|
|
437
|
-
const flags =
|
|
424
|
+
const { flags } = parseArgs(rest);
|
|
438
425
|
const creds = requireCreds();
|
|
439
|
-
let
|
|
440
|
-
if (!
|
|
426
|
+
let instance = typeof flags.instance === "string" ? flags.instance : null;
|
|
427
|
+
if (!instance) {
|
|
441
428
|
// Belirtilmediyse: `theme dev` / `theme push --draft`'ın yazdığı taslağı yayınla.
|
|
442
429
|
const session = await fetchDevSession({ url: creds.url, token: creds.token });
|
|
443
|
-
|
|
430
|
+
instance = session.draftInstanceId;
|
|
444
431
|
}
|
|
445
|
-
const result = await publishInstance({ url: creds.url, token: creds.token, instanceId });
|
|
432
|
+
const result = await publishInstance({ url: creds.url, token: creds.token, instanceId: instance });
|
|
446
433
|
console.log(
|
|
447
|
-
`✓ Theme
|
|
434
|
+
`✓ Theme ${result.published} is now LIVE${result.cloned ? " (pages cloned from the previous live theme)" : ""}.`,
|
|
448
435
|
);
|
|
449
436
|
}
|
|
450
437
|
|
|
@@ -452,10 +439,10 @@ async function status() {
|
|
|
452
439
|
const creds = requireCreds();
|
|
453
440
|
const s = await fetchSiteStatus({ url: creds.url, token: creds.token });
|
|
454
441
|
const live = s.live_theme_instance;
|
|
455
|
-
console.log(`\nSite: ${s.site.slug}
|
|
442
|
+
console.log(`\nSite: ${s.site.slug} · ${s.site.id}`);
|
|
456
443
|
console.log(
|
|
457
444
|
live
|
|
458
|
-
? `Live theme:
|
|
445
|
+
? `Live theme: ${live.id}${live.name ? ` ${live.name}` : ""} — ${live.template_count} files, ${s.pages_on_live} pages`
|
|
459
446
|
: `Live theme: none`,
|
|
460
447
|
);
|
|
461
448
|
console.log(`Health: ${s.health}`);
|
|
@@ -467,7 +454,7 @@ async function status() {
|
|
|
467
454
|
console.warn(` ⚠ ${s.orphaned_pages} page(s) live on a non-live instance (orphan).`);
|
|
468
455
|
}
|
|
469
456
|
if (Array.isArray(s.drafts) && s.drafts.length > 0) {
|
|
470
|
-
console.log(`Drafts: ${s.drafts.map((d) =>
|
|
457
|
+
console.log(`Drafts: ${s.drafts.map((d) => `${d.id}${d.name ? ` ${d.name}` : ""}`).join(", ")}`);
|
|
471
458
|
}
|
|
472
459
|
console.log("");
|
|
473
460
|
}
|
package/lib/args.mjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Birleşik arg tokenizer. Tek geçişte hem flag'leri hem positional'ları çıkarır —
|
|
3
|
+
* böylece bir flag DEĞERİ (örn. `--port 3035`'in `3035`'i) yanlışlıkla positional
|
|
4
|
+
* (tema dizini) sanılmaz. Boolean flag'ler sonraki token'ı YUTMAZ.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Sonraki token'ı değer olarak almayan flag'ler. */
|
|
8
|
+
const BOOLEAN_FLAGS = new Set(["draft", "yes", "confirm", "no-sync", "dry", "help", "version"]);
|
|
9
|
+
|
|
10
|
+
export function parseArgs(rest) {
|
|
11
|
+
const flags = {};
|
|
12
|
+
const positionals = [];
|
|
13
|
+
for (let i = 0; i < rest.length; i++) {
|
|
14
|
+
const arg = rest[i];
|
|
15
|
+
if (!arg.startsWith("--")) {
|
|
16
|
+
positionals.push(arg);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const key = arg.slice(2);
|
|
20
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
21
|
+
flags[key] = true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const next = rest[i + 1];
|
|
25
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
26
|
+
flags[key] = next;
|
|
27
|
+
i++;
|
|
28
|
+
} else {
|
|
29
|
+
flags[key] = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { flags, positionals };
|
|
33
|
+
}
|
package/lib/messages.mjs
CHANGED
|
@@ -26,8 +26,8 @@ export function syncScopeNote() {
|
|
|
26
26
|
*/
|
|
27
27
|
export function statusLine(session) {
|
|
28
28
|
if (!session) return null;
|
|
29
|
-
const draft = session.draftInstanceId != null ?
|
|
30
|
-
const live = session.liveThemeId != null ?
|
|
29
|
+
const draft = session.draftInstanceId != null ? session.draftInstanceId : "—";
|
|
30
|
+
const live = session.liveThemeId != null ? session.liveThemeId : "(none)";
|
|
31
31
|
return `Local files → Draft theme ${draft} · Live theme → ${live}`;
|
|
32
32
|
}
|
|
33
33
|
|
package/lib/theme-sync.mjs
CHANGED
|
@@ -23,9 +23,10 @@ async function errorText(res) {
|
|
|
23
23
|
* With `draft`, pulls the "CLI Draft" instance (what `theme dev` syncs into)
|
|
24
24
|
* instead of the live theme — symmetric with `push --draft`.
|
|
25
25
|
*/
|
|
26
|
-
export async function pullTheme({ dir, url, token, draft = false }) {
|
|
26
|
+
export async function pullTheme({ dir, url, token, draft = false, instance = null }) {
|
|
27
27
|
const base = url.replace(/\/+$/, "");
|
|
28
|
-
const
|
|
28
|
+
const query = instance ? `?instance=${encodeURIComponent(instance)}` : draft ? "?draft=1" : "";
|
|
29
|
+
const res = await fetch(`${base}/api/dev/theme${query}`, {
|
|
29
30
|
headers: { authorization: `Bearer ${token}` },
|
|
30
31
|
});
|
|
31
32
|
if (!res.ok) throw new Error(await errorText(res));
|
|
@@ -73,13 +74,14 @@ export async function fetchDevSession({ url, token }) {
|
|
|
73
74
|
* writes to a draft theme instance instead of the live theme — preview & publish
|
|
74
75
|
* it from the admin panel without affecting the live site.
|
|
75
76
|
*/
|
|
76
|
-
export async function pushTheme({ dir, url, token, draft = false }) {
|
|
77
|
+
export async function pushTheme({ dir, url, token, draft = false, instance = null }) {
|
|
77
78
|
const base = url.replace(/\/+$/, "");
|
|
78
79
|
const files = readLocalTemplates(dir);
|
|
80
|
+
const payload = instance ? { files, instance } : { files, draft };
|
|
79
81
|
const res = await fetch(`${base}/api/dev/theme`, {
|
|
80
82
|
method: "POST",
|
|
81
83
|
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
82
|
-
body: JSON.stringify(
|
|
84
|
+
body: JSON.stringify(payload),
|
|
83
85
|
});
|
|
84
86
|
if (!res.ok) throw new Error(await errorText(res));
|
|
85
87
|
return res.json();
|