@curenorway/kode-cli 1.9.0 → 1.10.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.
@@ -1337,11 +1337,16 @@ async function initCommand(options) {
1337
1337
  console.log(chalk.bold("\n\u{1F680} Cure Kode Setup\n"));
1338
1338
  const { apiKey } = await prompt([
1339
1339
  {
1340
- type: "password",
1340
+ type: "input",
1341
1341
  name: "apiKey",
1342
1342
  message: "API Key (from Cure App \u2192 Tools \u2192 Kode \u2192 API Keys):",
1343
1343
  initial: options.apiKey,
1344
- validate: (value) => value.length > 0 ? true : "API key is required"
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
+ }
1345
1350
  }
1346
1351
  ]);
1347
1352
  const spinner = ora("Validating API key...").start();
@@ -1405,7 +1410,7 @@ config.json
1405
1410
  mcpConfig.mcpServers["cure-kode"] = {
1406
1411
  type: "stdio",
1407
1412
  command: "npx",
1408
- args: ["-y", "@curenorway/kode-mcp"]
1413
+ args: ["-y", "@curenorway/kode-mcp@^1.3.0"]
1409
1414
  };
1410
1415
  let webflowToken = site.webflow_token;
1411
1416
  let webflowMcpMethod = null;
@@ -1456,7 +1461,7 @@ config.json
1456
1461
  mcpConfig.mcpServers["webflow"] = {
1457
1462
  type: "stdio",
1458
1463
  command: "npx",
1459
- args: ["-y", "webflow-mcp-server@latest"],
1464
+ args: ["-y", "webflow-mcp-server@^0.6.0"],
1460
1465
  env: {
1461
1466
  WEBFLOW_TOKEN: webflowToken
1462
1467
  }
@@ -1470,7 +1475,7 @@ config.json
1470
1475
  mcpConfig.mcpServers["playwright"] = {
1471
1476
  type: "stdio",
1472
1477
  command: "npx",
1473
- args: ["-y", "@playwright/mcp@latest"]
1478
+ args: ["-y", "@playwright/mcp@^0.0.21"]
1474
1479
  };
1475
1480
  spinner.start("Generating AI context files...");
1476
1481
  writeFileSync3(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n");
@@ -1641,6 +1646,80 @@ config.json
1641
1646
  }
1642
1647
  }
1643
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
+
1644
1723
  // src/api.ts
1645
1724
  var KodeApiError = class extends Error {
1646
1725
  constructor(message, statusCode, response) {
@@ -1663,23 +1742,38 @@ var KodeApiClient = class {
1663
1742
  }
1664
1743
  async request(endpoint, options = {}) {
1665
1744
  const url = `${this.baseUrl}${endpoint}`;
1666
- const response = await fetch(url, {
1667
- ...options,
1668
- headers: {
1669
- "Content-Type": "application/json",
1670
- "X-API-Key": this.apiKey,
1671
- ...options.headers
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
+ }
1672
1775
  }
1673
- });
1674
- const data = await response.json();
1675
- if (!response.ok) {
1676
- throw new KodeApiError(
1677
- data.error || `Request failed with status ${response.status}`,
1678
- response.status,
1679
- data
1680
- );
1681
- }
1682
- return data;
1776
+ );
1683
1777
  }
1684
1778
  // Sites
