@sleekcms/sync 1.7.0 โ 2.1.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/dist/AGENT.md +262 -535
- package/dist/index.js +9 -181
- package/package.json +11 -6
- package/dist/cli.d.ts +0 -21
- package/dist/cli.js +0 -173
- package/dist/index.d.ts +0 -9
- package/dist/setup-site.d.ts +0 -30
- package/dist/setup-site.js +0 -299
- package/dist/sync-site.d.ts +0 -14
- package/dist/sync-site.js +0 -49
- package/dist/watcher.d.ts +0 -18
- package/dist/watcher.js +0 -122
package/dist/index.js
CHANGED
|
@@ -1,182 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
-
}
|
|
16
|
-
Object.defineProperty(o, k2, desc);
|
|
17
|
-
}) : (function(o, m, k, k2) {
|
|
18
|
-
if (k2 === undefined) k2 = k;
|
|
19
|
-
o[k2] = m[k];
|
|
20
|
-
}));
|
|
21
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
-
}) : function(o, v) {
|
|
24
|
-
o["default"] = v;
|
|
25
|
-
});
|
|
26
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
-
var ownKeys = function(o) {
|
|
28
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
-
var ar = [];
|
|
30
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
-
return ar;
|
|
32
|
-
};
|
|
33
|
-
return ownKeys(o);
|
|
34
|
-
};
|
|
35
|
-
return function (mod) {
|
|
36
|
-
if (mod && mod.__esModule) return mod;
|
|
37
|
-
var result = {};
|
|
38
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
-
__setModuleDefault(result, mod);
|
|
40
|
-
return result;
|
|
41
|
-
};
|
|
42
|
-
})();
|
|
43
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
44
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
|
-
};
|
|
46
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
48
|
-
const os_1 = __importDefault(require("os"));
|
|
49
|
-
const path_1 = __importDefault(require("path"));
|
|
50
|
-
const cli = __importStar(require("./cli"));
|
|
51
|
-
const watcher = __importStar(require("./watcher"));
|
|
52
|
-
const setup_site_1 = require("./setup-site");
|
|
53
|
-
const agentMdContent = fs_extra_1.default.readFileSync(path_1.default.join(__dirname, "AGENT.md"), "utf-8");
|
|
54
|
-
const options = cli.parseArgs();
|
|
55
|
-
if (options.version) {
|
|
56
|
-
// package.json sits one directory above the compiled output
|
|
57
|
-
const pkg = require("../package.json");
|
|
58
|
-
console.log(pkg.version);
|
|
59
|
-
process.exit(0);
|
|
60
|
-
}
|
|
61
|
-
let VIEWS_DIR = null;
|
|
62
|
-
let ENV = null;
|
|
63
|
-
let TOKEN = null;
|
|
64
|
-
let site = null;
|
|
65
|
-
let isShuttingDown = false;
|
|
66
|
-
async function cleanupFiles(dir) {
|
|
67
|
-
if (!dir)
|
|
68
|
-
return;
|
|
69
|
-
console.log("๐งน Cleaning up files...");
|
|
70
|
-
try {
|
|
71
|
-
await fs_extra_1.default.remove(dir);
|
|
72
|
-
console.log(`โ
Cleanup complete. Deleted workspace at ${dir}.`);
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
const e = err;
|
|
76
|
-
console.error("โ Error during cleanup:", e.message);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
async function runSync({ flush = false } = {}) {
|
|
80
|
-
const result = await (0, setup_site_1.syncSite)({
|
|
81
|
-
token: TOKEN,
|
|
82
|
-
viewsDir: VIEWS_DIR ?? undefined,
|
|
83
|
-
path: VIEWS_DIR ? undefined : options.path,
|
|
84
|
-
env: ENV ?? undefined,
|
|
85
|
-
agentMd: agentMdContent,
|
|
86
|
-
flush,
|
|
87
|
-
});
|
|
88
|
-
VIEWS_DIR = result.viewsDir;
|
|
89
|
-
site = result.site;
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
async function initConfig() {
|
|
93
|
-
TOKEN = options.token ?? null;
|
|
94
|
-
if (!TOKEN) {
|
|
95
|
-
TOKEN = await cli.prompt("Enter SleekCMS CLI auth token: ");
|
|
96
|
-
if (!TOKEN) {
|
|
97
|
-
console.error("โ Token is required.");
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const tokenParts = TOKEN.trim().split("-");
|
|
102
|
-
ENV = (tokenParts[2] || options.env || "production").toLowerCase();
|
|
103
|
-
let customPath = options.path;
|
|
104
|
-
if (customPath && customPath.startsWith("~")) {
|
|
105
|
-
customPath = path_1.default.join(os_1.default.homedir(), customPath.slice(1));
|
|
106
|
-
}
|
|
107
|
-
options.path = customPath;
|
|
108
|
-
}
|
|
109
|
-
async function handleExit() {
|
|
110
|
-
if (isShuttingDown)
|
|
111
|
-
return;
|
|
112
|
-
isShuttingDown = true;
|
|
113
|
-
watcher.setShuttingDown(true);
|
|
114
|
-
console.log("\nโ ๏ธ Shutting down...");
|
|
115
|
-
await watcher.stopWatching();
|
|
116
|
-
await cleanupFiles(VIEWS_DIR);
|
|
117
|
-
process.exit(0);
|
|
118
|
-
}
|
|
119
|
-
async function handleQuit() {
|
|
120
|
-
if (isShuttingDown)
|
|
121
|
-
return;
|
|
122
|
-
isShuttingDown = true;
|
|
123
|
-
watcher.setShuttingDown(true);
|
|
124
|
-
console.log("\nโ ๏ธ Quitting (keeping files)...");
|
|
125
|
-
await watcher.stopWatching();
|
|
126
|
-
if (VIEWS_DIR)
|
|
127
|
-
console.log(`๐ Workspace left at: ${VIEWS_DIR}`);
|
|
128
|
-
process.exit(0);
|
|
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
|
-
}
|
|
145
|
-
async function main() {
|
|
146
|
-
await initConfig();
|
|
147
|
-
try {
|
|
148
|
-
await runSync();
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
const e = err;
|
|
152
|
-
console.error("โ Sync failed:", e.body || e.message);
|
|
153
|
-
process.exit(1);
|
|
154
|
-
}
|
|
155
|
-
const manual = options.manual ?? false;
|
|
156
|
-
if (!manual) {
|
|
157
|
-
watcher.init({ viewsDir: VIEWS_DIR, onSync: runSync, onIdle: handleExit });
|
|
158
|
-
watcher.monitorFiles();
|
|
159
|
-
}
|
|
160
|
-
console.log(`\nโ
Ready! Editing session started for site - ${site.name}.`);
|
|
161
|
-
console.log(`\n๐ Workspace created at: ${VIEWS_DIR}`);
|
|
162
|
-
if (ENV !== "production")
|
|
163
|
-
console.log(`๐ Environment: ${ENV}`);
|
|
164
|
-
if (manual)
|
|
165
|
-
console.log(`\nโ Manual sync mode โ changes are pushed only when you press [s].`);
|
|
166
|
-
console.log(`\nโ ๏ธ Files will be cleaned up on exit (Ctrl+C).`);
|
|
167
|
-
cli.showEditorMenu(VIEWS_DIR, {
|
|
168
|
-
onExit: handleExit,
|
|
169
|
-
onQuit: handleQuit,
|
|
170
|
-
onRefetch: () => runSync({ flush: true }),
|
|
171
|
-
...(manual ? { onSync: handleManualSync } : {}),
|
|
172
|
-
}, manual);
|
|
173
|
-
process.on("SIGINT", async () => {
|
|
174
|
-
console.log("\n๐ Caught interrupt signal (Ctrl+C)");
|
|
175
|
-
await handleExit();
|
|
176
|
-
});
|
|
177
|
-
process.on("SIGTERM", async () => {
|
|
178
|
-
console.log("\n๐ Caught termination signal");
|
|
179
|
-
await handleExit();
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
main();
|
|
2
|
+
import{readFileSync as hn}from"node:fs";import{fileURLToPath as pn}from"node:url";import{createRequire as mn}from"node:module";import pe from"node:path";import{render as fn}from"ink";import{execSync as _t,spawn as N}from"child_process";import{program as It,Option as gt}from"commander";function Ft(){return It.name("cms-sync").description("SleekCMS CLI tool to sync and edit CMS templates locally. Log in once, then add the sites you want to sync \u2014 changes are watched and pushed automatically.").addOption(new gt("-v, --version","output the version number").hideHelp()).addOption(new gt("-e, --env <env>","Environment (localhost, development, production)").default("production").hideHelp()).addOption(new gt("-d, --dev <secret>","Dev secret for non-production envs (sent as the _dev cookie; enables OTP-in-response). Falls back to SLEEKCMS_DEV_SECRET.").hideHelp()).option("-p, --path <path>","Directory path for files (default: ~/SleekCMS)").addHelpText("after",`
|
|
3
|
+
Examples:
|
|
4
|
+
$ sleekcms # log in and pick sites to sync
|
|
5
|
+
$ sleekcms -e development # use a non-production backend
|
|
6
|
+
`).parse(process.argv),It.opts()}function L(t){let e=process.platform==="win32"?`where ${t}`:`which ${t}`;try{return _t(e,{stdio:"ignore"}),!0}catch{return!1}}function jt(t){if(process.platform!=="darwin")return!1;try{return _t(`osascript -e 'id of application "${t}"'`,{stdio:"ignore"}),!0}catch{return!1}}var Lt=[{name:"VS Code",cmd:"code",args:t=>["-n",t]},{name:"Cursor",cmd:"cursor",args:t=>["-n",t]}];function Bt(t,e){let r=(e?Lt.filter(i=>i.cmd===e):Lt).find(i=>L(i.cmd));return r?(N(r.cmd,r.args(t),{detached:!0,stdio:"ignore"}).unref(),r.name):null}function Ee(t){if(process.platform==="darwin")return N("open",["-a","Terminal",t],{detached:!0,stdio:"ignore"}).unref(),!0;if(process.platform==="win32"){if(L("wt"))N("wt.exe",["-d",t],{detached:!0,stdio:"ignore"}).unref();else{let e=L("powershell")?"powershell.exe":"cmd.exe";N("cmd.exe",["/c","start","",e],{cwd:t,detached:!0,stdio:"ignore"}).unref()}return!0}return!1}function Ce(t){return jt("iTerm")?(N("open",["-a","iTerm",t],{detached:!0,stdio:"ignore"}).unref(),!0):!1}function Me(t){if(process.platform!=="darwin"||!L("claude"))return!1;let e=`tell application "Terminal" to do script ("cd " & quoted form of "${t}" & " && claude")`;return N("osascript",["-e",e,"-e",'tell application "Terminal" to activate'],{detached:!0,stdio:"ignore"}).unref(),!0}function Nt(){let t=process.platform==="darwin",e=process.platform==="win32",n=[];return(t||e)&&n.push({key:"terminal",label:"terminal",run:Ee}),jt("iTerm")&&n.push({key:"iterm",label:"iterm",run:Ce}),L("code")&&n.push({key:"code",label:"vscode",run:r=>Bt(r,"code")!==null}),L("cursor")&&n.push({key:"cursor",label:"cursor",run:r=>Bt(r,"cursor")!==null}),t&&L("claude")&&n.push({key:"claude",label:"claude",run:Me}),n}import{EventEmitter as Ye}from"node:events";import ee from"fs-extra";import ne from"path";import w from"fs-extra";import Pe from"os";import b from"path";var Gt={localhost:"http://app.sleekcms.test/api/mcp",development:"https://app.sleekcms.dev/api/mcp",production:"https://app.sleekcms.com/api/mcp"},De=["src/views/blocks","src/views/entries","src/views/pages","src/views/layouts","src/models/blocks","src/models/entries","src/models/pages","src/content/pages","src/content/entries","src/public/js","src/public/css"],dt=class extends Error{status;body;constructor(e,n,r){super(e),this.status=n,this.body=r}};async function pt(t,e,n,r,i){let s=await fetch(t+r,{method:n,headers:{Authorization:`Bearer ${e}`,...i!==void 0?{"Content-Type":"application/json"}:{}},body:i!==void 0?JSON.stringify(i):void 0});if(!s.ok){let a=await s.text();throw new dt(`${n} ${r} \u2192 ${s.status}: ${a}`,s.status,a)}return s.json()}async function $e(t,e){e&&(await w.outputFile(b.join(t,"AGENT.md"),e),await w.outputFile(b.join(t,"CLAUDE.md"),e),await w.outputFile(b.join(t,".vscode","copilot-instructions.md"),e)),await w.outputFile(b.join(t,".vscode","settings.json"),JSON.stringify({"files.associations":{"*.model":"javascript"},"[javascript]":{"editor.defaultFormatter":"esbenp.prettier-vscode"},"js/ts.validate.enabled":!1},null,2))}async function Ae(t,e){let n=b.join(t,".cache","token");if(await w.pathExists(n)){if((await w.readFile(n,"utf-8")).trim()!==e)throw new Error(`Workspace at ${t} is tied to a different token. Remove ${n} or use a different workspace.`);return}await w.outputFile(n,e)}function Re(t,e){let n=`${e.name.substr(0,20)} ${e.id}`.replace(/[\s_]+/g,"-").toLowerCase(),r=t||b.join(Pe.homedir(),"SleekCMS");return b.resolve(r,n)}async function mt(t){let e=(t.token||"").trim();if(!e)throw new Error("syncSite: token is required");let n=t.log??(v=>console.log(v)),r=t.onProgress,i=(t.env||e.split("-")[2]||"production").toLowerCase(),s=Gt[i]||Gt.production,a=await pt(s,e,"GET","/get_site"),d=t.viewsDir?b.resolve(t.viewsDir):Re(t.path,a);await w.ensureDir(d),await Promise.all(De.map(v=>w.ensureDir(b.join(d,v)))),await Ae(d,e);let h=b.join(d,".cache","state.json");t.flush&&await w.remove(h);let S=!await w.pathExists(h),f=S?{}:(await w.readJson(h)).fileMap||{},p=0,y=0;return S?({fileMap:f,pulled:y}=await We(d,s,e,n,r),await $e(d,t.agentMd)):p=await Be(d,f,s,e,n,r),await w.outputJson(h,{fileMap:f},{spaces:2}),{viewsDir:d,site:a,isFirstRun:S,pushed:p,pulled:y}}var Ie=new Set([".DS_Store","Thumbs.db","desktop.ini"]);async function Le(t){let e=b.join(t,"src");if(!await w.pathExists(e))return[];let n=[];async function r(i){let s=await w.readdir(i,{withFileTypes:!0});for(let a of s){if(Ie.has(a.name))continue;let d=b.join(i,a.name);a.isDirectory()?await r(d):a.isFile()&&n.push(b.relative(t,d).replace(/\\/g,"/"))}}return await r(e),n}async function Be(t,e,n,r,i,s){let a=[];for(let p of await Le(t)){let y=b.join(t,p),v=e[p],O=await w.stat(y);if(v&&v.mtimeMs===O.mtimeMs)continue;let D=await w.readFile(y,"utf-8");D.trim()&&a.push({rel:p,full:y,stat:O,content:D,prior:v})}if(a.length===0)return 0;let d;try{d=await pt(n,r,"POST","/save_files",a.map(p=>({path:p.rel,content:p.content})))}catch(p){let y=p;return i(`\u274C Error saving files: ${y.body||y.message}`),0}let h=await je(t),S=0,f=_e(d);for(let p=0;p<a.length;p++){let y=a[p],v=Fe(y,p,d,f);if(s?.(p+1,a.length),!v){let O="No save response returned";h[y.rel]=O,i(`\u274C Error saving ${y.rel}: ${O}`);continue}if(v.error){h[y.rel]=v.error,i(`\u274C Error saving ${y.rel}: ${v.error}`);continue}delete h[y.rel],e[y.rel]={mtimeMs:y.stat.mtimeMs},i(`\u2705 ${y.prior?"Updated":"Created"} ${y.rel}`),S++}return await Ge(t,h),S}function _e(t){let e=new Map;for(let n of t)n.path&&e.set(n.path,n);return e}function Fe(t,e,n,r){let i=r.get(t.rel);if(i)return i;let s=n[e];if(!s?.path||s.path===t.rel)return s}var ft="sync-errors.log",ht=new Map;async function je(t){let e=b.resolve(t),n=ht.get(e);if(n)return n;let r=await Ne(e);return ht.set(e,r),r}async function Ne(t){let e=b.join(t,ft);if(!await w.pathExists(e))return{};let n=await w.readFile(e,"utf-8"),r={};for(let i of n.split(`
|
|
7
|
+
`)){let s=i.indexOf(": ");s>0&&(r[i.slice(0,s)]=i.slice(s+2))}return r}async function Ge(t,e){let n=b.join(t,ft),r=Object.entries(e);if(r.length===0){await w.remove(n);return}await w.outputFile(n,r.map(([i,s])=>`${i}: ${s}`).join(`
|
|
8
|
+
`)+`
|
|
9
|
+
`)}async function We(t,e,n,r,i){ht.delete(b.resolve(t)),await w.remove(b.join(t,ft)),r("\u{1F4E5} Fetching files...");let s=await pt(e,n,"GET","/get_files"),a={},d=0;for(let h of s){let S=b.join(t,h.path);await w.outputFile(S,h.content);let f=(await w.stat(S)).mtimeMs;d++,i?.(d,s.length),a[h.path]={mtimeMs:f}}return r(`\u2714\uFE0F Synced ${s.length} file(s).`),{fileMap:a,pulled:d}}import qe from"path";import Je from"chokidar";var Ue=5e3,Wt=3600*1e3,He=60*1e3,Xe=/(^|[/\\])(\.DS_Store|Thumbs\.db|desktop\.ini)$/,z=class{viewsDir;onSync;onActivity;debounceMs;watcher=null;debounceTimer=null;isShuttingDown=!1;dirty=!1;syncInFlight=!1;constructor(e){this.viewsDir=e.viewsDir,this.onSync=e.onSync,this.onActivity=e.onActivity??(()=>{}),this.debounceMs=e.debounceMs??Ue}start(){if(this.watcher)return;let e=qe.join(this.viewsDir,"src");this.watcher=Je.watch([e],{persistent:!0,ignoreInitial:!0,ignored:Xe}).on("change",()=>this.scheduleSync()).on("add",()=>this.scheduleSync()).on("unlink",()=>this.scheduleSync())}setShuttingDown(e){this.isShuttingDown=e}async stop(){this.debounceTimer&&(clearTimeout(this.debounceTimer),this.debounceTimer=null),this.watcher&&(await this.watcher.close(),this.watcher=null)}scheduleSync(){this.isShuttingDown||(this.dirty=!0,this.debounceTimer&&clearTimeout(this.debounceTimer),this.debounceTimer=setTimeout(()=>this.flush(),this.debounceMs),W(),this.onActivity())}async flush(){if(this.debounceTimer=null,!(!this.dirty||this.isShuttingDown)){if(this.syncInFlight){this.debounceTimer=setTimeout(()=>this.flush(),this.debounceMs);return}this.dirty=!1,this.syncInFlight=!0;try{await this.onSync()}catch(e){let n=e;console.error("\u274C Sync failed:",n.body||n.message)}finally{this.syncInFlight=!1}}}},G=null,St=0,yt=null,wt=Wt,xt=!1;function qt(t,e=Wt){yt=t,wt=e,xt=!1,St=Date.now(),!G&&(G=setInterval(()=>{xt||!yt||Date.now()-St<wt||(bt(),console.log(`
|
|
10
|
+
\u{1F4A4} No changes for ${Math.round(wt/6e4)} minutes. Terminating.`),yt())},He),G.unref?.())}function W(){St=Date.now()}function bt(){xt=!0,G&&(clearInterval(G),G=null)}import nt from"fs-extra";import ze from"os";import Ut from"path";var Jt={localhost:"http://app.sleekcms.test/api",development:"https://app.sleekcms.dev/api",production:"https://app.sleekcms.com/api"};function Tt(t){return Jt[t]||Jt.production}var Ke=Ut.join(ze.homedir(),"SleekCMS"),rt=Ut.join(Ke,"auth.json"),vt;function Ht(t){vt=t||void 0}function kt(t){let e=[];return t&&e.push(`_auth=${t}`),vt&&e.push(`_dev=${vt}`),e.length?e.join("; "):void 0}var P=class extends Error{status;body;constructor(e,n,r=""){super(e),this.name="UserAuthError",this.status=n,this.body=r}};async function Xt(){try{let t=await nt.readJson(rt);return t&&t.userToken?t:null}catch{return null}}async function zt(t){await nt.outputJson(rt,t,{spaces:2});try{await nt.chmod(rt,384)}catch{}}async function Kt(){await nt.remove(rt)}async function K(t){let e={},n=kt(t.userToken);n&&(e.Cookie=n),t.subdomain&&(e["x-subdomain"]=t.subdomain),t.slug&&(e.site=t.slug),t.body!==void 0&&(e["Content-Type"]="application/json");let r=await fetch(Tt(t.env)+t.path,{method:t.method,headers:e,body:t.body!==void 0?JSON.stringify(t.body):void 0});if(!r.ok){let i=await r.text().catch(()=>"");throw new P(`${t.method} ${t.path} \u2192 ${r.status}: ${i}`,r.status,i)}return r.json()}async function Vt(t,e){let n={"Content-Type":"application/json"},r=kt();r&&(n.Cookie=r);let i=await fetch(Tt(t)+"/auth/otp",{method:"PUT",headers:n,body:JSON.stringify({email:e})});if(!i.ok){let a=await i.text().catch(()=>"");throw new P(`Failed to send OTP \u2192 ${i.status}: ${a}`,i.status,a)}return{otp:(await i.json().catch(()=>({}))).do_not_use_in_code}}async function Et(t,e,n){let r={"Content-Type":"application/json"},i=kt();i&&(r.Cookie=i);let s=await fetch(Tt(t)+"/auth/login",{method:"POST",headers:r,body:JSON.stringify({email:e,otp:n})});if(!s.ok){let h=await s.text().catch(()=>"");throw new P(`Login failed \u2192 ${s.status}: ${h}`,s.status,h)}let a=s.headers.getSetCookie?.()??[],d=Ve(a);if(!d)throw new P("Login succeeded but no _auth cookie was returned.",500);return d}function Ve(t){for(let e of t){let n=e.match(/(?:^|;\s*)_auth=([^;]+)/);if(n&&n[1])return decodeURIComponent(n[1])}return null}async function Ct(t,e){return K({env:t,userToken:e,method:"GET",path:"/org"})}async function Yt(t,e,n){return K({env:t,userToken:e,method:"GET",path:"/site",subdomain:n})}async function Qt(t,e,n,r){let{token:i}=await K({env:t,userToken:e,method:"POST",path:"/site_user/api_token",subdomain:n,slug:r,body:{refresh:!1}});return i}async function Zt(t,e,n){let r=await K({env:t,userToken:e,method:"POST",path:"/org",body:n?{name:n}:{}});return{id:r.id,name:r.name,subdomain:r.subdomain}}async function te(t,e,n,r){let i=await K({env:t,userToken:e,method:"POST",path:"/site",subdomain:n,body:{name:r}});return{id:i.id,name:i.name,slug:i.slug}}var re=500;function Qe(){return new Date().toTimeString().slice(0,8)}function it(t){let e=t;if(e.body){try{let n=JSON.parse(e.body);if(n&&typeof n.message=="string")return n.message;if(n&&typeof n.error=="string")return n.error}catch{}return e.body}return e.message}async function ie(t){t&&(await ee.remove(ne.join(t,"src")),await ee.remove(ne.join(t,".cache","state.json")))}var st=class extends Ye{env="production";userToken="";email;catalog=[];synced=new Map;message;messageIsError=!1;agentMd;isShuttingDown=!1;constructor(e){super(),this.agentMd=e}changed(){this.emit("change")}flash(e,n=!1){this.message=e,this.messageIsError=n,this.changed()}appendLog(e,n){let r=this.synced.get(e);r&&(r.logs.push({time:Qe(),text:n}),r.logs.length>re&&r.logs.splice(0,r.logs.length-re),this.changed())}siteCount(){return this.synced.size}syncingCount(){let e=0;for(let n of this.synced.values())n.status==="syncing"&&e++;return e}watchingCount(){let e=0;for(let n of this.synced.values())(n.status==="watching"||n.status==="syncing")&&e++;return e}async init(e,n,r){this.env=e,this.userToken=n,r&&(this.email=r);let i=await Ct(e,n);await this.buildCatalog(i),qt(()=>{this.exit()})}async buildCatalog(e){let n=[];for(let r of e)try{n.push({org:r,sites:await Yt(this.env,this.userToken,r.subdomain)})}catch(i){if(i instanceof P&&i.status===401)throw i;n.push({org:r,sites:[]})}this.catalog=n,this.changed()}isSynced(e){return this.synced.has(e)}availableSites(){let e=[];for(let n of this.catalog)for(let r of n.sites)this.synced.has(r.id)||e.push({org:n.org,site:r});return e}async addSite(e,n){if(this.synced.has(n.id))return;let r;try{r=await Qt(this.env,this.userToken,e.subdomain,n.slug)}catch(s){this.flash(`Failed to authorize "${n.name}": ${it(s)}`,!0);return}let i={org:e,site:n,siteToken:r,viewsDir:"",status:"syncing",logs:[]};this.synced.set(n.id,i),this.appendLog(n.id,"\u2795 added \u2014 pulling files"),W(),this.changed();try{let s=await mt({token:r,env:this.env,agentMd:this.agentMd,log:a=>this.appendLog(n.id,a),onProgress:(a,d)=>{i.progress={done:a,total:d},this.changed()}});i.viewsDir=s.viewsDir,i.progress=void 0,i.status="watching",i.lastSyncedAt=Date.now(),i.lastMsg=`pulled ${s.pulled} file(s)`,i.watcher=new z({viewsDir:s.viewsDir,onSync:()=>this.runSync(n.id),onActivity:W}),i.watcher.start(),this.appendLog(n.id,"\u{1F440} watching for changes")}catch(s){let a=s;i.status="error",i.progress=void 0,i.lastMsg=a.body||a.message,this.appendLog(n.id,`\u274C ${a.body||a.message}`)}this.changed()}async removeSite(e){let n=this.synced.get(e);n&&(n.watcher?.setShuttingDown(!0),await n.watcher?.stop(),await ie(n.viewsDir),this.synced.delete(e),W(),this.changed())}async ensureOrg(e){if(e)return e;if(this.catalog[0])return this.catalog[0].org;let n;try{n=await Ct(this.env,this.userToken)}catch(r){return this.flash(`Couldn't check your organizations: ${it(r)}`,!0),null}if(n.length>0){try{await this.buildCatalog(n)}catch{}return n[0]}try{let r=this.email?`${this.email.split("@")[0]}'s Sites`:"My Sites";this.flash("No organization yet \u2014 creating one\u2026");let i=await Zt(this.env,this.userToken,r);return this.catalog=[...this.catalog,{org:i,sites:[]}],this.changed(),i}catch(r){return this.flash(`Could not create an organization: ${it(r)}`,!0),null}}async createNewSite(e,n){let r=await this.ensureOrg(n);if(!r)return;let i;try{i=await te(this.env,this.userToken,r.subdomain,e)}catch(a){this.flash(`Could not create "${e}": ${it(a)}`,!0);return}let s=this.catalog.find(a=>a.org.id===r.id);s&&!s.sites.some(a=>a.id===i.id)&&(s.sites=[...s.sites,i]),this.flash(`Created "${i.name}".`),await this.addSite(r,i)}async refetch(e){await this.runSync(e,{flush:!0})}async runSync(e,n={}){let r=this.synced.get(e);if(!(!r||!r.viewsDir||r.status==="paused")){r.status="syncing",this.changed();try{let i=await mt({token:r.siteToken,env:this.env,viewsDir:r.viewsDir,agentMd:this.agentMd,flush:n.flush,log:s=>this.appendLog(e,s),onProgress:(s,a)=>{r.progress={done:s,total:a},this.changed()}});r.progress=void 0,r.status="watching",r.lastSyncedAt=Date.now(),n.flush?r.lastMsg=`re-fetched ${i.pulled} file(s)`:r.lastMsg=i.pushed>0?`pushed ${i.pushed} file(s)`:"up to date"}catch(i){let s=i;r.status="error",r.progress=void 0,r.lastMsg=s.body||s.message,this.appendLog(e,`\u274C ${s.body||s.message}`)}this.changed()}}async togglePause(e){let n=this.synced.get(e);!n||!n.viewsDir||(n.status==="paused"?(n.watcher=new z({viewsDir:n.viewsDir,onSync:()=>this.runSync(e),onActivity:W}),n.watcher.start(),n.status="watching",this.appendLog(e,"\u25B6 resumed \u2014 watching for changes")):(n.watcher?.setShuttingDown(!0),await n.watcher?.stop(),n.watcher=void 0,n.progress=void 0,n.status="paused",this.appendLog(e,"\u23F8 paused")),this.changed())}async cleanupAll(){bt();for(let e of this.synced.values())e.watcher?.setShuttingDown(!0),await e.watcher?.stop(),await ie(e.viewsDir)}async exit(){this.isShuttingDown||(this.isShuttingDown=!0,await this.cleanupAll(),this.emit("exit"))}async logout(){this.isShuttingDown||(this.isShuttingDown=!0,await Kt(),await this.cleanupAll(),this.emit("logout"))}};import{useEffect as un,useState as ge}from"react";import{Box as ln,Text as gn,useInput as dn}from"ink";import{useState as V}from"react";import{Box as Y,Text as q}from"ink";import se from"ink-text-input";import{jsx as B,jsxs as J}from"react/jsx-runtime";function oe(t){let e=t;return e?.body||e?.message||String(t)}function ae({env:t,initialError:e,onAuthed:n}){let[r,i]=V("email"),[s,a]=V(""),[d,h]=V(""),[S,f]=V(e),[p,y]=V(),v=async D=>{let T=D.trim();if(T){f(void 0),i("busy");try{let{otp:E}=await Vt(t,T);if(a(T),E){let U=await Et(t,T,E);await n(U,T);return}y(`OTP sent to ${T}`),i("otp")}catch(E){f(oe(E)),i("email")}}},O=async D=>{let T=D.trim();if(T){f(void 0),i("busy");try{let E=await Et(t,s,T);await n(E,s)}catch(E){f(oe(E)),h(""),i("otp")}}};return J(Y,{flexDirection:"column",children:[J(q,{bold:!0,children:["\u25C6 SleekCMS \u2014 sign in",t!=="production"?` (${t})`:""]}),S?J(q,{color:"red",children:["\u2717 ",S]}):null,r==="email"&&J(Y,{marginTop:1,children:[B(q,{children:"Email: "}),B(se,{value:s,onChange:a,onSubmit:v,placeholder:"you@example.com"})]}),r==="otp"&&J(Y,{flexDirection:"column",marginTop:1,children:[p?B(q,{color:"green",children:p}):null,J(Y,{children:[B(q,{children:"Enter OTP: "}),B(se,{value:d,onChange:h,onSubmit:O})]})]}),r==="busy"&&B(Y,{marginTop:1,children:B(q,{children:"Working\u2026"})})]})}import{Fragment as tn,useEffect as Ot,useMemo as en,useState as $}from"react";import{Box as m,Text as u,useInput as nn,useStdout as rn}from"ink";import ue from"ink-text-input";import Ze from"node:os";function Mt(t){if(!t)return"\u2014";let e=Ze.homedir();return t.startsWith(e)?"~"+t.slice(e.length):t}function ce(t){if(!t)return"\u2014";let e=Math.floor((Date.now()-t)/1e3);if(e<5)return"just now";if(e<60)return`${e}s ago`;let n=Math.floor(e/60);if(n<60)return`${n}m ago`;let r=Math.floor(n/60);return r<24?`${r}h ago`:`${Math.floor(r/24)}d ago`}function ot(t){return!t||t.total<=0?null:Math.round(t.done/t.total*100)}function at(t){switch(t){case"syncing":return{dot:"\u25CF",color:"green",label:"syncing"};case"watching":return{dot:"\u25CB",color:"gray",label:"watching"};case"paused":return{dot:"\u2016",color:"yellow",label:"paused"};case"error":return{dot:"\u2717",color:"red",label:"error"}}}import{Fragment as ct,jsx as o,jsxs as g}from"react/jsx-runtime";var _=(t,e)=>e<=0?0:Math.max(0,Math.min(t,e-1)),sn=16,on=5;function an({k:t}){return o(u,{color:"black",backgroundColor:"green",children:` ${t} `})}function A({k:t,label:e,width:n=16}){return g(m,{width:n,children:[o(an,{k:t}),o(u,{color:"gray",children:` ${e}`})]})}function cn({percent:t,width:e=28}){let n=Math.round(Math.max(0,Math.min(100,t))/100*e);return g(u,{children:[o(u,{color:"green",children:"\u2588".repeat(n)}),o(u,{color:"gray",children:"\u2591".repeat(Math.max(0,e-n))})]})}function le({controller:t}){let{stdout:e}=rn(),n=en(()=>Nt(),[]),[,r]=$(0),[i,s]=$(()=>new Date),[a,d]=$(null),[h,S]=$(()=>t.synced.size>0||t.catalog.some(l=>l.sites.length>0)?"dashboard":t.catalog.length<=1?"newName":"newOrg"),[f,p]=$(0),[y,v]=$(""),[O,D]=$(!1),[T,E]=$(""),[U,Q]=$(!1),Dt=()=>r(c=>c+1);Ot(()=>(t.on("change",Dt),()=>{t.off("change",Dt)}),[t]),Ot(()=>{let c=setInterval(()=>s(new Date),1e3);return()=>clearInterval(c)},[]);let R=t.catalog.flatMap(c=>c.sites.map(l=>({org:c.org,site:l,entry:t.synced.get(l.id)}))).sort((c,l)=>(c.entry?0:1)-(l.entry?0:1)),I=T.trim().toLowerCase(),C=I?R.filter(c=>c.site.name.toLowerCase().includes(I)||(c.org.name||c.org.subdomain||"").toLowerCase().includes(I)):R,F=C.findIndex(c=>c.site.id===a);F<0&&(F=0);let k=C[F],x=k?.entry,$t=C.findIndex(c=>!c.entry),j=Math.max(on,(e?.rows??30)-sn),H=C.length,Z=H>j,me=Math.floor(j/2),X=Z?Math.max(0,Math.min(F-me,H-j)):0,lt=Math.min(H,X+j),fe=C.slice(X,lt);Ot(()=>{h!=="dashboard"||O||R.length>0||(v(""),t.catalog.length<=1?S("newName"):(p(0),S("newOrg")))},[R.length,h,O,t.catalog.length]);let tt=c=>{if(C.length===0)return;let l=_(F+c,C.length);d(C[l].site.id)},ye=()=>{k&&(k.entry?t.removeSite(k.site.id):t.addSite(k.org,k.site))},we=()=>{v(""),t.catalog.length<=1?S("newName"):(p(0),S("newOrg"))},Se=c=>{if(!x){t.flash("Press \u23CE to start syncing this site first.",!0);return}let l=c.run(x.viewsDir);t.flash(l?`Opened ${c.label} at ${Mt(x.viewsDir)}`:`Couldn't open ${c.label}.`,!l)},xe=()=>{if(!x){t.flash("Press \u23CE to start syncing this site first.",!0);return}t.togglePause(x.site.id)};nn((c,l)=>{if(h==="newOrg"){l.escape?S("dashboard"):l.upArrow?p(M=>_(M-1,t.catalog.length)):l.downArrow?p(M=>_(M+1,t.catalog.length)):l.return&&S("newName");return}if(U){l.escape?(Q(!1),E(""),d(null)):l.return?Q(!1):l.upArrow?tt(-1):l.downArrow&&tt(1);return}if(l.escape){E(""),d(null);return}if(c==="/")return void Q(!0);if(c==="n")return void we();if(c==="q")return void t.logout();if(l.upArrow)return tt(-1);if(l.downArrow)return tt(1);if(l.return||c===" ")return ye();if(c==="p")return xe();if(c>="1"&&c<="9"){let M=n[parseInt(c,10)-1];if(M)return Se(M)}},{isActive:h!=="newName"});let be=async c=>{let l=c.trim();if(!l){S("dashboard");return}let M=t.catalog[_(f,t.catalog.length)]?.org??t.catalog[0]?.org;D(!0),S("dashboard"),await t.createNewSite(l,M),D(!1)},ve=Math.max(5,(e?.rows??30)-18);return g(m,{flexDirection:"column",children:[g(m,{justifyContent:"space-between",paddingX:1,children:[g(m,{children:[o(u,{color:"green",bold:!0,children:"\u25C6 SleekCMS"}),o(u,{color:"gray",children:" \u2502 "}),o(u,{children:t.email??"signed in"})]}),g(m,{children:[g(u,{color:"gray",children:[I?`${C.length}/${R.length}`:R.length," sites \xB7 "]}),g(u,{color:"green",children:[t.watchingCount()," watching"]}),o(u,{color:"gray",children:` \xB7 ${i.toTimeString().slice(0,8)}`})]})]}),g(m,{children:[g(m,{flexDirection:"column",borderStyle:"round",borderColor:"green",width:38,paddingX:1,children:[g(m,{justifyContent:"space-between",children:[o(u,{color:"green",bold:!0,children:"SITE WORKSPACES"}),R.length>j&&!U&&!I?o(u,{color:"gray",children:"/ filter"}):null]}),U?g(m,{children:[o(u,{color:"cyan",children:"/ "}),o(ue,{value:T,onChange:E,onSubmit:()=>Q(!1),placeholder:"type to filter\u2026"})]}):I?o(u,{color:"gray",children:`/ ${T} \xB7 ${C.length} match${C.length===1?"":"es"}`}):null,o(m,{flexDirection:"column",marginTop:1,minHeight:Z?j+3:void 0,children:C.length===0?o(u,{color:"gray",children:I?`no matches for \u201C${T}\u201D`:"no sites \u2014 press n to create one"}):g(ct,{children:[Z?o(u,{color:"gray",children:X>0?` \u2191 ${X} more`:" "}):null,fe.map((c,l)=>{let M=X+l,At=c.entry?at(c.entry.status):{dot:"\xB7",color:"gray",label:"idle"},Rt=ot(c.entry?.progress),Te=c.entry?c.entry.status==="syncing"&&Rt!=null?`${Rt}%`:c.entry.status==="paused"?"paused":"--":"",et=M===F,ke=M===$t&&$t>0;return g(tn,{children:[ke?o(u,{color:"gray",children:"\u2500".repeat(34)}):null,g(m,{justifyContent:"space-between",children:[g(m,{children:[o(u,{color:et?"green":"gray",children:et?"\u276F ":" "}),g(u,{color:At.color,children:[At.dot," "]}),o(u,{color:et?"white":c.entry?void 0:"gray",bold:et,children:c.site.name})]}),o(u,{color:c.entry?.status==="syncing"?"green":"gray",children:Te})]})]},c.site.id)}),Z?o(u,{color:"gray",children:lt<H?` \u2193 ${H-lt} more`:" "}):null]})}),o(m,{marginTop:1,children:o(u,{color:"gray",children:"+ new site \xB7 n"})})]}),g(m,{flexDirection:"column",flexGrow:1,marginLeft:1,children:[o(m,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,children:k?g(ct,{children:[g(u,{children:[k.org.name||k.org.subdomain?g(u,{color:"gray",children:[k.org.name||k.org.subdomain," / "]}):null,o(u,{color:"cyan",bold:!0,children:k.site.name})]}),g(m,{marginTop:1,children:[o(u,{color:"gray",children:"status "}),x?o(u,{color:at(x.status).color,children:`: ${at(x.status).label}`}):o(u,{color:"gray",children:": idle \u2014 not syncing (press \u23CE to start)"})]}),g(m,{children:[o(u,{color:"gray",children:"path "}),o(u,{children:x?.viewsDir?Mt(x.viewsDir):"\u2014"})]}),g(m,{children:[o(u,{color:"gray",children:"synced "}),o(u,{children:ce(x?.lastSyncedAt)}),o(u,{color:"gray",children:" progress "}),o(u,{color:x?.status==="syncing"?"green":"gray",children:x?.status==="syncing"?`${ot(x.progress)??0}%`:"\u2014"})]}),o(m,{marginTop:1,children:o(cn,{percent:x?x.status==="syncing"?ot(x.progress)??0:x.status==="watching"?100:0:0})})]}):o(u,{color:"gray",children:"No site selected."})}),h==="dashboard"?g(m,{flexDirection:"column",borderStyle:"round",borderColor:"gray",paddingX:1,marginTop:1,flexGrow:1,children:[g(u,{children:[o(u,{color:"cyan",bold:!0,children:"LOGS \xB7 "}),o(u,{color:"white",children:k?.site.name??"\u2014"})]}),o(m,{flexDirection:"column",marginTop:1,children:!x||x.logs.length===0?o(u,{color:"gray",children:"no activity yet"}):x.logs.slice(-ve).map((c,l)=>g(m,{children:[g(u,{color:"gray",children:[c.time," "]}),o(u,{children:c.text})]},l))})]}):g(m,{flexDirection:"column",borderStyle:"round",borderColor:"blue",paddingX:1,marginTop:1,flexGrow:1,children:[h==="newOrg"&&g(ct,{children:[o(u,{color:"cyan",bold:!0,children:"CREATE SITE \u2014 PICK ORG"}),o(u,{color:"gray",children:"\u2191/\u2193 select \xB7 \u23CE next \xB7 esc cancel"}),o(m,{flexDirection:"column",marginTop:1,children:t.catalog.map((c,l)=>g(u,{color:l===_(f,t.catalog.length)?"blue":void 0,bold:l===_(f,t.catalog.length),children:[l===_(f,t.catalog.length)?"\u276F ":" ",c.org.name||c.org.subdomain]},c.org.id))})]}),h==="newName"&&g(ct,{children:[o(u,{color:"cyan",bold:!0,children:"CREATE SITE"}),g(m,{marginTop:1,children:[o(u,{children:"New site name: "}),o(ue,{value:y,onChange:v,onSubmit:be})]}),o(u,{color:"gray",children:"\u23CE create \xB7 empty + \u23CE to cancel"})]})]})]})]}),t.message?o(m,{paddingX:1,children:g(u,{color:t.messageIsError?"red":"gray",children:[t.messageIsError?"\u2717 ":"",t.message]})}):null,g(m,{flexDirection:"column",marginTop:1,children:[g(m,{paddingX:1,flexWrap:"wrap",children:[o(A,{k:"\u2191\u2193",label:"select"}),o(A,{k:"/",label:"search"}),o(A,{k:"\u23CE",label:"start/stop"}),o(A,{k:"p",label:"pause"}),o(A,{k:"n",label:"new site"}),o(A,{k:"q",label:"logout"}),o(A,{k:"ctrl+c",label:"quit",width:14})]}),n.length>0?o(m,{paddingX:1,marginTop:1,flexWrap:"wrap",children:n.map((c,l)=>o(A,{k:String(l+1),label:c.label},c.key))}):null]})]})}import{jsx as ut}from"react/jsx-runtime";function de(t){let e=t;return e?.body||e?.message||String(t)}function he({controller:t,env:e,devSecret:n,savedToken:r,savedEmail:i}){let[s,a]=ge(r?"loading":"auth"),[d,h]=ge();dn((f,p)=>{p.ctrl&&f==="c"&&t.exit()}),un(()=>{r&&t.init(e,r,i).then(()=>a("manage")).catch(f=>{f instanceof P&&f.status===401?h("Saved session expired \u2014 please sign in again."):h(de(f)),a("auth")})},[]);let S=async(f,p)=>{h(void 0),await zt({env:e,userToken:f,devSecret:n,email:p}),a("loading");try{await t.init(e,f,p),a("manage")}catch(y){h(de(y)),a("auth")}};return s==="auth"?ut(ae,{env:e,initialError:d,onAuthed:S}):s==="loading"?ut(ln,{children:ut(gn,{children:"Loading your sites\u2026 "})}):ut(le,{controller:t})}import{jsx as bn}from"react/jsx-runtime";var yn=mn(import.meta.url),Pt=Ft();if(Pt.version){let t=yn("../package.json");console.log(t.version),process.exit(0)}var wn=pe.dirname(pn(import.meta.url)),Sn=hn(pe.join(wn,"AGENT.md"),"utf-8");async function xn(){let t=new st(Sn),e=await Xt(),n=(Pt.env??e?.env??"production").toLowerCase(),r=Pt.dev??process.env.SLEEKCMS_DEV_SECRET??e?.devSecret;Ht(r),process.stdout.isTTY&&process.stdout.write("\x1B[2J\x1B[3J\x1B[H");let i=fn(bn(he,{controller:t,env:n,devSecret:r,savedToken:e?.userToken,savedEmail:e?.email}),{exitOnCtrlC:!1}),s=a=>{i.unmount(),console.log(a),process.exit(0)};t.once("exit",()=>s("\u2713 Stopped. Local site files were cleaned up.")),t.once("logout",()=>s("\u{1F44B} Logged out. Run `sleekcms` to sign in again.")),process.on("SIGINT",()=>{t.exit()}),process.on("SIGTERM",()=>{t.exit()}),await i.waitUntilExit()}xn().catch(t=>{let e=t;console.error("\u274C",e.body||e.message),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleekcms/sync",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Edit SleekCMS sites locally โ models, content, templates, images โ with live two-way sync and AI agent support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sleekcms",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"bugs": {
|
|
20
20
|
"url": "https://github.com/sleekcms/cms-sync/issues"
|
|
21
21
|
},
|
|
22
|
+
"type": "module",
|
|
22
23
|
"main": "dist/index.js",
|
|
23
|
-
"types": "dist/index.d.ts",
|
|
24
24
|
"bin": {
|
|
25
25
|
"sleekcms": "dist/index.js"
|
|
26
26
|
},
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"scripts": {
|
|
31
|
-
"build": "
|
|
32
|
-
"dev": "tsx src/index.
|
|
31
|
+
"build": "npm run clean && node esbuild.config.mjs && cp src/AGENT.md dist/ && chmod +x dist/index.js",
|
|
32
|
+
"dev": "tsx src/index.tsx",
|
|
33
33
|
"typecheck": "tsc --noEmit",
|
|
34
34
|
"clean": "rm -rf dist",
|
|
35
35
|
"test": "node --import tsx --test __tests__/*.test.ts",
|
|
36
|
-
"prepublishOnly": "npm run
|
|
36
|
+
"prepublishOnly": "npm run typecheck && npm run build",
|
|
37
37
|
"publish:npm": "npm publish --access public"
|
|
38
38
|
},
|
|
39
39
|
"author": "Yusuf Bhabhrawala",
|
|
@@ -41,11 +41,16 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"chokidar": "^4.0.3",
|
|
43
43
|
"commander": "^13.1.0",
|
|
44
|
-
"fs-extra": "^11.3.0"
|
|
44
|
+
"fs-extra": "^11.3.0",
|
|
45
|
+
"ink": "^7.1.0",
|
|
46
|
+
"ink-text-input": "^6.0.0",
|
|
47
|
+
"react": "^19.2.7"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"@types/fs-extra": "^11.0.4",
|
|
48
51
|
"@types/node": "^22.10.0",
|
|
52
|
+
"@types/react": "^19.2.17",
|
|
53
|
+
"esbuild": "^0.28.1",
|
|
49
54
|
"tsx": "^4.19.2",
|
|
50
55
|
"typescript": "^5.7.2"
|
|
51
56
|
}
|
package/dist/cli.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI setup, prompts, and UI for the CMS CLI
|
|
3
|
-
*/
|
|
4
|
-
export interface CliOptions {
|
|
5
|
-
version?: boolean;
|
|
6
|
-
token?: string;
|
|
7
|
-
env?: string;
|
|
8
|
-
path?: string;
|
|
9
|
-
manual?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export interface KeyboardHandlers {
|
|
12
|
-
onExit?: () => void | Promise<unknown>;
|
|
13
|
-
onQuit?: () => void | Promise<unknown>;
|
|
14
|
-
onRefetch?: () => void | Promise<unknown>;
|
|
15
|
-
onSync?: () => void | Promise<unknown>;
|
|
16
|
-
}
|
|
17
|
-
export declare function parseArgs(): CliOptions;
|
|
18
|
-
export declare function prompt(question: string): Promise<string>;
|
|
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
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* CLI setup, prompts, and UI for the CMS CLI
|
|
4
|
-
*/
|
|
5
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
|
-
};
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.parseArgs = parseArgs;
|
|
10
|
-
exports.prompt = prompt;
|
|
11
|
-
exports.showWatchHelp = showWatchHelp;
|
|
12
|
-
exports.setupKeyboardInput = setupKeyboardInput;
|
|
13
|
-
exports.showEditorMenu = showEditorMenu;
|
|
14
|
-
const readline_1 = __importDefault(require("readline"));
|
|
15
|
-
const child_process_1 = require("child_process");
|
|
16
|
-
const commander_1 = require("commander");
|
|
17
|
-
let rawModeEnabled = false;
|
|
18
|
-
function parseArgs() {
|
|
19
|
-
commander_1.program
|
|
20
|
-
.name("cms-sync")
|
|
21
|
-
.description("SleekCMS CLI tool to sync and edit CMS templates locally. Downloads templates, watches for changes, and syncs updates back to the API.")
|
|
22
|
-
.addOption(new commander_1.Option("-v, --version", "output the version number").hideHelp())
|
|
23
|
-
.option("-t, --token <token>", "API authentication token (required)")
|
|
24
|
-
.addOption(new commander_1.Option("-e, --env <env>", "Environment (localhost, development, production)").default("production").hideHelp())
|
|
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")
|
|
27
|
-
.addHelpText("after", `
|
|
28
|
-
Examples:
|
|
29
|
-
$ cms-sync --token abc123-xxxx
|
|
30
|
-
$ cms-sync -t abc123-xxxx -e development
|
|
31
|
-
$ cms-sync -t abc123-xxxx -p ./my-templates
|
|
32
|
-
$ cms-sync -t abc123-xxxx -m # manual sync โ push only when you press [s]
|
|
33
|
-
`)
|
|
34
|
-
.parse(process.argv);
|
|
35
|
-
return commander_1.program.opts();
|
|
36
|
-
}
|
|
37
|
-
function suspendRawMode() {
|
|
38
|
-
if (process.stdin.isTTY && rawModeEnabled) {
|
|
39
|
-
process.stdin.setRawMode(false);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
function resumeRawMode() {
|
|
43
|
-
if (process.stdin.isTTY && rawModeEnabled) {
|
|
44
|
-
process.stdin.setRawMode(true);
|
|
45
|
-
process.stdin.resume();
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function prompt(question) {
|
|
49
|
-
suspendRawMode();
|
|
50
|
-
const rl = readline_1.default.createInterface({
|
|
51
|
-
input: process.stdin,
|
|
52
|
-
output: process.stdout,
|
|
53
|
-
});
|
|
54
|
-
return new Promise(resolve => {
|
|
55
|
-
rl.question(question, answer => {
|
|
56
|
-
rl.close();
|
|
57
|
-
resumeRawMode();
|
|
58
|
-
resolve(answer.trim());
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
function commandExists(cmd) {
|
|
63
|
-
try {
|
|
64
|
-
(0, child_process_1.execSync)(`which ${cmd}`, { stdio: "ignore" });
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
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`);
|
|
74
|
-
}
|
|
75
|
-
function setupKeyboardInput(handlers, manual = false) {
|
|
76
|
-
if (process.stdin.isTTY) {
|
|
77
|
-
process.stdin.setRawMode(true);
|
|
78
|
-
rawModeEnabled = true;
|
|
79
|
-
}
|
|
80
|
-
process.stdin.resume();
|
|
81
|
-
process.stdin.setEncoding("utf8");
|
|
82
|
-
process.stdin.on("data", async (key) => {
|
|
83
|
-
// Handle Ctrl+C
|
|
84
|
-
if (key === "\u0003") {
|
|
85
|
-
if (handlers.onExit)
|
|
86
|
-
await handlers.onExit();
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
const cmd = key.toLowerCase();
|
|
90
|
-
if (cmd === "s" && handlers.onSync) {
|
|
91
|
-
await handlers.onSync();
|
|
92
|
-
showWatchHelp(manual);
|
|
93
|
-
}
|
|
94
|
-
else if (cmd === "r" && handlers.onRefetch) {
|
|
95
|
-
console.log("\n๐ Re-fetching all files...");
|
|
96
|
-
await handlers.onRefetch();
|
|
97
|
-
if (!manual)
|
|
98
|
-
console.log("๐ Watching for changes...");
|
|
99
|
-
showWatchHelp(manual);
|
|
100
|
-
}
|
|
101
|
-
else if (cmd === "x" && handlers.onExit) {
|
|
102
|
-
await handlers.onExit();
|
|
103
|
-
}
|
|
104
|
-
else if (cmd === "q" && handlers.onQuit) {
|
|
105
|
-
await handlers.onQuit();
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
const EDITOR_CANDIDATES = [
|
|
110
|
-
{ name: "VS Code", cmd: "code", args: (dir) => ["-n", dir] },
|
|
111
|
-
{ name: "Cursor", cmd: "cursor", args: (dir) => ["-n", dir] },
|
|
112
|
-
];
|
|
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) {
|
|
118
|
-
const editors = EDITOR_CANDIDATES
|
|
119
|
-
.filter(e => commandExists(e.cmd))
|
|
120
|
-
.map((e, i) => ({ ...e, key: String(i + 1) }));
|
|
121
|
-
if (editors.length === 0) {
|
|
122
|
-
console.log("");
|
|
123
|
-
statusLine(manual);
|
|
124
|
-
showWatchHelp(manual);
|
|
125
|
-
setupKeyboardInput(handlers, manual);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
console.log("\n๐ Open in editor:");
|
|
129
|
-
editors.forEach(e => console.log(` [${e.key}] ${e.name}`));
|
|
130
|
-
console.log(" [Enter] Skip");
|
|
131
|
-
console.log(" [x] Exit (cleanup)");
|
|
132
|
-
console.log(" [q] Quit (keep files)\n");
|
|
133
|
-
const rl = readline_1.default.createInterface({
|
|
134
|
-
input: process.stdin,
|
|
135
|
-
output: process.stdout,
|
|
136
|
-
});
|
|
137
|
-
// Count lines to clear (menu header + editors + skip + exit + quit + empty + prompt)
|
|
138
|
-
const linesToClear = editors.length + 6;
|
|
139
|
-
rl.question("Select editor: ", async (answer) => {
|
|
140
|
-
rl.close();
|
|
141
|
-
// Clear the menu lines
|
|
142
|
-
process.stdout.write(`\x1b[${linesToClear}A`); // Move cursor up
|
|
143
|
-
for (let i = 0; i < linesToClear; i++) {
|
|
144
|
-
process.stdout.write("\x1b[2K\n"); // Clear each line
|
|
145
|
-
}
|
|
146
|
-
process.stdout.write(`\x1b[${linesToClear}A`); // Move back up
|
|
147
|
-
const choice = answer.trim().toLowerCase();
|
|
148
|
-
if (choice === "x") {
|
|
149
|
-
if (handlers.onExit)
|
|
150
|
-
await handlers.onExit();
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (choice === "q") {
|
|
154
|
-
if (handlers.onQuit)
|
|
155
|
-
await handlers.onQuit();
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const selected = editors.find(e => e.key === answer.trim());
|
|
159
|
-
if (selected) {
|
|
160
|
-
statusLine(manual, ` (opened ${selected.name})`);
|
|
161
|
-
(0, child_process_1.spawn)(selected.cmd, selected.args(viewsDir), {
|
|
162
|
-
detached: true,
|
|
163
|
-
stdio: "ignore",
|
|
164
|
-
...(selected.cwd ? { cwd: selected.cwd(viewsDir) } : {}),
|
|
165
|
-
}).unref();
|
|
166
|
-
}
|
|
167
|
-
else {
|
|
168
|
-
statusLine(manual);
|
|
169
|
-
}
|
|
170
|
-
showWatchHelp(manual);
|
|
171
|
-
setupKeyboardInput(handlers, manual);
|
|
172
|
-
});
|
|
173
|
-
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* SleekCMS CLI โ interactive entry point.
|
|
4
|
-
*
|
|
5
|
-
* Handles CLI prompts, editor launch, and file watching. All sync work
|
|
6
|
-
* (fetch, push, pull, cache) is delegated to setup-site.ts so the same
|
|
7
|
-
* logic can be invoked standalone (e.g. as a skill for managed agents).
|
|
8
|
-
*/
|
|
9
|
-
export {};
|
package/dist/setup-site.d.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* SleekCMS site sync โ standalone, self-contained.
|
|
4
|
-
*
|
|
5
|
-
* Bi-directional sync between a local workspace and the SleekCMS server.
|
|
6
|
-
* Safe to invoke repeatedly: a `.cache/state.json` inside the workspace
|
|
7
|
-
* tracks server-known state so only real diffs are pushed.
|
|
8
|
-
*/
|
|
9
|
-
export interface Site {
|
|
10
|
-
id: number;
|
|
11
|
-
name: string;
|
|
12
|
-
[key: string]: unknown;
|
|
13
|
-
}
|
|
14
|
-
export interface SyncSiteOptions {
|
|
15
|
-
token: string;
|
|
16
|
-
viewsDir?: string;
|
|
17
|
-
path?: string;
|
|
18
|
-
env?: string;
|
|
19
|
-
agentMd?: string;
|
|
20
|
-
flush?: boolean;
|
|
21
|
-
}
|
|
22
|
-
export interface SyncSiteResult {
|
|
23
|
-
viewsDir: string;
|
|
24
|
-
site: Site;
|
|
25
|
-
isFirstRun: boolean;
|
|
26
|
-
pushed: number;
|
|
27
|
-
pulled: number;
|
|
28
|
-
}
|
|
29
|
-
export declare function resolveViewsDir(basePath: string | undefined, site: Site): string;
|
|
30
|
-
export declare function syncSite(opts: SyncSiteOptions): Promise<SyncSiteResult>;
|