clefbase 1.1.5 → 1.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/README.md +169 -103
- package/bin/clefbase.js +15 -0
- package/dist/app.d.ts +16 -32
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +29 -44
- package/dist/app.js.map +1 -1
- package/dist/auth/index.d.ts +16 -44
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +40 -95
- package/dist/auth/index.js.map +1 -1
- package/dist/cli-src/cli/api.js +170 -0
- package/dist/cli-src/cli/commands/deploy.js +285 -0
- package/dist/cli-src/cli/commands/info.js +67 -0
- package/dist/cli-src/cli/commands/init.js +226 -0
- package/dist/cli-src/cli/config.js +82 -0
- package/dist/cli-src/cli/index.js +110 -0
- package/dist/cli-src/types.js +17 -0
- package/dist/cli.js +34375 -0
- package/dist/db/index.d.ts +26 -62
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +34 -70
- package/dist/db/index.js.map +1 -1
- package/dist/hosting/index.d.ts +123 -0
- package/dist/hosting/index.d.ts.map +1 -0
- package/dist/hosting/index.js +202 -0
- package/dist/hosting/index.js.map +1 -0
- package/dist/http.d.ts +2 -4
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +8 -22
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +11 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -6
- package/dist/index.js.map +1 -1
- package/dist/storage/index.d.ts +8 -50
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +11 -61
- package/dist/storage/index.js.map +1 -1
- package/dist/types.d.ts +10 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/package.json +21 -5
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runDeploy = runDeploy;
|
|
7
|
+
exports.runHostingInit = runHostingInit;
|
|
8
|
+
exports.runStatus = runStatus;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const ora_1 = __importDefault(require("ora"));
|
|
13
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
14
|
+
const config_1 = require("../config");
|
|
15
|
+
const api_1 = require("../api");
|
|
16
|
+
// ─── MIME map ─────────────────────────────────────────────────────────────────
|
|
17
|
+
const MIME = {
|
|
18
|
+
".html": "text/html", ".htm": "text/html",
|
|
19
|
+
".css": "text/css",
|
|
20
|
+
".js": "application/javascript", ".mjs": "application/javascript", ".cjs": "application/javascript",
|
|
21
|
+
".json": "application/json", ".map": "application/json",
|
|
22
|
+
".ts": "application/typescript",
|
|
23
|
+
".svg": "image/svg+xml",
|
|
24
|
+
".png": "image/png",
|
|
25
|
+
".jpg": "image/jpeg", ".jpeg": "image/jpeg",
|
|
26
|
+
".gif": "image/gif",
|
|
27
|
+
".webp": "image/webp",
|
|
28
|
+
".avif": "image/avif",
|
|
29
|
+
".ico": "image/x-icon",
|
|
30
|
+
".woff": "font/woff", ".woff2": "font/woff2",
|
|
31
|
+
".ttf": "font/ttf", ".otf": "font/otf",
|
|
32
|
+
".txt": "text/plain", ".md": "text/markdown",
|
|
33
|
+
".xml": "application/xml",
|
|
34
|
+
".pdf": "application/pdf",
|
|
35
|
+
".webmanifest": "application/manifest+json",
|
|
36
|
+
};
|
|
37
|
+
function mimeFor(filePath) {
|
|
38
|
+
return MIME[path_1.default.extname(filePath).toLowerCase()] ?? "application/octet-stream";
|
|
39
|
+
}
|
|
40
|
+
const SKIP_FILES = new Set([".DS_Store", "Thumbs.db", ".gitkeep", ".gitignore"]);
|
|
41
|
+
const SKIP_DIRS = new Set(["node_modules", ".git", ".svn", ".hg"]);
|
|
42
|
+
function scanDir(dir, base, out) {
|
|
43
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
44
|
+
if (SKIP_FILES.has(entry.name))
|
|
45
|
+
continue;
|
|
46
|
+
const full = path_1.default.join(dir, entry.name);
|
|
47
|
+
if (entry.isDirectory()) {
|
|
48
|
+
if (!SKIP_DIRS.has(entry.name))
|
|
49
|
+
scanDir(full, base, out);
|
|
50
|
+
}
|
|
51
|
+
else if (entry.isFile()) {
|
|
52
|
+
const stat = fs_1.default.statSync(full);
|
|
53
|
+
out.push({
|
|
54
|
+
absPath: full,
|
|
55
|
+
deployPath: path_1.default.relative(base, full).replace(/\\/g, "/"),
|
|
56
|
+
size: stat.size,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function fmtBytes(n) {
|
|
62
|
+
if (n < 1024)
|
|
63
|
+
return `${n} B`;
|
|
64
|
+
if (n < 1024 * 1024)
|
|
65
|
+
return `${(n / 1024).toFixed(1)} KB`;
|
|
66
|
+
return `${(n / (1024 * 1024)).toFixed(2)} MB`;
|
|
67
|
+
}
|
|
68
|
+
// ─── deploy ───────────────────────────────────────────────────────────────────
|
|
69
|
+
async function runDeploy(opts) {
|
|
70
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
71
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(chalk_1.default.bold.cyan(" Clefbase Deploy"));
|
|
74
|
+
console.log();
|
|
75
|
+
// ── Resolve / pick a site ─────────────────────────────────────────────────
|
|
76
|
+
let siteId = opts.site ?? cfg.hosting?.siteId ?? "";
|
|
77
|
+
let siteName = cfg.hosting?.siteName ?? "";
|
|
78
|
+
if (!siteId) {
|
|
79
|
+
siteId = await pickOrCreateSite(cfg);
|
|
80
|
+
const sites = await (0, api_1.listSites)(cfg).catch(() => []);
|
|
81
|
+
siteName = sites.find(s => s.id === siteId)?.name ?? siteId;
|
|
82
|
+
cfg.hosting = {
|
|
83
|
+
siteId,
|
|
84
|
+
siteName,
|
|
85
|
+
distDir: cfg.hosting?.distDir ?? "dist",
|
|
86
|
+
entrypoint: cfg.hosting?.entrypoint ?? "index.html",
|
|
87
|
+
};
|
|
88
|
+
(0, config_1.saveConfig)(cfg, cwd);
|
|
89
|
+
}
|
|
90
|
+
// ── Resolve dist dir ──────────────────────────────────────────────────────
|
|
91
|
+
const distDir = opts.dir ?? cfg.hosting?.distDir ?? await promptDistDir(cwd);
|
|
92
|
+
const absDir = path_1.default.isAbsolute(distDir) ? distDir : path_1.default.join(cwd, distDir);
|
|
93
|
+
if (!fs_1.default.existsSync(absDir)) {
|
|
94
|
+
console.error(chalk_1.default.red(`\n ✗ Directory not found: ${absDir}`));
|
|
95
|
+
console.error(chalk_1.default.dim(" Build your project first (e.g. npm run build), then redeploy.\n"));
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
// ── Scan ──────────────────────────────────────────────────────────────────
|
|
99
|
+
const scanSpinner = (0, ora_1.default)("Scanning files…").start();
|
|
100
|
+
const files = [];
|
|
101
|
+
scanDir(absDir, absDir, files);
|
|
102
|
+
const totalBytes = files.reduce((a, f) => a + f.size, 0);
|
|
103
|
+
scanSpinner.succeed(`${chalk_1.default.bold(files.length)} files ${chalk_1.default.dim("(" + fmtBytes(totalBytes) + ")")}`);
|
|
104
|
+
if (files.length === 0) {
|
|
105
|
+
console.error(chalk_1.default.red(` ✗ No files found in ${distDir}\n`));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const entrypoint = cfg.hosting?.entrypoint ?? "index.html";
|
|
109
|
+
if (!files.some(f => f.deployPath === entrypoint)) {
|
|
110
|
+
console.warn(chalk_1.default.yellow(` ⚠ Entrypoint "${entrypoint}" not found — is your build output correct?`));
|
|
111
|
+
}
|
|
112
|
+
// ── Summary ───────────────────────────────────────────────────────────────
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(` ${chalk_1.default.bold("Site:")} ${siteName} ${chalk_1.default.dim(siteId)}`);
|
|
115
|
+
console.log(` ${chalk_1.default.bold("From:")} ${path_1.default.relative(cwd, absDir)}`);
|
|
116
|
+
console.log(` ${chalk_1.default.bold("Files:")} ${files.length} ${chalk_1.default.dim("(" + fmtBytes(totalBytes) + ")")}`);
|
|
117
|
+
console.log();
|
|
118
|
+
// ── Create deploy ─────────────────────────────────────────────────────────
|
|
119
|
+
const deploySpinner = (0, ora_1.default)("Creating deploy…").start();
|
|
120
|
+
let deploy;
|
|
121
|
+
try {
|
|
122
|
+
deploy = await (0, api_1.createDeploy)(cfg, siteId, entrypoint);
|
|
123
|
+
deploySpinner.succeed(`Deploy created ${chalk_1.default.dim(deploy.id)}`);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
deploySpinner.fail(`Failed: ${err.message}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
// ── Upload in batches of 20 ───────────────────────────────────────────────
|
|
130
|
+
const BATCH = 20;
|
|
131
|
+
let uploaded = 0;
|
|
132
|
+
const allErrors = [];
|
|
133
|
+
const upSpinner = (0, ora_1.default)(`Uploading 0 / ${files.length}`).start();
|
|
134
|
+
for (let i = 0; i < files.length; i += BATCH) {
|
|
135
|
+
const batch = files.slice(i, i + BATCH).map(f => ({
|
|
136
|
+
path: f.deployPath,
|
|
137
|
+
buffer: fs_1.default.readFileSync(f.absPath),
|
|
138
|
+
}));
|
|
139
|
+
try {
|
|
140
|
+
const result = await (0, api_1.uploadFileBatch)(cfg, deploy.id, batch);
|
|
141
|
+
uploaded += result.uploaded ?? batch.length;
|
|
142
|
+
if (result.errors?.length)
|
|
143
|
+
allErrors.push(...result.errors);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
allErrors.push(`Batch ${i + 1}–${Math.min(i + BATCH, files.length)}: ${err.message}`);
|
|
147
|
+
}
|
|
148
|
+
const done = Math.min(i + BATCH, files.length);
|
|
149
|
+
upSpinner.text = `Uploading ${done} / ${files.length}`;
|
|
150
|
+
}
|
|
151
|
+
if (allErrors.length > 0) {
|
|
152
|
+
upSpinner.warn(`Uploaded ${uploaded} / ${files.length} (${allErrors.length} error(s))`);
|
|
153
|
+
allErrors.forEach(e => console.log(chalk_1.default.dim(` • ${e}`)));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
upSpinner.succeed(`Uploaded ${chalk_1.default.bold(uploaded)} files`);
|
|
157
|
+
}
|
|
158
|
+
// ── Finalize ──────────────────────────────────────────────────────────────
|
|
159
|
+
const finSpinner = (0, ora_1.default)("Going live…").start();
|
|
160
|
+
try {
|
|
161
|
+
await (0, api_1.finalizeDeploy)(cfg, deploy.id, opts.message);
|
|
162
|
+
finSpinner.succeed(chalk_1.default.green("Live!"));
|
|
163
|
+
}
|
|
164
|
+
catch (err) {
|
|
165
|
+
finSpinner.fail(`Finalize failed: ${err.message}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
// ── Result ────────────────────────────────────────────────────────────────
|
|
169
|
+
const url = `${cfg.serverUrl.replace(/\/+$/, "")}/hosted/${cfg.projectId}/${siteId}`;
|
|
170
|
+
console.log();
|
|
171
|
+
console.log(chalk_1.default.green.bold(` ✓ ${url}`));
|
|
172
|
+
console.log();
|
|
173
|
+
}
|
|
174
|
+
// ─── hosting:init ─────────────────────────────────────────────────────────────
|
|
175
|
+
async function runHostingInit(cwd = process.cwd()) {
|
|
176
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
177
|
+
console.log();
|
|
178
|
+
console.log(chalk_1.default.bold.cyan(" Hosting Setup"));
|
|
179
|
+
console.log();
|
|
180
|
+
const siteId = await pickOrCreateSite(cfg);
|
|
181
|
+
const sites = await (0, api_1.listSites)(cfg).catch(() => []);
|
|
182
|
+
const siteName = sites.find(s => s.id === siteId)?.name ?? siteId;
|
|
183
|
+
const { distDir } = await inquirer_1.default.prompt([{
|
|
184
|
+
type: "input", name: "distDir",
|
|
185
|
+
message: "Build output directory",
|
|
186
|
+
default: cfg.hosting?.distDir ?? guessDistDir(cwd),
|
|
187
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
188
|
+
}]);
|
|
189
|
+
const { entrypoint } = await inquirer_1.default.prompt([{
|
|
190
|
+
type: "input", name: "entrypoint",
|
|
191
|
+
message: "HTML entrypoint",
|
|
192
|
+
default: cfg.hosting?.entrypoint ?? "index.html",
|
|
193
|
+
}]);
|
|
194
|
+
cfg.services.hosting = true;
|
|
195
|
+
cfg.hosting = { siteId, siteName, distDir: distDir.trim(), entrypoint: entrypoint.trim() };
|
|
196
|
+
(0, config_1.saveConfig)(cfg, cwd);
|
|
197
|
+
console.log();
|
|
198
|
+
console.log(chalk_1.default.green(` ✓ Linked to "${siteName}"`));
|
|
199
|
+
console.log(chalk_1.default.dim(" Run `clefbase deploy` to go live."));
|
|
200
|
+
console.log();
|
|
201
|
+
}
|
|
202
|
+
// ─── hosting:status ───────────────────────────────────────────────────────────
|
|
203
|
+
async function runStatus(cwd = process.cwd()) {
|
|
204
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
205
|
+
console.log();
|
|
206
|
+
console.log(chalk_1.default.bold(" Deploy Status"));
|
|
207
|
+
console.log();
|
|
208
|
+
if (!cfg.hosting?.siteId) {
|
|
209
|
+
console.log(chalk_1.default.yellow(" No site linked. Run `clefbase hosting:init` first.\n"));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const sp = (0, ora_1.default)("Fetching active deploy…").start();
|
|
213
|
+
try {
|
|
214
|
+
const active = await (0, api_1.getActiveDeploy)(cfg, cfg.hosting.siteId);
|
|
215
|
+
if (!active) {
|
|
216
|
+
sp.info("No active deploy yet.");
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
sp.succeed("Active deploy");
|
|
220
|
+
const d = active.deploy;
|
|
221
|
+
const url = `${cfg.serverUrl.replace(/\/+$/, "")}/hosted/${cfg.projectId}/${cfg.hosting.siteId}`;
|
|
222
|
+
console.log();
|
|
223
|
+
console.log(` ${chalk_1.default.bold("Site:")} ${cfg.hosting.siteName} ${chalk_1.default.dim(cfg.hosting.siteId)}`);
|
|
224
|
+
console.log(` ${chalk_1.default.bold("Deploy:")} ${chalk_1.default.dim(d.id)}`);
|
|
225
|
+
console.log(` ${chalk_1.default.bold("Status:")} ${chalk_1.default.green(d.status)}`);
|
|
226
|
+
console.log(` ${chalk_1.default.bold("Files:")} ${d.fileCount} ${chalk_1.default.dim(fmtBytes(d.totalBytes ?? 0))}`);
|
|
227
|
+
console.log(` ${chalk_1.default.bold("Deployed:")} ${new Date(d.finishedAt ?? d.createdAt).toLocaleString()}`);
|
|
228
|
+
console.log(` ${chalk_1.default.bold("URL:")} ${url}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
sp.fail(err.message);
|
|
233
|
+
}
|
|
234
|
+
console.log();
|
|
235
|
+
}
|
|
236
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
237
|
+
async function pickOrCreateSite(cfg) {
|
|
238
|
+
const sp = (0, ora_1.default)("Fetching sites…").start();
|
|
239
|
+
let sites = [];
|
|
240
|
+
try {
|
|
241
|
+
sites = await (0, api_1.listSites)(cfg);
|
|
242
|
+
sp.succeed(`${sites.length} site(s)`);
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
sp.warn("Could not fetch sites");
|
|
246
|
+
}
|
|
247
|
+
if (sites.length > 0) {
|
|
248
|
+
const choices = [
|
|
249
|
+
...sites.map(s => ({ name: `${s.name} ${chalk_1.default.dim(s.id)}`, value: s.id })),
|
|
250
|
+
{ name: chalk_1.default.cyan("+ Create a new site"), value: "__new__" },
|
|
251
|
+
];
|
|
252
|
+
const { chosen } = await inquirer_1.default.prompt([{
|
|
253
|
+
type: "list", name: "chosen",
|
|
254
|
+
message: "Which site should receive this deploy?",
|
|
255
|
+
choices,
|
|
256
|
+
}]);
|
|
257
|
+
if (chosen !== "__new__")
|
|
258
|
+
return chosen;
|
|
259
|
+
}
|
|
260
|
+
const { name } = await inquirer_1.default.prompt([{
|
|
261
|
+
type: "input", name: "name",
|
|
262
|
+
message: "New site name",
|
|
263
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
264
|
+
}]);
|
|
265
|
+
const s = (0, ora_1.default)(`Creating "${name}"…`).start();
|
|
266
|
+
const site = await (0, api_1.createSite)(cfg, name.trim());
|
|
267
|
+
s.succeed(chalk_1.default.green(`Created "${site.name}" ${chalk_1.default.dim(site.id)}`));
|
|
268
|
+
return site.id;
|
|
269
|
+
}
|
|
270
|
+
async function promptDistDir(cwd) {
|
|
271
|
+
const { dir } = await inquirer_1.default.prompt([{
|
|
272
|
+
type: "input", name: "dir",
|
|
273
|
+
message: "Build output directory",
|
|
274
|
+
default: guessDistDir(cwd),
|
|
275
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
276
|
+
}]);
|
|
277
|
+
return dir.trim();
|
|
278
|
+
}
|
|
279
|
+
function guessDistDir(cwd) {
|
|
280
|
+
for (const c of ["dist", "build", "out", ".next", "public"]) {
|
|
281
|
+
if (fs_1.default.existsSync(path_1.default.join(cwd, c)))
|
|
282
|
+
return c;
|
|
283
|
+
}
|
|
284
|
+
return "dist";
|
|
285
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runInfo = runInfo;
|
|
7
|
+
exports.runSitesList = runSitesList;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const api_1 = require("../api");
|
|
12
|
+
async function runInfo(cwd = process.cwd()) {
|
|
13
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(chalk_1.default.bold(" Project Info"));
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(` ${chalk_1.default.bold("Server:")} ${cfg.serverUrl}`);
|
|
18
|
+
console.log(` ${chalk_1.default.bold("Project ID:")} ${cfg.projectId}`);
|
|
19
|
+
console.log(` ${chalk_1.default.bold("API Key:")} ${cfg.apiKey.slice(0, 8)}${"●".repeat(10)}`);
|
|
20
|
+
console.log();
|
|
21
|
+
console.log(` ${chalk_1.default.bold("Services:")}`);
|
|
22
|
+
for (const [svc, on] of Object.entries(cfg.services)) {
|
|
23
|
+
console.log(` ${on ? chalk_1.default.green("✓") : chalk_1.default.dim("✗")} ${svc}`);
|
|
24
|
+
}
|
|
25
|
+
if (cfg.hosting) {
|
|
26
|
+
const url = `${cfg.serverUrl.replace(/\/+$/, "")}/hosted/${cfg.projectId}/${cfg.hosting.siteId}`;
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(` ${chalk_1.default.bold("Hosting:")}`);
|
|
29
|
+
console.log(` ${chalk_1.default.dim("Site:")} ${cfg.hosting.siteName} ${chalk_1.default.dim(cfg.hosting.siteId)}`);
|
|
30
|
+
console.log(` ${chalk_1.default.dim("Dist:")} ${cfg.hosting.distDir}`);
|
|
31
|
+
console.log(` ${chalk_1.default.dim("Entry:")} ${cfg.hosting.entrypoint}`);
|
|
32
|
+
console.log(` ${chalk_1.default.dim("URL:")} ${url}`);
|
|
33
|
+
}
|
|
34
|
+
console.log();
|
|
35
|
+
const sp = (0, ora_1.default)("Checking connection…").start();
|
|
36
|
+
const ok = await (0, api_1.testConnection)(cfg);
|
|
37
|
+
ok
|
|
38
|
+
? sp.succeed(chalk_1.default.green("Server reachable"))
|
|
39
|
+
: sp.fail(chalk_1.default.red("Cannot reach server"));
|
|
40
|
+
console.log();
|
|
41
|
+
}
|
|
42
|
+
async function runSitesList(cwd = process.cwd()) {
|
|
43
|
+
const cfg = (0, config_1.requireConfig)(cwd);
|
|
44
|
+
const sp = (0, ora_1.default)("Fetching sites…").start();
|
|
45
|
+
try {
|
|
46
|
+
const sites = await (0, api_1.listSites)(cfg);
|
|
47
|
+
sp.succeed(`${sites.length} site(s)`);
|
|
48
|
+
console.log();
|
|
49
|
+
if (sites.length === 0) {
|
|
50
|
+
console.log(chalk_1.default.dim(" No sites yet. Run `clefbase hosting:init` to create one.\n"));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const linked = cfg.hosting?.siteId;
|
|
54
|
+
for (const s of sites) {
|
|
55
|
+
const tag = s.id === linked ? chalk_1.default.cyan(" ← linked") : "";
|
|
56
|
+
const url = `${cfg.serverUrl.replace(/\/+$/, "")}/hosted/${cfg.projectId}/${s.id}`;
|
|
57
|
+
console.log(` ${chalk_1.default.bold(s.name)}${tag}`);
|
|
58
|
+
console.log(` ${chalk_1.default.dim("ID:")} ${s.id}`);
|
|
59
|
+
console.log(` ${chalk_1.default.dim("Status:")} ${s.status}`);
|
|
60
|
+
console.log(` ${chalk_1.default.dim("URL:")} ${url}`);
|
|
61
|
+
console.log();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
sp.fail(err.message);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runInit = runInit;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
11
|
+
const ora_1 = __importDefault(require("ora"));
|
|
12
|
+
const config_1 = require("../config");
|
|
13
|
+
const api_1 = require("../api");
|
|
14
|
+
const DEFAULT_SERVER = "https://api.cleforyx.com";
|
|
15
|
+
async function runInit(cwd = process.cwd()) {
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(chalk_1.default.bold.cyan(" ╔══════════════════════════════════╗"));
|
|
18
|
+
console.log(chalk_1.default.bold.cyan(" ║ Clefbase · Project Setup ║"));
|
|
19
|
+
console.log(chalk_1.default.bold.cyan(" ╚══════════════════════════════════╝"));
|
|
20
|
+
console.log();
|
|
21
|
+
// ── Step 1: Credentials ───────────────────────────────────────────────────
|
|
22
|
+
const { serverUrl } = await inquirer_1.default.prompt([{
|
|
23
|
+
type: "input",
|
|
24
|
+
name: "serverUrl",
|
|
25
|
+
message: "Server URL",
|
|
26
|
+
default: DEFAULT_SERVER,
|
|
27
|
+
validate: (v) => {
|
|
28
|
+
try {
|
|
29
|
+
new URL(v);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return "Please enter a valid URL";
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
}]);
|
|
37
|
+
const { projectId } = await inquirer_1.default.prompt([{
|
|
38
|
+
type: "input",
|
|
39
|
+
name: "projectId",
|
|
40
|
+
message: "Project ID",
|
|
41
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
42
|
+
}]);
|
|
43
|
+
const { apiKey } = await inquirer_1.default.prompt([{
|
|
44
|
+
type: "password",
|
|
45
|
+
name: "apiKey",
|
|
46
|
+
message: "API Key (x-cfx-key)",
|
|
47
|
+
mask: "●",
|
|
48
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
49
|
+
}]);
|
|
50
|
+
const { adminSecret } = await inquirer_1.default.prompt([{
|
|
51
|
+
type: "password",
|
|
52
|
+
name: "adminSecret",
|
|
53
|
+
message: "Admin Secret (x-admin-secret, needed for hosting)",
|
|
54
|
+
mask: "●",
|
|
55
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
56
|
+
}]);
|
|
57
|
+
const cfg = {
|
|
58
|
+
serverUrl: serverUrl.replace(/\/+$/, ""),
|
|
59
|
+
projectId: projectId.trim(),
|
|
60
|
+
apiKey: apiKey.trim(),
|
|
61
|
+
adminSecret: adminSecret.trim(),
|
|
62
|
+
services: { database: false, auth: false, storage: false, hosting: false },
|
|
63
|
+
};
|
|
64
|
+
// ── Step 2: Connection test ───────────────────────────────────────────────
|
|
65
|
+
const connSpinner = (0, ora_1.default)("Testing connection…").start();
|
|
66
|
+
const ok = await (0, api_1.testConnection)(cfg);
|
|
67
|
+
if (ok) {
|
|
68
|
+
connSpinner.succeed(chalk_1.default.green("Connected to server"));
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
connSpinner.warn(chalk_1.default.yellow("Could not reach server — config will be saved anyway"));
|
|
72
|
+
}
|
|
73
|
+
// ── Step 3: Services ──────────────────────────────────────────────────────
|
|
74
|
+
console.log();
|
|
75
|
+
const { services } = await inquirer_1.default.prompt([{
|
|
76
|
+
type: "checkbox",
|
|
77
|
+
name: "services",
|
|
78
|
+
message: "Which services will you use?",
|
|
79
|
+
choices: [
|
|
80
|
+
{ name: "Database — store and query documents", value: "database", checked: true },
|
|
81
|
+
{ name: "Auth — user sign-up / sign-in", value: "auth", checked: true },
|
|
82
|
+
{ name: "Storage — file uploads", value: "storage", checked: false },
|
|
83
|
+
{ name: "Hosting — deploy static sites", value: "hosting", checked: false },
|
|
84
|
+
],
|
|
85
|
+
}]);
|
|
86
|
+
cfg.services = {
|
|
87
|
+
database: services.includes("database"),
|
|
88
|
+
auth: services.includes("auth"),
|
|
89
|
+
storage: services.includes("storage"),
|
|
90
|
+
hosting: services.includes("hosting"),
|
|
91
|
+
};
|
|
92
|
+
// ── Step 4: Hosting setup ─────────────────────────────────────────────────
|
|
93
|
+
if (cfg.services.hosting) {
|
|
94
|
+
await setupHosting(cfg, cwd);
|
|
95
|
+
}
|
|
96
|
+
// ── Step 5: Write config files ────────────────────────────────────────────
|
|
97
|
+
const configPath = (0, config_1.saveConfig)(cfg, cwd);
|
|
98
|
+
(0, config_1.ensureGitignore)(cwd);
|
|
99
|
+
(0, config_1.writeEnvExample)(cfg, cwd);
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(chalk_1.default.green.bold(" ✓ Project initialised!"));
|
|
102
|
+
console.log();
|
|
103
|
+
console.log(chalk_1.default.dim(` Config: ${path_1.default.relative(cwd, configPath)}`));
|
|
104
|
+
console.log(chalk_1.default.dim(" Secrets: clefbase.json has been added to .gitignore"));
|
|
105
|
+
console.log(chalk_1.default.dim(" Env example: .env.example created"));
|
|
106
|
+
console.log();
|
|
107
|
+
printUsageHint(cfg);
|
|
108
|
+
}
|
|
109
|
+
// ─── Hosting sub-flow ─────────────────────────────────────────────────────────
|
|
110
|
+
async function setupHosting(cfg, cwd) {
|
|
111
|
+
console.log();
|
|
112
|
+
console.log(chalk_1.default.bold(" Hosting"));
|
|
113
|
+
console.log();
|
|
114
|
+
// Fetch existing sites
|
|
115
|
+
let existing = [];
|
|
116
|
+
const siteSpinner = (0, ora_1.default)("Fetching existing sites…").start();
|
|
117
|
+
try {
|
|
118
|
+
existing = await (0, api_1.listSites)(cfg);
|
|
119
|
+
siteSpinner.succeed(existing.length > 0
|
|
120
|
+
? `Found ${existing.length} existing site(s)`
|
|
121
|
+
: "No sites yet — will create one");
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
siteSpinner.warn("Could not fetch sites — you can set this up later with `clefbase hosting:init`");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let siteId = "";
|
|
128
|
+
let siteName = "";
|
|
129
|
+
if (existing.length > 0) {
|
|
130
|
+
const choices = [
|
|
131
|
+
...existing.map(s => ({ name: `${s.name} ${chalk_1.default.dim(s.id)}`, value: s.id })),
|
|
132
|
+
{ name: chalk_1.default.cyan("+ Create a new site"), value: "__new__" },
|
|
133
|
+
];
|
|
134
|
+
const { chosen } = await inquirer_1.default.prompt([{
|
|
135
|
+
type: "list",
|
|
136
|
+
name: "chosen",
|
|
137
|
+
message: "Which site should this project deploy to?",
|
|
138
|
+
choices,
|
|
139
|
+
}]);
|
|
140
|
+
if (chosen !== "__new__") {
|
|
141
|
+
siteId = chosen;
|
|
142
|
+
siteName = existing.find(s => s.id === chosen)?.name ?? chosen;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!siteId) {
|
|
146
|
+
const { newName } = await inquirer_1.default.prompt([{
|
|
147
|
+
type: "input",
|
|
148
|
+
name: "newName",
|
|
149
|
+
message: "Site name",
|
|
150
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
151
|
+
}]);
|
|
152
|
+
const s = (0, ora_1.default)(`Creating site "${newName}"…`).start();
|
|
153
|
+
try {
|
|
154
|
+
const site = await (0, api_1.createSite)(cfg, newName.trim());
|
|
155
|
+
siteId = site.id;
|
|
156
|
+
siteName = site.name;
|
|
157
|
+
s.succeed(chalk_1.default.green(`Created "${siteName}" ${chalk_1.default.dim(siteId)}`));
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
s.fail(`Failed: ${err.message}`);
|
|
161
|
+
console.log(chalk_1.default.dim(" Run `clefbase hosting:init` to retry later."));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const { distDir } = await inquirer_1.default.prompt([{
|
|
166
|
+
type: "input",
|
|
167
|
+
name: "distDir",
|
|
168
|
+
message: "Build output directory",
|
|
169
|
+
default: guessDistDir(cwd),
|
|
170
|
+
validate: (v) => v.trim().length > 0 || "Required",
|
|
171
|
+
}]);
|
|
172
|
+
const { entrypoint } = await inquirer_1.default.prompt([{
|
|
173
|
+
type: "input",
|
|
174
|
+
name: "entrypoint",
|
|
175
|
+
message: "HTML entrypoint",
|
|
176
|
+
default: "index.html",
|
|
177
|
+
}]);
|
|
178
|
+
cfg.hosting = {
|
|
179
|
+
siteId,
|
|
180
|
+
siteName,
|
|
181
|
+
distDir: distDir.trim(),
|
|
182
|
+
entrypoint: entrypoint.trim(),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
186
|
+
function guessDistDir(cwd) {
|
|
187
|
+
for (const c of ["dist", "build", "out", ".next", "public"]) {
|
|
188
|
+
if (fs_1.default.existsSync(path_1.default.join(cwd, c)))
|
|
189
|
+
return c;
|
|
190
|
+
}
|
|
191
|
+
return "dist";
|
|
192
|
+
}
|
|
193
|
+
function printUsageHint(cfg) {
|
|
194
|
+
const imports = ["initClefbase"];
|
|
195
|
+
if (cfg.services.database)
|
|
196
|
+
imports.push("getDatabase");
|
|
197
|
+
if (cfg.services.auth)
|
|
198
|
+
imports.push("getAuth");
|
|
199
|
+
if (cfg.services.storage)
|
|
200
|
+
imports.push("getStorage");
|
|
201
|
+
if (cfg.services.hosting)
|
|
202
|
+
imports.push("getHosting");
|
|
203
|
+
console.log(chalk_1.default.bold(" Quick start:"));
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(chalk_1.default.cyan(` import { ${imports.join(", ")} } from "clefbase";`));
|
|
206
|
+
console.log(chalk_1.default.cyan(` import config from "./clefbase.json";`));
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(chalk_1.default.cyan(` const app = initClefbase(config);`));
|
|
209
|
+
if (cfg.services.database) {
|
|
210
|
+
console.log();
|
|
211
|
+
console.log(chalk_1.default.cyan(` const db = getDatabase(app);`));
|
|
212
|
+
console.log(chalk_1.default.cyan(` const docs = await db.collection("items").getDocs();`));
|
|
213
|
+
}
|
|
214
|
+
if (cfg.services.auth) {
|
|
215
|
+
console.log();
|
|
216
|
+
console.log(chalk_1.default.cyan(` const auth = getAuth(app);`));
|
|
217
|
+
console.log(chalk_1.default.cyan(` const { user } = await auth.signIn("email", "pass");`));
|
|
218
|
+
}
|
|
219
|
+
if (cfg.services.hosting && cfg.hosting) {
|
|
220
|
+
console.log();
|
|
221
|
+
console.log(chalk_1.default.bold(" Deploy:"));
|
|
222
|
+
console.log(chalk_1.default.cyan(` $ npm run build && clefbase deploy`));
|
|
223
|
+
console.log(chalk_1.default.dim(` Site: ${cfg.serverUrl}/hosted/${cfg.projectId}/${cfg.hosting.siteId}`));
|
|
224
|
+
}
|
|
225
|
+
console.log();
|
|
226
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findConfigPath = findConfigPath;
|
|
7
|
+
exports.loadConfig = loadConfig;
|
|
8
|
+
exports.saveConfig = saveConfig;
|
|
9
|
+
exports.requireConfig = requireConfig;
|
|
10
|
+
exports.ensureGitignore = ensureGitignore;
|
|
11
|
+
exports.writeEnvExample = writeEnvExample;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
// ─── File helpers ─────────────────────────────────────────────────────────────
|
|
15
|
+
const CONFIG_FILE = "clefbase.json";
|
|
16
|
+
/** Walk up the directory tree looking for clefbase.json (max 5 levels). */
|
|
17
|
+
function findConfigPath(cwd = process.cwd()) {
|
|
18
|
+
let dir = cwd;
|
|
19
|
+
for (let i = 0; i < 6; i++) {
|
|
20
|
+
const candidate = path_1.default.join(dir, CONFIG_FILE);
|
|
21
|
+
if (fs_1.default.existsSync(candidate))
|
|
22
|
+
return candidate;
|
|
23
|
+
const parent = path_1.default.dirname(dir);
|
|
24
|
+
if (parent === dir)
|
|
25
|
+
break;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function loadConfig(cwd = process.cwd()) {
|
|
31
|
+
const p = findConfigPath(cwd);
|
|
32
|
+
if (!p)
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(fs_1.default.readFileSync(p, "utf-8"));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function saveConfig(config, cwd = process.cwd()) {
|
|
42
|
+
const p = path_1.default.join(cwd, CONFIG_FILE);
|
|
43
|
+
fs_1.default.writeFileSync(p, JSON.stringify(config, null, 2) + "\n");
|
|
44
|
+
return p;
|
|
45
|
+
}
|
|
46
|
+
function requireConfig(cwd = process.cwd()) {
|
|
47
|
+
const config = loadConfig(cwd);
|
|
48
|
+
if (!config) {
|
|
49
|
+
throw new Error(`No ${CONFIG_FILE} found. Run "clefbase init" to set up this project.`);
|
|
50
|
+
}
|
|
51
|
+
return config;
|
|
52
|
+
}
|
|
53
|
+
// ─── Gitignore ────────────────────────────────────────────────────────────────
|
|
54
|
+
/** Ensure clefbase.json (which contains secrets) is in .gitignore. */
|
|
55
|
+
function ensureGitignore(cwd = process.cwd()) {
|
|
56
|
+
const p = path_1.default.join(cwd, ".gitignore");
|
|
57
|
+
const entry = "clefbase.json";
|
|
58
|
+
if (fs_1.default.existsSync(p)) {
|
|
59
|
+
const content = fs_1.default.readFileSync(p, "utf-8");
|
|
60
|
+
if (!content.split("\n").some(l => l.trim() === entry)) {
|
|
61
|
+
fs_1.default.appendFileSync(p, `\n# Clefbase config (contains secrets)\n${entry}\n`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
fs_1.default.writeFileSync(p, `# Clefbase config (contains secrets)\n${entry}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ─── .env.example ─────────────────────────────────────────────────────────────
|
|
69
|
+
/** Write a .env.example if one doesn't already exist. */
|
|
70
|
+
function writeEnvExample(cfg, cwd = process.cwd()) {
|
|
71
|
+
const p = path_1.default.join(cwd, ".env.example");
|
|
72
|
+
if (fs_1.default.existsSync(p))
|
|
73
|
+
return;
|
|
74
|
+
const lines = [
|
|
75
|
+
"# Clefbase — copy to .env and fill in your values",
|
|
76
|
+
`CLEFBASE_SERVER_URL=${cfg.serverUrl}`,
|
|
77
|
+
`CLEFBASE_PROJECT_ID=${cfg.projectId}`,
|
|
78
|
+
"CLEFBASE_API_KEY=your_api_key_here",
|
|
79
|
+
"CLEFBASE_ADMIN_SECRET=your_admin_secret_here",
|
|
80
|
+
];
|
|
81
|
+
fs_1.default.writeFileSync(p, lines.join("\n") + "\n");
|
|
82
|
+
}
|