@curenorway/kode-cli 1.17.1 → 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 +28 -1
- package/dist/{chunk-7EPXFFNR.js → chunk-6RYN72CO.js} +829 -1136
- 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 +552 -12
- package/dist/index.d.ts +111 -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,267 +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
|
-
|
|
1359
|
-
## MCP Tools
|
|
1360
|
-
|
|
1361
|
-
AI agents can use these tools directly:
|
|
1362
|
-
|
|
1363
|
-
- \`kode_list_scripts\` - List all scripts
|
|
1364
|
-
- \`kode_push\` - Upload local files
|
|
1365
|
-
- \`kode_deploy\` / \`kode_promote\` - Deploy to staging/production
|
|
1366
|
-
- \`kode_create_page\` - Create CDN page with URL patterns
|
|
1367
|
-
- \`kode_assign_script_to_page\` - Assign script to page
|
|
1368
|
-
|
|
1369
|
-
## File Structure
|
|
1370
|
-
|
|
1371
|
-
\`\`\`
|
|
1372
|
-
.cure-kode/
|
|
1373
|
-
\u251C\u2500\u2500 config.json # Site configuration (contains API key - gitignored)
|
|
1374
|
-
\u251C\u2500\u2500 scripts.json # Sync state for conflict detection
|
|
1375
|
-
\u251C\u2500\u2500 context.md # Dynamic context for AI agents
|
|
1376
|
-
\u2514\u2500\u2500 KODE.md # This file - auto-generated docs
|
|
1377
|
-
|
|
1378
|
-
.cure-kode-scripts/ # Your script files
|
|
1379
|
-
\u251C\u2500\u2500 main.js
|
|
1380
|
-
\u251C\u2500\u2500 form-handler.js
|
|
1381
|
-
\u2514\u2500\u2500 ...
|
|
1382
|
-
|
|
1383
|
-
.claude/skills/ # Claude Code skills
|
|
1384
|
-
\u251C\u2500\u2500 deploy/ # /deploy \u2014 push and deploy workflow
|
|
1385
|
-
\u2514\u2500\u2500 webflow-patterns/ # Auto-loaded Webflow DOM reference
|
|
1386
|
-
\`\`\`
|
|
1387
|
-
`;
|
|
1388
|
-
return md;
|
|
1389
|
-
}
|
|
1390
|
-
function ensureClaudeMdReference(projectRoot) {
|
|
1391
|
-
const claudeMdPath = join3(projectRoot, "CLAUDE.md");
|
|
1392
|
-
if (!existsSync3(claudeMdPath)) {
|
|
1393
|
-
writeFileSync3(claudeMdPath, KODE_REFERENCE);
|
|
1394
|
-
return { created: true, updated: false, skipped: false };
|
|
1395
|
-
}
|
|
1396
|
-
const content = readFileSync3(claudeMdPath, "utf-8");
|
|
1397
|
-
if (hasKodeReference(content)) {
|
|
1398
|
-
return { created: false, updated: false, skipped: true };
|
|
1399
|
-
}
|
|
1400
|
-
const newContent = KODE_REFERENCE + content;
|
|
1401
|
-
writeFileSync3(claudeMdPath, newContent);
|
|
1402
|
-
return { created: false, updated: true, skipped: false };
|
|
1403
|
-
}
|
|
1404
|
-
function updateKodeMd(projectRoot, siteName, siteSlug, scripts, pages, options) {
|
|
1405
|
-
const kodeMdPath = join3(projectRoot, ".cure-kode", "KODE.md");
|
|
1406
|
-
const existed = existsSync3(kodeMdPath);
|
|
1407
|
-
const content = generateKodeMd(siteName, siteSlug, scripts, pages, options);
|
|
1408
|
-
writeFileSync3(kodeMdPath, content);
|
|
1409
|
-
return { created: !existed, updated: existed };
|
|
1410
|
-
}
|
|
1411
|
-
function updateKodeDocs(projectRoot, siteName, siteSlug, scripts, pages, options) {
|
|
1412
|
-
const kodeMd = updateKodeMd(projectRoot, siteName, siteSlug, scripts, pages, options);
|
|
1413
|
-
const claudeMd = ensureClaudeMdReference(projectRoot);
|
|
1414
|
-
return { kodeMd, claudeMd };
|
|
1415
|
-
}
|
|
1416
|
-
function scriptsToDocsFormat(scripts, pages) {
|
|
1417
|
-
return scripts.map((s) => {
|
|
1418
|
-
const pageAssignments = (s.pages || []).filter((p) => p.is_enabled).map((p) => {
|
|
1419
|
-
const page = pages?.find((pg) => pg.id === p.page_id);
|
|
1420
|
-
return page ? { pageId: page.id, pageSlug: page.slug, pageName: page.name } : null;
|
|
1421
|
-
}).filter((p) => p !== null);
|
|
1422
|
-
return {
|
|
1423
|
-
slug: s.slug,
|
|
1424
|
-
name: s.name,
|
|
1425
|
-
type: s.type,
|
|
1426
|
-
scope: s.scope,
|
|
1427
|
-
autoLoad: s.auto_load,
|
|
1428
|
-
purpose: s.metadata?.purpose || s.description,
|
|
1429
|
-
pageAssignments: pageAssignments.length > 0 ? pageAssignments : void 0
|
|
1430
|
-
};
|
|
1431
|
-
});
|
|
1432
|
-
}
|
|
1433
|
-
function pagesToInfoFormat(pages) {
|
|
1434
|
-
return pages.filter((p) => p.is_active).map((p) => ({
|
|
1435
|
-
name: p.name,
|
|
1436
|
-
slug: p.slug,
|
|
1437
|
-
patterns: p.url_patterns
|
|
1438
|
-
}));
|
|
1439
|
-
}
|
|
1440
|
-
var updateClaudeMd = updateKodeDocs;
|
|
1441
1170
|
|
|
1442
1171
|
// src/lib/skills.ts
|
|
1443
|
-
import { existsSync as
|
|
1444
|
-
import { join as
|
|
1172
|
+
import { existsSync as existsSync2, mkdirSync, writeFileSync as writeFileSync2 } from "fs";
|
|
1173
|
+
import { join as join2 } from "path";
|
|
1445
1174
|
function generateSkills(projectRoot, siteSlug) {
|
|
1446
|
-
const skillsDir =
|
|
1175
|
+
const skillsDir = join2(projectRoot, ".claude", "skills");
|
|
1447
1176
|
const created = [];
|
|
1448
1177
|
const skipped = [];
|
|
1449
|
-
const deployDir =
|
|
1450
|
-
const deployPath =
|
|
1451
|
-
if (!
|
|
1452
|
-
|
|
1453
|
-
|
|
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));
|
|
1454
1183
|
created.push("deploy");
|
|
1455
1184
|
} else {
|
|
1456
1185
|
skipped.push("deploy");
|
|
1457
1186
|
}
|
|
1458
|
-
const
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
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);
|
|
1192
|
+
created.push("library");
|
|
1193
|
+
} else {
|
|
1194
|
+
skipped.push("library");
|
|
1195
|
+
}
|
|
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);
|
|
1465
1203
|
created.push("webflow-patterns");
|
|
1466
1204
|
} else {
|
|
1467
1205
|
skipped.push("webflow-patterns");
|
|
@@ -1565,6 +1303,85 @@ See [reference.md](reference.md) for:
|
|
|
1565
1303
|
- Animation and interaction scripting
|
|
1566
1304
|
- Cure Kode script loading and execution order
|
|
1567
1305
|
`;
|
|
1306
|
+
var LIBRARY_SKILL = `---
|
|
1307
|
+
name: library
|
|
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.
|
|
1309
|
+
user-invocable: false
|
|
1310
|
+
---
|
|
1311
|
+
|
|
1312
|
+
# Cure Kode Library (Bibliotek)
|
|
1313
|
+
|
|
1314
|
+
Before writing common JavaScript/CSS patterns, check if the global library already has a reusable snippet.
|
|
1315
|
+
|
|
1316
|
+
## Workflow
|
|
1317
|
+
|
|
1318
|
+
### 1. Browse available snippets
|
|
1319
|
+
|
|
1320
|
+
\`\`\`
|
|
1321
|
+
kode library list # Flat list grouped by category
|
|
1322
|
+
kode library --tree # Folder tree view
|
|
1323
|
+
\`\`\`
|
|
1324
|
+
|
|
1325
|
+
Or search for a specific pattern:
|
|
1326
|
+
|
|
1327
|
+
\`\`\`
|
|
1328
|
+
kode library search "scroll"
|
|
1329
|
+
\`\`\`
|
|
1330
|
+
|
|
1331
|
+
Via MCP: use \`kode_library_list\` (with category/folder filter) or \`kode_library_get\`.
|
|
1332
|
+
|
|
1333
|
+
### 2. Copy and adapt
|
|
1334
|
+
|
|
1335
|
+
**Direct copy** (for utilities that don't need changes):
|
|
1336
|
+
\`\`\`
|
|
1337
|
+
kode library add debounce-throttle
|
|
1338
|
+
kode push
|
|
1339
|
+
\`\`\`
|
|
1340
|
+
|
|
1341
|
+
**Copy-and-adapt** (for patterns that need project-specific selectors):
|
|
1342
|
+
1. Read the snippet: \`kode library search gsap\`
|
|
1343
|
+
2. Copy it: \`kode library add gsap-scroll-reveal\`
|
|
1344
|
+
3. Edit the local file \u2014 update CSS selectors, classes, timing
|
|
1345
|
+
4. Push: \`kode push\`
|
|
1346
|
+
|
|
1347
|
+
### 3. Contribute back
|
|
1348
|
+
|
|
1349
|
+
If you improve a library snippet or create a reusable pattern:
|
|
1350
|
+
\`\`\`
|
|
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
|
|
1367
|
+
\`\`\`
|
|
1368
|
+
|
|
1369
|
+
## Categories
|
|
1370
|
+
|
|
1371
|
+
| Category | Content |
|
|
1372
|
+
|----------|---------|
|
|
1373
|
+
| \`animations\` | GSAP scroll reveals, smooth scrolling, parallax, counters |
|
|
1374
|
+
| \`forms\` | Form validation, multi-step forms, Webflow form intercept |
|
|
1375
|
+
| \`utilities\` | Debounce/throttle, viewport detection, cookie helpers |
|
|
1376
|
+
| \`tracking\` | Event tracking, scroll depth, click maps |
|
|
1377
|
+
| \`integrations\` | Third-party widget loaders, API connectors |
|
|
1378
|
+
|
|
1379
|
+
## Local reference files
|
|
1380
|
+
|
|
1381
|
+
Library snippets are downloaded to \`.cure-kode-scripts/library/\` during \`kode library pull\`.
|
|
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).
|
|
1384
|
+
`;
|
|
1568
1385
|
var WEBFLOW_REFERENCE = `# Webflow Patterns \u2014 Detailed Reference
|
|
1569
1386
|
|
|
1570
1387
|
## CMS Collection Lists
|
|
@@ -1897,26 +1714,154 @@ async function initCommand(options) {
|
|
|
1897
1714
|
apiKey,
|
|
1898
1715
|
scriptsDir: DEFAULT_SCRIPTS_DIR,
|
|
1899
1716
|
environment: "staging",
|
|
1717
|
+
...options.dev && { apiUrl: "http://localhost:3000" },
|
|
1900
1718
|
...projectCtx?.project && { projectId: projectCtx.project.id }
|
|
1901
1719
|
};
|
|
1902
1720
|
spinner.start("Oppretter prosjektstruktur...");
|
|
1903
1721
|
saveProjectConfig(config, cwd);
|
|
1904
|
-
const scriptsPath =
|
|
1905
|
-
if (!
|
|
1906
|
-
|
|
1722
|
+
const scriptsPath = join3(cwd, DEFAULT_SCRIPTS_DIR);
|
|
1723
|
+
if (!existsSync3(scriptsPath)) {
|
|
1724
|
+
mkdirSync2(scriptsPath, { recursive: true });
|
|
1907
1725
|
}
|
|
1908
|
-
const gitignorePath =
|
|
1726
|
+
const gitignorePath = join3(cwd, ".cure-kode", ".gitignore");
|
|
1909
1727
|
const gitignoreContent = `# Hold config.json privat - inneholder API-n\xF8kkel
|
|
1910
1728
|
config.json
|
|
1911
1729
|
`;
|
|
1912
|
-
|
|
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
|
+
}
|
|
1913
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
|
+
}
|
|
1914
1859
|
spinner.start("Konfigurerer MCP-servere...");
|
|
1915
|
-
const mcpConfigPath =
|
|
1860
|
+
const mcpConfigPath = join3(cwd, ".mcp.json");
|
|
1916
1861
|
let mcpConfig = {};
|
|
1917
|
-
if (
|
|
1862
|
+
if (existsSync3(mcpConfigPath)) {
|
|
1918
1863
|
try {
|
|
1919
|
-
mcpConfig = JSON.parse(
|
|
1864
|
+
mcpConfig = JSON.parse(readFileSync3(mcpConfigPath, "utf-8"));
|
|
1920
1865
|
} catch {
|
|
1921
1866
|
}
|
|
1922
1867
|
}
|
|
@@ -1965,7 +1910,7 @@ config.json
|
|
|
1965
1910
|
mcpConfig.mcpServers[server.name] = serverConfig;
|
|
1966
1911
|
}
|
|
1967
1912
|
}
|
|
1968
|
-
|
|
1913
|
+
writeFileSync3(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
1969
1914
|
spinner.succeed("MCP-servere konfigurert");
|
|
1970
1915
|
if (secretWarnings.length > 0) {
|
|
1971
1916
|
console.log();
|
|
@@ -2017,8 +1962,46 @@ config.json
|
|
|
2017
1962
|
production_domain: site.production_domain
|
|
2018
1963
|
};
|
|
2019
1964
|
const contextMdContent = generateInitialContext(config, scripts, siteInfo);
|
|
2020
|
-
|
|
1965
|
+
writeFileSync3(join3(cwd, ".cure-kode", "context.md"), contextMdContent);
|
|
2021
1966
|
spinner.succeed("AI-dokumentasjon generert");
|
|
1967
|
+
spinner.start("Laster ned bibliotek-referanser...");
|
|
1968
|
+
try {
|
|
1969
|
+
const libResponse = await fetch("https://app.cure.no/api/cdn/library", {
|
|
1970
|
+
headers: { "X-API-Key": apiKey }
|
|
1971
|
+
});
|
|
1972
|
+
if (libResponse.ok) {
|
|
1973
|
+
const libSnippets = await libResponse.json();
|
|
1974
|
+
if (libSnippets.length > 0) {
|
|
1975
|
+
const { existsSync: exists, mkdirSync: mkDir, writeFileSync: writeFile } = await import("fs");
|
|
1976
|
+
const { join: joinPath } = await import("path");
|
|
1977
|
+
const libDir = joinPath(scriptsPath, "library");
|
|
1978
|
+
let downloaded = 0;
|
|
1979
|
+
for (const s of libSnippets) {
|
|
1980
|
+
const catDir = joinPath(libDir, s.category || "other");
|
|
1981
|
+
if (!exists(catDir)) mkDir(catDir, { recursive: true });
|
|
1982
|
+
const ext = s.type === "javascript" ? "js" : "css";
|
|
1983
|
+
try {
|
|
1984
|
+
const snippetRes = await fetch(`https://app.cure.no/api/cdn/library/${s.slug}`, {
|
|
1985
|
+
headers: { "X-API-Key": apiKey }
|
|
1986
|
+
});
|
|
1987
|
+
if (snippetRes.ok) {
|
|
1988
|
+
const full = await snippetRes.json();
|
|
1989
|
+
writeFile(joinPath(catDir, `${s.slug}.${ext}`), full.code);
|
|
1990
|
+
downloaded++;
|
|
1991
|
+
}
|
|
1992
|
+
} catch {
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
spinner.succeed(`${downloaded} bibliotek-referanser lastet ned`);
|
|
1996
|
+
} else {
|
|
1997
|
+
spinner.succeed("Biblioteket er tomt");
|
|
1998
|
+
}
|
|
1999
|
+
} else {
|
|
2000
|
+
spinner.succeed("Bibliotek ikke tilgjengelig");
|
|
2001
|
+
}
|
|
2002
|
+
} catch {
|
|
2003
|
+
spinner.succeed("Bibliotek hoppet over");
|
|
2004
|
+
}
|
|
2022
2005
|
spinner.start("Genererer Claude Code skills...");
|
|
2023
2006
|
const skillsResult = generateSkills(cwd, config.siteSlug);
|
|
2024
2007
|
if (skillsResult.created.length > 0) {
|
|
@@ -2026,6 +2009,23 @@ config.json
|
|
|
2026
2009
|
} else {
|
|
2027
2010
|
spinner.succeed("Claude Code skills finnes allerede");
|
|
2028
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
|
+
}
|
|
2029
2029
|
console.log();
|
|
2030
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"));
|
|
2031
2031
|
console.log(chalk.green(" \u2551") + chalk.bold.green(" \u2705 Cure Kode er klar! ") + chalk.green("\u2551"));
|
|
@@ -2041,9 +2041,12 @@ config.json
|
|
|
2041
2041
|
console.log(chalk.dim(" \u251C\u2500\u2500 CLAUDE.md ") + chalk.dim("(uendret - har allerede referanse)"));
|
|
2042
2042
|
}
|
|
2043
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)"));
|
|
2044
2046
|
console.log(chalk.dim(" \u251C\u2500\u2500 .claude/skills/ ") + chalk.green("(Claude Code skills)"));
|
|
2045
2047
|
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 deploy/ (/deploy \u2014 push og deploy)"));
|
|
2046
|
-
console.log(chalk.dim(" \u2502 \
|
|
2048
|
+
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 webflow-patterns/ (Webflow DOM-referanse)"));
|
|
2049
|
+
console.log(chalk.dim(" \u2502 \u2514\u2500\u2500 library/ (Bibliotek-referanse)"));
|
|
2047
2050
|
console.log(chalk.dim(" \u251C\u2500\u2500 .cure-kode/"));
|
|
2048
2051
|
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 config.json (konfigurasjon)"));
|
|
2049
2052
|
console.log(chalk.dim(" \u2502 \u251C\u2500\u2500 KODE.md ") + chalk.green("(Kode-dokumentasjon)"));
|
|
@@ -2073,9 +2076,8 @@ config.json
|
|
|
2073
2076
|
}
|
|
2074
2077
|
console.log(chalk.bold(" Neste steg:"));
|
|
2075
2078
|
console.log();
|
|
2076
|
-
console.log(chalk.cyan(" 1.
|
|
2077
|
-
console.log(chalk.cyan(" 2.") + chalk.dim("
|
|
2078
|
-
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"));
|
|
2079
2081
|
console.log();
|
|
2080
2082
|
console.log(chalk.bold(" MCP-verkt\xF8y tilgjengelig:"));
|
|
2081
2083
|
console.log();
|
|
@@ -2097,470 +2099,70 @@ config.json
|
|
|
2097
2099
|
}
|
|
2098
2100
|
}
|
|
2099
2101
|
|
|
2100
|
-
// src/
|
|
2101
|
-
function isRetryableError(error) {
|
|
2102
|
-
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
2103
|
-
return true;
|
|
2104
|
-
}
|
|
2105
|
-
const err = error;
|
|
2106
|
-
const statusCode = err.statusCode || err.status;
|
|
2107
|
-
if (statusCode && statusCode >= 400 && statusCode < 500) {
|
|
2108
|
-
return false;
|
|
2109
|
-
}
|
|
2110
|
-
if (statusCode && statusCode >= 500) {
|
|
2111
|
-
return true;
|
|
2112
|
-
}
|
|
2113
|
-
if (err.code === "ECONNRESET" || err.code === "ETIMEDOUT" || err.code === "ENOTFOUND") {
|
|
2114
|
-
return true;
|
|
2115
|
-
}
|
|
2116
|
-
if (error instanceof Error) {
|
|
2117
|
-
const message = error.message.toLowerCase();
|
|
2118
|
-
if (message.includes("network") || message.includes("timeout") || message.includes("connection") || message.includes("socket")) {
|
|
2119
|
-
return true;
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
return false;
|
|
2123
|
-
}
|
|
2124
|
-
function calculateDelay(attempt, baseDelayMs, maxDelayMs, backoffMultiplier, jitter) {
|
|
2125
|
-
const exponentialDelay = baseDelayMs * Math.pow(backoffMultiplier, attempt - 1);
|
|
2126
|
-
const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
|
|
2127
|
-
if (!jitter) {
|
|
2128
|
-
return cappedDelay;
|
|
2129
|
-
}
|
|
2130
|
-
const jitterRange = cappedDelay * 0.25;
|
|
2131
|
-
const jitterOffset = (Math.random() - 0.5) * 2 * jitterRange;
|
|
2132
|
-
return Math.max(0, Math.round(cappedDelay + jitterOffset));
|
|
2133
|
-
}
|
|
2134
|
-
function sleep(ms) {
|
|
2135
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2136
|
-
}
|
|
2137
|
-
async function withRetry(fn, options = {}) {
|
|
2138
|
-
const {
|
|
2139
|
-
maxAttempts = 3,
|
|
2140
|
-
baseDelayMs = 500,
|
|
2141
|
-
maxDelayMs = 5e3,
|
|
2142
|
-
backoffMultiplier = 2,
|
|
2143
|
-
jitter = true,
|
|
2144
|
-
isRetryable = isRetryableError,
|
|
2145
|
-
onRetry
|
|
2146
|
-
} = options;
|
|
2147
|
-
let lastError;
|
|
2148
|
-
let totalDelayMs = 0;
|
|
2149
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2150
|
-
try {
|
|
2151
|
-
return await fn();
|
|
2152
|
-
} catch (error) {
|
|
2153
|
-
lastError = error instanceof Error ? error : new Error(String(error));
|
|
2154
|
-
if (attempt >= maxAttempts || !isRetryable(error)) {
|
|
2155
|
-
throw lastError;
|
|
2156
|
-
}
|
|
2157
|
-
const delayMs = calculateDelay(
|
|
2158
|
-
attempt,
|
|
2159
|
-
baseDelayMs,
|
|
2160
|
-
maxDelayMs,
|
|
2161
|
-
backoffMultiplier,
|
|
2162
|
-
jitter
|
|
2163
|
-
);
|
|
2164
|
-
totalDelayMs += delayMs;
|
|
2165
|
-
if (onRetry) {
|
|
2166
|
-
onRetry(attempt, error, delayMs);
|
|
2167
|
-
}
|
|
2168
|
-
await sleep(delayMs);
|
|
2169
|
-
}
|
|
2170
|
-
}
|
|
2171
|
-
throw lastError || new Error("Retry failed");
|
|
2172
|
-
}
|
|
2173
|
-
|
|
2174
|
-
// src/api.ts
|
|
2175
|
-
var KodeApiError = class extends Error {
|
|
2176
|
-
constructor(message, statusCode, response) {
|
|
2177
|
-
super(message);
|
|
2178
|
-
this.statusCode = statusCode;
|
|
2179
|
-
this.response = response;
|
|
2180
|
-
this.name = "KodeApiError";
|
|
2181
|
-
}
|
|
2182
|
-
};
|
|
2183
|
-
var KodeApiClient = class {
|
|
2184
|
-
baseUrl;
|
|
2185
|
-
apiKey;
|
|
2186
|
-
constructor(config) {
|
|
2187
|
-
this.baseUrl = getApiUrl(config);
|
|
2188
|
-
const apiKey = getApiKey(config);
|
|
2189
|
-
if (!apiKey) {
|
|
2190
|
-
throw new Error('API key is required. Run "kode init" first.');
|
|
2191
|
-
}
|
|
2192
|
-
this.apiKey = apiKey;
|
|
2193
|
-
}
|
|
2194
|
-
async request(endpoint, options = {}) {
|
|
2195
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
2196
|
-
return withRetry(
|
|
2197
|
-
async () => {
|
|
2198
|
-
const response = await fetch(url, {
|
|
2199
|
-
...options,
|
|
2200
|
-
headers: {
|
|
2201
|
-
"Content-Type": "application/json",
|
|
2202
|
-
"X-API-Key": this.apiKey,
|
|
2203
|
-
...options.headers
|
|
2204
|
-
}
|
|
2205
|
-
});
|
|
2206
|
-
const data = await response.json();
|
|
2207
|
-
if (!response.ok) {
|
|
2208
|
-
throw new KodeApiError(
|
|
2209
|
-
data.error || `Request failed with status ${response.status}`,
|
|
2210
|
-
response.status,
|
|
2211
|
-
data
|
|
2212
|
-
);
|
|
2213
|
-
}
|
|
2214
|
-
return data;
|
|
2215
|
-
},
|
|
2216
|
-
{
|
|
2217
|
-
maxAttempts: 3,
|
|
2218
|
-
baseDelayMs: 500,
|
|
2219
|
-
// Custom retry check: retry on network errors and 5xx, but not 4xx
|
|
2220
|
-
isRetryable: (error) => {
|
|
2221
|
-
if (error instanceof KodeApiError) {
|
|
2222
|
-
return error.statusCode >= 500;
|
|
2223
|
-
}
|
|
2224
|
-
return isRetryableError(error);
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
);
|
|
2228
|
-
}
|
|
2229
|
-
// Sites
|
|
2230
|
-
async getSite(siteId) {
|
|
2231
|
-
return this.request(`/api/cdn/sites/${siteId}`);
|
|
2232
|
-
}
|
|
2233
|
-
async listSites() {
|
|
2234
|
-
return this.request("/api/cdn/sites");
|
|
2235
|
-
}
|
|
2236
|
-
// Scripts
|
|
2237
|
-
async listScripts(siteId) {
|
|
2238
|
-
return this.request(`/api/cdn/sites/${siteId}/scripts`);
|
|
2239
|
-
}
|
|
2240
|
-
async getScript(scriptId) {
|
|
2241
|
-
return this.request(`/api/cdn/scripts/${scriptId}`);
|
|
2242
|
-
}
|
|
2243
|
-
async createScript(siteId, data) {
|
|
2244
|
-
return this.request(`/api/cdn/sites/${siteId}/scripts`, {
|
|
2245
|
-
method: "POST",
|
|
2246
|
-
body: JSON.stringify(data)
|
|
2247
|
-
});
|
|
2248
|
-
}
|
|
2249
|
-
async updateScript(scriptId, data) {
|
|
2250
|
-
return this.request(`/api/cdn/scripts/${scriptId}`, {
|
|
2251
|
-
method: "PUT",
|
|
2252
|
-
body: JSON.stringify(data)
|
|
2253
|
-
});
|
|
2254
|
-
}
|
|
2255
|
-
async deleteScript(scriptId) {
|
|
2256
|
-
await this.request(`/api/cdn/scripts/${scriptId}`, {
|
|
2257
|
-
method: "DELETE"
|
|
2258
|
-
});
|
|
2259
|
-
}
|
|
2260
|
-
// Upload (alternative endpoint that works with API keys)
|
|
2261
|
-
async uploadScript(data) {
|
|
2262
|
-
return this.request("/api/cdn/upload", {
|
|
2263
|
-
method: "POST",
|
|
2264
|
-
body: JSON.stringify({
|
|
2265
|
-
...data,
|
|
2266
|
-
apiKey: this.apiKey
|
|
2267
|
-
})
|
|
2268
|
-
});
|
|
2269
|
-
}
|
|
2270
|
-
// Pages
|
|
2271
|
-
async listPages(siteId) {
|
|
2272
|
-
return this.request(`/api/cdn/sites/${siteId}/pages`);
|
|
2273
|
-
}
|
|
2274
|
-
async getPage(pageId) {
|
|
2275
|
-
return this.request(`/api/cdn/pages/${pageId}`);
|
|
2276
|
-
}
|
|
2277
|
-
async createPage(siteId, data) {
|
|
2278
|
-
return this.request(`/api/cdn/sites/${siteId}/pages`, {
|
|
2279
|
-
method: "POST",
|
|
2280
|
-
body: JSON.stringify(data)
|
|
2281
|
-
});
|
|
2282
|
-
}
|
|
2283
|
-
async updatePage(pageId, data) {
|
|
2284
|
-
return this.request(`/api/cdn/pages/${pageId}`, {
|
|
2285
|
-
method: "PATCH",
|
|
2286
|
-
body: JSON.stringify(data)
|
|
2287
|
-
});
|
|
2288
|
-
}
|
|
2289
|
-
async deletePage(pageId) {
|
|
2290
|
-
return this.request(`/api/cdn/pages/${pageId}`, {
|
|
2291
|
-
method: "DELETE"
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
async assignScriptToPage(pageId, scriptId, loadOrderOverride) {
|
|
2295
|
-
return this.request(`/api/cdn/pages/${pageId}/scripts`, {
|
|
2296
|
-
method: "POST",
|
|
2297
|
-
body: JSON.stringify({ scriptId, loadOrderOverride })
|
|
2298
|
-
});
|
|
2299
|
-
}
|
|
2300
|
-
async removeScriptFromPage(pageId, scriptId) {
|
|
2301
|
-
return this.request(`/api/cdn/pages/${pageId}/scripts/${scriptId}`, {
|
|
2302
|
-
method: "DELETE"
|
|
2303
|
-
});
|
|
2304
|
-
}
|
|
2305
|
-
async getPageScripts(pageId) {
|
|
2306
|
-
return this.request(`/api/cdn/pages/${pageId}/scripts`);
|
|
2307
|
-
}
|
|
2308
|
-
// Deployments
|
|
2309
|
-
async deploy(siteId, environment = "staging") {
|
|
2310
|
-
return this.request("/api/cdn/deploy", {
|
|
2311
|
-
method: "POST",
|
|
2312
|
-
body: JSON.stringify({
|
|
2313
|
-
siteId,
|
|
2314
|
-
environment,
|
|
2315
|
-
apiKey: this.apiKey
|
|
2316
|
-
})
|
|
2317
|
-
});
|
|
2318
|
-
}
|
|
2319
|
-
async promoteToProduction(siteId, stagingDeploymentId) {
|
|
2320
|
-
return this.request("/api/cdn/deploy/promote", {
|
|
2321
|
-
method: "POST",
|
|
2322
|
-
body: JSON.stringify({
|
|
2323
|
-
siteId,
|
|
2324
|
-
stagingDeploymentId,
|
|
2325
|
-
apiKey: this.apiKey
|
|
2326
|
-
})
|
|
2327
|
-
});
|
|
2328
|
-
}
|
|
2329
|
-
async getDeploymentStatus(siteId) {
|
|
2330
|
-
return this.request(`/api/cdn/sites/${siteId}/deployments/status`);
|
|
2331
|
-
}
|
|
2332
|
-
async rollback(siteId, environment = "staging") {
|
|
2333
|
-
return this.request("/api/cdn/deploy/rollback", {
|
|
2334
|
-
method: "POST",
|
|
2335
|
-
body: JSON.stringify({
|
|
2336
|
-
siteId,
|
|
2337
|
-
environment
|
|
2338
|
-
})
|
|
2339
|
-
});
|
|
2340
|
-
}
|
|
2341
|
-
// Production enabled toggle (v2.3)
|
|
2342
|
-
async setProductionEnabled(siteId, enabled, productionDomain) {
|
|
2343
|
-
return this.request(`/api/cdn/sites/${siteId}/production`, {
|
|
2344
|
-
method: "POST",
|
|
2345
|
-
body: JSON.stringify({
|
|
2346
|
-
enabled,
|
|
2347
|
-
productionDomain
|
|
2348
|
-
})
|
|
2349
|
-
});
|
|
2350
|
-
}
|
|
2351
|
-
// HTML Fetch
|
|
2352
|
-
async fetchHtml(url) {
|
|
2353
|
-
return this.request("/api/cdn/fetch-html", {
|
|
2354
|
-
method: "POST",
|
|
2355
|
-
body: JSON.stringify({ url })
|
|
2356
|
-
});
|
|
2357
|
-
}
|
|
2358
|
-
// Lock management
|
|
2359
|
-
async getLockStatus(siteId) {
|
|
2360
|
-
return this.request(`/api/cdn/deploy/lock?siteId=${siteId}`);
|
|
2361
|
-
}
|
|
2362
|
-
async forceReleaseLock(siteId) {
|
|
2363
|
-
return this.request("/api/cdn/deploy/lock", {
|
|
2364
|
-
method: "DELETE",
|
|
2365
|
-
body: JSON.stringify({ siteId })
|
|
2366
|
-
});
|
|
2367
|
-
}
|
|
2368
|
-
// Webflow Custom Code injection
|
|
2369
|
-
async getWebflowCustomCodeStatus(siteId) {
|
|
2370
|
-
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`);
|
|
2371
|
-
}
|
|
2372
|
-
async injectWebflowCustomCode(siteId, location = "header") {
|
|
2373
|
-
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`, {
|
|
2374
|
-
method: "POST",
|
|
2375
|
-
body: JSON.stringify({ location })
|
|
2376
|
-
});
|
|
2377
|
-
}
|
|
2378
|
-
async removeWebflowCustomCode(siteId) {
|
|
2379
|
-
return this.request(`/api/cdn/sites/${siteId}/webflow/custom-code`, {
|
|
2380
|
-
method: "DELETE"
|
|
2381
|
-
});
|
|
2382
|
-
}
|
|
2383
|
-
};
|
|
2384
|
-
function createApiClient(config) {
|
|
2385
|
-
return new KodeApiClient(config);
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
// src/commands/pull.ts
|
|
2102
|
+
// src/commands/push.ts
|
|
2389
2103
|
import chalk2 from "chalk";
|
|
2390
2104
|
import ora2 from "ora";
|
|
2391
|
-
import {
|
|
2392
|
-
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";
|
|
2393
2107
|
import { createHash } from "crypto";
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
async function pullCommand(options) {
|
|
2398
|
-
const projectRoot = findProjectRoot();
|
|
2399
|
-
if (!projectRoot) {
|
|
2400
|
-
console.log(chalk2.red("Feil: Ikke i et Cure Kode-prosjekt."));
|
|
2401
|
-
console.log(chalk2.dim('Kj\xF8r "kode init" f\xF8rst.'));
|
|
2402
|
-
return;
|
|
2403
|
-
}
|
|
2404
|
-
const config = getProjectConfig(projectRoot);
|
|
2405
|
-
if (!config) {
|
|
2406
|
-
console.log(chalk2.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
|
|
2407
|
-
return;
|
|
2408
|
-
}
|
|
2409
|
-
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) {
|
|
2410
2111
|
try {
|
|
2411
|
-
const
|
|
2412
|
-
const
|
|
2413
|
-
if (
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
}
|
|
2423
|
-
const metadataPath = join6(projectRoot, ".cure-kode", "scripts.json");
|
|
2424
|
-
let existingMetadata = [];
|
|
2425
|
-
if (existsSync6(metadataPath)) {
|
|
2426
|
-
try {
|
|
2427
|
-
existingMetadata = JSON.parse(readFileSync6(metadataPath, "utf-8"));
|
|
2428
|
-
} catch {
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
const scriptsToPull = options.script ? scripts.filter((s) => s.slug === options.script || s.name === options.script) : scripts;
|
|
2432
|
-
if (options.script && scriptsToPull.length === 0) {
|
|
2433
|
-
console.log(chalk2.yellow(`
|
|
2434
|
-
Fant ikke skript "${options.script}".`));
|
|
2435
|
-
console.log(chalk2.dim("Tilgjengelige skript:"));
|
|
2436
|
-
scripts.forEach((s) => {
|
|
2437
|
-
console.log(chalk2.dim(` - ${s.slug} (${s.name})`));
|
|
2438
|
-
});
|
|
2439
|
-
return;
|
|
2440
|
-
}
|
|
2441
|
-
console.log();
|
|
2442
|
-
let pulled = 0;
|
|
2443
|
-
let skipped = 0;
|
|
2444
|
-
let updated = 0;
|
|
2445
|
-
for (const script of scriptsToPull) {
|
|
2446
|
-
const ext = script.type === "javascript" ? "js" : "css";
|
|
2447
|
-
const fileName = `${script.slug}.${ext}`;
|
|
2448
|
-
const filePath = join6(scriptsDir, fileName);
|
|
2449
|
-
const existingMeta = existingMetadata.find((m) => m.slug === script.slug);
|
|
2450
|
-
const lastPulledVersion = existingMeta?.lastPulledVersion || 0;
|
|
2451
|
-
if (existsSync6(filePath) && !options.force) {
|
|
2452
|
-
const localContent = readFileSync6(filePath, "utf-8");
|
|
2453
|
-
const localHash = hashContent(localContent);
|
|
2454
|
-
const hasLocalChanges = existingMeta && localHash !== existingMeta.contentHash;
|
|
2455
|
-
if (hasLocalChanges) {
|
|
2456
|
-
if (script.current_version > lastPulledVersion) {
|
|
2457
|
-
console.log(
|
|
2458
|
-
chalk2.yellow(` \u26A0 ${fileName}`) + chalk2.dim(` (lokal v${lastPulledVersion} \u2192 server v${script.current_version}, konflikt)`)
|
|
2459
|
-
);
|
|
2460
|
-
console.log(chalk2.dim(` Bruk --force for \xE5 overskrive lokale endringer`));
|
|
2461
|
-
} else {
|
|
2462
|
-
console.log(
|
|
2463
|
-
chalk2.yellow(` \u26A0 ${fileName}`) + chalk2.dim(" (lokale endringer, bruk --force)")
|
|
2464
|
-
);
|
|
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;
|
|
2465
2123
|
}
|
|
2466
|
-
skipped++;
|
|
2467
|
-
continue;
|
|
2468
|
-
}
|
|
2469
|
-
if (script.content === localContent) {
|
|
2470
|
-
console.log(chalk2.dim(` \u25CB ${fileName} (ingen endringer)`));
|
|
2471
|
-
skipped++;
|
|
2472
|
-
continue;
|
|
2473
2124
|
}
|
|
2474
2125
|
}
|
|
2475
|
-
writeFileSync6(filePath, script.content);
|
|
2476
|
-
const scopeTag = script.scope === "global" ? chalk2.blue("[G]") : chalk2.magenta("[P]");
|
|
2477
|
-
const loadTag = script.auto_load ? chalk2.green("\u26A1") : chalk2.dim("\u25CB");
|
|
2478
|
-
if (lastPulledVersion > 0 && script.current_version > lastPulledVersion) {
|
|
2479
|
-
console.log(
|
|
2480
|
-
chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${lastPulledVersion} \u2192 v${script.current_version}`) + ` ${scopeTag} ${loadTag}`
|
|
2481
|
-
);
|
|
2482
|
-
updated++;
|
|
2483
|
-
} else {
|
|
2484
|
-
console.log(
|
|
2485
|
-
chalk2.green(` \u2713 ${fileName}`) + chalk2.dim(` v${script.current_version}`) + ` ${scopeTag} ${loadTag}`
|
|
2486
|
-
);
|
|
2487
|
-
pulled++;
|
|
2488
|
-
}
|
|
2489
|
-
}
|
|
2490
|
-
console.log();
|
|
2491
|
-
if (pulled > 0) {
|
|
2492
|
-
console.log(chalk2.green(`Hentet ${pulled} skript til ${config.scriptsDir}/`));
|
|
2493
|
-
}
|
|
2494
|
-
if (updated > 0) {
|
|
2495
|
-
console.log(chalk2.cyan(`Oppdatert ${updated} skript med nye versjoner`));
|
|
2496
|
-
}
|
|
2497
|
-
if (skipped > 0) {
|
|
2498
|
-
console.log(chalk2.dim(`Hoppet over ${skipped} skript`));
|
|
2499
2126
|
}
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
autoLoad: s.auto_load,
|
|
2520
|
-
version: s.current_version,
|
|
2521
|
-
loadOrder: s.load_order,
|
|
2522
|
-
pageAssignments: pageAssignments.length > 0 ? pageAssignments : void 0,
|
|
2523
|
-
lastPulledVersion: s.current_version,
|
|
2524
|
-
lastPulledAt: now,
|
|
2525
|
-
contentHash: hashContent(localContent)
|
|
2526
|
-
};
|
|
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"
|
|
2527
2146
|
});
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
console.log(chalk2.green("Opprettet CLAUDE.md med referanse"));
|
|
2544
|
-
} else if (result.claudeMd.updated) {
|
|
2545
|
-
console.log(chalk2.dim("La til Kode-referanse i CLAUDE.md"));
|
|
2546
|
-
}
|
|
2547
|
-
} catch {
|
|
2548
|
-
}
|
|
2549
|
-
console.log(chalk2.dim("\n[G]=Global [P]=Sidespesifikk \u26A1=Auto-last \u25CB=Manuell"));
|
|
2550
|
-
} catch (error) {
|
|
2551
|
-
spinner.fail("Kunne ikke hente skript");
|
|
2552
|
-
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) };
|
|
2553
2162
|
}
|
|
2554
2163
|
}
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
import chalk3 from "chalk";
|
|
2558
|
-
import ora3 from "ora";
|
|
2559
|
-
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync7, readdirSync } from "fs";
|
|
2560
|
-
import { join as join7, basename, extname } from "path";
|
|
2561
|
-
import { createHash as createHash2 } from "crypto";
|
|
2562
|
-
function hashContent2(content) {
|
|
2563
|
-
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);
|
|
2564
2166
|
}
|
|
2565
2167
|
function formatTimeDiff(date) {
|
|
2566
2168
|
const diff = Date.now() - new Date(date).getTime();
|
|
@@ -2575,50 +2177,67 @@ function formatTimeDiff(date) {
|
|
|
2575
2177
|
async function pushCommand(options) {
|
|
2576
2178
|
const projectRoot = findProjectRoot();
|
|
2577
2179
|
if (!projectRoot) {
|
|
2578
|
-
console.log(
|
|
2579
|
-
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.'));
|
|
2580
2182
|
return;
|
|
2581
2183
|
}
|
|
2582
2184
|
const config = getProjectConfig(projectRoot);
|
|
2583
2185
|
if (!config) {
|
|
2584
|
-
console.log(
|
|
2186
|
+
console.log(chalk2.red("Feil: Kunne ikke lese prosjektkonfigurasjon."));
|
|
2585
2187
|
return;
|
|
2586
2188
|
}
|
|
2587
2189
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2588
|
-
if (!
|
|
2589
|
-
console.log(
|
|
2590
|
-
console.log(
|
|
2591
|
-
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.'));
|
|
2592
2194
|
return;
|
|
2593
2195
|
}
|
|
2594
|
-
const metadataPath =
|
|
2196
|
+
const metadataPath = join4(projectRoot, ".cure-kode", "scripts.json");
|
|
2595
2197
|
let metadata = [];
|
|
2596
|
-
if (
|
|
2198
|
+
if (existsSync4(metadataPath)) {
|
|
2597
2199
|
try {
|
|
2598
|
-
metadata = JSON.parse(
|
|
2200
|
+
metadata = JSON.parse(readFileSync4(metadataPath, "utf-8"));
|
|
2599
2201
|
} catch {
|
|
2600
2202
|
}
|
|
2601
2203
|
}
|
|
2204
|
+
const SUPPORTED_EXTENSIONS = [".js", ".css", ".ts", ".tsx", ".jsx"];
|
|
2602
2205
|
const files = readdirSync(scriptsDir).filter(
|
|
2603
|
-
(f) =>
|
|
2206
|
+
(f) => SUPPORTED_EXTENSIONS.some((ext) => f.endsWith(ext))
|
|
2604
2207
|
);
|
|
2605
2208
|
if (files.length === 0) {
|
|
2606
|
-
console.log(
|
|
2607
|
-
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}/`));
|
|
2608
2211
|
return;
|
|
2609
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
|
+
}
|
|
2610
2229
|
const filesToPush = options.script ? files.filter(
|
|
2611
|
-
(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
|
|
2612
2231
|
) : files;
|
|
2613
2232
|
if (options.script && filesToPush.length === 0) {
|
|
2614
|
-
console.log(
|
|
2615
|
-
console.log(
|
|
2233
|
+
console.log(chalk2.yellow(`Fant ikke skript "${options.script}" lokalt.`));
|
|
2234
|
+
console.log(chalk2.dim("Tilgjengelige lokale skript:"));
|
|
2616
2235
|
files.forEach((f) => {
|
|
2617
|
-
console.log(
|
|
2236
|
+
console.log(chalk2.dim(` - ${f}`));
|
|
2618
2237
|
});
|
|
2619
2238
|
return;
|
|
2620
2239
|
}
|
|
2621
|
-
const spinner =
|
|
2240
|
+
const spinner = ora2("Laster opp skript...").start();
|
|
2622
2241
|
try {
|
|
2623
2242
|
const client = createApiClient(config);
|
|
2624
2243
|
const remoteScripts = await client.listScripts(config.siteId);
|
|
@@ -2630,46 +2249,74 @@ async function pushCommand(options) {
|
|
|
2630
2249
|
console.log();
|
|
2631
2250
|
let emptyScriptCount = 0;
|
|
2632
2251
|
for (const file of filesToPush) {
|
|
2633
|
-
const filePath =
|
|
2634
|
-
const
|
|
2252
|
+
const filePath = join4(scriptsDir, file);
|
|
2253
|
+
const sourceContent = readFileSync4(filePath, "utf-8");
|
|
2254
|
+
let content = sourceContent;
|
|
2635
2255
|
const slug = basename(file, extname(file));
|
|
2636
|
-
const type = extname(file) === ".
|
|
2637
|
-
|
|
2638
|
-
console.log(chalk3.yellow(` \u26A0 ${file}`) + chalk3.dim(" (tom fil)"));
|
|
2639
|
-
emptyScriptCount++;
|
|
2640
|
-
}
|
|
2256
|
+
const type = extname(file) === ".css" ? "css" : "javascript";
|
|
2257
|
+
const needsBundle = BUNDLEABLE_EXTENSIONS.includes(extname(file));
|
|
2641
2258
|
const remoteScript = remoteScripts.find((s) => s.slug === slug);
|
|
2642
|
-
const localMeta = metadata.find((m) => m.slug === slug);
|
|
2643
|
-
|
|
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) {
|
|
2644
2268
|
const lastPulledVersion = localMeta?.lastPulledVersion || 0;
|
|
2645
|
-
if (remoteScript.current_version > lastPulledVersion
|
|
2269
|
+
if (remoteScript.current_version > lastPulledVersion) {
|
|
2646
2270
|
console.log();
|
|
2647
|
-
console.log(
|
|
2648
|
-
console.log(
|
|
2649
|
-
console.log(
|
|
2650
|
-
console.log(
|
|
2651
|
-
console.log();
|
|
2652
|
-
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.`));
|
|
2653
2275
|
console.log();
|
|
2654
2276
|
conflicts++;
|
|
2655
2277
|
continue;
|
|
2656
2278
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
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}`));
|
|
2660
2287
|
continue;
|
|
2661
2288
|
}
|
|
2662
|
-
|
|
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();
|
|
2663
2304
|
await client.updateScript(remoteScript.id, {
|
|
2664
2305
|
content,
|
|
2665
|
-
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
|
+
} : {}
|
|
2666
2312
|
});
|
|
2667
2313
|
updateSpinner.succeed(
|
|
2668
|
-
|
|
2314
|
+
chalk2.green(` \u2713 ${file}`) + chalk2.dim(` v${remoteScript.current_version + 1}`) + suffix
|
|
2669
2315
|
);
|
|
2670
2316
|
pushed++;
|
|
2671
2317
|
} else {
|
|
2672
|
-
const
|
|
2318
|
+
const sourceExt = needsBundle ? extname(file).slice(1) : void 0;
|
|
2319
|
+
const createSpinner = ora2(chalk2.dim(` + ${file}`)).start();
|
|
2673
2320
|
const scriptScope = localMeta?.scope || "global";
|
|
2674
2321
|
const newScript = await client.createScript(config.siteId, {
|
|
2675
2322
|
name: slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, " "),
|
|
@@ -2677,33 +2324,37 @@ async function pushCommand(options) {
|
|
|
2677
2324
|
type,
|
|
2678
2325
|
scope: scriptScope,
|
|
2679
2326
|
autoLoad: options.autoLoad,
|
|
2680
|
-
content
|
|
2327
|
+
content,
|
|
2328
|
+
...needsBundle ? {
|
|
2329
|
+
sourceContent,
|
|
2330
|
+
sourceType: sourceExt
|
|
2331
|
+
} : {}
|
|
2681
2332
|
});
|
|
2682
|
-
const autoLoadInfo = newScript.auto_load ?
|
|
2333
|
+
const autoLoadInfo = newScript.auto_load ? chalk2.dim("auto") : chalk2.dim("manuell");
|
|
2683
2334
|
createSpinner.succeed(
|
|
2684
|
-
|
|
2335
|
+
chalk2.green(` \u2713 ${file}`) + chalk2.cyan(" ny") + chalk2.dim(` [${autoLoadInfo}]`) + suffix
|
|
2685
2336
|
);
|
|
2686
2337
|
created++;
|
|
2687
2338
|
}
|
|
2688
2339
|
}
|
|
2689
2340
|
console.log();
|
|
2690
2341
|
if (pushed > 0) {
|
|
2691
|
-
console.log(
|
|
2342
|
+
console.log(chalk2.green(`Oppdatert ${pushed} skript`));
|
|
2692
2343
|
}
|
|
2693
2344
|
if (created > 0) {
|
|
2694
|
-
console.log(
|
|
2345
|
+
console.log(chalk2.cyan(`Opprettet ${created} nye skript`));
|
|
2695
2346
|
}
|
|
2696
2347
|
if (skipped > 0) {
|
|
2697
|
-
console.log(
|
|
2348
|
+
console.log(chalk2.dim(`Hoppet over ${skipped} uendrede skript`));
|
|
2698
2349
|
}
|
|
2699
2350
|
if (conflicts > 0) {
|
|
2700
|
-
console.log(
|
|
2351
|
+
console.log(chalk2.yellow(`
|
|
2701
2352
|
${conflicts} skript med konflikter (bruk --force for \xE5 overskrive)`));
|
|
2702
2353
|
}
|
|
2703
2354
|
if (emptyScriptCount > 0) {
|
|
2704
|
-
console.log(
|
|
2355
|
+
console.log(chalk2.yellow(`
|
|
2705
2356
|
${emptyScriptCount} tomme skript lastet opp`));
|
|
2706
|
-
console.log(
|
|
2357
|
+
console.log(chalk2.dim("Tomme skript har ingen effekt ved deploy."));
|
|
2707
2358
|
}
|
|
2708
2359
|
let pages = [];
|
|
2709
2360
|
try {
|
|
@@ -2713,9 +2364,17 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2713
2364
|
const updatedScripts = await client.listScripts(config.siteId);
|
|
2714
2365
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2715
2366
|
const updatedMetadata = updatedScripts.map((s) => {
|
|
2716
|
-
const
|
|
2717
|
-
|
|
2718
|
-
|
|
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
|
+
}
|
|
2719
2378
|
const pageAssignments = (s.pages || []).filter((p) => p.is_enabled).map((p) => {
|
|
2720
2379
|
const page = pages.find((pg) => pg.id === p.page_id);
|
|
2721
2380
|
return page ? { pageId: page.id, pageSlug: page.slug, pageName: page.name } : null;
|
|
@@ -2732,10 +2391,11 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2732
2391
|
pageAssignments: pageAssignments.length > 0 ? pageAssignments : void 0,
|
|
2733
2392
|
lastPulledVersion: s.current_version,
|
|
2734
2393
|
lastPulledAt: now,
|
|
2735
|
-
contentHash:
|
|
2394
|
+
contentHash: hashContent(localContent),
|
|
2395
|
+
localFileName: localFilePath ? basename(localFilePath) : void 0
|
|
2736
2396
|
};
|
|
2737
2397
|
});
|
|
2738
|
-
|
|
2398
|
+
writeFileSync4(metadataPath, JSON.stringify(updatedMetadata, null, 2));
|
|
2739
2399
|
try {
|
|
2740
2400
|
const result = updateClaudeMd(
|
|
2741
2401
|
projectRoot,
|
|
@@ -2745,69 +2405,117 @@ ${emptyScriptCount} tomme skript lastet opp`));
|
|
|
2745
2405
|
pagesToInfoFormat(pages)
|
|
2746
2406
|
);
|
|
2747
2407
|
if (result.kodeMd.created) {
|
|
2748
|
-
console.log(
|
|
2408
|
+
console.log(chalk2.green("Opprettet .cure-kode/KODE.md"));
|
|
2749
2409
|
} else if (result.kodeMd.updated) {
|
|
2750
|
-
console.log(
|
|
2410
|
+
console.log(chalk2.dim("Oppdatert .cure-kode/KODE.md"));
|
|
2751
2411
|
}
|
|
2752
2412
|
if (result.claudeMd.created) {
|
|
2753
|
-
console.log(
|
|
2413
|
+
console.log(chalk2.green("Opprettet CLAUDE.md med referanse"));
|
|
2754
2414
|
} else if (result.claudeMd.updated) {
|
|
2755
|
-
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
|
+
}
|
|
2756
2464
|
}
|
|
2757
2465
|
} catch {
|
|
2758
2466
|
}
|
|
2759
2467
|
} catch (error) {
|
|
2760
2468
|
spinner.fail("Kunne ikke laste opp skript");
|
|
2761
|
-
console.error(
|
|
2469
|
+
console.error(chalk2.red("\nFeil:"), error);
|
|
2762
2470
|
}
|
|
2763
2471
|
}
|
|
2764
2472
|
|
|
2765
2473
|
// src/commands/watch.ts
|
|
2766
|
-
import
|
|
2474
|
+
import chalk3 from "chalk";
|
|
2767
2475
|
import chokidar from "chokidar";
|
|
2768
|
-
import { readFileSync as
|
|
2769
|
-
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";
|
|
2770
2478
|
async function watchCommand(options) {
|
|
2771
2479
|
const projectRoot = findProjectRoot();
|
|
2772
2480
|
if (!projectRoot) {
|
|
2773
|
-
console.log(
|
|
2774
|
-
console.log(
|
|
2481
|
+
console.log(chalk3.red("\u274C Not in a Cure Kode project."));
|
|
2482
|
+
console.log(chalk3.dim(' Run "kode init" first.'));
|
|
2775
2483
|
return;
|
|
2776
2484
|
}
|
|
2777
2485
|
const config = getProjectConfig(projectRoot);
|
|
2778
2486
|
if (!config) {
|
|
2779
|
-
console.log(
|
|
2487
|
+
console.log(chalk3.red("\u274C Could not read project configuration."));
|
|
2780
2488
|
return;
|
|
2781
2489
|
}
|
|
2782
2490
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
2783
|
-
if (!
|
|
2784
|
-
console.log(
|
|
2785
|
-
console.log(
|
|
2786
|
-
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.'));
|
|
2787
2495
|
return;
|
|
2788
2496
|
}
|
|
2789
|
-
console.log(
|
|
2790
|
-
console.log(
|
|
2791
|
-
console.log(
|
|
2792
|
-
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"}`));
|
|
2793
2501
|
if (options.deploy) {
|
|
2794
|
-
console.log(
|
|
2502
|
+
console.log(chalk3.cyan(` Auto-deploy: enabled`));
|
|
2795
2503
|
}
|
|
2796
2504
|
console.log();
|
|
2797
|
-
console.log(
|
|
2505
|
+
console.log(chalk3.dim("Press Ctrl+C to stop.\n"));
|
|
2798
2506
|
const client = createApiClient(config);
|
|
2799
|
-
const metadataPath =
|
|
2507
|
+
const metadataPath = join5(projectRoot, ".cure-kode", "scripts.json");
|
|
2800
2508
|
let metadata = [];
|
|
2801
|
-
if (
|
|
2802
|
-
metadata = JSON.parse(
|
|
2509
|
+
if (existsSync5(metadataPath)) {
|
|
2510
|
+
metadata = JSON.parse(readFileSync5(metadataPath, "utf-8"));
|
|
2803
2511
|
}
|
|
2804
2512
|
let remoteScripts = [];
|
|
2805
2513
|
try {
|
|
2806
2514
|
remoteScripts = await client.listScripts(config.siteId);
|
|
2807
|
-
console.log(
|
|
2515
|
+
console.log(chalk3.dim(`Loaded ${remoteScripts.length} remote script(s)
|
|
2808
2516
|
`));
|
|
2809
2517
|
} catch (error) {
|
|
2810
|
-
console.log(
|
|
2518
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Could not load remote scripts. Will create new on change."));
|
|
2811
2519
|
}
|
|
2812
2520
|
const pendingChanges = /* @__PURE__ */ new Map();
|
|
2813
2521
|
const DEBOUNCE_MS = 500;
|
|
@@ -2820,18 +2528,18 @@ async function watchCommand(options) {
|
|
|
2820
2528
|
if (failedSyncs.size === 0 && successCount === 0) return;
|
|
2821
2529
|
const statusParts = [];
|
|
2822
2530
|
if (successCount > 0) {
|
|
2823
|
-
statusParts.push(
|
|
2531
|
+
statusParts.push(chalk3.green(`${successCount} synced`));
|
|
2824
2532
|
}
|
|
2825
2533
|
if (failedSyncs.size > 0) {
|
|
2826
|
-
statusParts.push(
|
|
2534
|
+
statusParts.push(chalk3.red(`${failedSyncs.size} pending errors`));
|
|
2827
2535
|
}
|
|
2828
|
-
console.log(
|
|
2536
|
+
console.log(chalk3.dim(`
|
|
2829
2537
|
\u2500\u2500\u2500 Status: ${statusParts.join(", ")} \u2500\u2500\u2500
|
|
2830
2538
|
`));
|
|
2831
2539
|
};
|
|
2832
2540
|
const retryFailedSyncs = async () => {
|
|
2833
2541
|
for (const [filePath, failed] of failedSyncs.entries()) {
|
|
2834
|
-
if (!
|
|
2542
|
+
if (!existsSync5(filePath)) {
|
|
2835
2543
|
failedSyncs.delete(filePath);
|
|
2836
2544
|
continue;
|
|
2837
2545
|
}
|
|
@@ -2844,7 +2552,7 @@ async function watchCommand(options) {
|
|
|
2844
2552
|
}
|
|
2845
2553
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
2846
2554
|
console.log(
|
|
2847
|
-
|
|
2555
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.yellow(`\u21BB Retrying ${failed.fileName}...`) + chalk3.dim(` (attempt ${failed.attempts + 1}/${MAX_RETRY_ATTEMPTS})`)
|
|
2848
2556
|
);
|
|
2849
2557
|
await handleChange(
|
|
2850
2558
|
filePath,
|
|
@@ -2865,7 +2573,7 @@ async function watchCommand(options) {
|
|
|
2865
2573
|
}
|
|
2866
2574
|
const syncFile = async () => {
|
|
2867
2575
|
try {
|
|
2868
|
-
const content =
|
|
2576
|
+
const content = readFileSync5(filePath, "utf-8");
|
|
2869
2577
|
const remoteScript = remoteScripts.find((s) => s.slug === slug);
|
|
2870
2578
|
const localMeta = metadata.find((m) => m.slug === slug);
|
|
2871
2579
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
@@ -2887,16 +2595,16 @@ async function watchCommand(options) {
|
|
|
2887
2595
|
failedSyncs.delete(filePath);
|
|
2888
2596
|
if (wasRetry) {
|
|
2889
2597
|
console.log(
|
|
2890
|
-
|
|
2598
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.dim(` \u2192 v${remoteScript.current_version}`) + chalk3.cyan(" (recovered)")
|
|
2891
2599
|
);
|
|
2892
2600
|
} else {
|
|
2893
2601
|
console.log(
|
|
2894
|
-
|
|
2602
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.dim(` \u2192 v${remoteScript.current_version}`)
|
|
2895
2603
|
);
|
|
2896
2604
|
}
|
|
2897
2605
|
} else {
|
|
2898
2606
|
console.log(
|
|
2899
|
-
|
|
2607
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.dim(` \u2192 v${remoteScript.current_version}`)
|
|
2900
2608
|
);
|
|
2901
2609
|
}
|
|
2902
2610
|
successCount++;
|
|
@@ -2904,11 +2612,11 @@ async function watchCommand(options) {
|
|
|
2904
2612
|
try {
|
|
2905
2613
|
await client.deploy(config.siteId, config.environment || "staging");
|
|
2906
2614
|
console.log(
|
|
2907
|
-
|
|
2615
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.cyan(` \u21B3 Deployed to ${config.environment || "staging"}`)
|
|
2908
2616
|
);
|
|
2909
2617
|
} catch (deployError) {
|
|
2910
2618
|
console.log(
|
|
2911
|
-
|
|
2619
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.red(` \u21B3 Deploy failed: ${deployError.message || "Unknown error"}`)
|
|
2912
2620
|
);
|
|
2913
2621
|
}
|
|
2914
2622
|
}
|
|
@@ -2924,11 +2632,11 @@ async function watchCommand(options) {
|
|
|
2924
2632
|
if (failedSyncs.has(filePath)) {
|
|
2925
2633
|
failedSyncs.delete(filePath);
|
|
2926
2634
|
console.log(
|
|
2927
|
-
|
|
2635
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.cyan(" (created, recovered)")
|
|
2928
2636
|
);
|
|
2929
2637
|
} else {
|
|
2930
2638
|
console.log(
|
|
2931
|
-
|
|
2639
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.green(`\u2713 ${fileName}`) + chalk3.cyan(" (created)")
|
|
2932
2640
|
);
|
|
2933
2641
|
}
|
|
2934
2642
|
successCount++;
|
|
@@ -2959,11 +2667,11 @@ async function watchCommand(options) {
|
|
|
2959
2667
|
errorCount++;
|
|
2960
2668
|
if (attempts >= MAX_RETRY_ATTEMPTS) {
|
|
2961
2669
|
console.log(
|
|
2962
|
-
|
|
2670
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.red(`\u2717 ${fileName}`) + chalk3.dim(` - ${errorMessage}`) + chalk3.red(` (gave up after ${MAX_RETRY_ATTEMPTS} attempts)`)
|
|
2963
2671
|
);
|
|
2964
2672
|
} else {
|
|
2965
2673
|
console.log(
|
|
2966
|
-
|
|
2674
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.red(`\u2717 ${fileName}`) + chalk3.dim(` - ${errorMessage}`) + chalk3.yellow(` (will retry in ${RETRY_DELAY_MS / 1e3}s)`)
|
|
2967
2675
|
);
|
|
2968
2676
|
}
|
|
2969
2677
|
printStatus();
|
|
@@ -2979,7 +2687,7 @@ async function watchCommand(options) {
|
|
|
2979
2687
|
}, DEBOUNCE_MS);
|
|
2980
2688
|
pendingChanges.set(filePath, timeout);
|
|
2981
2689
|
};
|
|
2982
|
-
const watcher = chokidar.watch(
|
|
2690
|
+
const watcher = chokidar.watch(join5(scriptsDir, "**/*.{js,css}"), {
|
|
2983
2691
|
persistent: true,
|
|
2984
2692
|
ignoreInitial: true,
|
|
2985
2693
|
awaitWriteFinish: {
|
|
@@ -2993,21 +2701,21 @@ async function watchCommand(options) {
|
|
|
2993
2701
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
2994
2702
|
const fileName = basename2(filePath);
|
|
2995
2703
|
console.log(
|
|
2996
|
-
|
|
2704
|
+
chalk3.dim(`[${timestamp}] `) + chalk3.yellow(`\u26A0 ${fileName} deleted locally`) + chalk3.dim(" (remote not affected)")
|
|
2997
2705
|
);
|
|
2998
2706
|
});
|
|
2999
2707
|
process.on("SIGINT", () => {
|
|
3000
2708
|
clearInterval(retryInterval);
|
|
3001
|
-
console.log(
|
|
2709
|
+
console.log(chalk3.dim("\n\nStopping watch...\n"));
|
|
3002
2710
|
if (successCount > 0 || failedSyncs.size > 0) {
|
|
3003
|
-
console.log(
|
|
2711
|
+
console.log(chalk3.bold("Session summary:"));
|
|
3004
2712
|
if (successCount > 0) {
|
|
3005
|
-
console.log(
|
|
2713
|
+
console.log(chalk3.green(` \u2713 ${successCount} file(s) synced`));
|
|
3006
2714
|
}
|
|
3007
2715
|
if (failedSyncs.size > 0) {
|
|
3008
|
-
console.log(
|
|
2716
|
+
console.log(chalk3.red(` \u2717 ${failedSyncs.size} file(s) failed:`));
|
|
3009
2717
|
for (const failed of failedSyncs.values()) {
|
|
3010
|
-
console.log(
|
|
2718
|
+
console.log(chalk3.dim(` - ${failed.fileName}: ${failed.error}`));
|
|
3011
2719
|
}
|
|
3012
2720
|
}
|
|
3013
2721
|
console.log();
|
|
@@ -3018,10 +2726,10 @@ async function watchCommand(options) {
|
|
|
3018
2726
|
}
|
|
3019
2727
|
|
|
3020
2728
|
// src/commands/deploy.ts
|
|
3021
|
-
import
|
|
3022
|
-
import
|
|
3023
|
-
import { readFileSync as
|
|
3024
|
-
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";
|
|
3025
2733
|
function formatBytes(bytes) {
|
|
3026
2734
|
if (bytes < 1024) return `${bytes} B`;
|
|
3027
2735
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -3030,7 +2738,7 @@ function formatBytes(bytes) {
|
|
|
3030
2738
|
async function showDeploymentPreview(config, projectRoot) {
|
|
3031
2739
|
if (!config) return;
|
|
3032
2740
|
const client = createApiClient(config);
|
|
3033
|
-
const spinner =
|
|
2741
|
+
const spinner = ora3("Analyserer deployment...").start();
|
|
3034
2742
|
try {
|
|
3035
2743
|
const [remoteScripts, deployStatus] = await Promise.all([
|
|
3036
2744
|
client.listScripts(config.siteId),
|
|
@@ -3038,39 +2746,39 @@ async function showDeploymentPreview(config, projectRoot) {
|
|
|
3038
2746
|
]);
|
|
3039
2747
|
spinner.stop();
|
|
3040
2748
|
console.log();
|
|
3041
|
-
console.log(
|
|
3042
|
-
console.log(
|
|
2749
|
+
console.log(chalk4.bold("\u{1F50D} Deployment Preview (t\xF8rrkj\xF8ring)"));
|
|
2750
|
+
console.log(chalk4.dim(` M\xE5l: staging`));
|
|
3043
2751
|
console.log();
|
|
3044
2752
|
const stagingVersion = deployStatus.staging.lastSuccessful?.version || null;
|
|
3045
2753
|
const productionVersion = deployStatus.production.lastSuccessful?.version || null;
|
|
3046
|
-
console.log(
|
|
2754
|
+
console.log(chalk4.bold("N\xE5v\xE6rende status"));
|
|
3047
2755
|
if (stagingVersion) {
|
|
3048
|
-
console.log(
|
|
2756
|
+
console.log(chalk4.dim(` Staging: v${stagingVersion}`));
|
|
3049
2757
|
} else {
|
|
3050
|
-
console.log(
|
|
2758
|
+
console.log(chalk4.dim(` Staging: ingen deployments`));
|
|
3051
2759
|
}
|
|
3052
2760
|
if (deployStatus.productionEnabled) {
|
|
3053
2761
|
if (productionVersion) {
|
|
3054
|
-
console.log(
|
|
2762
|
+
console.log(chalk4.dim(` Production: v${productionVersion}`));
|
|
3055
2763
|
} else {
|
|
3056
|
-
console.log(
|
|
2764
|
+
console.log(chalk4.dim(` Production: aktivert, ingen deployments`));
|
|
3057
2765
|
}
|
|
3058
2766
|
}
|
|
3059
2767
|
console.log();
|
|
3060
2768
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
3061
|
-
const localFiles =
|
|
3062
|
-
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");
|
|
3063
2771
|
let metadata = [];
|
|
3064
|
-
if (
|
|
2772
|
+
if (existsSync6(metadataPath)) {
|
|
3065
2773
|
try {
|
|
3066
|
-
metadata = JSON.parse(
|
|
2774
|
+
metadata = JSON.parse(readFileSync6(metadataPath, "utf-8"));
|
|
3067
2775
|
} catch {
|
|
3068
2776
|
}
|
|
3069
2777
|
}
|
|
3070
2778
|
const localBySlug = /* @__PURE__ */ new Map();
|
|
3071
2779
|
for (const file of localFiles) {
|
|
3072
2780
|
const slug = basename3(file, extname3(file));
|
|
3073
|
-
const content =
|
|
2781
|
+
const content = readFileSync6(join6(scriptsDir, file), "utf-8");
|
|
3074
2782
|
localBySlug.set(slug, { content, size: Buffer.byteLength(content, "utf-8") });
|
|
3075
2783
|
}
|
|
3076
2784
|
const remoteBySlug = /* @__PURE__ */ new Map();
|
|
@@ -3127,63 +2835,63 @@ async function showDeploymentPreview(config, projectRoot) {
|
|
|
3127
2835
|
}
|
|
3128
2836
|
}
|
|
3129
2837
|
if (changes.length > 0) {
|
|
3130
|
-
console.log(
|
|
2838
|
+
console.log(chalk4.bold("Endringer"));
|
|
3131
2839
|
console.log();
|
|
3132
2840
|
console.log(
|
|
3133
|
-
|
|
2841
|
+
chalk4.dim(" ") + chalk4.dim("Skript".padEnd(25)) + chalk4.dim("F\xF8r".padStart(8)) + chalk4.dim("Etter".padStart(8)) + chalk4.dim("Endring".padStart(15))
|
|
3134
2842
|
);
|
|
3135
|
-
console.log(
|
|
2843
|
+
console.log(chalk4.dim(" " + "\u2500".repeat(56)));
|
|
3136
2844
|
for (const change of changes) {
|
|
3137
2845
|
const beforeStr = change.beforeVersion ? `v${change.beforeVersion}` : "-";
|
|
3138
2846
|
const afterStr = `v${change.afterVersion}`;
|
|
3139
2847
|
let diffStr;
|
|
3140
2848
|
if (change.isNew) {
|
|
3141
|
-
diffStr =
|
|
2849
|
+
diffStr = chalk4.cyan(`+${change.linesDiff.added} linjer`);
|
|
3142
2850
|
} else {
|
|
3143
2851
|
const parts = [];
|
|
3144
|
-
if (change.linesDiff.added > 0) parts.push(
|
|
3145
|
-
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}`));
|
|
3146
2854
|
diffStr = parts.join(" ") + " linjer";
|
|
3147
2855
|
}
|
|
3148
|
-
const nameColor = change.isNew ?
|
|
2856
|
+
const nameColor = change.isNew ? chalk4.cyan : chalk4.white;
|
|
3149
2857
|
console.log(
|
|
3150
|
-
` ${nameColor(change.fileName.padEnd(25))}${
|
|
2858
|
+
` ${nameColor(change.fileName.padEnd(25))}${chalk4.dim(beforeStr.padStart(8))}${afterStr.padStart(8)}${diffStr.padStart(15)}`
|
|
3151
2859
|
);
|
|
3152
2860
|
}
|
|
3153
2861
|
console.log();
|
|
3154
2862
|
const sizeDiff = totalAfterSize - totalBeforeSize;
|
|
3155
|
-
const sizeDiffStr = sizeDiff > 0 ?
|
|
2863
|
+
const sizeDiffStr = sizeDiff > 0 ? chalk4.yellow(`+${formatBytes(sizeDiff)}`) : sizeDiff < 0 ? chalk4.green(`-${formatBytes(Math.abs(sizeDiff))}`) : chalk4.dim("uendret");
|
|
3156
2864
|
console.log(
|
|
3157
|
-
|
|
2865
|
+
chalk4.dim(` Total: ${formatBytes(totalBeforeSize)} \u2192 ${formatBytes(totalAfterSize)} (${sizeDiffStr})`)
|
|
3158
2866
|
);
|
|
3159
2867
|
} else {
|
|
3160
|
-
console.log(
|
|
2868
|
+
console.log(chalk4.dim(" Ingen endringer \xE5 deploye"));
|
|
3161
2869
|
}
|
|
3162
2870
|
if (warnings.length > 0) {
|
|
3163
2871
|
console.log();
|
|
3164
|
-
console.log(
|
|
2872
|
+
console.log(chalk4.bold("Advarsler"));
|
|
3165
2873
|
for (const warning of warnings) {
|
|
3166
|
-
console.log(
|
|
2874
|
+
console.log(chalk4.yellow(` \u26A0 ${warning}`));
|
|
3167
2875
|
}
|
|
3168
2876
|
}
|
|
3169
2877
|
console.log();
|
|
3170
|
-
console.log(
|
|
2878
|
+
console.log(chalk4.dim('Dette er en t\xF8rrkj\xF8ring. Kj\xF8r "kode deploy" for \xE5 deploye.'));
|
|
3171
2879
|
console.log();
|
|
3172
2880
|
} catch (error) {
|
|
3173
2881
|
spinner.fail("Kunne ikke analysere deployment");
|
|
3174
|
-
console.error(
|
|
2882
|
+
console.error(chalk4.red("\nFeil:"), error.message || error);
|
|
3175
2883
|
}
|
|
3176
2884
|
}
|
|
3177
2885
|
async function deployCommand(environment, options) {
|
|
3178
2886
|
const projectRoot = findProjectRoot();
|
|
3179
2887
|
if (!projectRoot) {
|
|
3180
|
-
console.log(
|
|
3181
|
-
console.log(
|
|
2888
|
+
console.log(chalk4.red("\u274C Not in a Cure Kode project."));
|
|
2889
|
+
console.log(chalk4.dim(' Run "kode init" first.'));
|
|
3182
2890
|
return;
|
|
3183
2891
|
}
|
|
3184
2892
|
const config = getProjectConfig(projectRoot);
|
|
3185
2893
|
if (!config) {
|
|
3186
|
-
console.log(
|
|
2894
|
+
console.log(chalk4.red("\u274C Could not read project configuration."));
|
|
3187
2895
|
return;
|
|
3188
2896
|
}
|
|
3189
2897
|
if (options?.dryRun) {
|
|
@@ -3193,15 +2901,15 @@ async function deployCommand(environment, options) {
|
|
|
3193
2901
|
const shouldPromote = options?.promote || environment === "production";
|
|
3194
2902
|
if (shouldPromote) {
|
|
3195
2903
|
const client2 = createApiClient(config);
|
|
3196
|
-
const spinner2 =
|
|
2904
|
+
const spinner2 = ora3("Sjekker produksjonsstatus...").start();
|
|
3197
2905
|
try {
|
|
3198
2906
|
const status = await client2.getDeploymentStatus(config.siteId);
|
|
3199
2907
|
if (!status.productionEnabled) {
|
|
3200
2908
|
spinner2.fail("Produksjon er ikke aktivert");
|
|
3201
2909
|
console.log();
|
|
3202
|
-
console.log(
|
|
3203
|
-
console.log(
|
|
3204
|
-
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>]"));
|
|
3205
2913
|
console.log();
|
|
3206
2914
|
return;
|
|
3207
2915
|
}
|
|
@@ -3209,23 +2917,23 @@ async function deployCommand(environment, options) {
|
|
|
3209
2917
|
const deployment = await client2.promoteToProduction(config.siteId);
|
|
3210
2918
|
spinner2.succeed("Promoted to production");
|
|
3211
2919
|
console.log();
|
|
3212
|
-
console.log(
|
|
3213
|
-
console.log(
|
|
3214
|
-
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}`));
|
|
3215
2923
|
console.log();
|
|
3216
|
-
console.log(
|
|
3217
|
-
console.log(
|
|
2924
|
+
console.log(chalk4.bold("CDN URL:"));
|
|
2925
|
+
console.log(chalk4.cyan(` https://app.cure.no/api/cdn/${config.siteSlug}/init.js`));
|
|
3218
2926
|
console.log();
|
|
3219
|
-
console.log(
|
|
2927
|
+
console.log(chalk4.green("\u2705 Production is now running the latest staging version!"));
|
|
3220
2928
|
} catch (error) {
|
|
3221
2929
|
spinner2.fail("Promotion failed");
|
|
3222
|
-
console.error(
|
|
2930
|
+
console.error(chalk4.red("\nError:"), error.message || error);
|
|
3223
2931
|
}
|
|
3224
2932
|
return;
|
|
3225
2933
|
}
|
|
3226
2934
|
const client = createApiClient(config);
|
|
3227
2935
|
if (options?.force) {
|
|
3228
|
-
const forceSpinner =
|
|
2936
|
+
const forceSpinner = ora3("Sjekker l\xE5s...").start();
|
|
3229
2937
|
try {
|
|
3230
2938
|
const lockStatus = await client.getLockStatus(config.siteId);
|
|
3231
2939
|
if (lockStatus.isLocked) {
|
|
@@ -3233,18 +2941,18 @@ async function deployCommand(environment, options) {
|
|
|
3233
2941
|
forceSpinner.text = "Frigj\xF8r gammel l\xE5s...";
|
|
3234
2942
|
} else {
|
|
3235
2943
|
forceSpinner.warn("Aktiv l\xE5s funnet");
|
|
3236
|
-
console.log(
|
|
3237
|
-
console.log(
|
|
3238
|
-
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}`));
|
|
3239
2947
|
console.log();
|
|
3240
|
-
console.log(
|
|
3241
|
-
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."));
|
|
3242
2950
|
return;
|
|
3243
2951
|
}
|
|
3244
2952
|
const result = await client.forceReleaseLock(config.siteId);
|
|
3245
2953
|
if (result.wasLocked) {
|
|
3246
2954
|
forceSpinner.succeed("L\xE5s frigjort");
|
|
3247
|
-
console.log(
|
|
2955
|
+
console.log(chalk4.dim(` Tidligere l\xE5s fra: ${result.acquiredAt ? new Date(result.acquiredAt).toLocaleString("nb-NO") : "ukjent"}`));
|
|
3248
2956
|
console.log();
|
|
3249
2957
|
} else {
|
|
3250
2958
|
forceSpinner.info("Ingen l\xE5s \xE5 frigj\xF8re");
|
|
@@ -3254,11 +2962,11 @@ async function deployCommand(environment, options) {
|
|
|
3254
2962
|
}
|
|
3255
2963
|
} catch (error) {
|
|
3256
2964
|
forceSpinner.fail("Kunne ikke sjekke/frigj\xF8re l\xE5s");
|
|
3257
|
-
console.error(
|
|
2965
|
+
console.error(chalk4.red("\nError:"), error.message || error);
|
|
3258
2966
|
return;
|
|
3259
2967
|
}
|
|
3260
2968
|
}
|
|
3261
|
-
const preCheckSpinner =
|
|
2969
|
+
const preCheckSpinner = ora3("Sjekker scripts...").start();
|
|
3262
2970
|
try {
|
|
3263
2971
|
const scripts = await client.listScripts(config.siteId);
|
|
3264
2972
|
const emptyScripts = scripts.filter(
|
|
@@ -3266,45 +2974,45 @@ async function deployCommand(environment, options) {
|
|
|
3266
2974
|
);
|
|
3267
2975
|
if (emptyScripts.length > 0) {
|
|
3268
2976
|
preCheckSpinner.warn(`${emptyScripts.length} tomme script(s) funnet`);
|
|
3269
|
-
console.log(
|
|
2977
|
+
console.log(chalk4.yellow("\n\u26A0\uFE0F F\xF8lgende scripts er tomme:"));
|
|
3270
2978
|
emptyScripts.forEach((s) => {
|
|
3271
|
-
console.log(
|
|
2979
|
+
console.log(chalk4.dim(` - ${s.slug}.${s.type === "javascript" ? "js" : "css"}`));
|
|
3272
2980
|
});
|
|
3273
|
-
console.log(
|
|
2981
|
+
console.log(chalk4.dim(" Tomme scripts har ingen effekt n\xE5r de er deployet.\n"));
|
|
3274
2982
|
} else {
|
|
3275
2983
|
preCheckSpinner.succeed(`${scripts.filter((s) => s.is_active).length} script(s) klare`);
|
|
3276
2984
|
}
|
|
3277
2985
|
} catch {
|
|
3278
2986
|
preCheckSpinner.info("Kunne ikke sjekke scripts");
|
|
3279
2987
|
}
|
|
3280
|
-
const spinner =
|
|
2988
|
+
const spinner = ora3("Deploying to staging...").start();
|
|
3281
2989
|
try {
|
|
3282
2990
|
const deployment = await client.deploy(config.siteId, "staging");
|
|
3283
2991
|
spinner.succeed("Deployed to staging");
|
|
3284
2992
|
console.log();
|
|
3285
|
-
console.log(
|
|
3286
|
-
console.log(
|
|
3287
|
-
console.log(
|
|
3288
|
-
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")}`));
|
|
3289
2997
|
console.log();
|
|
3290
|
-
console.log(
|
|
3291
|
-
console.log(
|
|
2998
|
+
console.log(chalk4.bold("CDN URL:"));
|
|
2999
|
+
console.log(chalk4.cyan(` https://app.cure.no/api/cdn/${config.siteSlug}/init.js`));
|
|
3292
3000
|
console.log();
|
|
3293
|
-
console.log(
|
|
3001
|
+
console.log(chalk4.cyan('\u{1F4A1} Tip: Use "kode deploy production" to promote to production.'));
|
|
3294
3002
|
} catch (error) {
|
|
3295
3003
|
spinner.fail("Deployment failed");
|
|
3296
|
-
console.error(
|
|
3004
|
+
console.error(chalk4.red("\nError:"), error.message || error);
|
|
3297
3005
|
}
|
|
3298
3006
|
}
|
|
3299
3007
|
|
|
3300
3008
|
// src/commands/html.ts
|
|
3301
|
-
import
|
|
3302
|
-
import
|
|
3009
|
+
import chalk5 from "chalk";
|
|
3010
|
+
import ora4 from "ora";
|
|
3303
3011
|
|
|
3304
3012
|
// src/lib/page-cache.ts
|
|
3305
|
-
import { existsSync as
|
|
3306
|
-
import { join as
|
|
3307
|
-
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";
|
|
3308
3016
|
var PAGES_DIR = "pages";
|
|
3309
3017
|
function urlToSlug(url) {
|
|
3310
3018
|
try {
|
|
@@ -3317,32 +3025,32 @@ function urlToSlug(url) {
|
|
|
3317
3025
|
}
|
|
3318
3026
|
}
|
|
3319
3027
|
function getPagesDir(projectRoot) {
|
|
3320
|
-
return
|
|
3028
|
+
return join7(projectRoot, PROJECT_CONFIG_DIR2, PAGES_DIR);
|
|
3321
3029
|
}
|
|
3322
3030
|
function getPageCachePath(projectRoot, urlOrSlug) {
|
|
3323
3031
|
const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
|
|
3324
|
-
return
|
|
3032
|
+
return join7(getPagesDir(projectRoot), `${slug}.json`);
|
|
3325
3033
|
}
|
|
3326
3034
|
function ensurePagesDir(projectRoot) {
|
|
3327
3035
|
const pagesDir = getPagesDir(projectRoot);
|
|
3328
|
-
if (!
|
|
3329
|
-
|
|
3036
|
+
if (!existsSync7(pagesDir)) {
|
|
3037
|
+
mkdirSync3(pagesDir, { recursive: true });
|
|
3330
3038
|
}
|
|
3331
3039
|
}
|
|
3332
3040
|
function savePageContext(projectRoot, context) {
|
|
3333
3041
|
ensurePagesDir(projectRoot);
|
|
3334
3042
|
const slug = urlToSlug(context.url);
|
|
3335
3043
|
const cachePath = getPageCachePath(projectRoot, slug);
|
|
3336
|
-
|
|
3044
|
+
writeFileSync5(cachePath, JSON.stringify(context, null, 2), "utf-8");
|
|
3337
3045
|
return slug;
|
|
3338
3046
|
}
|
|
3339
3047
|
function readPageContext(projectRoot, urlOrSlug) {
|
|
3340
3048
|
const cachePath = getPageCachePath(projectRoot, urlOrSlug);
|
|
3341
|
-
if (!
|
|
3049
|
+
if (!existsSync7(cachePath)) {
|
|
3342
3050
|
return null;
|
|
3343
3051
|
}
|
|
3344
3052
|
try {
|
|
3345
|
-
const content =
|
|
3053
|
+
const content = readFileSync7(cachePath, "utf-8");
|
|
3346
3054
|
return JSON.parse(content);
|
|
3347
3055
|
} catch {
|
|
3348
3056
|
return null;
|
|
@@ -3350,7 +3058,7 @@ function readPageContext(projectRoot, urlOrSlug) {
|
|
|
3350
3058
|
}
|
|
3351
3059
|
function deletePageContext(projectRoot, urlOrSlug) {
|
|
3352
3060
|
const cachePath = getPageCachePath(projectRoot, urlOrSlug);
|
|
3353
|
-
if (
|
|
3061
|
+
if (existsSync7(cachePath)) {
|
|
3354
3062
|
unlinkSync(cachePath);
|
|
3355
3063
|
return true;
|
|
3356
3064
|
}
|
|
@@ -3358,7 +3066,7 @@ function deletePageContext(projectRoot, urlOrSlug) {
|
|
|
3358
3066
|
}
|
|
3359
3067
|
function listCachedPages(projectRoot) {
|
|
3360
3068
|
const pagesDir = getPagesDir(projectRoot);
|
|
3361
|
-
if (!
|
|
3069
|
+
if (!existsSync7(pagesDir)) {
|
|
3362
3070
|
return [];
|
|
3363
3071
|
}
|
|
3364
3072
|
const files = readdirSync3(pagesDir).filter((f) => f.endsWith(".json"));
|
|
@@ -3423,34 +3131,34 @@ function toCachedContext(structure) {
|
|
|
3423
3131
|
async function htmlCommand(url, options) {
|
|
3424
3132
|
const projectRoot = findProjectRoot();
|
|
3425
3133
|
if (!projectRoot) {
|
|
3426
|
-
console.log(
|
|
3427
|
-
console.log(
|
|
3134
|
+
console.log(chalk5.red("\u274C Not in a Cure Kode project."));
|
|
3135
|
+
console.log(chalk5.dim(' Run "kode init" first.'));
|
|
3428
3136
|
return;
|
|
3429
3137
|
}
|
|
3430
3138
|
const config = getProjectConfig(projectRoot);
|
|
3431
3139
|
if (!config) {
|
|
3432
|
-
console.log(
|
|
3140
|
+
console.log(chalk5.red("\u274C Could not read project configuration."));
|
|
3433
3141
|
return;
|
|
3434
3142
|
}
|
|
3435
3143
|
let parsedUrl;
|
|
3436
3144
|
try {
|
|
3437
3145
|
parsedUrl = new URL(url.startsWith("http") ? url : `https://${url}`);
|
|
3438
3146
|
} catch {
|
|
3439
|
-
console.log(
|
|
3147
|
+
console.log(chalk5.red("\u274C Invalid URL."));
|
|
3440
3148
|
return;
|
|
3441
3149
|
}
|
|
3442
3150
|
if (options?.save && !options?.force) {
|
|
3443
3151
|
const slug = urlToSlug(parsedUrl.toString());
|
|
3444
3152
|
const cached = readPageContext(projectRoot, slug);
|
|
3445
3153
|
if (cached) {
|
|
3446
|
-
console.log(
|
|
3447
|
-
console.log(
|
|
3154
|
+
console.log(chalk5.dim(`Using cached version from ${cached.extractedAt}`));
|
|
3155
|
+
console.log(chalk5.dim(`Use --force to refresh`));
|
|
3448
3156
|
console.log();
|
|
3449
3157
|
printPageContext(cached);
|
|
3450
3158
|
return;
|
|
3451
3159
|
}
|
|
3452
3160
|
}
|
|
3453
|
-
const spinner =
|
|
3161
|
+
const spinner = ora4(`Fetching ${parsedUrl.hostname}${parsedUrl.pathname}...`).start();
|
|
3454
3162
|
try {
|
|
3455
3163
|
const client = createApiClient(config);
|
|
3456
3164
|
if (options?.save) {
|
|
@@ -3471,7 +3179,7 @@ async function htmlCommand(url, options) {
|
|
|
3471
3179
|
spinner.succeed("Page structure extracted");
|
|
3472
3180
|
const cachedContext = toCachedContext(structure);
|
|
3473
3181
|
const slug = savePageContext(projectRoot, cachedContext);
|
|
3474
|
-
console.log(
|
|
3182
|
+
console.log(chalk5.dim(`Saved to .cure-kode/pages/${slug}.json`));
|
|
3475
3183
|
const contextPage = {
|
|
3476
3184
|
slug,
|
|
3477
3185
|
url: structure.url,
|
|
@@ -3482,7 +3190,7 @@ async function htmlCommand(url, options) {
|
|
|
3482
3190
|
cms: structure.cmsPatterns.length > 0 ? structure.cmsPatterns.map((c) => `${c.containerClass} (${c.itemCount})`).join(", ") : void 0
|
|
3483
3191
|
};
|
|
3484
3192
|
upsertPage(projectRoot, contextPage, "kode html --save");
|
|
3485
|
-
console.log(
|
|
3193
|
+
console.log(chalk5.dim(`Updated .cure-kode/context.md`));
|
|
3486
3194
|
console.log();
|
|
3487
3195
|
printPageContext(cachedContext);
|
|
3488
3196
|
return;
|
|
@@ -3494,115 +3202,115 @@ async function htmlCommand(url, options) {
|
|
|
3494
3202
|
return;
|
|
3495
3203
|
}
|
|
3496
3204
|
console.log();
|
|
3497
|
-
console.log(
|
|
3498
|
-
console.log(
|
|
3205
|
+
console.log(chalk5.bold(result.title || parsedUrl.hostname));
|
|
3206
|
+
console.log(chalk5.dim(result.url));
|
|
3499
3207
|
console.log();
|
|
3500
3208
|
const hasCureKode = result.scripts.cureKode.length > 0;
|
|
3501
3209
|
if (hasCureKode) {
|
|
3502
|
-
console.log(
|
|
3210
|
+
console.log(chalk5.green("\u2705 Cure Kode installed"));
|
|
3503
3211
|
} else {
|
|
3504
|
-
console.log(
|
|
3505
|
-
console.log(
|
|
3506
|
-
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>`));
|
|
3507
3215
|
}
|
|
3508
3216
|
console.log();
|
|
3509
|
-
console.log(
|
|
3217
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
3510
3218
|
console.log(
|
|
3511
|
-
` Scripts: ${result.stats.totalScripts}` +
|
|
3219
|
+
` Scripts: ${result.stats.totalScripts}` + chalk5.dim(` (${result.stats.externalScripts} external, ${result.stats.inlineScripts} inline)`)
|
|
3512
3220
|
);
|
|
3513
3221
|
console.log(` Styles: ${result.stats.totalStyles}`);
|
|
3514
|
-
console.log(
|
|
3222
|
+
console.log(chalk5.dim("\u2500".repeat(50)));
|
|
3515
3223
|
console.log();
|
|
3516
3224
|
if (!options?.styles) {
|
|
3517
|
-
console.log(
|
|
3225
|
+
console.log(chalk5.bold("Scripts"));
|
|
3518
3226
|
console.log();
|
|
3519
3227
|
if (result.scripts.webflow.length > 0) {
|
|
3520
|
-
console.log(
|
|
3228
|
+
console.log(chalk5.blue(" Webflow:"));
|
|
3521
3229
|
result.scripts.webflow.forEach((s) => {
|
|
3522
|
-
console.log(
|
|
3230
|
+
console.log(chalk5.dim(` ${s.src || "(inline)"}`));
|
|
3523
3231
|
});
|
|
3524
3232
|
}
|
|
3525
3233
|
if (result.scripts.cureKode.length > 0) {
|
|
3526
|
-
console.log(
|
|
3234
|
+
console.log(chalk5.green(" Cure Kode:"));
|
|
3527
3235
|
result.scripts.cureKode.forEach((s) => {
|
|
3528
|
-
console.log(
|
|
3236
|
+
console.log(chalk5.dim(` ${s.src || "(inline)"}`));
|
|
3529
3237
|
});
|
|
3530
3238
|
}
|
|
3531
3239
|
if (result.scripts.thirdParty.length > 0) {
|
|
3532
|
-
console.log(
|
|
3240
|
+
console.log(chalk5.yellow(" Third-party:"));
|
|
3533
3241
|
result.scripts.thirdParty.forEach((s) => {
|
|
3534
|
-
console.log(
|
|
3242
|
+
console.log(chalk5.dim(` ${s.src || "(inline)"}`));
|
|
3535
3243
|
});
|
|
3536
3244
|
}
|
|
3537
3245
|
if (result.scripts.custom.length > 0) {
|
|
3538
|
-
console.log(
|
|
3246
|
+
console.log(chalk5.magenta(" Custom:"));
|
|
3539
3247
|
result.scripts.custom.forEach((s) => {
|
|
3540
3248
|
const label = s.src || (s.inline ? `(inline, ${s.content?.length || 0} chars)` : "(inline)");
|
|
3541
|
-
console.log(
|
|
3249
|
+
console.log(chalk5.dim(` ${label}`));
|
|
3542
3250
|
});
|
|
3543
3251
|
}
|
|
3544
3252
|
console.log();
|
|
3545
3253
|
}
|
|
3546
3254
|
if (result.detectedComponents.length > 0) {
|
|
3547
|
-
console.log(
|
|
3255
|
+
console.log(chalk5.bold("Detected Components"));
|
|
3548
3256
|
console.log();
|
|
3549
3257
|
result.detectedComponents.forEach((comp) => {
|
|
3550
|
-
console.log(
|
|
3258
|
+
console.log(chalk5.dim(` \u2022 ${comp}`));
|
|
3551
3259
|
});
|
|
3552
3260
|
console.log();
|
|
3553
3261
|
}
|
|
3554
3262
|
if (options?.styles) {
|
|
3555
|
-
console.log(
|
|
3263
|
+
console.log(chalk5.bold("Stylesheets"));
|
|
3556
3264
|
console.log();
|
|
3557
3265
|
result.styles.forEach((style) => {
|
|
3558
3266
|
if (style.href) {
|
|
3559
|
-
const isWebflow = style.isWebflow ?
|
|
3560
|
-
console.log(
|
|
3267
|
+
const isWebflow = style.isWebflow ? chalk5.blue(" [WF]") : "";
|
|
3268
|
+
console.log(chalk5.dim(` ${style.href}${isWebflow}`));
|
|
3561
3269
|
} else {
|
|
3562
|
-
console.log(
|
|
3270
|
+
console.log(chalk5.dim(" (inline style)"));
|
|
3563
3271
|
}
|
|
3564
3272
|
});
|
|
3565
3273
|
console.log();
|
|
3566
3274
|
}
|
|
3567
3275
|
} catch (error) {
|
|
3568
3276
|
spinner.fail("Failed to fetch HTML");
|
|
3569
|
-
console.error(
|
|
3277
|
+
console.error(chalk5.red("\nError:"), error.message || error);
|
|
3570
3278
|
}
|
|
3571
3279
|
}
|
|
3572
3280
|
function printPageContext(context) {
|
|
3573
|
-
console.log(
|
|
3574
|
-
console.log(
|
|
3281
|
+
console.log(chalk5.bold(context.title || context.url));
|
|
3282
|
+
console.log(chalk5.dim(context.url));
|
|
3575
3283
|
console.log();
|
|
3576
3284
|
if (context.sections.length > 0) {
|
|
3577
|
-
console.log(
|
|
3285
|
+
console.log(chalk5.bold("Sections"));
|
|
3578
3286
|
for (const section of context.sections.slice(0, 8)) {
|
|
3579
3287
|
const name = section.heading || section.id || section.className?.split(" ")[0] || "section";
|
|
3580
3288
|
const badges = [];
|
|
3581
|
-
if (section.hasCms) badges.push(
|
|
3582
|
-
if (section.hasForm) badges.push(
|
|
3289
|
+
if (section.hasCms) badges.push(chalk5.cyan("CMS"));
|
|
3290
|
+
if (section.hasForm) badges.push(chalk5.yellow("Form"));
|
|
3583
3291
|
const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
|
|
3584
3292
|
console.log(` \u2022 ${name}${badgeStr}`);
|
|
3585
3293
|
if (section.textSample) {
|
|
3586
|
-
console.log(
|
|
3294
|
+
console.log(chalk5.dim(` "${section.textSample.slice(0, 60)}..."`));
|
|
3587
3295
|
}
|
|
3588
3296
|
}
|
|
3589
3297
|
if (context.sections.length > 8) {
|
|
3590
|
-
console.log(
|
|
3298
|
+
console.log(chalk5.dim(` ... and ${context.sections.length - 8} more`));
|
|
3591
3299
|
}
|
|
3592
3300
|
console.log();
|
|
3593
3301
|
}
|
|
3594
3302
|
if (context.ctas.length > 0) {
|
|
3595
|
-
console.log(
|
|
3303
|
+
console.log(chalk5.bold("CTAs"));
|
|
3596
3304
|
for (const cta of context.ctas.slice(0, 6)) {
|
|
3597
|
-
console.log(` \u2022 "${cta.text}" ${
|
|
3305
|
+
console.log(` \u2022 "${cta.text}" ${chalk5.dim(`(${cta.location})`)}`);
|
|
3598
3306
|
}
|
|
3599
3307
|
if (context.ctas.length > 6) {
|
|
3600
|
-
console.log(
|
|
3308
|
+
console.log(chalk5.dim(` ... and ${context.ctas.length - 6} more`));
|
|
3601
3309
|
}
|
|
3602
3310
|
console.log();
|
|
3603
3311
|
}
|
|
3604
3312
|
if (context.forms.length > 0) {
|
|
3605
|
-
console.log(
|
|
3313
|
+
console.log(chalk5.bold("Forms"));
|
|
3606
3314
|
for (const form of context.forms) {
|
|
3607
3315
|
const fields = form.fields.map((f) => f.label || f.type).slice(0, 4).join(", ");
|
|
3608
3316
|
const more = form.fields.length > 4 ? ` +${form.fields.length - 4}` : "";
|
|
@@ -3611,17 +3319,17 @@ function printPageContext(context) {
|
|
|
3611
3319
|
console.log();
|
|
3612
3320
|
}
|
|
3613
3321
|
if (context.cmsPatterns.length > 0) {
|
|
3614
|
-
console.log(
|
|
3322
|
+
console.log(chalk5.bold("CMS Collections"));
|
|
3615
3323
|
for (const cms of context.cmsPatterns) {
|
|
3616
|
-
console.log(` \u2022 ${cms.containerClass}: ${
|
|
3324
|
+
console.log(` \u2022 ${cms.containerClass}: ${chalk5.cyan(`${cms.itemCount} items`)}`);
|
|
3617
3325
|
if (cms.templateFields.length > 0) {
|
|
3618
|
-
console.log(
|
|
3326
|
+
console.log(chalk5.dim(` Fields: ${cms.templateFields.slice(0, 5).join(", ")}`));
|
|
3619
3327
|
}
|
|
3620
3328
|
}
|
|
3621
3329
|
console.log();
|
|
3622
3330
|
}
|
|
3623
3331
|
if (context.navigation.length > 0) {
|
|
3624
|
-
console.log(
|
|
3332
|
+
console.log(chalk5.bold("Navigation"));
|
|
3625
3333
|
for (const nav of context.navigation) {
|
|
3626
3334
|
const items = nav.items.slice(0, 5).map((i) => i.text).join(", ");
|
|
3627
3335
|
const more = nav.items.length > 5 ? ` +${nav.items.length - 5}` : "";
|
|
@@ -3630,7 +3338,7 @@ function printPageContext(context) {
|
|
|
3630
3338
|
console.log();
|
|
3631
3339
|
}
|
|
3632
3340
|
if (context.notes && context.notes.length > 0) {
|
|
3633
|
-
console.log(
|
|
3341
|
+
console.log(chalk5.bold("Notes"));
|
|
3634
3342
|
for (const note of context.notes) {
|
|
3635
3343
|
console.log(` \u2022 ${note}`);
|
|
3636
3344
|
}
|
|
@@ -3639,13 +3347,13 @@ function printPageContext(context) {
|
|
|
3639
3347
|
}
|
|
3640
3348
|
|
|
3641
3349
|
// src/commands/status.ts
|
|
3642
|
-
import
|
|
3643
|
-
import
|
|
3644
|
-
import { readFileSync as
|
|
3645
|
-
import { join as
|
|
3646
|
-
import { createHash as
|
|
3647
|
-
function
|
|
3648
|
-
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);
|
|
3649
3357
|
}
|
|
3650
3358
|
function formatRelativeTime(dateStr) {
|
|
3651
3359
|
const date = new Date(dateStr);
|
|
@@ -3680,36 +3388,36 @@ function isSyncStale(metadata) {
|
|
|
3680
3388
|
async function statusCommand(options) {
|
|
3681
3389
|
const projectRoot = findProjectRoot();
|
|
3682
3390
|
if (!projectRoot) {
|
|
3683
|
-
console.log(
|
|
3684
|
-
console.log(
|
|
3391
|
+
console.log(chalk6.red("\u274C Not in a Cure Kode project."));
|
|
3392
|
+
console.log(chalk6.dim(' Run "kode init" first.'));
|
|
3685
3393
|
return;
|
|
3686
3394
|
}
|
|
3687
3395
|
const config = getProjectConfig(projectRoot);
|
|
3688
3396
|
if (!config) {
|
|
3689
|
-
console.log(
|
|
3397
|
+
console.log(chalk6.red("\u274C Could not read project configuration."));
|
|
3690
3398
|
return;
|
|
3691
3399
|
}
|
|
3692
|
-
const metadataPath =
|
|
3400
|
+
const metadataPath = join8(projectRoot, ".cure-kode", "scripts.json");
|
|
3693
3401
|
let syncMetadata = [];
|
|
3694
|
-
if (
|
|
3402
|
+
if (existsSync8(metadataPath)) {
|
|
3695
3403
|
try {
|
|
3696
|
-
syncMetadata = JSON.parse(
|
|
3404
|
+
syncMetadata = JSON.parse(readFileSync8(metadataPath, "utf-8"));
|
|
3697
3405
|
} catch {
|
|
3698
3406
|
}
|
|
3699
3407
|
}
|
|
3700
3408
|
console.log();
|
|
3701
|
-
console.log(
|
|
3702
|
-
console.log(
|
|
3703
|
-
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}`));
|
|
3704
3412
|
console.log();
|
|
3705
|
-
console.log(
|
|
3413
|
+
console.log(chalk6.bold("CDN URL"));
|
|
3706
3414
|
console.log();
|
|
3707
|
-
console.log(
|
|
3415
|
+
console.log(chalk6.cyan(` https://app.cure.no/api/cdn/${config.siteSlug}/init.js`));
|
|
3708
3416
|
console.log();
|
|
3709
|
-
console.log(
|
|
3710
|
-
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>`));
|
|
3711
3419
|
console.log();
|
|
3712
|
-
const spinner =
|
|
3420
|
+
const spinner = ora5("Fetching status...").start();
|
|
3713
3421
|
try {
|
|
3714
3422
|
const client = createApiClient(config);
|
|
3715
3423
|
const [remoteScripts, deployStatus] = await Promise.all([
|
|
@@ -3717,46 +3425,46 @@ async function statusCommand(options) {
|
|
|
3717
3425
|
client.getDeploymentStatus(config.siteId)
|
|
3718
3426
|
]);
|
|
3719
3427
|
spinner.stop();
|
|
3720
|
-
console.log(
|
|
3428
|
+
console.log(chalk6.bold("Environments"));
|
|
3721
3429
|
console.log();
|
|
3722
3430
|
const stagingStatus = deployStatus.staging.lastSuccessful;
|
|
3723
3431
|
if (stagingStatus) {
|
|
3724
3432
|
console.log(
|
|
3725
|
-
|
|
3433
|
+
chalk6.blue(" Staging: ") + chalk6.green("\u25CF") + chalk6.dim(` v${stagingStatus.version}`) + chalk6.dim(` (${formatDate(stagingStatus.completedAt)})`)
|
|
3726
3434
|
);
|
|
3727
3435
|
} else {
|
|
3728
|
-
console.log(
|
|
3436
|
+
console.log(chalk6.blue(" Staging: ") + chalk6.yellow("\u25CB") + chalk6.dim(" No deployments"));
|
|
3729
3437
|
}
|
|
3730
3438
|
const productionEnabled = deployStatus.productionEnabled ?? false;
|
|
3731
3439
|
const prodStatus = deployStatus.production.lastSuccessful;
|
|
3732
3440
|
if (!productionEnabled) {
|
|
3733
3441
|
console.log(
|
|
3734
|
-
|
|
3442
|
+
chalk6.gray(" Production: ") + chalk6.gray("\u25CB") + chalk6.gray(" Deaktivert") + chalk6.dim(" (kun staging)")
|
|
3735
3443
|
);
|
|
3736
3444
|
console.log();
|
|
3737
3445
|
console.log(
|
|
3738
|
-
|
|
3446
|
+
chalk6.dim(' \u{1F4A1} Run "kode production enable" to activate production environment.')
|
|
3739
3447
|
);
|
|
3740
3448
|
} else if (prodStatus) {
|
|
3741
3449
|
console.log(
|
|
3742
|
-
|
|
3450
|
+
chalk6.green(" Production: ") + chalk6.green("\u25CF") + chalk6.dim(` v${prodStatus.version}`) + chalk6.dim(` (${formatDate(prodStatus.completedAt)})`)
|
|
3743
3451
|
);
|
|
3744
3452
|
if (deployStatus.productionDomain) {
|
|
3745
|
-
console.log(
|
|
3453
|
+
console.log(chalk6.dim(` Domain: ${deployStatus.productionDomain}`));
|
|
3746
3454
|
}
|
|
3747
3455
|
} else {
|
|
3748
3456
|
console.log(
|
|
3749
|
-
|
|
3457
|
+
chalk6.green(" Production: ") + chalk6.yellow("\u25CB") + chalk6.dim(" Aktivert, ingen deployments enda")
|
|
3750
3458
|
);
|
|
3751
3459
|
}
|
|
3752
3460
|
if (deployStatus.canPromote && productionEnabled) {
|
|
3753
3461
|
console.log();
|
|
3754
3462
|
console.log(
|
|
3755
|
-
|
|
3463
|
+
chalk6.cyan(' \u{1F4A1} Staging is ahead of production. Run "kode deploy --promote" to update.')
|
|
3756
3464
|
);
|
|
3757
3465
|
}
|
|
3758
3466
|
console.log();
|
|
3759
|
-
console.log(
|
|
3467
|
+
console.log(chalk6.bold("Scripts"));
|
|
3760
3468
|
const syncStatus = isSyncStale(syncMetadata);
|
|
3761
3469
|
if (syncStatus.isStale && syncStatus.lastSyncedAt) {
|
|
3762
3470
|
const syncDate = new Date(syncStatus.lastSyncedAt);
|
|
@@ -3767,20 +3475,20 @@ async function statusCommand(options) {
|
|
|
3767
3475
|
minute: "2-digit"
|
|
3768
3476
|
});
|
|
3769
3477
|
console.log(
|
|
3770
|
-
|
|
3478
|
+
chalk6.yellow(` \u26A0 Sist synkronisert: ${formatRelativeTime(syncStatus.lastSyncedAt)}`) + chalk6.dim(` (${dateStr})`)
|
|
3771
3479
|
);
|
|
3772
3480
|
} else if (!syncStatus.lastSyncedAt && syncMetadata.length === 0) {
|
|
3773
|
-
console.log(
|
|
3481
|
+
console.log(chalk6.yellow(` \u26A0 Aldri synkronisert - kj\xF8r "kode pull" f\xF8rst`));
|
|
3774
3482
|
}
|
|
3775
3483
|
console.log();
|
|
3776
3484
|
const scriptsDir = getScriptsDir(projectRoot, config);
|
|
3777
|
-
const localFiles =
|
|
3485
|
+
const localFiles = existsSync8(scriptsDir) ? readdirSync4(scriptsDir).filter((f) => f.endsWith(".js") || f.endsWith(".css")) : [];
|
|
3778
3486
|
const localBySlug = /* @__PURE__ */ new Map();
|
|
3779
3487
|
for (const file of localFiles) {
|
|
3780
3488
|
const slug = basename5(file, extname4(file));
|
|
3781
|
-
const filePath =
|
|
3782
|
-
const content =
|
|
3783
|
-
const stats =
|
|
3489
|
+
const filePath = join8(scriptsDir, file);
|
|
3490
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
3491
|
+
const stats = statSync2(filePath);
|
|
3784
3492
|
localBySlug.set(slug, { file, content, modified: stats.mtime });
|
|
3785
3493
|
}
|
|
3786
3494
|
const remoteBySlug = /* @__PURE__ */ new Map();
|
|
@@ -3798,48 +3506,48 @@ async function statusCommand(options) {
|
|
|
3798
3506
|
const remote = remoteBySlug.get(slug);
|
|
3799
3507
|
const meta = metadataBySlug.get(slug);
|
|
3800
3508
|
if (local && remote) {
|
|
3801
|
-
const scopeTag = remote.scope === "global" ?
|
|
3802
|
-
const localHash =
|
|
3509
|
+
const scopeTag = remote.scope === "global" ? chalk6.blue("[G]") : chalk6.magenta("[P]");
|
|
3510
|
+
const localHash = hashContent2(local.content);
|
|
3803
3511
|
const hasLocalChanges = meta && localHash !== meta.contentHash;
|
|
3804
3512
|
const lastPulledVersion = meta?.lastPulledVersion || 0;
|
|
3805
3513
|
const hasRemoteChanges = remote.current_version > lastPulledVersion && lastPulledVersion > 0;
|
|
3806
3514
|
if (hasLocalChanges && hasRemoteChanges) {
|
|
3807
3515
|
console.log(
|
|
3808
|
-
|
|
3516
|
+
chalk6.red(" \u26A0 ") + `${local.file}` + chalk6.dim(` v${lastPulledVersion}`) + chalk6.red(` \u2192 v${remote.current_version}`) + ` ${scopeTag}` + chalk6.red(" (konflikt)")
|
|
3809
3517
|
);
|
|
3810
3518
|
outdatedCount++;
|
|
3811
3519
|
} else if (hasLocalChanges) {
|
|
3812
3520
|
console.log(
|
|
3813
|
-
|
|
3521
|
+
chalk6.yellow(" M ") + `${local.file}` + chalk6.dim(` v${remote.current_version} ${scopeTag}`) + chalk6.yellow(" (lokalt endret)")
|
|
3814
3522
|
);
|
|
3815
3523
|
} else if (hasRemoteChanges) {
|
|
3816
3524
|
console.log(
|
|
3817
|
-
|
|
3525
|
+
chalk6.cyan(" \u26A0 ") + `${local.file}` + chalk6.dim(` v${lastPulledVersion}`) + chalk6.cyan(` \u2192 v${remote.current_version}`) + ` ${scopeTag}` + chalk6.cyan(" (server oppdatert)")
|
|
3818
3526
|
);
|
|
3819
3527
|
outdatedCount++;
|
|
3820
3528
|
} else if (local.content !== remote.content) {
|
|
3821
3529
|
console.log(
|
|
3822
|
-
|
|
3530
|
+
chalk6.yellow(" M ") + `${local.file}` + chalk6.dim(` v${remote.current_version} ${scopeTag}`) + chalk6.yellow(" (lokalt endret)")
|
|
3823
3531
|
);
|
|
3824
3532
|
} else {
|
|
3825
3533
|
console.log(
|
|
3826
|
-
|
|
3534
|
+
chalk6.green(" \u2713 ") + chalk6.dim(`${local.file}`) + chalk6.dim(` v${remote.current_version} ${scopeTag}`)
|
|
3827
3535
|
);
|
|
3828
3536
|
}
|
|
3829
3537
|
} else if (local && !remote) {
|
|
3830
3538
|
console.log(
|
|
3831
|
-
|
|
3539
|
+
chalk6.cyan(" + ") + `${local.file}` + chalk6.cyan(" (ny, ikke pushet)")
|
|
3832
3540
|
);
|
|
3833
3541
|
} else if (!local && remote) {
|
|
3834
3542
|
const ext = remote.type === "javascript" ? "js" : "css";
|
|
3835
3543
|
const fileName = `${slug}.${ext}`;
|
|
3836
3544
|
console.log(
|
|
3837
|
-
|
|
3545
|
+
chalk6.red(" - ") + chalk6.dim(`${fileName}`) + chalk6.red(" (kun p\xE5 server, ikke pullet)")
|
|
3838
3546
|
);
|
|
3839
3547
|
}
|
|
3840
3548
|
}
|
|
3841
3549
|
if (allSlugs.size === 0) {
|
|
3842
|
-
console.log(
|
|
3550
|
+
console.log(chalk6.dim(" Ingen skript enda."));
|
|
3843
3551
|
}
|
|
3844
3552
|
console.log();
|
|
3845
3553
|
const modified = [...allSlugs].filter((slug) => {
|
|
@@ -3847,7 +3555,7 @@ async function statusCommand(options) {
|
|
|
3847
3555
|
const remote = remoteBySlug.get(slug);
|
|
3848
3556
|
if (!local || !remote) return false;
|
|
3849
3557
|
const meta = metadataBySlug.get(slug);
|
|
3850
|
-
const localHash =
|
|
3558
|
+
const localHash = hashContent2(local.content);
|
|
3851
3559
|
return meta ? localHash !== meta.contentHash : local.content !== remote.content;
|
|
3852
3560
|
}).length;
|
|
3853
3561
|
const newLocal = [...allSlugs].filter(
|
|
@@ -3857,25 +3565,25 @@ async function statusCommand(options) {
|
|
|
3857
3565
|
(slug) => !localBySlug.has(slug) && remoteBySlug.has(slug)
|
|
3858
3566
|
).length;
|
|
3859
3567
|
if (modified > 0 || newLocal > 0 || remoteOnly > 0 || outdatedCount > 0) {
|
|
3860
|
-
console.log(
|
|
3568
|
+
console.log(chalk6.bold("Handlinger"));
|
|
3861
3569
|
console.log();
|
|
3862
3570
|
if (modified > 0) {
|
|
3863
|
-
console.log(
|
|
3571
|
+
console.log(chalk6.yellow(` ${modified} endret lokalt`) + chalk6.dim(' \u2192 "kode push"'));
|
|
3864
3572
|
}
|
|
3865
3573
|
if (newLocal > 0) {
|
|
3866
|
-
console.log(
|
|
3574
|
+
console.log(chalk6.cyan(` ${newLocal} nye lokale`) + chalk6.dim(' \u2192 "kode push"'));
|
|
3867
3575
|
}
|
|
3868
3576
|
if (outdatedCount > 0) {
|
|
3869
|
-
console.log(
|
|
3577
|
+
console.log(chalk6.cyan(` ${outdatedCount} utdaterte`) + chalk6.dim(' \u2192 "kode pull"'));
|
|
3870
3578
|
}
|
|
3871
3579
|
if (remoteOnly > 0) {
|
|
3872
|
-
console.log(
|
|
3580
|
+
console.log(chalk6.red(` ${remoteOnly} kun p\xE5 server`) + chalk6.dim(' \u2192 "kode pull"'));
|
|
3873
3581
|
}
|
|
3874
3582
|
console.log();
|
|
3875
3583
|
}
|
|
3876
3584
|
} catch (error) {
|
|
3877
3585
|
spinner.fail("Failed to fetch status");
|
|
3878
|
-
console.error(
|
|
3586
|
+
console.error(chalk6.red("\nError:"), error.message || error);
|
|
3879
3587
|
}
|
|
3880
3588
|
}
|
|
3881
3589
|
function formatDate(dateStr) {
|
|
@@ -3890,25 +3598,25 @@ function formatDate(dateStr) {
|
|
|
3890
3598
|
}
|
|
3891
3599
|
|
|
3892
3600
|
// src/commands/context.ts
|
|
3893
|
-
import
|
|
3894
|
-
import
|
|
3601
|
+
import chalk7 from "chalk";
|
|
3602
|
+
import ora6 from "ora";
|
|
3895
3603
|
import { spawn } from "child_process";
|
|
3896
3604
|
async function contextCommand(options) {
|
|
3897
3605
|
const projectRoot = findProjectRoot();
|
|
3898
3606
|
if (!projectRoot) {
|
|
3899
|
-
console.log(
|
|
3900
|
-
console.log(
|
|
3607
|
+
console.log(chalk7.red("Error: Not in a Cure Kode project."));
|
|
3608
|
+
console.log(chalk7.dim('Run "kode init" first.'));
|
|
3901
3609
|
return;
|
|
3902
3610
|
}
|
|
3903
3611
|
const config = getProjectConfig(projectRoot);
|
|
3904
3612
|
if (!config) {
|
|
3905
|
-
console.log(
|
|
3613
|
+
console.log(chalk7.red("Error: Invalid project configuration."));
|
|
3906
3614
|
return;
|
|
3907
3615
|
}
|
|
3908
3616
|
const contextPath = getContextPath(projectRoot);
|
|
3909
3617
|
const context = readContext(projectRoot);
|
|
3910
3618
|
if (options.refresh) {
|
|
3911
|
-
const spinner =
|
|
3619
|
+
const spinner = ora6("Refreshing context from server...").start();
|
|
3912
3620
|
try {
|
|
3913
3621
|
const siteResponse = await fetch(
|
|
3914
3622
|
`https://app.cure.no/api/cdn/sites/${config.siteId}`,
|
|
@@ -3957,28 +3665,28 @@ async function contextCommand(options) {
|
|
|
3957
3665
|
};
|
|
3958
3666
|
writeContext(projectRoot, newContext);
|
|
3959
3667
|
spinner.succeed("Context refreshed");
|
|
3960
|
-
console.log(
|
|
3668
|
+
console.log(chalk7.dim(`Updated: ${contextPath}`));
|
|
3961
3669
|
return;
|
|
3962
3670
|
} catch (error) {
|
|
3963
3671
|
spinner.fail("Failed to refresh context");
|
|
3964
|
-
console.log(
|
|
3672
|
+
console.log(chalk7.red(error instanceof Error ? error.message : "Unknown error"));
|
|
3965
3673
|
return;
|
|
3966
3674
|
}
|
|
3967
3675
|
}
|
|
3968
3676
|
if (options.edit) {
|
|
3969
3677
|
if (!context) {
|
|
3970
|
-
console.log(
|
|
3971
|
-
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.'));
|
|
3972
3680
|
return;
|
|
3973
3681
|
}
|
|
3974
3682
|
const editor = process.env.EDITOR || process.env.VISUAL || "vim";
|
|
3975
|
-
console.log(
|
|
3683
|
+
console.log(chalk7.dim(`Opening ${contextPath} in ${editor}...`));
|
|
3976
3684
|
const child = spawn(editor, [contextPath], {
|
|
3977
3685
|
stdio: "inherit"
|
|
3978
3686
|
});
|
|
3979
3687
|
child.on("error", (err) => {
|
|
3980
|
-
console.log(
|
|
3981
|
-
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}`));
|
|
3982
3690
|
});
|
|
3983
3691
|
return;
|
|
3984
3692
|
}
|
|
@@ -3991,38 +3699,38 @@ async function contextCommand(options) {
|
|
|
3991
3699
|
return;
|
|
3992
3700
|
}
|
|
3993
3701
|
if (!context) {
|
|
3994
|
-
console.log(
|
|
3995
|
-
console.log(
|
|
3702
|
+
console.log(chalk7.yellow("No context file found."));
|
|
3703
|
+
console.log(chalk7.dim('Run "kode context --refresh" to create one.'));
|
|
3996
3704
|
return;
|
|
3997
3705
|
}
|
|
3998
|
-
console.log(
|
|
3999
|
-
console.log(
|
|
4000
|
-
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}`));
|
|
4001
3709
|
console.log();
|
|
4002
3710
|
if (context.site.domain || context.site.stagingDomain) {
|
|
4003
3711
|
if (context.site.domain) {
|
|
4004
|
-
console.log(
|
|
3712
|
+
console.log(chalk7.dim("Domain: ") + context.site.domain);
|
|
4005
3713
|
}
|
|
4006
3714
|
if (context.site.stagingDomain) {
|
|
4007
|
-
console.log(
|
|
3715
|
+
console.log(chalk7.dim("Staging: ") + context.site.stagingDomain);
|
|
4008
3716
|
}
|
|
4009
3717
|
console.log();
|
|
4010
3718
|
}
|
|
4011
|
-
console.log(
|
|
3719
|
+
console.log(chalk7.bold("Scripts:"));
|
|
4012
3720
|
if (context.scripts.length === 0) {
|
|
4013
|
-
console.log(
|
|
3721
|
+
console.log(chalk7.dim(" (no scripts)"));
|
|
4014
3722
|
} else {
|
|
4015
3723
|
for (const script of context.scripts) {
|
|
4016
3724
|
const typeIcon = script.type === "css" ? "\u{1F3A8}" : "\u{1F4DC}";
|
|
4017
|
-
const scopeLabel = script.scope === "global" ?
|
|
4018
|
-
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}`) : "";
|
|
4019
3727
|
console.log(` ${typeIcon} ${script.slug} [${scopeLabel}]${purpose}`);
|
|
4020
3728
|
}
|
|
4021
3729
|
}
|
|
4022
3730
|
console.log();
|
|
4023
|
-
console.log(
|
|
3731
|
+
console.log(chalk7.bold("Notes:"));
|
|
4024
3732
|
if (context.notes.length === 0 || context.notes.length === 3 && context.notes[0].startsWith("(HTML")) {
|
|
4025
|
-
console.log(
|
|
3733
|
+
console.log(chalk7.dim(" (no notes yet)"));
|
|
4026
3734
|
} else {
|
|
4027
3735
|
for (const note of context.notes) {
|
|
4028
3736
|
if (!note.startsWith("(")) {
|
|
@@ -4031,67 +3739,67 @@ async function contextCommand(options) {
|
|
|
4031
3739
|
}
|
|
4032
3740
|
}
|
|
4033
3741
|
console.log();
|
|
4034
|
-
console.log(
|
|
3742
|
+
console.log(chalk7.bold("Recent Sessions:"));
|
|
4035
3743
|
if (context.sessions.length === 0) {
|
|
4036
|
-
console.log(
|
|
3744
|
+
console.log(chalk7.dim(" (no sessions recorded)"));
|
|
4037
3745
|
} else {
|
|
4038
3746
|
const recentSessions = context.sessions.slice(0, 3);
|
|
4039
3747
|
for (const session of recentSessions) {
|
|
4040
|
-
console.log(` ${
|
|
3748
|
+
console.log(` ${chalk7.dim(session.date)} ${session.agent}`);
|
|
4041
3749
|
console.log(` ${session.task}`);
|
|
4042
3750
|
if (session.changes.length > 0) {
|
|
4043
|
-
console.log(
|
|
3751
|
+
console.log(chalk7.dim(` ${session.changes.length} change(s)`));
|
|
4044
3752
|
}
|
|
4045
3753
|
}
|
|
4046
3754
|
if (context.sessions.length > 3) {
|
|
4047
|
-
console.log(
|
|
3755
|
+
console.log(chalk7.dim(` ... and ${context.sessions.length - 3} more sessions`));
|
|
4048
3756
|
}
|
|
4049
3757
|
}
|
|
4050
3758
|
console.log();
|
|
4051
|
-
console.log(
|
|
4052
|
-
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"));
|
|
4053
3761
|
}
|
|
4054
3762
|
|
|
4055
3763
|
// src/commands/sync-config.ts
|
|
4056
|
-
import
|
|
4057
|
-
import
|
|
4058
|
-
import { existsSync as
|
|
4059
|
-
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";
|
|
4060
3768
|
async function syncConfigCommand() {
|
|
4061
3769
|
const cwd = process.cwd();
|
|
4062
3770
|
const projectRoot = findProjectRoot(cwd);
|
|
4063
3771
|
if (!projectRoot) {
|
|
4064
|
-
console.log(
|
|
4065
|
-
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."));
|
|
4066
3774
|
return;
|
|
4067
3775
|
}
|
|
4068
3776
|
const config = getProjectConfig(projectRoot);
|
|
4069
3777
|
if (!config) {
|
|
4070
|
-
console.log(
|
|
3778
|
+
console.log(chalk8.red(" Feil: Kunne ikke lese prosjektkonfigurasjon."));
|
|
4071
3779
|
return;
|
|
4072
3780
|
}
|
|
4073
3781
|
if (!config.projectId) {
|
|
4074
|
-
console.log(
|
|
4075
|
-
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"'));
|
|
4076
3784
|
return;
|
|
4077
3785
|
}
|
|
4078
|
-
const spinner =
|
|
3786
|
+
const spinner = ora7("Henter prosjektkonfigurasjon...").start();
|
|
4079
3787
|
try {
|
|
4080
3788
|
const response = await fetch(`https://app.cure.no/api/cdn/sites/${config.siteId}/project`, {
|
|
4081
3789
|
headers: { "X-API-Key": config.apiKey }
|
|
4082
3790
|
});
|
|
4083
3791
|
if (!response.ok) {
|
|
4084
3792
|
spinner.fail("Kunne ikke hente prosjektkonfigurasjon");
|
|
4085
|
-
console.log(
|
|
3793
|
+
console.log(chalk8.dim(` HTTP ${response.status}: ${response.statusText}`));
|
|
4086
3794
|
return;
|
|
4087
3795
|
}
|
|
4088
3796
|
const projectCtx = await response.json();
|
|
4089
3797
|
spinner.succeed("Prosjektkonfigurasjon hentet");
|
|
4090
|
-
const mcpConfigPath =
|
|
3798
|
+
const mcpConfigPath = join9(projectRoot, ".mcp.json");
|
|
4091
3799
|
let mcpConfig = {};
|
|
4092
|
-
if (
|
|
3800
|
+
if (existsSync9(mcpConfigPath)) {
|
|
4093
3801
|
try {
|
|
4094
|
-
mcpConfig = JSON.parse(
|
|
3802
|
+
mcpConfig = JSON.parse(readFileSync9(mcpConfigPath, "utf-8"));
|
|
4095
3803
|
} catch {
|
|
4096
3804
|
}
|
|
4097
3805
|
}
|
|
@@ -4135,7 +3843,7 @@ async function syncConfigCommand() {
|
|
|
4135
3843
|
added.push(server.name);
|
|
4136
3844
|
}
|
|
4137
3845
|
}
|
|
4138
|
-
|
|
3846
|
+
writeFileSync6(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
4139
3847
|
spinner.start("Oppdaterer dokumentasjon...");
|
|
4140
3848
|
let scripts = [];
|
|
4141
3849
|
let pages = [];
|
|
@@ -4171,43 +3879,36 @@ async function syncConfigCommand() {
|
|
|
4171
3879
|
spinner.succeed("Dokumentasjon oppdatert");
|
|
4172
3880
|
console.log();
|
|
4173
3881
|
if (projectCtx.project) {
|
|
4174
|
-
console.log(
|
|
3882
|
+
console.log(chalk8.bold(" Prosjekt: ") + chalk8.cyan(projectCtx.project.name));
|
|
4175
3883
|
}
|
|
4176
3884
|
if (added.length > 0 || updated.length > 0) {
|
|
4177
|
-
console.log(
|
|
3885
|
+
console.log(chalk8.bold(" MCP-servere:"));
|
|
4178
3886
|
for (const name of added) {
|
|
4179
|
-
console.log(
|
|
3887
|
+
console.log(chalk8.green(` + ${name}`) + chalk8.dim(" (ny)"));
|
|
4180
3888
|
}
|
|
4181
3889
|
for (const name of updated) {
|
|
4182
|
-
console.log(
|
|
3890
|
+
console.log(chalk8.blue(` ~ ${name}`) + chalk8.dim(" (oppdatert)"));
|
|
4183
3891
|
}
|
|
4184
3892
|
} else if (projectCtx.mcp_servers.length === 0) {
|
|
4185
|
-
console.log(
|
|
3893
|
+
console.log(chalk8.dim(" Ingen prosjekt-MCP-servere konfigurert."));
|
|
4186
3894
|
} else {
|
|
4187
|
-
console.log(
|
|
3895
|
+
console.log(chalk8.dim(" Ingen endringer i MCP-servere."));
|
|
4188
3896
|
}
|
|
4189
3897
|
if (secretWarnings.length > 0) {
|
|
4190
3898
|
console.log();
|
|
4191
|
-
console.log(
|
|
3899
|
+
console.log(chalk8.yellow(" \u26A0\uFE0F Hemmeligheter som m\xE5 settes i .mcp.json:"));
|
|
4192
3900
|
for (const warning of secretWarnings) {
|
|
4193
|
-
console.log(
|
|
3901
|
+
console.log(chalk8.dim(` \u2022 ${warning}`));
|
|
4194
3902
|
}
|
|
4195
3903
|
}
|
|
4196
3904
|
console.log();
|
|
4197
3905
|
} catch (error) {
|
|
4198
3906
|
spinner.fail("Sync feilet");
|
|
4199
|
-
console.log(
|
|
3907
|
+
console.log(chalk8.red(` Feil: ${error.message}`));
|
|
4200
3908
|
}
|
|
4201
3909
|
}
|
|
4202
3910
|
|
|
4203
3911
|
export {
|
|
4204
|
-
findProjectRoot,
|
|
4205
|
-
getProjectConfig,
|
|
4206
|
-
saveProjectConfig,
|
|
4207
|
-
getApiUrl,
|
|
4208
|
-
getApiKey,
|
|
4209
|
-
setGlobalConfig,
|
|
4210
|
-
getScriptsDir,
|
|
4211
3912
|
getContextPath,
|
|
4212
3913
|
parseContext,
|
|
4213
3914
|
serializeContext,
|
|
@@ -4218,17 +3919,9 @@ export {
|
|
|
4218
3919
|
updateScriptPurpose,
|
|
4219
3920
|
generateInitialContext,
|
|
4220
3921
|
generateClaudeMd,
|
|
4221
|
-
updateKodeDocs,
|
|
4222
|
-
scriptsToDocsFormat,
|
|
4223
|
-
pagesToInfoFormat,
|
|
4224
|
-
updateClaudeMd,
|
|
4225
3922
|
CLI_VERSION,
|
|
4226
3923
|
showCompactBanner,
|
|
4227
3924
|
initCommand,
|
|
4228
|
-
KodeApiError,
|
|
4229
|
-
KodeApiClient,
|
|
4230
|
-
createApiClient,
|
|
4231
|
-
pullCommand,
|
|
4232
3925
|
pushCommand,
|
|
4233
3926
|
watchCommand,
|
|
4234
3927
|
deployCommand,
|