@curenorway/kode-cli 1.8.0 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-P74WQ3SF.js → chunk-CUZJE4JZ.js} +506 -50
- package/dist/cli.js +235 -81
- package/dist/index.d.ts +47 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -426,6 +426,35 @@ Scripts have two properties: **scope** and **autoLoad**.
|
|
|
426
426
|
|
|
427
427
|
**CLI flags**: \`kode push --auto-load\` or \`kode push --no-auto-load\`
|
|
428
428
|
|
|
429
|
+
### Script Metadata (Documentation)
|
|
430
|
+
|
|
431
|
+
**ALWAYS provide metadata when creating or updating scripts.** This helps other AI agents and the Chrome extension understand what scripts do.
|
|
432
|
+
|
|
433
|
+
| Field | Description | Example |
|
|
434
|
+
|-------|-------------|---------|
|
|
435
|
+
| \`purpose\` | What the script does (1-2 sentences) | "Adds smooth scroll behavior to anchor links" |
|
|
436
|
+
| \`triggers\` | When the script runs | \`[{ event: 'domReady' }, { event: 'click', selector: '.nav-link' }]\` |
|
|
437
|
+
| \`dependencies\` | External libraries needed | \`[{ name: 'gsap', version: '3.12' }]\` |
|
|
438
|
+
| \`domTargets\` | CSS selectors the script affects | \`[{ selector: '.hero-title' }, { selector: '#contact-form' }]\` |
|
|
439
|
+
|
|
440
|
+
**MCP Tools**:
|
|
441
|
+
- \`kode_create_script\` / \`kode_update_script\` \u2192 include \`metadata: { purpose: "..." }\`
|
|
442
|
+
- \`kode_analyze_script\` \u2192 auto-detect triggers, dependencies, and DOM targets from code
|
|
443
|
+
- \`kode_get_script_metadata\` \u2192 get existing metadata and AI summary
|
|
444
|
+
|
|
445
|
+
**Example**:
|
|
446
|
+
\`\`\`json
|
|
447
|
+
{
|
|
448
|
+
"name": "smooth-scroll",
|
|
449
|
+
"content": "...",
|
|
450
|
+
"metadata": {
|
|
451
|
+
"purpose": "Adds smooth scroll animation to all anchor links",
|
|
452
|
+
"triggers": [{ "event": "click", "selector": "a[href^='#']" }],
|
|
453
|
+
"domTargets": [{ "selector": "a[href^='#']" }]
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
\`\`\`
|
|
457
|
+
|
|
429
458
|
### Context
|
|
430
459
|
|
|
431
460
|
**Read \`.cure-kode/context.md\` before working on scripts** - it contains:
|
|
@@ -597,13 +626,119 @@ if (CK.isLoaded('my-modal-script')) {
|
|
|
597
626
|
}
|
|
598
627
|
\`\`\`
|
|
599
628
|
|
|
629
|
+
## Script Metadata (Documentation)
|
|
630
|
+
|
|
631
|
+
**IMPORTANT: Always provide metadata when creating or updating scripts!**
|
|
632
|
+
|
|
633
|
+
Metadata helps:
|
|
634
|
+
- Other AI agents understand what scripts do
|
|
635
|
+
- The Chrome extension display script info
|
|
636
|
+
- Future debugging and maintenance
|
|
637
|
+
|
|
638
|
+
### Metadata Fields
|
|
639
|
+
|
|
640
|
+
| Field | Type | Description |
|
|
641
|
+
|-------|------|-------------|
|
|
642
|
+
| \`purpose\` | string | 1-2 sentence description of what the script does |
|
|
643
|
+
| \`triggers\` | array | Events that activate the script |
|
|
644
|
+
| \`dependencies\` | array | External libraries the script needs |
|
|
645
|
+
| \`domTargets\` | array | CSS selectors the script manipulates |
|
|
646
|
+
|
|
647
|
+
### Trigger Events
|
|
648
|
+
|
|
649
|
+
\`\`\`typescript
|
|
650
|
+
type TriggerEvent =
|
|
651
|
+
| 'domReady' // DOMContentLoaded
|
|
652
|
+
| 'load' // window.onload
|
|
653
|
+
| 'scroll' // scroll events
|
|
654
|
+
| 'click' // click events
|
|
655
|
+
| 'hover' // mouseenter/mouseleave
|
|
656
|
+
| 'resize' // window resize
|
|
657
|
+
| 'mutation' // MutationObserver
|
|
658
|
+
| 'interval' // setInterval
|
|
659
|
+
| 'timeout' // setTimeout
|
|
660
|
+
| 'formSubmit' // form submission
|
|
661
|
+
| 'visibility' // IntersectionObserver
|
|
662
|
+
| 'immediate' // runs immediately
|
|
663
|
+
| 'custom' // custom event
|
|
664
|
+
|
|
665
|
+
// Each trigger can have optional selector/target
|
|
666
|
+
{ event: 'click', selector: '.btn-primary' }
|
|
667
|
+
{ event: 'scroll', target: 'window' }
|
|
668
|
+
\`\`\`
|
|
669
|
+
|
|
670
|
+
### Providing Metadata via MCP
|
|
671
|
+
|
|
672
|
+
When creating scripts:
|
|
673
|
+
\`\`\`javascript
|
|
674
|
+
kode_create_script({
|
|
675
|
+
name: 'accordion',
|
|
676
|
+
slug: 'accordion',
|
|
677
|
+
type: 'javascript',
|
|
678
|
+
content: '// accordion code...',
|
|
679
|
+
metadata: {
|
|
680
|
+
purpose: 'Expands and collapses FAQ accordion items on click',
|
|
681
|
+
triggers: [
|
|
682
|
+
{ event: 'domReady' },
|
|
683
|
+
{ event: 'click', selector: '.accordion-trigger' }
|
|
684
|
+
],
|
|
685
|
+
domTargets: [
|
|
686
|
+
{ selector: '.accordion-trigger' },
|
|
687
|
+
{ selector: '.accordion-content' }
|
|
688
|
+
]
|
|
689
|
+
}
|
|
690
|
+
})
|
|
691
|
+
\`\`\`
|
|
692
|
+
|
|
693
|
+
When updating scripts:
|
|
694
|
+
\`\`\`javascript
|
|
695
|
+
kode_update_script({
|
|
696
|
+
scriptId: 'xxx',
|
|
697
|
+
content: '// updated code...',
|
|
698
|
+
metadata: {
|
|
699
|
+
purpose: 'Updated description...',
|
|
700
|
+
// ... other fields
|
|
701
|
+
}
|
|
702
|
+
})
|
|
703
|
+
\`\`\`
|
|
704
|
+
|
|
705
|
+
### Auto-Analyze Scripts
|
|
706
|
+
|
|
707
|
+
Use \`kode_analyze_script\` to automatically detect metadata from code:
|
|
708
|
+
\`\`\`javascript
|
|
709
|
+
kode_analyze_script({ scriptId: 'xxx' })
|
|
710
|
+
// Returns detected: triggers, dependencies, domTargets
|
|
711
|
+
\`\`\`
|
|
712
|
+
|
|
713
|
+
The analyzer detects:
|
|
714
|
+
- **DOM selectors**: querySelector, getElementById, getElementsByClassName
|
|
715
|
+
- **Event listeners**: addEventListener, on events
|
|
716
|
+
- **Dependencies**: Library globals (gsap, Swiper, jQuery, etc.)
|
|
717
|
+
- **Timing patterns**: DOMContentLoaded, load, intervals
|
|
718
|
+
|
|
719
|
+
### Get Script Metadata
|
|
720
|
+
|
|
721
|
+
\`\`\`javascript
|
|
722
|
+
kode_get_script_metadata({ scriptId: 'xxx' })
|
|
723
|
+
// Returns: purpose, triggers, dependencies, domTargets, aiSummary
|
|
724
|
+
\`\`\`
|
|
725
|
+
|
|
726
|
+
### Best Practice Workflow
|
|
727
|
+
|
|
728
|
+
1. **Create script** with at least \`purpose\` in metadata
|
|
729
|
+
2. **Run analyze** to auto-detect triggers and targets
|
|
730
|
+
3. **Update metadata** with any corrections or additions
|
|
731
|
+
4. **Deploy** - metadata is included in init.js for Chrome extension
|
|
732
|
+
|
|
600
733
|
## Best Practices
|
|
601
734
|
|
|
602
735
|
1. **Always deploy to staging first** - Test before production
|
|
603
|
-
2. **
|
|
604
|
-
3. **Use
|
|
605
|
-
4. **
|
|
606
|
-
5. **
|
|
736
|
+
2. **Always provide metadata** - At minimum, include \`purpose\` when creating scripts
|
|
737
|
+
3. **Use page-specific scripts** for page-only functionality
|
|
738
|
+
4. **Use \`autoLoad: false\`** for lazy-loaded features (modals, etc.)
|
|
739
|
+
5. **Document your changes** - Update context.md
|
|
740
|
+
6. **Cache page HTML** - Use \`kode html <url> --save\` to understand structure
|
|
741
|
+
7. **Run analyze after creating** - Use \`kode_analyze_script\` to auto-detect metadata
|
|
607
742
|
|
|
608
743
|
## MCP Tools
|
|
609
744
|
|
|
@@ -611,10 +746,12 @@ If using the Kode MCP server, these tools are available:
|
|
|
611
746
|
|
|
612
747
|
### Script Management
|
|
613
748
|
- \`kode_list_scripts\` - List all scripts with scope and autoLoad status
|
|
614
|
-
- \`kode_get_script\` - Get script content
|
|
615
|
-
- \`kode_create_script\` - Create new script (accepts \`scope
|
|
616
|
-
- \`kode_update_script\` - Update script content, scope, or
|
|
749
|
+
- \`kode_get_script\` - Get script content (use \`includeContent: false\` to save tokens)
|
|
750
|
+
- \`kode_create_script\` - Create new script (accepts \`scope\`, \`autoLoad\`, \`metadata\`)
|
|
751
|
+
- \`kode_update_script\` - Update script content, scope, autoLoad, or metadata
|
|
617
752
|
- \`kode_delete_script\` - Delete a script
|
|
753
|
+
- \`kode_analyze_script\` - Auto-detect metadata (triggers, dependencies, DOM targets) from code
|
|
754
|
+
- \`kode_get_script_metadata\` - Get script metadata and AI summary
|
|
618
755
|
|
|
619
756
|
### Page Assignment (for page-specific scripts)
|
|
620
757
|
- \`kode_list_pages\` - List page definitions with URL patterns
|
|
@@ -1200,11 +1337,16 @@ async function initCommand(options) {
|
|
|
1200
1337
|
console.log(chalk.bold("\n\u{1F680} Cure Kode Setup\n"));
|
|
1201
1338
|
const { apiKey } = await prompt([
|
|
1202
1339
|
{
|
|
1203
|
-
type: "
|
|
1340
|
+
type: "input",
|
|
1204
1341
|
name: "apiKey",
|
|
1205
1342
|
message: "API Key (from Cure App \u2192 Tools \u2192 Kode \u2192 API Keys):",
|
|
1206
1343
|
initial: options.apiKey,
|
|
1207
|
-
validate: (value) =>
|
|
1344
|
+
validate: (value) => {
|
|
1345
|
+
if (value.length === 0) return "API key is required";
|
|
1346
|
+
if (!value.startsWith("ck_")) return "API key should start with ck_";
|
|
1347
|
+
if (value.length < 30) return "API key looks truncated - make sure you copied the full key";
|
|
1348
|
+
return true;
|
|
1349
|
+
}
|
|
1208
1350
|
}
|
|
1209
1351
|
]);
|
|
1210
1352
|
const spinner = ora("Validating API key...").start();
|
|
@@ -1268,7 +1410,7 @@ config.json
|
|
|
1268
1410
|
mcpConfig.mcpServers["cure-kode"] = {
|
|
1269
1411
|
type: "stdio",
|
|
1270
1412
|
command: "npx",
|
|
1271
|
-
args: ["-y", "@curenorway/kode-mcp"]
|
|
1413
|
+
args: ["-y", "@curenorway/kode-mcp@^1.3.0"]
|
|
1272
1414
|
};
|
|
1273
1415
|
let webflowToken = site.webflow_token;
|
|
1274
1416
|
let webflowMcpMethod = null;
|
|
@@ -1319,7 +1461,7 @@ config.json
|
|
|
1319
1461
|
mcpConfig.mcpServers["webflow"] = {
|
|
1320
1462
|
type: "stdio",
|
|
1321
1463
|
command: "npx",
|
|
1322
|
-
args: ["-y", "webflow-mcp-server
|
|
1464
|
+
args: ["-y", "webflow-mcp-server@^0.6.0"],
|
|
1323
1465
|
env: {
|
|
1324
1466
|
WEBFLOW_TOKEN: webflowToken
|
|
1325
1467
|
}
|
|
@@ -1333,7 +1475,7 @@ config.json
|
|
|
1333
1475
|
mcpConfig.mcpServers["playwright"] = {
|
|
1334
1476
|
type: "stdio",
|
|
1335
1477
|
command: "npx",
|
|
1336
|
-
args: ["-y", "@playwright/mcp
|
|
1478
|
+
args: ["-y", "@playwright/mcp@^0.0.21"]
|
|
1337
1479
|
};
|
|
1338
1480
|
spinner.start("Generating AI context files...");
|
|
1339
1481
|
writeFileSync3(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
@@ -1504,6 +1646,80 @@ config.json
|
|
|
1504
1646
|
}
|
|
1505
1647
|
}
|
|
1506
1648
|
|
|
1649
|
+
// src/lib/retry.ts
|
|
1650
|
+
function isRetryableError(error) {
|
|
1651
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
1652
|
+
return true;
|
|
1653
|
+
}
|
|
1654
|
+
const err = error;
|
|
1655
|
+
const statusCode = err.statusCode || err.status;
|
|
1656
|
+
if (statusCode && statusCode >= 400 && statusCode < 500) {
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1659
|
+
if (statusCode && statusCode >= 500) {
|
|
1660
|
+
return true;
|
|
1661
|
+
}
|
|
1662
|
+
if (err.code === "ECONNRESET" || err.code === "ETIMEDOUT" || err.code === "ENOTFOUND") {
|
|
1663
|
+
return true;
|
|
1664
|
+
}
|
|
1665
|
+
if (error instanceof Error) {
|
|
1666
|
+
const message = error.message.toLowerCase();
|
|
1667
|
+
if (message.includes("network") || message.includes("timeout") || message.includes("connection") || message.includes("socket")) {
|
|
1668
|
+
return true;
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
return false;
|
|
1672
|
+
}
|
|
1673
|
+
function calculateDelay(attempt, baseDelayMs, maxDelayMs, backoffMultiplier, jitter) {
|
|
1674
|
+
const exponentialDelay = baseDelayMs * Math.pow(backoffMultiplier, attempt - 1);
|
|
1675
|
+
const cappedDelay = Math.min(exponentialDelay, maxDelayMs);
|
|
1676
|
+
if (!jitter) {
|
|
1677
|
+
return cappedDelay;
|
|
1678
|
+
}
|
|
1679
|
+
const jitterRange = cappedDelay * 0.25;
|
|
1680
|
+
const jitterOffset = (Math.random() - 0.5) * 2 * jitterRange;
|
|
1681
|
+
return Math.max(0, Math.round(cappedDelay + jitterOffset));
|
|
1682
|
+
}
|
|
1683
|
+
function sleep(ms) {
|
|
1684
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1685
|
+
}
|
|
1686
|
+
async function withRetry(fn, options = {}) {
|
|
1687
|
+
const {
|
|
1688
|
+
maxAttempts = 3,
|
|
1689
|
+
baseDelayMs = 500,
|
|
1690
|
+
maxDelayMs = 5e3,
|
|
1691
|
+
backoffMultiplier = 2,
|
|
1692
|
+
jitter = true,
|
|
1693
|
+
isRetryable = isRetryableError,
|
|
1694
|
+
onRetry
|
|
1695
|
+
} = options;
|
|
1696
|
+
let lastError;
|
|
1697
|
+
let totalDelayMs = 0;
|
|
1698
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
1699
|
+
try {
|
|
1700
|
+
return await fn();
|
|
1701
|
+
} catch (error) {
|
|
1702
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1703
|
+
if (attempt >= maxAttempts || !isRetryable(error)) {
|
|
1704
|
+
throw lastError;
|
|
1705
|
+
}
|
|
1706
|
+
const delayMs = calculateDelay(
|
|
1707
|
+
attempt,
|
|
1708
|
+
baseDelayMs,
|
|
1709
|
+
maxDelayMs,
|
|
1710
|
+
backoffMultiplier,
|
|
1711
|
+
jitter
|
|
1712
|
+
);
|
|
1713
|
+
totalDelayMs += delayMs;
|
|
1714
|
+
if (onRetry) {
|
|
1715
|
+
onRetry(attempt, error, delayMs);
|
|
1716
|
+
}
|
|
1717
|
+
await sleep(delayMs);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
throw lastError || new Error("Retry failed");
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1507
1723
|
// src/api.ts
|
|
1508
1724
|
var KodeApiError = class extends Error {
|
|
1509
1725
|
constructor(message, statusCode, response) {
|
|
@@ -1526,23 +1742,38 @@ var KodeApiClient = class {
|
|
|
1526
1742
|
}
|
|
1527
1743
|
async request(endpoint, options = {}) {
|
|
1528
1744
|
const url = `${this.baseUrl}${endpoint}`;
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1745
|
+
return withRetry(
|
|
1746
|
+
async () => {
|
|
1747
|
+
const response = await fetch(url, {
|
|
1748
|
+
...options,
|
|
1749
|
+
headers: {
|
|
1750
|
+
"Content-Type": "application/json",
|
|
1751
|
+
"X-API-Key": this.apiKey,
|
|
1752
|
+
...options.headers
|
|
1753
|
+
}
|
|
1754
|
+
});
|
|
1755
|
+
const data = await response.json();
|
|
1756
|
+
if (!response.ok) {
|
|
1757
|
+
throw new KodeApiError(
|
|
1758
|
+
data.error || `Request failed with status ${response.status}`,
|
|
1759
|
+
response.status,
|
|
1760
|
+
data
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
return data;
|
|
1764
|
+
},
|
|
1765
|
+
{
|
|
1766
|
+
maxAttempts: 3,
|
|
1767
|
+
baseDelayMs: 500,
|
|
1768
|
+
// Custom retry check: retry on network errors and 5xx, but not 4xx
|
|
1769
|
+
isRetryable: (error) => {
|
|
1770
|
+
if (error instanceof KodeApiError) {
|
|
1771
|
+
return error.statusCode >= 500;
|
|
1772
|
+
}
|
|
1773
|
+
return isRetryableError(error);
|
|
1774
|
+
}
|
|
1535
1775
|
}
|
|
1536
|
-
|
|
1537
|
-
const data = await response.json();
|
|
1538
|
-
if (!response.ok) {
|
|
1539
|
-
throw new KodeApiError(
|
|
1540
|
-
data.error || `Request failed with status ${response.status}`,
|
|
1541
|
-
response.status,
|
|
1542
|
-
data
|
|
1543
|
-
);
|
|
1544
|
-
}
|
|
1545
|
-
return data;
|
|
1776
|
+
);
|
|
1546
1777
|
}
|
|
1547
1778
|
// Sites
|
|
1548
1779
|
async getSite(siteId) {
|
|
@@ -1613,6 +1844,25 @@ var KodeApiClient = class {
|
|
|
1613
1844
|
async getDeploymentStatus(siteId) {
|
|
1614
1845
|
return this.request(`/api/cdn/sites/${siteId}/deployments/status`);
|
|
1615
1846
|
}
|
|
1847
|
+
async rollback(siteId, environment = "staging") {
|
|
1848
|
+
return this.request("/api/cdn/deploy/rollback", {
|
|
1849
|
+
method: "POST",
|
|
1850
|
+
body: JSON.stringify({
|
|
1851
|
+
siteId,
|
|
1852
|
+
environment
|
|
1853
|
+
})
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
// Production enabled toggle (v2.3)
|
|
1857
|
+
async setProductionEnabled(siteId, enabled, productionDomain) {
|
|
1858
|
+
return this.request(`/api/cdn/sites/${siteId}/production`, {
|
|
1859
|
+
method: "POST",
|
|
1860
|
+
body: JSON.stringify({
|
|
1861
|
+
enabled,
|
|
1862
|
+
productionDomain
|
|
1863
|
+
})
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1616
1866
|
// HTML Fetch
|
|
1617
1867
|
async fetchHtml(url) {
|
|
1618
1868
|
return this.request("/api/cdn/fetch-html", {
|
|
@@ -1620,6 +1870,16 @@ var KodeApiClient = class {
|
|
|
1620
1870
|
body: JSON.stringify({ url })
|
|
1621
1871
|
});
|
|
1622
1872
|
}
|
|
1873
|
+
// Lock management
|
|
1874
|
+
async getLockStatus(siteId) {
|
|
1875
|
+
return this.request(`/api/cdn/deploy/lock?siteId=${siteId}`);
|
|
1876
|
+
}
|
|
1877
|
+
async forceReleaseLock(siteId) {
|
|
1878
|
+
return this.request("/api/cdn/deploy/lock", {
|
|
1879
|
+
method: "DELETE",
|
|
1880
|
+
body: JSON.stringify({ siteId })
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1623
1883
|
};
|
|
1624
1884
|
function createApiClient(config) {
|
|
1625
1885
|
return new KodeApiClient(config);
|
|
@@ -1776,11 +2036,16 @@ async function pushCommand(options) {
|
|
|
1776
2036
|
let skipped = 0;
|
|
1777
2037
|
spinner.stop();
|
|
1778
2038
|
console.log();
|
|
2039
|
+
let emptyScriptCount = 0;
|
|
1779
2040
|
for (const file of filesToPush) {
|
|
1780
2041
|
const filePath = join5(scriptsDir, file);
|
|
1781
2042
|
const content = readFileSync4(filePath, "utf-8");
|
|
1782
2043
|
const slug = basename(file, extname(file));
|
|
1783
2044
|
const type = extname(file) === ".js" ? "javascript" : "css";
|
|
2045
|
+
if (content.trim().length === 0) {
|
|
2046
|
+
console.log(chalk3.yellow(` \u26A0 ${file}`) + chalk3.dim(" (empty file)"));
|
|
2047
|
+
emptyScriptCount++;
|
|
2048
|
+
}
|
|
1784
2049
|
const remoteScript = remoteScripts.find((s) => s.slug === slug);
|
|
1785
2050
|
const localMeta = metadata.find((m) => m.slug === slug);
|
|
1786
2051
|
if (remoteScript) {
|
|
@@ -1827,6 +2092,11 @@ async function pushCommand(options) {
|
|
|
1827
2092
|
if (skipped > 0) {
|
|
1828
2093
|
console.log(chalk3.dim(` Skipped ${skipped} unchanged script(s)`));
|
|
1829
2094
|
}
|
|
2095
|
+
if (emptyScriptCount > 0) {
|
|
2096
|
+
console.log(chalk3.yellow(`
|
|
2097
|
+
\u26A0\uFE0F ${emptyScriptCount} empty script(s) pushed`));
|
|
2098
|
+
console.log(chalk3.dim(" Empty scripts will have no effect when deployed."));
|
|
2099
|
+
}
|
|
1830
2100
|
const updatedScripts = await client.listScripts(config.siteId);
|
|
1831
2101
|
const updatedMetadata = updatedScripts.map((s) => ({
|
|
1832
2102
|
id: s.id,
|
|
@@ -1895,15 +2165,59 @@ async function watchCommand(options) {
|
|
|
1895
2165
|
}
|
|
1896
2166
|
const pendingChanges = /* @__PURE__ */ new Map();
|
|
1897
2167
|
const DEBOUNCE_MS = 500;
|
|
1898
|
-
const
|
|
2168
|
+
const failedSyncs = /* @__PURE__ */ new Map();
|
|
2169
|
+
let successCount = 0;
|
|
2170
|
+
let errorCount = 0;
|
|
2171
|
+
const RETRY_DELAY_MS = 3e4;
|
|
2172
|
+
const MAX_RETRY_ATTEMPTS = 3;
|
|
2173
|
+
const printStatus = () => {
|
|
2174
|
+
if (failedSyncs.size === 0 && successCount === 0) return;
|
|
2175
|
+
const statusParts = [];
|
|
2176
|
+
if (successCount > 0) {
|
|
2177
|
+
statusParts.push(chalk4.green(`${successCount} synced`));
|
|
2178
|
+
}
|
|
2179
|
+
if (failedSyncs.size > 0) {
|
|
2180
|
+
statusParts.push(chalk4.red(`${failedSyncs.size} pending errors`));
|
|
2181
|
+
}
|
|
2182
|
+
console.log(chalk4.dim(`
|
|
2183
|
+
\u2500\u2500\u2500 Status: ${statusParts.join(", ")} \u2500\u2500\u2500
|
|
2184
|
+
`));
|
|
2185
|
+
};
|
|
2186
|
+
const retryFailedSyncs = async () => {
|
|
2187
|
+
for (const [filePath, failed] of failedSyncs.entries()) {
|
|
2188
|
+
if (!existsSync6(filePath)) {
|
|
2189
|
+
failedSyncs.delete(filePath);
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
if (failed.attempts >= MAX_RETRY_ATTEMPTS) {
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
const timeSinceLastAttempt = Date.now() - failed.lastAttempt.getTime();
|
|
2196
|
+
if (timeSinceLastAttempt < RETRY_DELAY_MS) {
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
2200
|
+
console.log(
|
|
2201
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.yellow(`\u21BB Retrying ${failed.fileName}...`) + chalk4.dim(` (attempt ${failed.attempts + 1}/${MAX_RETRY_ATTEMPTS})`)
|
|
2202
|
+
);
|
|
2203
|
+
await handleChange(
|
|
2204
|
+
filePath,
|
|
2205
|
+
true
|
|
2206
|
+
/* isRetry */
|
|
2207
|
+
);
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
const retryInterval = setInterval(retryFailedSyncs, RETRY_DELAY_MS);
|
|
2211
|
+
const handleChange = async (filePath, isRetry = false) => {
|
|
1899
2212
|
const fileName = basename2(filePath);
|
|
1900
2213
|
const slug = basename2(fileName, extname2(fileName));
|
|
1901
2214
|
const type = extname2(fileName) === ".js" ? "javascript" : "css";
|
|
1902
|
-
if (
|
|
1903
|
-
|
|
2215
|
+
if (!isRetry) {
|
|
2216
|
+
if (pendingChanges.has(filePath)) {
|
|
2217
|
+
clearTimeout(pendingChanges.get(filePath));
|
|
2218
|
+
}
|
|
1904
2219
|
}
|
|
1905
|
-
const
|
|
1906
|
-
pendingChanges.delete(filePath);
|
|
2220
|
+
const syncFile = async () => {
|
|
1907
2221
|
try {
|
|
1908
2222
|
const content = readFileSync5(filePath, "utf-8");
|
|
1909
2223
|
const remoteScript = remoteScripts.find((s) => s.slug === slug);
|
|
@@ -1911,6 +2225,9 @@ async function watchCommand(options) {
|
|
|
1911
2225
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
1912
2226
|
if (remoteScript) {
|
|
1913
2227
|
if (remoteScript.content === content) {
|
|
2228
|
+
if (failedSyncs.has(filePath)) {
|
|
2229
|
+
failedSyncs.delete(filePath);
|
|
2230
|
+
}
|
|
1914
2231
|
return;
|
|
1915
2232
|
}
|
|
1916
2233
|
await client.updateScript(remoteScript.id, {
|
|
@@ -1919,9 +2236,24 @@ async function watchCommand(options) {
|
|
|
1919
2236
|
});
|
|
1920
2237
|
remoteScript.content = content;
|
|
1921
2238
|
remoteScript.current_version++;
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2239
|
+
if (failedSyncs.has(filePath)) {
|
|
2240
|
+
const wasRetry = failedSyncs.get(filePath).attempts > 0;
|
|
2241
|
+
failedSyncs.delete(filePath);
|
|
2242
|
+
if (wasRetry) {
|
|
2243
|
+
console.log(
|
|
2244
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.green(`\u2713 ${fileName}`) + chalk4.dim(` \u2192 v${remoteScript.current_version}`) + chalk4.cyan(" (recovered)")
|
|
2245
|
+
);
|
|
2246
|
+
} else {
|
|
2247
|
+
console.log(
|
|
2248
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.green(`\u2713 ${fileName}`) + chalk4.dim(` \u2192 v${remoteScript.current_version}`)
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2251
|
+
} else {
|
|
2252
|
+
console.log(
|
|
2253
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.green(`\u2713 ${fileName}`) + chalk4.dim(` \u2192 v${remoteScript.current_version}`)
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
successCount++;
|
|
1925
2257
|
if (options.deploy) {
|
|
1926
2258
|
try {
|
|
1927
2259
|
await client.deploy(config.siteId, config.environment || "staging");
|
|
@@ -1930,7 +2262,7 @@ async function watchCommand(options) {
|
|
|
1930
2262
|
);
|
|
1931
2263
|
} catch (deployError) {
|
|
1932
2264
|
console.log(
|
|
1933
|
-
chalk4.dim(`[${timestamp}] `) + chalk4.red(` \u21B3 Deploy failed`)
|
|
2265
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.red(` \u21B3 Deploy failed: ${deployError.message || "Unknown error"}`)
|
|
1934
2266
|
);
|
|
1935
2267
|
}
|
|
1936
2268
|
}
|
|
@@ -1943,9 +2275,17 @@ async function watchCommand(options) {
|
|
|
1943
2275
|
content
|
|
1944
2276
|
});
|
|
1945
2277
|
remoteScripts.push(newScript);
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2278
|
+
if (failedSyncs.has(filePath)) {
|
|
2279
|
+
failedSyncs.delete(filePath);
|
|
2280
|
+
console.log(
|
|
2281
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.green(`\u2713 ${fileName}`) + chalk4.cyan(" (created, recovered)")
|
|
2282
|
+
);
|
|
2283
|
+
} else {
|
|
2284
|
+
console.log(
|
|
2285
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.green(`\u2713 ${fileName}`) + chalk4.cyan(" (created)")
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
successCount++;
|
|
1949
2289
|
const updatedMetadata = remoteScripts.map((s) => ({
|
|
1950
2290
|
id: s.id,
|
|
1951
2291
|
slug: s.slug,
|
|
@@ -1960,10 +2300,36 @@ async function watchCommand(options) {
|
|
|
1960
2300
|
}
|
|
1961
2301
|
} catch (error) {
|
|
1962
2302
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2303
|
+
const errorMessage = error.message || "Unknown error";
|
|
2304
|
+
const existing = failedSyncs.get(filePath);
|
|
2305
|
+
const attempts = existing ? existing.attempts + 1 : 1;
|
|
2306
|
+
failedSyncs.set(filePath, {
|
|
2307
|
+
filePath,
|
|
2308
|
+
fileName,
|
|
2309
|
+
error: errorMessage,
|
|
2310
|
+
attempts,
|
|
2311
|
+
lastAttempt: /* @__PURE__ */ new Date()
|
|
2312
|
+
});
|
|
2313
|
+
errorCount++;
|
|
2314
|
+
if (attempts >= MAX_RETRY_ATTEMPTS) {
|
|
2315
|
+
console.log(
|
|
2316
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.red(`\u2717 ${fileName}`) + chalk4.dim(` - ${errorMessage}`) + chalk4.red(` (gave up after ${MAX_RETRY_ATTEMPTS} attempts)`)
|
|
2317
|
+
);
|
|
2318
|
+
} else {
|
|
2319
|
+
console.log(
|
|
2320
|
+
chalk4.dim(`[${timestamp}] `) + chalk4.red(`\u2717 ${fileName}`) + chalk4.dim(` - ${errorMessage}`) + chalk4.yellow(` (will retry in ${RETRY_DELAY_MS / 1e3}s)`)
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2323
|
+
printStatus();
|
|
1966
2324
|
}
|
|
2325
|
+
};
|
|
2326
|
+
if (isRetry) {
|
|
2327
|
+
await syncFile();
|
|
2328
|
+
return;
|
|
2329
|
+
}
|
|
2330
|
+
const timeout = setTimeout(async () => {
|
|
2331
|
+
pendingChanges.delete(filePath);
|
|
2332
|
+
await syncFile();
|
|
1967
2333
|
}, DEBOUNCE_MS);
|
|
1968
2334
|
pendingChanges.set(filePath, timeout);
|
|
1969
2335
|
};
|
|
@@ -1985,7 +2351,21 @@ async function watchCommand(options) {
|
|
|
1985
2351
|
);
|
|
1986
2352
|
});
|
|
1987
2353
|
process.on("SIGINT", () => {
|
|
2354
|
+
clearInterval(retryInterval);
|
|
1988
2355
|
console.log(chalk4.dim("\n\nStopping watch...\n"));
|
|
2356
|
+
if (successCount > 0 || failedSyncs.size > 0) {
|
|
2357
|
+
console.log(chalk4.bold("Session summary:"));
|
|
2358
|
+
if (successCount > 0) {
|
|
2359
|
+
console.log(chalk4.green(` \u2713 ${successCount} file(s) synced`));
|
|
2360
|
+
}
|
|
2361
|
+
if (failedSyncs.size > 0) {
|
|
2362
|
+
console.log(chalk4.red(` \u2717 ${failedSyncs.size} file(s) failed:`));
|
|
2363
|
+
for (const failed of failedSyncs.values()) {
|
|
2364
|
+
console.log(chalk4.dim(` - ${failed.fileName}: ${failed.error}`));
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
console.log();
|
|
2368
|
+
}
|
|
1989
2369
|
watcher.close();
|
|
1990
2370
|
process.exit(0);
|
|
1991
2371
|
});
|
|
@@ -2008,10 +2388,21 @@ async function deployCommand(environment, options) {
|
|
|
2008
2388
|
}
|
|
2009
2389
|
const shouldPromote = options?.promote || environment === "production";
|
|
2010
2390
|
if (shouldPromote) {
|
|
2011
|
-
const
|
|
2391
|
+
const client2 = createApiClient(config);
|
|
2392
|
+
const spinner2 = ora4("Sjekker produksjonsstatus...").start();
|
|
2012
2393
|
try {
|
|
2013
|
-
const
|
|
2014
|
-
|
|
2394
|
+
const status = await client2.getDeploymentStatus(config.siteId);
|
|
2395
|
+
if (!status.productionEnabled) {
|
|
2396
|
+
spinner2.fail("Produksjon er ikke aktivert");
|
|
2397
|
+
console.log();
|
|
2398
|
+
console.log(chalk5.yellow("\u26A0\uFE0F Produksjon er deaktivert for dette prosjektet."));
|
|
2399
|
+
console.log(chalk5.dim(" Aktiver produksjon f\xF8rst:"));
|
|
2400
|
+
console.log(chalk5.dim(" kode production enable [--domain <domain>]"));
|
|
2401
|
+
console.log();
|
|
2402
|
+
return;
|
|
2403
|
+
}
|
|
2404
|
+
spinner2.text = "Promoterer staging til produksjon...";
|
|
2405
|
+
const deployment = await client2.promoteToProduction(config.siteId);
|
|
2015
2406
|
spinner2.succeed("Promoted to production");
|
|
2016
2407
|
console.log();
|
|
2017
2408
|
console.log(chalk5.dim("Deployment details:"));
|
|
@@ -2028,9 +2419,62 @@ async function deployCommand(environment, options) {
|
|
|
2028
2419
|
}
|
|
2029
2420
|
return;
|
|
2030
2421
|
}
|
|
2422
|
+
const client = createApiClient(config);
|
|
2423
|
+
if (options?.force) {
|
|
2424
|
+
const forceSpinner = ora4("Sjekker l\xE5s...").start();
|
|
2425
|
+
try {
|
|
2426
|
+
const lockStatus = await client.getLockStatus(config.siteId);
|
|
2427
|
+
if (lockStatus.isLocked) {
|
|
2428
|
+
if (lockStatus.isStale) {
|
|
2429
|
+
forceSpinner.text = "Frigj\xF8r gammel l\xE5s...";
|
|
2430
|
+
} else {
|
|
2431
|
+
forceSpinner.warn("Aktiv l\xE5s funnet");
|
|
2432
|
+
console.log(chalk5.yellow("\n\u26A0\uFE0F Deployment er l\xE5st av en annen prosess."));
|
|
2433
|
+
console.log(chalk5.dim(` L\xE5st siden: ${lockStatus.acquiredAt ? new Date(lockStatus.acquiredAt).toLocaleString("nb-NO") : "ukjent"}`));
|
|
2434
|
+
console.log(chalk5.dim(` L\xE5s-ID: ${lockStatus.lockHolder}`));
|
|
2435
|
+
console.log();
|
|
2436
|
+
console.log(chalk5.yellow(" Hvis du er sikker p\xE5 at l\xE5sen er foreldet, kj\xF8r med --force igjen."));
|
|
2437
|
+
console.log(chalk5.dim(" L\xE5sen vil utl\xF8pe automatisk etter 10 minutter."));
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const result = await client.forceReleaseLock(config.siteId);
|
|
2441
|
+
if (result.wasLocked) {
|
|
2442
|
+
forceSpinner.succeed("L\xE5s frigjort");
|
|
2443
|
+
console.log(chalk5.dim(` Tidligere l\xE5s fra: ${result.acquiredAt ? new Date(result.acquiredAt).toLocaleString("nb-NO") : "ukjent"}`));
|
|
2444
|
+
console.log();
|
|
2445
|
+
} else {
|
|
2446
|
+
forceSpinner.info("Ingen l\xE5s \xE5 frigj\xF8re");
|
|
2447
|
+
}
|
|
2448
|
+
} else {
|
|
2449
|
+
forceSpinner.info("Ingen l\xE5s aktiv");
|
|
2450
|
+
}
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
forceSpinner.fail("Kunne ikke sjekke/frigj\xF8re l\xE5s");
|
|
2453
|
+
console.error(chalk5.red("\nError:"), error.message || error);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
const preCheckSpinner = ora4("Sjekker scripts...").start();
|
|
2458
|
+
try {
|
|
2459
|
+
const scripts = await client.listScripts(config.siteId);
|
|
2460
|
+
const emptyScripts = scripts.filter(
|
|
2461
|
+
(s) => s.is_active && (!s.content || s.content.trim().length === 0)
|
|
2462
|
+
);
|
|
2463
|
+
if (emptyScripts.length > 0) {
|
|
2464
|
+
preCheckSpinner.warn(`${emptyScripts.length} tomme script(s) funnet`);
|
|
2465
|
+
console.log(chalk5.yellow("\n\u26A0\uFE0F F\xF8lgende scripts er tomme:"));
|
|
2466
|
+
emptyScripts.forEach((s) => {
|
|
2467
|
+
console.log(chalk5.dim(` - ${s.slug}.${s.type === "javascript" ? "js" : "css"}`));
|
|
2468
|
+
});
|
|
2469
|
+
console.log(chalk5.dim(" Tomme scripts har ingen effekt n\xE5r de er deployet.\n"));
|
|
2470
|
+
} else {
|
|
2471
|
+
preCheckSpinner.succeed(`${scripts.filter((s) => s.is_active).length} script(s) klare`);
|
|
2472
|
+
}
|
|
2473
|
+
} catch {
|
|
2474
|
+
preCheckSpinner.info("Kunne ikke sjekke scripts");
|
|
2475
|
+
}
|
|
2031
2476
|
const spinner = ora4("Deploying to staging...").start();
|
|
2032
2477
|
try {
|
|
2033
|
-
const client = createApiClient(config);
|
|
2034
2478
|
const deployment = await client.deploy(config.siteId, "staging");
|
|
2035
2479
|
spinner.succeed("Deployed to staging");
|
|
2036
2480
|
console.log();
|
|
@@ -2437,17 +2881,29 @@ async function statusCommand(options) {
|
|
|
2437
2881
|
} else {
|
|
2438
2882
|
console.log(chalk7.blue(" Staging: ") + chalk7.yellow("\u25CB") + chalk7.dim(" No deployments"));
|
|
2439
2883
|
}
|
|
2884
|
+
const productionEnabled = deployStatus.productionEnabled ?? false;
|
|
2440
2885
|
const prodStatus = deployStatus.production.lastSuccessful;
|
|
2441
|
-
if (
|
|
2886
|
+
if (!productionEnabled) {
|
|
2887
|
+
console.log(
|
|
2888
|
+
chalk7.gray(" Production: ") + chalk7.gray("\u25CB") + chalk7.gray(" Deaktivert") + chalk7.dim(" (kun staging)")
|
|
2889
|
+
);
|
|
2890
|
+
console.log();
|
|
2891
|
+
console.log(
|
|
2892
|
+
chalk7.dim(' \u{1F4A1} Run "kode production enable" to activate production environment.')
|
|
2893
|
+
);
|
|
2894
|
+
} else if (prodStatus) {
|
|
2442
2895
|
console.log(
|
|
2443
2896
|
chalk7.green(" Production: ") + chalk7.green("\u25CF") + chalk7.dim(` v${prodStatus.version}`) + chalk7.dim(` (${formatDate(prodStatus.completedAt)})`)
|
|
2444
2897
|
);
|
|
2898
|
+
if (deployStatus.productionDomain) {
|
|
2899
|
+
console.log(chalk7.dim(` Domain: ${deployStatus.productionDomain}`));
|
|
2900
|
+
}
|
|
2445
2901
|
} else {
|
|
2446
2902
|
console.log(
|
|
2447
|
-
chalk7.green(" Production: ") + chalk7.yellow("\u25CB") + chalk7.dim("
|
|
2903
|
+
chalk7.green(" Production: ") + chalk7.yellow("\u25CB") + chalk7.dim(" Aktivert, ingen deployments enda")
|
|
2448
2904
|
);
|
|
2449
2905
|
}
|
|
2450
|
-
if (deployStatus.canPromote) {
|
|
2906
|
+
if (deployStatus.canPromote && productionEnabled) {
|
|
2451
2907
|
console.log();
|
|
2452
2908
|
console.log(
|
|
2453
2909
|
chalk7.cyan(' \u{1F4A1} Staging is ahead of production. Run "kode deploy --promote" to update.')
|
package/dist/cli.js
CHANGED
|
@@ -15,41 +15,101 @@ import {
|
|
|
15
15
|
readPageContext,
|
|
16
16
|
statusCommand,
|
|
17
17
|
watchCommand
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-CUZJE4JZ.js";
|
|
19
19
|
|
|
20
20
|
// src/cli.ts
|
|
21
21
|
import { Command } from "commander";
|
|
22
|
-
import
|
|
22
|
+
import chalk6 from "chalk";
|
|
23
23
|
import { createRequire } from "module";
|
|
24
24
|
|
|
25
|
-
// src/commands/
|
|
25
|
+
// src/commands/rollback.ts
|
|
26
26
|
import chalk from "chalk";
|
|
27
|
+
import ora from "ora";
|
|
28
|
+
async function rollbackCommand(environment = "staging") {
|
|
29
|
+
const projectRoot = findProjectRoot();
|
|
30
|
+
if (!projectRoot) {
|
|
31
|
+
console.log(chalk.red("\u274C Not in a Cure Kode project."));
|
|
32
|
+
console.log(chalk.dim(' Run "kode init" first.'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const config = getProjectConfig(projectRoot);
|
|
36
|
+
if (!config) {
|
|
37
|
+
console.log(chalk.red("\u274C Could not read project configuration."));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (!["staging", "production"].includes(environment)) {
|
|
41
|
+
console.log(chalk.red(`\u274C Invalid environment: ${environment}`));
|
|
42
|
+
console.log(chalk.dim(' Use "staging" or "production"'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const client = createApiClient(config);
|
|
46
|
+
const spinner = ora(`Ruller tilbake ${environment}...`).start();
|
|
47
|
+
try {
|
|
48
|
+
if (environment === "production") {
|
|
49
|
+
const status = await client.getDeploymentStatus(config.siteId);
|
|
50
|
+
if (!status.productionEnabled) {
|
|
51
|
+
spinner.fail("Produksjon er ikke aktivert");
|
|
52
|
+
console.log();
|
|
53
|
+
console.log(chalk.yellow("\u26A0\uFE0F Produksjon er deaktivert for dette prosjektet."));
|
|
54
|
+
console.log(chalk.dim(" Kan ikke rulle tilbake n\xE5r produksjon er deaktivert."));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const result = await client.rollback(config.siteId, environment);
|
|
59
|
+
spinner.succeed(`Tilbakerulling fullf\xF8rt (${result.duration_ms}ms)`);
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(chalk.dim("Tilbakerulling detaljer:"));
|
|
62
|
+
console.log(chalk.dim(` Fra: ${result.rolledBackFrom.version}`));
|
|
63
|
+
console.log(chalk.dim(` Til: ${result.rolledBackTo.version}`));
|
|
64
|
+
console.log();
|
|
65
|
+
console.log(chalk.bold("CDN URL:"));
|
|
66
|
+
console.log(chalk.cyan(` ${result.cdn_url}`));
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(chalk.green(`\u2705 ${environment.charAt(0).toUpperCase() + environment.slice(1)} er n\xE5 tilbake til forrige versjon!`));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
spinner.fail("Tilbakerulling feilet");
|
|
71
|
+
if (error.statusCode === 404) {
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(chalk.yellow("\u26A0\uFE0F Ingen tidligere versjon \xE5 rulle tilbake til."));
|
|
74
|
+
console.log(chalk.dim(" Det m\xE5 v\xE6re minst 2 deployments for \xE5 kunne rulle tilbake."));
|
|
75
|
+
} else if (error.statusCode === 409) {
|
|
76
|
+
console.log();
|
|
77
|
+
console.log(chalk.yellow("\u26A0\uFE0F En annen deployment kj\xF8rer."));
|
|
78
|
+
console.log(chalk.dim(" Vent til den er ferdig og pr\xF8v igjen."));
|
|
79
|
+
} else {
|
|
80
|
+
console.error(chalk.red("\nError:"), error.message || error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/commands/pages.ts
|
|
86
|
+
import chalk2 from "chalk";
|
|
27
87
|
async function pagesCommand(options) {
|
|
28
88
|
const projectRoot = findProjectRoot();
|
|
29
89
|
if (!projectRoot) {
|
|
30
|
-
console.log(
|
|
31
|
-
console.log(
|
|
90
|
+
console.log(chalk2.red("Error: Not in a Cure Kode project."));
|
|
91
|
+
console.log(chalk2.dim('Run "kode init" first.'));
|
|
32
92
|
return;
|
|
33
93
|
}
|
|
34
94
|
const config = getProjectConfig(projectRoot);
|
|
35
95
|
if (!config) {
|
|
36
|
-
console.log(
|
|
96
|
+
console.log(chalk2.red("Error: Invalid project configuration."));
|
|
37
97
|
return;
|
|
38
98
|
}
|
|
39
99
|
if (options.delete && options.page) {
|
|
40
100
|
const deleted = deletePageContext(projectRoot, options.page);
|
|
41
101
|
if (deleted) {
|
|
42
|
-
console.log(
|
|
102
|
+
console.log(chalk2.green(`Deleted: ${options.page}`));
|
|
43
103
|
} else {
|
|
44
|
-
console.log(
|
|
104
|
+
console.log(chalk2.red(`Not found: ${options.page}`));
|
|
45
105
|
}
|
|
46
106
|
return;
|
|
47
107
|
}
|
|
48
108
|
if (options.page) {
|
|
49
109
|
const context = readPageContext(projectRoot, options.page);
|
|
50
110
|
if (!context) {
|
|
51
|
-
console.log(
|
|
52
|
-
console.log(
|
|
111
|
+
console.log(chalk2.red(`Page not found: ${options.page}`));
|
|
112
|
+
console.log(chalk2.dim('Use "kode pages" to list cached pages'));
|
|
53
113
|
return;
|
|
54
114
|
}
|
|
55
115
|
if (options.json) {
|
|
@@ -61,80 +121,80 @@ async function pagesCommand(options) {
|
|
|
61
121
|
}
|
|
62
122
|
const pages = listCachedPages(projectRoot);
|
|
63
123
|
if (pages.length === 0) {
|
|
64
|
-
console.log(
|
|
65
|
-
console.log(
|
|
124
|
+
console.log(chalk2.yellow("No cached pages."));
|
|
125
|
+
console.log(chalk2.dim('Use "kode html <url> --save" to cache page structures.'));
|
|
66
126
|
return;
|
|
67
127
|
}
|
|
68
128
|
if (options.json) {
|
|
69
129
|
console.log(JSON.stringify(pages, null, 2));
|
|
70
130
|
return;
|
|
71
131
|
}
|
|
72
|
-
console.log(
|
|
132
|
+
console.log(chalk2.bold(`Cached Pages (${pages.length})`));
|
|
73
133
|
console.log();
|
|
74
134
|
for (const page of pages) {
|
|
75
135
|
const path = new URL(page.url).pathname;
|
|
76
136
|
const date = new Date(page.extractedAt).toLocaleDateString();
|
|
77
137
|
const badges = [];
|
|
78
138
|
if (page.sectionCount > 0) badges.push(`${page.sectionCount} sections`);
|
|
79
|
-
if (page.cmsCollectionCount > 0) badges.push(
|
|
80
|
-
console.log(` ${
|
|
139
|
+
if (page.cmsCollectionCount > 0) badges.push(chalk2.cyan(`${page.cmsCollectionCount} CMS`));
|
|
140
|
+
console.log(` ${chalk2.bold(path)} ${chalk2.dim(`[${page.slug}]`)}`);
|
|
81
141
|
if (page.title) {
|
|
82
|
-
console.log(
|
|
142
|
+
console.log(chalk2.dim(` "${page.title}"`));
|
|
83
143
|
}
|
|
84
|
-
console.log(
|
|
144
|
+
console.log(chalk2.dim(` ${badges.join(", ")} \u2022 ${date}`));
|
|
85
145
|
console.log();
|
|
86
146
|
}
|
|
87
|
-
console.log(
|
|
88
|
-
console.log(
|
|
147
|
+
console.log(chalk2.dim(`Use "kode pages <slug>" to see details`));
|
|
148
|
+
console.log(chalk2.dim(`Use "kode html <url> --save --force" to refresh`));
|
|
89
149
|
}
|
|
90
150
|
function printPageDetails(context) {
|
|
91
|
-
console.log(
|
|
92
|
-
console.log(
|
|
93
|
-
console.log(
|
|
151
|
+
console.log(chalk2.bold(context.title || context.url));
|
|
152
|
+
console.log(chalk2.dim(context.url));
|
|
153
|
+
console.log(chalk2.dim(`Extracted: ${context.extractedAt}`));
|
|
94
154
|
console.log();
|
|
95
155
|
if (context.sections.length > 0) {
|
|
96
|
-
console.log(
|
|
156
|
+
console.log(chalk2.bold("Sections"));
|
|
97
157
|
for (const section of context.sections) {
|
|
98
158
|
const name = section.heading || section.id || section.className?.split(" ")[0] || "section";
|
|
99
159
|
const badges = [];
|
|
100
|
-
if (section.hasCms) badges.push(
|
|
101
|
-
if (section.hasForm) badges.push(
|
|
160
|
+
if (section.hasCms) badges.push(chalk2.cyan("CMS"));
|
|
161
|
+
if (section.hasForm) badges.push(chalk2.yellow("Form"));
|
|
102
162
|
const badgeStr = badges.length > 0 ? ` [${badges.join(", ")}]` : "";
|
|
103
163
|
console.log(` \u2022 ${name}${badgeStr}`);
|
|
104
164
|
if (section.textSample) {
|
|
105
|
-
console.log(
|
|
165
|
+
console.log(chalk2.dim(` "${section.textSample.slice(0, 80)}..."`));
|
|
106
166
|
}
|
|
107
167
|
}
|
|
108
168
|
console.log();
|
|
109
169
|
}
|
|
110
170
|
if (context.headings.length > 0) {
|
|
111
|
-
console.log(
|
|
171
|
+
console.log(chalk2.bold("Headings"));
|
|
112
172
|
for (const h of context.headings.slice(0, 10)) {
|
|
113
173
|
const level = "H" + h.level;
|
|
114
|
-
console.log(` ${
|
|
174
|
+
console.log(` ${chalk2.dim(level)} ${h.text}`);
|
|
115
175
|
}
|
|
116
176
|
if (context.headings.length > 10) {
|
|
117
|
-
console.log(
|
|
177
|
+
console.log(chalk2.dim(` ... and ${context.headings.length - 10} more`));
|
|
118
178
|
}
|
|
119
179
|
console.log();
|
|
120
180
|
}
|
|
121
181
|
if (context.ctas.length > 0) {
|
|
122
|
-
console.log(
|
|
182
|
+
console.log(chalk2.bold("CTAs"));
|
|
123
183
|
for (const cta of context.ctas) {
|
|
124
|
-
const href = cta.href ?
|
|
184
|
+
const href = cta.href ? chalk2.dim(` \u2192 ${cta.href}`) : "";
|
|
125
185
|
console.log(` \u2022 "${cta.text}"${href}`);
|
|
126
|
-
console.log(
|
|
186
|
+
console.log(chalk2.dim(` in ${cta.location}`));
|
|
127
187
|
}
|
|
128
188
|
console.log();
|
|
129
189
|
}
|
|
130
190
|
if (context.forms.length > 0) {
|
|
131
|
-
console.log(
|
|
191
|
+
console.log(chalk2.bold("Forms"));
|
|
132
192
|
for (const form of context.forms) {
|
|
133
|
-
console.log(` ${
|
|
193
|
+
console.log(` ${chalk2.bold(form.name || "form")}`);
|
|
134
194
|
for (const field of form.fields) {
|
|
135
|
-
const required = field.required ?
|
|
195
|
+
const required = field.required ? chalk2.red("*") : "";
|
|
136
196
|
const label = field.label || field.type;
|
|
137
|
-
console.log(` \u2022 ${label}${required} ${
|
|
197
|
+
console.log(` \u2022 ${label}${required} ${chalk2.dim(`(${field.type})`)}`);
|
|
138
198
|
}
|
|
139
199
|
if (form.submitText) {
|
|
140
200
|
console.log(` \u2192 Submit: "${form.submitText}"`);
|
|
@@ -143,31 +203,31 @@ function printPageDetails(context) {
|
|
|
143
203
|
console.log();
|
|
144
204
|
}
|
|
145
205
|
if (context.cmsPatterns.length > 0) {
|
|
146
|
-
console.log(
|
|
206
|
+
console.log(chalk2.bold("CMS Collections"));
|
|
147
207
|
for (const cms of context.cmsPatterns) {
|
|
148
|
-
console.log(` ${
|
|
208
|
+
console.log(` ${chalk2.bold(cms.containerClass)}: ${chalk2.cyan(`${cms.itemCount} items`)}`);
|
|
149
209
|
if (cms.templateFields.length > 0) {
|
|
150
|
-
console.log(
|
|
210
|
+
console.log(chalk2.dim(` Template fields: ${cms.templateFields.join(", ")}`));
|
|
151
211
|
}
|
|
152
212
|
}
|
|
153
213
|
console.log();
|
|
154
214
|
}
|
|
155
215
|
if (context.navigation.length > 0) {
|
|
156
|
-
console.log(
|
|
216
|
+
console.log(chalk2.bold("Navigation"));
|
|
157
217
|
for (const nav of context.navigation) {
|
|
158
|
-
console.log(` ${
|
|
218
|
+
console.log(` ${chalk2.bold(nav.type)}:`);
|
|
159
219
|
for (const item of nav.items.slice(0, 8)) {
|
|
160
|
-
const href = item.href ?
|
|
220
|
+
const href = item.href ? chalk2.dim(` \u2192 ${item.href}`) : "";
|
|
161
221
|
console.log(` \u2022 ${item.text}${href}`);
|
|
162
222
|
}
|
|
163
223
|
if (nav.items.length > 8) {
|
|
164
|
-
console.log(
|
|
224
|
+
console.log(chalk2.dim(` ... and ${nav.items.length - 8} more`));
|
|
165
225
|
}
|
|
166
226
|
}
|
|
167
227
|
console.log();
|
|
168
228
|
}
|
|
169
229
|
if (context.notes && context.notes.length > 0) {
|
|
170
|
-
console.log(
|
|
230
|
+
console.log(chalk2.bold("Notes"));
|
|
171
231
|
for (const note of context.notes) {
|
|
172
232
|
console.log(` \u2022 ${note}`);
|
|
173
233
|
}
|
|
@@ -176,40 +236,40 @@ function printPageDetails(context) {
|
|
|
176
236
|
}
|
|
177
237
|
|
|
178
238
|
// src/commands/set.ts
|
|
179
|
-
import
|
|
180
|
-
import
|
|
239
|
+
import chalk3 from "chalk";
|
|
240
|
+
import ora2 from "ora";
|
|
181
241
|
async function setCommand(script, options) {
|
|
182
242
|
const projectRoot = findProjectRoot();
|
|
183
243
|
if (!projectRoot) {
|
|
184
|
-
console.log(
|
|
185
|
-
console.log(
|
|
244
|
+
console.log(chalk3.red("\u274C Not in a Cure Kode project."));
|
|
245
|
+
console.log(chalk3.dim(' Run "kode init" first.'));
|
|
186
246
|
return;
|
|
187
247
|
}
|
|
188
248
|
const config = getProjectConfig(projectRoot);
|
|
189
249
|
if (!config) {
|
|
190
|
-
console.log(
|
|
250
|
+
console.log(chalk3.red("\u274C Could not read project configuration."));
|
|
191
251
|
return;
|
|
192
252
|
}
|
|
193
253
|
if (!options.scope && options.autoLoad === void 0) {
|
|
194
|
-
console.log(
|
|
195
|
-
console.log(
|
|
254
|
+
console.log(chalk3.yellow("\u26A0\uFE0F No changes specified."));
|
|
255
|
+
console.log(chalk3.dim(" Use --scope or --auto-load/--no-auto-load"));
|
|
196
256
|
console.log();
|
|
197
|
-
console.log(
|
|
198
|
-
console.log(
|
|
199
|
-
console.log(
|
|
200
|
-
console.log(
|
|
257
|
+
console.log(chalk3.dim("Examples:"));
|
|
258
|
+
console.log(chalk3.dim(" kode set my-script --scope page-specific"));
|
|
259
|
+
console.log(chalk3.dim(" kode set my-script --scope global --auto-load"));
|
|
260
|
+
console.log(chalk3.dim(" kode set my-script --no-auto-load"));
|
|
201
261
|
return;
|
|
202
262
|
}
|
|
203
|
-
const spinner =
|
|
263
|
+
const spinner = ora2(`Updating ${script}...`).start();
|
|
204
264
|
try {
|
|
205
265
|
const client = createApiClient(config);
|
|
206
266
|
const scripts = await client.listScripts(config.siteId);
|
|
207
267
|
const targetScript = scripts.find((s) => s.slug === script || s.name === script);
|
|
208
268
|
if (!targetScript) {
|
|
209
269
|
spinner.fail(`Script "${script}" not found`);
|
|
210
|
-
console.log(
|
|
270
|
+
console.log(chalk3.dim("\nAvailable scripts:"));
|
|
211
271
|
scripts.forEach((s) => {
|
|
212
|
-
console.log(
|
|
272
|
+
console.log(chalk3.dim(` - ${s.slug}`));
|
|
213
273
|
});
|
|
214
274
|
return;
|
|
215
275
|
}
|
|
@@ -229,46 +289,134 @@ async function setCommand(script, options) {
|
|
|
229
289
|
}
|
|
230
290
|
updates.changeSummary = `Updated settings: ${changes.join(", ")}`;
|
|
231
291
|
const updated = await client.updateScript(targetScript.id, updates);
|
|
232
|
-
spinner.succeed(
|
|
292
|
+
spinner.succeed(chalk3.green(`Updated ${script}`));
|
|
233
293
|
console.log();
|
|
234
294
|
for (const change of changes) {
|
|
235
|
-
console.log(
|
|
295
|
+
console.log(chalk3.dim(` ${change}`));
|
|
236
296
|
}
|
|
237
297
|
console.log();
|
|
238
298
|
if (options.scope === "page-specific") {
|
|
239
|
-
console.log(
|
|
240
|
-
console.log(
|
|
241
|
-
console.log(
|
|
299
|
+
console.log(chalk3.yellow("\u26A0\uFE0F Page-specific scripts need page assignments to load."));
|
|
300
|
+
console.log(chalk3.dim(" Use app.cure.no \u2192 Kode to assign pages, or use MCP:"));
|
|
301
|
+
console.log(chalk3.dim(" kode_assign_script_to_page(scriptSlug, pageSlug)"));
|
|
242
302
|
console.log();
|
|
243
303
|
}
|
|
244
|
-
console.log(
|
|
304
|
+
console.log(chalk3.dim('Run "kode deploy" to make changes live.'));
|
|
245
305
|
} catch (error) {
|
|
246
306
|
spinner.fail("Failed to update script");
|
|
247
|
-
console.error(
|
|
307
|
+
console.error(chalk3.red("\nError:"), error);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/commands/production.ts
|
|
312
|
+
import chalk4 from "chalk";
|
|
313
|
+
import ora3 from "ora";
|
|
314
|
+
async function productionCommand(action, options) {
|
|
315
|
+
if (!["enable", "disable", "status"].includes(action)) {
|
|
316
|
+
console.log(chalk4.red("\u274C Invalid action. Use: enable, disable, or status"));
|
|
317
|
+
console.log(chalk4.dim(" kode production enable [--domain <domain>]"));
|
|
318
|
+
console.log(chalk4.dim(" kode production disable"));
|
|
319
|
+
console.log(chalk4.dim(" kode production status"));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const projectRoot = findProjectRoot();
|
|
323
|
+
if (!projectRoot) {
|
|
324
|
+
console.log(chalk4.red("\u274C Not in a Cure Kode project."));
|
|
325
|
+
console.log(chalk4.dim(' Run "kode init" first.'));
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const config = getProjectConfig(projectRoot);
|
|
329
|
+
if (!config) {
|
|
330
|
+
console.log(chalk4.red("\u274C Could not read project configuration."));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const client = createApiClient(config);
|
|
334
|
+
if (action === "status") {
|
|
335
|
+
const spinner2 = ora3("Fetching production status...").start();
|
|
336
|
+
try {
|
|
337
|
+
const status = await client.getDeploymentStatus(config.siteId);
|
|
338
|
+
spinner2.stop();
|
|
339
|
+
console.log();
|
|
340
|
+
console.log(chalk4.bold("Production Status"));
|
|
341
|
+
console.log();
|
|
342
|
+
if (status.productionEnabled) {
|
|
343
|
+
console.log(chalk4.green(" \u25CF Produksjon er aktivert"));
|
|
344
|
+
if (status.productionDomain) {
|
|
345
|
+
console.log(chalk4.dim(` Domain: ${status.productionDomain}`));
|
|
346
|
+
}
|
|
347
|
+
if (status.production.lastSuccessful) {
|
|
348
|
+
console.log(
|
|
349
|
+
chalk4.dim(
|
|
350
|
+
` Siste deploy: v${status.production.lastSuccessful.version}`
|
|
351
|
+
)
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
console.log(chalk4.gray(" \u25CB Produksjon er deaktivert"));
|
|
356
|
+
console.log(chalk4.dim(" Kun staging er aktiv"));
|
|
357
|
+
}
|
|
358
|
+
console.log();
|
|
359
|
+
} catch (error) {
|
|
360
|
+
spinner2.fail("Failed to fetch status");
|
|
361
|
+
console.error(chalk4.red("\nError:"), error.message || error);
|
|
362
|
+
}
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const spinner = ora3(
|
|
366
|
+
action === "enable" ? "Aktiverer produksjon..." : "Deaktiverer produksjon..."
|
|
367
|
+
).start();
|
|
368
|
+
try {
|
|
369
|
+
const result = await client.setProductionEnabled(
|
|
370
|
+
config.siteId,
|
|
371
|
+
action === "enable",
|
|
372
|
+
options?.domain
|
|
373
|
+
);
|
|
374
|
+
spinner.stop();
|
|
375
|
+
console.log();
|
|
376
|
+
if (action === "enable") {
|
|
377
|
+
console.log(chalk4.green("\u2713 Produksjon er n\xE5 aktivert"));
|
|
378
|
+
if (result.productionDomain) {
|
|
379
|
+
console.log(chalk4.dim(` Domain: ${result.productionDomain}`));
|
|
380
|
+
}
|
|
381
|
+
console.log();
|
|
382
|
+
console.log(chalk4.dim(" Neste steg:"));
|
|
383
|
+
console.log(chalk4.dim(" 1. Deploy til staging: kode deploy"));
|
|
384
|
+
console.log(chalk4.dim(" 2. Promoter til produksjon: kode deploy --promote"));
|
|
385
|
+
} else {
|
|
386
|
+
console.log(chalk4.yellow("\u2713 Produksjon er n\xE5 deaktivert"));
|
|
387
|
+
console.log(chalk4.dim(" Kun staging-milj\xF8et er aktivt."));
|
|
388
|
+
console.log(
|
|
389
|
+
chalk4.dim(" Produksjonsdomenet vil f\xE5 en tom script-respons.")
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
console.log();
|
|
393
|
+
} catch (error) {
|
|
394
|
+
spinner.fail(action === "enable" ? "Kunne ikke aktivere produksjon" : "Kunne ikke deaktivere produksjon");
|
|
395
|
+
console.error(chalk4.red("\nError:"), error.message || error);
|
|
248
396
|
}
|
|
249
397
|
}
|
|
250
398
|
|
|
251
399
|
// src/commands/update-claude-md.ts
|
|
252
|
-
import
|
|
400
|
+
import chalk5 from "chalk";
|
|
253
401
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
254
402
|
import { join } from "path";
|
|
255
403
|
async function updateClaudeMdCommand() {
|
|
256
404
|
const projectRoot = findProjectRoot();
|
|
257
405
|
if (!projectRoot) {
|
|
258
|
-
console.log(
|
|
259
|
-
console.log(
|
|
406
|
+
console.log(chalk5.red("\u274C Not in a Cure Kode project."));
|
|
407
|
+
console.log(chalk5.dim(' Run "kode init" first.'));
|
|
260
408
|
return;
|
|
261
409
|
}
|
|
262
410
|
const config = getProjectConfig(projectRoot);
|
|
263
411
|
if (!config) {
|
|
264
|
-
console.log(
|
|
412
|
+
console.log(chalk5.red("\u274C Could not read project configuration."));
|
|
265
413
|
return;
|
|
266
414
|
}
|
|
267
415
|
const claudeMdPath = join(projectRoot, "CLAUDE.md");
|
|
268
416
|
const newKodeSection = generateClaudeMdMinimal(config.siteName, config.siteSlug);
|
|
269
417
|
if (!existsSync(claudeMdPath)) {
|
|
270
418
|
writeFileSync(claudeMdPath, newKodeSection);
|
|
271
|
-
console.log(
|
|
419
|
+
console.log(chalk5.green("\u2705 Created CLAUDE.md with Cure Kode section"));
|
|
272
420
|
return;
|
|
273
421
|
}
|
|
274
422
|
let content = readFileSync(claudeMdPath, "utf-8");
|
|
@@ -287,18 +435,18 @@ async function updateClaudeMdCommand() {
|
|
|
287
435
|
content = newKodeSection + "---\n\n" + content;
|
|
288
436
|
writeFileSync(claudeMdPath, content);
|
|
289
437
|
if (removedCount > 1) {
|
|
290
|
-
console.log(
|
|
438
|
+
console.log(chalk5.green(`\u2705 Cleaned up ${removedCount} duplicate Kode sections and added fresh one`));
|
|
291
439
|
} else if (removedCount === 1) {
|
|
292
|
-
console.log(
|
|
440
|
+
console.log(chalk5.green("\u2705 Updated Cure Kode section in CLAUDE.md"));
|
|
293
441
|
} else {
|
|
294
|
-
console.log(
|
|
442
|
+
console.log(chalk5.green("\u2705 Added Cure Kode section to CLAUDE.md"));
|
|
295
443
|
}
|
|
296
444
|
console.log();
|
|
297
|
-
console.log(
|
|
298
|
-
console.log(
|
|
299
|
-
console.log(
|
|
300
|
-
console.log(
|
|
301
|
-
console.log(
|
|
445
|
+
console.log(chalk5.dim("The Cure Kode section now includes:"));
|
|
446
|
+
console.log(chalk5.dim(" \u2022 What is Cure Kode (internal tool explanation)"));
|
|
447
|
+
console.log(chalk5.dim(" \u2022 CDN URL with script tag for Webflow"));
|
|
448
|
+
console.log(chalk5.dim(" \u2022 Workflow steps"));
|
|
449
|
+
console.log(chalk5.dim(" \u2022 Command reference"));
|
|
302
450
|
}
|
|
303
451
|
|
|
304
452
|
// src/cli.ts
|
|
@@ -318,9 +466,12 @@ program.command("push").description("Upload local scripts to Cure").argument("[s
|
|
|
318
466
|
program.command("watch").description("Watch for changes and auto-push").option("-d, --deploy", "Auto-deploy after each push").action((options) => {
|
|
319
467
|
watchCommand(options);
|
|
320
468
|
});
|
|
321
|
-
program.command("deploy [environment]").description("Deploy to staging or production").option("-p, --promote", "Promote staging to production").action((environment, options) => {
|
|
469
|
+
program.command("deploy [environment]").description("Deploy to staging or production").option("-p, --promote", "Promote staging to production").option("-f, --force", "Force release stale deploy lock before deploying").action((environment, options) => {
|
|
322
470
|
deployCommand(environment, options);
|
|
323
471
|
});
|
|
472
|
+
program.command("rollback [environment]").description("Rollback to previous deployment").action((environment = "staging") => {
|
|
473
|
+
rollbackCommand(environment);
|
|
474
|
+
});
|
|
324
475
|
program.command("html <url>").description("Fetch and analyze HTML from a URL").option("-j, --json", "Output as JSON").option("--scripts", "Show only scripts").option("--styles", "Show only styles").option("-s, --save", "Save page structure to context").option("-f, --force", "Force refresh when using --save").action((url, options) => {
|
|
325
476
|
htmlCommand(url, options);
|
|
326
477
|
});
|
|
@@ -336,12 +487,15 @@ program.command("context").description("View or edit project context for AI agen
|
|
|
336
487
|
program.command("set <script>").description("Update script settings (scope, autoLoad)").option("--scope <scope>", "Set scope: global or page-specific").option("--auto-load", "Enable auto-loading").option("--no-auto-load", "Disable auto-loading").action((script, options) => {
|
|
337
488
|
setCommand(script, options);
|
|
338
489
|
});
|
|
490
|
+
program.command("production <action>").description("Enable or disable production environment (v2.3)").option("-d, --domain <domain>", "Set production domain when enabling").action((action, options) => {
|
|
491
|
+
productionCommand(action, options);
|
|
492
|
+
});
|
|
339
493
|
program.command("update-claude-md").alias("ucm").description("Add or update Cure Kode section in CLAUDE.md").action(() => {
|
|
340
494
|
updateClaudeMdCommand();
|
|
341
495
|
});
|
|
342
496
|
program.showHelpAfterError();
|
|
343
497
|
console.log();
|
|
344
|
-
console.log(
|
|
345
|
-
console.log(
|
|
498
|
+
console.log(chalk6.bold(" Cure Kode CLI"));
|
|
499
|
+
console.log(chalk6.dim(" Manage JS/CSS for Webflow sites"));
|
|
346
500
|
console.log();
|
|
347
501
|
program.parse();
|
package/dist/index.d.ts
CHANGED
|
@@ -41,6 +41,9 @@ declare function getScriptsDir(projectRoot: string, projectConfig?: ProjectConfi
|
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Cure Kode API Client
|
|
44
|
+
*
|
|
45
|
+
* All requests automatically retry on network/transient errors
|
|
46
|
+
* with exponential backoff (500ms → 1s → 2s).
|
|
44
47
|
*/
|
|
45
48
|
interface CdnSite {
|
|
46
49
|
id: string;
|
|
@@ -146,6 +149,8 @@ declare class KodeApiClient {
|
|
|
146
149
|
deploy(siteId: string, environment?: 'staging' | 'production'): Promise<CdnDeployment>;
|
|
147
150
|
promoteToProduction(siteId: string, stagingDeploymentId?: string): Promise<CdnDeployment>;
|
|
148
151
|
getDeploymentStatus(siteId: string): Promise<{
|
|
152
|
+
productionEnabled: boolean;
|
|
153
|
+
productionDomain: string | null;
|
|
149
154
|
staging: {
|
|
150
155
|
latest: CdnDeployment | null;
|
|
151
156
|
lastSuccessful: any;
|
|
@@ -156,7 +161,42 @@ declare class KodeApiClient {
|
|
|
156
161
|
};
|
|
157
162
|
canPromote: boolean;
|
|
158
163
|
}>;
|
|
164
|
+
rollback(siteId: string, environment?: 'staging' | 'production'): Promise<{
|
|
165
|
+
id: string;
|
|
166
|
+
status: 'rolled_back';
|
|
167
|
+
environment: 'staging' | 'production';
|
|
168
|
+
rolledBackFrom: {
|
|
169
|
+
version: string;
|
|
170
|
+
deploymentId: string;
|
|
171
|
+
};
|
|
172
|
+
rolledBackTo: {
|
|
173
|
+
version: string;
|
|
174
|
+
deploymentId: string;
|
|
175
|
+
};
|
|
176
|
+
cdn_url: string;
|
|
177
|
+
duration_ms: number;
|
|
178
|
+
}>;
|
|
179
|
+
setProductionEnabled(siteId: string, enabled: boolean, productionDomain?: string): Promise<{
|
|
180
|
+
success: boolean;
|
|
181
|
+
productionEnabled: boolean;
|
|
182
|
+
productionDomain: string | null;
|
|
183
|
+
}>;
|
|
159
184
|
fetchHtml(url: string): Promise<ParsedHtmlResult>;
|
|
185
|
+
getLockStatus(siteId: string): Promise<{
|
|
186
|
+
isLocked: boolean;
|
|
187
|
+
isStale: boolean;
|
|
188
|
+
lockHolder: string | null;
|
|
189
|
+
acquiredAt: string | null;
|
|
190
|
+
}>;
|
|
191
|
+
forceReleaseLock(siteId: string): Promise<{
|
|
192
|
+
success: boolean;
|
|
193
|
+
message: string;
|
|
194
|
+
wasLocked: boolean;
|
|
195
|
+
wasStale?: boolean;
|
|
196
|
+
previousLockHolder?: string;
|
|
197
|
+
acquiredAt?: string;
|
|
198
|
+
duration_ms?: number;
|
|
199
|
+
}>;
|
|
160
200
|
}
|
|
161
201
|
/**
|
|
162
202
|
* Create API client from project config
|
|
@@ -196,6 +236,11 @@ declare function pushCommand(options: {
|
|
|
196
236
|
|
|
197
237
|
/**
|
|
198
238
|
* Watch for local changes and auto-push
|
|
239
|
+
*
|
|
240
|
+
* Features:
|
|
241
|
+
* - Error tracking with retry queue
|
|
242
|
+
* - Status summary on error/success
|
|
243
|
+
* - Automatic retry after 30 seconds
|
|
199
244
|
*/
|
|
200
245
|
declare function watchCommand(options: {
|
|
201
246
|
deploy?: boolean;
|
|
@@ -207,9 +252,11 @@ declare function watchCommand(options: {
|
|
|
207
252
|
* Workflow:
|
|
208
253
|
* - `kode deploy` or `kode deploy staging` → deploys to staging
|
|
209
254
|
* - `kode deploy production` or `kode deploy --promote` → promotes staging to production
|
|
255
|
+
* - `kode deploy --force` → force release stale lock before deploying
|
|
210
256
|
*/
|
|
211
257
|
declare function deployCommand(environment?: 'staging' | 'production', options?: {
|
|
212
258
|
promote?: boolean;
|
|
259
|
+
force?: boolean;
|
|
213
260
|
}): Promise<void>;
|
|
214
261
|
|
|
215
262
|
/**
|
package/dist/index.js
CHANGED