1685
1779
  async getSite(siteId) {
@@ -1750,6 +1844,15 @@ var KodeApiClient = class {
1750
1844
  async getDeploymentStatus(siteId) {
1751
1845
  return this.request(`/api/cdn/sites/${siteId}/deployments/status`);
1752
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
+ }
1753
1856
  // Production enabled toggle (v2.3)
1754
1857
  async setProductionEnabled(siteId, enabled, productionDomain) {
1755
1858
  return this.request(`/api/cdn/sites/${siteId}/production`, {
@@ -1767,6 +1870,16 @@ var KodeApiClient = class {
1767
1870
  body: JSON.stringify({ url })
1768
1871
  });
1769
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
+ }
1770
1883
  };
1771
1884
  function createApiClient(config) {
1772
1885
  return new KodeApiClient(config);
@@ -1923,11 +2036,16 @@ async function pushCommand(options) {
1923
2036
  let skipped = 0;
1924
2037
  spinner.stop();
1925
2038
  console.log();
2039
+ let emptyScriptCount = 0;
1926
2040
  for (const file of filesToPush) {
1927
2041
  const filePath = join5(scriptsDir, file);
1928
2042
  const content = readFileSync4(filePath, "utf-8");
1929
2043
  const slug = basename(file, extname(file));
1930
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
+ }
1931
2049
  const remoteScript = remoteScripts.find((s) => s.slug === slug);
1932
2050
  const localMeta = metadata.find((m) => m.slug === slug);
1933
2051
  if (remoteScript) {
@@ -1974,6 +2092,11 @@ async function pushCommand(options) {
1974
2092
  if (skipped > 0) {
1975
2093
  console.log(chalk3.dim(` Skipped ${skipped} unchanged script(s)`));
1976
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
+ }
1977
2100
  const updatedScripts = await client.listScripts(config.siteId);
1978
2101
  const updatedMetadata = updatedScripts.map((s) => ({
1979
2102
  id: s.id,
@@ -2042,15 +2165,59 @@ async function watchCommand(options) {
2042
2165
  }
2043
2166
  const pendingChanges = /* @__PURE__ */ new Map();
2044
2167
  const DEBOUNCE_MS = 500;
2045
- const handleChange = async (filePath) => {
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) => {
2046
2212
  const fileName = basename2(filePath);
2047
2213
  const slug = basename2(fileName, extname2(fileName));
2048
2214
  const type = extname2(fileName) === ".js" ? "javascript" : "css";
2049
- if (pendingChanges.has(filePath)) {
2050
- clearTimeout(pendingChanges.get(filePath));
2215
+ if (!isRetry) {
2216
+ if (pendingChanges.has(filePath)) {
2217
+ clearTimeout(pendingChanges.get(filePath));
2218
+ }
2051
2219
  }
2052
- const timeout = setTimeout(async () => {
2053
- pendingChanges.delete(filePath);
2220
+ const syncFile = async () => {
2054
2221
  try {
2055
2222
  const content = readFileSync5(filePath, "utf-8");
2056
2223
  const remoteScript = remoteScripts.find((s) => s.slug === slug);
@@ -2058,6 +2225,9 @@ async function watchCommand(options) {
2058
2225
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
2059
2226
  if (remoteScript) {
2060
2227
  if (remoteScript.content === content) {
2228
+ if (failedSyncs.has(filePath)) {
2229
+ failedSyncs.delete(filePath);
2230
+ }
2061
2231
  return;
2062
2232
  }
2063
2233
  await client.updateScript(remoteScript.id, {
@@ -2066,9 +2236,24 @@ async function watchCommand(options) {
2066
2236
  });
2067
2237
  remoteScript.content = content;
2068
2238
  remoteScript.current_version++;
2069
- console.log(
2070
- chalk4.dim(`[${timestamp}] `) + chalk4.green(`\u2713 ${fileName}`) + chalk4.dim(` \u2192 v${remoteScript.current_version}`)
2071
- );
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++;
2072
2257
  if (options.deploy) {
2073
2258
  try {
2074
2259
  await client.deploy(config.siteId, config.environment || "staging");
@@ -2077,7 +2262,7 @@ async function watchCommand(options) {
2077
2262
  );
2078
2263
  } catch (deployError) {
2079
2264
  console.log(
2080
- chalk4.dim(`[${timestamp}] `) + chalk4.red(` \u21B3 Deploy failed`)
2265
+ chalk4.dim(`[${timestamp}] `) + chalk4.red(` \u21B3 Deploy failed: ${deployError.message || "Unknown error"}`)
2081
2266
  );
2082
2267
  }
2083
2268
  }
@@ -2090,9 +2275,17 @@ async function watchCommand(options) {
2090
2275
  content
2091
2276
  });
2092
2277
  remoteScripts.push(newScript);
2093
- console.log(
2094
- chalk4.dim(`[${timestamp}] `) + chalk4.green(`\u2713 ${fileName}`) + chalk4.cyan(" (created)")
2095
- );
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++;
2096
2289
  const updatedMetadata = remoteScripts.map((s) => ({
2097
2290
  id: s.id,
2098
2291
  slug: s.slug,
@@ -2107,10 +2300,36 @@ async function watchCommand(options) {
2107
2300
  }
2108
2301
  } catch (error) {
2109
2302
  const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString("nb-NO");
2110
- console.log(
2111
- chalk4.dim(`[${timestamp}] `) + chalk4.red(`\u2717 ${fileName}`) + chalk4.dim(` - ${error.message || "Unknown error"}`)
2112
- );
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();
2113
2324
  }
2325
+ };
2326
+ if (isRetry) {
2327
+ await syncFile();
2328
+ return;
2329
+ }
2330
+ const timeout = setTimeout(async () => {
2331
+ pendingChanges.delete(filePath);
2332
+ await syncFile();
2114
2333
  }, DEBOUNCE_MS);
2115
2334
  pendingChanges.set(filePath, timeout);
2116
2335
  };
@@ -2132,7 +2351,21 @@ async function watchCommand(options) {
2132
2351
  );
2133
2352
  });
2134
2353
  process.on("SIGINT", () => {
2354
+ clearInterval(retryInterval);
2135
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
+ }
2136
2369
  watcher.close();
2137
2370
  process.exit(0);
2138
2371
  });
@@ -2155,10 +2388,10 @@ async function deployCommand(environment, options) {
2155
2388
  }
2156
2389
  const shouldPromote = options?.promote || environment === "production";
2157
2390
  if (shouldPromote) {
2158
- const client = createApiClient(config);
2391
+ const client2 = createApiClient(config);
2159
2392
  const spinner2 = ora4("Sjekker produksjonsstatus...").start();
2160
2393
  try {
2161
- const status = await client.getDeploymentStatus(config.siteId);
2394
+ const status = await client2.getDeploymentStatus(config.siteId);
2162
2395
  if (!status.productionEnabled) {
2163
2396
  spinner2.fail("Produksjon er ikke aktivert");
2164
2397
  console.log();
@@ -2169,7 +2402,7 @@ async function deployCommand(environment, options) {
2169
2402
  return;
2170
2403
  }
2171
2404
  spinner2.text = "Promoterer staging til produksjon...";
2172
- const deployment = await client.promoteToProduction(config.siteId);
2405
+ const deployment = await client2.promoteToProduction(config.siteId);
2173
2406
  spinner2.succeed("Promoted to production");
2174
2407
  console.log();
2175
2408
  console.log(chalk5.dim("Deployment details:"));
@@ -2186,9 +2419,62 @@ async function deployCommand(environment, options) {
2186
2419
  }
2187
2420
  return;
2188
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
+ }
2189
2476
  const spinner = ora4("Deploying to staging...").start();
2190
2477
  try {
2191
- const client = createApiClient(config);
2192
2478
  const deployment = await client.deploy(config.siteId, "staging");
2193
2479
  spinner.succeed("Deployed to staging");
2194
2480
  console.log();