@sleekcms/sync 1.4.4 → 1.6.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 +19 -4
- package/dist/AGENT.md +3 -3
- package/dist/cli.d.ts +5 -3
- package/dist/cli.js +27 -14
- package/dist/index.js +24 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -48,12 +48,15 @@ sleekcms --token <YOUR_AUTH_TOKEN>
|
|
|
48
48
|
## Usage
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
# Basic
|
|
51
|
+
# Basic — auto-sync on every save
|
|
52
52
|
npx @sleekcms/sync --token abc123-xxxx
|
|
53
53
|
|
|
54
|
+
# Manual sync — push changes only when you press [s]
|
|
55
|
+
npx @sleekcms/sync --token abc123-xxxx --manual
|
|
56
|
+
|
|
54
57
|
```
|
|
55
58
|
|
|
56
|
-
Once running, press `r` to re-fetch all files or `x` / `Ctrl+C` to exit.
|
|
59
|
+
Once running, press `r` to re-fetch all files or `x` / `Ctrl+C` to exit. In `--manual` mode, press `s` to push your changes on demand.
|
|
57
60
|
|
|
58
61
|
---
|
|
59
62
|
|
|
@@ -136,12 +139,24 @@ On first run, the CLI pulls the full site state from the server. After that, a l
|
|
|
136
139
|
Your editor → file save → watcher → SleekCMS API → rebuild → live site
|
|
137
140
|
```
|
|
138
141
|
|
|
139
|
-
###
|
|
142
|
+
### Manual sync mode (`--manual`)
|
|
143
|
+
|
|
144
|
+
Pass `--manual` (`-m`) to skip the live watcher. The CLI still does the initial fetch, but after that nothing is pushed until you press `s`. On each `s`, it scans the workspace, diffs against the cached state in `.cache/`, and pushes only the files that changed since your last fetch or sync — the same diff logic, run on demand instead of on every save.
|
|
145
|
+
|
|
146
|
+
This is useful when you want to batch a set of edits (or let an AI agent finish writing several files) and push them as one deliberate action.
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
Your editor → edit freely → press [s] → diff vs last fetch → SleekCMS API → live site
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Session commands
|
|
140
153
|
|
|
141
154
|
| Key | Action |
|
|
142
155
|
|---|---|
|
|
156
|
+
| `s` | Push changed files now (`--manual` mode only) |
|
|
143
157
|
| `r` | Re-fetch all files from server |
|
|
144
158
|
| `x` or `Ctrl+C` | Exit and clean up local workspace |
|
|
159
|
+
| `q` | Quit, keeping local files |
|
|
145
160
|
|
|
146
161
|
---
|
|
147
162
|
|
|
@@ -266,7 +281,7 @@ Models describe the shape of your content. They're JSON-like files with unquoted
|
|
|
266
281
|
| `json` | Object or array |
|
|
267
282
|
| `block(key)` | Embedded block object |
|
|
268
283
|
| `stack` | Array of heterogeneous block objects; each item carries `_block: "<block_key>"` to name its block |
|
|
269
|
-
| `entry(key)` |
|
|
284
|
+
| `entry(key)` | Reference token `"<index>\|<text>"` — `index` is the entry's 0-based position in `content/entries/<key>+.json` (what binds); the optional `<text>` is the entry's opening content copied verbatim (a substring check, not a summary you write). Resolves to the entry object in templates |
|
|
270
285
|
|
|
271
286
|
---
|
|
272
287
|
|
package/dist/AGENT.md
CHANGED
|
@@ -279,7 +279,7 @@ Model fields declare both the editor type and the shape expected in content JSON
|
|
|
279
279
|
| `block(key)` | Object matching that block's model; stored inline in the parent page/entry content and rendered with `blocks/<key>.ejs` |
|
|
280
280
|
| `[block(key)]` | Array of block objects; each item matches the block model and renders with `blocks/<key>.ejs` |
|
|
281
281
|
| `stack` | Array of heterogeneous block-shaped objects. Each item is `{ "_block": "<block_key>", ...fields_of_that_block }`. `_block` is the target block's key as a plain string; the remaining keys must match that block's model. Different items in the same stack may use different blocks. Stored inline; each item renders through its own `blocks/<key>.ejs`. The CMS resolves block keys to ids server-side — never write numeric ids. |
|
|
282
|
-
| `entry(key)` / `[entry(key)]` |
|
|
282
|
+
| `entry(key)` / `[entry(key)]` | Reference token `"<index>\|<text>"` (or an array of them) in content JSON; entry object / array of entry objects in templates. `index` is the 0-based position of the target entry in its `content/entries/<key>+.json` list — that is what binds. `\|<text>` is optional: the **opening characters of that entry's content, copied verbatim** (matched as a substring — not a description you compose), used only to relocate the entry if the list shifted. You may write just the index (`"2"` or `2`). |
|
|
283
283
|
| Group `{ ... }` | Nested object |
|
|
284
284
|
| Collection `[{ ... }]` | Array of nested objects |
|
|
285
285
|
|
|
@@ -324,11 +324,11 @@ The content file at `content/pages/about.json`:
|
|
|
324
324
|
"cta_label": "Contact us",
|
|
325
325
|
"cta_link": "/contact"
|
|
326
326
|
},
|
|
327
|
-
"tags": ["
|
|
327
|
+
"tags": ["0|Engineering", "2|Design"]
|
|
328
328
|
}
|
|
329
329
|
```
|
|
330
330
|
|
|
331
|
-
Here `hero` is embedded block data stored directly in the page content file. `image` and `hero.background` use the shortcut form — on save, the sync engine replaces each shortcut with a real image object (`{ "url": "...", "alt": "..." }`). Write the object form directly when you have a specific asset URL.
|
|
331
|
+
Here `hero` is embedded block data stored directly in the page content file. `image` and `hero.background` use the shortcut form — on save, the sync engine replaces each shortcut with a real image object (`{ "url": "...", "alt": "..." }`). Write the object form directly when you have a specific asset URL. `tags` references entries by position — `"0|Engineering"` points at the first entry in `content/entries/tags+.json`, where `Engineering` is just the start of that entry's content copied verbatim (a substring check, not a description you write). The index is what binds on save; the text only helps relocate the entry if the list shifted, and a bare index (`"0"`) is also accepted.
|
|
332
332
|
|
|
333
333
|
### Markdown content files
|
|
334
334
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -6,14 +6,16 @@ export interface CliOptions {
|
|
|
6
6
|
token?: string;
|
|
7
7
|
env?: string;
|
|
8
8
|
path?: string;
|
|
9
|
+
manual?: boolean;
|
|
9
10
|
}
|
|
10
11
|
export interface KeyboardHandlers {
|
|
11
12
|
onExit?: () => void | Promise<unknown>;
|
|
12
13
|
onQuit?: () => void | Promise<unknown>;
|
|
13
14
|
onRefetch?: () => void | Promise<unknown>;
|
|
15
|
+
onSync?: () => void | Promise<unknown>;
|
|
14
16
|
}
|
|
15
17
|
export declare function parseArgs(): CliOptions;
|
|
16
18
|
export declare function prompt(question: string): Promise<string>;
|
|
17
|
-
export declare function showWatchHelp(): void;
|
|
18
|
-
export declare function setupKeyboardInput(handlers: KeyboardHandlers): void;
|
|
19
|
-
export declare function showEditorMenu(viewsDir: string, handlers: KeyboardHandlers): void;
|
|
19
|
+
export declare function showWatchHelp(manual?: boolean): void;
|
|
20
|
+
export declare function setupKeyboardInput(handlers: KeyboardHandlers, manual?: boolean): void;
|
|
21
|
+
export declare function showEditorMenu(viewsDir: string, handlers: KeyboardHandlers, manual?: boolean): void;
|
package/dist/cli.js
CHANGED
|
@@ -23,11 +23,13 @@ function parseArgs() {
|
|
|
23
23
|
.option("-t, --token <token>", "API authentication token (required)")
|
|
24
24
|
.addOption(new commander_1.Option("-e, --env <env>", "Environment (localhost, development, production)").default("production").hideHelp())
|
|
25
25
|
.option("-p, --path <path>", "Directory path for files (default: <token-prefix>-views)")
|
|
26
|
+
.option("-m, --manual", "Manual sync mode: don't auto-sync on file changes; press [s] to push diffs on demand")
|
|
26
27
|
.addHelpText("after", `
|
|
27
28
|
Examples:
|
|
28
29
|
$ cms-sync --token abc123-xxxx
|
|
29
30
|
$ cms-sync -t abc123-xxxx -e development
|
|
30
31
|
$ cms-sync -t abc123-xxxx -p ./my-templates
|
|
32
|
+
$ cms-sync -t abc123-xxxx -m # manual sync — push only when you press [s]
|
|
31
33
|
`)
|
|
32
34
|
.parse(process.argv);
|
|
33
35
|
return commander_1.program.opts();
|
|
@@ -66,10 +68,11 @@ function commandExists(cmd) {
|
|
|
66
68
|
return false;
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
|
-
function showWatchHelp() {
|
|
70
|
-
|
|
71
|
+
function showWatchHelp(manual = false) {
|
|
72
|
+
const sync = manual ? "[s] Sync changes " : "";
|
|
73
|
+
console.log(`📋 Commands: ${sync}[r] Re-fetch all files [x] Exit (cleanup) [q] Quit (keep files)\n`);
|
|
71
74
|
}
|
|
72
|
-
function setupKeyboardInput(handlers) {
|
|
75
|
+
function setupKeyboardInput(handlers, manual = false) {
|
|
73
76
|
if (process.stdin.isTTY) {
|
|
74
77
|
process.stdin.setRawMode(true);
|
|
75
78
|
rawModeEnabled = true;
|
|
@@ -84,11 +87,16 @@ function setupKeyboardInput(handlers) {
|
|
|
84
87
|
return;
|
|
85
88
|
}
|
|
86
89
|
const cmd = key.toLowerCase();
|
|
87
|
-
if (cmd === "
|
|
90
|
+
if (cmd === "s" && handlers.onSync) {
|
|
91
|
+
await handlers.onSync();
|
|
92
|
+
showWatchHelp(manual);
|
|
93
|
+
}
|
|
94
|
+
else if (cmd === "r" && handlers.onRefetch) {
|
|
88
95
|
console.log("\n🔄 Re-fetching all files...");
|
|
89
96
|
await handlers.onRefetch();
|
|
90
|
-
|
|
91
|
-
|
|
97
|
+
if (!manual)
|
|
98
|
+
console.log("👀 Watching for changes...");
|
|
99
|
+
showWatchHelp(manual);
|
|
92
100
|
}
|
|
93
101
|
else if (cmd === "x" && handlers.onExit) {
|
|
94
102
|
await handlers.onExit();
|
|
@@ -102,14 +110,19 @@ const EDITOR_CANDIDATES = [
|
|
|
102
110
|
{ name: "VS Code", cmd: "code", args: (dir) => ["-n", dir] },
|
|
103
111
|
{ name: "Cursor", cmd: "cursor", args: (dir) => ["-n", dir] },
|
|
104
112
|
];
|
|
105
|
-
function
|
|
113
|
+
function statusLine(manual, suffix = "") {
|
|
114
|
+
const base = manual ? "✋ Manual sync mode — press [s] to push changes." : "👀 Watching for changes...";
|
|
115
|
+
console.log(`${base}${suffix}`);
|
|
116
|
+
}
|
|
117
|
+
function showEditorMenu(viewsDir, handlers, manual = false) {
|
|
106
118
|
const editors = EDITOR_CANDIDATES
|
|
107
119
|
.filter(e => commandExists(e.cmd))
|
|
108
120
|
.map((e, i) => ({ ...e, key: String(i + 1) }));
|
|
109
121
|
if (editors.length === 0) {
|
|
110
|
-
console.log("
|
|
111
|
-
|
|
112
|
-
|
|
122
|
+
console.log("");
|
|
123
|
+
statusLine(manual);
|
|
124
|
+
showWatchHelp(manual);
|
|
125
|
+
setupKeyboardInput(handlers, manual);
|
|
113
126
|
return;
|
|
114
127
|
}
|
|
115
128
|
console.log("\n📂 Open in editor:");
|
|
@@ -144,7 +157,7 @@ function showEditorMenu(viewsDir, handlers) {
|
|
|
144
157
|
}
|
|
145
158
|
const selected = editors.find(e => e.key === answer.trim());
|
|
146
159
|
if (selected) {
|
|
147
|
-
|
|
160
|
+
statusLine(manual, ` (opened ${selected.name})`);
|
|
148
161
|
(0, child_process_1.spawn)(selected.cmd, selected.args(viewsDir), {
|
|
149
162
|
detached: true,
|
|
150
163
|
stdio: "ignore",
|
|
@@ -152,9 +165,9 @@ function showEditorMenu(viewsDir, handlers) {
|
|
|
152
165
|
}).unref();
|
|
153
166
|
}
|
|
154
167
|
else {
|
|
155
|
-
|
|
168
|
+
statusLine(manual);
|
|
156
169
|
}
|
|
157
|
-
showWatchHelp();
|
|
158
|
-
setupKeyboardInput(handlers);
|
|
170
|
+
showWatchHelp(manual);
|
|
171
|
+
setupKeyboardInput(handlers, manual);
|
|
159
172
|
});
|
|
160
173
|
}
|
package/dist/index.js
CHANGED
|
@@ -127,6 +127,21 @@ async function handleQuit() {
|
|
|
127
127
|
console.log(`📁 Workspace left at: ${VIEWS_DIR}`);
|
|
128
128
|
process.exit(0);
|
|
129
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Manual sync trigger ([s] key). Scans the workspace on demand, diffs against
|
|
132
|
+
* the cached state in .cache/state.json, and pushes only changed files.
|
|
133
|
+
*/
|
|
134
|
+
async function handleManualSync() {
|
|
135
|
+
console.log("\n🔄 Syncing changes...");
|
|
136
|
+
try {
|
|
137
|
+
const { pushed } = await runSync();
|
|
138
|
+
console.log(pushed > 0 ? `✅ Synced ${pushed} change(s).` : "✔️ No changes to sync.");
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
const e = err;
|
|
142
|
+
console.error("❌ Sync failed:", e.body || e.message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
130
145
|
async function main() {
|
|
131
146
|
await initConfig();
|
|
132
147
|
try {
|
|
@@ -137,18 +152,24 @@ async function main() {
|
|
|
137
152
|
console.error("❌ Sync failed:", e.body || e.message);
|
|
138
153
|
process.exit(1);
|
|
139
154
|
}
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
const manual = options.manual ?? false;
|
|
156
|
+
if (!manual) {
|
|
157
|
+
watcher.init({ viewsDir: VIEWS_DIR, onSync: runSync, onIdle: handleExit });
|
|
158
|
+
watcher.monitorFiles();
|
|
159
|
+
}
|
|
142
160
|
console.log(`\n✅ Ready! Editing session started for site - ${site.name}.`);
|
|
143
161
|
console.log(`\n📁 Workspace created at: ${VIEWS_DIR}`);
|
|
144
162
|
if (ENV !== "production")
|
|
145
163
|
console.log(`🌐 Environment: ${ENV}`);
|
|
164
|
+
if (manual)
|
|
165
|
+
console.log(`\n✋ Manual sync mode — changes are pushed only when you press [s].`);
|
|
146
166
|
console.log(`\n⚠️ Files will be cleaned up on exit (Ctrl+C).`);
|
|
147
167
|
cli.showEditorMenu(VIEWS_DIR, {
|
|
148
168
|
onExit: handleExit,
|
|
149
169
|
onQuit: handleQuit,
|
|
150
170
|
onRefetch: () => runSync({ flush: true }),
|
|
151
|
-
|
|
171
|
+
...(manual ? { onSync: handleManualSync } : {}),
|
|
172
|
+
}, manual);
|
|
152
173
|
process.on("SIGINT", async () => {
|
|
153
174
|
console.log("\n🛑 Caught interrupt signal (Ctrl+C)");
|
|
154
175
|
await handleExit();
|