@blocofy/cli 0.1.9 → 0.1.11
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 +60 -1
- package/lib/content-sync.mjs +77 -0
- 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 { pullContent, pushContent } from "../lib/content-sync.mjs";
|
|
15
16
|
import { credentialsPath, loadCredentials, saveCredentials } from "../lib/credentials.mjs";
|
|
16
17
|
import { startDevServer } from "../lib/dev-server.mjs";
|
|
17
18
|
import { readLocalTemplates } from "../lib/local-theme.mjs";
|
|
@@ -20,7 +21,7 @@ import { fetchDevSession, pullTheme, pushTheme } from "../lib/theme-sync.mjs";
|
|
|
20
21
|
import { hyperlink, openUrl } from "../lib/term.mjs";
|
|
21
22
|
import { isValidToken, isValidUrl, normalizeUrl } from "../lib/validate.mjs";
|
|
22
23
|
|
|
23
|
-
const VERSION = "0.1.
|
|
24
|
+
const VERSION = "0.1.11";
|
|
24
25
|
const args = process.argv.slice(2);
|
|
25
26
|
|
|
26
27
|
/** Parse `--key value` and `--flag` (boolean) arguments. */
|
|
@@ -66,6 +67,14 @@ Usage
|
|
|
66
67
|
Write the local theme to the site (create/update; no delete).
|
|
67
68
|
--draft write to a draft theme — preview & publish it from the admin panel
|
|
68
69
|
|
|
70
|
+
blocofy pages pull [dir] / pages push [dir]
|
|
71
|
+
pull: download published pages → pages/<slug>.json.
|
|
72
|
+
push: write pages/*.json to the site. Updates EXISTING pages only —
|
|
73
|
+
never creates or deletes a page; unchanged pages are skipped.
|
|
74
|
+
|
|
75
|
+
blocofy settings pull [dir] / settings push [dir]
|
|
76
|
+
Download / upload config/settings.json (theme tokens/settings + color schemes).
|
|
77
|
+
|
|
69
78
|
blocofy --version
|
|
70
79
|
blocofy --help
|
|
71
80
|
|
|
@@ -180,6 +189,36 @@ async function themePush(rest) {
|
|
|
180
189
|
}
|
|
181
190
|
}
|
|
182
191
|
|
|
192
|
+
async function contentPush(scope, rest) {
|
|
193
|
+
const positional = rest.filter((a) => !a.startsWith("--"));
|
|
194
|
+
const dir = resolve(positional[0] ?? process.cwd());
|
|
195
|
+
if (!existsSync(dir)) {
|
|
196
|
+
console.error(`Directory not found: ${dir}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
const creds = requireCreds();
|
|
200
|
+
const result = await pushContent({ dir, url: creds.url, token: creds.token, scope });
|
|
201
|
+
if (scope === "settings") {
|
|
202
|
+
console.log(
|
|
203
|
+
`Settings push: ${result.settingsUpdated ? "theme settings updated" : "theme settings unchanged"}, ` +
|
|
204
|
+
`${result.schemesUpserted} color scheme(s) upserted (${result.fileCount} file).`,
|
|
205
|
+
);
|
|
206
|
+
} else {
|
|
207
|
+
console.log(
|
|
208
|
+
`Pages push: ${result.pagesUpdated} updated, ${result.pagesSkipped} skipped ` +
|
|
209
|
+
`(only existing pages are updated — none created or deleted).`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function contentPull(scope, rest) {
|
|
215
|
+
const positional = rest.filter((a) => !a.startsWith("--"));
|
|
216
|
+
const dir = resolve(positional[0] ?? process.cwd());
|
|
217
|
+
const creds = requireCreds();
|
|
218
|
+
const { count } = await pullContent({ dir, url: creds.url, token: creds.token, scope });
|
|
219
|
+
console.log(`Downloaded ${count} ${scope === "settings" ? "settings" : "page"} file(s) → ${dir}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
183
222
|
async function themeDev(rest) {
|
|
184
223
|
const flags = parseFlags(rest);
|
|
185
224
|
const positional = rest.filter((a) => !a.startsWith("--"));
|
|
@@ -344,6 +383,26 @@ if (first === "--version" || first === "-v") {
|
|
|
344
383
|
console.error(error?.message ?? error);
|
|
345
384
|
process.exit(1);
|
|
346
385
|
});
|
|
386
|
+
} else if (first === "pages" && rest[0] === "pull") {
|
|
387
|
+
contentPull("pages", rest.slice(1)).catch((error) => {
|
|
388
|
+
console.error(error?.message ?? error);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
});
|
|
391
|
+
} else if (first === "pages" && rest[0] === "push") {
|
|
392
|
+
contentPush("pages", rest.slice(1)).catch((error) => {
|
|
393
|
+
console.error(error?.message ?? error);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
});
|
|
396
|
+
} else if (first === "settings" && rest[0] === "pull") {
|
|
397
|
+
contentPull("settings", rest.slice(1)).catch((error) => {
|
|
398
|
+
console.error(error?.message ?? error);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
});
|
|
401
|
+
} else if (first === "settings" && rest[0] === "push") {
|
|
402
|
+
contentPush("settings", rest.slice(1)).catch((error) => {
|
|
403
|
+
console.error(error?.message ?? error);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
});
|
|
347
406
|
} else {
|
|
348
407
|
console.error(`Unknown command: ${args.join(" ")}\n`);
|
|
349
408
|
printHelp();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `blocofy pages pull/push` + `settings pull/push` (#119). CLI'nın yerel içerik
|
|
6
|
+
* dosyalarını (`pages/<slug>.json` + `config/settings.json`) platformun
|
|
7
|
+
* `/api/dev/content` endpoint'iyle çeker/gönderir. Tema KODU `theme pull/push`'a
|
|
8
|
+
* gider; bu yalnız içerik+ayar. Güvenlik platformda: push yalnız mevcut sayfayı
|
|
9
|
+
* günceller, yeni oluşturmaz, silmez.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
async function errorText(res) {
|
|
13
|
+
const text = await res.text();
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(text).error ?? text;
|
|
16
|
+
} catch {
|
|
17
|
+
return text;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** scope "pages" → pages dizinindeki tüm .json (recursive); "settings" → config/settings.json. `{path: content}`. */
|
|
22
|
+
export function readContentFiles(dir, scope) {
|
|
23
|
+
const out = {};
|
|
24
|
+
if (scope === "settings") {
|
|
25
|
+
const p = join(dir, "config", "settings.json");
|
|
26
|
+
if (existsSync(p)) out["config/settings.json"] = readFileSync(p, "utf8");
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
// pages
|
|
30
|
+
const pagesDir = join(dir, "pages");
|
|
31
|
+
if (!existsSync(pagesDir)) return out;
|
|
32
|
+
const walk = (current) => {
|
|
33
|
+
for (const name of readdirSync(current)) {
|
|
34
|
+
const full = join(current, name);
|
|
35
|
+
if (statSync(full).isDirectory()) {
|
|
36
|
+
walk(full);
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (!name.endsWith(".json")) continue;
|
|
40
|
+
const key = "pages/" + relative(pagesDir, full).split(sep).join("/");
|
|
41
|
+
out[key] = readFileSync(full, "utf8");
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
walk(pagesDir);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Yerel içeriği push'la. Yanıt: platform sonucu + gönderilen dosya sayısı. */
|
|
49
|
+
export async function pushContent({ dir, url, token, scope }) {
|
|
50
|
+
const files = readContentFiles(dir, scope);
|
|
51
|
+
const base = url.replace(/\/+$/, "");
|
|
52
|
+
const res = await fetch(`${base}/api/dev/content`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
55
|
+
body: JSON.stringify({ files }),
|
|
56
|
+
});
|
|
57
|
+
if (!res.ok) throw new Error(await errorText(res));
|
|
58
|
+
return { ...(await res.json()), fileCount: Object.keys(files).length };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Site içeriğini diske çek (pull). scope "pages" | "settings" | "all". Yazılan dosya sayısı. */
|
|
62
|
+
export async function pullContent({ dir, url, token, scope }) {
|
|
63
|
+
const base = url.replace(/\/+$/, "");
|
|
64
|
+
const res = await fetch(`${base}/api/dev/content?scope=${encodeURIComponent(scope)}`, {
|
|
65
|
+
headers: { authorization: `Bearer ${token}` },
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok) throw new Error(await errorText(res));
|
|
68
|
+
const { files } = await res.json();
|
|
69
|
+
let count = 0;
|
|
70
|
+
for (const [path, content] of Object.entries(files ?? {})) {
|
|
71
|
+
const full = join(dir, ...path.split("/"));
|
|
72
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
73
|
+
writeFileSync(full, typeof content === "string" ? content : "");
|
|
74
|
+
count += 1;
|
|
75
|
+
}
|
|
76
|
+
return { count };
|
|
77
|
+
}
|