@blocofy/cli 0.1.1 → 0.1.3
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 +10 -4
- package/bin/blocofy.mjs +42 -15
- package/lib/dev-server.mjs +22 -2
- package/lib/theme-sync.mjs +20 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,8 +15,9 @@ npx @blocofy/cli theme dev # http://localhost:3030 — local theme + live
|
|
|
15
15
|
```bash
|
|
16
16
|
blocofy login [--url <url>] [--token <bcf_…>]
|
|
17
17
|
# Save your platform URL + dev token (~/.blocofy/credentials.json, 0600).
|
|
18
|
-
blocofy theme dev [dir] # Local dev server
|
|
19
|
-
#
|
|
18
|
+
blocofy theme dev [dir] # Local dev server. Prints 3 views — local preview, a live-domain
|
|
19
|
+
# preview link, and the theme editor — all auto-reloading on save.
|
|
20
|
+
# --port <n> (default 3030), --no-sync (local preview only)
|
|
20
21
|
blocofy theme pull [dir] # Download the live theme to disk.
|
|
21
22
|
blocofy theme push [dir] # Write the local theme to the live site (create/update; no delete).
|
|
22
23
|
blocofy --version
|
|
@@ -28,8 +29,13 @@ blocofy --help
|
|
|
28
29
|
`theme dev` starts a local HTTP server. For each page request it reads your local theme
|
|
29
30
|
files and sends them to the platform's dev-render endpoint (`/api/dev/render`). The platform
|
|
30
31
|
renders them with the site's **live data** and returns HTML — so the CLI ships no rendering
|
|
31
|
-
engine and you see exactly the production output.
|
|
32
|
-
|
|
32
|
+
engine and you see exactly the production output.
|
|
33
|
+
|
|
34
|
+
It also continuously syncs your local files to a **draft theme** so you can view the same work
|
|
35
|
+
three ways — the local preview, a shareable live-domain preview link, and the admin theme
|
|
36
|
+
editor — without affecting your published theme. Save a file and **every open view reloads**
|
|
37
|
+
(the platform-rendered pages connect back to the local dev server's reload channel). Publish
|
|
38
|
+
the draft from the theme editor when you're ready.
|
|
33
39
|
|
|
34
40
|
Credentials come from `~/.blocofy/credentials.json` (written by `blocofy login`) or the
|
|
35
41
|
`BLOCOFY_URL` + `BLOCOFY_TOKEN` environment variables (for CI/automation).
|
package/bin/blocofy.mjs
CHANGED
|
@@ -14,9 +14,9 @@ import { createInterface } from "node:readline/promises";
|
|
|
14
14
|
|
|
15
15
|
import { credentialsPath, loadCredentials, saveCredentials } from "../lib/credentials.mjs";
|
|
16
16
|
import { startDevServer } from "../lib/dev-server.mjs";
|
|
17
|
-
import { pullTheme, pushTheme } from "../lib/theme-sync.mjs";
|
|
17
|
+
import { fetchDevSession, pullTheme, pushTheme } from "../lib/theme-sync.mjs";
|
|
18
18
|
|
|
19
|
-
const VERSION = "0.1.
|
|
19
|
+
const VERSION = "0.1.3";
|
|
20
20
|
const args = process.argv.slice(2);
|
|
21
21
|
|
|
22
22
|
/** Parse `--key value` and `--flag` (boolean) arguments. */
|
|
@@ -49,6 +49,7 @@ Usage:
|
|
|
49
49
|
--port <n> (default 3030)
|
|
50
50
|
blocofy theme pull [dir] Download the live theme to disk. dir defaults to cwd.
|
|
51
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)
|
|
52
53
|
blocofy --version
|
|
53
54
|
blocofy --help
|
|
54
55
|
|
|
@@ -105,6 +106,7 @@ async function themePull(rest) {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
async function themePush(rest) {
|
|
109
|
+
const flags = parseFlags(rest);
|
|
108
110
|
const positional = rest.filter((a) => !a.startsWith("--"));
|
|
109
111
|
const dir = resolve(positional[0] ?? process.cwd());
|
|
110
112
|
if (!existsSync(dir)) {
|
|
@@ -112,14 +114,20 @@ async function themePush(rest) {
|
|
|
112
114
|
process.exit(1);
|
|
113
115
|
}
|
|
114
116
|
const creds = requireCreds();
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const draft = Boolean(flags.draft);
|
|
118
|
+
const result = await pushTheme({ dir, url: creds.url, token: creds.token, draft });
|
|
119
|
+
if (result.draft) {
|
|
120
|
+
console.log(`Pushed to draft theme #${result.instanceId} (${result.created} created, ${result.updated} updated).`);
|
|
121
|
+
console.log(`Preview & publish it in the admin panel: Theme → Theme library → "Open in editor".`);
|
|
122
|
+
} else {
|
|
123
|
+
const extra = result.skippedDeletes
|
|
124
|
+
? `, ${result.skippedDeletes} remote file(s) absent locally (not deleted)`
|
|
125
|
+
: "";
|
|
126
|
+
console.log(`Push: ${result.created} created, ${result.updated} updated${extra}.`);
|
|
127
|
+
}
|
|
120
128
|
}
|
|
121
129
|
|
|
122
|
-
function themeDev(rest) {
|
|
130
|
+
async function themeDev(rest) {
|
|
123
131
|
const flags = parseFlags(rest);
|
|
124
132
|
const positional = rest.filter((a) => !a.startsWith("--"));
|
|
125
133
|
const themeDir = resolve(positional[0] ?? process.cwd());
|
|
@@ -132,12 +140,27 @@ function themeDev(rest) {
|
|
|
132
140
|
const creds = requireCreds();
|
|
133
141
|
const port = Number(flags.port) || 3030;
|
|
134
142
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
// Dev session: live-domain preview + theme editor URLs (+ a draft to sync into).
|
|
144
|
+
// Graceful: if the platform can't provide one, fall back to local-only preview.
|
|
145
|
+
let session = null;
|
|
146
|
+
if (!flags["no-sync"]) {
|
|
147
|
+
try {
|
|
148
|
+
session = await fetchDevSession({ url: creds.url, token: creds.token });
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.warn(
|
|
151
|
+
`Warning: dev session unavailable (${error?.message ?? error}). ` +
|
|
152
|
+
`Local preview only — live-domain/editor views + draft sync disabled.`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
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`);
|
|
141
164
|
|
|
142
165
|
if (flags.dry) {
|
|
143
166
|
console.log("(--dry: server not started)");
|
|
@@ -149,6 +172,7 @@ function themeDev(rest) {
|
|
|
149
172
|
url: creds.url,
|
|
150
173
|
token: creds.token,
|
|
151
174
|
port,
|
|
175
|
+
syncDraft: Boolean(session),
|
|
152
176
|
onError: (err) => {
|
|
153
177
|
if (err && err.code === "EADDRINUSE") {
|
|
154
178
|
console.error(`Port ${port} is in use. Try a different port: blocofy theme dev --port <n>`);
|
|
@@ -178,7 +202,10 @@ if (first === "--version" || first === "-v") {
|
|
|
178
202
|
process.exit(1);
|
|
179
203
|
});
|
|
180
204
|
} else if (first === "theme" && rest[0] === "dev") {
|
|
181
|
-
themeDev(rest.slice(1))
|
|
205
|
+
themeDev(rest.slice(1)).catch((error) => {
|
|
206
|
+
console.error(error?.message ?? error);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
182
209
|
} else if (first === "theme" && rest[0] === "pull") {
|
|
183
210
|
themePull(rest.slice(1)).catch((error) => {
|
|
184
211
|
console.error(error?.message ?? error);
|
package/lib/dev-server.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
injectLivereload,
|
|
9
9
|
readLocalTemplates,
|
|
10
10
|
} from "./local-theme.mjs";
|
|
11
|
+
import { pushTheme } from "./theme-sync.mjs";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Standalone dev server. For each page request it reads the local theme files and
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
* separate request); direct `/asset/*` references are served from disk. Images/JS
|
|
19
20
|
* are remote (Directus/CDN), so no local serving is needed for them.
|
|
20
21
|
*/
|
|
21
|
-
export function startDevServer({ dir, url, token, port, onError }) {
|
|
22
|
+
export function startDevServer({ dir, url, token, port, syncDraft = false, onError }) {
|
|
22
23
|
const base = url.replace(/\/+$/, "");
|
|
23
24
|
const clients = new Set();
|
|
24
25
|
|
|
@@ -56,6 +57,8 @@ export function startDevServer({ dir, url, token, port, onError }) {
|
|
|
56
57
|
"content-type": "text/event-stream",
|
|
57
58
|
"cache-control": "no-cache, no-transform",
|
|
58
59
|
connection: "keep-alive",
|
|
60
|
+
// Cross-origin iframe'ler (canlı-domain önizleme + editör) subscribe edebilsin.
|
|
61
|
+
"access-control-allow-origin": "*",
|
|
59
62
|
});
|
|
60
63
|
res.write("retry: 1000\n\n");
|
|
61
64
|
clients.add(res);
|
|
@@ -83,10 +86,27 @@ export function startDevServer({ dir, url, token, port, onError }) {
|
|
|
83
86
|
|
|
84
87
|
if (onError) server.on("error", onError);
|
|
85
88
|
|
|
86
|
-
const
|
|
89
|
+
const broadcast = () => {
|
|
87
90
|
for (const client of clients) client.write("data: reload\n\n");
|
|
91
|
+
};
|
|
92
|
+
// Değişimde: önce taslağa senkronla (canlı-domain önizleme + editör güncel olsun),
|
|
93
|
+
// SONRA reload yayınla → tüm görünümler en güncel taslağı çeker.
|
|
94
|
+
const watcher = startWatch(dir, async () => {
|
|
95
|
+
if (syncDraft) {
|
|
96
|
+
try {
|
|
97
|
+
await pushTheme({ dir, url, token, draft: true });
|
|
98
|
+
} catch {
|
|
99
|
+
/* senkron hatası reload'u engellemesin */
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
broadcast();
|
|
88
103
|
});
|
|
89
104
|
|
|
105
|
+
// Açılışta taslağı bir kez doldur → uzak görünümler hemen güncel local'i gösterir.
|
|
106
|
+
if (syncDraft) {
|
|
107
|
+
void pushTheme({ dir, url, token, draft: true }).catch(() => {});
|
|
108
|
+
}
|
|
109
|
+
|
|
90
110
|
server.listen(port);
|
|
91
111
|
|
|
92
112
|
return {
|
package/lib/theme-sync.mjs
CHANGED
|
@@ -34,14 +34,31 @@ export async function pullTheme({ dir, url, token }) {
|
|
|
34
34
|
return { count };
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Dev session bilgisi (#119 `theme dev`): platform draft instance'ı hazırlar ve
|
|
39
|
+
* 3 görünümün URL'lerini döner — `{ draftInstanceId, previewUrl, editorUrl, site }`.
|
|
40
|
+
*/
|
|
41
|
+
export async function fetchDevSession({ url, token }) {
|
|
42
|
+
const base = url.replace(/\/+$/, "");
|
|
43
|
+
const res = await fetch(`${base}/api/dev/session`, {
|
|
44
|
+
headers: { authorization: `Bearer ${token}` },
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok) throw new Error(await errorText(res));
|
|
47
|
+
return res.json();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Write the local theme to the site (create/update; no delete). With `draft`,
|
|
52
|
+
* writes to a draft theme instance instead of the live theme — preview & publish
|
|
53
|
+
* it from the admin panel without affecting the live site.
|
|
54
|
+
*/
|
|
55
|
+
export async function pushTheme({ dir, url, token, draft = false }) {
|
|
39
56
|
const base = url.replace(/\/+$/, "");
|
|
40
57
|
const files = readLocalTemplates(dir);
|
|
41
58
|
const res = await fetch(`${base}/api/dev/theme`, {
|
|
42
59
|
method: "POST",
|
|
43
60
|
headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
|
|
44
|
-
body: JSON.stringify({ files }),
|
|
61
|
+
body: JSON.stringify({ files, draft }),
|
|
45
62
|
});
|
|
46
63
|
if (!res.ok) throw new Error(await errorText(res));
|
|
47
64
|
return res.json();
|