@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
|
@@ -1,67 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
pagesToInfoFormat,
|
|
3
|
+
scriptsToDocsFormat,
|
|
4
|
+
updateClaudeMd,
|
|
5
|
+
updateKodeDocs
|
|
6
|
+
} from "./chunk-GO6KLUXM.js";
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_SCRIPTS_DIR,
|
|
9
|
+
createApiClient,
|
|
10
|
+
findProjectRoot,
|
|
11
|
+
getProjectConfig,
|
|
12
|
+
getScriptsDir,
|
|
13
|
+
saveProjectConfig
|
|
14
|
+
} from "./chunk-TLLGB46I.js";
|
|
57
15
|
|
|
58
16
|
// src/lib/context.ts
|
|
59
|
-
import { existsSync
|
|
60
|
-
import { join
|
|
17
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
18
|
+
import { join } from "path";
|
|
61
19
|
var CONTEXT_FILE = "context.md";
|
|
62
|
-
var
|
|
20
|
+
var PROJECT_CONFIG_DIR = ".cure-kode";
|
|
63
21
|
function getContextPath(projectRoot) {
|
|
64
|
-
return
|
|
22
|
+
return join(projectRoot, PROJECT_CONFIG_DIR, CONTEXT_FILE);
|
|
65
23
|
}
|
|
66
24
|
function parseContext(content) {
|
|
67
25
|
const context = {
|
|
@@ -270,11 +228,11 @@ function serializeContext(context) {
|
|
|
270
228
|
}
|
|
271
229
|
function readContext(projectRoot) {
|
|
272
230
|
const contextPath = getContextPath(projectRoot);
|
|
273
|
-
if (!
|
|
231
|
+
if (!existsSync(contextPath)) {
|
|
274
232
|
return null;
|
|
275
233
|
}
|
|
276
234
|
try {
|
|
277
|
-
const content =
|
|
235
|
+
const content = readFileSync(contextPath, "utf-8");
|
|
278
236
|
return parseContext(content);
|
|
279
237
|
} catch {
|
|
280
238
|
return null;
|
|
@@ -283,7 +241,7 @@ function readContext(projectRoot) {
|
|
|
283
241
|
function writeContext(projectRoot, context) {
|
|
284
242
|
const contextPath = getContextPath(projectRoot);
|
|
285
243
|
const content = serializeContext(context);
|
|
286
|
-
|
|
244
|
+
writeFileSync(contextPath, content, "utf-8");
|
|
287
245
|
}
|
|
288
246
|
function appendNote(projectRoot, note, agent = "CLI") {
|
|
289
247
|
const context = readContext(projectRoot);
|
|
@@ -1201,285 +1159,47 @@ Option C: Full rollback
|
|
|
1201
1159
|
}
|
|
1202
1160
|
|
|
1203
1161
|
// src/version.ts
|
|
1204
|
-
var CLI_VERSION = "1.
|
|
1162
|
+
var CLI_VERSION = "1.19.0";
|
|
1205
1163
|
|
|
1206
1164
|
// src/commands/init.ts
|
|
1207
1165
|
import chalk from "chalk";
|
|
1208
1166
|
import ora from "ora";
|
|
1209
1167
|
import enquirer from "enquirer";
|
|
1210
|
-
import { existsSync as
|
|
1211
|
-
import { join as join5 } from "path";
|
|
1212
|
-
|
|
1213
|
-
// src/lib/claude-md.ts
|
|
1214
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
1168
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync3 } from "fs";
|
|
1215
1169
|
import { join as join3 } from "path";
|
|
1216
|
-
var KODE_REFERENCE = `## Cure Kode
|
|
1217
|
-
|
|
1218
|
-
This project uses **Cure Kode** for JS/CSS delivery. **Before modifying scripts:**
|
|
1219
|
-
|
|
1220
|
-
1. Run \`kode pull\` to get latest from server
|
|
1221
|
-
2. Make your changes in \`.cure-kode-scripts/\`
|
|
1222
|
-
3. Run \`kode push\` to upload, then \`kode deploy\` to publish
|
|
1223
|
-
|
|
1224
|
-
Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
|
|
1225
|
-
|
|
1226
|
-
---
|
|
1227
|
-
|
|
1228
|
-
`;
|
|
1229
|
-
function hasKodeReference(content) {
|
|
1230
|
-
return content.includes(".cure-kode/KODE.md") || content.includes("cure-kode/KODE.md") || content.includes("KODE.md");
|
|
1231
|
-
}
|
|
1232
|
-
function generateKodeMd(siteName, siteSlug, scripts, pages, options) {
|
|
1233
|
-
const productionEnabled = options?.productionEnabled ?? false;
|
|
1234
|
-
let md = `# Cure Kode: ${siteName}
|
|
1235
|
-
|
|
1236
|
-
This project uses **Cure Kode** CDN for JavaScript/CSS delivery to Webflow.
|
|
1237
|
-
|
|
1238
|
-
> **This file is auto-generated.** Do not edit manually - changes will be overwritten by \`kode pull\`, \`kode push\`, and \`kode sync\`.
|
|
1239
|
-
|
|
1240
|
-
**Produksjon:** ${productionEnabled ? "\u2713 Aktivert \u2014 deploy til staging og produksjon" : "\u25CB Deaktivert \u2014 kun staging. Ikke hent fra produksjons-URL."}
|
|
1241
|
-
|
|
1242
|
-
---
|
|
1243
|
-
|
|
1244
|
-
`;
|
|
1245
|
-
md += `## CDN URL
|
|
1246
|
-
|
|
1247
|
-
Add this to Webflow \u2192 Project Settings \u2192 Custom Code \u2192 Head:
|
|
1248
|
-
|
|
1249
|
-
\`\`\`html
|
|
1250
|
-
<script src="https://app.cure.no/api/cdn/${siteSlug}/init.js"></script>
|
|
1251
|
-
\`\`\`
|
|
1252
|
-
|
|
1253
|
-
---
|
|
1254
|
-
|
|
1255
|
-
`;
|
|
1256
|
-
md += `## Scripts
|
|
1257
|
-
|
|
1258
|
-
`;
|
|
1259
|
-
if (scripts.length > 0) {
|
|
1260
|
-
md += `| Script | Type | Scope | Auto | Purpose |
|
|
1261
|
-
|--------|------|-------|------|---------|
|
|
1262
|
-
`;
|
|
1263
|
-
for (const script of scripts) {
|
|
1264
|
-
const type = script.type === "javascript" ? "JS" : "CSS";
|
|
1265
|
-
const scope = script.scope === "global" ? "Global" : "Page";
|
|
1266
|
-
const auto = script.autoLoad ? "\u26A1" : "\u25CB";
|
|
1267
|
-
const purpose = script.purpose || script.description || "\u2014";
|
|
1268
|
-
const truncated = purpose.length > 50 ? purpose.slice(0, 47) + "..." : purpose;
|
|
1269
|
-
md += `| \`${script.slug}\` | ${type} | ${scope} | ${auto} | ${truncated} |
|
|
1270
|
-
`;
|
|
1271
|
-
}
|
|
1272
|
-
md += `
|
|
1273
|
-
> \u26A1 = auto-loads on every page, \u25CB = manual load via \`CK.loadScript('slug')\`
|
|
1274
|
-
|
|
1275
|
-
`;
|
|
1276
|
-
} else {
|
|
1277
|
-
md += `No scripts yet. Create one with \`kode push\`.
|
|
1278
|
-
|
|
1279
|
-
`;
|
|
1280
|
-
}
|
|
1281
|
-
if (pages.length > 0) {
|
|
1282
|
-
md += `---
|
|
1283
|
-
|
|
1284
|
-
## Pages
|
|
1285
|
-
|
|
1286
|
-
Page-specific scripts only load when URL matches these patterns:
|
|
1287
|
-
|
|
1288
|
-
`;
|
|
1289
|
-
for (const page of pages) {
|
|
1290
|
-
md += `- **${page.name}** (\`${page.slug}\`) \u2192 \`${page.patterns.join("`, `")}\`
|
|
1291
|
-
`;
|
|
1292
|
-
}
|
|
1293
|
-
md += `
|
|
1294
|
-
`;
|
|
1295
|
-
}
|
|
1296
|
-
const pageSpecificScripts = scripts.filter(
|
|
1297
|
-
(s) => s.scope === "page-specific" && s.pageAssignments && s.pageAssignments.length > 0
|
|
1298
|
-
);
|
|
1299
|
-
if (pageSpecificScripts.length > 0) {
|
|
1300
|
-
md += `---
|
|
1301
|
-
|
|
1302
|
-
## Script \u2192 Page Assignments
|
|
1303
|
-
|
|
1304
|
-
| Script | Pages |
|
|
1305
|
-
|--------|-------|
|
|
1306
|
-
`;
|
|
1307
|
-
for (const script of pageSpecificScripts) {
|
|
1308
|
-
const pageList = script.pageAssignments.map((pa) => {
|
|
1309
|
-
const page = pages.find((p) => p.slug === pa.pageSlug);
|
|
1310
|
-
const patterns = page ? ` (\`${page.patterns.join("`, `")}\`)` : "";
|
|
1311
|
-
return `\`${pa.pageSlug}\`${patterns}`;
|
|
1312
|
-
}).join(", ");
|
|
1313
|
-
md += `| \`${script.slug}\` | ${pageList} |
|
|
1314
|
-
`;
|
|
1315
|
-
}
|
|
1316
|
-
md += `
|
|
1317
|
-
`;
|
|
1318
|
-
}
|
|
1319
|
-
md += `---
|
|
1320
|
-
|
|
1321
|
-
## Workflow
|
|
1322
|
-
|
|
1323
|
-
**Before making changes:**
|
|
1324
|
-
\`\`\`bash
|
|
1325
|
-
kode pull # Get latest scripts from server
|
|
1326
|
-
\`\`\`
|
|
1327
|
-
|
|
1328
|
-
**After making changes:**
|
|
1329
|
-
\`\`\`bash
|
|
1330
|
-
kode push # Upload your changes
|
|
1331
|
-
kode deploy # Deploy to staging
|
|
1332
|
-
kode deploy --promote # When ready, promote to production
|
|
1333
|
-
\`\`\`
|
|
1334
|
-
|
|
1335
|
-
**Periodically:**
|
|
1336
|
-
\`\`\`bash
|
|
1337
|
-
kode doctor # Check for issues and CLI updates
|
|
1338
|
-
\`\`\`
|
|
1339
|
-
|
|
1340
|
-
---
|
|
1341
|
-
|
|
1342
|
-
## CLI Commands
|
|
1343
|
-
|
|
1344
|
-
| Command | Description |
|
|
1345
|
-
|---------|-------------|
|
|
1346
|
-
| \`kode status\` | Show sync status and staleness warnings |
|
|
1347
|
-
| \`kode pull\` | Download scripts from server |
|
|
1348
|
-
| \`kode push\` | Upload local changes |
|
|
1349
|
-
| \`kode sync\` | Bidirectional sync with conflict detection |
|
|
1350
|
-
| \`kode diff <script>\` | Compare local vs remote versions |
|
|
1351
|
-
| \`kode deploy\` | Deploy to staging |
|
|
1352
|
-
| \`kode deploy --dry-run\` | Preview deployment without executing |
|
|
1353
|
-
| \`kode deploy --promote\` | Promote staging to production |
|
|
1354
|
-
| \`kode doctor\` | Check config, API, sync state, and CLI version |
|
|
1355
|
-
| \`kode pages\` | List CDN pages |
|
|
1356
|
-
| \`kode pages create <name> -p /path\` | Create a page with URL patterns |
|
|
1357
|
-
| \`kode pages assign <page> <script>\` | Assign script to page |
|
|
1358
|
-
| \`kode library\` | List global library snippets |
|
|
1359
|
-
| \`kode library search <query>\` | Search library |
|
|
1360
|
-
| \`kode library add <slug>\` | Copy snippet into project |
|
|
1361
|
-
|
|
1362
|
-
## MCP Tools
|
|
1363
|
-
|
|
1364
|
-
AI agents can use these tools directly:
|
|
1365
|
-
|
|
1366
|
-
- \`kode_list_scripts\` - List all scripts
|
|
1367
|
-
- \`kode_push\` - Upload local files
|
|
1368
|
-
- \`kode_deploy\` / \`kode_promote\` - Deploy to staging/production
|
|
1369
|
-
- \`kode_create_page\` - Create CDN page with URL patterns
|
|
1370
|
-
- \`kode_assign_script_to_page\` - Assign script to page
|
|
1371
|
-
- \`kode_library_list\` - Browse global script library
|
|
1372
|
-
- \`kode_library_get\` - Get snippet with full code
|
|
1373
|
-
- \`kode_library_use\` - Copy snippet into project
|
|
1374
|
-
|
|
1375
|
-
## File Structure
|
|
1376
|
-
|
|
1377
|
-
\`\`\`
|
|
1378
|
-
.cure-kode/
|
|
1379
|
-
\u251C\u2500\u2500 config.json # Site configuration (contains API key - gitignored)
|
|
1380
|
-
\u251C\u2500\u2500 scripts.json # Sync state for conflict detection
|
|
1381
|
-
\u251C\u2500\u2500 context.md # Dynamic context for AI agents
|
|
1382
|
-
\u2514\u2500\u2500 KODE.md # This file - auto-generated docs
|
|
1383
|
-
|
|
1384
|
-
.cure-kode-scripts/ # Your script files
|
|
1385
|
-
\u251C\u2500\u2500 main.js
|
|
1386
|
-
\u251C\u2500\u2500 form-handler.js
|
|
1387
|
-
\u2514\u2500\u2500 library/ # Reference only (never deployed)
|
|
1388
|
-
\u251C\u2500\u2500 animations/
|
|
1389
|
-
\u251C\u2500\u2500 forms/
|
|
1390
|
-
\u2514\u2500\u2500 utilities/
|
|
1391
|
-
|
|
1392
|
-
.claude/skills/ # Claude Code skills
|
|
1393
|
-
\u251C\u2500\u2500 deploy/ # /deploy \u2014 push and deploy workflow
|
|
1394
|
-
\u2514\u2500\u2500 webflow-patterns/ # Auto-loaded Webflow DOM reference
|
|
1395
|
-
\`\`\`
|
|
1396
|
-
`;
|
|
1397
|
-
return md;
|
|
1398
|
-
}
|
|
1399
|
-
function ensureClaudeMdReference(projectRoot) {
|
|
1400
|
-
const claudeMdPath = join3(projectRoot, "CLAUDE.md");
|
|
1401
|
-
if (!existsSync3(claudeMdPath)) {
|
|
1402
|
-
writeFileSync3(claudeMdPath, KODE_REFERENCE);
|
|
1403
|
-
return { created: true, updated: false, skipped: false };
|
|
1404
|
-
}
|
|
1405
|
-
const content = readFileSync3(claudeMdPath, "utf-8");
|
|
1406
|
-
if (hasKodeReference(content)) {
|
|
1407
|
-
return { created: false, updated: false, skipped: true };
|
|
1408
|
-
}
|
|
1409
|
-
const newContent = KODE_REFERENCE + content;
|
|
1410
|
-
writeFileSync3(claudeMdPath, newContent);
|
|
1411
|
-
return { created: false, updated: true, skipped: false };
|
|
1412
|
-
}
|
|
1413
|
-
function updateKodeMd(projectRoot, siteName, siteSlug, scripts, pages, options) {
|
|
1414
|
-
const kodeMdPath = join3(projectRoot, ".cure-kode", "KODE.md");
|
|
1415
|
-
const existed = existsSync3(kodeMdPath);
|
|
1416
|
-
const content = generateKodeMd(siteName, siteSlug, scripts, pages, options);
|
|
1417
|
-
writeFileSync3(kodeMdPath, content);
|
|
1418
|
-
return { created: !existed, updated: existed };
|
|
1419
|
-
}
|
|
1420
|
-
function updateKodeDocs(projectRoot, siteName, siteSlug, scripts, pages, options) {
|
|
1421
|
-
const kodeMd = updateKodeMd(projectRoot, siteName, siteSlug, scripts, pages, options);
|
|
1422
|
-
const claudeMd = ensureClaudeMdReference(projectRoot);
|
|
1423
|
-
return { kodeMd, claudeMd };
|
|
1424
|
-
}
|
|
1425
|
-
function scriptsToDocsFormat(scripts, pages) {
|
|
1426
|
-
return scripts.map((s) => {
|
|
1427
|
-
const pageAssignments = (s.pages || []).filter((p) => p.is_enabled).map((p) => {
|
|
1428
|
-
const page = pages?.find((pg) => pg.id === p.page_id);
|
|
1429
|
-
return page ? { pageId: page.id, pageSlug: page.slug, pageName: page.name } : null;
|
|
1430
|
-
}).filter((p) => p !== null);
|
|
1431
|
-
return {
|
|
1432
|
-
slug: s.slug,
|
|
1433
|
-
name: s.name,
|
|
1434
|
-
type: s.type,
|
|
1435
|
-
scope: s.scope,
|
|
1436
|
-
autoLoad: s.auto_load,
|
|
1437
|
-
purpose: s.metadata?.purpose || s.description,
|
|
1438
|
-
pageAssignments: pageAssignments.length > 0 ? pageAssignments : void 0
|
|
1439
|
-
};
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
function pagesToInfoFormat(pages) {
|
|
1443
|
-
return pages.filter((p) => p.is_active).map((p) => ({
|
|
1444
|
-
name: p.name,
|
|
1445
|
-
slug: p.slug,
|
|
1446
|
-
patterns: p.url_patterns
|
|
1447
|
-
}));
|
|
1448
|
-
}
|
|
1449
|
-
var updateClaudeMd = updateKodeDocs;
|
|
1450
1170
|
|
|
1451
1171
|
// src/lib/skills.ts
|
|
1452
|
-
import { existsSync as
|
|
1453
|
-
import { join as
|
|
1172
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1173
|
+
import { join as join2 } from "path";
|
|
1454
1174
|
function generateSkills(projectRoot, siteSlug) {
|
|
1455
|
-
const skillsDir =
|
|
1175
|
+
const skillsDir = join2(projectRoot, ".claude", "skills");
|
|
1456
1176
|
const created = [];
|
|
1457
1177
|
const skipped = [];
|
|
1458
|
-
const deployDir =
|
|
1459
|
-
const deployPath =
|
|
1460
|
-
if (!
|
|
1461
|
-
|
|
1462
|
-
|
|
1178
|
+
const deployDir = join2(skillsDir, "deploy");
|
|
1179
|
+
const deployPath = join2(deployDir, "SKILL.md");
|
|
1180
|
+
if (!existsSync2(deployPath)) {
|
|
1181
|
+
mkdirSync(deployDir, { recursive: true });
|
|
1182
|
+
writeFileSync2(deployPath, generateDeploySkill(siteSlug));
|
|
1463
1183
|
created.push("deploy");
|
|
1464
1184
|
} else {
|
|
1465
1185
|
skipped.push("deploy");
|
|
1466
1186
|
}
|
|
1467
|
-
const libraryDir =
|
|
1468
|
-
const libraryPath =
|
|
1469
|
-
if (!
|
|
1470
|
-
|
|
1471
|
-
|
|
1187
|
+
const libraryDir = join2(skillsDir, "library");
|
|
1188
|
+
const libraryPath = join2(libraryDir, "SKILL.md");
|
|
1189
|
+
if (!existsSync2(libraryPath)) {
|
|
1190
|
+
mkdirSync(libraryDir, { recursive: true });
|
|
1191
|
+
writeFileSync2(libraryPath, LIBRARY_SKILL);
|
|
1472
1192
|
created.push("library");
|
|
1473
1193
|
} else {
|
|
1474
1194
|
skipped.push("library");
|
|
1475
1195
|
}
|
|
1476
|
-
const webflowDir =
|
|
1477
|
-
const webflowSkillPath =
|
|
1478
|
-
const webflowRefPath =
|
|
1479
|
-
if (!
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1196
|
+
const webflowDir = join2(skillsDir, "webflow-patterns");
|
|
1197
|
+
const webflowSkillPath = join2(webflowDir, "SKILL.md");
|
|
1198
|
+
const webflowRefPath = join2(webflowDir, "reference.md");
|
|
1199
|
+
if (!existsSync2(webflowSkillPath)) {
|
|
1200
|
+
mkdirSync(webflowDir, { recursive: true });
|
|
1201
|
+
writeFileSync2(webflowSkillPath, WEBFLOW_SKILL);
|
|
1202
|
+
writeFileSync2(webflowRefPath, WEBFLOW_REFERENCE);
|
|
1483
1203
|
created.push("webflow-patterns");
|
|
1484
1204
|
} else {
|
|
1485
1205
|
skipped.push("webflow-patterns");
|
|
@@ -1585,7 +1305,7 @@ See [reference.md](reference.md) for:
|
|
|
1585
1305
|
`;
|
|
1586
1306
|
var LIBRARY_SKILL = `---
|
|
1587
1307
|
name: library
|
|
1588
|
-
description: Check the Cure Kode global library before writing common patterns from scratch. Contains reusable GSAP animations, form handlers, scroll effects, and utilities.
|
|
1308
|
+
description: Check the Cure Kode global library before writing common patterns from scratch. Contains reusable GSAP animations, form handlers, scroll effects, and utilities organized in folders.
|
|
1589
1309
|
user-invocable: false
|
|
1590
1310
|
---
|
|
1591
1311
|
|
|
@@ -1598,7 +1318,8 @@ Before writing common JavaScript/CSS patterns, check if the global library alrea
|
|
|
1598
1318
|
### 1. Browse available snippets
|
|
1599
1319
|
|
|
1600
1320
|
\`\`\`
|
|
1601
|
-
kode library list
|
|
1321
|
+
kode library list # Flat list grouped by category
|
|
1322
|
+
kode library --tree # Folder tree view
|
|
1602
1323
|
\`\`\`
|
|
1603
1324
|
|
|
1604
1325
|
Or search for a specific pattern:
|
|
@@ -1607,7 +1328,7 @@ Or search for a specific pattern:
|
|
|
1607
1328
|
kode library search "scroll"
|
|
1608
1329
|
\`\`\`
|
|
1609
1330
|
|
|
1610
|
-
Via MCP: use \`kode_library_list\` or \`kode_library_get\`.
|
|
1331
|
+
Via MCP: use \`kode_library_list\` (with category/folder filter) or \`kode_library_get\`.
|
|
1611
1332
|
|
|
1612
1333
|
### 2. Copy and adapt
|
|
1613
1334
|
|
|
@@ -1628,6 +1349,21 @@ kode push
|
|
|
1628
1349
|
If you improve a library snippet or create a reusable pattern:
|
|
1629
1350
|
\`\`\`
|
|
1630
1351
|
kode library push my-improved-script.js
|
|
1352
|
+
kode library push my-script.js --folder animations # Push to folder
|
|
1353
|
+
\`\`\`
|
|
1354
|
+
|
|
1355
|
+
Update an existing library snippet from a local file:
|
|
1356
|
+
\`\`\`
|
|
1357
|
+
kode library update gsap-scroll-reveal
|
|
1358
|
+
\`\`\`
|
|
1359
|
+
|
|
1360
|
+
### 4. Manage folders and trash
|
|
1361
|
+
|
|
1362
|
+
\`\`\`
|
|
1363
|
+
kode library folders # List all folders
|
|
1364
|
+
kode library folders --create "Forms" # Create folder
|
|
1365
|
+
kode library trash # List trashed snippets
|
|
1366
|
+
kode library trash --restore <id> # Restore from trash
|
|
1631
1367
|
\`\`\`
|
|
1632
1368
|
|
|
1633
1369
|
## Categories
|
|
@@ -1642,8 +1378,9 @@ kode library push my-improved-script.js
|
|
|
1642
1378
|
|
|
1643
1379
|
## Local reference files
|
|
1644
1380
|
|
|
1645
|
-
Library snippets are downloaded to \`.cure-kode-scripts/library/\` during \`kode
|
|
1381
|
+
Library snippets are downloaded to \`.cure-kode-scripts/library/\` during \`kode library pull\`.
|
|
1646
1382
|
These are **reference only** \u2014 they are never deployed. Only files in the root of \`.cure-kode-scripts/\` are pushed.
|
|
1383
|
+
Files are organized by folder name (or category as fallback).
|
|
1647
1384
|
`;
|
|
1648
1385
|
var WEBFLOW_REFERENCE = `# Webflow Patterns \u2014 Detailed Reference
|
|
1649
1386
|
|
|
@@ -1977,26 +1714,154 @@ async function initCommand(options) {
|
|
|
1977
1714
|
apiKey,
|
|
1978
1715
|
scriptsDir: DEFAULT_SCRIPTS_DIR,
|
|
1979
1716
|
environment: "staging",
|
|
1717
|
+
...options.dev && { apiUrl: "http://localhost:3000" },
|
|
1980
1718
|
...projectCtx?.project && { projectId: projectCtx.project.id }
|
|
1981
1719
|
};
|
|
1982
1720
|
spinner.start("Oppretter prosjektstruktur...");
|
|
1983
1721
|
saveProjectConfig(config, cwd);
|
|
1984
|
-
const scriptsPath =
|
|
1985
|
-
if (!
|
|
1986
|
-
|
|
1722
|
+
const scriptsPath = join3(cwd, DEFAULT_SCRIPTS_DIR);
|
|
1723
|
+
if (!existsSync3(scriptsPath)) {
|
|
1724
|
+
mkdirSync2(scriptsPath, { recursive: true });
|
|
1987
1725
|
}
|
|
1988
|
-
const gitignorePath =
|
|
1726
|
+
const gitignorePath = join3(cwd, ".cure-kode", ".gitignore");
|
|
1989
1727
|
const gitignoreContent = `# Hold config.json privat - inneholder API-n\xF8kkel
|
|
1990
1728
|
config.json
|
|
1991
1729
|
`;
|
|
1992
|
-
|
|
1730
|
+
writeFileSync3(gitignorePath, gitignoreContent);
|
|
1731
|
+
const tsconfigPath = join3(cwd, "tsconfig.json");
|
|
1732
|
+
if (!existsSync3(tsconfigPath)) {
|
|
1733
|
+
writeFileSync3(tsconfigPath, JSON.stringify({
|
|
1734
|
+
compilerOptions: {
|
|
1735
|
+
target: "ES2020",
|
|
1736
|
+
module: "ESNext",
|
|
1737
|
+
moduleResolution: "bundler",
|
|
1738
|
+
jsx: "react-jsx",
|
|
1739
|
+
strict: true,
|
|
1740
|
+
noEmit: true,
|
|
1741
|
+
skipLibCheck: true,
|
|
1742
|
+
baseUrl: ".",
|
|
1743
|
+
paths: {
|
|
1744
|
+
"@kode/*": [".cure-kode-scripts/packages/@kode/*"]
|
|
1745
|
+
}
|
|
1746
|
+
},
|
|
1747
|
+
include: [".cure-kode-scripts/**/*"]
|
|
1748
|
+
}, null, 2) + "\n");
|
|
1749
|
+
}
|
|
1750
|
+
const packageJsonPath = join3(cwd, "package.json");
|
|
1751
|
+
if (!existsSync3(packageJsonPath)) {
|
|
1752
|
+
writeFileSync3(packageJsonPath, JSON.stringify({
|
|
1753
|
+
private: true,
|
|
1754
|
+
name: `kode-${site.slug}`,
|
|
1755
|
+
description: `Cure Kode scripts for ${site.name}`,
|
|
1756
|
+
devDependencies: {
|
|
1757
|
+
"@types/react": "^18.0.0",
|
|
1758
|
+
"@types/react-dom": "^18.0.0",
|
|
1759
|
+
"typescript": "^5.0.0"
|
|
1760
|
+
}
|
|
1761
|
+
}, null, 2) + "\n");
|
|
1762
|
+
}
|
|
1993
1763
|
spinner.succeed("Prosjektstruktur opprettet");
|
|
1764
|
+
spinner.start("Installerer TypeScript-typer...");
|
|
1765
|
+
try {
|
|
1766
|
+
const { execSync } = await import("child_process");
|
|
1767
|
+
execSync("npm install", { cwd, stdio: "ignore", timeout: 3e4 });
|
|
1768
|
+
spinner.succeed("TypeScript-typer installert");
|
|
1769
|
+
} catch {
|
|
1770
|
+
spinner.warn('Kunne ikke installere TypeScript-typer automatisk. Kj\xF8r "npm install" manuelt.');
|
|
1771
|
+
}
|
|
1772
|
+
const webflowPkgDir = join3(scriptsPath, "packages", "@kode", "webflow");
|
|
1773
|
+
if (!existsSync3(webflowPkgDir)) {
|
|
1774
|
+
mkdirSync2(webflowPkgDir, { recursive: true });
|
|
1775
|
+
const siteSlug = config.siteSlug || "";
|
|
1776
|
+
writeFileSync3(join3(webflowPkgDir, "index.ts"), `/**
|
|
1777
|
+
* @kode/webflow \u2014 Runtime CMS helper for Webflow sites
|
|
1778
|
+
* Auto-generated by Cure Kode. Do not edit.
|
|
1779
|
+
*/
|
|
1780
|
+
|
|
1781
|
+
import type { CmsCollections } from '../../../types/cms'
|
|
1782
|
+
|
|
1783
|
+
type CollectionSlug = keyof CmsCollections
|
|
1784
|
+
|
|
1785
|
+
interface CmsQueryOptions {
|
|
1786
|
+
limit?: number
|
|
1787
|
+
offset?: number
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
const SITE_SLUG = '${siteSlug}'
|
|
1791
|
+
|
|
1792
|
+
function getApiBase(): string {
|
|
1793
|
+
if (typeof window === 'undefined') return 'https://app.cure.no'
|
|
1794
|
+
// Detect from the init.js script tag URL
|
|
1795
|
+
const scripts = document.querySelectorAll('script[src*="cure-kode"], script[src*="/api/cdn/"]')
|
|
1796
|
+
for (const s of scripts) {
|
|
1797
|
+
const src = (s as HTMLScriptElement).src
|
|
1798
|
+
if (src.includes('/api/cdn/')) {
|
|
1799
|
+
const url = new URL(src)
|
|
1800
|
+
return url.origin
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
return 'https://app.cure.no'
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
const API_BASE = getApiBase()
|
|
1807
|
+
|
|
1808
|
+
/**
|
|
1809
|
+
* Fetch all items from a CMS collection with full type safety
|
|
1810
|
+
*
|
|
1811
|
+
* @example
|
|
1812
|
+
* import { getCmsItems } from '@kode/webflow'
|
|
1813
|
+
* const ideas = await getCmsItems('ide')
|
|
1814
|
+
* ideas.forEach(idea => console.log(idea.name, idea.description))
|
|
1815
|
+
*/
|
|
1816
|
+
export async function getCmsItems<K extends CollectionSlug>(
|
|
1817
|
+
collection: K,
|
|
1818
|
+
options?: CmsQueryOptions
|
|
1819
|
+
): Promise<CmsCollections[K][]> {
|
|
1820
|
+
const params = new URLSearchParams({ collection })
|
|
1821
|
+
if (options?.limit) params.set('limit', String(options.limit))
|
|
1822
|
+
if (options?.offset) params.set('offset', String(options.offset))
|
|
1823
|
+
|
|
1824
|
+
const res = await fetch(\`\${API_BASE}/api/cdn/\${SITE_SLUG}/cms?\${params}\`)
|
|
1825
|
+
|
|
1826
|
+
if (!res.ok) throw new Error(\`[Kode] CMS fetch failed: \${res.status}\`)
|
|
1827
|
+
const data = await res.json()
|
|
1828
|
+
return data.items as CmsCollections[K][]
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
/**
|
|
1832
|
+
* Fetch a single CMS item by ID or slug
|
|
1833
|
+
*
|
|
1834
|
+
* @example
|
|
1835
|
+
* import { getCmsItem } from '@kode/webflow'
|
|
1836
|
+
* const idea = await getCmsItem('ide', 'my-great-idea')
|
|
1837
|
+
*/
|
|
1838
|
+
export async function getCmsItem<K extends CollectionSlug>(
|
|
1839
|
+
collection: K,
|
|
1840
|
+
itemId: string
|
|
1841
|
+
): Promise<CmsCollections[K] | null> {
|
|
1842
|
+
const params = new URLSearchParams({ collection, itemId })
|
|
1843
|
+
|
|
1844
|
+
const res = await fetch(\`\${API_BASE}/api/cdn/\${SITE_SLUG}/cms?\${params}\`)
|
|
1845
|
+
|
|
1846
|
+
if (res.status === 404) return null
|
|
1847
|
+
if (!res.ok) throw new Error(\`[Kode] CMS fetch failed: \${res.status}\`)
|
|
1848
|
+
return res.json() as Promise<CmsCollections[K]>
|
|
1849
|
+
}
|
|
1850
|
+
`, "utf-8");
|
|
1851
|
+
writeFileSync3(join3(webflowPkgDir, "kode.json"), JSON.stringify({
|
|
1852
|
+
name: "webflow",
|
|
1853
|
+
version: "1.0.0",
|
|
1854
|
+
description: "Runtime CMS helper for Webflow sites",
|
|
1855
|
+
entryPoint: "index.ts",
|
|
1856
|
+
builtin: true
|
|
1857
|
+
}, null, 2), "utf-8");
|
|
1858
|
+
}
|
|
1994
1859
|
spinner.start("Konfigurerer MCP-servere...");
|
|
1995
|
-
const mcpConfigPath =
|
|
1860
|
+
const mcpConfigPath = join3(cwd, ".mcp.json");
|
|
1996
1861
|
let mcpConfig = {};
|
|
1997
|
-
if (
|
|
1862
|
+
if (existsSync3(mcpConfigPath)) {
|
|
1998
1863
|
try {
|
|
1999
|
-
mcpConfig = JSON.parse(
|
|
1864
|
+
mcpConfig = JSON.parse(readFileSync3(mcpConfigPath, "utf-8"));
|
|
2000
1865
|
} catch {
|
|
2001
1866
|
}
|
|
2002
1867
|
}
|
|
@@ -2045,7 +1910,7 @@ config.json
|
|
|
2045
1910
|
mcpConfig.mcpServers[server.name] = serverConfig;
|
|
2046
1911
|
}
|
|
2047
1912
|
}
|
|
2048
|
-
|
|
1913
|
+
writeFileSync3(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
2049
1914
|
spinner.succeed("MCP-servere konfigurert");
|
|
2050
1915
|
if (secretWarnings.length > 0) {
|
|
2051
1916
|
console.log();
|
|
@@ -2097,7 +1962,7 @@ config.json
|
|
|
2097
1962
|
production_domain: site.production_domain
|
|
2098
1963
|
};
|
|
2099
1964
|
const contextMdContent = generateInitialContext(config, scripts, siteInfo);
|
|
2100
|
-
|
|
1965
|
+
writeFileSync3(join3(cwd, ".cure-kode", "context.md"), contextMdContent);
|
|
2101
1966
|
spinner.succeed("AI-dokumentasjon generert");
|
|
2102
1967
|
spinner.start("Laster ned bibliotek-referanser...");
|
|
2103
1968
|
try {
|
|
@@ -2144,6 +2009,23 @@ config.json
|
|
|
2144
2009
|
} else {
|
|
2145
2010
|
spinner.succeed("Claude Code skills finnes allerede");
|
|
2146
2011
|
}
|
|
2012
|
+
try {
|
|
2013
|
+
const { pullCommand } = await import("./pull-G7GR67GG.js");
|
|
2014
|
+
console.log();
|
|
2015
|
+
await pullCommand({});
|
|
2016
|
+
} catch {
|
|
2017
|
+
}
|
|
2018
|
+
try {
|
|
2019
|
+
const { generateCmsTypes } = await import("./types-PZ7GFJEK.js");
|
|
2020
|
+
await generateCmsTypes({ silent: false });
|
|
2021
|
+
} catch {
|
|
2022
|
+
}
|
|
2023
|
+
try {
|
|
2024
|
+
const { restorePackages } = await import("./pkg-DOXTOICF.js");
|
|
2025
|
+
await restorePackages();
|
|
2026
|
+
} catch (err) {
|
|
2027
|
+
console.log(chalk.dim(` Pakke-gjenoppretting hoppet over: ${err?.message || "ukjent feil"}`));
|
|
2028
|
+
}
|
|
2147
2029
|
console.log();
|
|
2148
2030
|
console.log(chalk.green(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
2149
2031
|
console.log(chalk.green(" \u2551") + chalk.bold.green(" \u2705 Cure Kode er klar! ") + chalk.green("\u2551"));
|
|
@@ -2159,6 +2041,8 @@ config.json
|
|
|
2159
2041
|
console.log(chalk.dim(" \u251C\u2500\u2500 CLAUDE.md ") + chalk.dim("(uendret - har allerede referanse)"));
|
|
2160
2042
|
}
|
|
2161
2043
|
console.log(chalk.dim(" \u251C\u2500\u2500 .mcp.json ") + chalk.green("(MCP-servere)"));
|
|
2044
|
+
console.log(chalk.dim(" \u251C\u2500\u2500 package.json ") + chalk.green("(TypeScript/React-typer)"));
|
|
2045
|
+
console.log(chalk.dim(" \u251C\u2500\u2500 tsconfig.json ") + chalk.green("(TypeScript-konfigurasjon)"));
|
|
2162
2046
|
console.log(chalk.dim(" \u251C\u2500\u2500 .claude/skills/ ") + chalk.green("(Claude Code skills)"));
|
|
2163
2047
|
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 deploy/ (/deploy \u2014 push og deploy)"));
|
|
2164
2048
|
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 webflow-patterns/ (Webflow DOM-referanse)"));
|
|
@@ -2192,9 +2076,8 @@ config.json
|
|
|
2192
2076
|
}
|
|
2193
2077
|
console.log(chalk.bold(" Neste steg:"));
|
|
2194
2078
|
console.log();
|
|
2195
|
-
console.log(chalk.cyan(" 1.
|
|
2196
|
-
console.log(chalk.cyan(" 2.") + chalk.dim("
|
|
2197
|
-
console.log(chalk.cyan(" 3.") + chalk.dim(" Godkjenn MCP-servere n\xE5r agenten starter"));
|
|
2079
|
+
console.log(chalk.cyan(" 1.") + chalk.dim(" Start din AI-agent (Claude Code, Cursor, etc.)"));
|
|
2080
|
+
console.log(chalk.cyan(" 2.") + chalk.dim(" Godkjenn MCP-servere n\xE5r agenten starter"));
|
|
2198
2081
|
console.log();
|
|
2199
2082
|
console.log(chalk.bold(" MCP-verkt\xF8y tilgjengelig:"));
|
|
2200
2083
|
console.log();
|
|
@@ -2216,489 +2099,70 @@ config.json
|
|
|
2216
2099
|
}
|
|
2217
2100
|
}
|
|
2218
2101
|
|
|
2219
|
-
// src/
|
|
2220
|
-
function isRetryableError(error) {
|
|
2221
|
-
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
2222
|
-
return true;
|
|
2223
|
-
}
|
|
2224
|
-
const err = error;
|
|
2225
|
-
const statusCode = err.statusCode || err.status;
|
|
2226
|
-
if (statusCode && statusCode >= 400 && statusCode < 500) {
|
|
2227
|
-
return false;
|
|
2228
|
-
}
|
|
2229
|
-
if (statusCode && statusCode >= 500) {
|
|
2230
|
-
return true;
|
|
2231
|
-
}
|
|
2232
|
-
if (err.code === "ECONNRESET" || err.code === "ETIMEDOUT" || err.code === "ENOTFOUND") {
|
|
2233
|
-
return true;
|
|
2234
|
-
}
|
|
2235
|
-
if (error instanceof Error) {
|
|
2236
|
-
const message = error.message.toLowerCase();
|
|
2237
|
-
if (message.includes("network") || message.includes("timeout") || message.includes("connection") || message.includes("socket")) {
|
|
2238
|
-
return true;
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
return false;
|
|
2242
|
-
}
|
|
2243
|
-
function calculateDelay(attempt, baseDelayMs, maxDelayMs, backoffMultiplier, jitter) {
|
|
2244
|
-
const exponentialDelay = baseDelayMs * Math.pow(backoffMultiplier, attempt - 1);
|
|
2245
|
-
const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
|
|
2246
|
-
if (!jitter) {
|
|
2247
|
-
return cappedDelay;
|
|
2248
|
-
}
|
|
2249
|
-
const jitterRange = cappedDelay * 0.25;
|
|
2250
|
-
const jitterOffset = (Math.random() - 0.5) * 2 * jitterRange;
|
|
2251
|
-
return Math.max(0, Math.round(cappedDelay + jitterOffset));
|
|
2252
|
-
}
|
|
2253
|
-
function sleep(ms) {
|
|
2254
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2255
|
-
}
|
|
2256
|
-
async function withRetry(fn, options = {}) {
|
|
2257
|
-
const {
|
|
2258
|
-
maxAttempts = 3,
|
|
2259
|
-
baseDelayMs = 500,
|
|
2260
|
-
maxDelayMs = 5e3,
|
|
2261
|
-
backoffMultiplier = 2,
|
|
2262
|
-
jitter = true,
|
|
2263
|
-
isRetryable = isRetryableError,
|
|
2264
|
-
onRetry
|
|
2265
|
-
} = options;
|
|
2266
|
-
let lastError;
|
|
2267
|
-
let totalDelayMs = 0;
|
|
2268
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2269
|
-
try {
|
|
2270
|
-
return await fn();
|
|
2271
|
-
} catch (error) {
|
|
2272
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2273
|
-
if (attempt >= maxAttempts || !isRetryable(error)) {
|
|
2274
|
-
throw lastError;
|
|
2275
|
-
}
|
|
2276
|
-
const delayMs = calculateDelay(
|
|
2277
|
-
attempt,
|
|
2278
|
-
baseDelayMs,
|
|
2279
|
-
maxDelayMs,
|
|
2280
|
-
backoffMultiplier,
|
|
2281
|
-
jitter
|
|
2282
|
-
);
|
|
2283
|
-
totalDelayMs += delayMs;
|
|
2284
|
-
if (onRetry) {
|
|
2285
|
-
onRetry(attempt, error, delayMs);
|
|
2286
|
-
}
|
|
2287
|
-
await sleep(delayMs);
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
throw lastError || new Error("Retry failed");
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2293
|
-
// src/api.ts
|
|
2294
|
-
var KodeApiError = class extends Error {
|
|
2295
|
-
constructor(message, statusCode, response) {
|
|
2296
|
-
super(message);
|
|
2297
|
-
this.statusCode = statusCode;
|
|
2298
|
-
this.response = response;
|
|
2299
|
-
this.name = "KodeApiError";
|
|
2300
|
-
}
|
|
2301
|
-
};
|
|
2302
|
-
var KodeApiClient = class {
|
|
2303
|
-
baseUrl;
|
|
2304
|
-
apiKey;
|
|
2305
|
-
constructor(config) {
|
|
2306
|
-
this.baseUrl = getApiUrl(config);
|
|
2307
|
-
const apiKey = getApiKey(config);
|
|
2308
|
-
if (!apiKey) {
|
|
2309
|
-
throw new Error('API key is required. Run "kode init" first.');
|
|
2310
|
-
}
|
|
2311
|
-
this.apiKey = apiKey;
|
|
2312
|
-
}
|
|
2313
|
-
async request(endpoint, options = {}) {
|
|
2314
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
2315
|
-
return withRetry(
|
|
2316
|
-
async () => {
|
|
2317
|
-
const response = await fetch(url, {
|
|
2318
|
-
...options,
|
|
2319
|
-
headers: {
|
|
2320
|
-
"Content-Type": "application/json",
|
|
2321
|
-
"X-API-Key": this.apiKey,
|
|
2322
|
-
...options.headers
|
|
2323
|
-
}
|
|
2324
|
-
});
|
|
2325
|
-
const data = await response.json();
|
|
2326
|
-
if (!response.ok) {
|
|
2327
|
-
throw new KodeApiError(
|
|
2328
|
-
data.error || `Request failed with status ${response.status}`,
|
|
2329
|
-
response.status,
|
|
2330
|
-
data
|
|
2331
|
-
);
|
|
2332
|
-
}
|
|
2333
|
-
return data;
|
|
2334
|
-
},
|
|
2335
|
-
{
|
|
2336
|
-
maxAttempts: 3,
|
|
2337
|
-
baseDelayMs: 500,
|
|
2338
|
-
// Custom retry check: retry on network errors and 5xx, but not 4xx
|
|
2339
|
-
isRetryable: (error) => {
|
|
2340
|
-
if (error instanceof KodeApiError) {
|
|
2341
|
-
return error.statusCode >= 500;
|
|
2342
|
-
}
|
|
2343
|
-
return isRetryableError(error);
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
);
|
|
2347
|
-
}
|
|
2348
|
-
// Sites
|
|
2349
|
-
async getSite(siteId) {
|
|
2350
|
-
return this.request(`/api/cdn/sites/${siteId}`);
|
|
2351
|
-
}
|
|
2352
|
-
async listSites() {
|
|
2353
|
-
return this.request("/api/cdn/sites");
|
|
2354
|
-
}
|
|
2355
|
-
// Scripts
|
|
2356
|
-
async listScripts(siteId) {
|
|
2357
|
-
return this.request(`/api/cdn/sites/${siteId}/scripts`);
|
|
2358
|
-
}
|
|
2359
|
-
async getScript(scriptId) {
|
|
2360
|
-
return this.request(`/api/cdn/scripts/${scriptId}`);
|
|
2361
|
-
}
|
|
2362
|
-
async createScript(siteId, data) {
|
|
2363
|
-
return this.request(`/api/cdn/sites/${siteId}/scripts`, {
|
|
2364
|
-
method: "POST",
|
|
2365
|
-
body: JSON.stringify(data)
|
|
2366
|
-
});
|
|
2367
|
-
}
|
|
2368
|
-
async updateScript(scriptId, data) {
|
|
2369
|
-
return this.request(`/api/cdn/scripts/${scriptId}`, {
|
|
2370
|
-
method: "PUT",
|
|
2371
|
-
body: JSON.stringify(data)
|
|
2372
|
-
});
|
|
2373
|
-
}
|
|
2374
|
-
async deleteScript(scriptId) {
|
|
2375
|
-
await this.request(`/api/cdn/scripts/${scriptId}`, {
|
|
2376
|
-
method: "DELETE"
|
|
2377
|
-
});
|
|
2378
|
-
}
|
|
2379
|
-
// Upload (alternative endpoint that works with API keys)
|
|
2380
|
-
async uploadScript(data) {
|
|
2381
|
-
return this.request("/api/cdn/upload", {
|
|
2382
|
-
method: "POST",
|
|
2383
|
-
body: JSON.stringify({
|
|
2384
|
-
...data,
|
|
2385
|
-
apiKey: this.apiKey
|
|
2386
|
-
})
|
|
2387
|
-
});
|
|
2388
|
-
}
|
|
2389
|
-
// Pages
|
|
2390
|
-
async listPages(siteId) {
|
|
2391
|
-
return this.request(`/api/cdn/sites/${siteId}/pages`);
|
|
2392
|
-
}
|
|
2393
|
-
async getPage(pageId) {
|
|
2394
|
-
return this.request(`/api/cdn/pages/${pageId}`);
|
|
2395
|
-
}
|
|
2396
|
-
async createPage(siteId, data) {
|
|
2397
|
-
return this.request(`/api/cdn/sites/${siteId}/pages`, {
|
|
2398
|
-
method: "POST",
|
|
2399
|
-
body: JSON.stringify(data)
|
|
2400
|
-
});
|
|
2401
|
-
}
|
|
2402
|
-
async updatePage(pageId, data) {
|
|
2403
|
-
return this.request(`/api/cdn/pages/${pageId}`, {
|
|
2404
|
-
method: "PATCH",
|
|
2405
|
-
body: JSON.stringify(data)
|
|
2406
|
-
});
|
|
2407
|
-
}
|
|
2408
|
-
async deletePage(pageId) {
|
|
2409
|
-
return this.request(`/api/cdn/pages/${pageId}`, {
|
|
2410
|
-
method: "DELETE"
|
|
2411
|
-
});
|
|
2412
|
-
}
|
|
2413
|
-
async assignScriptToPage(pageId, scriptId, loadOrderOverride) {
|
|
2414
|
-
return this.request(`/api/cdn/pages/${pageId}/scripts`, {
|
|
2415
|
-
method: "POST",
|
|
2416
|
-
body: JSON.stringify({ scriptId, loadOrderOverride })
|
|
2417
|
-
});
|
|
2418
|
-
}
|
|
2419
|
-
async removeScriptFromPage(pageId, scriptId) {
|
|
2420
|
-
return this.request(`/api/cdn/pages/${pageId}/scripts/${scriptId}`, {
|
|
2421
|
-
method: "DELETE"
|
|
2422
|
-
});
|
|
2423
|
-
}
|
|
2424
|
-
async getPageScripts(pageId) {
|
|
2425
|
-
return this.request(`/api/cdn/pages/${pageId}/scripts`);
|
|
2426
|
-
}
|
|
2427
|
-
// Deployments
|
|
2428
|
-
async deploy(siteId, environment = "staging") {
|
|
2429
|
-
return this.request("/api/cdn/deploy", {
|
|
2430
|
-
method: "POST",
|
|
2431
|
-
body: JSON.stringify({
|
|
2432
|
-
siteId,
|
|
2433
|
-
environment,
|
|
2434
|
-
apiKey: this.apiKey
|
|
2435
|
-
})
|
|
2436
|
-
});
|
|
2437
|
-
}
|
|
2438
|
-
async promoteToProduction(siteId, stagingDeploymentId) {
|
|
2439
|
-
return this.request("/api/cdn/deploy/promote", {
|
|
2440
|
-
method: "POST",
|
|
2441
|
-
body: JSON.stringify({
|
|
2442
|
-
siteId,
|
|
2443
|
-
stagingDeploymentId,
|
|
2444
|
-
apiKey: this.apiKey
|
|
2445
|
-
})
|
|
2446
|
-
});
|
|
2447
|
-
}
|
|
2448
|
-
async getDeploymentStatus(siteId) {
|
|
2449
|
-
return this.request(`/api/cdn/sites/${siteId}/deployments/status`);
|
|
2450
|
-
}
|
|
2451
|
-
async rollback(siteId, environment = "staging") {
|
|
2452
|
-
return this.request("/api/cdn/deploy/rollback", {
|
|
2453
|
-
method: "POST",
|
|
2454
|
-
body: JSON.stringify({
|
|
2455
|
-
siteId,
|
|
2456
|
-
environment
|
|
2457
|
-
})
|
|
2458
|
-
});
|
|
2459
|
-
}
|
|
2460
|
-
// Production enabled toggle (v2.3)
|
|
2461
|
-
async setProductionEnabled(siteId, enabled, productionDomain) {
|
|
2462
|
-
return this.request(`/api/cdn/sites/${siteId}/production`, {
|
|
2463
|
-
method: "POST",
|
|
2464
|
-
body: JSON.stringify({
|
|
2465
|
-
enabled,
|
|
2466
|
-
productionDomain
|
|
2467
|
-
})
|
|
2468
|
-
});
|
|
2469
|
-
}
|
|
2470
|
-
// HTML Fetch
|
|
2471
|
-
async fetchHtml(url) {
|
|
2472
|
-
return this.request("/api/cdn/fetch-html", {
|
|
2473
|
-
method: "POST",
|
|
2474
|
-
body: JSON.stringify({ url })
|
|
2475
|
-
});
|
|
2476
|
-
}
|
|
2477
|
-
// Lock management
|
|
2478
|
-
async getLockStatus(siteId) {
|
|
2479
|
-
return this.request(`/api/cdn/deploy/lock?siteId=${siteId}`);
|
|
2480
|
-
}
|
|
2481
|
-
async forceReleaseLock(siteId) {
|
|
2482
|
-
return this.request("/api/cdn/deploy/lock", {
|
|
2483
|
-
method: "DELETE",
|
|
2484
|
-
body: JSON.stringify({ siteId })
|
|
2485
|
-
});
|
|
2486
|
-
}
|
|
2487
|
-
// Library (global snippets)
|
|
2488
|
-
async listLibrary(search) {
|
|
2489
|
-
const params = new URLSearchParams();
|
|
2490
|
-
if (search) params.set("search", search);
|
|
2491
|
-
const qs = params.toString();
|
|
2492
|
-
return this.request(`/api/cdn/library${qs ? `?${qs}` : ""}`);
|
|
2493
|
-
}
|
|
2494
|
-
async getLibrarySnippet(slugOrId) {
|
|
2495
|
-
return this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`);
|
|
2496
|
-
}
|
|
2497
|
-
async createLibrarySnippet(data) {
|
|
2498
|
-
return this.request("/api/cdn/library", {
|
|
2499
|
-
method: "POST",
|
|
2500
|
-
body: JSON.stringify(data)
|
|
2501
|
-
});
|
|
2502
|
-
}
|
|
2503
|
-
async recordLibraryUsage(snippetId) {
|
|
2504
|
-
await this.request(`/api/cdn/library/${snippetId}`);
|
|
2505
|
-
}
|
|
2506
|
-
// Webflow Custom Code injection
|
|
2507
|
-
async getWebflowCustomCodeStatus(siteId) {
|
|
2508
|
-
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`);
|
|
2509
|
-
}
|
|
2510
|
-
async injectWebflowCustomCode(siteId, location = "header") {
|
|
2511
|
-
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`, {
|
|
2512
|
-
method: "POST",
|
|
2513
|
-
body: JSON.stringify({ location })
|
|
2514
|
-
});
|
|
2515
|
-
}
|
|
2516
|
-
async removeWebflowCustomCode(siteId) {
|
|
2517
|
-
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`, {
|
|
2518
|
-
method: "DELETE"
|
|
2519
|
-
});
|
|
2520
|
-
}
|
|
2521
|
-
};
|
|
2522
|
-
function createApiClient(config) {
|
|
2523
|
-
return new KodeApiClient(config);
|
|
2524
|
-
}
|
|
2525
|
-
|
|
2526
|
-
// src/commands/pull.ts
|
|
2102
|
+
// src/commands/push.ts
|
|
2527
2103
|
import chalk2 from "chalk";
|
|
2528
2104
|
import ora2 from "ora";
|
|
2529
|
-
import {
|
|
2530
|
-
import { join as
|
|
2105
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4, readdirSync, statSync } from "fs";
|
|
2106
|
+
import { join as join4, basename, extname } from "path";
|
|
2531
2107
|
import { createHash } from "crypto";
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
async function pullCommand(options) {
|
|
2536
|
-
const projectRoot = findProjectRoot();
|
|
2537
|
-
if (!projectRoot) {
|
|
2538
|
-
console.log(chalk2.red("Feil: Ikke i et Cure Kode-prosjekt."));
|
|
2539
|
-
console.log(chalk2.dim('Kj\xF8r "kode init" f\xF8rst.'));
|
|
2540
|
-
return;
|
|
2541
|
-
}
|
|
2542
|
-
const config = getProjectConfig(projectRoot);
|
|
2543
|
-
if (!config) {
|
|
2544
|
-
console.log(chalk2.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
|
|
2545
|
-
return;
|
|
2546
|
-
}
|
|
2547
|
-
const spinner = ora2("Henter skript...").start();
|
|
2108
|
+
import * as esbuild from "esbuild";
|
|
2109
|
+
var BUNDLEABLE_EXTENSIONS = [".ts", ".tsx", ".jsx"];
|
|
2110
|
+
async function bundleFile(filePath, scriptsDir) {
|
|
2548
2111
|
try {
|
|
2549
|
-
const
|
|
2550
|
-
const
|
|
2551
|
-
if (
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
}
|
|
2561
|
-
const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
|
|
2562
|
-
let existingMetadata = [];
|
|
2563
|
-
if (existsSync6(metadataPath)) {
|
|
2564
|
-
try {
|
|
2565
|
-
existingMetadata = JSON.parse(readFileSync6(metadataPath, "utf-8"));
|
|
2566
|
-
} catch {
|
|
2567
|
-
}
|
|
2568
|
-
}
|
|
2569
|
-
const scriptsToPull = options.script ? scripts.filter((s) => s.slug === options.script || s.name === options.script) : scripts;
|
|
2570
|
-
if (options.script && scriptsToPull.length === 0) {
|
|
2571
|
-
console.log(chalk2.yellow(`
|
|
2572
|
-
Fant ikke skript "${options.script}".`));
|
|
2573
|
-
console.log(chalk2.dim("Tilgjengelige skript:"));
|
|
2574
|
-
scripts.forEach((s) => {
|
|
2575
|
-
console.log(chalk2.dim(` - ${s.slug} (${s.name})`));
|
|
2576
|
-
});
|
|
2577
|
-
return;
|
|
2578
|
-
}
|
|
2579
|
-
console.log();
|
|
2580
|
-
let pulled = 0;
|
|
2581
|
-
let skipped = 0;
|
|
2582
|
-
let updated = 0;
|
|
2583
|
-
for (const script of scriptsToPull) {
|
|
2584
|
-
const ext = script.type === "javascript" ? "js" : "css";
|
|
2585
|
-
const fileName = `${script.slug}.${ext}`;
|
|
2586
|
-
const filePath = join6(scriptsDir, fileName);
|
|
2587
|
-
const existingMeta = existingMetadata.find((m) => m.slug === script.slug);
|
|
2588
|
-
const lastPulledVersion = existingMeta?.lastPulledVersion || 0;
|
|
2589
|
-
if (existsSync6(filePath) && !options.force) {
|
|
2590
|
-
const localContent = readFileSync6(filePath, "utf-8");
|
|
2591
|
-
const localHash = hashContent(localContent);
|
|
2592
|
-
const hasLocalChanges = existingMeta && localHash !== existingMeta.contentHash;
|
|
2593
|
-
if (hasLocalChanges) {
|
|
2594
|
-
if (script.current_version > lastPulledVersion) {
|
|
2595
|
-
console.log(
|
|
2596
|
-
chalk2.yellow(` \u26A0 ${fileName}`) + chalk2.dim(` (lokal v${lastPulledVersion} \u2192 server v${script.current_version}, konflikt)`)
|
|
2597
|
-
);
|
|
2598
|
-
console.log(chalk2.dim(` Bruk --force for \xE5 overskrive lokale endringer`));
|
|
2599
|
-
} else {
|
|
2600
|
-
console.log(
|
|
2601
|
-
chalk2.yellow(` \u26A0 ${fileName}`) + chalk2.dim(" (lokale endringer, bruk --force)")
|
|
2602
|
-
);
|
|
2112
|
+
const aliases = {};
|
|
2113
|
+
const pkgDir = join4(scriptsDir, "packages", "@kode");
|
|
2114
|
+
if (existsSync4(pkgDir)) {
|
|
2115
|
+
for (const name of readdirSync(pkgDir)) {
|
|
2116
|
+
const pkgPath = join4(pkgDir, name);
|
|
2117
|
+
if (!statSync(pkgPath).isDirectory()) continue;
|
|
2118
|
+
for (const entry of ["index.ts", "index.tsx", "index.jsx", "index.js"]) {
|
|
2119
|
+
const entryPath = join4(pkgPath, entry);
|
|
2120
|
+
if (existsSync4(entryPath)) {
|
|
2121
|
+
aliases[`@kode/${name}`] = entryPath;
|
|
2122
|
+
break;
|
|
2603
2123
|
}
|
|
2604
|
-
skipped++;
|
|
2605
|
-
continue;
|
|
2606
2124
|
}
|
|
2607
|
-
if (script.content === localContent) {
|
|
2608
|
-
console.log(chalk2.dim(` \u25CB ${fileName} (ingen endringer)`));
|
|
2609
|
-
skipped++;
|
|
2610
|
-
continue;
|
|
2611
|
-
}
|
|
2612
|
-
}
|
|
2613
|
-
writeFileSync6(filePath, script.content);
|
|
2614
|
-
const scopeTag = script.scope === "global" ? chalk2.blue("[G]") : chalk2.magenta("[P]");
|
|
2615
|
-
const loadTag = script.auto_load ? chalk2.green("\u26A1") : chalk2.dim("\u25CB");
|
|
2616
|
-
if (lastPulledVersion > 0 && script.current_version > lastPulledVersion) {
|
|
2617
|
-
console.log(
|
|
2618
|
-
chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${lastPulledVersion} \u2192 v${script.current_version}`) + ` ${scopeTag} ${loadTag}`
|
|
2619
|
-
);
|
|
2620
|
-
updated++;
|
|
2621
|
-
} else {
|
|
2622
|
-
console.log(
|
|
2623
|
-
chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${script.current_version}`) + ` ${scopeTag} ${loadTag}`
|
|
2624
|
-
);
|
|
2625
|
-
pulled++;
|
|
2626
2125
|
}
|
|
2627
2126
|
}
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
const pageAssignments = (s.pages || []).filter((p) => p.is_enabled).map((p) => {
|
|
2648
|
-
const page = pages.find((pg) => pg.id === p.page_id);
|
|
2649
|
-
return page ? { pageId: page.id, pageSlug: page.slug, pageName: page.name } : null;
|
|
2650
|
-
}).filter((p) => p !== null);
|
|
2651
|
-
return {
|
|
2652
|
-
id: s.id,
|
|
2653
|
-
slug: s.slug,
|
|
2654
|
-
name: s.name,
|
|
2655
|
-
type: s.type,
|
|
2656
|
-
scope: s.scope,
|
|
2657
|
-
autoLoad: s.auto_load,
|
|
2658
|
-
version: s.current_version,
|
|
2659
|
-
loadOrder: s.load_order,
|
|
2660
|
-
pageAssignments: pageAssignments.length > 0 ? pageAssignments : void 0,
|
|
2661
|
-
lastPulledVersion: s.current_version,
|
|
2662
|
-
lastPulledAt: now,
|
|
2663
|
-
contentHash: hashContent(localContent)
|
|
2664
|
-
};
|
|
2127
|
+
const result = await esbuild.build({
|
|
2128
|
+
entryPoints: [filePath],
|
|
2129
|
+
bundle: true,
|
|
2130
|
+
format: "iife",
|
|
2131
|
+
platform: "browser",
|
|
2132
|
+
target: ["es2020"],
|
|
2133
|
+
minify: true,
|
|
2134
|
+
write: false,
|
|
2135
|
+
external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
|
|
2136
|
+
jsx: "automatic",
|
|
2137
|
+
jsxImportSource: "react",
|
|
2138
|
+
absWorkingDir: scriptsDir,
|
|
2139
|
+
resolveExtensions: [".tsx", ".ts", ".jsx", ".js", ".css"],
|
|
2140
|
+
alias: aliases,
|
|
2141
|
+
// Shim require() for external React in browser IIFE context
|
|
2142
|
+
banner: {
|
|
2143
|
+
js: `var require=function(m){var g={"react":window.React,"react-dom":window.ReactDOM,"react-dom/client":window.ReactDOM,"react/jsx-runtime":window.React,"react/jsx-dev-runtime":window.React};if(g[m])return g[m];throw new Error("Cannot find module '"+m+"'")};`
|
|
2144
|
+
},
|
|
2145
|
+
logLevel: "silent"
|
|
2665
2146
|
});
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
}
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
console.log(chalk2.green("Opprettet CLAUDE.md med referanse"));
|
|
2682
|
-
} else if (result.claudeMd.updated) {
|
|
2683
|
-
console.log(chalk2.dim("La til Kode-referanse i CLAUDE.md"));
|
|
2684
|
-
}
|
|
2685
|
-
} catch {
|
|
2686
|
-
}
|
|
2687
|
-
console.log(chalk2.dim("\n[G]=Global [P]=Sidespesifikk \u26A1=Auto-last \u25CB=Manuell"));
|
|
2688
|
-
} catch (error) {
|
|
2689
|
-
spinner.fail("Kunne ikke hente skript");
|
|
2690
|
-
console.error(chalk2.red("\nFeil:"), error);
|
|
2147
|
+
const code = result.outputFiles?.[0]?.text || "";
|
|
2148
|
+
const source = readFileSync4(filePath, "utf-8");
|
|
2149
|
+
const usesReact = /from\s+['"]react['"]/.test(source) || /from\s+['"]react-dom/.test(source) || /import\s+React/.test(source) || /require\s*\(\s*['"]react/.test(source) || /React\.createElement/.test(source);
|
|
2150
|
+
return { code, usesReact };
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
if (err && typeof err === "object" && "errors" in err) {
|
|
2153
|
+
const buildErr = err;
|
|
2154
|
+
const msg = buildErr.errors.map((e) => {
|
|
2155
|
+
let text = e.text;
|
|
2156
|
+
if (e.location) text = `${e.location.file}:${e.location.line}: ${text}`;
|
|
2157
|
+
return text;
|
|
2158
|
+
}).join("\n");
|
|
2159
|
+
return { code: "", usesReact: false, error: msg };
|
|
2160
|
+
}
|
|
2161
|
+
return { code: "", usesReact: false, error: err instanceof Error ? err.message : String(err) };
|
|
2691
2162
|
}
|
|
2692
2163
|
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
import chalk3 from "chalk";
|
|
2696
|
-
import ora3 from "ora";
|
|
2697
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync7, readdirSync } from "fs";
|
|
2698
|
-
import { join as join7, basename, extname } from "path";
|
|
2699
|
-
import { createHash as createHash2 } from "crypto";
|
|
2700
|
-
function hashContent2(content) {
|
|
2701
|
-
return createHash2("sha256").update(content).digest("hex").substring(0, 16);
|
|
2164
|
+
function hashContent(content) {
|
|
2165
|
+
return createHash("sha256").update(content).digest("hex").substring(0, 16);
|
|
2702
2166
|
}
|
|
2703
2167
|
function formatTimeDiff(date) {
|
|
2704
2168
|
const diff = Date.now() - new Date(date).getTime();
|
|
@@ -2713,50 +2177,67 @@ function formatTimeDiff(date) {
|
|
|
2713
2177
|
async function pushCommand(options) {
|
|
2714
2178
|
const projectRoot = findProjectRoot();
|
|
2715
2179
|
if (!projectRoot) {
|
|
2716
|
-
console.log(
|
|
2717
|
-
console.log(
|
|
2180
|
+
console.log(chalk2.red("Feil: Ikke i et Cure Kode-prosjekt."));
|
|
2181
|
+
console.log(chalk2.dim('Kj\xF8r "kode init" f\xF8rst.'));
|
|
2718
2182
|
return;
|
|
2719
2183
|
}
|
|
2720
2184
|
const config = getProjectConfig(projectRoot);
|
|
2721
2185
|
if (!config) {
|
|
2722
|
-
console.log(
|
|
2186
|
+
console.log(chalk2.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
|
|
2723
2187
|
return;
|
|
2724
2188
|
}
|
|
2725
2189
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2726
|
-
if (!
|
|
2727
|
-
console.log(
|
|
2728
|
-
console.log(
|
|
2729
|
-
console.log(
|
|
2190
|
+
if (!existsSync4(scriptsDir)) {
|
|
2191
|
+
console.log(chalk2.yellow("Skriptmappen finnes ikke."));
|
|
2192
|
+
console.log(chalk2.dim(`Forventet: ${scriptsDir}`));
|
|
2193
|
+
console.log(chalk2.dim('Kj\xF8r "kode pull" f\xF8rst eller opprett skript manuelt.'));
|
|
2730
2194
|
return;
|
|
2731
2195
|
}
|
|
2732
|
-
const metadataPath =
|
|
2196
|
+
const metadataPath = join4(projectRoot, ".cure-kode", "scripts.json");
|
|
2733
2197
|
let metadata = [];
|
|
2734
|
-
if (
|
|
2198
|
+
if (existsSync4(metadataPath)) {
|
|
2735
2199
|
try {
|
|
2736
|
-
metadata = JSON.parse(
|
|
2200
|
+
metadata = JSON.parse(readFileSync4(metadataPath, "utf-8"));
|
|
2737
2201
|
} catch {
|
|
2738
2202
|
}
|
|
2739
2203
|
}
|
|
2204
|
+
const SUPPORTED_EXTENSIONS = [".js", ".css", ".ts", ".tsx", ".jsx"];
|
|
2740
2205
|
const files = readdirSync(scriptsDir).filter(
|
|
2741
|
-
(f) =>
|
|
2206
|
+
(f) => SUPPORTED_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
2742
2207
|
);
|
|
2743
2208
|
if (files.length === 0) {
|
|
2744
|
-
console.log(
|
|
2745
|
-
console.log(
|
|
2209
|
+
console.log(chalk2.yellow("Ingen skriptfiler funnet."));
|
|
2210
|
+
console.log(chalk2.dim(`Legg til .js, .ts, .tsx, .jsx eller .css filer i ${scriptsDir}/`));
|
|
2746
2211
|
return;
|
|
2747
2212
|
}
|
|
2213
|
+
const slugMap = /* @__PURE__ */ new Map();
|
|
2214
|
+
for (const f of files) {
|
|
2215
|
+
const slug = basename(f, extname(f));
|
|
2216
|
+
const existing = slugMap.get(slug) || [];
|
|
2217
|
+
existing.push(f);
|
|
2218
|
+
slugMap.set(slug, existing);
|
|
2219
|
+
}
|
|
2220
|
+
const collisions = [...slugMap.entries()].filter(([, fs]) => fs.length > 1);
|
|
2221
|
+
if (collisions.length > 0) {
|
|
2222
|
+
console.log();
|
|
2223
|
+
for (const [slug, collidingFiles] of collisions) {
|
|
2224
|
+
console.log(chalk2.yellow(` \u26A0\uFE0F Slug-kollisjon: "${slug}" brukes av: ${collidingFiles.join(", ")}`));
|
|
2225
|
+
console.log(chalk2.dim(` CDN kan bare ha \xE9n script per slug. Gi nytt navn til en av filene.`));
|
|
2226
|
+
}
|
|
2227
|
+
console.log();
|
|
2228
|
+
}
|
|
2748
2229
|
const filesToPush = options.script ? files.filter(
|
|
2749
|
-
(f) => basename(f, extname(f)) === options.script || f === options.script
|
|
2230
|
+
(f) => basename(f, extname(f)) === options.script || basename(f, extname(f)) === basename(options.script, extname(options.script)) || f === options.script
|
|
2750
2231
|
) : files;
|
|
2751
2232
|
if (options.script && filesToPush.length === 0) {
|
|
2752
|
-
console.log(
|
|
2753
|
-
console.log(
|
|
2233
|
+
console.log(chalk2.yellow(`Fant ikke skript "${options.script}" lokalt.`));
|
|
2234
|
+
console.log(chalk2.dim("Tilgjengelige lokale skript:"));
|
|
2754
2235
|
files.forEach((f) => {
|
|
2755
|
-
console.log(
|
|
2236
|
+
console.log(chalk2.dim(` - ${f}`));
|
|
2756
2237
|
});
|
|
2757
2238
|
return;
|
|
2758
2239
|
}
|
|
2759
|
-
const spinner =
|
|
2240
|
+
const spinner = ora2("Laster opp skript...").start();
|
|
2760
2241
|
try {
|
|
2761
2242
|
const client = createApiClient(config);
|
|
2762
2243
|
const remoteScripts = await client.listScripts(config.siteId);
|
|
@@ -2768,46 +2249,74 @@ async function pushCommand(options) {
|
|
|
2768
2249
|
console.log();
|
|
2769
2250
|
let emptyScriptCount = 0;
|
|
2770
2251
|
for (const file of filesToPush) {
|
|
2771
|
-
const filePath =
|
|
2772
|
-
const
|
|
2252
|
+
const filePath = join4(scriptsDir, file);
|
|
2253
|
+
const sourceContent = readFileSync4(filePath, "utf-8");
|
|
2254
|
+
let content = sourceContent;
|
|
2773
2255
|
const slug = basename(file, extname(file));
|
|
2774
|
-
const type = extname(file) === ".
|
|
2775
|
-
|
|
2776
|
-
console.log(chalk3.yellow(` \u26A0 ${file}`) + chalk3.dim(" (tom fil)"));
|
|
2777
|
-
emptyScriptCount++;
|
|
2778
|
-
}
|
|
2256
|
+
const type = extname(file) === ".css" ? "css" : "javascript";
|
|
2257
|
+
const needsBundle = BUNDLEABLE_EXTENSIONS.includes(extname(file));
|
|
2779
2258
|
const remoteScript = remoteScripts.find((s) => s.slug === slug);
|
|
2780
|
-
const localMeta = metadata.find((m) => m.slug === slug);
|
|
2781
|
-
|
|
2259
|
+
const localMeta = metadata.find((m) => m.localFileName === file) || metadata.find((m) => m.slug === slug);
|
|
2260
|
+
const sourceHash = hashContent(sourceContent);
|
|
2261
|
+
const localSourceUnchanged = localMeta?.contentHash === sourceHash;
|
|
2262
|
+
if (remoteScript && localSourceUnchanged && !options.all) {
|
|
2263
|
+
console.log(chalk2.dim(` \u25CB ${file}`) + chalk2.dim(" ingen endringer"));
|
|
2264
|
+
skipped++;
|
|
2265
|
+
continue;
|
|
2266
|
+
}
|
|
2267
|
+
if (remoteScript && !options.force) {
|
|
2782
2268
|
const lastPulledVersion = localMeta?.lastPulledVersion || 0;
|
|
2783
|
-
if (remoteScript.current_version > lastPulledVersion
|
|
2269
|
+
if (remoteScript.current_version > lastPulledVersion) {
|
|
2784
2270
|
console.log();
|
|
2785
|
-
console.log(
|
|
2786
|
-
console.log(
|
|
2787
|
-
console.log(
|
|
2788
|
-
console.log(
|
|
2789
|
-
console.log();
|
|
2790
|
-
console.log(chalk3.dim(` Bruk --force for \xE5 overskrive, eller kj\xF8r "kode pull" f\xF8rst.`));
|
|
2271
|
+
console.log(chalk2.yellow(` \u26A0 ${file}: Server har nyere versjon`));
|
|
2272
|
+
console.log(chalk2.dim(` Lokal: v${lastPulledVersion} (fra ${localMeta?.lastPulledAt ? formatTimeDiff(localMeta.lastPulledAt) : "ukjent"})`));
|
|
2273
|
+
console.log(chalk2.dim(` Server: v${remoteScript.current_version}`));
|
|
2274
|
+
console.log(chalk2.dim(` Bruk --force for \xE5 overskrive, eller kj\xF8r "kode pull" f\xF8rst.`));
|
|
2791
2275
|
console.log();
|
|
2792
2276
|
conflicts++;
|
|
2793
2277
|
continue;
|
|
2794
2278
|
}
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2279
|
+
}
|
|
2280
|
+
let usesReact = false;
|
|
2281
|
+
let bundleSize = 0;
|
|
2282
|
+
if (needsBundle) {
|
|
2283
|
+
const buildSpinner = ora2(chalk2.dim(` \u2699 ${file}`)).start();
|
|
2284
|
+
const result = await bundleFile(filePath, scriptsDir);
|
|
2285
|
+
if (result.error) {
|
|
2286
|
+
buildSpinner.fail(chalk2.red(` \u2717 ${file}`) + chalk2.dim(` bygging feilet: ${result.error}`));
|
|
2798
2287
|
continue;
|
|
2799
2288
|
}
|
|
2800
|
-
|
|
2289
|
+
content = result.code;
|
|
2290
|
+
usesReact = result.usesReact;
|
|
2291
|
+
bundleSize = content.length;
|
|
2292
|
+
buildSpinner.stop();
|
|
2293
|
+
}
|
|
2294
|
+
if (content.trim().length === 0) {
|
|
2295
|
+
console.log(chalk2.yellow(` \u26A0 ${file}`) + chalk2.dim(" tom fil"));
|
|
2296
|
+
emptyScriptCount++;
|
|
2297
|
+
}
|
|
2298
|
+
const bundleInfo = needsBundle ? chalk2.dim(` ${bundleSize} bytes`) : "";
|
|
2299
|
+
const reactInfo = usesReact ? chalk2.magenta(" react") : "";
|
|
2300
|
+
const suffix = bundleInfo + reactInfo;
|
|
2301
|
+
if (remoteScript) {
|
|
2302
|
+
const sourceExt = needsBundle ? extname(file).slice(1) : void 0;
|
|
2303
|
+
const updateSpinner = ora2(chalk2.dim(` \u2191 ${file}`)).start();
|
|
2801
2304
|
await client.updateScript(remoteScript.id, {
|
|
2802
2305
|
content,
|
|
2803
|
-
changeSummary: options.message || `Oppdatert via CLI
|
|
2306
|
+
changeSummary: options.message || `Oppdatert via CLI`,
|
|
2307
|
+
...needsBundle ? {
|
|
2308
|
+
metadata: { ...remoteScript.metadata || {}, usesReact },
|
|
2309
|
+
sourceContent,
|
|
2310
|
+
sourceType: sourceExt
|
|
2311
|
+
} : {}
|
|
2804
2312
|
});
|
|
2805
2313
|
updateSpinner.succeed(
|
|
2806
|
-
|
|
2314
|
+
chalk2.green(` \u2713 ${file}`) + chalk2.dim(` v${remoteScript.current_version + 1}`) + suffix
|
|
2807
2315
|
);
|
|
2808
2316
|
pushed++;
|
|
2809
2317
|
} else {
|
|
2810
|
-
const
|
|
2318
|
+
const sourceExt = needsBundle ? extname(file).slice(1) : void 0;
|
|
2319
|
+
const createSpinner = ora2(chalk2.dim(` + ${file}`)).start();
|
|
2811
2320
|
const scriptScope = localMeta?.scope || "global";
|
|
2812
2321
|
const newScript = await client.createScript(config.siteId, {
|
|
2813
2322
|
name: slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, " "),
|
|
@@ -2815,33 +2324,37 @@ async function pushCommand(options) {
|
|
|
2815
2324
|
type,
|
|
2816
2325
|
scope: scriptScope,
|
|
2817
2326
|
autoLoad: options.autoLoad,
|
|
2818
|
-
content
|
|
2327
|
+
content,
|
|
2328
|
+
...needsBundle ? {
|
|
2329
|
+
sourceContent,
|
|
2330
|
+
sourceType: sourceExt
|
|
2331
|
+
} : {}
|
|
2819
2332
|
});
|
|
2820
|
-
const autoLoadInfo = newScript.auto_load ?
|
|
2333
|
+
const autoLoadInfo = newScript.auto_load ? chalk2.dim("auto") : chalk2.dim("manuell");
|
|
2821
2334
|
createSpinner.succeed(
|
|
2822
|
-
|
|
2335
|
+
chalk2.green(` \u2713 ${file}`) + chalk2.cyan(" ny") + chalk2.dim(` [${autoLoadInfo}]`) + suffix
|
|
2823
2336
|
);
|
|
2824
2337
|
created++;
|
|
2825
2338
|
}
|
|
2826
2339
|
}
|
|
2827
2340
|
console.log();
|
|
2828
2341
|
if (pushed > 0) {
|
|
2829
|
-
console.log(
|
|
2342
|
+
console.log(chalk2.green(`Oppdatert ${pushed} skript`));
|
|
2830
2343
|
}
|
|
2831
2344
|
if (created > 0) {
|
|
2832
|
-
console.log(
|
|
2345
|
+
console.log(chalk2.cyan(`Opprettet ${created} nye skript`));
|
|
2833
2346
|
}
|
|
2834
2347
|
if (skipped > 0) {
|
|
2835
|
-
console.log(
|
|
2348
|
+
console.log(chalk2.dim(`Hoppet over ${skipped} uendrede skript`));
|
|
2836
2349
|
}
|
|
2837
2350
|
if (conflicts > 0) {
|
|
2838
|
-
console.log(
|
|
2351
|
+
console.log(chalk2.yellow(`
|
|
2839
2352
|
${conflicts} skript med konflikter (bruk --force for \xE5 overskrive)`));
|
|
2840
2353
|
}
|
|
2841
2354
|
if (emptyScriptCount > 0) {
|
|
2842
|
-
console.log(
|
|
2355
|
+
console.log(chalk2.yellow(`
|
|
2843
2356
|
${emptyScriptCount} tomme skript lastet opp`));
|
|
2844
|
-
console.log(
|
|
2357
|
+
console.log(chalk2.dim("Tomme skript har ingen effekt ved deploy."));
|
|
2845
2358
|
}
|
|
2846
2359
|
let pages = [];
|
|
2847
2360
|
try {
|
|
@@ -2851,9 +2364,17 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2851
2364
|
const updatedScripts = await client.listScripts(config.siteId);
|
|
2852
2365
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2853
2366
|
const updatedMetadata = updatedScripts.map((s) => {
|
|
2854
|
-
const
|
|
2855
|
-
|
|
2856
|
-
|
|
2367
|
+
const possibleExts = s.type === "css" ? [".css"] : [".js", ".ts", ".tsx", ".jsx"];
|
|
2368
|
+
let localFilePath = "";
|
|
2369
|
+
let localContent = s.content;
|
|
2370
|
+
for (const ext of possibleExts) {
|
|
2371
|
+
const candidate = join4(scriptsDir, `${s.slug}${ext}`);
|
|
2372
|
+
if (existsSync4(candidate)) {
|
|
2373
|
+
localFilePath = candidate;
|
|
2374
|
+
localContent = readFileSync4(candidate, "utf-8");
|
|
2375
|
+
break;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2857
2378
|
const pageAssignments = (s.pages || []).filter((p) => p.is_enabled).map((p) => {
|
|
2858
2379
|
const page = pages.find((pg) => pg.id === p.page_id);
|
|
2859
2380
|
return page ? { pageId: page.id, pageSlug: page.slug, pageName: page.name } : null;
|
|
@@ -2870,10 +2391,11 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2870
2391
|
pageAssignments: pageAssignments.length > 0 ? pageAssignments : void 0,
|
|
2871
2392
|
lastPulledVersion: s.current_version,
|
|
2872
2393
|
lastPulledAt: now,
|
|
2873
|
-
contentHash:
|
|
2394
|
+
contentHash: hashContent(localContent),
|
|
2395
|
+
localFileName: localFilePath ? basename(localFilePath) : void 0
|
|
2874
2396
|
};
|
|
2875
2397
|
});
|
|
2876
|
-
|
|
2398
|
+
writeFileSync4(metadataPath, JSON.stringify(updatedMetadata, null, 2));
|
|
2877
2399
|
try {
|
|
2878
2400
|
const result = updateClaudeMd(
|
|
2879
2401
|
projectRoot,
|
|
@@ -2883,69 +2405,117 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2883
2405
|
pagesToInfoFormat(pages)
|
|
2884
2406
|
);
|
|
2885
2407
|
if (result.kodeMd.created) {
|
|
2886
|
-
console.log(
|
|
2408
|
+
console.log(chalk2.green("Opprettet .cure-kode/KODE.md"));
|
|
2887
2409
|
} else if (result.kodeMd.updated) {
|
|
2888
|
-
console.log(
|
|
2410
|
+
console.log(chalk2.dim("Oppdatert .cure-kode/KODE.md"));
|
|
2889
2411
|
}
|
|
2890
2412
|
if (result.claudeMd.created) {
|
|
2891
|
-
console.log(
|
|
2413
|
+
console.log(chalk2.green("Opprettet CLAUDE.md med referanse"));
|
|
2892
2414
|
} else if (result.claudeMd.updated) {
|
|
2893
|
-
console.log(
|
|
2415
|
+
console.log(chalk2.dim("La til Kode-referanse i CLAUDE.md"));
|
|
2416
|
+
}
|
|
2417
|
+
} catch {
|
|
2418
|
+
}
|
|
2419
|
+
try {
|
|
2420
|
+
let scanDir2 = function(dir, relativeTo) {
|
|
2421
|
+
if (!existsSync4(dir)) return;
|
|
2422
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
2423
|
+
if (entry.isDirectory()) {
|
|
2424
|
+
if (!EXCLUDED_DIRS.includes(entry.name) && !entry.name.startsWith(".")) {
|
|
2425
|
+
scanDir2(join4(dir, entry.name), relativeTo);
|
|
2426
|
+
}
|
|
2427
|
+
} else if (entry.isFile() && SUPPORTED_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
|
|
2428
|
+
const fullPath = join4(dir, entry.name);
|
|
2429
|
+
const relPath = fullPath.slice(relativeTo.length + 1);
|
|
2430
|
+
const fileContent = readFileSync4(fullPath, "utf-8");
|
|
2431
|
+
moduleFiles.push({
|
|
2432
|
+
path: relPath,
|
|
2433
|
+
content: fileContent,
|
|
2434
|
+
contentHash: hashContent(fileContent)
|
|
2435
|
+
});
|
|
2436
|
+
}
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2439
|
+
var scanDir = scanDir2;
|
|
2440
|
+
const EXCLUDED_DIRS = ["packages", "node_modules", ".git", "dist", "build", "types"];
|
|
2441
|
+
const moduleFiles = [];
|
|
2442
|
+
for (const entry of readdirSync(scriptsDir, { withFileTypes: true })) {
|
|
2443
|
+
if (entry.isDirectory() && !EXCLUDED_DIRS.includes(entry.name) && !entry.name.startsWith(".")) {
|
|
2444
|
+
scanDir2(join4(scriptsDir, entry.name), scriptsDir);
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
if (moduleFiles.length > 0) {
|
|
2448
|
+
let remoteFiles = [];
|
|
2449
|
+
try {
|
|
2450
|
+
remoteFiles = await client.getProjectFiles(config.siteId);
|
|
2451
|
+
} catch {
|
|
2452
|
+
}
|
|
2453
|
+
const hasChanges = moduleFiles.length !== remoteFiles.length || moduleFiles.some((f) => {
|
|
2454
|
+
const remote = remoteFiles.find((r) => r.path === f.path);
|
|
2455
|
+
return !remote || remote.contentHash !== f.contentHash;
|
|
2456
|
+
});
|
|
2457
|
+
if (hasChanges) {
|
|
2458
|
+
await client.updateProjectFiles(config.siteId, moduleFiles);
|
|
2459
|
+
console.log(chalk2.green(`Synkronisert ${moduleFiles.length} prosjektfil(er)`));
|
|
2460
|
+
for (const f of moduleFiles) {
|
|
2461
|
+
console.log(chalk2.dim(` + ${f.path}`));
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2894
2464
|
}
|
|
2895
2465
|
} catch {
|
|
2896
2466
|
}
|
|
2897
2467
|
} catch (error) {
|
|
2898
2468
|
spinner.fail("Kunne ikke laste opp skript");
|
|
2899
|
-
console.error(
|
|
2469
|
+
console.error(chalk2.red("\nFeil:"), error);
|
|
2900
2470
|
}
|
|
2901
2471
|
}
|
|
2902
2472
|
|
|
2903
2473
|
// src/commands/watch.ts
|
|
2904
|
-
import
|
|
2474
|
+
import chalk3 from "chalk";
|
|
2905
2475
|
import chokidar from "chokidar";
|
|
2906
|
-
import { readFileSync as
|
|
2907
|
-
import { join as
|
|
2476
|
+
import { readFileSync as readFileSync5, existsSync as existsSync5 } from "fs";
|
|
2477
|
+
import { join as join5, basename as basename2, extname as extname2 } from "path";
|
|
2908
2478
|
async function watchCommand(options) {
|
|
2909
2479
|
const projectRoot = findProjectRoot();
|
|
2910
2480
|
if (!projectRoot) {
|
|
2911
|
-
console.log(
|
|
2912
|
-
console.log(
|
|
2481
|
+
console.log(chalk3.red("\u274C Not in a Cure Kode project."));
|
|
2482
|
+
console.log(chalk3.dim(' Run "kode init" first.'));
|
|
2913
2483
|
return;
|
|
2914
2484
|
}
|
|
2915
2485
|
const config = getProjectConfig(projectRoot);
|
|
2916
2486
|
if (!config) {
|
|
2917
|
-
console.log(
|
|
2487
|
+
console.log(chalk3.red("\u274C Could not read project configuration."));
|
|
2918
2488
|
return;
|
|
2919
2489
|
}
|
|
2920
2490
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2921
|
-
if (!
|
|
2922
|
-
console.log(
|
|
2923
|
-
console.log(
|
|
2924
|
-
console.log(
|
|
2491
|
+
if (!existsSync5(scriptsDir)) {
|
|
2492
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Scripts directory not found."));
|
|
2493
|
+
console.log(chalk3.dim(` Expected: ${scriptsDir}`));
|
|
2494
|
+
console.log(chalk3.dim(' Run "kode pull" first.'));
|
|
2925
2495
|
return;
|
|
2926
2496
|
}
|
|
2927
|
-
console.log(
|
|
2928
|
-
console.log(
|
|
2929
|
-
console.log(
|
|
2930
|
-
console.log(
|
|
2497
|
+
console.log(chalk3.bold("\n\u{1F440} Watching for changes...\n"));
|
|
2498
|
+
console.log(chalk3.dim(` Directory: ${scriptsDir}`));
|
|
2499
|
+
console.log(chalk3.dim(` Site: ${config.siteName} (${config.siteSlug})`));
|
|
2500
|
+
console.log(chalk3.dim(` Environment: ${config.environment || "staging"}`));
|
|
2931
2501
|
if (options.deploy) {
|
|
2932
|
-
console.log(
|
|
2502
|
+
console.log(chalk3.cyan(` Auto-deploy: enabled`));
|
|
2933
2503
|
}
|
|
2934
2504
|
console.log();
|
|
2935
|
-
console.log(
|
|
2505
|
+
console.log(chalk3.dim("Press Ctrl+C to stop.\n"));
|
|
2936
2506
|
const client = createApiClient(config);
|
|
2937
|
-
const metadataPath =
|
|
2507
|
+
const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
|
|
2938
2508
|
let metadata = [];
|
|
2939
|
-
if (
|
|
2940
|
-
metadata = JSON.parse(
|
|
2509
|
+
if (existsSync5(metadataPath)) {
|
|
2510
|
+
metadata = JSON.parse(readFileSync5(metadataPath, "utf-8"));
|
|
2941
2511
|
}
|
|
2942
2512
|
let remoteScripts = [];
|
|
2943
2513
|
try {
|
|
2944
2514
|
remoteScripts = await client.listScripts(config.siteId);
|
|
2945
|
-
console.log(
|
|
2515
|
+
console.log(chalk3.dim(`Loaded ${remoteScripts.length} remote script(s)
|
|
2946
2516
|
`));
|
|
2947
2517
|
} catch (error) {
|
|
2948
|
-
console.log(
|
|
2518
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Could not load remote scripts. Will create new on change."));
|
|
2949
2519
|
}
|
|
2950
2520
|
const pendingChanges = /* @__PURE__ */ new Map();
|
|
2951
2521
|
const DEBOUNCE_MS = 500;
|
|
@@ -2958,18 +2528,18 @@ async function watchCommand(options) {
|
|
|
2958
2528
|
if (failedSyncs.size === 0 && successCount === 0) return;
|
|
2959
2529
|
const statusParts = [];
|
|
2960
2530
|
if (successCount > 0) {
|
|
2961
|
-
statusParts.push(
|
|
2531
|
+
statusParts.push(chalk3.green(`${successCount} synced`));
|
|
2962
2532
|
}
|
|
2963
2533
|
if (failedSyncs.size > 0) {
|
|
2964
|
-
statusParts.push(
|
|
2534
|
+
statusParts.push(chalk3.red(`${failedSyncs.size} pending errors`));
|
|
2965
2535
|
}
|
|
2966
|
-
console.log(
|
|
2536
|
+
console.log(chalk3.dim(`
|
|
2967
2537
|
\u2500\u2500\u2500 Status: ${statusParts.join(", ")} \u2500\u2500\u2500
|
|
2968
2538
|
`));
|
|
2969
2539
|
};
|
|
2970
2540
|
const retryFailedSyncs = async () => {
|
|
2971
2541
|
for (const [filePath, failed] of failedSyncs.entries()) {
|
|
2972
|
-
if (!
|
|
2542
|
+
if (!existsSync5(filePath)) {
|
|
2973
2543
|
failedSyncs.delete(filePath);
|
|
2974
2544
|
continue;
|
|
2975
2545
|
}
|
|
@@ -2982,7 +2552,7 @@ async function watchCommand(options) {
|
|
|
2982
2552
|
}
|
|
2983
2553
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
2984
2554
|
console.log(
|
|
2985
|
-
|
|
2555
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.yellow(`\u21BB Retrying ${failed.fileName}...`) + chalk3.dim(` (attempt ${failed.attempts + 1}/${MAX_RETRY_ATTEMPTS})`)
|
|
2986
2556
|
);
|
|
2987
2557
|
await handleChange(
|
|
2988
2558
|
filePath,
|
|
@@ -3003,7 +2573,7 @@ async function watchCommand(options) {
|
|
|
3003
2573
|
}
|
|
3004
2574
|
const syncFile = async () => {
|
|
3005
2575
|
try {
|
|
3006
|
-
const content =
|
|
2576
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
3007
2577
|
const remoteScript = remoteScripts.find((s) => s.slug === slug);
|
|
3008
2578
|
const localMeta = metadata.find((m) => m.slug === slug);
|
|
3009
2579
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
@@ -3025,16 +2595,16 @@ async function watchCommand(options) {
|
|
|
3025
2595
|
failedSyncs.delete(filePath);
|
|
3026
2596
|
if (wasRetry) {
|
|
3027
2597
|
console.log(
|
|
3028
|
-
|
|
2598
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.dim(` \u2192 v${remoteScript.current_version}`) + chalk3.cyan(" (recovered)")
|
|
3029
2599
|
);
|
|
3030
2600
|
} else {
|
|
3031
2601
|
console.log(
|
|
3032
|
-
|
|
2602
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.dim(` \u2192 v${remoteScript.current_version}`)
|
|
3033
2603
|
);
|
|
3034
2604
|
}
|
|
3035
2605
|
} else {
|
|
3036
2606
|
console.log(
|
|
3037
|
-
|
|
2607
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.dim(` \u2192 v${remoteScript.current_version}`)
|
|
3038
2608
|
);
|
|
3039
2609
|
}
|
|
3040
2610
|
successCount++;
|
|
@@ -3042,11 +2612,11 @@ async function watchCommand(options) {
|
|
|
3042
2612
|
try {
|
|
3043
2613
|
await client.deploy(config.siteId, config.environment || "staging");
|
|
3044
2614
|
console.log(
|
|
3045
|
-
|
|
2615
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.cyan(` \u21B3 Deployed to ${config.environment || "staging"}`)
|
|
3046
2616
|
);
|
|
3047
2617
|
} catch (deployError) {
|
|
3048
2618
|
console.log(
|
|
3049
|
-
|
|
2619
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.red(` \u21B3 Deploy failed: ${deployError.message || "Unknown error"}`)
|
|
3050
2620
|
);
|
|
3051
2621
|
}
|
|
3052
2622
|
}
|
|
@@ -3062,11 +2632,11 @@ async function watchCommand(options) {
|
|
|
3062
2632
|
if (failedSyncs.has(filePath)) {
|
|
3063
2633
|
failedSyncs.delete(filePath);
|
|
3064
2634
|
console.log(
|
|
3065
|
-
|
|
2635
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.cyan(" (created, recovered)")
|
|
3066
2636
|
);
|
|
3067
2637
|
} else {
|
|
3068
2638
|
console.log(
|
|
3069
|
-
|
|
2639
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.cyan(" (created)")
|
|
3070
2640
|
);
|
|
3071
2641
|
}
|
|
3072
2642
|
successCount++;
|
|
@@ -3097,11 +2667,11 @@ async function watchCommand(options) {
|
|
|
3097
2667
|
errorCount++;
|
|
3098
2668
|
if (attempts >= MAX_RETRY_ATTEMPTS) {
|
|
3099
2669
|
console.log(
|
|
3100
|
-
|
|
2670
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.red(`\u2717 ${fileName}`) + chalk3.dim(` - ${errorMessage}`) + chalk3.red(` (gave up after ${MAX_RETRY_ATTEMPTS} attempts)`)
|
|
3101
2671
|
);
|
|
3102
2672
|
} else {
|
|
3103
2673
|
console.log(
|
|
3104
|
-
|
|
2674
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.red(`\u2717 ${fileName}`) + chalk3.dim(` - ${errorMessage}`) + chalk3.yellow(` (will retry in ${RETRY_DELAY_MS / 1e3}s)`)
|
|
3105
2675
|
);
|
|
3106
2676
|
}
|
|
3107
2677
|
printStatus();
|
|
@@ -3117,7 +2687,7 @@ async function watchCommand(options) {
|
|
|
3117
2687
|
}, DEBOUNCE_MS);
|
|
3118
2688
|
pendingChanges.set(filePath, timeout);
|
|
3119
2689
|
};
|
|
3120
|
-
const watcher = chokidar.watch(
|
|
2690
|
+
const watcher = chokidar.watch(join5(scriptsDir, "**/*.{js,css}"), {
|
|
3121
2691
|
persistent: true,
|
|
3122
2692
|
ignoreInitial: true,
|
|
3123
2693
|
awaitWriteFinish: {
|
|
@@ -3131,21 +2701,21 @@ async function watchCommand(options) {
|
|
|
3131
2701
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
3132
2702
|
const fileName = basename2(filePath);
|
|
3133
2703
|
console.log(
|
|
3134
|
-
|
|
2704
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.yellow(`\u26A0 ${fileName} deleted locally`) + chalk3.dim(" (remote not affected)")
|
|
3135
2705
|
);
|
|
3136
2706
|
});
|
|
3137
2707
|
process.on("SIGINT", () => {
|
|
3138
2708
|
clearInterval(retryInterval);
|
|
3139
|
-
console.log(
|
|
2709
|
+
console.log(chalk3.dim("\n\nStopping watch...\n"));
|
|
3140
2710
|
if (successCount > 0 || failedSyncs.size > 0) {
|
|
3141
|
-
console.log(
|
|
2711
|
+
console.log(chalk3.bold("Session summary:"));
|
|
3142
2712
|
if (successCount > 0) {
|
|
3143
|
-
console.log(
|
|
2713
|
+
console.log(chalk3.green(` \u2713 ${successCount} file(s) synced`));
|
|
3144
2714
|
}
|
|
3145
2715
|
if (failedSyncs.size > 0) {
|
|
3146
|
-
console.log(
|
|
2716
|
+
console.log(chalk3.red(` \u2717 ${failedSyncs.size} file(s) failed:`));
|
|
3147
2717
|
for (const failed of failedSyncs.values()) {
|
|
3148
|
-
console.log(
|
|
2718
|
+
console.log(chalk3.dim(` - ${failed.fileName}: ${failed.error}`));
|
|
3149
2719
|
}
|
|
3150
2720
|
}
|
|
3151
2721
|
console.log();
|
|
@@ -3156,10 +2726,10 @@ async function watchCommand(options) {
|
|
|
3156
2726
|
}
|
|
3157
2727
|
|
|
3158
2728
|
// src/commands/deploy.ts
|
|
3159
|
-
import
|
|
3160
|
-
import
|
|
3161
|
-
import { readFileSync as
|
|
3162
|
-
import { join as
|
|
2729
|
+
import chalk4 from "chalk";
|
|
2730
|
+
import ora3 from "ora";
|
|
2731
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
|
|
2732
|
+
import { join as join6, basename as basename3, extname as extname3 } from "path";
|
|
3163
2733
|
function formatBytes(bytes) {
|
|
3164
2734
|
if (bytes < 1024) return `${bytes} B`;
|
|
3165
2735
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -3168,7 +2738,7 @@ function formatBytes(bytes) {
|
|
|
3168
2738
|
async function showDeploymentPreview(config, projectRoot) {
|
|
3169
2739
|
if (!config) return;
|
|
3170
2740
|
const client = createApiClient(config);
|
|
3171
|
-
const spinner =
|
|
2741
|
+
const spinner = ora3("Analyserer deployment...").start();
|
|
3172
2742
|
try {
|
|
3173
2743
|
const [remoteScripts, deployStatus] = await Promise.all([
|
|
3174
2744
|
client.listScripts(config.siteId),
|
|
@@ -3176,39 +2746,39 @@ async function showDeploymentPreview(config, projectRoot) {
|
|
|
3176
2746
|
]);
|
|
3177
2747
|
spinner.stop();
|
|
3178
2748
|
console.log();
|
|
3179
|
-
console.log(
|
|
3180
|
-
console.log(
|
|
2749
|
+
console.log(chalk4.bold("\u{1F50D} Deployment Preview (t\xF8rrkj\xF8ring)"));
|
|
2750
|
+
console.log(chalk4.dim(` M\xE5l: staging`));
|
|
3181
2751
|
console.log();
|
|
3182
2752
|
const stagingVersion = deployStatus.staging.lastSuccessful?.version || null;
|
|
3183
2753
|
const productionVersion = deployStatus.production.lastSuccessful?.version || null;
|
|
3184
|
-
console.log(
|
|
2754
|
+
console.log(chalk4.bold("N\xE5v\xE6rende status"));
|
|
3185
2755
|
if (stagingVersion) {
|
|
3186
|
-
console.log(
|
|
2756
|
+
console.log(chalk4.dim(` Staging: v${stagingVersion}`));
|
|
3187
2757
|
} else {
|
|
3188
|
-
console.log(
|
|
2758
|
+
console.log(chalk4.dim(` Staging: ingen deployments`));
|
|
3189
2759
|
}
|
|
3190
2760
|
if (deployStatus.productionEnabled) {
|
|
3191
2761
|
if (productionVersion) {
|
|
3192
|
-
console.log(
|
|
2762
|
+
console.log(chalk4.dim(` Production: v${productionVersion}`));
|
|
3193
2763
|
} else {
|
|
3194
|
-
console.log(
|
|
2764
|
+
console.log(chalk4.dim(` Production: aktivert, ingen deployments`));
|
|
3195
2765
|
}
|
|
3196
2766
|
}
|
|
3197
2767
|
console.log();
|
|
3198
2768
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
3199
|
-
const localFiles =
|
|
3200
|
-
const metadataPath =
|
|
2769
|
+
const localFiles = existsSync6(scriptsDir) ? readdirSync2(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
|
|
2770
|
+
const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
|
|
3201
2771
|
let metadata = [];
|
|
3202
|
-
if (
|
|
2772
|
+
if (existsSync6(metadataPath)) {
|
|
3203
2773
|
try {
|
|
3204
|
-
metadata = JSON.parse(
|
|
2774
|
+
metadata = JSON.parse(readFileSync6(metadataPath, "utf-8"));
|
|
3205
2775
|
} catch {
|
|
3206
2776
|
}
|
|
3207
2777
|
}
|
|
3208
2778
|
const localBySlug = /* @__PURE__ */ new Map();
|
|
3209
2779
|
for (const file of localFiles) {
|
|
3210
2780
|
const slug = basename3(file, extname3(file));
|
|
3211
|
-
const content =
|
|
2781
|
+
const content = readFileSync6(join6(scriptsDir, file), "utf-8");
|
|
3212
2782
|
localBySlug.set(slug, { content, size: Buffer.byteLength(content, "utf-8") });
|
|
3213
2783
|
}
|
|
3214
2784
|
const remoteBySlug = /* @__PURE__ */ new Map();
|
|
@@ -3265,63 +2835,63 @@ async function showDeploymentPreview(config, projectRoot) {
|
|
|
3265
2835
|
}
|
|
3266
2836
|
}
|
|
3267
2837
|
if (changes.length > 0) {
|
|
3268
|
-
console.log(
|
|
2838
|
+
console.log(chalk4.bold("Endringer"));
|
|
3269
2839
|
console.log();
|
|
3270
2840
|
console.log(
|
|
3271
|
-
|
|
2841
|
+
chalk4.dim(" ") + chalk4.dim("Skript".padEnd(25)) + chalk4.dim("F\xF8r".padStart(8)) + chalk4.dim("Etter".padStart(8)) + chalk4.dim("Endring".padStart(15))
|
|
3272
2842
|
);
|
|
3273
|
-
console.log(
|
|
2843
|
+
console.log(chalk4.dim(" " + "\u2500".repeat(56)));
|
|
3274
2844
|
for (const change of changes) {
|
|
3275
2845
|
const beforeStr = change.beforeVersion ? `v${change.beforeVersion}` : "-";
|
|
3276
2846
|
const afterStr = `v${change.afterVersion}`;
|
|
3277
2847
|
let diffStr;
|
|
3278
2848
|
if (change.isNew) {
|
|
3279
|
-
diffStr =
|
|
2849
|
+
diffStr = chalk4.cyan(`+${change.linesDiff.added} linjer`);
|
|
3280
2850
|
} else {
|
|
3281
2851
|
const parts = [];
|
|
3282
|
-
if (change.linesDiff.added > 0) parts.push(
|
|
3283
|
-
if (change.linesDiff.removed > 0) parts.push(
|
|
2852
|
+
if (change.linesDiff.added > 0) parts.push(chalk4.green(`+${change.linesDiff.added}`));
|
|
2853
|
+
if (change.linesDiff.removed > 0) parts.push(chalk4.red(`-${change.linesDiff.removed}`));
|
|
3284
2854
|
diffStr = parts.join(" ") + " linjer";
|
|
3285
2855
|
}
|
|
3286
|
-
const nameColor = change.isNew ?
|
|
2856
|
+
const nameColor = change.isNew ? chalk4.cyan : chalk4.white;
|
|
3287
2857
|
console.log(
|
|
3288
|
-
` ${nameColor(change.fileName.padEnd(25))}${
|
|
2858
|
+
` ${nameColor(change.fileName.padEnd(25))}${chalk4.dim(beforeStr.padStart(8))}${afterStr.padStart(8)}${diffStr.padStart(15)}`
|
|
3289
2859
|
);
|
|
3290
2860
|
}
|
|
3291
2861
|
console.log();
|
|
3292
2862
|
const sizeDiff = totalAfterSize - totalBeforeSize;
|
|
3293
|
-
const sizeDiffStr = sizeDiff > 0 ?
|
|
2863
|
+
const sizeDiffStr = sizeDiff > 0 ? chalk4.yellow(`+${formatBytes(sizeDiff)}`) : sizeDiff < 0 ? chalk4.green(`-${formatBytes(Math.abs(sizeDiff))}`) : chalk4.dim("uendret");
|
|
3294
2864
|
console.log(
|
|
3295
|
-
|
|
2865
|
+
chalk4.dim(` Total: ${formatBytes(totalBeforeSize)} \u2192 ${formatBytes(totalAfterSize)} (${sizeDiffStr})`)
|
|
3296
2866
|
);
|
|
3297
2867
|
} else {
|
|
3298
|
-
console.log(
|
|
2868
|
+
console.log(chalk4.dim(" Ingen endringer \xE5 deploye"));
|
|
3299
2869
|
}
|
|
3300
2870
|
if (warnings.length > 0) {
|
|
3301
2871
|
console.log();
|
|
3302
|
-
console.log(
|
|
2872
|
+
console.log(chalk4.bold("Advarsler"));
|
|
3303
2873
|
for (const warning of warnings) {
|
|
3304
|
-
console.log(
|
|
2874
|
+
console.log(chalk4.yellow(` \u26A0 ${warning}`));
|
|
3305
2875
|
}
|
|
3306
2876
|
}
|
|
3307
2877
|
console.log();
|
|
3308
|
-
console.log(
|
|
2878
|
+
console.log(chalk4.dim('Dette er en t\xF8rrkj\xF8ring. Kj\xF8r "kode deploy" for \xE5 deploye.'));
|
|
3309
2879
|
console.log();
|
|
3310
2880
|
} catch (error) {
|
|
3311
2881
|
spinner.fail("Kunne ikke analysere deployment");
|
|
3312
|
-
console.error(
|
|
2882
|
+
console.error(chalk4.red("\nFeil:"), error.message || error);
|
|
3313
2883
|
}
|
|
3314
2884
|
}
|
|
3315
2885
|
async function deployCommand(environment, options) {
|
|
3316
2886
|
const projectRoot = findProjectRoot();
|
|
3317
2887
|
if (!projectRoot) {
|
|
3318
|
-
console.log(
|
|
3319
|
-
console.log(
|
|
2888
|
+
console.log(chalk4.red("\u274C Not in a Cure Kode project."));
|
|
2889
|
+
console.log(chalk4.dim(' Run "kode init" first.'));
|
|
3320
2890
|
return;
|
|
3321
2891
|
}
|
|
3322
2892
|
const config = getProjectConfig(projectRoot);
|
|
3323
2893
|
if (!config) {
|
|
3324
|
-
console.log(
|
|
2894
|
+
console.log(chalk4.red("\u274C Could not read project configuration."));
|
|
3325
2895
|
return;
|
|
3326
2896
|
}
|
|
3327
2897
|
if (options?.dryRun) {
|
|
@@ -3331,15 +2901,15 @@ async function deployCommand(environment, options) {
|
|
|
3331
2901
|
const shouldPromote = options?.promote || environment === "production";
|
|
3332
2902
|
if (shouldPromote) {
|
|
3333
2903
|
const client2 = createApiClient(config);
|
|
3334
|
-
const spinner2 =
|
|
2904
|
+
const spinner2 = ora3("Sjekker produksjonsstatus...").start();
|
|
3335
2905
|
try {
|
|
3336
2906
|
const status = await client2.getDeploymentStatus(config.siteId);
|
|
3337
2907
|
if (!status.productionEnabled) {
|
|
3338
2908
|
spinner2.fail("Produksjon er ikke aktivert");
|
|
3339
2909
|
console.log();
|
|
3340
|
-
console.log(
|
|
3341
|
-
console.log(
|
|
3342
|
-
console.log(
|
|
2910
|
+
console.log(chalk4.yellow("\u26A0\uFE0F Produksjon er deaktivert for dette prosjektet."));
|
|
2911
|
+
console.log(chalk4.dim(" Aktiver produksjon f\xF8rst:"));
|
|
2912
|
+
console.log(chalk4.dim(" kode production enable [--domain <domain>]"));
|
|
3343
2913
|
console.log();
|
|
3344
2914
|
return;
|
|
3345
2915
|
}
|
|
@@ -3347,23 +2917,23 @@ async function deployCommand(environment, options) {
|
|
|
3347
2917
|
const deployment = await client2.promoteToProduction(config.siteId);
|
|
3348
2918
|
spinner2.succeed("Promoted to production");
|
|
3349
2919
|
console.log();
|
|
3350
|
-
console.log(
|
|
3351
|
-
console.log(
|
|
3352
|
-
console.log(
|
|
2920
|
+
console.log(chalk4.dim("Deployment details:"));
|
|
2921
|
+
console.log(chalk4.dim(` Version: ${deployment.version}`));
|
|
2922
|
+
console.log(chalk4.dim(` Status: ${deployment.status}`));
|
|
3353
2923
|
console.log();
|
|
3354
|
-
console.log(
|
|
3355
|
-
console.log(
|
|
2924
|
+
console.log(chalk4.bold("CDN URL:"));
|
|
2925
|
+
console.log(chalk4.cyan(` https://app.cure.no/api/cdn/${config.siteSlug}/init.js`));
|
|
3356
2926
|
console.log();
|
|
3357
|
-
console.log(
|
|
2927
|
+
console.log(chalk4.green("\u2705 Production is now running the latest staging version!"));
|
|
3358
2928
|
} catch (error) {
|
|
3359
2929
|
spinner2.fail("Promotion failed");
|
|
3360
|
-
console.error(
|
|
2930
|
+
console.error(chalk4.red("\nError:"), error.message || error);
|
|
3361
2931
|
}
|
|
3362
2932
|
return;
|
|
3363
2933
|
}
|
|
3364
2934
|
const client = createApiClient(config);
|
|
3365
2935
|
if (options?.force) {
|
|
3366
|
-
const forceSpinner =
|
|
2936
|
+
const forceSpinner = ora3("Sjekker l\xE5s...").start();
|
|
3367
2937
|
try {
|
|
3368
2938
|
const lockStatus = await client.getLockStatus(config.siteId);
|
|
3369
2939
|
if (lockStatus.isLocked) {
|
|
@@ -3371,18 +2941,18 @@ async function deployCommand(environment, options) {
|
|
|
3371
2941
|
forceSpinner.text = "Frigj\xF8r gammel l\xE5s...";
|
|
3372
2942
|
} else {
|
|
3373
2943
|
forceSpinner.warn("Aktiv l\xE5s funnet");
|
|
3374
|
-
console.log(
|
|
3375
|
-
console.log(
|
|
3376
|
-
console.log(
|
|
2944
|
+
console.log(chalk4.yellow("\n\u26A0\uFE0F Deployment er l\xE5st av en annen prosess."));
|
|
2945
|
+
console.log(chalk4.dim(` L\xE5st siden: ${lockStatus.acquiredAt ? new Date(lockStatus.acquiredAt).toLocaleString("nb-NO") : "ukjent"}`));
|
|
2946
|
+
console.log(chalk4.dim(` L\xE5s-ID: ${lockStatus.lockHolder}`));
|
|
3377
2947
|
console.log();
|
|
3378
|
-
console.log(
|
|
3379
|
-
console.log(
|
|
2948
|
+
console.log(chalk4.yellow(" Hvis du er sikker p\xE5 at l\xE5sen er foreldet, kj\xF8r med --force igjen."));
|
|
2949
|
+
console.log(chalk4.dim(" L\xE5sen vil utl\xF8pe automatisk etter 10 minutter."));
|
|
3380
2950
|
return;
|
|
3381
2951
|
}
|
|
3382
2952
|
const result = await client.forceReleaseLock(config.siteId);
|
|
3383
2953
|
if (result.wasLocked) {
|
|
3384
2954
|
forceSpinner.succeed("L\xE5s frigjort");
|
|
3385
|
-
console.log(
|
|
2955
|
+
console.log(chalk4.dim(` Tidligere l\xE5s fra: ${result.acquiredAt ? new Date(result.acquiredAt).toLocaleString("nb-NO") : "ukjent"}`));
|
|
3386
2956
|
console.log();
|
|
3387
2957
|
} else {
|
|
3388
2958
|
forceSpinner.info("Ingen l\xE5s \xE5 frigj\xF8re");
|
|
@@ -3392,11 +2962,11 @@ async function deployCommand(environment, options) {
|
|
|
3392
2962
|
}
|
|
3393
2963
|
} catch (error) {
|
|
3394
2964
|
forceSpinner.fail("Kunne ikke sjekke/frigj\xF8re l\xE5s");
|
|
3395
|
-
console.error(
|
|
2965
|
+
console.error(chalk4.red("\nError:"), error.message || error);
|
|
3396
2966
|
return;
|
|
3397
2967
|
}
|
|
3398
2968
|
}
|
|
3399
|
-
const preCheckSpinner =
|
|
2969
|
+
const preCheckSpinner = ora3("Sjekker scripts...").start();
|
|
3400
2970
|
try {
|
|
3401
2971
|
const scripts = await client.listScripts(config.siteId);
|
|
3402
2972
|
const emptyScripts = scripts.filter(
|
|
@@ -3404,45 +2974,45 @@ async function deployCommand(environment, options) {
|
|
|
3404
2974
|
);
|
|
3405
2975
|
if (emptyScripts.length > 0) {
|
|
3406
2976
|
preCheckSpinner.warn(`${emptyScripts.length} tomme script(s) funnet`);
|
|
3407
|
-
console.log(
|
|
2977
|
+
console.log(chalk4.yellow("\n\u26A0\uFE0F F\xF8lgende scripts er tomme:"));
|
|
3408
2978
|
emptyScripts.forEach((s) => {
|
|
3409
|
-
console.log(
|
|
2979
|
+
console.log(chalk4.dim(` - ${s.slug}.${s.type === "javascript" ? "js" : "css"}`));
|
|
3410
2980
|
});
|
|
3411
|
-
console.log(
|
|
2981
|
+
console.log(chalk4.dim(" Tomme scripts har ingen effekt n\xE5r de er deployet.\n"));
|
|
3412
2982
|
} else {
|
|
3413
2983
|
preCheckSpinner.succeed(`${scripts.filter((s) => s.is_active).length} script(s) klare`);
|
|
3414
2984
|
}
|
|
3415
2985
|
} catch {
|
|
3416
2986
|
preCheckSpinner.info("Kunne ikke sjekke scripts");
|
|
3417
2987
|
}
|
|
3418
|
-
const spinner =
|
|
2988
|
+
const spinner = ora3("Deploying to staging...").start();
|
|
3419
2989
|
try {
|
|
3420
2990
|
const deployment = await client.deploy(config.siteId, "staging");
|
|
3421
2991
|
spinner.succeed("Deployed to staging");
|
|
3422
2992
|
console.log();
|
|
3423
|
-
console.log(
|
|
3424
|
-
console.log(
|
|
3425
|
-
console.log(
|
|
3426
|
-
console.log(
|
|
2993
|
+
console.log(chalk4.dim("Deployment details:"));
|
|
2994
|
+
console.log(chalk4.dim(` Version: ${deployment.version}`));
|
|
2995
|
+
console.log(chalk4.dim(` Status: ${deployment.status}`));
|
|
2996
|
+
console.log(chalk4.dim(` Started: ${new Date(deployment.started_at).toLocaleString("nb-NO")}`));
|
|
3427
2997
|
console.log();
|
|
3428
|
-
console.log(
|
|
3429
|
-
console.log(
|
|
2998
|
+
console.log(chalk4.bold("CDN URL:"));
|
|
2999
|
+
console.log(chalk4.cyan(` https://app.cure.no/api/cdn/${config.siteSlug}/init.js`));
|
|
3430
3000
|
console.log();
|
|
3431
|
-
console.log(
|
|
3001
|
+
console.log(chalk4.cyan('\u{1F4A1} Tip: Use "kode deploy production" to promote to production.'));
|
|
3432
3002
|
} catch (error) {
|
|
3433
3003
|
spinner.fail("Deployment failed");
|
|
3434
|
-
console.error(
|
|
3004
|
+
console.error(chalk4.red("\nError:"), error.message || error);
|
|
3435
3005
|
}
|
|
3436
3006
|
}
|
|
3437
3007
|
|
|
3438
3008
|
// src/commands/html.ts
|
|
3439
|
-
import
|
|
3440
|
-
import
|
|
3009
|
+
import chalk5 from "chalk";
|
|
3010
|
+
import ora4 from "ora";
|
|
3441
3011
|
|
|
3442
3012
|
// src/lib/page-cache.ts
|
|
3443
|
-
import { existsSync as
|
|
3444
|
-
import { join as
|
|
3445
|
-
var
|
|
3013
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5, unlinkSync } from "fs";
|
|
3014
|
+
import { join as join7, basename as basename4 } from "path";
|
|
3015
|
+
var PROJECT_CONFIG_DIR2 = ".cure-kode";
|
|
3446
3016
|
var PAGES_DIR = "pages";
|
|
3447
3017
|
function urlToSlug(url) {
|
|
3448
3018
|
try {
|
|
@@ -3455,32 +3025,32 @@ function urlToSlug(url) {
|
|
|
3455
3025
|
}
|
|
3456
3026
|
}
|
|
3457
3027
|
function getPagesDir(projectRoot) {
|
|
3458
|
-
return
|
|
3028
|
+
return join7(projectRoot, PROJECT_CONFIG_DIR2, PAGES_DIR);
|
|
3459
3029
|
}
|
|
3460
3030
|
function getPageCachePath(projectRoot, urlOrSlug) {
|
|
3461
3031
|
const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
|
|
3462
|
-
return
|
|
3032
|
+
return join7(getPagesDir(projectRoot), `${slug}.json`);
|
|
3463
3033
|
}
|
|
3464
3034
|
function ensurePagesDir(projectRoot) {
|
|
3465
3035
|
const pagesDir = getPagesDir(projectRoot);
|
|
3466
|
-
if (!
|
|
3467
|
-
|
|
3036
|
+
if (!existsSync7(pagesDir)) {
|
|
3037
|
+
mkdirSync3(pagesDir, { recursive: true });
|
|
3468
3038
|
}
|
|
3469
3039
|
}
|
|
3470
3040
|
function savePageContext(projectRoot, context) {
|
|
3471
3041
|
ensurePagesDir(projectRoot);
|
|
3472
3042
|
const slug = urlToSlug(context.url);
|
|
3473
3043
|
const cachePath = getPageCachePath(projectRoot, slug);
|
|
3474
|
-
|
|
3044
|
+
writeFileSync5(cachePath, JSON.stringify(context, null, 2), "utf-8");
|
|
3475
3045
|
return slug;
|
|
3476
3046
|
}
|
|
3477
3047
|
function readPageContext(projectRoot, urlOrSlug) {
|
|
3478
3048
|
const cachePath = getPageCachePath(projectRoot, urlOrSlug);
|
|
3479
|
-
if (!
|
|
3049
|
+
if (!existsSync7(cachePath)) {
|
|
3480
3050
|
return null;
|
|
3481
3051
|
}
|
|
3482
3052
|
try {
|
|
3483
|
-
const content =
|
|
3053
|
+
const content = readFileSync7(cachePath, "utf-8");
|
|
3484
3054
|
return JSON.parse(content);
|
|
3485
3055
|
} catch {
|
|
3486
3056
|
return null;
|
|
@@ -3488,7 +3058,7 @@ function readPageContext(projectRoot, urlOrSlug) {
|
|
|
3488
3058
|
}
|
|
3489
3059
|
function deletePageContext(projectRoot, urlOrSlug) {
|
|
3490
3060
|
const cachePath = getPageCachePath(projectRoot, urlOrSlug);
|
|
3491
|
-
if (
|
|
3061
|
+
if (existsSync7(cachePath)) {
|
|
3492
3062
|
unlinkSync(cachePath);
|
|
3493
3063
|
return true;
|
|
3494
3064
|
}
|
|
@@ -3496,7 +3066,7 @@ function deletePageContext(projectRoot, urlOrSlug) {
|
|
|
3496
3066
|
}
|
|
3497
3067
|
function listCachedPages(projectRoot) {
|
|
3498
3068
|
const pagesDir = getPagesDir(projectRoot);
|
|
3499
|
-
if (!
|
|
3069
|
+
if (!existsSync7(pagesDir)) {
|
|
3500
3070
|
return [];
|
|
3501
3071
|
}
|
|
3502
3072
|
const files = readdirSync3(pagesDir).filter((f) => f.endsWith(".json"));
|
|
@@ -3561,34 +3131,34 @@ function toCachedContext(structure) {
|
|
|
3561
3131
|
async function htmlCommand(url, options) {
|
|
3562
3132
|
const projectRoot = findProjectRoot();
|
|
3563
3133
|
if (!projectRoot) {
|
|
3564
|
-
console.log(
|
|
3565
|
-
console.log(
|
|
3134
|
+
console.log(chalk5.red("\u274C Not in a Cure Kode project."));
|
|
3135
|
+
console.log(chalk5.dim(' Run "kode init" first.'));
|
|
3566
3136
|
return;
|
|
3567
3137
|
}
|
|
3568
3138
|
const config = getProjectConfig(projectRoot);
|
|
3569
3139
|
if (!config) {
|
|
3570
|
-
console.log(
|
|
3140
|
+
console.log(chalk5.red("\u274C Could not read project configuration."));
|
|
3571
3141
|
return;
|
|
3572
3142
|
}
|
|
3573
3143
|
let parsedUrl;
|
|
3574
3144
|
try {
|
|
3575
3145
|
parsedUrl = new URL(url.startsWith("http") ? url : `https://${url}`);
|
|
3576
3146
|
} catch {
|
|
3577
|
-
console.log(
|
|
3147
|
+
console.log(chalk5.red("\u274C Invalid URL."));
|
|
3578
3148
|
return;
|
|
3579
3149
|
}
|
|
3580
3150
|
if (options?.save && !options?.force) {
|
|
3581
3151
|
const slug = urlToSlug(parsedUrl.toString());
|
|
3582
3152
|
const cached = readPageContext(projectRoot, slug);
|
|
3583
3153
|
if (cached) {
|
|
3584
|
-
console.log(
|
|
3585
|
-
console.log(
|
|
3154
|
+
console.log(chalk5.dim(`Using cached version from ${cached.extractedAt}`));
|
|
3155
|
+
console.log(chalk5.dim(`Use --force to refresh`));
|
|
3586
3156
|
console.log();
|
|
3587
3157
|
printPageContext(cached);
|
|
3588
3158
|
return;
|
|
3589
3159
|
}
|
|
3590
3160
|
}
|
|
3591
|
-
const spinner =
|
|
3161
|
+
const spinner = ora4(`Fetching ${parsedUrl.hostname}${parsedUrl.pathname}...`).start();
|
|
3592
3162
|
try {
|
|
3593
3163
|
const client = createApiClient(config);
|
|
3594
3164
|
if (options?.save) {
|
|
@@ -3609,7 +3179,7 @@ async function htmlCommand(url, options) {
|
|
|
3609
3179
|
spinner.succeed("Page structure extracted");
|
|
3610
3180
|
const cachedContext = toCachedContext(structure);
|
|
3611
3181
|
const slug = savePageContext(projectRoot, cachedContext);
|
|
3612
|
-
console.log(
|
|
3182
|
+
console.log(chalk5.dim(`Saved to .cure-kode/pages/${slug}.json`));
|
|
3613
3183
|
const contextPage = {
|
|
3614
3184
|
slug,
|
|
3615
3185
|
url: structure.url,
|
|
@@ -3620,7 +3190,7 @@ async function htmlCommand(url, options) {
|
|
|
3620
3190
|
cms: structure.cmsPatterns.length > 0 ? structure.cmsPatterns.map((c) => `${c.containerClass} (${c.itemCount})`).join(", ") : void 0
|
|
3621
3191
|
};
|
|
3622
3192
|
upsertPage(projectRoot, contextPage, "kode html --save");
|
|
3623
|
-
console.log(
|
|
3193
|
+
console.log(chalk5.dim(`Updated .cure-kode/context.md`));
|
|
3624
3194
|
console.log();
|
|
3625
3195
|
printPageContext(cachedContext);
|
|
3626
3196
|
return;
|
|
@@ -3632,115 +3202,115 @@ async function htmlCommand(url, options) {
|
|
|
3632
3202
|
return;
|
|
3633
3203
|
}
|
|
3634
3204
|
console.log();
|
|
3635
|
-
console.log(
|
|
3636
|
-
console.log(
|
|
3205
|
+
console.log(chalk5.bold(result.title || parsedUrl.hostname));
|
|
3206
|
+
console.log(chalk5.dim(result.url));
|
|
3637
3207
|
console.log();
|
|
3638
3208
|
const hasCureKode = result.scripts.cureKode.length > 0;
|
|
3639
3209
|
if (hasCureKode) {
|
|
3640
|
-
console.log(
|
|
3210
|
+
console.log(chalk5.green("\u2705 Cure Kode installed"));
|
|
3641
3211
|
} else {
|
|
3642
|
-
console.log(
|
|
3643
|
-
console.log(
|
|
3644
|
-
console.log(
|
|
3212
|
+
console.log(chalk5.yellow("\u26A0\uFE0F Cure Kode not found"));
|
|
3213
|
+
console.log(chalk5.dim(" Add this to your Webflow site:"));
|
|
3214
|
+
console.log(chalk5.cyan(` <script src="https://app.cure.no/api/cdn/${config.siteSlug}/init.js"></script>`));
|
|
3645
3215
|
}
|
|
3646
3216
|
console.log();
|
|
3647
|
-
console.log(
|
|
3217
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
3648
3218
|
console.log(
|
|
3649
|
-
` Scripts: ${result.stats.totalScripts}` +
|
|
3219
|
+
` Scripts: ${result.stats.totalScripts}` + chalk5.dim(` (${result.stats.externalScripts} external, ${result.stats.inlineScripts} inline)`)
|
|
3650
3220
|
);
|
|
3651
3221
|
console.log(` Styles: ${result.stats.totalStyles}`);
|
|
3652
|
-
console.log(
|
|
3222
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
3653
3223
|
console.log();
|
|
3654
3224
|
if (!options?.styles) {
|
|
3655
|
-
console.log(
|
|
3225
|
+
console.log(chalk5.bold("Scripts"));
|
|
3656
3226
|
console.log();
|
|
3657
3227
|
if (result.scripts.webflow.length > 0) {
|
|
3658
|
-
console.log(
|
|
3228
|
+
console.log(chalk5.blue(" Webflow:"));
|
|
3659
3229
|
result.scripts.webflow.forEach((s) => {
|
|
3660
|
-
console.log(
|
|
3230
|
+
console.log(chalk5.dim(` ${s.src || "(inline)"}`));
|
|
3661
3231
|
});
|
|
3662
3232
|
}
|
|
3663
3233
|
if (result.scripts.cureKode.length > 0) {
|
|
3664
|
-
console.log(
|
|
3234
|
+
console.log(chalk5.green(" Cure Kode:"));
|
|
3665
3235
|
result.scripts.cureKode.forEach((s) => {
|
|
3666
|
-
console.log(
|
|
3236
|
+
console.log(chalk5.dim(` ${s.src || "(inline)"}`));
|
|
3667
3237
|
});
|
|
3668
3238
|
}
|
|
3669
3239
|
if (result.scripts.thirdParty.length > 0) {
|
|
3670
|
-
console.log(
|
|
3240
|
+
console.log(chalk5.yellow(" Third-party:"));
|
|
3671
3241
|
result.scripts.thirdParty.forEach((s) => {
|
|
3672
|
-
console.log(
|
|
3242
|
+
console.log(chalk5.dim(` ${s.src || "(inline)"}`));
|
|
3673
3243
|
});
|
|
3674
3244
|
}
|
|
3675
3245
|
if (result.scripts.custom.length > 0) {
|
|
3676
|
-
console.log(
|
|
3246
|
+
console.log(chalk5.magenta(" Custom:"));
|
|
3677
3247
|
result.scripts.custom.forEach((s) => {
|
|
3678
3248
|
const label = s.src || (s.inline ? `(inline, ${s.content?.length || 0} chars)` : "(inline)");
|
|
3679
|
-
console.log(
|
|
3249
|
+
console.log(chalk5.dim(` ${label}`));
|
|
3680
3250
|
});
|
|
3681
3251
|
}
|
|
3682
3252
|
console.log();
|
|
3683
3253
|
}
|
|
3684
3254
|
if (result.detectedComponents.length > 0) {
|
|
3685
|
-
console.log(
|
|
3255
|
+
console.log(chalk5.bold("Detected Components"));
|
|
3686
3256
|
console.log();
|
|
3687
3257
|
result.detectedComponents.forEach((comp) => {
|
|
3688
|
-
console.log(
|
|
3258
|
+
console.log(chalk5.dim(` \u2022 ${comp}`));
|
|
3689
3259
|
});
|
|
3690
3260
|
console.log();
|
|
3691
3261
|
}
|
|
3692
3262
|
if (options?.styles) {
|
|
3693
|
-
console.log(
|
|
3263
|
+
console.log(chalk5.bold("Stylesheets"));
|
|
3694
3264
|
console.log();
|
|
3695
3265
|
result.styles.forEach((style) => {
|
|
3696
3266
|
if (style.href) {
|
|
3697
|
-
const isWebflow = style.isWebflow ?
|
|
3698
|
-
console.log(
|
|
3267
|
+
const isWebflow = style.isWebflow ? chalk5.blue(" [WF]") : "";
|
|
3268
|
+
console.log(chalk5.dim(` ${style.href}${isWebflow}`));
|
|
3699
3269
|
} else {
|
|
3700
|
-
console.log(
|
|
3270
|
+
console.log(chalk5.dim(" (inline style)"));
|
|
3701
3271
|
}
|
|
3702
3272
|
});
|
|
3703
3273
|
console.log();
|
|
3704
3274
|
}
|
|
3705
3275
|
} catch (error) {
|
|
3706
3276
|
spinner.fail("Failed to fetch HTML");
|
|
3707
|
-
console.error(
|
|
3277
|
+
console.error(chalk5.red("\nError:"), error.message || error);
|
|
3708
3278
|
}
|
|
3709
3279
|
}
|
|
3710
3280
|
function printPageContext(context) {
|
|
3711
|
-
console.log(
|
|
3712
|
-
console.log(
|
|
3281
|
+
console.log(chalk5.bold(context.title || context.url));
|
|
3282
|
+
console.log(chalk5.dim(context.url));
|
|
3713
3283
|
console.log();
|
|
3714
3284
|
if (context.sections.length > 0) {
|
|
3715
|
-
console.log(
|
|
3285
|
+
console.log(chalk5.bold("Sections"));
|
|
3716
3286
|
for (const section of context.sections.slice(0, 8)) {
|
|
3717
3287
|
const name = section.heading || section.id || section.className?.split(" ")[0] || "section";
|
|
3718
3288
|
const badges = [];
|
|
3719
|
-
if (section.hasCms) badges.push(
|
|
3720
|
-
if (section.hasForm) badges.push(
|
|
3289
|
+
if (section.hasCms) badges.push(chalk5.cyan("CMS"));
|
|
3290
|
+
if (section.hasForm) badges.push(chalk5.yellow("Form"));
|
|
3721
3291
|
const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
|
|
3722
3292
|
console.log(` \u2022 ${name}${badgeStr}`);
|
|
3723
3293
|
if (section.textSample) {
|
|
3724
|
-
console.log(
|
|
3294
|
+
console.log(chalk5.dim(` "${section.textSample.slice(0, 60)}..."`));
|
|
3725
3295
|
}
|
|
3726
3296
|
}
|
|
3727
3297
|
if (context.sections.length > 8) {
|
|
3728
|
-
console.log(
|
|
3298
|
+
console.log(chalk5.dim(` ... and ${context.sections.length - 8} more`));
|
|
3729
3299
|
}
|
|
3730
3300
|
console.log();
|
|
3731
3301
|
}
|
|
3732
3302
|
if (context.ctas.length > 0) {
|
|
3733
|
-
console.log(
|
|
3303
|
+
console.log(chalk5.bold("CTAs"));
|
|
3734
3304
|
for (const cta of context.ctas.slice(0, 6)) {
|
|
3735
|
-
console.log(` \u2022 "${cta.text}" ${
|
|
3305
|
+
console.log(` \u2022 "${cta.text}" ${chalk5.dim(`(${cta.location})`)}`);
|
|
3736
3306
|
}
|
|
3737
3307
|
if (context.ctas.length > 6) {
|
|
3738
|
-
console.log(
|
|
3308
|
+
console.log(chalk5.dim(` ... and ${context.ctas.length - 6} more`));
|
|
3739
3309
|
}
|
|
3740
3310
|
console.log();
|
|
3741
3311
|
}
|
|
3742
3312
|
if (context.forms.length > 0) {
|
|
3743
|
-
console.log(
|
|
3313
|
+
console.log(chalk5.bold("Forms"));
|
|
3744
3314
|
for (const form of context.forms) {
|
|
3745
3315
|
const fields = form.fields.map((f) => f.label || f.type).slice(0, 4).join(", ");
|
|
3746
3316
|
const more = form.fields.length > 4 ? ` +${form.fields.length - 4}` : "";
|
|
@@ -3749,17 +3319,17 @@ function printPageContext(context) {
|
|
|
3749
3319
|
console.log();
|
|
3750
3320
|
}
|
|
3751
3321
|
if (context.cmsPatterns.length > 0) {
|
|
3752
|
-
console.log(
|
|
3322
|
+
console.log(chalk5.bold("CMS Collections"));
|
|
3753
3323
|
for (const cms of context.cmsPatterns) {
|
|
3754
|
-
console.log(` \u2022 ${cms.containerClass}: ${
|
|
3324
|
+
console.log(` \u2022 ${cms.containerClass}: ${chalk5.cyan(`${cms.itemCount} items`)}`);
|
|
3755
3325
|
if (cms.templateFields.length > 0) {
|
|
3756
|
-
console.log(
|
|
3326
|
+
console.log(chalk5.dim(` Fields: ${cms.templateFields.slice(0, 5).join(", ")}`));
|
|
3757
3327
|
}
|
|
3758
3328
|
}
|
|
3759
3329
|
console.log();
|
|
3760
3330
|
}
|
|
3761
3331
|
if (context.navigation.length > 0) {
|
|
3762
|
-
console.log(
|
|
3332
|
+
console.log(chalk5.bold("Navigation"));
|
|
3763
3333
|
for (const nav of context.navigation) {
|
|
3764
3334
|
const items = nav.items.slice(0, 5).map((i) => i.text).join(", ");
|
|
3765
3335
|
const more = nav.items.length > 5 ? ` +${nav.items.length - 5}` : "";
|
|
@@ -3768,7 +3338,7 @@ function printPageContext(context) {
|
|
|
3768
3338
|
console.log();
|
|
3769
3339
|
}
|
|
3770
3340
|
if (context.notes && context.notes.length > 0) {
|
|
3771
|
-
console.log(
|
|
3341
|
+
console.log(chalk5.bold("Notes"));
|
|
3772
3342
|
for (const note of context.notes) {
|
|
3773
3343
|
console.log(` \u2022 ${note}`);
|
|
3774
3344
|
}
|
|
@@ -3777,13 +3347,13 @@ function printPageContext(context) {
|
|
|
3777
3347
|
}
|
|
3778
3348
|
|
|
3779
3349
|
// src/commands/status.ts
|
|
3780
|
-
import
|
|
3781
|
-
import
|
|
3782
|
-
import { readFileSync as
|
|
3783
|
-
import { join as
|
|
3784
|
-
import { createHash as
|
|
3785
|
-
function
|
|
3786
|
-
return
|
|
3350
|
+
import chalk6 from "chalk";
|
|
3351
|
+
import ora5 from "ora";
|
|
3352
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
3353
|
+
import { join as join8, basename as basename5, extname as extname4 } from "path";
|
|
3354
|
+
import { createHash as createHash2 } from "crypto";
|
|
3355
|
+
function hashContent2(content) {
|
|
3356
|
+
return createHash2("sha256").update(content).digest("hex").substring(0, 16);
|
|
3787
3357
|
}
|
|
3788
3358
|
function formatRelativeTime(dateStr) {
|
|
3789
3359
|
const date = new Date(dateStr);
|
|
@@ -3818,36 +3388,36 @@ function isSyncStale(metadata) {
|
|
|
3818
3388
|
async function statusCommand(options) {
|
|
3819
3389
|
const projectRoot = findProjectRoot();
|
|
3820
3390
|
if (!projectRoot) {
|
|
3821
|
-
console.log(
|
|
3822
|
-
console.log(
|
|
3391
|
+
console.log(chalk6.red("\u274C Not in a Cure Kode project."));
|
|
3392
|
+
console.log(chalk6.dim(' Run "kode init" first.'));
|
|
3823
3393
|
return;
|
|
3824
3394
|
}
|
|
3825
3395
|
const config = getProjectConfig(projectRoot);
|
|
3826
3396
|
if (!config) {
|
|
3827
|
-
console.log(
|
|
3397
|
+
console.log(chalk6.red("\u274C Could not read project configuration."));
|
|
3828
3398
|
return;
|
|
3829
3399
|
}
|
|
3830
|
-
const metadataPath =
|
|
3400
|
+
const metadataPath = join8(projectRoot, ".cure-kode", "scripts.json");
|
|
3831
3401
|
let syncMetadata = [];
|
|
3832
|
-
if (
|
|
3402
|
+
if (existsSync8(metadataPath)) {
|
|
3833
3403
|
try {
|
|
3834
|
-
syncMetadata = JSON.parse(
|
|
3404
|
+
syncMetadata = JSON.parse(readFileSync8(metadataPath, "utf-8"));
|
|
3835
3405
|
} catch {
|
|
3836
3406
|
}
|
|
3837
3407
|
}
|
|
3838
3408
|
console.log();
|
|
3839
|
-
console.log(
|
|
3840
|
-
console.log(
|
|
3841
|
-
console.log(
|
|
3409
|
+
console.log(chalk6.bold(`\u{1F4E6} ${config.siteName}`));
|
|
3410
|
+
console.log(chalk6.dim(` Slug: ${config.siteSlug}`));
|
|
3411
|
+
console.log(chalk6.dim(` ID: ${config.siteId}`));
|
|
3842
3412
|
console.log();
|
|
3843
|
-
console.log(
|
|
3413
|
+
console.log(chalk6.bold("CDN URL"));
|
|
3844
3414
|
console.log();
|
|
3845
|
-
console.log(
|
|
3415
|
+
console.log(chalk6.cyan(` https://app.cure.no/api/cdn/${config.siteSlug}/init.js`));
|
|
3846
3416
|
console.log();
|
|
3847
|
-
console.log(
|
|
3848
|
-
console.log(
|
|
3417
|
+
console.log(chalk6.dim(" Add to Webflow \u2192 Project Settings \u2192 Custom Code \u2192 Body (before </body>):"));
|
|
3418
|
+
console.log(chalk6.dim(` <script src="https://app.cure.no/api/cdn/${config.siteSlug}/init.js"></script>`));
|
|
3849
3419
|
console.log();
|
|
3850
|
-
const spinner =
|
|
3420
|
+
const spinner = ora5("Fetching status...").start();
|
|
3851
3421
|
try {
|
|
3852
3422
|
const client = createApiClient(config);
|
|
3853
3423
|
const [remoteScripts, deployStatus] = await Promise.all([
|
|
@@ -3855,46 +3425,46 @@ async function statusCommand(options) {
|
|
|
3855
3425
|
client.getDeploymentStatus(config.siteId)
|
|
3856
3426
|
]);
|
|
3857
3427
|
spinner.stop();
|
|
3858
|
-
console.log(
|
|
3428
|
+
console.log(chalk6.bold("Environments"));
|
|
3859
3429
|
console.log();
|
|
3860
3430
|
const stagingStatus = deployStatus.staging.lastSuccessful;
|
|
3861
3431
|
if (stagingStatus) {
|
|
3862
3432
|
console.log(
|
|
3863
|
-
|
|
3433
|
+
chalk6.blue(" Staging: ") + chalk6.green("\u25CF") + chalk6.dim(` v${stagingStatus.version}`) + chalk6.dim(` (${formatDate(stagingStatus.completedAt)})`)
|
|
3864
3434
|
);
|
|
3865
3435
|
} else {
|
|
3866
|
-
console.log(
|
|
3436
|
+
console.log(chalk6.blue(" Staging: ") + chalk6.yellow("\u25CB") + chalk6.dim(" No deployments"));
|
|
3867
3437
|
}
|
|
3868
3438
|
const productionEnabled = deployStatus.productionEnabled ?? false;
|
|
3869
3439
|
const prodStatus = deployStatus.production.lastSuccessful;
|
|
3870
3440
|
if (!productionEnabled) {
|
|
3871
3441
|
console.log(
|
|
3872
|
-
|
|
3442
|
+
chalk6.gray(" Production: ") + chalk6.gray("\u25CB") + chalk6.gray(" Deaktivert") + chalk6.dim(" (kun staging)")
|
|
3873
3443
|
);
|
|
3874
3444
|
console.log();
|
|
3875
3445
|
console.log(
|
|
3876
|
-
|
|
3446
|
+
chalk6.dim(' \u{1F4A1} Run "kode production enable" to activate production environment.')
|
|
3877
3447
|
);
|
|
3878
3448
|
} else if (prodStatus) {
|
|
3879
3449
|
console.log(
|
|
3880
|
-
|
|
3450
|
+
chalk6.green(" Production: ") + chalk6.green("\u25CF") + chalk6.dim(` v${prodStatus.version}`) + chalk6.dim(` (${formatDate(prodStatus.completedAt)})`)
|
|
3881
3451
|
);
|
|
3882
3452
|
if (deployStatus.productionDomain) {
|
|
3883
|
-
console.log(
|
|
3453
|
+
console.log(chalk6.dim(` Domain: ${deployStatus.productionDomain}`));
|
|
3884
3454
|
}
|
|
3885
3455
|
} else {
|
|
3886
3456
|
console.log(
|
|
3887
|
-
|
|
3457
|
+
chalk6.green(" Production: ") + chalk6.yellow("\u25CB") + chalk6.dim(" Aktivert, ingen deployments enda")
|
|
3888
3458
|
);
|
|
3889
3459
|
}
|
|
3890
3460
|
if (deployStatus.canPromote && productionEnabled) {
|
|
3891
3461
|
console.log();
|
|
3892
3462
|
console.log(
|
|
3893
|
-
|
|
3463
|
+
chalk6.cyan(' \u{1F4A1} Staging is ahead of production. Run "kode deploy --promote" to update.')
|
|
3894
3464
|
);
|
|
3895
3465
|
}
|
|
3896
3466
|
console.log();
|
|
3897
|
-
console.log(
|
|
3467
|
+
console.log(chalk6.bold("Scripts"));
|
|
3898
3468
|
const syncStatus = isSyncStale(syncMetadata);
|
|
3899
3469
|
if (syncStatus.isStale && syncStatus.lastSyncedAt) {
|
|
3900
3470
|
const syncDate = new Date(syncStatus.lastSyncedAt);
|
|
@@ -3905,20 +3475,20 @@ async function statusCommand(options) {
|
|
|
3905
3475
|
minute: "2-digit"
|
|
3906
3476
|
});
|
|
3907
3477
|
console.log(
|
|
3908
|
-
|
|
3478
|
+
chalk6.yellow(` \u26A0 Sist synkronisert: ${formatRelativeTime(syncStatus.lastSyncedAt)}`) + chalk6.dim(` (${dateStr})`)
|
|
3909
3479
|
);
|
|
3910
3480
|
} else if (!syncStatus.lastSyncedAt && syncMetadata.length === 0) {
|
|
3911
|
-
console.log(
|
|
3481
|
+
console.log(chalk6.yellow(` \u26A0 Aldri synkronisert - kj\xF8r "kode pull" f\xF8rst`));
|
|
3912
3482
|
}
|
|
3913
3483
|
console.log();
|
|
3914
3484
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
3915
|
-
const localFiles =
|
|
3485
|
+
const localFiles = existsSync8(scriptsDir) ? readdirSync4(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
|
|
3916
3486
|
const localBySlug = /* @__PURE__ */ new Map();
|
|
3917
3487
|
for (const file of localFiles) {
|
|
3918
3488
|
const slug = basename5(file, extname4(file));
|
|
3919
|
-
const filePath =
|
|
3920
|
-
const content =
|
|
3921
|
-
const stats =
|
|
3489
|
+
const filePath = join8(scriptsDir, file);
|
|
3490
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
3491
|
+
const stats = statSync2(filePath);
|
|
3922
3492
|
localBySlug.set(slug, { file, content, modified: stats.mtime });
|
|
3923
3493
|
}
|
|
3924
3494
|
const remoteBySlug = /* @__PURE__ */ new Map();
|
|
@@ -3936,48 +3506,48 @@ async function statusCommand(options) {
|
|
|
3936
3506
|
const remote = remoteBySlug.get(slug);
|
|
3937
3507
|
const meta = metadataBySlug.get(slug);
|
|
3938
3508
|
if (local && remote) {
|
|
3939
|
-
const scopeTag = remote.scope === "global" ?
|
|
3940
|
-
const localHash =
|
|
3509
|
+
const scopeTag = remote.scope === "global" ? chalk6.blue("[G]") : chalk6.magenta("[P]");
|
|
3510
|
+
const localHash = hashContent2(local.content);
|
|
3941
3511
|
const hasLocalChanges = meta && localHash !== meta.contentHash;
|
|
3942
3512
|
const lastPulledVersion = meta?.lastPulledVersion || 0;
|
|
3943
3513
|
const hasRemoteChanges = remote.current_version > lastPulledVersion && lastPulledVersion > 0;
|
|
3944
3514
|
if (hasLocalChanges && hasRemoteChanges) {
|
|
3945
3515
|
console.log(
|
|
3946
|
-
|
|
3516
|
+
chalk6.red(" \u26A0 ") + `${local.file}` + chalk6.dim(` v${lastPulledVersion}`) + chalk6.red(` \u2192 v${remote.current_version}`) + ` ${scopeTag}` + chalk6.red(" (konflikt)")
|
|
3947
3517
|
);
|
|
3948
3518
|
outdatedCount++;
|
|
3949
3519
|
} else if (hasLocalChanges) {
|
|
3950
3520
|
console.log(
|
|
3951
|
-
|
|
3521
|
+
chalk6.yellow(" M ") + `${local.file}` + chalk6.dim(` v${remote.current_version} ${scopeTag}`) + chalk6.yellow(" (lokalt endret)")
|
|
3952
3522
|
);
|
|
3953
3523
|
} else if (hasRemoteChanges) {
|
|
3954
3524
|
console.log(
|
|
3955
|
-
|
|
3525
|
+
chalk6.cyan(" \u26A0 ") + `${local.file}` + chalk6.dim(` v${lastPulledVersion}`) + chalk6.cyan(` \u2192 v${remote.current_version}`) + ` ${scopeTag}` + chalk6.cyan(" (server oppdatert)")
|
|
3956
3526
|
);
|
|
3957
3527
|
outdatedCount++;
|
|
3958
3528
|
} else if (local.content !== remote.content) {
|
|
3959
3529
|
console.log(
|
|
3960
|
-
|
|
3530
|
+
chalk6.yellow(" M ") + `${local.file}` + chalk6.dim(` v${remote.current_version} ${scopeTag}`) + chalk6.yellow(" (lokalt endret)")
|
|
3961
3531
|
);
|
|
3962
3532
|
} else {
|
|
3963
3533
|
console.log(
|
|
3964
|
-
|
|
3534
|
+
chalk6.green(" \u2713 ") + chalk6.dim(`${local.file}`) + chalk6.dim(` v${remote.current_version} ${scopeTag}`)
|
|
3965
3535
|
);
|
|
3966
3536
|
}
|
|
3967
3537
|
} else if (local && !remote) {
|
|
3968
3538
|
console.log(
|
|
3969
|
-
|
|
3539
|
+
chalk6.cyan(" + ") + `${local.file}` + chalk6.cyan(" (ny, ikke pushet)")
|
|
3970
3540
|
);
|
|
3971
3541
|
} else if (!local && remote) {
|
|
3972
3542
|
const ext = remote.type === "javascript" ? "js" : "css";
|
|
3973
3543
|
const fileName = `${slug}.${ext}`;
|
|
3974
3544
|
console.log(
|
|
3975
|
-
|
|
3545
|
+
chalk6.red(" - ") + chalk6.dim(`${fileName}`) + chalk6.red(" (kun p\xE5 server, ikke pullet)")
|
|
3976
3546
|
);
|
|
3977
3547
|
}
|
|
3978
3548
|
}
|
|
3979
3549
|
if (allSlugs.size === 0) {
|
|
3980
|
-
console.log(
|
|
3550
|
+
console.log(chalk6.dim(" Ingen skript enda."));
|
|
3981
3551
|
}
|
|
3982
3552
|
console.log();
|
|
3983
3553
|
const modified = [...allSlugs].filter((slug) => {
|
|
@@ -3985,7 +3555,7 @@ async function statusCommand(options) {
|
|
|
3985
3555
|
const remote = remoteBySlug.get(slug);
|
|
3986
3556
|
if (!local || !remote) return false;
|
|
3987
3557
|
const meta = metadataBySlug.get(slug);
|
|
3988
|
-
const localHash =
|
|
3558
|
+
const localHash = hashContent2(local.content);
|
|
3989
3559
|
return meta ? localHash !== meta.contentHash : local.content !== remote.content;
|
|
3990
3560
|
}).length;
|
|
3991
3561
|
const newLocal = [...allSlugs].filter(
|
|
@@ -3995,25 +3565,25 @@ async function statusCommand(options) {
|
|
|
3995
3565
|
(slug) => !localBySlug.has(slug) && remoteBySlug.has(slug)
|
|
3996
3566
|
).length;
|
|
3997
3567
|
if (modified > 0 || newLocal > 0 || remoteOnly > 0 || outdatedCount > 0) {
|
|
3998
|
-
console.log(
|
|
3568
|
+
console.log(chalk6.bold("Handlinger"));
|
|
3999
3569
|
console.log();
|
|
4000
3570
|
if (modified > 0) {
|
|
4001
|
-
console.log(
|
|
3571
|
+
console.log(chalk6.yellow(` ${modified} endret lokalt`) + chalk6.dim(' \u2192 "kode push"'));
|
|
4002
3572
|
}
|
|
4003
3573
|
if (newLocal > 0) {
|
|
4004
|
-
console.log(
|
|
3574
|
+
console.log(chalk6.cyan(` ${newLocal} nye lokale`) + chalk6.dim(' \u2192 "kode push"'));
|
|
4005
3575
|
}
|
|
4006
3576
|
if (outdatedCount > 0) {
|
|
4007
|
-
console.log(
|
|
3577
|
+
console.log(chalk6.cyan(` ${outdatedCount} utdaterte`) + chalk6.dim(' \u2192 "kode pull"'));
|
|
4008
3578
|
}
|
|
4009
3579
|
if (remoteOnly > 0) {
|
|
4010
|
-
console.log(
|
|
3580
|
+
console.log(chalk6.red(` ${remoteOnly} kun p\xE5 server`) + chalk6.dim(' \u2192 "kode pull"'));
|
|
4011
3581
|
}
|
|
4012
3582
|
console.log();
|
|
4013
3583
|
}
|
|
4014
3584
|
} catch (error) {
|
|
4015
3585
|
spinner.fail("Failed to fetch status");
|
|
4016
|
-
console.error(
|
|
3586
|
+
console.error(chalk6.red("\nError:"), error.message || error);
|
|
4017
3587
|
}
|
|
4018
3588
|
}
|
|
4019
3589
|
function formatDate(dateStr) {
|
|
@@ -4028,25 +3598,25 @@ function formatDate(dateStr) {
|
|
|
4028
3598
|
}
|
|
4029
3599
|
|
|
4030
3600
|
// src/commands/context.ts
|
|
4031
|
-
import
|
|
4032
|
-
import
|
|
3601
|
+
import chalk7 from "chalk";
|
|
3602
|
+
import ora6 from "ora";
|
|
4033
3603
|
import { spawn } from "child_process";
|
|
4034
3604
|
async function contextCommand(options) {
|
|
4035
3605
|
const projectRoot = findProjectRoot();
|
|
4036
3606
|
if (!projectRoot) {
|
|
4037
|
-
console.log(
|
|
4038
|
-
console.log(
|
|
3607
|
+
console.log(chalk7.red("Error: Not in a Cure Kode project."));
|
|
3608
|
+
console.log(chalk7.dim('Run "kode init" first.'));
|
|
4039
3609
|
return;
|
|
4040
3610
|
}
|
|
4041
3611
|
const config = getProjectConfig(projectRoot);
|
|
4042
3612
|
if (!config) {
|
|
4043
|
-
console.log(
|
|
3613
|
+
console.log(chalk7.red("Error: Invalid project configuration."));
|
|
4044
3614
|
return;
|
|
4045
3615
|
}
|
|
4046
3616
|
const contextPath = getContextPath(projectRoot);
|
|
4047
3617
|
const context = readContext(projectRoot);
|
|
4048
3618
|
if (options.refresh) {
|
|
4049
|
-
const spinner =
|
|
3619
|
+
const spinner = ora6("Refreshing context from server...").start();
|
|
4050
3620
|
try {
|
|
4051
3621
|
const siteResponse = await fetch(
|
|
4052
3622
|
`https://app.cure.no/api/cdn/sites/${config.siteId}`,
|
|
@@ -4095,28 +3665,28 @@ async function contextCommand(options) {
|
|
|
4095
3665
|
};
|
|
4096
3666
|
writeContext(projectRoot, newContext);
|
|
4097
3667
|
spinner.succeed("Context refreshed");
|
|
4098
|
-
console.log(
|
|
3668
|
+
console.log(chalk7.dim(`Updated: ${contextPath}`));
|
|
4099
3669
|
return;
|
|
4100
3670
|
} catch (error) {
|
|
4101
3671
|
spinner.fail("Failed to refresh context");
|
|
4102
|
-
console.log(
|
|
3672
|
+
console.log(chalk7.red(error instanceof Error ? error.message : "Unknown error"));
|
|
4103
3673
|
return;
|
|
4104
3674
|
}
|
|
4105
3675
|
}
|
|
4106
3676
|
if (options.edit) {
|
|
4107
3677
|
if (!context) {
|
|
4108
|
-
console.log(
|
|
4109
|
-
console.log(
|
|
3678
|
+
console.log(chalk7.red("Error: No context file found."));
|
|
3679
|
+
console.log(chalk7.dim('Run "kode init" or "kode context --refresh" first.'));
|
|
4110
3680
|
return;
|
|
4111
3681
|
}
|
|
4112
3682
|
const editor = process.env.EDITOR || process.env.VISUAL || "vim";
|
|
4113
|
-
console.log(
|
|
3683
|
+
console.log(chalk7.dim(`Opening ${contextPath} in ${editor}...`));
|
|
4114
3684
|
const child = spawn(editor, [contextPath], {
|
|
4115
3685
|
stdio: "inherit"
|
|
4116
3686
|
});
|
|
4117
3687
|
child.on("error", (err) => {
|
|
4118
|
-
console.log(
|
|
4119
|
-
console.log(
|
|
3688
|
+
console.log(chalk7.red(`Failed to open editor: ${err.message}`));
|
|
3689
|
+
console.log(chalk7.dim(`You can edit the file manually: ${contextPath}`));
|
|
4120
3690
|
});
|
|
4121
3691
|
return;
|
|
4122
3692
|
}
|
|
@@ -4129,38 +3699,38 @@ async function contextCommand(options) {
|
|
|
4129
3699
|
return;
|
|
4130
3700
|
}
|
|
4131
3701
|
if (!context) {
|
|
4132
|
-
console.log(
|
|
4133
|
-
console.log(
|
|
3702
|
+
console.log(chalk7.yellow("No context file found."));
|
|
3703
|
+
console.log(chalk7.dim('Run "kode context --refresh" to create one.'));
|
|
4134
3704
|
return;
|
|
4135
3705
|
}
|
|
4136
|
-
console.log(
|
|
4137
|
-
console.log(
|
|
4138
|
-
console.log(
|
|
3706
|
+
console.log(chalk7.bold(`Context: ${context.site.name}`));
|
|
3707
|
+
console.log(chalk7.dim(`Last updated: ${context.lastUpdated}`));
|
|
3708
|
+
console.log(chalk7.dim(`Updated by: ${context.updatedBy}`));
|
|
4139
3709
|
console.log();
|
|
4140
3710
|
if (context.site.domain || context.site.stagingDomain) {
|
|
4141
3711
|
if (context.site.domain) {
|
|
4142
|
-
console.log(
|
|
3712
|
+
console.log(chalk7.dim("Domain: ") + context.site.domain);
|
|
4143
3713
|
}
|
|
4144
3714
|
if (context.site.stagingDomain) {
|
|
4145
|
-
console.log(
|
|
3715
|
+
console.log(chalk7.dim("Staging: ") + context.site.stagingDomain);
|
|
4146
3716
|
}
|
|
4147
3717
|
console.log();
|
|
4148
3718
|
}
|
|
4149
|
-
console.log(
|
|
3719
|
+
console.log(chalk7.bold("Scripts:"));
|
|
4150
3720
|
if (context.scripts.length === 0) {
|
|
4151
|
-
console.log(
|
|
3721
|
+
console.log(chalk7.dim(" (no scripts)"));
|
|
4152
3722
|
} else {
|
|
4153
3723
|
for (const script of context.scripts) {
|
|
4154
3724
|
const typeIcon = script.type === "css" ? "\u{1F3A8}" : "\u{1F4DC}";
|
|
4155
|
-
const scopeLabel = script.scope === "global" ?
|
|
4156
|
-
const purpose = script.purpose ?
|
|
3725
|
+
const scopeLabel = script.scope === "global" ? chalk7.cyan("global") : chalk7.yellow("page");
|
|
3726
|
+
const purpose = script.purpose ? chalk7.dim(` - ${script.purpose}`) : "";
|
|
4157
3727
|
console.log(` ${typeIcon} ${script.slug} [${scopeLabel}]${purpose}`);
|
|
4158
3728
|
}
|
|
4159
3729
|
}
|
|
4160
3730
|
console.log();
|
|
4161
|
-
console.log(
|
|
3731
|
+
console.log(chalk7.bold("Notes:"));
|
|
4162
3732
|
if (context.notes.length === 0 || context.notes.length === 3 && context.notes[0].startsWith("(HTML")) {
|
|
4163
|
-
console.log(
|
|
3733
|
+
console.log(chalk7.dim(" (no notes yet)"));
|
|
4164
3734
|
} else {
|
|
4165
3735
|
for (const note of context.notes) {
|
|
4166
3736
|
if (!note.startsWith("(")) {
|
|
@@ -4169,67 +3739,67 @@ async function contextCommand(options) {
|
|
|
4169
3739
|
}
|
|
4170
3740
|
}
|
|
4171
3741
|
console.log();
|
|
4172
|
-
console.log(
|
|
3742
|
+
console.log(chalk7.bold("Recent Sessions:"));
|
|
4173
3743
|
if (context.sessions.length === 0) {
|
|
4174
|
-
console.log(
|
|
3744
|
+
console.log(chalk7.dim(" (no sessions recorded)"));
|
|
4175
3745
|
} else {
|
|
4176
3746
|
const recentSessions = context.sessions.slice(0, 3);
|
|
4177
3747
|
for (const session of recentSessions) {
|
|
4178
|
-
console.log(` ${
|
|
3748
|
+
console.log(` ${chalk7.dim(session.date)} ${session.agent}`);
|
|
4179
3749
|
console.log(` ${session.task}`);
|
|
4180
3750
|
if (session.changes.length > 0) {
|
|
4181
|
-
console.log(
|
|
3751
|
+
console.log(chalk7.dim(` ${session.changes.length} change(s)`));
|
|
4182
3752
|
}
|
|
4183
3753
|
}
|
|
4184
3754
|
if (context.sessions.length > 3) {
|
|
4185
|
-
console.log(
|
|
3755
|
+
console.log(chalk7.dim(` ... and ${context.sessions.length - 3} more sessions`));
|
|
4186
3756
|
}
|
|
4187
3757
|
}
|
|
4188
3758
|
console.log();
|
|
4189
|
-
console.log(
|
|
4190
|
-
console.log(
|
|
3759
|
+
console.log(chalk7.dim(`Context file: ${contextPath}`));
|
|
3760
|
+
console.log(chalk7.dim("Use --edit to open in editor, --refresh to update from server"));
|
|
4191
3761
|
}
|
|
4192
3762
|
|
|
4193
3763
|
// src/commands/sync-config.ts
|
|
4194
|
-
import
|
|
4195
|
-
import
|
|
4196
|
-
import { existsSync as
|
|
4197
|
-
import { join as
|
|
3764
|
+
import chalk8 from "chalk";
|
|
3765
|
+
import ora7 from "ora";
|
|
3766
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
3767
|
+
import { join as join9 } from "path";
|
|
4198
3768
|
async function syncConfigCommand() {
|
|
4199
3769
|
const cwd = process.cwd();
|
|
4200
3770
|
const projectRoot = findProjectRoot(cwd);
|
|
4201
3771
|
if (!projectRoot) {
|
|
4202
|
-
console.log(
|
|
4203
|
-
console.log(
|
|
3772
|
+
console.log(chalk8.red(" Feil: Cure Kode er ikke initialisert i denne mappen."));
|
|
3773
|
+
console.log(chalk8.dim(" Kj\xF8r ") + chalk8.cyan("kode init") + chalk8.dim(" f\xF8rst."));
|
|
4204
3774
|
return;
|
|
4205
3775
|
}
|
|
4206
3776
|
const config = getProjectConfig(projectRoot);
|
|
4207
3777
|
if (!config) {
|
|
4208
|
-
console.log(
|
|
3778
|
+
console.log(chalk8.red(" Feil: Kunne ikke lese prosjektkonfigurasjon."));
|
|
4209
3779
|
return;
|
|
4210
3780
|
}
|
|
4211
3781
|
if (!config.projectId) {
|
|
4212
|
-
console.log(
|
|
4213
|
-
console.log(
|
|
3782
|
+
console.log(chalk8.yellow(" Nettstedet er ikke koblet til et prosjekt."));
|
|
3783
|
+
console.log(chalk8.dim(' Koble til via Curie: "Koble denne Kode-siten til prosjekt X"'));
|
|
4214
3784
|
return;
|
|
4215
3785
|
}
|
|
4216
|
-
const spinner =
|
|
3786
|
+
const spinner = ora7("Henter prosjektkonfigurasjon...").start();
|
|
4217
3787
|
try {
|
|
4218
3788
|
const response = await fetch(`https://app.cure.no/api/cdn/sites/${config.siteId}/project`, {
|
|
4219
3789
|
headers: { "X-API-Key": config.apiKey }
|
|
4220
3790
|
});
|
|
4221
3791
|
if (!response.ok) {
|
|
4222
3792
|
spinner.fail("Kunne ikke hente prosjektkonfigurasjon");
|
|
4223
|
-
console.log(
|
|
3793
|
+
console.log(chalk8.dim(` HTTP ${response.status}: ${response.statusText}`));
|
|
4224
3794
|
return;
|
|
4225
3795
|
}
|
|
4226
3796
|
const projectCtx = await response.json();
|
|
4227
3797
|
spinner.succeed("Prosjektkonfigurasjon hentet");
|
|
4228
|
-
const mcpConfigPath =
|
|
3798
|
+
const mcpConfigPath = join9(projectRoot, ".mcp.json");
|
|
4229
3799
|
let mcpConfig = {};
|
|
4230
|
-
if (
|
|
3800
|
+
if (existsSync9(mcpConfigPath)) {
|
|
4231
3801
|
try {
|
|
4232
|
-
mcpConfig = JSON.parse(
|
|
3802
|
+
mcpConfig = JSON.parse(readFileSync9(mcpConfigPath, "utf-8"));
|
|
4233
3803
|
} catch {
|
|
4234
3804
|
}
|
|
4235
3805
|
}
|
|
@@ -4273,7 +3843,7 @@ async function syncConfigCommand() {
|
|
|
4273
3843
|
added.push(server.name);
|
|
4274
3844
|
}
|
|
4275
3845
|
}
|
|
4276
|
-
|
|
3846
|
+
writeFileSync6(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
4277
3847
|
spinner.start("Oppdaterer dokumentasjon...");
|
|
4278
3848
|
let scripts = [];
|
|
4279
3849
|
let pages = [];
|
|
@@ -4309,43 +3879,36 @@ async function syncConfigCommand() {
|
|
|
4309
3879
|
spinner.succeed("Dokumentasjon oppdatert");
|
|
4310
3880
|
console.log();
|
|
4311
3881
|
if (projectCtx.project) {
|
|
4312
|
-
console.log(
|
|
3882
|
+
console.log(chalk8.bold(" Prosjekt: ") + chalk8.cyan(projectCtx.project.name));
|
|
4313
3883
|
}
|
|
4314
3884
|
if (added.length > 0 || updated.length > 0) {
|
|
4315
|
-
console.log(
|
|
3885
|
+
console.log(chalk8.bold(" MCP-servere:"));
|
|
4316
3886
|
for (const name of added) {
|
|
4317
|
-
console.log(
|
|
3887
|
+
console.log(chalk8.green(` + ${name}`) + chalk8.dim(" (ny)"));
|
|
4318
3888
|
}
|
|
4319
3889
|
for (const name of updated) {
|
|
4320
|
-
console.log(
|
|
3890
|
+
console.log(chalk8.blue(` ~ ${name}`) + chalk8.dim(" (oppdatert)"));
|
|
4321
3891
|
}
|
|
4322
3892
|
} else if (projectCtx.mcp_servers.length === 0) {
|
|
4323
|
-
console.log(
|
|
3893
|
+
console.log(chalk8.dim(" Ingen prosjekt-MCP-servere konfigurert."));
|
|
4324
3894
|
} else {
|
|
4325
|
-
console.log(
|
|
3895
|
+
console.log(chalk8.dim(" Ingen endringer i MCP-servere."));
|
|
4326
3896
|
}
|
|
4327
3897
|
if (secretWarnings.length > 0) {
|
|
4328
3898
|
console.log();
|
|
4329
|
-
console.log(
|
|
3899
|
+
console.log(chalk8.yellow(" \u26A0\uFE0F Hemmeligheter som m\xE5 settes i .mcp.json:"));
|
|
4330
3900
|
for (const warning of secretWarnings) {
|
|
4331
|
-
console.log(
|
|
3901
|
+
console.log(chalk8.dim(` \u2022 ${warning}`));
|
|
4332
3902
|
}
|
|
4333
3903
|
}
|
|
4334
3904
|
console.log();
|
|
4335
3905
|
} catch (error) {
|
|
4336
3906
|
spinner.fail("Sync feilet");
|
|
4337
|
-
console.log(
|
|
3907
|
+
console.log(chalk8.red(` Feil: ${error.message}`));
|
|
4338
3908
|
}
|
|
4339
3909
|
}
|
|
4340
3910
|
|
|
4341
3911
|
export {
|
|
4342
|
-
findProjectRoot,
|
|
4343
|
-
getProjectConfig,
|
|
4344
|
-
saveProjectConfig,
|
|
4345
|
-
getApiUrl,
|
|
4346
|
-
getApiKey,
|
|
4347
|
-
setGlobalConfig,
|
|
4348
|
-
getScriptsDir,
|
|
4349
3912
|
getContextPath,
|
|
4350
3913
|
parseContext,
|
|
4351
3914
|
serializeContext,
|
|
@@ -4356,17 +3919,9 @@ export {
|
|
|
4356
3919
|
updateScriptPurpose,
|
|
4357
3920
|
generateInitialContext,
|
|
4358
3921
|
generateClaudeMd,
|
|
4359
|
-
updateKodeDocs,
|
|
4360
|
-
scriptsToDocsFormat,
|
|
4361
|
-
pagesToInfoFormat,
|
|
4362
|
-
updateClaudeMd,
|
|
4363
3922
|
CLI_VERSION,
|
|
4364
3923
|
showCompactBanner,
|
|
4365
3924
|
initCommand,
|
|
4366
|
-
KodeApiError,
|
|
4367
|
-
KodeApiClient,
|
|
4368
|
-
createApiClient,
|
|
4369
|
-
pullCommand,
|
|
4370
3925
|
pushCommand,
|
|
4371
3926
|
watchCommand,
|
|
4372
3927
|
deployCommand,
|