@curenorway/kode-cli 1.18.0 → 2.0.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 +10 -1
- package/dist/{chunk-MSXS4ARI.js → chunk-6RYN72CO.js} +727 -1172
- package/dist/chunk-GO6KLUXM.js +500 -0
- package/dist/chunk-MUW33LPZ.js +265 -0
- package/dist/chunk-R4KWWTS4.js +75 -0
- package/dist/chunk-TLLGB46I.js +418 -0
- package/dist/cli.js +345 -38
- package/dist/index.d.ts +73 -0
- package/dist/index.js +16 -12
- package/dist/pkg-DOXTOICF.js +18 -0
- package/dist/pull-G7GR67GG.js +7 -0
- package/dist/types-PZ7GFJEK.js +9 -0
- package/package.json +2 -1
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import Conf from "conf";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { join, dirname } from "path";
|
|
5
|
+
var globalConfig = new Conf({
|
|
6
|
+
projectName: "cure-kode",
|
|
7
|
+
defaults: {
|
|
8
|
+
apiUrl: "https://app.cure.no"
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
var PROJECT_CONFIG_DIR = ".cure-kode";
|
|
12
|
+
var PROJECT_CONFIG_FILE = "config.json";
|
|
13
|
+
var DEFAULT_SCRIPTS_DIR = ".cure-kode-scripts";
|
|
14
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
15
|
+
let currentDir = startDir;
|
|
16
|
+
while (currentDir !== dirname(currentDir)) {
|
|
17
|
+
const configPath = join(currentDir, PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILE);
|
|
18
|
+
if (existsSync(configPath)) {
|
|
19
|
+
return currentDir;
|
|
20
|
+
}
|
|
21
|
+
currentDir = dirname(currentDir);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
function getProjectConfig(projectRoot) {
|
|
26
|
+
const root = projectRoot || findProjectRoot();
|
|
27
|
+
if (!root) return null;
|
|
28
|
+
const configPath = join(root, PROJECT_CONFIG_DIR, PROJECT_CONFIG_FILE);
|
|
29
|
+
if (!existsSync(configPath)) return null;
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(configPath, "utf-8");
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function saveProjectConfig(config, projectRoot = process.cwd()) {
|
|
38
|
+
const configDir = join(projectRoot, PROJECT_CONFIG_DIR);
|
|
39
|
+
const configPath = join(configDir, PROJECT_CONFIG_FILE);
|
|
40
|
+
if (!existsSync(configDir)) {
|
|
41
|
+
mkdirSync(configDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
44
|
+
}
|
|
45
|
+
function getApiUrl(projectConfig) {
|
|
46
|
+
return projectConfig?.apiUrl || globalConfig.get("apiUrl");
|
|
47
|
+
}
|
|
48
|
+
function getApiKey(projectConfig) {
|
|
49
|
+
return projectConfig?.apiKey || globalConfig.get("defaultApiKey") || null;
|
|
50
|
+
}
|
|
51
|
+
function setGlobalConfig(key, value) {
|
|
52
|
+
globalConfig.set(key, value);
|
|
53
|
+
}
|
|
54
|
+
function getScriptsDir(projectRoot, projectConfig) {
|
|
55
|
+
return join(projectRoot, projectConfig?.scriptsDir || DEFAULT_SCRIPTS_DIR);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/lib/retry.ts
|
|
59
|
+
function isRetryableError(error) {
|
|
60
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
const err = error;
|
|
64
|
+
const statusCode = err.statusCode || err.status;
|
|
65
|
+
if (statusCode && statusCode >= 400 && statusCode < 500) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
if (statusCode && statusCode >= 500) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
if (err.code === "ECONNRESET" || err.code === "ETIMEDOUT" || err.code === "ENOTFOUND") {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (error instanceof Error) {
|
|
75
|
+
const message = error.message.toLowerCase();
|
|
76
|
+
if (message.includes("network") || message.includes("timeout") || message.includes("connection") || message.includes("socket")) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
function calculateDelay(attempt, baseDelayMs, maxDelayMs, backoffMultiplier, jitter) {
|
|
83
|
+
const exponentialDelay = baseDelayMs * Math.pow(backoffMultiplier, attempt - 1);
|
|
84
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
|
|
85
|
+
if (!jitter) {
|
|
86
|
+
return cappedDelay;
|
|
87
|
+
}
|
|
88
|
+
const jitterRange = cappedDelay * 0.25;
|
|
89
|
+
const jitterOffset = (Math.random() - 0.5) * 2 * jitterRange;
|
|
90
|
+
return Math.max(0, Math.round(cappedDelay + jitterOffset));
|
|
91
|
+
}
|
|
92
|
+
function sleep(ms) {
|
|
93
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
94
|
+
}
|
|
95
|
+
async function withRetry(fn, options = {}) {
|
|
96
|
+
const {
|
|
97
|
+
maxAttempts = 3,
|
|
98
|
+
baseDelayMs = 500,
|
|
99
|
+
maxDelayMs = 5e3,
|
|
100
|
+
backoffMultiplier = 2,
|
|
101
|
+
jitter = true,
|
|
102
|
+
isRetryable = isRetryableError,
|
|
103
|
+
onRetry
|
|
104
|
+
} = options;
|
|
105
|
+
let lastError;
|
|
106
|
+
let totalDelayMs = 0;
|
|
107
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
108
|
+
try {
|
|
109
|
+
return await fn();
|
|
110
|
+
} catch (error) {
|
|
111
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
112
|
+
if (attempt >= maxAttempts || !isRetryable(error)) {
|
|
113
|
+
throw lastError;
|
|
114
|
+
}
|
|
115
|
+
const delayMs = calculateDelay(
|
|
116
|
+
attempt,
|
|
117
|
+
baseDelayMs,
|
|
118
|
+
maxDelayMs,
|
|
119
|
+
backoffMultiplier,
|
|
120
|
+
jitter
|
|
121
|
+
);
|
|
122
|
+
totalDelayMs += delayMs;
|
|
123
|
+
if (onRetry) {
|
|
124
|
+
onRetry(attempt, error, delayMs);
|
|
125
|
+
}
|
|
126
|
+
await sleep(delayMs);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
throw lastError || new Error("Retry failed");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/api.ts
|
|
133
|
+
var KodeApiError = class extends Error {
|
|
134
|
+
constructor(message, statusCode, response) {
|
|
135
|
+
super(message);
|
|
136
|
+
this.statusCode = statusCode;
|
|
137
|
+
this.response = response;
|
|
138
|
+
this.name = "KodeApiError";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var KodeApiClient = class {
|
|
142
|
+
baseUrl;
|
|
143
|
+
apiKey;
|
|
144
|
+
constructor(config) {
|
|
145
|
+
this.baseUrl = getApiUrl(config);
|
|
146
|
+
const apiKey = getApiKey(config);
|
|
147
|
+
if (!apiKey) {
|
|
148
|
+
throw new Error('API key is required. Run "kode init" first.');
|
|
149
|
+
}
|
|
150
|
+
this.apiKey = apiKey;
|
|
151
|
+
}
|
|
152
|
+
async request(endpoint, options = {}) {
|
|
153
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
154
|
+
return withRetry(
|
|
155
|
+
async () => {
|
|
156
|
+
const response = await fetch(url, {
|
|
157
|
+
...options,
|
|
158
|
+
headers: {
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
"X-API-Key": this.apiKey,
|
|
161
|
+
...options.headers
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
const data = await response.json();
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
throw new KodeApiError(
|
|
167
|
+
data.error || `Request failed with status ${response.status}`,
|
|
168
|
+
response.status,
|
|
169
|
+
data
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return data;
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
maxAttempts: 3,
|
|
176
|
+
baseDelayMs: 500,
|
|
177
|
+
// Custom retry check: retry on network errors and 5xx, but not 4xx
|
|
178
|
+
isRetryable: (error) => {
|
|
179
|
+
if (error instanceof KodeApiError) {
|
|
180
|
+
return error.statusCode >= 500;
|
|
181
|
+
}
|
|
182
|
+
return isRetryableError(error);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
// Sites
|
|
188
|
+
async getSite(siteId) {
|
|
189
|
+
return this.request(`/api/cdn/sites/${siteId}`);
|
|
190
|
+
}
|
|
191
|
+
async listSites() {
|
|
192
|
+
return this.request("/api/cdn/sites");
|
|
193
|
+
}
|
|
194
|
+
// Scripts
|
|
195
|
+
async listScripts(siteId) {
|
|
196
|
+
return this.request(`/api/cdn/sites/${siteId}/scripts`);
|
|
197
|
+
}
|
|
198
|
+
async getScript(scriptId) {
|
|
199
|
+
return this.request(`/api/cdn/scripts/${scriptId}`);
|
|
200
|
+
}
|
|
201
|
+
async createScript(siteId, data) {
|
|
202
|
+
return this.request(`/api/cdn/sites/${siteId}/scripts`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
body: JSON.stringify(data)
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async updateScript(scriptId, data) {
|
|
208
|
+
return this.request(`/api/cdn/scripts/${scriptId}`, {
|
|
209
|
+
method: "PUT",
|
|
210
|
+
body: JSON.stringify(data)
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async deleteScript(scriptId) {
|
|
214
|
+
await this.request(`/api/cdn/scripts/${scriptId}`, {
|
|
215
|
+
method: "DELETE"
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// Upload (alternative endpoint that works with API keys)
|
|
219
|
+
async uploadScript(data) {
|
|
220
|
+
return this.request("/api/cdn/upload", {
|
|
221
|
+
method: "POST",
|
|
222
|
+
body: JSON.stringify({
|
|
223
|
+
...data,
|
|
224
|
+
apiKey: this.apiKey
|
|
225
|
+
})
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
// Pages
|
|
229
|
+
async listPages(siteId) {
|
|
230
|
+
return this.request(`/api/cdn/sites/${siteId}/pages`);
|
|
231
|
+
}
|
|
232
|
+
async getPage(pageId) {
|
|
233
|
+
return this.request(`/api/cdn/pages/${pageId}`);
|
|
234
|
+
}
|
|
235
|
+
async createPage(siteId, data) {
|
|
236
|
+
return this.request(`/api/cdn/sites/${siteId}/pages`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: JSON.stringify(data)
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async updatePage(pageId, data) {
|
|
242
|
+
return this.request(`/api/cdn/pages/${pageId}`, {
|
|
243
|
+
method: "PATCH",
|
|
244
|
+
body: JSON.stringify(data)
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async deletePage(pageId) {
|
|
248
|
+
return this.request(`/api/cdn/pages/${pageId}`, {
|
|
249
|
+
method: "DELETE"
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async assignScriptToPage(pageId, scriptId, loadOrderOverride) {
|
|
253
|
+
return this.request(`/api/cdn/pages/${pageId}/scripts`, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
body: JSON.stringify({ scriptId, loadOrderOverride })
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
async removeScriptFromPage(pageId, scriptId) {
|
|
259
|
+
return this.request(`/api/cdn/pages/${pageId}/scripts/${scriptId}`, {
|
|
260
|
+
method: "DELETE"
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
async getPageScripts(pageId) {
|
|
264
|
+
return this.request(`/api/cdn/pages/${pageId}/scripts`);
|
|
265
|
+
}
|
|
266
|
+
// Deployments
|
|
267
|
+
async deploy(siteId, environment = "staging") {
|
|
268
|
+
return this.request("/api/cdn/deploy", {
|
|
269
|
+
method: "POST",
|
|
270
|
+
body: JSON.stringify({
|
|
271
|
+
siteId,
|
|
272
|
+
environment,
|
|
273
|
+
apiKey: this.apiKey
|
|
274
|
+
})
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
async promoteToProduction(siteId, stagingDeploymentId) {
|
|
278
|
+
return this.request("/api/cdn/deploy/promote", {
|
|
279
|
+
method: "POST",
|
|
280
|
+
body: JSON.stringify({
|
|
281
|
+
siteId,
|
|
282
|
+
stagingDeploymentId,
|
|
283
|
+
apiKey: this.apiKey
|
|
284
|
+
})
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
async getDeploymentStatus(siteId) {
|
|
288
|
+
return this.request(`/api/cdn/sites/${siteId}/deployments/status`);
|
|
289
|
+
}
|
|
290
|
+
async rollback(siteId, environment = "staging") {
|
|
291
|
+
return this.request("/api/cdn/deploy/rollback", {
|
|
292
|
+
method: "POST",
|
|
293
|
+
body: JSON.stringify({
|
|
294
|
+
siteId,
|
|
295
|
+
environment
|
|
296
|
+
})
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
// Production enabled toggle (v2.3)
|
|
300
|
+
async setProductionEnabled(siteId, enabled, productionDomain) {
|
|
301
|
+
return this.request(`/api/cdn/sites/${siteId}/production`, {
|
|
302
|
+
method: "POST",
|
|
303
|
+
body: JSON.stringify({
|
|
304
|
+
enabled,
|
|
305
|
+
productionDomain
|
|
306
|
+
})
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
// HTML Fetch
|
|
310
|
+
async fetchHtml(url) {
|
|
311
|
+
return this.request("/api/cdn/fetch-html", {
|
|
312
|
+
method: "POST",
|
|
313
|
+
body: JSON.stringify({ url })
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
// Lock management
|
|
317
|
+
async getLockStatus(siteId) {
|
|
318
|
+
return this.request(`/api/cdn/deploy/lock?siteId=${siteId}`);
|
|
319
|
+
}
|
|
320
|
+
async forceReleaseLock(siteId) {
|
|
321
|
+
return this.request("/api/cdn/deploy/lock", {
|
|
322
|
+
method: "DELETE",
|
|
323
|
+
body: JSON.stringify({ siteId })
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
// Library (global snippets)
|
|
327
|
+
async listLibrary(search) {
|
|
328
|
+
const params = new URLSearchParams();
|
|
329
|
+
if (search) params.set("search", search);
|
|
330
|
+
const qs = params.toString();
|
|
331
|
+
return this.request(`/api/cdn/library${qs ? `?${qs}` : ""}`);
|
|
332
|
+
}
|
|
333
|
+
async getLibrarySnippet(slugOrId) {
|
|
334
|
+
return this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`);
|
|
335
|
+
}
|
|
336
|
+
async createLibrarySnippet(data) {
|
|
337
|
+
return this.request("/api/cdn/library", {
|
|
338
|
+
method: "POST",
|
|
339
|
+
body: JSON.stringify(data)
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
async recordLibraryUsage(snippetId) {
|
|
343
|
+
await this.request(`/api/cdn/library/${snippetId}`);
|
|
344
|
+
}
|
|
345
|
+
async updateLibrarySnippet(slugOrId, data) {
|
|
346
|
+
return this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`, {
|
|
347
|
+
method: "PATCH",
|
|
348
|
+
body: JSON.stringify(data)
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
async listLibraryFolders() {
|
|
352
|
+
return this.request("/api/cdn/library/folders");
|
|
353
|
+
}
|
|
354
|
+
async createLibraryFolder(data) {
|
|
355
|
+
return this.request("/api/cdn/library/folders", {
|
|
356
|
+
method: "POST",
|
|
357
|
+
body: JSON.stringify(data)
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
async listLibraryTrash() {
|
|
361
|
+
return this.request("/api/cdn/library/trash");
|
|
362
|
+
}
|
|
363
|
+
async restoreLibrarySnippet(snippetId) {
|
|
364
|
+
await this.request("/api/cdn/library/trash", {
|
|
365
|
+
method: "POST",
|
|
366
|
+
body: JSON.stringify({ action: "restore", snippetId })
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
async permanentDeleteLibrarySnippet(snippetId) {
|
|
370
|
+
await this.request(`/api/cdn/library/trash?snippetId=${encodeURIComponent(snippetId)}`, {
|
|
371
|
+
method: "DELETE"
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
// Webflow Custom Code injection
|
|
375
|
+
async getWebflowCustomCodeStatus(siteId) {
|
|
376
|
+
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`);
|
|
377
|
+
}
|
|
378
|
+
async injectWebflowCustomCode(siteId, location = "header") {
|
|
379
|
+
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`, {
|
|
380
|
+
method: "POST",
|
|
381
|
+
body: JSON.stringify({ location })
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
async removeWebflowCustomCode(siteId) {
|
|
385
|
+
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`, {
|
|
386
|
+
method: "DELETE"
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
async getCmsTypes(siteId) {
|
|
390
|
+
return this.request(`/api/cdn/sites/${siteId}/cms-types`);
|
|
391
|
+
}
|
|
392
|
+
async getProjectFiles(siteId) {
|
|
393
|
+
return this.request(`/api/cdn/sites/${siteId}/files`);
|
|
394
|
+
}
|
|
395
|
+
async updateProjectFiles(siteId, files) {
|
|
396
|
+
return this.request(`/api/cdn/sites/${siteId}/files`, {
|
|
397
|
+
method: "PUT",
|
|
398
|
+
body: JSON.stringify({ files })
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
function createApiClient(config) {
|
|
403
|
+
return new KodeApiClient(config);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export {
|
|
407
|
+
DEFAULT_SCRIPTS_DIR,
|
|
408
|
+
findProjectRoot,
|
|
409
|
+
getProjectConfig,
|
|
410
|
+
saveProjectConfig,
|
|
411
|
+
getApiUrl,
|
|
412
|
+
getApiKey,
|
|
413
|
+
setGlobalConfig,
|
|
414
|
+
getScriptsDir,
|
|
415
|
+
KodeApiError,
|
|
416
|
+
KodeApiClient,
|
|
417
|
+
createApiClient
|
|
418
|
+
};
|