@walkeros/cli 4.7.0-next-1790722899482 → 4.7.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/dist/index.js CHANGED
@@ -5,8 +5,8 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/commands/bundle/index.ts
8
- import path14 from "path";
9
- import fs15 from "fs-extra";
8
+ import path15 from "path";
9
+ import fs16 from "fs-extra";
10
10
 
11
11
  // src/core/cli-logger.ts
12
12
  import chalk from "chalk";
@@ -220,7 +220,7 @@ function isAlreadyLocked(error) {
220
220
  return hasCode(error) && error.code === "EEXIST";
221
221
  }
222
222
  function delay(ms) {
223
- return new Promise((resolve3) => setTimeout(resolve3, ms));
223
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
224
224
  }
225
225
  function breakIfStale(lockPath) {
226
226
  try {
@@ -553,18 +553,18 @@ function buildHeaders(token, headers) {
553
553
  ...mergeAuthHeaders(token, headers)
554
554
  };
555
555
  }
556
- async function apiFetch(path20, init) {
556
+ async function apiFetch(path23, init) {
557
557
  const baseUrl = resolveAppUrl();
558
558
  const token = await resolveAccessToken();
559
559
  if (token) requireSecureUrl(baseUrl);
560
- return fetch(`${baseUrl}${path20}`, {
560
+ return fetch(`${baseUrl}${path23}`, {
561
561
  ...init,
562
562
  headers: buildHeaders(token, init?.headers)
563
563
  });
564
564
  }
565
- async function publicFetch(path20, init) {
565
+ async function publicFetch(path23, init) {
566
566
  const baseUrl = resolveAppUrl();
567
- return fetch(`${baseUrl}${path20}`, {
567
+ return fetch(`${baseUrl}${path23}`, {
568
568
  ...init,
569
569
  headers: {
570
570
  ...clientContextHeaders(),
@@ -572,7 +572,7 @@ async function publicFetch(path20, init) {
572
572
  }
573
573
  });
574
574
  }
575
- async function deployFetch(path20, init) {
575
+ async function deployFetch(path23, init) {
576
576
  const baseUrl = resolveAppUrl();
577
577
  const token = resolveDeployToken() ?? await resolveAccessToken();
578
578
  if (!token)
@@ -580,7 +580,7 @@ async function deployFetch(path20, init) {
580
580
  "No authentication token available. Set WALKEROS_DEPLOY_TOKEN or run walkeros auth login."
581
581
  );
582
582
  requireSecureUrl(baseUrl);
583
- return fetch(`${baseUrl}${path20}`, {
583
+ return fetch(`${baseUrl}${path23}`, {
584
584
  ...init,
585
585
  headers: buildHeaders(token, init?.headers)
586
586
  });
@@ -1015,8 +1015,8 @@ function validateFlowConfig(data) {
1015
1015
  throw new Error("Invalid configuration: failed Flow.Json type guard.");
1016
1016
  }
1017
1017
  const errors = result.error.issues.map((issue) => {
1018
- const path20 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
1019
- return ` - ${path20}: ${issue.message}`;
1018
+ const path23 = issue.path.length > 0 ? issue.path.map(String).join(".") : "root";
1019
+ return ` - ${path23}: ${issue.message}`;
1020
1020
  }).join("\n");
1021
1021
  throw new Error(`Invalid configuration:
1022
1022
  ${errors}`);
@@ -1055,9 +1055,86 @@ function getDefaultOutput(platform) {
1055
1055
  }
1056
1056
 
1057
1057
  // src/config/loader.ts
1058
+ import path8 from "path";
1059
+ import fs8 from "fs-extra";
1060
+ import { getFlowSettings, getPlatform } from "@walkeros/core";
1061
+
1062
+ // src/core/content-digest.ts
1063
+ import { createHash } from "crypto";
1058
1064
  import path7 from "path";
1059
1065
  import fs7 from "fs-extra";
1060
- import { getFlowSettings, getPlatform } from "@walkeros/core";
1066
+ var DIGEST_LENGTH = 12;
1067
+ function isRecord(value) {
1068
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1069
+ }
1070
+ function canonicalJson(value) {
1071
+ return JSON.stringify(value, (_key, inner) => {
1072
+ if (!isRecord(inner)) return inner;
1073
+ const sorted = {};
1074
+ for (const key of Object.keys(inner).sort()) sorted[key] = inner[key];
1075
+ return sorted;
1076
+ });
1077
+ }
1078
+ function configDigest(flowSettings, flowName) {
1079
+ const collector = {
1080
+ ...flowSettings.collector ?? {}
1081
+ };
1082
+ delete collector.release;
1083
+ collector.name ??= flowName;
1084
+ const config = flowSettings.config;
1085
+ const declaredEnv = config?.bundle?.env;
1086
+ const hashedConfig = config && declaredEnv ? {
1087
+ ...config,
1088
+ bundle: { ...config.bundle, env: Object.keys(declaredEnv).sort() }
1089
+ } : config;
1090
+ return createHash("sha256").update(canonicalJson({ ...flowSettings, config: hashedConfig, collector })).digest("hex");
1091
+ }
1092
+ async function hashInstalledDir(dir) {
1093
+ const hash = createHash("sha256");
1094
+ for (const rel of await listFiles(dir, "")) {
1095
+ const abs = path7.join(dir, rel);
1096
+ const stat2 = await fs7.lstat(abs);
1097
+ hash.update(`${rel}\0`);
1098
+ hash.update(
1099
+ stat2.isSymbolicLink() ? await fs7.readlink(abs) : await fs7.readFile(abs)
1100
+ );
1101
+ hash.update("\0");
1102
+ }
1103
+ return hash.digest("hex").slice(0, DIGEST_LENGTH);
1104
+ }
1105
+ async function listFiles(root, rel) {
1106
+ const entries = await fs7.readdir(path7.join(root, rel), {
1107
+ withFileTypes: true
1108
+ });
1109
+ const files = [];
1110
+ for (const entry of entries) {
1111
+ const entryRel = rel ? `${rel}/${entry.name}` : entry.name;
1112
+ if (entry.isDirectory()) files.push(...await listFiles(root, entryRel));
1113
+ else files.push(entryRel);
1114
+ }
1115
+ return files.sort();
1116
+ }
1117
+ function releaseDigest(input) {
1118
+ return createHash("sha256").update(
1119
+ canonicalJson({
1120
+ config: input.configDigest,
1121
+ versions: input.versions,
1122
+ toolchain: input.toolchain
1123
+ })
1124
+ ).digest("hex").slice(0, DIGEST_LENGTH);
1125
+ }
1126
+
1127
+ // src/config/window-collector.ts
1128
+ import { checkWindowCollector } from "@walkeros/core";
1129
+ function assertWindowCollector(value, source = {}) {
1130
+ const check = checkWindowCollector(value);
1131
+ if (check.ok) return check.name;
1132
+ const path23 = source.path ?? "config.settings.windowCollector";
1133
+ const subject = source.written !== void 0 && source.written !== value ? `${JSON.stringify(source.written)} resolved to ${JSON.stringify(value)}, which` : JSON.stringify(value);
1134
+ throw new Error(`${path23}: ${subject} ${check.reason}.`);
1135
+ }
1136
+
1137
+ // src/config/loader.ts
1061
1138
  var DEFAULT_INCLUDE_FOLDER = "./shared";
1062
1139
  function loadBundleConfig(rawConfig, options) {
1063
1140
  const sanitized = stripLegacyBundleExternal(rawConfig, options.logger);
@@ -1067,6 +1144,7 @@ function loadBundleConfig(rawConfig, options) {
1067
1144
  assertLiteralBuildEnv(config, flowName);
1068
1145
  let flowSettings = getFlowSettings(config, flowName, { deferred: true });
1069
1146
  const platform = getPlatform(flowSettings);
1147
+ const flowConfigDigest = configDigest(flowSettings, flowName);
1070
1148
  if (!platform) {
1071
1149
  throw new Error(
1072
1150
  `Invalid configuration: flow "${flowName}" must have config.platform set to "web" or "server".`
@@ -1083,16 +1161,20 @@ function loadBundleConfig(rawConfig, options) {
1083
1161
  const overrides = bundle2?.overrides ?? {};
1084
1162
  const traceInclude = bundle2?.traceInclude;
1085
1163
  const output = options.buildOverrides?.output || getDefaultOutput(platform);
1086
- const configDir = isUrl(options.configPath) ? process.cwd() : path7.dirname(options.configPath);
1164
+ const configDir = isUrl(options.configPath) ? process.cwd() : path8.dirname(options.configPath);
1087
1165
  let includes = config.include;
1088
1166
  if (!includes) {
1089
- const defaultIncludePath = path7.resolve(configDir, DEFAULT_INCLUDE_FOLDER);
1090
- if (fs7.pathExistsSync(defaultIncludePath)) {
1167
+ const defaultIncludePath = path8.resolve(configDir, DEFAULT_INCLUDE_FOLDER);
1168
+ if (fs8.pathExistsSync(defaultIncludePath)) {
1091
1169
  includes = [DEFAULT_INCLUDE_FOLDER];
1092
1170
  }
1093
1171
  }
1094
1172
  const buildOptions = {
1095
1173
  ...buildDefaults,
1174
+ ...settingsToBuildOptions(platform, flowSettings.config?.settings, {
1175
+ path: `flows.${flowName}.config.settings.windowCollector`,
1176
+ written: config.flows[flowName]?.config?.settings?.windowCollector
1177
+ }),
1096
1178
  packages,
1097
1179
  overrides,
1098
1180
  traceInclude,
@@ -1112,7 +1194,14 @@ function loadBundleConfig(rawConfig, options) {
1112
1194
  buildOptions,
1113
1195
  flowName,
1114
1196
  isMultiFlow,
1115
- availableFlows
1197
+ availableFlows,
1198
+ configDigest: flowConfigDigest
1199
+ };
1200
+ }
1201
+ function settingsToBuildOptions(platform, settings, source = {}) {
1202
+ if (platform !== "web" || settings?.windowCollector === void 0) return {};
1203
+ return {
1204
+ windowCollector: assertWindowCollector(settings.windowCollector, source)
1116
1205
  };
1117
1206
  }
1118
1207
  var REFERENCE_PATTERN = /\$(?:env|var|secret|flow|contract)\.|\$code:|__WALKEROS_(?:ENV|SECRET):/;
@@ -1203,8 +1292,8 @@ function isPlainObject(value) {
1203
1292
  // src/commands/bundle/bundler.ts
1204
1293
  import esbuild2 from "esbuild";
1205
1294
  import { builtinModules } from "module";
1206
- import path13 from "path";
1207
- import fs13 from "fs-extra";
1295
+ import path14 from "path";
1296
+ import fs14 from "fs-extra";
1208
1297
  import {
1209
1298
  packageNameToVariable as packageNameToVariable2,
1210
1299
  getStepRuntimeProps,
@@ -1372,8 +1461,8 @@ function applyStepPackages(flowSettings, packages, logger) {
1372
1461
 
1373
1462
  // src/core/package-manager.ts
1374
1463
  import pacote from "pacote";
1375
- import path9 from "path";
1376
- import fs9 from "fs-extra";
1464
+ import path10 from "path";
1465
+ import fs10 from "fs-extra";
1377
1466
  import { readFile } from "fs/promises";
1378
1467
  import os from "os";
1379
1468
  import semver2 from "semver";
@@ -1400,8 +1489,7 @@ async function getPackageCacheKey(packageName, version, date) {
1400
1489
  return getHashServer(input, HASH_LENGTH);
1401
1490
  }
1402
1491
  function normalizeJson(content) {
1403
- const parsed = JSON.parse(content);
1404
- return JSON.stringify(parsed);
1492
+ return canonicalJson(JSON.parse(content));
1405
1493
  }
1406
1494
  async function getFlowSettingsCacheKey(content, date) {
1407
1495
  const dateStr = date ?? getTodayDate();
@@ -1412,8 +1500,8 @@ async function getFlowSettingsCacheKey(content, date) {
1412
1500
 
1413
1501
  // src/core/atomic-cache.ts
1414
1502
  import crypto from "crypto";
1415
- import path8 from "path";
1416
- import fs8 from "fs-extra";
1503
+ import path9 from "path";
1504
+ import fs9 from "fs-extra";
1417
1505
  var CACHE_COMPLETE_MARKER = ".walkeros-complete";
1418
1506
  var TMP_INFIX = ".tmp-";
1419
1507
  var BASE36 = "0123456789abcdefghijklmnopqrstuvwxyz";
@@ -1440,26 +1528,26 @@ function isCacheTempName(name) {
1440
1528
  return TMP_NAME.test(name);
1441
1529
  }
1442
1530
  async function isCacheDirComplete(dir) {
1443
- return fs8.pathExists(path8.join(dir, CACHE_COMPLETE_MARKER));
1531
+ return fs9.pathExists(path9.join(dir, CACHE_COMPLETE_MARKER));
1444
1532
  }
1445
1533
  async function readCacheDir(src, dest) {
1446
- const marker = path8.join(src, CACHE_COMPLETE_MARKER);
1447
- await fs8.copy(src, dest, { filter: (file) => file !== marker });
1534
+ const marker = path9.join(src, CACHE_COMPLETE_MARKER);
1535
+ await fs9.copy(src, dest, { filter: (file) => file !== marker });
1448
1536
  }
1449
1537
  async function sweepStaleCacheTemps(dir, maxAgeMs = 36e5) {
1450
1538
  let entries;
1451
1539
  try {
1452
- entries = await fs8.readdir(dir);
1540
+ entries = await fs9.readdir(dir);
1453
1541
  } catch {
1454
1542
  return;
1455
1543
  }
1456
1544
  const cutoff = Date.now() - maxAgeMs;
1457
1545
  await Promise.all(
1458
1546
  entries.filter(isCacheTempName).map(async (entry) => {
1459
- const entryPath = path8.join(dir, entry);
1547
+ const entryPath = path9.join(dir, entry);
1460
1548
  try {
1461
- const stats = await fs8.stat(entryPath);
1462
- if (stats.mtimeMs < cutoff) await fs8.remove(entryPath);
1549
+ const stats = await fs9.stat(entryPath);
1550
+ if (stats.mtimeMs < cutoff) await fs9.remove(entryPath);
1463
1551
  } catch {
1464
1552
  }
1465
1553
  })
@@ -1469,7 +1557,7 @@ async function replaceIncomplete(tmp, dest) {
1469
1557
  const aside = tempSibling(dest);
1470
1558
  let moved = false;
1471
1559
  try {
1472
- await fs8.rename(dest, aside);
1560
+ await fs9.rename(dest, aside);
1473
1561
  moved = true;
1474
1562
  } catch (error) {
1475
1563
  if (errorCode2(error) !== "ENOENT") throw error;
@@ -1478,54 +1566,54 @@ async function replaceIncomplete(tmp, dest) {
1478
1566
  const winner = restore ? aside : tmp;
1479
1567
  const loser = restore ? tmp : aside;
1480
1568
  try {
1481
- await fs8.rename(winner, dest);
1569
+ await fs9.rename(winner, dest);
1482
1570
  } catch (error) {
1483
1571
  if (!isOccupied(error) || !await isCacheDirComplete(dest)) {
1484
- if (!restore && moved) await fs8.remove(aside);
1572
+ if (!restore && moved) await fs9.remove(aside);
1485
1573
  throw error;
1486
1574
  }
1487
- await fs8.remove(winner);
1575
+ await fs9.remove(winner);
1488
1576
  }
1489
- await fs8.remove(loser);
1577
+ await fs9.remove(loser);
1490
1578
  }
1491
1579
  async function writeCacheDir(src, dest, logger, options = {}) {
1492
- const copy = options.copy ?? ((from, to) => fs8.copy(from, to));
1493
- const dir = path8.dirname(dest);
1580
+ const copy = options.copy ?? ((from, to) => fs9.copy(from, to));
1581
+ const dir = path9.dirname(dest);
1494
1582
  const tmp = tempSibling(dest);
1495
1583
  try {
1496
- await fs8.ensureDir(dir);
1584
+ await fs9.ensureDir(dir);
1497
1585
  await sweepStaleCacheTemps(dir);
1498
1586
  await copy(src, tmp);
1499
- await fs8.writeFile(path8.join(tmp, CACHE_COMPLETE_MARKER), "");
1587
+ await fs9.writeFile(path9.join(tmp, CACHE_COMPLETE_MARKER), "");
1500
1588
  try {
1501
- await fs8.rename(tmp, dest);
1589
+ await fs9.rename(tmp, dest);
1502
1590
  } catch (error) {
1503
1591
  if (!isOccupied(error)) throw error;
1504
1592
  if (await isCacheDirComplete(dest)) {
1505
- await fs8.remove(tmp);
1593
+ await fs9.remove(tmp);
1506
1594
  return;
1507
1595
  }
1508
1596
  await replaceIncomplete(tmp, dest);
1509
1597
  }
1510
1598
  } catch (error) {
1511
- await fs8.remove(tmp).catch(() => {
1599
+ await fs9.remove(tmp).catch(() => {
1512
1600
  });
1513
- const label = options.label ?? path8.basename(dest);
1601
+ const label = options.label ?? path9.basename(dest);
1514
1602
  logger.warn(
1515
1603
  `Package cache write failed for ${label}: ${getErrorMessage(error)}. The build continues without caching it.`
1516
1604
  );
1517
1605
  }
1518
1606
  }
1519
1607
  async function writeCacheFile(dest, content) {
1520
- const dir = path8.dirname(dest);
1608
+ const dir = path9.dirname(dest);
1521
1609
  const tmp = tempSibling(dest);
1522
- await fs8.ensureDir(dir);
1610
+ await fs9.ensureDir(dir);
1523
1611
  await sweepStaleCacheTemps(dir);
1524
1612
  try {
1525
- await fs8.writeFile(tmp, content, "utf-8");
1526
- await fs8.rename(tmp, dest);
1613
+ await fs9.writeFile(tmp, content, "utf-8");
1614
+ await fs9.rename(tmp, dest);
1527
1615
  } catch (error) {
1528
- await fs8.remove(tmp).catch(() => {
1616
+ await fs9.remove(tmp).catch(() => {
1529
1617
  });
1530
1618
  throw error;
1531
1619
  }
@@ -1584,8 +1672,8 @@ var PACOTE_OPTS = {
1584
1672
  async function loadNpmConfigForPacote(projectDir = process.cwd(), homeDir = os.homedir()) {
1585
1673
  const merged = {};
1586
1674
  const sources = [
1587
- path9.join(homeDir, ".npmrc"),
1588
- path9.join(projectDir, ".npmrc")
1675
+ path10.join(homeDir, ".npmrc"),
1676
+ path10.join(projectDir, ".npmrc")
1589
1677
  ];
1590
1678
  for (const file of sources) {
1591
1679
  let content;
@@ -1651,8 +1739,8 @@ function backoffForAttempt(index) {
1651
1739
  return base + (Math.random() * 2 - 1) * spread;
1652
1740
  }
1653
1741
  function delay2(ms) {
1654
- return new Promise((resolve3) => {
1655
- setTimeout(resolve3, ms);
1742
+ return new Promise((resolve5) => {
1743
+ setTimeout(resolve5, ms);
1656
1744
  });
1657
1745
  }
1658
1746
  function describeError(error) {
@@ -1713,10 +1801,10 @@ function manifestWithResilience(spec, opts, _logger, retryOptions) {
1713
1801
  );
1714
1802
  }
1715
1803
  function getPackageDirectory(baseDir, packageName) {
1716
- return path9.join(baseDir, "node_modules", packageName);
1804
+ return path10.join(baseDir, "node_modules", packageName);
1717
1805
  }
1718
1806
  function getNestedPackageDirectory(baseDir, consumerName, nestedPackageName) {
1719
- return path9.join(
1807
+ return path10.join(
1720
1808
  baseDir,
1721
1809
  "node_modules",
1722
1810
  consumerName,
@@ -1797,12 +1885,12 @@ async function collectAllSpecs(packages, logger, configDir, overrides = {}, npmC
1797
1885
  if (visited.has(visitKey)) continue;
1798
1886
  visited.add(visitKey);
1799
1887
  if (item.localPath) {
1800
- const resolvedPath = path9.isAbsolute(item.localPath) ? item.localPath : path9.resolve(configDir || process.cwd(), item.localPath);
1801
- const candidatePath = path9.join(resolvedPath, "package.json");
1802
- const hasPkgJson = await fs9.pathExists(candidatePath);
1888
+ const resolvedPath = path10.isAbsolute(item.localPath) ? item.localPath : path10.resolve(configDir || process.cwd(), item.localPath);
1889
+ const candidatePath = path10.join(resolvedPath, "package.json");
1890
+ const hasPkgJson = await fs10.pathExists(candidatePath);
1803
1891
  if (hasPkgJson) {
1804
1892
  try {
1805
- const pkgJson = await fs9.readJson(candidatePath);
1893
+ const pkgJson = await fs10.readJson(candidatePath);
1806
1894
  const deps2 = pkgJson.dependencies || {};
1807
1895
  for (const [depName, depSpec] of Object.entries(deps2)) {
1808
1896
  if (typeof depSpec === "string") {
@@ -2031,7 +2119,7 @@ async function downloadPackagesImpl(packages, targetDir, logger, useCache, confi
2031
2119
  );
2032
2120
  const resolution = await resolveVersionConflicts(allSpecs, logger, npmConfig);
2033
2121
  const { topLevel, nested } = resolution;
2034
- await fs9.ensureDir(targetDir);
2122
+ await fs10.ensureDir(targetDir);
2035
2123
  const localPackageMap = /* @__PURE__ */ new Map();
2036
2124
  for (const pkg of packages) {
2037
2125
  if (pkg.path) localPackageMap.set(pkg.name, pkg.path);
@@ -2060,7 +2148,7 @@ async function downloadPackagesImpl(packages, targetDir, logger, useCache, confi
2060
2148
  logger.debug(`Downloading ${packageSpec} (cached)`);
2061
2149
  }
2062
2150
  try {
2063
- await fs9.ensureDir(path9.dirname(packageDir));
2151
+ await fs10.ensureDir(path10.dirname(packageDir));
2064
2152
  await readCacheDir(cachedPath, packageDir);
2065
2153
  packagePaths.set(name, packageDir);
2066
2154
  continue;
@@ -2069,7 +2157,7 @@ async function downloadPackagesImpl(packages, targetDir, logger, useCache, confi
2069
2157
  }
2070
2158
  }
2071
2159
  try {
2072
- await fs9.ensureDir(path9.dirname(packageDir));
2160
+ await fs10.ensureDir(path10.dirname(packageDir));
2073
2161
  const cacheDir = process.env.NPM_CACHE_DIR || getTmpPath(tmpDir, "cache", "npm");
2074
2162
  await extractWithResilience(
2075
2163
  packageSpec,
@@ -2113,7 +2201,7 @@ async function downloadPackagesImpl(packages, targetDir, logger, useCache, confi
2113
2201
  nestedPkg.name
2114
2202
  );
2115
2203
  try {
2116
- await fs9.ensureDir(path9.dirname(nestedDir));
2204
+ await fs10.ensureDir(path10.dirname(nestedDir));
2117
2205
  const cacheDir = process.env.NPM_CACHE_DIR || getTmpPath(tmpDir, "cache", "npm");
2118
2206
  await extractWithResilience(
2119
2207
  resolvedSpec,
@@ -2134,7 +2222,7 @@ async function downloadPackagesImpl(packages, targetDir, logger, useCache, confi
2134
2222
  async function getCachedPackagePath(pkg, tmpDir) {
2135
2223
  const cacheDir = getTmpPath(tmpDir, "cache", "packages");
2136
2224
  const cacheKey = await getPackageCacheKey(pkg.name, pkg.version);
2137
- return path9.join(cacheDir, cacheKey);
2225
+ return path10.join(cacheDir, cacheKey);
2138
2226
  }
2139
2227
  async function isPackageCached(pkg, tmpDir) {
2140
2228
  const cachedPath = await getCachedPackagePath(pkg, tmpDir);
@@ -2166,18 +2254,18 @@ Each package must use the same version across all declarations. Please update yo
2166
2254
  // src/commands/bundle/nft-trace.ts
2167
2255
  import esbuild from "esbuild";
2168
2256
  import { nodeFileTrace } from "@vercel/nft";
2169
- import * as path10 from "path";
2170
- import * as fs10 from "fs/promises";
2257
+ import * as path11 from "path";
2258
+ import * as fs11 from "fs/promises";
2171
2259
  import picomatch from "picomatch";
2172
2260
  async function traceAndCopy(opts) {
2173
2261
  try {
2174
- await fs10.stat(opts.entry);
2262
+ await fs11.stat(opts.entry);
2175
2263
  } catch {
2176
2264
  throw new Error(
2177
2265
  `nft-trace: entry file not found at '${opts.entry}'. Check the path or run 'walkeros bundle' from the directory containing your flow.json.`
2178
2266
  );
2179
2267
  }
2180
- const realBase = await fs10.realpath(opts.base);
2268
+ const realBase = await fs11.realpath(opts.base);
2181
2269
  const expandedExtras = await expandTraceIncludes(
2182
2270
  opts.extraIncludes ?? [],
2183
2271
  realBase
@@ -2186,22 +2274,22 @@ async function traceAndCopy(opts) {
2186
2274
  const { fileList, reasons } = await nodeFileTrace(entries, {
2187
2275
  base: realBase
2188
2276
  });
2189
- const outDirReal = path10.resolve(opts.outDir);
2277
+ const outDirReal = path11.resolve(opts.outDir);
2190
2278
  let copied = 0;
2191
2279
  for (const file of fileList) {
2192
2280
  const reason = reasons.get(file);
2193
2281
  if (reason?.ignored) continue;
2194
- const dst = path10.resolve(outDirReal, file);
2195
- if (!dst.startsWith(outDirReal + path10.sep) && dst !== outDirReal) {
2282
+ const dst = path11.resolve(outDirReal, file);
2283
+ if (!dst.startsWith(outDirReal + path11.sep) && dst !== outDirReal) {
2196
2284
  throw new Error(
2197
2285
  `nft-trace: traced file '${file}' resolves outside outDir. This usually means base is too narrow for a hoisted monorepo. Move base up to the workspace root or set traceInclude explicitly.`
2198
2286
  );
2199
2287
  }
2200
- const src = path10.join(realBase, file);
2201
- await fs10.mkdir(path10.dirname(dst), { recursive: true });
2202
- await fs10.copyFile(src, dst);
2203
- const s = await fs10.stat(src);
2204
- if (s.mode & 73) await fs10.chmod(dst, s.mode);
2288
+ const src = path11.join(realBase, file);
2289
+ await fs11.mkdir(path11.dirname(dst), { recursive: true });
2290
+ await fs11.copyFile(src, dst);
2291
+ const s = await fs11.stat(src);
2292
+ if (s.mode & 73) await fs11.chmod(dst, s.mode);
2205
2293
  copied++;
2206
2294
  }
2207
2295
  return { fileList: Array.from(fileList), copied, reasons };
@@ -2261,13 +2349,13 @@ async function expandTraceIncludes(patterns, base) {
2261
2349
  else literals.push(entry);
2262
2350
  }
2263
2351
  const resolvedLiterals = literals.map(
2264
- (p) => path10.isAbsolute(p) ? p : path10.resolve(base, p)
2352
+ (p) => path11.isAbsolute(p) ? p : path11.resolve(base, p)
2265
2353
  );
2266
2354
  if (globs.length === 0) return resolvedLiterals;
2267
2355
  const matchers = globs.map((g) => picomatch(g));
2268
2356
  const matchedFiles = [];
2269
2357
  await walkFiles(base, base, async (absFile, relFile) => {
2270
- const relPosix = relFile.split(path10.sep).join("/");
2358
+ const relPosix = relFile.split(path11.sep).join("/");
2271
2359
  if (matchers.some((m) => m(relPosix))) {
2272
2360
  matchedFiles.push(absFile);
2273
2361
  }
@@ -2277,27 +2365,27 @@ async function expandTraceIncludes(patterns, base) {
2277
2365
  async function walkFiles(root, dir, visit) {
2278
2366
  let entries;
2279
2367
  try {
2280
- entries = await fs10.readdir(dir, { withFileTypes: true });
2368
+ entries = await fs11.readdir(dir, { withFileTypes: true });
2281
2369
  } catch {
2282
2370
  return;
2283
2371
  }
2284
2372
  for (const entry of entries) {
2285
- const abs = path10.join(dir, entry.name);
2373
+ const abs = path11.join(dir, entry.name);
2286
2374
  if (entry.isDirectory()) {
2287
2375
  await walkFiles(root, abs, visit);
2288
2376
  } else if (entry.isFile()) {
2289
- await visit(abs, path10.relative(root, abs));
2377
+ await visit(abs, path11.relative(root, abs));
2290
2378
  }
2291
2379
  }
2292
2380
  }
2293
2381
 
2294
2382
  // src/commands/bundle/assert-consumer-deps.ts
2295
- import fs11 from "fs-extra";
2296
- import * as path11 from "path";
2383
+ import fs12 from "fs-extra";
2384
+ import * as path12 from "path";
2297
2385
  import semver3 from "semver";
2298
2386
  async function assertConsumerDepsSatisfied(root, logger) {
2299
2387
  const consumers = await collectInstalledPackages(
2300
- path11.join(root, "node_modules")
2388
+ path12.join(root, "node_modules")
2301
2389
  );
2302
2390
  const violations = [];
2303
2391
  for (const consumer of consumers) {
@@ -2344,17 +2432,17 @@ async function assertConsumerDepsSatisfied(root, logger) {
2344
2432
  }
2345
2433
  async function collectInstalledPackages(nodeModulesDir) {
2346
2434
  const result = [];
2347
- if (!await fs11.pathExists(nodeModulesDir)) return result;
2435
+ if (!await fs12.pathExists(nodeModulesDir)) return result;
2348
2436
  async function walk(dir) {
2349
- const entries = await fs11.readdir(dir, { withFileTypes: true });
2437
+ const entries = await fs12.readdir(dir, { withFileTypes: true });
2350
2438
  for (const entry of entries) {
2351
2439
  if (!entry.isDirectory()) continue;
2352
- const full = path11.join(dir, entry.name);
2440
+ const full = path12.join(dir, entry.name);
2353
2441
  if (entry.name.startsWith("@")) {
2354
- const scopedEntries = await fs11.readdir(full, { withFileTypes: true });
2442
+ const scopedEntries = await fs12.readdir(full, { withFileTypes: true });
2355
2443
  for (const scoped of scopedEntries) {
2356
2444
  if (!scoped.isDirectory()) continue;
2357
- await collectOne(path11.join(full, scoped.name));
2445
+ await collectOne(path12.join(full, scoped.name));
2358
2446
  }
2359
2447
  continue;
2360
2448
  }
@@ -2362,12 +2450,12 @@ async function collectInstalledPackages(nodeModulesDir) {
2362
2450
  }
2363
2451
  }
2364
2452
  async function collectOne(pkgDir) {
2365
- const pkgJsonPath = path11.join(pkgDir, "package.json");
2366
- if (!await fs11.pathExists(pkgJsonPath)) return;
2367
- const pkg = await fs11.readJson(pkgJsonPath);
2453
+ const pkgJsonPath = path12.join(pkgDir, "package.json");
2454
+ if (!await fs12.pathExists(pkgJsonPath)) return;
2455
+ const pkg = await fs12.readJson(pkgJsonPath);
2368
2456
  result.push({ dir: pkgDir, pkg });
2369
- const nestedNm = path11.join(pkgDir, "node_modules");
2370
- if (await fs11.pathExists(nestedNm)) await walk(nestedNm);
2457
+ const nestedNm = path12.join(pkgDir, "node_modules");
2458
+ if (await fs12.pathExists(nestedNm)) await walk(nestedNm);
2371
2459
  }
2372
2460
  await walk(nodeModulesDir);
2373
2461
  return result;
@@ -2375,13 +2463,13 @@ async function collectInstalledPackages(nodeModulesDir) {
2375
2463
  async function resolveSiblingOrAncestor(consumerDir, depName, root) {
2376
2464
  let current = consumerDir;
2377
2465
  while (true) {
2378
- const candidate = path11.join(current, "node_modules", depName);
2379
- const pkgJson = path11.join(candidate, "package.json");
2380
- if (await fs11.pathExists(pkgJson)) {
2381
- const pkg = await fs11.readJson(pkgJson);
2466
+ const candidate = path12.join(current, "node_modules", depName);
2467
+ const pkgJson = path12.join(candidate, "package.json");
2468
+ if (await fs12.pathExists(pkgJson)) {
2469
+ const pkg = await fs12.readJson(pkgJson);
2382
2470
  if (pkg.version) return { dir: candidate, version: pkg.version };
2383
2471
  }
2384
- const parent = path11.dirname(current);
2472
+ const parent = path12.dirname(current);
2385
2473
  if (parent === current || !current.startsWith(root)) return null;
2386
2474
  current = parent;
2387
2475
  }
@@ -2391,8 +2479,8 @@ async function resolveSiblingOrAncestor(consumerDir, depName, root) {
2391
2479
  import { getHashServer as getHashServer3 } from "@walkeros/server-core";
2392
2480
 
2393
2481
  // src/core/build-cache.ts
2394
- import fs12 from "fs-extra";
2395
- import path12 from "path";
2482
+ import fs13 from "fs-extra";
2483
+ import path13 from "path";
2396
2484
  import { getHashServer as getHashServer2 } from "@walkeros/server-core";
2397
2485
  function serializeKeyInputs(inputs) {
2398
2486
  return JSON.stringify({
@@ -2405,7 +2493,8 @@ function serializeKeyInputs(inputs) {
2405
2493
  minifyOptions: inputs.minifyOptions ?? null,
2406
2494
  windowCollector: inputs.windowCollector ?? null,
2407
2495
  windowElb: inputs.windowElb ?? null,
2408
- versionsHash: inputs.versionsHash
2496
+ versionsHash: inputs.versionsHash,
2497
+ toolchain: inputs.toolchain
2409
2498
  });
2410
2499
  }
2411
2500
  async function computeCodeCacheKey(codeContent, inputs) {
@@ -2417,11 +2506,11 @@ ${serializeKeyInputs(inputs)}`;
2417
2506
  async function getBuildCachePath(configContent, tmpDir) {
2418
2507
  const cacheDir = getTmpPath(tmpDir, "cache", "builds");
2419
2508
  const cacheKey = await getFlowSettingsCacheKey(configContent);
2420
- return path12.join(cacheDir, `${cacheKey}.js`);
2509
+ return path13.join(cacheDir, `${cacheKey}.js`);
2421
2510
  }
2422
2511
  async function isBuildCached(configContent, tmpDir) {
2423
2512
  const cachePath = await getBuildCachePath(configContent, tmpDir);
2424
- return fs12.pathExists(cachePath);
2513
+ return fs13.pathExists(cachePath);
2425
2514
  }
2426
2515
  async function cacheBuild(configContent, buildOutput, tmpDir) {
2427
2516
  const cachePath = await getBuildCachePath(configContent, tmpDir);
@@ -2429,15 +2518,15 @@ async function cacheBuild(configContent, buildOutput, tmpDir) {
2429
2518
  }
2430
2519
  async function getCachedBuild(configContent, tmpDir) {
2431
2520
  const cachePath = await getBuildCachePath(configContent, tmpDir);
2432
- if (await fs12.pathExists(cachePath)) {
2433
- return await fs12.readFile(cachePath, "utf-8");
2521
+ if (await fs13.pathExists(cachePath)) {
2522
+ return await fs13.readFile(cachePath, "utf-8");
2434
2523
  }
2435
2524
  return null;
2436
2525
  }
2437
2526
  async function getCodeCachePath(codeContent, tmpDir, inputs) {
2438
2527
  const cacheDir = getTmpPath(tmpDir, "cache", "code");
2439
2528
  const cacheKey = await computeCodeCacheKey(codeContent, inputs);
2440
- return path12.join(cacheDir, `${cacheKey}.js`);
2529
+ return path13.join(cacheDir, `${cacheKey}.js`);
2441
2530
  }
2442
2531
  async function cacheCode(codeContent, codeOutput, tmpDir, inputs) {
2443
2532
  const cachePath = await getCodeCachePath(codeContent, tmpDir, inputs);
@@ -2445,21 +2534,44 @@ async function cacheCode(codeContent, codeOutput, tmpDir, inputs) {
2445
2534
  }
2446
2535
  async function getCachedCode(codeContent, tmpDir, inputs) {
2447
2536
  const cachePath = await getCodeCachePath(codeContent, tmpDir, inputs);
2448
- if (await fs12.pathExists(cachePath)) {
2449
- return fs12.readFile(cachePath, "utf-8");
2537
+ if (await fs13.pathExists(cachePath)) {
2538
+ return fs13.readFile(cachePath, "utf-8");
2450
2539
  }
2451
2540
  return null;
2452
2541
  }
2453
2542
  async function ensureCodeOnDisk(codeContent, compiledCode, tmpDir, inputs) {
2454
2543
  const cacheDir = getTmpPath(tmpDir, "cache", "code");
2455
2544
  const cacheKey = await computeCodeCacheKey(codeContent, inputs);
2456
- const cachePath = path12.join(cacheDir, `${cacheKey}.mjs`);
2457
- if (!await fs12.pathExists(cachePath)) {
2545
+ const cachePath = path13.join(cacheDir, `${cacheKey}.mjs`);
2546
+ if (!await fs13.pathExists(cachePath)) {
2458
2547
  await writeCacheFile(cachePath, compiledCode);
2459
2548
  }
2460
2549
  return cachePath;
2461
2550
  }
2462
2551
 
2552
+ // src/version.ts
2553
+ import { readFileSync as readFileSync2 } from "fs";
2554
+ import { fileURLToPath } from "url";
2555
+ import { dirname as dirname3, join as join4 } from "path";
2556
+ var versionFilename = fileURLToPath(import.meta.url);
2557
+ var versionDirname = dirname3(versionFilename);
2558
+ function findPackageJson() {
2559
+ const paths = [
2560
+ join4(versionDirname, "../package.json"),
2561
+ // dist/ or src/
2562
+ join4(versionDirname, "../../package.json")
2563
+ // src/core/ (not used, but safe)
2564
+ ];
2565
+ for (const p of paths) {
2566
+ try {
2567
+ return readFileSync2(p, "utf-8");
2568
+ } catch {
2569
+ }
2570
+ }
2571
+ return JSON.stringify({ version: "0.0.0" });
2572
+ }
2573
+ var VERSION = JSON.parse(findPackageJson()).version;
2574
+
2463
2575
  // src/commands/bundle/bundler.ts
2464
2576
  function isInlineCode(code) {
2465
2577
  return code !== null && typeof code === "object" && !Array.isArray(code) && "push" in code;
@@ -2500,18 +2612,18 @@ function generateInlineCode(inline, runtimeProps, isDestination) {
2500
2612
  }
2501
2613
  async function copyIncludes(includes, sourceDir, outputDir, logger) {
2502
2614
  for (const include of includes) {
2503
- const sourcePath = path13.resolve(sourceDir, include);
2504
- const folderName = path13.basename(include);
2505
- const destPath = path13.join(outputDir, folderName);
2506
- const resolvedOutput = path13.resolve(outputDir);
2507
- const resolvedSource = path13.resolve(sourcePath);
2508
- if (resolvedSource === resolvedOutput || resolvedOutput.startsWith(resolvedSource + path13.sep) || resolvedSource.startsWith(resolvedOutput + path13.sep)) {
2615
+ const sourcePath = path14.resolve(sourceDir, include);
2616
+ const folderName = path14.basename(include);
2617
+ const destPath = path14.join(outputDir, folderName);
2618
+ const resolvedOutput = path14.resolve(outputDir);
2619
+ const resolvedSource = path14.resolve(sourcePath);
2620
+ if (resolvedSource === resolvedOutput || resolvedOutput.startsWith(resolvedSource + path14.sep) || resolvedSource.startsWith(resolvedOutput + path14.sep)) {
2509
2621
  throw new Error(
2510
2622
  `Circular include detected: "${include}" resolves to "${resolvedSource}" which overlaps with output directory "${resolvedOutput}"`
2511
2623
  );
2512
2624
  }
2513
- if (await fs13.pathExists(sourcePath)) {
2514
- await fs13.copy(sourcePath, destPath);
2625
+ if (await fs14.pathExists(sourcePath)) {
2626
+ await fs14.copy(sourcePath, destPath);
2515
2627
  logger.debug(`Copied ${include} to output`);
2516
2628
  } else {
2517
2629
  logger.warn(`Include folder not found: ${include}`);
@@ -2536,27 +2648,42 @@ function generateCacheKeyContent(flowSettings, buildOptions, versionsHash) {
2536
2648
  // not change this key. The durable cross-version cache bust is the
2537
2649
  // version/toolchain hash, not this field.
2538
2650
  emittedFormat: resolveEmittedFormat(buildOptions),
2539
- versionsHash
2651
+ versionsHash,
2652
+ // Generated code changes with the CLI, so a CLI upgrade never serves a
2653
+ // build made by the previous version.
2654
+ toolchain: VERSION
2540
2655
  };
2541
2656
  return JSON.stringify(configForCache);
2542
2657
  }
2543
2658
  function resolveEmittedFormat(buildOptions) {
2544
2659
  return buildOptions.platform === "browser" ? "iife" : "esm";
2545
2660
  }
2546
- async function bundleCore(flowSettings, buildOptions, logger, showStats = false) {
2661
+ async function resolvedVersionLines(topLevel, packagePaths) {
2662
+ const lines = [];
2663
+ for (const [name, pkg] of [...topLevel.entries()].sort(
2664
+ ([a], [b]) => a.localeCompare(b)
2665
+ )) {
2666
+ const installed = packagePaths.get(name);
2667
+ lines.push(
2668
+ pkg.version === "local" && installed ? `${name}@local:${await hashInstalledDir(installed)}` : `${name}@${pkg.version}`
2669
+ );
2670
+ }
2671
+ return lines;
2672
+ }
2673
+ async function bundleCore(flowSettings, buildOptions, logger, showStats = false, provenance) {
2547
2674
  const bundleStartTime = Date.now();
2548
2675
  const CACHE_DIR = buildOptions.tempDir || getTmpPath();
2549
2676
  const npmConfig = await loadNpmConfigForPacote(
2550
2677
  buildOptions.configDir ?? process.cwd()
2551
2678
  );
2552
- const outputPath = path13.resolve(buildOptions.output);
2553
- const outputDirAbs = path13.dirname(outputPath);
2554
- await fs13.remove(path13.join(outputDirAbs, "node_modules"));
2555
- await fs13.remove(path13.join(outputDirAbs, "package.json"));
2556
- await fs13.remove(path13.join(outputDirAbs, "package-lock.json"));
2679
+ const outputPath = path14.resolve(buildOptions.output);
2680
+ const outputDirAbs = path14.dirname(outputPath);
2681
+ await fs14.remove(path14.join(outputDirAbs, "node_modules"));
2682
+ await fs14.remove(path14.join(outputDirAbs, "package.json"));
2683
+ await fs14.remove(path14.join(outputDirAbs, "package-lock.json"));
2557
2684
  const TEMP_DIR = buildOptions.tempDir || await tmpRunDir("build");
2558
2685
  try {
2559
- await fs13.ensureDir(TEMP_DIR);
2686
+ await fs14.ensureDir(TEMP_DIR);
2560
2687
  const hasSourcesOrDests = Object.keys(flowSettings.sources || {}).length > 0 || Object.keys(flowSettings.destinations || {}).length > 0;
2561
2688
  if (hasSourcesOrDests && !buildOptions.packages["@walkeros/collector"]) {
2562
2689
  buildOptions.packages["@walkeros/collector"] = {};
@@ -2584,10 +2711,10 @@ async function bundleCore(flowSettings, buildOptions, logger, showStats = false)
2584
2711
  );
2585
2712
  const warnedBundleFieldPackages = /* @__PURE__ */ new Set();
2586
2713
  for (const [pkgName, pkgPath] of packagePaths.entries()) {
2587
- const pkgJsonPath = path13.join(pkgPath, "package.json");
2714
+ const pkgJsonPath = path14.join(pkgPath, "package.json");
2588
2715
  let pkgJson;
2589
2716
  try {
2590
- pkgJson = await fs13.readJSON(pkgJsonPath);
2717
+ pkgJson = await fs14.readJSON(pkgJsonPath);
2591
2718
  } catch {
2592
2719
  continue;
2593
2720
  }
@@ -2612,15 +2739,30 @@ async function bundleCore(flowSettings, buildOptions, logger, showStats = false)
2612
2739
  require: pkgJson.main
2613
2740
  }
2614
2741
  };
2615
- await fs13.writeJSON(pkgJsonPath, pkgJson, { spaces: 2 });
2742
+ await fs14.writeJSON(pkgJsonPath, pkgJson, { spaces: 2 });
2616
2743
  }
2617
2744
  }
2618
2745
  }
2619
2746
  if (process.env.BUNDLER_STRICT_RANGES !== "0") {
2620
2747
  await assertConsumerDepsSatisfied(TEMP_DIR, logger);
2621
2748
  }
2622
- const sortedVersions = [...resolutionResult.topLevel.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([name, p]) => `${name}@${p.version}`);
2749
+ const sortedVersions = await resolvedVersionLines(
2750
+ resolutionResult.topLevel,
2751
+ packagePaths
2752
+ );
2623
2753
  const versionsHash = await getHashServer3(sortedVersions.join("\n"), 12);
2754
+ if (provenance) {
2755
+ applyCollectorProvenance(
2756
+ flowSettings,
2757
+ provenance.flowName,
2758
+ provenance.release,
2759
+ () => releaseDigest({
2760
+ configDigest: provenance.configDigest,
2761
+ versions: sortedVersions,
2762
+ toolchain: VERSION
2763
+ })
2764
+ );
2765
+ }
2624
2766
  const declaredPackages = Object.keys(buildOptions.packages).filter(
2625
2767
  (name) => !name.startsWith(".") && !name.startsWith("/")
2626
2768
  );
@@ -2635,8 +2777,8 @@ async function bundleCore(flowSettings, buildOptions, logger, showStats = false)
2635
2777
  const cachedBuild = await getCachedBuild(configContent, CACHE_DIR);
2636
2778
  if (cachedBuild) {
2637
2779
  logger.debug("Using cached build");
2638
- await fs13.ensureDir(path13.dirname(outputPath));
2639
- await fs13.writeFile(outputPath, cachedBuild);
2780
+ await fs14.ensureDir(path14.dirname(outputPath));
2781
+ await fs14.writeFile(outputPath, cachedBuild);
2640
2782
  await finishArtifact(
2641
2783
  outputPath,
2642
2784
  flowSettings,
@@ -2645,7 +2787,7 @@ async function bundleCore(flowSettings, buildOptions, logger, showStats = false)
2645
2787
  declaredPackages,
2646
2788
  logger
2647
2789
  );
2648
- const stats2 = await fs13.stat(outputPath);
2790
+ const stats2 = await fs14.stat(outputPath);
2649
2791
  const sizeKB2 = (stats2.size / 1024).toFixed(1);
2650
2792
  logger.info(`Output: ${outputPath} (${sizeKB2} KB, cached)`);
2651
2793
  if (showStats) {
@@ -2668,14 +2810,14 @@ async function bundleCore(flowSettings, buildOptions, logger, showStats = false)
2668
2810
  }
2669
2811
  }
2670
2812
  }
2671
- const packageJsonPath = path13.join(TEMP_DIR, "package.json");
2672
- await fs13.writeFile(
2813
+ const packageJsonPath = path14.join(TEMP_DIR, "package.json");
2814
+ await fs14.writeFile(
2673
2815
  packageJsonPath,
2674
2816
  JSON.stringify({ type: "module" }, null, 2)
2675
2817
  );
2676
2818
  logger.debug("Creating entry point");
2677
2819
  const { codeEntry, dataPayload, hasFlow, devPackages } = await createEntryPoint(flowSettings, buildOptions, packagePaths, logger);
2678
- await fs13.ensureDir(path13.dirname(outputPath));
2820
+ await fs14.ensureDir(path14.dirname(outputPath));
2679
2821
  const codeKeyInputs = {
2680
2822
  // Deploy browser skeletons (externalizeDev) externalize each `<pkg>/dev`
2681
2823
  // so the lazy registry is not inlined; in-process simulate/push inline it.
@@ -2695,7 +2837,8 @@ async function bundleCore(flowSettings, buildOptions, logger, showStats = false)
2695
2837
  // Retained but inert (deprecation surface): no codegen reads it anymore.
2696
2838
  // Keeping it here only over-keys the code cache, which is harmless.
2697
2839
  windowElb: buildOptions.windowElb,
2698
- versionsHash
2840
+ versionsHash,
2841
+ toolchain: VERSION
2699
2842
  };
2700
2843
  let compiledCode = null;
2701
2844
  if (buildOptions.cache !== false) {
@@ -2707,8 +2850,8 @@ async function bundleCore(flowSettings, buildOptions, logger, showStats = false)
2707
2850
  logger.debug(
2708
2851
  `Running esbuild (target: ${buildOptions.target || "es2018"}, format: ${buildOptions.format})`
2709
2852
  );
2710
- const entryPath = path13.join(TEMP_DIR, "entry.js");
2711
- await fs13.writeFile(entryPath, codeEntry);
2853
+ const entryPath = path14.join(TEMP_DIR, "entry.js");
2854
+ await fs14.writeFile(entryPath, codeEntry);
2712
2855
  const esbuildOptions = createEsbuildOptions(
2713
2856
  { ...buildOptions, minify: false },
2714
2857
  entryPath,
@@ -2729,7 +2872,7 @@ async function bundleCore(flowSettings, buildOptions, logger, showStats = false)
2729
2872
  } finally {
2730
2873
  await esbuild2.stop();
2731
2874
  }
2732
- compiledCode = await fs13.readFile(outputPath, "utf-8");
2875
+ compiledCode = await fs14.readFile(outputPath, "utf-8");
2733
2876
  if (buildOptions.cache !== false) {
2734
2877
  await cacheCode(codeEntry, compiledCode, CACHE_DIR, codeKeyInputs);
2735
2878
  }
@@ -2747,7 +2890,7 @@ export { __configData };`;
2747
2890
  ` : "";
2748
2891
  const esmOutput = `${banner}${compiledCode}
2749
2892
  ${dataDeclaration}`;
2750
- await fs13.writeFile(outputPath, esmOutput);
2893
+ await fs14.writeFile(outputPath, esmOutput);
2751
2894
  } else {
2752
2895
  const stage2Entry = (buildOptions.platform || "node") === "browser" ? generateWebEntry(stage1Path, dataPayload, {
2753
2896
  windowCollector: buildOptions.windowCollector,
@@ -2755,8 +2898,8 @@ ${dataDeclaration}`;
2755
2898
  platform: buildOptions.platform,
2756
2899
  observe: readObserveConnect(flowSettings, logger)
2757
2900
  }) : generateServerEntry(stage1Path, dataPayload);
2758
- const stage2EntryPath = path13.join(TEMP_DIR, "stage2.mjs");
2759
- await fs13.writeFile(stage2EntryPath, stage2Entry);
2901
+ const stage2EntryPath = path14.join(TEMP_DIR, "stage2.mjs");
2902
+ await fs14.writeFile(stage2EntryPath, stage2Entry);
2760
2903
  const stage2Options = {
2761
2904
  entryPoints: [stage2EntryPath],
2762
2905
  bundle: true,
@@ -2794,7 +2937,7 @@ ${dataDeclaration}`;
2794
2937
  await esbuild2.stop();
2795
2938
  }
2796
2939
  }
2797
- const outputStats = await fs13.stat(outputPath);
2940
+ const outputStats = await fs14.stat(outputPath);
2798
2941
  const sizeKB = (outputStats.size / 1024).toFixed(1);
2799
2942
  const buildTime = ((Date.now() - bundleStartTime) / 1e3).toFixed(1);
2800
2943
  logger.info(`Output: ${outputPath} (${sizeKB} KB, ${buildTime}s)`);
@@ -2804,7 +2947,7 @@ ${dataDeclaration}`;
2804
2947
  buildOptions,
2805
2948
  versionsHash
2806
2949
  );
2807
- const buildOutput = await fs13.readFile(outputPath, "utf-8");
2950
+ const buildOutput = await fs14.readFile(outputPath, "utf-8");
2808
2951
  await cacheBuild(configContent, buildOutput, CACHE_DIR);
2809
2952
  logger.debug("Build cached for future use");
2810
2953
  }
@@ -2830,7 +2973,7 @@ ${dataDeclaration}`;
2830
2973
  throw error;
2831
2974
  } finally {
2832
2975
  if (!buildOptions.tempDir) {
2833
- await fs13.remove(TEMP_DIR).catch(() => {
2976
+ await fs14.remove(TEMP_DIR).catch(() => {
2834
2977
  });
2835
2978
  }
2836
2979
  }
@@ -2859,12 +3002,12 @@ async function finishArtifact(outputPath, flowSettings, buildOptions, tempDir, d
2859
3002
  await copyIncludes(
2860
3003
  buildOptions.include,
2861
3004
  buildOptions.configDir || process.cwd(),
2862
- path13.dirname(outputPath),
3005
+ path14.dirname(outputPath),
2863
3006
  logger
2864
3007
  );
2865
3008
  }
2866
3009
  async function collectBundleStats(outputPath, packages, startTime, entryContent) {
2867
- const stats = await fs13.stat(outputPath);
3010
+ const stats = await fs14.stat(outputPath);
2868
3011
  const totalSize = stats.size;
2869
3012
  const buildTime = Date.now() - startTime;
2870
3013
  const packageStats = Object.entries(packages).map(([name, pkg]) => ({
@@ -2943,13 +3086,13 @@ function getNodeExternals() {
2943
3086
  return externals;
2944
3087
  }
2945
3088
  async function runNftServerPath(outputPath, flowSettings, buildOptions, tempDir, declaredPackages, logger) {
2946
- const outDir = path13.dirname(outputPath);
3089
+ const outDir = path14.dirname(outputPath);
2947
3090
  const flowExtraIncludes = [
2948
3091
  ...flowSettings.config?.bundle?.traceInclude ?? [],
2949
3092
  ...buildOptions.traceInclude ?? []
2950
3093
  ];
2951
- const stagedEntry = path13.join(tempDir, "__nft-flow.mjs");
2952
- await fs13.copyFile(outputPath, stagedEntry);
3094
+ const stagedEntry = path14.join(tempDir, "__nft-flow.mjs");
3095
+ await fs14.copyFile(outputPath, stagedEntry);
2953
3096
  let result;
2954
3097
  try {
2955
3098
  result = await traceAndCopy({
@@ -2959,12 +3102,12 @@ async function runNftServerPath(outputPath, flowSettings, buildOptions, tempDir,
2959
3102
  extraIncludes: flowExtraIncludes
2960
3103
  });
2961
3104
  } finally {
2962
- await fs13.remove(stagedEntry).catch(() => {
3105
+ await fs14.remove(stagedEntry).catch(() => {
2963
3106
  });
2964
3107
  }
2965
- const stagedRel = path13.relative(await fs13.realpath(tempDir), stagedEntry);
3108
+ const stagedRel = path14.relative(await fs14.realpath(tempDir), stagedEntry);
2966
3109
  const trimmedFileList = result.fileList.filter((f) => f !== stagedRel);
2967
- await fs13.remove(path13.join(outDir, stagedRel)).catch(() => {
3110
+ await fs14.remove(path14.join(outDir, stagedRel)).catch(() => {
2968
3111
  });
2969
3112
  if (declaredPackages.length > 0) {
2970
3113
  const unused = assertDepsTraced({
@@ -2984,8 +3127,8 @@ async function runNftServerPath(outputPath, flowSettings, buildOptions, tempDir,
2984
3127
  if (name.startsWith(".") || name.startsWith("/")) continue;
2985
3128
  dependencies[name] = "*";
2986
3129
  }
2987
- const sidecarPath = path13.join(outDir, "package.json");
2988
- await fs13.writeJson(
3130
+ const sidecarPath = path14.join(outDir, "package.json");
3131
+ await fs14.writeJson(
2989
3132
  sidecarPath,
2990
3133
  {
2991
3134
  name: "walkeros-bundle",
@@ -3085,7 +3228,7 @@ async function computePackageExports(usedPackages, packagePaths) {
3085
3228
  const localPath = packagePaths.get(packageName);
3086
3229
  if (!localPath) continue;
3087
3230
  try {
3088
- const pkgJson = await fs13.readJSON(path13.join(localPath, "package.json"));
3231
+ const pkgJson = await fs14.readJSON(path14.join(localPath, "package.json"));
3089
3232
  const exports = pkgJson?.walkerOS?.exports;
3090
3233
  if (exports && typeof exports === "object" && !Array.isArray(exports))
3091
3234
  result[packageName] = Object.keys(exports);
@@ -3100,8 +3243,8 @@ async function computeDevPackages(usedPackages, packagePaths) {
3100
3243
  const localPath = packagePaths.get(packageName);
3101
3244
  if (!localPath) continue;
3102
3245
  try {
3103
- const pkgJsonPath = path13.join(localPath, "package.json");
3104
- const pkgJson = await fs13.readJSON(pkgJsonPath);
3246
+ const pkgJsonPath = path14.join(localPath, "package.json");
3247
+ const pkgJson = await fs14.readJSON(pkgJsonPath);
3105
3248
  const exports = pkgJson.exports;
3106
3249
  if (exports && typeof exports === "object" && "./dev" in exports) {
3107
3250
  devPackages.push(packageName);
@@ -3255,10 +3398,10 @@ ${firstError.text}`
3255
3398
  function buildDataPayload(flowSettings) {
3256
3399
  return buildSplitConfigObject(flowSettings, detectNamedImports(flowSettings)).dataPayloadObj;
3257
3400
  }
3258
- function applyCollectorProvenance(flowSettings, flowName, release2, now = () => (/* @__PURE__ */ new Date()).toISOString()) {
3401
+ function applyCollectorProvenance(flowSettings, flowName, release2, fallback) {
3259
3402
  const collector = flowSettings.collector ?? {};
3260
3403
  collector.name ??= flowName;
3261
- collector.release ??= release2 ?? now();
3404
+ collector.release = release2 ?? collector.release ?? fallback();
3262
3405
  flowSettings.collector = collector;
3263
3406
  }
3264
3407
  function buildSplitConfigObject(flowSettings, namedImports) {
@@ -3457,7 +3600,7 @@ function generateWebEntry(stage1Path, dataPayload, options = {}) {
3457
3600
  const assignments = [];
3458
3601
  if (options.windowCollector) {
3459
3602
  assignments.push(
3460
- ` if (typeof window !== 'undefined') window['${options.windowCollector}'] = collector;`
3603
+ ` if (typeof window !== 'undefined') window['${assertWindowCollector(options.windowCollector)}'] = collector;`
3461
3604
  );
3462
3605
  }
3463
3606
  const assignmentCode = assignments.length > 0 ? "\n" + assignments.join("\n") : "";
@@ -3487,7 +3630,7 @@ function generateWrapEntry(stage1Path, options = {}) {
3487
3630
  const assignments = [];
3488
3631
  if (options.windowCollector) {
3489
3632
  assignments.push(
3490
- ` if (typeof window !== 'undefined') window['${options.windowCollector}'] = collector;`
3633
+ ` if (typeof window !== 'undefined') window['${assertWindowCollector(options.windowCollector)}'] = collector;`
3491
3634
  );
3492
3635
  }
3493
3636
  const assignmentCode = assignments.length > 0 ? "\n" + assignments.join("\n") : "";
@@ -3726,12 +3869,12 @@ function resolveTarget2(target) {
3726
3869
  }
3727
3870
 
3728
3871
  // src/commands/bundle/upload.ts
3729
- import fs14 from "fs-extra";
3872
+ import fs15 from "fs-extra";
3730
3873
  function sanitizeUrl(url) {
3731
3874
  return url.split("?")[0];
3732
3875
  }
3733
3876
  async function uploadBundleToUrl(filePath, url, timeoutMs = 3e4) {
3734
- const bundleContent = await fs14.readFile(filePath);
3877
+ const bundleContent = await fs15.readFile(filePath);
3735
3878
  const doUpload = async (attempt) => {
3736
3879
  const response = await fetch(url, {
3737
3880
  method: "PUT",
@@ -3776,14 +3919,14 @@ Packages:`);
3776
3919
 
3777
3920
  // src/commands/bundle/index.ts
3778
3921
  function resolveOutputPath(output, buildOptions, flowSubdir) {
3779
- const resolved = path14.resolve(output);
3922
+ const resolved = path15.resolve(output);
3780
3923
  const filename = buildOptions.platform === "browser" ? "walker.js" : "flow.mjs";
3781
3924
  if (flowSubdir) {
3782
- return path14.join(resolved, flowSubdir, filename);
3925
+ return path15.join(resolved, flowSubdir, filename);
3783
3926
  }
3784
- const ext = path14.extname(resolved);
3785
- if (output.endsWith("/") || output.endsWith(path14.sep) || !ext) {
3786
- return path14.join(resolved, filename);
3927
+ const ext = path15.extname(resolved);
3928
+ if (output.endsWith("/") || output.endsWith(path15.sep) || !ext) {
3929
+ return path15.join(resolved, filename);
3787
3930
  }
3788
3931
  return resolved;
3789
3932
  }
@@ -3791,26 +3934,33 @@ function isArchiveOutput(output) {
3791
3934
  const lower = output.toLowerCase();
3792
3935
  return lower.endsWith(".tar.gz") || lower.endsWith(".tgz");
3793
3936
  }
3794
- async function runBundleCoreWithArchive(flowSettings, buildOptions, logger, showStats = false) {
3937
+ async function runBundleCoreWithArchive(flowSettings, buildOptions, logger, showStats, provenance) {
3795
3938
  if (!isArchiveOutput(buildOptions.output)) {
3796
- return bundleCore(flowSettings, buildOptions, logger, showStats);
3939
+ return bundleCore(
3940
+ flowSettings,
3941
+ buildOptions,
3942
+ logger,
3943
+ showStats,
3944
+ provenance
3945
+ );
3797
3946
  }
3798
3947
  if (buildOptions.platform !== "node") {
3799
3948
  throw new Error(
3800
- `Archive output (${path14.extname(buildOptions.output)}) is only supported for server (node) flows. Web bundles are a single-file IIFE with no node_modules to pack \u2014 use a .js output instead.`
3949
+ `Archive output (${path15.extname(buildOptions.output)}) is only supported for server (node) flows. Web bundles are a single-file IIFE with no node_modules to pack \u2014 use a .js output instead.`
3801
3950
  );
3802
3951
  }
3803
- const archivePath = path14.resolve(buildOptions.output);
3952
+ const archivePath = path15.resolve(buildOptions.output);
3804
3953
  const tempDir = await tmpRunDir("archive");
3805
3954
  try {
3806
3955
  const stats = await bundleCore(
3807
3956
  flowSettings,
3808
- { ...buildOptions, output: path14.join(tempDir, "flow.mjs") },
3957
+ { ...buildOptions, output: path15.join(tempDir, "flow.mjs") },
3809
3958
  logger,
3810
- showStats
3959
+ showStats,
3960
+ provenance
3811
3961
  );
3812
3962
  for (const required of ["flow.mjs", "package.json"]) {
3813
- if (!await fs15.pathExists(path14.join(tempDir, required))) {
3963
+ if (!await fs16.pathExists(path15.join(tempDir, required))) {
3814
3964
  throw new Error(
3815
3965
  `Bundle archive build is missing ${required}; cannot pack ${archivePath}.`
3816
3966
  );
@@ -3819,14 +3969,14 @@ async function runBundleCoreWithArchive(flowSettings, buildOptions, logger, show
3819
3969
  const candidates = ["flow.mjs", "node_modules", "package.json"];
3820
3970
  const entries = [];
3821
3971
  for (const entry of candidates) {
3822
- if (await fs15.pathExists(path14.join(tempDir, entry))) entries.push(entry);
3972
+ if (await fs16.pathExists(path15.join(tempDir, entry))) entries.push(entry);
3823
3973
  }
3824
- await fs15.ensureDir(path14.dirname(archivePath));
3974
+ await fs16.ensureDir(path15.dirname(archivePath));
3825
3975
  await packBundleDir(tempDir, archivePath, entries);
3826
3976
  logger.debug(`Packed bundle archive: ${archivePath}`);
3827
3977
  return stats;
3828
3978
  } finally {
3829
- await fs15.remove(tempDir).catch(() => {
3979
+ await fs16.remove(tempDir).catch(() => {
3830
3980
  });
3831
3981
  }
3832
3982
  }
@@ -3861,7 +4011,7 @@ async function bundleCommand(options) {
3861
4011
  } catch {
3862
4012
  throw new Error("Invalid JSON received on stdin");
3863
4013
  }
3864
- configPath = path14.resolve(process.cwd(), "stdin.config.json");
4014
+ configPath = path15.resolve(process.cwd(), "stdin.config.json");
3865
4015
  } else {
3866
4016
  const file = options.config || "bundle.config.json";
3867
4017
  configPath = resolveAsset(file, "config");
@@ -3879,18 +4029,18 @@ async function bundleCommand(options) {
3879
4029
  flowSettings,
3880
4030
  buildOptions,
3881
4031
  flowName,
3882
- isMultiFlow
4032
+ isMultiFlow,
4033
+ configDigest: configDigest2
3883
4034
  } of configsToBundle) {
3884
4035
  try {
3885
4036
  if (options.cache !== void 0) {
3886
4037
  buildOptions.cache = options.cache;
3887
4038
  }
3888
- applyCollectorProvenance(flowSettings, flowName, options.release);
3889
4039
  const outputIsUrl = options.output ? isUrl(options.output) : false;
3890
4040
  const uploadUrl = outputIsUrl ? options.output : void 0;
3891
4041
  if (outputIsUrl) {
3892
4042
  const ext = buildOptions.platform === "browser" ? ".js" : ".mjs";
3893
- buildOptions.output = path14.join(
4043
+ buildOptions.output = path15.join(
3894
4044
  await tmpRunDir("bundle"),
3895
4045
  `bundle${ext}`
3896
4046
  );
@@ -3914,19 +4064,20 @@ async function bundleCommand(options) {
3914
4064
  flowSettings,
3915
4065
  buildOptions,
3916
4066
  logger,
3917
- shouldCollectStats
4067
+ shouldCollectStats === true,
4068
+ { flowName, release: options.release, configDigest: configDigest2 }
3918
4069
  );
3919
4070
  results.push({ flowName, success: true, stats });
3920
4071
  if (uploadUrl) {
3921
4072
  await uploadBundleToUrl(buildOptions.output, uploadUrl);
3922
4073
  logger.info(`Uploaded to: ${sanitizeUrl(uploadUrl)}`);
3923
- await fs15.remove(path14.dirname(buildOptions.output));
4074
+ await fs16.remove(path15.dirname(buildOptions.output));
3924
4075
  }
3925
4076
  if (!options.json && !options.all && options.stats && stats) {
3926
4077
  displayStats(stats, logger);
3927
4078
  }
3928
4079
  if (writingToStdout && !options.json) {
3929
- const bundleContent = await fs15.readFile(buildOptions.output);
4080
+ const bundleContent = await fs16.readFile(buildOptions.output);
3930
4081
  await writeResult(bundleContent, {});
3931
4082
  if (process.stdout.isTTY) {
3932
4083
  const defaultPath = buildOptions.platform === "browser" ? "./dist/walker.js" : "./dist/flow.mjs";
@@ -4009,7 +4160,7 @@ async function bundle(configOrPath, options = {}) {
4009
4160
  );
4010
4161
  }
4011
4162
  let rawConfig;
4012
- let configPath = path14.resolve(process.cwd(), "walkeros.config.json");
4163
+ let configPath = path15.resolve(process.cwd(), "walkeros.config.json");
4013
4164
  if (typeof configOrPath === "string") {
4014
4165
  configPath = resolveAsset(configOrPath, "config");
4015
4166
  rawConfig = await loadJsonConfig(configPath);
@@ -4022,13 +4173,12 @@ async function bundle(configOrPath, options = {}) {
4022
4173
  withDev: preset.withDev,
4023
4174
  externalizeDev: preset.externalizeDev
4024
4175
  };
4025
- const { flowSettings, buildOptions, flowName } = loadBundleConfig(rawConfig, {
4176
+ const { flowSettings, buildOptions, flowName, configDigest: configDigest2 } = loadBundleConfig(rawConfig, {
4026
4177
  configPath,
4027
4178
  flowName: options.flowName,
4028
4179
  buildOverrides: mergedOverrides,
4029
4180
  buildEnv: options.buildEnv
4030
4181
  });
4031
- applyCollectorProvenance(flowSettings, flowName, options.release);
4032
4182
  if (options.cache !== void 0) {
4033
4183
  buildOptions.cache = options.cache;
4034
4184
  }
@@ -4037,13 +4187,14 @@ async function bundle(configOrPath, options = {}) {
4037
4187
  flowSettings,
4038
4188
  buildOptions,
4039
4189
  logger,
4040
- options.stats ?? false
4190
+ options.stats ?? false,
4191
+ { flowName, release: options.release, configDigest: configDigest2 }
4041
4192
  );
4042
4193
  }
4043
4194
 
4044
4195
  // src/commands/push/index.ts
4045
- import path17 from "path";
4046
- import fs17 from "fs-extra";
4196
+ import path18 from "path";
4197
+ import fs18 from "fs-extra";
4047
4198
  import { createIngest, getPlatform as getPlatform3, stepId } from "@walkeros/core";
4048
4199
  import {
4049
4200
  enrichEvent,
@@ -4054,7 +4205,7 @@ import {
4054
4205
  } from "@walkeros/collector";
4055
4206
 
4056
4207
  // src/core/redact-line.ts
4057
- import { scrubSecrets, redactLine } from "@walkeros/core/node";
4208
+ import { scrubJson, scrubSecrets, redactLine } from "@walkeros/core/node";
4058
4209
 
4059
4210
  // src/core/to-printable.ts
4060
4211
  import { toPrintable } from "@walkeros/core/node";
@@ -4251,7 +4402,7 @@ function applyOverrides(config, overrides) {
4251
4402
  }
4252
4403
 
4253
4404
  // src/commands/push/flow-context.ts
4254
- import path15 from "path";
4405
+ import path16 from "path";
4255
4406
  import { pathToFileURL } from "url";
4256
4407
  import { JSDOM, VirtualConsole } from "jsdom";
4257
4408
 
@@ -4310,7 +4461,7 @@ function installTimerInterception(options = {}) {
4310
4461
  if (options.domWindow && options.domWindow !== globalThis) {
4311
4462
  patchTarget(options.domWindow);
4312
4463
  }
4313
- const drainMicrotasks = () => new Promise((resolve3) => realSetTimeout(resolve3, 0));
4464
+ const drainMicrotasks = () => new Promise((resolve5) => realSetTimeout(resolve5, 0));
4314
4465
  async function flush(wallTimeout = 5e3) {
4315
4466
  const deadline = Date.now() + wallTimeout;
4316
4467
  const maxIterations = 100;
@@ -4405,7 +4556,12 @@ var DOM_GLOBALS = [
4405
4556
  "CustomEvent",
4406
4557
  "Event",
4407
4558
  "localStorage",
4408
- "sessionStorage"
4559
+ "sessionStorage",
4560
+ "Node",
4561
+ "Element",
4562
+ "HTMLElement",
4563
+ "Document",
4564
+ "ShadowRoot"
4409
4565
  ];
4410
4566
  function exposeDomGlobals(domWindow) {
4411
4567
  const saved = DOM_GLOBALS.map(
@@ -4495,7 +4651,7 @@ async function withFlowContext(options, fn, onError) {
4495
4651
  vm.runInThisContext(snapshotCode);
4496
4652
  }
4497
4653
  }
4498
- const fileUrl = pathToFileURL(path15.resolve(esmPath)).href;
4654
+ const fileUrl = pathToFileURL(path16.resolve(esmPath)).href;
4499
4655
  const module = await import(`${fileUrl}?t=${Date.now()}`);
4500
4656
  const { wireConfig, startFlow: startFlow2 } = module;
4501
4657
  if (typeof wireConfig !== "function" || typeof startFlow2 !== "function") {
@@ -4618,14 +4774,44 @@ function buildSimulationResult(args) {
4618
4774
  }
4619
4775
 
4620
4776
  // src/commands/push/prepare.ts
4621
- import path16 from "path";
4622
- import fs16 from "fs-extra";
4777
+ import path17 from "path";
4778
+ import fs17 from "fs-extra";
4623
4779
  import { getPlatform as getPlatform2 } from "@walkeros/core";
4780
+
4781
+ // src/core/known-secrets.ts
4782
+ import { REF_SECRET } from "@walkeros/core";
4783
+ function collectKnownSecrets(flow, env = process.env) {
4784
+ const names = /* @__PURE__ */ new Set();
4785
+ collectSecretNames(flow, names);
4786
+ const values = /* @__PURE__ */ new Set();
4787
+ for (const name of names) {
4788
+ const value = env[name];
4789
+ if (value) values.add(value);
4790
+ }
4791
+ return [...values];
4792
+ }
4793
+ function collectSecretNames(value, names) {
4794
+ if (typeof value === "string") {
4795
+ const match = value.match(REF_SECRET);
4796
+ if (match) names.add(match[1]);
4797
+ return;
4798
+ }
4799
+ if (Array.isArray(value)) {
4800
+ for (const item of value) collectSecretNames(item, names);
4801
+ return;
4802
+ }
4803
+ if (value && typeof value === "object") {
4804
+ for (const item of Object.values(value)) collectSecretNames(item, names);
4805
+ }
4806
+ }
4807
+
4808
+ // src/commands/push/prepare.ts
4624
4809
  async function prepareFlow(input) {
4625
4810
  const logger = createCLILogger({
4626
4811
  silent: input.silent,
4627
4812
  verbose: input.verbose,
4628
- stderr: input.json
4813
+ stderr: input.json,
4814
+ knownSecrets: collectKnownSecrets(input.config)
4629
4815
  });
4630
4816
  logger.debug("Loading flow configuration");
4631
4817
  const { flowSettings, buildOptions } = loadBundleConfig(input.config, {
@@ -4649,7 +4835,7 @@ async function prepareFlow(input) {
4649
4835
  }
4650
4836
  logger.debug("Bundling flow configuration");
4651
4837
  const tempDir = await tmpRunDir("push");
4652
- const bundlePath = path16.join(tempDir, "flow.mjs");
4838
+ const bundlePath = path17.join(tempDir, "flow.mjs");
4653
4839
  const pushBuildOptions = {
4654
4840
  ...buildOptions,
4655
4841
  output: bundlePath,
@@ -4666,7 +4852,7 @@ async function prepareFlow(input) {
4666
4852
  overrides,
4667
4853
  flowSettings,
4668
4854
  cleanup: async () => {
4669
- await fs16.remove(tempDir).catch(() => {
4855
+ await fs17.remove(tempDir).catch(() => {
4670
4856
  });
4671
4857
  }
4672
4858
  };
@@ -4710,51 +4896,6 @@ function dispatchSimulate(flags) {
4710
4896
  return { kind: plan.kind, ids: plan.ids };
4711
4897
  }
4712
4898
 
4713
- // src/core/known-secrets.ts
4714
- import { REF_SECRET } from "@walkeros/core";
4715
- function collectKnownSecrets(flow, env = process.env) {
4716
- const names = /* @__PURE__ */ new Set();
4717
- collectSecretNames(flow, names);
4718
- const values = /* @__PURE__ */ new Set();
4719
- for (const name of names) {
4720
- const value = env[name];
4721
- if (value) values.add(value);
4722
- }
4723
- return [...values];
4724
- }
4725
- function collectSecretNames(value, names) {
4726
- if (typeof value === "string") {
4727
- const match = value.match(REF_SECRET);
4728
- if (match) names.add(match[1]);
4729
- return;
4730
- }
4731
- if (Array.isArray(value)) {
4732
- for (const item of value) collectSecretNames(item, names);
4733
- return;
4734
- }
4735
- if (value && typeof value === "object") {
4736
- for (const item of Object.values(value)) collectSecretNames(item, names);
4737
- }
4738
- }
4739
- var MIN_KNOWN_LEN = 6;
4740
- function maskKnownNumbers(value, known) {
4741
- const candidates = known.filter((secret) => secret.length >= MIN_KNOWN_LEN);
4742
- const mask = (item) => {
4743
- if (typeof item === "number") {
4744
- const printed = String(item);
4745
- return candidates.some((secret) => printed.includes(secret)) ? "***" : item;
4746
- }
4747
- if (Array.isArray(item)) return item.map(mask);
4748
- if (item && typeof item === "object") {
4749
- const copy = {};
4750
- for (const [key, entry] of Object.entries(item)) copy[key] = mask(entry);
4751
- return copy;
4752
- }
4753
- return item;
4754
- };
4755
- return mask(value);
4756
- }
4757
-
4758
4899
  // src/commands/push/run.ts
4759
4900
  import { isObject as isObject3 } from "@walkeros/core";
4760
4901
  function simulationToPushResult(result) {
@@ -4951,22 +5092,22 @@ async function runDestinationSimulationLoop(config, event, destinationIds, optio
4951
5092
  }
4952
5093
 
4953
5094
  // src/commands/push/dev-examples.ts
4954
- function isRecord(value) {
5095
+ function isRecord2(value) {
4955
5096
  return value !== null && typeof value === "object";
4956
5097
  }
4957
5098
  function selectDevExamples(devModule, exportName) {
4958
- if (!isRecord(devModule)) return void 0;
5099
+ if (!isRecord2(devModule)) return void 0;
4959
5100
  const map = devModule.exportExamples;
4960
- if (isRecord(map) && exportName !== void 0) {
5101
+ if (isRecord2(map) && exportName !== void 0) {
4961
5102
  const selected = map[exportName];
4962
- return isRecord(selected) ? selected : void 0;
5103
+ return isRecord2(selected) ? selected : void 0;
4963
5104
  }
4964
5105
  const examples = devModule.examples;
4965
- return isRecord(examples) ? examples : void 0;
5106
+ return isRecord2(examples) ? examples : void 0;
4966
5107
  }
4967
5108
  function legacyExportRefusal(devModule, packageExports, packageName, exportName) {
4968
5109
  if (exportName === void 0) return void 0;
4969
- if (isRecord(devModule) && isRecord(devModule.exportExamples))
5110
+ if (isRecord2(devModule) && isRecord2(devModule.exportExamples))
4970
5111
  return void 0;
4971
5112
  if (packageExports) {
4972
5113
  const declared = packageExports[packageName];
@@ -5014,11 +5155,11 @@ function resolveExportName(flow, kind, id) {
5014
5155
  }
5015
5156
 
5016
5157
  // src/commands/push/index.ts
5017
- function isRecord2(value) {
5158
+ function isRecord3(value) {
5018
5159
  return value !== null && typeof value === "object";
5019
5160
  }
5020
5161
  function isFlowJson(value) {
5021
- return isRecord2(value) && isRecord2(value.flows);
5162
+ return isRecord3(value) && isRecord3(value.flows);
5022
5163
  }
5023
5164
  function isString(value) {
5024
5165
  return typeof value === "string";
@@ -5036,9 +5177,9 @@ function getDevEnv(devModule, exportName) {
5036
5177
  const examples = selectDevExamples(devModule, exportName);
5037
5178
  if (!examples || !("env" in examples)) return void 0;
5038
5179
  const env = examples.env;
5039
- if (!isRecord2(env)) return void 0;
5180
+ if (!isRecord3(env)) return void 0;
5040
5181
  const result = {};
5041
- if ("push" in env && isRecord2(env.push)) result.push = env.push;
5182
+ if ("push" in env && isRecord3(env.push)) result.push = env.push;
5042
5183
  if ("simulation" in env && isStringArray(env.simulation))
5043
5184
  result.simulation = env.simulation;
5044
5185
  return result;
@@ -5046,14 +5187,14 @@ function getDevEnv(devModule, exportName) {
5046
5187
  async function applyStoreMockEnvs(flowConfig, flowSettings, devExports) {
5047
5188
  const unmocked = [];
5048
5189
  const stores = flowConfig.stores;
5049
- if (!isRecord2(stores)) return unmocked;
5190
+ if (!isRecord3(stores)) return unmocked;
5050
5191
  for (const [storeId, storeDef] of Object.entries(stores)) {
5051
5192
  const packageName = flowSettings.stores?.[storeId]?.package;
5052
5193
  const loadDev = packageName ? devExports?.[packageName] : void 0;
5053
5194
  const devModule = typeof loadDev === "function" ? await loadDev() : void 0;
5054
5195
  const { exportName } = resolveExportName(flowSettings, "store", storeId);
5055
5196
  const push2 = getDevEnv(devModule, exportName)?.push;
5056
- if (!push2 || !isRecord2(storeDef)) {
5197
+ if (!push2 || !isRecord3(storeDef)) {
5057
5198
  unmocked.push(storeId);
5058
5199
  continue;
5059
5200
  }
@@ -5065,10 +5206,12 @@ function isStringArray(value) {
5065
5206
  return Array.isArray(value) && value.every(isString);
5066
5207
  }
5067
5208
  async function pushCore(inputPath, event, options = {}) {
5209
+ let known = options.knownSecrets ?? [];
5068
5210
  const logger = createCLILogger({
5069
5211
  silent: options.silent,
5070
5212
  verbose: options.verbose,
5071
- stderr: options.json
5213
+ stderr: options.json,
5214
+ knownSecrets: () => known
5072
5215
  });
5073
5216
  const startTime = Date.now();
5074
5217
  let tempDir;
@@ -5089,6 +5232,7 @@ async function pushCore(inputPath, event, options = {}) {
5089
5232
  if (detected.type === "config") {
5090
5233
  const raw = options.raw !== void 0 ? options.raw : "content" in detected ? JSON.parse(detected.content) : void 0;
5091
5234
  const knownSecrets = options.knownSecrets ?? (isFlowJson(raw) ? collectKnownSecrets(raw) : []);
5235
+ known = knownSecrets;
5092
5236
  result = await executeConfigPush(
5093
5237
  {
5094
5238
  config: inputPath,
@@ -5129,7 +5273,7 @@ async function pushCore(inputPath, event, options = {}) {
5129
5273
  };
5130
5274
  } finally {
5131
5275
  if (tempDir) {
5132
- await fs17.remove(tempDir).catch(() => {
5276
+ await fs18.remove(tempDir).catch(() => {
5133
5277
  });
5134
5278
  }
5135
5279
  }
@@ -5145,14 +5289,9 @@ async function pushCommand(options) {
5145
5289
  }
5146
5290
  function renderPushOutput(result, options = {}) {
5147
5291
  const knownSecrets = options.knownSecrets ?? [];
5148
- return scrubSecrets(
5149
- options.json ? JSON.stringify(
5150
- maskKnownNumbers(toPrintable(result), knownSecrets),
5151
- null,
5152
- 2
5153
- ) : formatPushResult(result, { knownSecrets }),
5154
- { known: knownSecrets }
5155
- );
5292
+ return options.json ? scrubJson(result, { known: knownSecrets, space: 2 }) : scrubSecrets(formatPushResult(result, { knownSecrets }), {
5293
+ known: knownSecrets
5294
+ });
5156
5295
  }
5157
5296
  var MAX_CALL_ARGS_LENGTH = 300;
5158
5297
  function compactJson(value) {
@@ -5206,7 +5345,7 @@ function formatPushResult(result, options = {}) {
5206
5345
  const lines = [];
5207
5346
  lines.push(`success: ${result.success}`);
5208
5347
  if (result.success) {
5209
- const elbResult = isRecord2(result.elbResult) ? result.elbResult : void 0;
5348
+ const elbResult = isRecord3(result.elbResult) ? result.elbResult : void 0;
5210
5349
  if (elbResult) {
5211
5350
  if (typeof elbResult.id === "string")
5212
5351
  lines.push(` Event ID: ${elbResult.id}`);
@@ -5260,7 +5399,7 @@ async function executeConfigPush(options, validatedEvent, logger, setTempDir, lo
5260
5399
  logger.debug("Bundling flow configuration");
5261
5400
  const tempDir = await tmpRunDir("push");
5262
5401
  setTempDir(tempDir);
5263
- const tempPath = path17.join(tempDir, "flow.mjs");
5402
+ const tempPath = path18.join(tempDir, "flow.mjs");
5264
5403
  const pushBuildOptions = {
5265
5404
  ...buildOptions,
5266
5405
  output: tempPath,
@@ -5289,8 +5428,8 @@ async function executeConfigPush(options, validatedEvent, logger, setTempDir, lo
5289
5428
  async function executeBundlePush(bundleContent, platform, validatedEvent, logger, setTempDir, overrides = {}, snapshotCode, flowLogs = {}) {
5290
5429
  const tempDir = await tmpRunDir("push");
5291
5430
  setTempDir(tempDir);
5292
- const tempPath = path17.join(tempDir, "flow.mjs");
5293
- await fs17.writeFile(tempPath, bundleContent, "utf8");
5431
+ const tempPath = path18.join(tempDir, "flow.mjs");
5432
+ await fs18.writeFile(tempPath, bundleContent, "utf8");
5294
5433
  logger.debug(`Bundle written to: ${tempPath}`);
5295
5434
  logger.debug(
5296
5435
  `Executing in ${platform} environment (${platform === "web" ? "JSDOM" : "Node.js"})`
@@ -5399,9 +5538,9 @@ function resolvePageUrl(input, pageUrl) {
5399
5538
  return parsed.href;
5400
5539
  }
5401
5540
  function triggerUrl(input) {
5402
- if (!isRecord2(input) || !isRecord2(input.trigger)) return void 0;
5541
+ if (!isRecord3(input) || !isRecord3(input.trigger)) return void 0;
5403
5542
  const { options } = input.trigger;
5404
- return isRecord2(options) && isString(options.url) ? options.url : void 0;
5543
+ return isRecord3(options) && isString(options.url) ? options.url : void 0;
5405
5544
  }
5406
5545
  function isSourceInit(value) {
5407
5546
  return typeof value === "function";
@@ -5527,12 +5666,12 @@ async function simulateSource(configOrPath, input, options) {
5527
5666
  `Stores without a mock env run as configured: ${unmockedStores.join(", ")}`
5528
5667
  );
5529
5668
  const wiredSources = flowConfig.sources;
5530
- const wiredSource = isRecord2(wiredSources) ? wiredSources[options.sourceId] : void 0;
5531
- const source = isRecord2(
5669
+ const wiredSource = isRecord3(wiredSources) ? wiredSources[options.sourceId] : void 0;
5670
+ const source = isRecord3(
5532
5671
  wiredSource
5533
5672
  ) ? {
5534
5673
  ...wiredSource,
5535
- ...isRecord2(wiredSource.config) ? { config: { ...wiredSource.config } } : {}
5674
+ ...isRecord3(wiredSource.config) ? { config: { ...wiredSource.config } } : {}
5536
5675
  } : void 0;
5537
5676
  flowConfig.sources = source ? { [options.sourceId]: source } : {};
5538
5677
  const commandCalls = [];
@@ -5546,7 +5685,7 @@ async function simulateSource(configOrPath, input, options) {
5546
5685
  simulation: devEnv.simulation
5547
5686
  });
5548
5687
  source.env = {
5549
- ...isRecord2(source.env) ? source.env : {},
5688
+ ...isRecord3(source.env) ? source.env : {},
5550
5689
  ...wrappedEnv
5551
5690
  };
5552
5691
  trackedCalls = calls;
@@ -5897,10 +6036,10 @@ async function simulateCollector(configOrPath, event, options) {
5897
6036
  }
5898
6037
  }
5899
6038
  function isConsentRecord(value) {
5900
- return isRecord2(value) && Object.values(value).every((granted) => typeof granted === "boolean");
6039
+ return isRecord3(value) && Object.values(value).every((granted) => typeof granted === "boolean");
5901
6040
  }
5902
6041
  function pendingRequire(definition) {
5903
- if (!isRecord2(definition) || !isRecord2(definition.config)) return [];
6042
+ if (!isRecord3(definition) || !isRecord3(definition.config)) return [];
5904
6043
  const { require: require2 } = definition.config;
5905
6044
  return isStringArray(require2) ? require2 : [];
5906
6045
  }
@@ -5970,16 +6109,16 @@ async function simulateDestination(configOrPath, event, options) {
5970
6109
  options.data ?? module.__configData ?? void 0
5971
6110
  );
5972
6111
  const wiredDestinations = flowConfig.destinations;
5973
- const wiredTarget = isRecord2(wiredDestinations) ? wiredDestinations[options.destinationId] : void 0;
5974
- if (!isRecord2(wiredTarget)) {
5975
- const available = isRecord2(wiredDestinations) ? Object.keys(wiredDestinations).join(", ") : "";
6112
+ const wiredTarget = isRecord3(wiredDestinations) ? wiredDestinations[options.destinationId] : void 0;
6113
+ if (!isRecord3(wiredTarget)) {
6114
+ const available = isRecord3(wiredDestinations) ? Object.keys(wiredDestinations).join(", ") : "";
5976
6115
  throw new Error(
5977
6116
  `Destination "${options.destinationId}" not found in collector. Available: ${available || "none"}`
5978
6117
  );
5979
6118
  }
5980
6119
  const target = {
5981
6120
  ...wiredTarget,
5982
- config: isRecord2(wiredTarget.config) ? { ...wiredTarget.config } : {}
6121
+ config: isRecord3(wiredTarget.config) ? { ...wiredTarget.config } : {}
5983
6122
  };
5984
6123
  flowConfig.destinations = { [options.destinationId]: target };
5985
6124
  if (flowConfig.sources) flowConfig.sources = {};
@@ -6019,7 +6158,7 @@ async function simulateDestination(configOrPath, event, options) {
6019
6158
  );
6020
6159
  }
6021
6160
  const destConfig = target.config;
6022
- if (isRecord2(destConfig)) {
6161
+ if (isRecord3(destConfig)) {
6023
6162
  destConfig.env = devEnv.push;
6024
6163
  }
6025
6164
  if (devEnv.simulation?.length) {
@@ -6028,7 +6167,7 @@ async function simulateDestination(configOrPath, event, options) {
6028
6167
  simulation: devEnv.simulation
6029
6168
  };
6030
6169
  const { wrappedEnv, calls } = wrapEnv(combined);
6031
- if (isRecord2(destConfig)) destConfig.env = wrappedEnv;
6170
+ if (isRecord3(destConfig)) destConfig.env = wrappedEnv;
6032
6171
  trackedCalls = calls;
6033
6172
  }
6034
6173
  }
@@ -6115,7 +6254,10 @@ async function simulateDestination(configOrPath, event, options) {
6115
6254
  }
6116
6255
 
6117
6256
  // src/commands/validate/index.ts
6257
+ import * as fs20 from "fs";
6258
+ import * as path20 from "path";
6118
6259
  import chalk2 from "chalk";
6260
+ import { isObject as isObject9 } from "@walkeros/core";
6119
6261
 
6120
6262
  // src/commands/validate/validators/contract.ts
6121
6263
  import { isObject as isObject4 } from "@walkeros/core";
@@ -6242,21 +6384,28 @@ function validateContract(input) {
6242
6384
  }
6243
6385
  }
6244
6386
  }
6387
+ const reportedCycles = /* @__PURE__ */ new Set();
6245
6388
  for (const name of contractNames) {
6246
- const visited = /* @__PURE__ */ new Set();
6389
+ const chain = [];
6247
6390
  let current = name;
6248
6391
  while (current) {
6249
- if (visited.has(current)) {
6250
- errors.push({
6251
- path: `${name}.extend`,
6252
- message: `Circular extend chain: ${[...visited, current].join(" \u2192 ")}`,
6253
- code: "CIRCULAR_EXTENDS"
6254
- });
6392
+ const start = chain.indexOf(current);
6393
+ if (start !== -1) {
6394
+ const cycle = chain.slice(start);
6395
+ const key = [...cycle].sort().join("\0");
6396
+ if (!reportedCycles.has(key)) {
6397
+ reportedCycles.add(key);
6398
+ errors.push({
6399
+ path: `${cycle[0]}.extend`,
6400
+ message: `Circular extend chain: ${[...cycle, current].join(" \u2192 ")}`,
6401
+ code: "CIRCULAR_EXTENDS"
6402
+ });
6403
+ }
6255
6404
  break;
6256
6405
  }
6257
- visited.add(current);
6406
+ chain.push(current);
6258
6407
  const entry = contracts[current];
6259
- current = entry?.extend || "";
6408
+ current = isObject4(entry) && typeof entry.extend === "string" ? entry.extend : "";
6260
6409
  }
6261
6410
  }
6262
6411
  return {
@@ -6323,21 +6472,25 @@ function validateEvent2(input) {
6323
6472
 
6324
6473
  // src/commands/validate/validators/flow.ts
6325
6474
  import {
6475
+ checkWindowCollector as checkWindowCollector2,
6476
+ ENV_MARKER_PREFIX as ENV_MARKER_PREFIX3,
6326
6477
  getByPath,
6327
- getFlowSettings as getFlowSettings2,
6328
6478
  getRouteGraph,
6329
- isObject as isObject6,
6479
+ isObject as isObject7,
6330
6480
  REF_CONTRACT,
6331
6481
  resolveContracts,
6332
6482
  validateStepEntry
6333
6483
  } from "@walkeros/core";
6334
- import { schemas as schemas4 } from "@walkeros/core/dev";
6484
+ import { schemas as schemas4, validateFlowStructure } from "@walkeros/core/dev";
6335
6485
  import {
6336
6486
  validateEventAgainstContract
6337
6487
  } from "@walkeros/transformer-validate";
6338
6488
 
6339
6489
  // src/commands/validate/scope.ts
6340
6490
  import { isObject as isObject5 } from "@walkeros/core";
6491
+ function isFlowJson2(value) {
6492
+ return isObject5(value) && "version" in value && isObject5(value.flows);
6493
+ }
6341
6494
  var ENTRY_SECTIONS = [
6342
6495
  "sources",
6343
6496
  "destinations",
@@ -6370,11 +6523,11 @@ function resolveScope(config, options = {}) {
6370
6523
  }
6371
6524
  flows = [options.flow];
6372
6525
  }
6373
- const path20 = options.path;
6374
- if (path20 === void 0) {
6526
+ const path23 = options.path;
6527
+ if (path23 === void 0) {
6375
6528
  return { allFlows, flows, entries: [], searchedFlows: [], errors: [] };
6376
6529
  }
6377
- const parts = path20.split(".");
6530
+ const parts = path23.split(".");
6378
6531
  let sections = ENTRY_SECTIONS;
6379
6532
  let section;
6380
6533
  let key;
@@ -6387,7 +6540,7 @@ function resolveScope(config, options = {}) {
6387
6540
  searchedFlows: [],
6388
6541
  errors: [
6389
6542
  {
6390
- path: path20,
6543
+ path: path23,
6391
6544
  message: `Section "${parts[0]}" cannot be addressed by --path. Use one of: ${ENTRY_SECTIONS.join(", ")}`,
6392
6545
  code: "UNSUPPORTED_SECTION"
6393
6546
  }
@@ -6407,8 +6560,8 @@ function resolveScope(config, options = {}) {
6407
6560
  searchedFlows: [],
6408
6561
  errors: [
6409
6562
  {
6410
- path: path20,
6411
- message: `Invalid path "${path20}". Use "section.key" or just "key"`,
6563
+ path: path23,
6564
+ message: `Invalid path "${path23}". Use "section.key" or just "key"`,
6412
6565
  code: "INVALID_PATH"
6413
6566
  }
6414
6567
  ]
@@ -6436,7 +6589,7 @@ function resolveScope(config, options = {}) {
6436
6589
  target: { key },
6437
6590
  errors: [
6438
6591
  {
6439
- path: path20,
6592
+ path: path23,
6440
6593
  message: `Ambiguous key "${key}" found in multiple sections: ${matched.join(", ")}. Use dot-notation (e.g., ${matched[0]}.${key})`,
6441
6594
  code: "AMBIGUOUS_ENTRY"
6442
6595
  }
@@ -6453,7 +6606,7 @@ function resolveScope(config, options = {}) {
6453
6606
  target,
6454
6607
  errors: [
6455
6608
  {
6456
- path: path20,
6609
+ path: path23,
6457
6610
  message: `Entry "${key}" not found in ${section ?? "any section"} of flow(s): ${flows.join(", ") || "none"}`,
6458
6611
  code: "ENTRY_NOT_FOUND"
6459
6612
  }
@@ -6469,9 +6622,9 @@ function resolveScope(config, options = {}) {
6469
6622
  errors: []
6470
6623
  };
6471
6624
  }
6472
- function flowOfPath(path20, flowNames) {
6625
+ function flowOfPath(path23, flowNames) {
6473
6626
  return flowNames.find(
6474
- (name) => path20 === `flows.${name}` || path20.startsWith(`flows.${name}.`)
6627
+ (name) => path23 === `flows.${name}` || path23.startsWith(`flows.${name}.`)
6475
6628
  );
6476
6629
  }
6477
6630
  function describeScope(resolved, checks) {
@@ -6488,93 +6641,554 @@ function describeScope(resolved, checks) {
6488
6641
  return scope;
6489
6642
  }
6490
6643
 
6491
- // src/commands/validate/validators/flow.ts
6492
- var { validateFlowConfig: validateFlowConfig2 } = schemas4;
6493
- function isFlowJson2(value) {
6494
- if (!isObject6(value)) return false;
6495
- if (!("version" in value) || !("flows" in value)) return false;
6496
- return isObject6(value.flows);
6497
- }
6498
- function validateFlow(input, options = {}) {
6499
- const errors = [];
6500
- const warnings = [];
6501
- const skipped = [];
6502
- const checks = ["file:schema", "flow:schema"];
6503
- const details = {};
6504
- let json;
6644
+ // src/commands/validate/validators/resolve.ts
6645
+ import {
6646
+ FlowCycleError,
6647
+ FlowReferenceError,
6648
+ getFlowSettings as getFlowSettings2,
6649
+ getErrorMessage as getErrorMessage2
6650
+ } from "@walkeros/core";
6651
+ function resolveFlow2(file, name) {
6505
6652
  try {
6506
- json = JSON.stringify(input, null, 2);
6507
- } catch {
6508
- errors.push({
6509
- path: "root",
6510
- message: "Input cannot be serialized to JSON",
6511
- code: "SERIALIZATION_ERROR"
6512
- });
6513
- return { valid: false, type: "flow", errors, warnings, details };
6514
- }
6515
- const scope = resolveScope(input, { flow: options.flow });
6516
- errors.push(...scope.errors);
6517
- const config = isObject6(input) ? input : {};
6518
- const flowsValue = config.flows;
6519
- const flows = isObject6(flowsValue) ? flowsValue : void 0;
6520
- const allFlows = scope.allFlows;
6521
- const inScope = (path20) => {
6522
- const flow = flowOfPath(path20, allFlows);
6523
- return flow === void 0 || scope.flows.includes(flow);
6524
- };
6525
- const coreResult = validateFlowConfig2(json);
6526
- for (const issue of coreResult.errors) {
6527
- const path20 = issue.path || "root";
6528
- if (!inScope(path20)) continue;
6529
- errors.push({ path: path20, message: issue.message, code: "SCHEMA_VALIDATION" });
6653
+ const flow = getFlowSettings2(file, name, { deferred: true });
6654
+ return { ok: true, flow };
6655
+ } catch (error) {
6656
+ return {
6657
+ ok: false,
6658
+ error: {
6659
+ path: `flows.${name}`,
6660
+ message: getErrorMessage2(error),
6661
+ code: resolutionCode(error)
6662
+ }
6663
+ };
6530
6664
  }
6531
- for (const issue of coreResult.warnings) {
6532
- const path20 = issue.path || "root";
6533
- if (!inScope(path20)) continue;
6534
- warnings.push({ path: path20, message: issue.message, code: "CONFIG_WARNING" });
6665
+ }
6666
+ function resolutionCode(error) {
6667
+ if (error instanceof FlowReferenceError) return error.code;
6668
+ if (error instanceof FlowCycleError) return error.code;
6669
+ return "UNRESOLVED_REFERENCE";
6670
+ }
6671
+
6672
+ // src/commands/validate/validators/settings.ts
6673
+ import * as fs19 from "fs";
6674
+ import * as path19 from "path";
6675
+ import Ajv from "ajv";
6676
+ import {
6677
+ ENV_MARKER_PREFIX as ENV_MARKER_PREFIX2,
6678
+ fetchPackage,
6679
+ getErrorMessage as getErrorMessage3,
6680
+ isObject as isObject6,
6681
+ REF_CODE_PREFIX,
6682
+ REF_STORE,
6683
+ SECRET_MARKER_PREFIX as SECRET_MARKER_PREFIX3
6684
+ } from "@walkeros/core";
6685
+ var CLIENT_HEADER = "walkeros-cli/4.7.0";
6686
+ function createSettingsContext(configDir) {
6687
+ return { configDir, cache: /* @__PURE__ */ new Map(), compiled: /* @__PURE__ */ new Map() };
6688
+ }
6689
+ function selectSettingsSchema(pkg, packageLabel, exportName) {
6690
+ if (exportName === void 0) return { ok: true, settings: pkg.settings };
6691
+ if (pkg.exportSchemas) {
6692
+ const entry = Object.prototype.hasOwnProperty.call(
6693
+ pkg.exportSchemas,
6694
+ exportName
6695
+ ) ? pkg.exportSchemas[exportName] : void 0;
6696
+ if (!isObject6(entry))
6697
+ return {
6698
+ ok: false,
6699
+ reason: `${packageLabel} has no export "${exportName}"`
6700
+ };
6701
+ return { ok: true, settings: entry.settings };
6535
6702
  }
6536
- if (flows && allFlows.length === 0) {
6537
- errors.push({
6538
- path: "flows",
6539
- message: "At least one flow is required",
6540
- code: "EMPTY_FLOWS"
6541
- });
6703
+ if (pkg.exports && pkg.exports.length < 2)
6704
+ return { ok: true, settings: pkg.settings };
6705
+ return {
6706
+ ok: false,
6707
+ reason: `${packageLabel} predates per-export settings schemas; the step imports "${exportName}"`
6708
+ };
6709
+ }
6710
+ var SECTION_KIND = {
6711
+ sources: "source",
6712
+ transformers: "transformer",
6713
+ destinations: "destination",
6714
+ stores: "store"
6715
+ };
6716
+ function pointerSegments(pointer) {
6717
+ if (pointer === "") return [];
6718
+ return pointer.split("/").slice(1).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
6719
+ }
6720
+ function valueAt(root, segments) {
6721
+ let current = root;
6722
+ for (const segment of segments) {
6723
+ if (Array.isArray(current)) current = current[Number(segment)];
6724
+ else if (isObject6(current)) current = current[segment];
6725
+ else return void 0;
6542
6726
  }
6543
- checks.push("flow:steps");
6544
- for (const flowName of scope.flows) {
6545
- const flowValue = flows?.[flowName];
6546
- if (!isObject6(flowValue)) continue;
6547
- const transformersValue = flowValue.transformers;
6548
- if (!isObject6(transformersValue)) continue;
6549
- for (const [name, transformerValue] of Object.entries(transformersValue)) {
6550
- if (!isObject6(transformerValue)) continue;
6551
- const result = validateStepEntry(transformerValue, "Transformer");
6552
- if (!result.ok) {
6553
- errors.push({
6554
- path: `flows.${flowName}.transformers.${name}`,
6555
- message: result.reason || "Invalid transformer entry.",
6556
- code: result.code
6557
- });
6558
- }
6727
+ return current;
6728
+ }
6729
+ var MARKER = ENV_MARKER_PREFIX2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6730
+ var ENV_MARKER = new RegExp(
6731
+ `${MARKER}([a-zA-Z_][a-zA-Z0-9_]*)(?::((?:(?!${MARKER})[^\\s"'])*))?`,
6732
+ "g"
6733
+ );
6734
+ function prepareSettings(settings) {
6735
+ const deferred = [];
6736
+ const walk = (value, segments) => {
6737
+ if (Array.isArray(value))
6738
+ return value.map((item, index) => walk(item, [...segments, `${index}`]));
6739
+ if (isObject6(value))
6740
+ return Object.fromEntries(
6741
+ Object.entries(value).map(([key, item]) => [
6742
+ key,
6743
+ walk(item, [...segments, key])
6744
+ ])
6745
+ );
6746
+ if (typeof value !== "string") return value;
6747
+ if (value.startsWith(SECRET_MARKER_PREFIX3)) {
6748
+ deferred.push({
6749
+ segments,
6750
+ reference: `$secret.${value.slice(SECRET_MARKER_PREFIX3.length)}`
6751
+ });
6752
+ return value;
6753
+ }
6754
+ if (value.includes(ENV_MARKER_PREFIX2)) {
6755
+ let runtimeOnly = false;
6756
+ const withDefaults = value.replace(
6757
+ ENV_MARKER,
6758
+ (_match, _name, fallback) => {
6759
+ if (fallback === void 0) runtimeOnly = true;
6760
+ return fallback ?? "";
6761
+ }
6762
+ );
6763
+ if (!runtimeOnly) return withDefaults;
6764
+ deferred.push({
6765
+ segments,
6766
+ reference: value.replace(
6767
+ ENV_MARKER,
6768
+ (_match, name, fallback) => fallback === void 0 ? `$env.${name}` : `$env.${name}:${fallback}`
6769
+ )
6770
+ });
6771
+ return value;
6772
+ }
6773
+ if (REF_STORE.test(value) || value.startsWith(REF_CODE_PREFIX)) {
6774
+ deferred.push({ segments, reference: value });
6775
+ }
6776
+ return value;
6777
+ };
6778
+ return { value: walk(settings, []), deferred };
6779
+ }
6780
+ function isUnder(segments, prefix) {
6781
+ return prefix.every((segment, index) => segments[index] === segment);
6782
+ }
6783
+ function bundlePin(rawFlow, name) {
6784
+ const config = isObject6(rawFlow) ? rawFlow.config : void 0;
6785
+ const bundle2 = isObject6(config) ? config.bundle : void 0;
6786
+ const packages = isObject6(bundle2) ? bundle2.packages : void 0;
6787
+ const pin = isObject6(packages) ? packages[name] : void 0;
6788
+ if (!isObject6(pin)) return {};
6789
+ return {
6790
+ ...typeof pin.version === "string" ? { version: pin.version } : {},
6791
+ ...typeof pin.path === "string" ? { path: pin.path } : {}
6792
+ };
6793
+ }
6794
+ function packageSource(spec, rawFlow) {
6795
+ if (spec.startsWith(".") || spec.startsWith("/"))
6796
+ return { name: spec, localPath: spec };
6797
+ const { name, version } = parsePackageSpec(spec);
6798
+ const pin = bundlePin(rawFlow, name);
6799
+ if (pin.path) return { name, localPath: pin.path };
6800
+ const pinned = pin.version ?? version;
6801
+ return pinned ? { name, version: pinned } : { name };
6802
+ }
6803
+ function readJson2(file) {
6804
+ return JSON.parse(fs19.readFileSync(file, "utf-8"));
6805
+ }
6806
+ function readLocalSchema(source, configDir) {
6807
+ const localPath = source.localPath ?? "";
6808
+ const root = path19.isAbsolute(localPath) ? localPath : path19.resolve(configDir, localPath);
6809
+ const file = [
6810
+ path19.join(root, "dist", "walkerOS.json"),
6811
+ path19.join(root, "walkerOS.json")
6812
+ ].find((candidate) => fs19.existsSync(candidate));
6813
+ if (!file) throw new Error(`no walkerOS.json under ${root}`);
6814
+ const meta = readJson2(file);
6815
+ const pkgFile = path19.join(root, "package.json");
6816
+ const pkg = fs19.existsSync(pkgFile) ? readJson2(pkgFile) : void 0;
6817
+ const schemas6 = isObject6(meta) ? meta.schemas : void 0;
6818
+ const exportSchemas = isObject6(meta) ? meta.exportSchemas : void 0;
6819
+ const declared = isObject6(meta) && isObject6(meta.$meta) ? meta.$meta.exports : void 0;
6820
+ const version = isObject6(pkg) && typeof pkg.version === "string" ? pkg.version : void 0;
6821
+ return {
6822
+ ...version ? { version } : {},
6823
+ settings: isObject6(schemas6) ? schemas6.settings : void 0,
6824
+ ...isObject6(exportSchemas) ? { exportSchemas } : {},
6825
+ ...isObject6(declared) ? { exports: Object.keys(declared) } : {}
6826
+ };
6827
+ }
6828
+ async function loadSchema(source, context2) {
6829
+ if (source.localPath !== void 0) {
6830
+ const configDir = context2.configDir;
6831
+ if (configDir === void 0)
6832
+ throw new Error(
6833
+ "it is a local path package and the input is not a file, so there is no directory to read it from"
6834
+ );
6835
+ return readLocalSchema(source, configDir);
6836
+ }
6837
+ const info = await fetchPackage(source.name, {
6838
+ ...source.version ? { version: source.version } : {},
6839
+ client: CLIENT_HEADER
6840
+ });
6841
+ return {
6842
+ version: info.version,
6843
+ settings: info.schemas.settings,
6844
+ ...info.exportSchemas ? { exportSchemas: info.exportSchemas } : {},
6845
+ ...info.exports ? { exports: Object.keys(info.exports) } : {}
6846
+ };
6847
+ }
6848
+ function compileSettings(settings) {
6849
+ if (!isObject6(settings)) return void 0;
6850
+ const ajv = new Ajv({ allErrors: true, validateFormats: false });
6851
+ try {
6852
+ return { ok: true, validate: ajv.compile(settings) };
6853
+ } catch (error) {
6854
+ return { ok: false, message: getErrorMessage3(error) };
6855
+ }
6856
+ }
6857
+ function sourceKey(source) {
6858
+ return source.localPath ? `path:${source.localPath}` : `${source.name}@${source.version ?? "latest"}`;
6859
+ }
6860
+ function cachedSchema(source, context2) {
6861
+ const key = sourceKey(source);
6862
+ let pending = context2.cache.get(key);
6863
+ if (!pending) {
6864
+ pending = loadSchema(source, context2);
6865
+ context2.cache.set(key, pending);
6866
+ }
6867
+ return pending;
6868
+ }
6869
+ function cachedCompile(source, exportName, settings, context2) {
6870
+ const key = `${sourceKey(source)}#${exportName ?? ""}`;
6871
+ if (context2.compiled.has(key)) return context2.compiled.get(key);
6872
+ const compiled = compileSettings(settings);
6873
+ context2.compiled.set(key, compiled);
6874
+ return compiled;
6875
+ }
6876
+ async function checkStepSettings(target, resolvedFlow, rawFlow, check, context2) {
6877
+ const at = `flows.${target.flow}.${target.section}.${target.key}`;
6878
+ const outcome = { findings: [], skipped: [], deferred: [] };
6879
+ const spec = typeof target.entry.package === "string" ? target.entry.package : "";
6880
+ if (!spec) {
6881
+ outcome.skipped.push({
6882
+ path: at,
6883
+ check,
6884
+ reason: "No package field, so there is no settings schema to fetch",
6885
+ code: "NO_PACKAGE"
6886
+ });
6887
+ return outcome;
6888
+ }
6889
+ const source = packageSource(spec, rawFlow);
6890
+ let schema;
6891
+ try {
6892
+ schema = await cachedSchema(source, context2);
6893
+ } catch (error) {
6894
+ outcome.skipped.push({
6895
+ path: at,
6896
+ check,
6897
+ reason: `Schema for ${source.name}${source.version ? `@${source.version}` : ""} could not be loaded: ${getErrorMessage3(error)}`,
6898
+ code: "SCHEMA_UNAVAILABLE"
6899
+ });
6900
+ return outcome;
6901
+ }
6902
+ outcome.checked = {
6903
+ path: at,
6904
+ package: source.name,
6905
+ ...schema.version ? { version: schema.version } : {}
6906
+ };
6907
+ const { exportName } = resolveExportName(
6908
+ resolvedFlow,
6909
+ SECTION_KIND[target.section],
6910
+ target.key
6911
+ );
6912
+ const version = schema.version ?? source.version;
6913
+ const selection = selectSettingsSchema(
6914
+ schema,
6915
+ version ? `${source.name}@${version}` : source.name,
6916
+ exportName
6917
+ );
6918
+ if (!selection.ok) {
6919
+ outcome.skipped.push({
6920
+ path: at,
6921
+ check,
6922
+ reason: selection.reason,
6923
+ code: "NO_SETTINGS_SCHEMA"
6924
+ });
6925
+ return outcome;
6926
+ }
6927
+ const compiled = cachedCompile(
6928
+ source,
6929
+ exportName,
6930
+ selection.settings,
6931
+ context2
6932
+ );
6933
+ if (!compiled) {
6934
+ outcome.skipped.push({
6935
+ path: at,
6936
+ check,
6937
+ reason: `Package ${source.name} has no settings schema`,
6938
+ code: "NO_SETTINGS_SCHEMA"
6939
+ });
6940
+ return outcome;
6941
+ }
6942
+ if (!compiled.ok) {
6943
+ outcome.skipped.push({
6944
+ path: at,
6945
+ check,
6946
+ reason: `The settings schema of ${source.name} cannot be compiled: ${compiled.message}`,
6947
+ code: "SCHEMA_UNAVAILABLE"
6948
+ });
6949
+ return outcome;
6950
+ }
6951
+ const validate2 = compiled.validate;
6952
+ const step = stepOf(resolvedFlow, target.section, target.key);
6953
+ const config = isObject6(step?.config) ? step.config : {};
6954
+ const prepared = prepareSettings(config.settings ?? {});
6955
+ const base = `${at}.config.settings`;
6956
+ for (const { segments, reference } of prepared.deferred) {
6957
+ outcome.deferred.push({
6958
+ path: [base, ...segments].join("."),
6959
+ reference
6960
+ });
6961
+ }
6962
+ if (validate2(prepared.value)) return outcome;
6963
+ for (const e of validate2.errors || []) {
6964
+ const segments = pointerSegments(e.instancePath);
6965
+ if (prepared.deferred.some((d) => isUnder(segments, d.segments))) continue;
6966
+ const finding = {
6967
+ path: [base, ...segments].join("."),
6968
+ message: describeAjvError(e),
6969
+ code: "ENTRY_SCHEMA",
6970
+ keyword: e.keyword
6971
+ };
6972
+ if (segments.length > 0) finding.value = valueAt(prepared.value, segments);
6973
+ outcome.findings.push(finding);
6974
+ }
6975
+ return outcome;
6976
+ }
6977
+ function describeAjvError(error) {
6978
+ const message = error.message || "Unknown error";
6979
+ const extra = error.params.additionalProperty;
6980
+ return error.keyword === "additionalProperties" && typeof extra === "string" ? `${message}: "${extra}"` : message;
6981
+ }
6982
+ function stepOf(flow, section, key) {
6983
+ switch (section) {
6984
+ case "sources":
6985
+ return flow.sources?.[key];
6986
+ case "destinations":
6987
+ return flow.destinations?.[key];
6988
+ case "transformers":
6989
+ return flow.transformers?.[key];
6990
+ case "stores":
6991
+ return flow.stores?.[key];
6992
+ }
6993
+ }
6994
+
6995
+ // src/commands/validate/validators/flow.ts
6996
+ var { validateFlowConfig: validateFlowConfig2 } = schemas4;
6997
+ var DEPRECATED_PACKAGES = {
6998
+ "@walkeros/store-memory": "has been removed. Use the built-in cache by omitting cache.store, or remove the store declaration if it was only used as a cache target."
6999
+ };
7000
+ var OPEN_STEP_KINDS = [
7001
+ ["destinations", "Destination"],
7002
+ ["stores", "Store"]
7003
+ ];
7004
+ async function validateFlowWithPackages(input, options = {}) {
7005
+ const run = runFlowChecks(input, options);
7006
+ const { result, scope } = run;
7007
+ if (options.offline) {
7008
+ const described = describeScope(scope, run.checks);
7009
+ described.offline = true;
7010
+ result.details.scope = described;
7011
+ return result;
7012
+ }
7013
+ run.checks.push("flow:package-settings");
7014
+ const context2 = createSettingsContext(options.configDir);
7015
+ const deferred = [];
7016
+ const packages = [];
7017
+ const pending = [];
7018
+ for (const name of scope.flows) {
7019
+ const flow = run.resolved.get(name);
7020
+ const rawFlow = run.typed?.flows[name];
7021
+ const reason = run.gate(name);
7022
+ if (!flow || !rawFlow || reason) {
7023
+ run.skipped.push({
7024
+ path: `flows.${name}`,
7025
+ check: "flow:package-settings",
7026
+ reason: reason ?? `flows.${name} did not resolve`,
7027
+ code: "GATED_BY_ERRORS"
7028
+ });
7029
+ continue;
7030
+ }
7031
+ for (const section of ENTRY_SECTIONS) {
7032
+ const steps = isObject7(rawFlow[section]) ? rawFlow[section] : {};
7033
+ for (const [key, entry] of Object.entries(steps)) {
7034
+ if (!isObject7(entry) || entry.package === void 0) continue;
7035
+ const target = { flow: name, section, key, entry };
7036
+ pending.push(
7037
+ checkStepSettings(
7038
+ target,
7039
+ flow,
7040
+ rawFlow,
7041
+ "flow:package-settings",
7042
+ context2
7043
+ )
7044
+ );
7045
+ }
7046
+ }
7047
+ }
7048
+ for (const outcome of await Promise.all(pending)) {
7049
+ result.warnings.push(...outcome.findings);
7050
+ run.skipped.push(...outcome.skipped);
7051
+ deferred.push(...outcome.deferred);
7052
+ if (outcome.checked) packages.push(outcome.checked);
7053
+ }
7054
+ result.details.scope = describeScope(scope, run.checks);
7055
+ result.details.deferred = [...result.details.deferred ?? [], ...deferred];
7056
+ if (packages.length > 0) result.details.packages = packages;
7057
+ return result;
7058
+ }
7059
+ function runFlowChecks(input, options) {
7060
+ const errors = [];
7061
+ const warnings = [];
7062
+ const skipped = [];
7063
+ const checks = ["file:schema", "flow:schema"];
7064
+ const details = {};
7065
+ const resolved = /* @__PURE__ */ new Map();
7066
+ const result = {
7067
+ valid: false,
7068
+ type: "flow",
7069
+ errors,
7070
+ warnings,
7071
+ details
7072
+ };
7073
+ let json;
7074
+ const scope = resolveScope(input, { flow: options.flow });
7075
+ try {
7076
+ json = JSON.stringify(input, null, 2);
7077
+ } catch {
7078
+ errors.push({
7079
+ path: "root",
7080
+ message: "Input cannot be serialized to JSON",
7081
+ code: "SERIALIZATION_ERROR"
7082
+ });
7083
+ return {
7084
+ result,
7085
+ scope,
7086
+ resolved,
7087
+ checks,
7088
+ skipped,
7089
+ gate: () => "the input cannot be serialized"
7090
+ };
7091
+ }
7092
+ errors.push(...scope.errors);
7093
+ const config = isObject7(input) ? input : {};
7094
+ const flowsValue = config.flows;
7095
+ const flows = isObject7(flowsValue) ? flowsValue : void 0;
7096
+ const allFlows = scope.allFlows;
7097
+ const inScope = (path23) => {
7098
+ const flow = flowOfPath(path23, allFlows);
7099
+ return flow === void 0 || scope.flows.includes(flow);
7100
+ };
7101
+ let contractErrors = 0;
7102
+ if ("contract" in config) {
7103
+ checks.push("file:contract");
7104
+ const contract = validateContract(config.contract);
7105
+ contractErrors = contract.errors.length;
7106
+ for (const error of contract.errors)
7107
+ errors.push({ ...error, path: `contract.${error.path}` });
7108
+ for (const warning of contract.warnings)
7109
+ warnings.push({ ...warning, path: `contract.${warning.path}` });
7110
+ }
7111
+ const coreResult = validateFlowConfig2(json);
7112
+ for (const issue of coreResult.errors) {
7113
+ const path23 = issue.path || "root";
7114
+ if (!inScope(path23)) continue;
7115
+ if (path23 === "contract" && contractErrors > 0) continue;
7116
+ errors.push({ path: path23, message: issue.message, code: "SCHEMA_VALIDATION" });
7117
+ }
7118
+ for (const issue of coreResult.warnings) {
7119
+ const path23 = issue.path || "root";
7120
+ if (!inScope(path23)) continue;
7121
+ warnings.push({ path: path23, message: issue.message, code: "CONFIG_WARNING" });
7122
+ }
7123
+ if (flows && allFlows.length === 0) {
7124
+ errors.push({
7125
+ path: "flows",
7126
+ message: "At least one flow is required",
7127
+ code: "EMPTY_FLOWS"
7128
+ });
7129
+ }
7130
+ checks.push("flow:steps");
7131
+ for (const flowName of scope.flows) {
7132
+ const flowValue = flows?.[flowName];
7133
+ if (!isObject7(flowValue)) continue;
7134
+ const transformersValue = flowValue.transformers;
7135
+ if (isObject7(transformersValue)) {
7136
+ for (const [name, transformerValue] of Object.entries(
7137
+ transformersValue
7138
+ )) {
7139
+ if (!isObject7(transformerValue)) continue;
7140
+ const result2 = validateStepEntry(transformerValue, "Transformer");
7141
+ if (!result2.ok) {
7142
+ errors.push({
7143
+ path: `flows.${flowName}.transformers.${name}`,
7144
+ message: result2.reason || "Invalid transformer entry.",
7145
+ code: result2.code
7146
+ });
7147
+ }
7148
+ }
7149
+ }
7150
+ for (const [section, kind] of OPEN_STEP_KINDS) {
7151
+ const steps = flowValue[section];
7152
+ if (!isObject7(steps)) continue;
7153
+ for (const [name, step] of Object.entries(steps)) {
7154
+ if (!isObject7(step)) continue;
7155
+ const at = `flows.${flowName}.${section}.${name}`;
7156
+ for (const key of unknownStepKeys(step, kind)) {
7157
+ warnings.push({
7158
+ path: `${at}.${key}`,
7159
+ message: `Unknown key "${key}" on ${kind} is ignored by the runtime`,
7160
+ suggestion: "Remove the key, or move it to where it belongs.",
7161
+ code: "UNKNOWN_KEY"
7162
+ });
7163
+ }
7164
+ const pkg = step.package;
7165
+ if (typeof pkg === "string" && pkg in DEPRECATED_PACKAGES) {
7166
+ warnings.push({
7167
+ path: at,
7168
+ message: `${kind} "${name}" uses ${pkg}, which ${DEPRECATED_PACKAGES[pkg]}`,
7169
+ code: "DEPRECATED_PACKAGE"
7170
+ });
7171
+ }
7172
+ }
6559
7173
  }
6560
7174
  }
6561
- if (flows) {
6562
- details.flowNames = allFlows;
6563
- details.flowCount = allFlows.length;
6564
- }
7175
+ if (flows) {
7176
+ details.flowNames = allFlows;
7177
+ details.flowCount = allFlows.length;
7178
+ }
6565
7179
  checks.push("flow:package-versions");
6566
7180
  let totalPackageCount = 0;
6567
7181
  for (const flowName of scope.flows) {
6568
7182
  const flowValue = flows?.[flowName];
6569
- if (!isObject6(flowValue)) continue;
7183
+ if (!isObject7(flowValue)) continue;
6570
7184
  const flowConfig = flowValue.config;
6571
- if (!isObject6(flowConfig)) continue;
7185
+ if (!isObject7(flowConfig)) continue;
6572
7186
  const bundle2 = flowConfig.bundle;
6573
- if (!isObject6(bundle2)) continue;
7187
+ if (!isObject7(bundle2)) continue;
6574
7188
  const packages = bundle2.packages;
6575
- if (!isObject6(packages)) continue;
7189
+ if (!isObject7(packages)) continue;
6576
7190
  for (const [pkgName, pkgConfigValue] of Object.entries(packages)) {
6577
- if (!isObject6(pkgConfigValue)) continue;
7191
+ if (!isObject7(pkgConfigValue)) continue;
6578
7192
  if (!pkgConfigValue.version && !pkgConfigValue.path) {
6579
7193
  warnings.push({
6580
7194
  path: `flows.${flowName}.config.bundle.packages.${pkgName}`,
@@ -6594,10 +7208,8 @@ function validateFlow(input, options = {}) {
6594
7208
  }
6595
7209
  const typed = isFlowJson2(input) ? input : void 0;
6596
7210
  const gate = (name) => {
6597
- if (!typed || errors.some(
6598
- (e) => e.code === "SCHEMA_VALIDATION" && flowOfPath(e.path, allFlows) === void 0
6599
- ))
6600
- return "file-level schema errors: the file shape is not valid";
7211
+ if (!typed || errors.some((e) => flowOfPath(e.path, allFlows) === void 0))
7212
+ return "file-level errors: the file shape or its contract is not valid";
6601
7213
  if (errors.some((e) => flowOfPath(e.path, allFlows) === name))
6602
7214
  return `earlier errors in flows.${name}`;
6603
7215
  return void 0;
@@ -6621,9 +7233,25 @@ function validateFlow(input, options = {}) {
6621
7233
  run(name, flow, typed);
6622
7234
  }
6623
7235
  };
7236
+ perFlow(["flow:structure"], (name, flow, file) => {
7237
+ const structure = validateFlowStructure({
7238
+ ...file,
7239
+ flows: { [name]: flow }
7240
+ });
7241
+ errors.push(...structure.errors);
7242
+ });
6624
7243
  perFlow(["flow:routes"], (name, flow) => {
6625
7244
  lintFlowRoutes(name, flow, errors, warnings);
6626
7245
  });
7246
+ const resolutions = /* @__PURE__ */ new Map();
7247
+ const resolutionOf = (name, file) => {
7248
+ let resolution = resolutions.get(name);
7249
+ if (!resolution) {
7250
+ resolution = resolveFlow2(file, name);
7251
+ resolutions.set(name, resolution);
7252
+ }
7253
+ return resolution;
7254
+ };
6627
7255
  let totalConnections;
6628
7256
  perFlow(
6629
7257
  ["flow:examples", "flow:contract-examples", "flow:mapping-keys"],
@@ -6633,23 +7261,27 @@ function validateFlow(input, options = {}) {
6633
7261
  checkCompatibility(name, conn, errors, warnings);
6634
7262
  }
6635
7263
  totalConnections = (totalConnections ?? 0) + connections.length;
7264
+ const resolution = resolutionOf(name, file);
6636
7265
  for (const [stepName, transformer] of Object.entries(
6637
7266
  flow.transformers || {}
6638
7267
  )) {
6639
7268
  if (!isValidateStep(transformer)) continue;
7269
+ const resolvedConfig = resolution.ok ? resolution.flow.transformers?.[stepName]?.config : void 0;
6640
7270
  checkValidateStepExamples(
6641
7271
  `flows.${name}.transformers.${stepName}`,
6642
7272
  transformer,
6643
7273
  file.contract,
6644
7274
  errors,
6645
7275
  warnings,
6646
- options.strict === true
7276
+ options.strict === true,
7277
+ skipped,
7278
+ isObject7(resolvedConfig) && isObject7(resolvedConfig.settings) ? resolvedConfig.settings : void 0
6647
7279
  );
6648
7280
  }
6649
7281
  for (const [destName, dest] of Object.entries(flow.destinations || {})) {
6650
- if (!isObject6(dest.config)) continue;
7282
+ if (!isObject7(dest.config)) continue;
6651
7283
  const mapping = dest.config.mapping;
6652
- if (!isObject6(mapping)) continue;
7284
+ if (!isObject7(mapping)) continue;
6653
7285
  for (const key of Object.keys(mapping)) {
6654
7286
  if (key.includes(".") && !key.includes(" ")) {
6655
7287
  const parts = key.split(".");
@@ -6667,41 +7299,59 @@ function validateFlow(input, options = {}) {
6667
7299
  if (totalConnections !== void 0) {
6668
7300
  details.connectionsChecked = totalConnections;
6669
7301
  }
6670
- perFlow(["flow:flow-refs"], (name, _flow, file) => {
6671
- try {
6672
- getFlowSettings2(file, name, {
6673
- deferred: true,
6674
- // don't fail on missing $env when validating
6675
- strictFlowRefs: false,
6676
- onWarning: (message) => {
6677
- warnings.push({
6678
- path: `flows.${name}`,
6679
- message,
6680
- code: "FLOW_REF_WARNING"
6681
- });
6682
- }
6683
- });
6684
- } catch (err) {
6685
- const message = err instanceof Error ? err.message : String(err);
6686
- if (/Cyclic \$flow reference/.test(message)) {
6687
- errors.push({
6688
- path: `flows.${name}`,
6689
- message,
6690
- code: "FLOW_CYCLE"
6691
- });
6692
- }
7302
+ const deferred = [];
7303
+ perFlow(["flow:resolve"], (name, flow, file) => {
7304
+ const resolution = resolutionOf(name, file);
7305
+ if (!resolution.ok) {
7306
+ errors.push(resolution.error);
7307
+ return;
6693
7308
  }
7309
+ resolved.set(name, resolution.flow);
7310
+ checkResolvedWindowCollector(name, flow, resolution.flow, errors, deferred);
6694
7311
  });
6695
7312
  details.scope = describeScope(scope, checks);
6696
7313
  details.skipped = skipped;
7314
+ details.deferred = deferred;
7315
+ result.valid = errors.length === 0;
6697
7316
  return {
6698
- valid: errors.length === 0,
6699
- type: "flow",
6700
- errors,
6701
- warnings,
6702
- details
7317
+ result,
7318
+ scope,
7319
+ typed,
7320
+ resolved,
7321
+ checks,
7322
+ skipped,
7323
+ gate
6703
7324
  };
6704
7325
  }
7326
+ function checkResolvedWindowCollector(name, raw, flow, errors, deferred) {
7327
+ const written = raw.config?.settings?.windowCollector;
7328
+ const value = flow.config?.settings?.windowCollector;
7329
+ if (flow.config?.platform !== "web" || typeof written !== "string" || !/\$(?:var|env)\./.test(written))
7330
+ return;
7331
+ const path23 = `flows.${name}.config.settings.windowCollector`;
7332
+ if (typeof value === "string" && value.includes(ENV_MARKER_PREFIX3)) {
7333
+ deferred.push({ path: path23, reference: written });
7334
+ return;
7335
+ }
7336
+ const check = checkWindowCollector2(value);
7337
+ if (check.ok) return;
7338
+ errors.push({
7339
+ path: path23,
7340
+ message: `${JSON.stringify(written)} resolved to ${JSON.stringify(value)}, which ${check.reason}`,
7341
+ code: "SCHEMA_VALIDATION"
7342
+ });
7343
+ }
7344
+ function unknownStepKeys(step, kind) {
7345
+ const unknown = [];
7346
+ const rest = { ...step };
7347
+ for (; ; ) {
7348
+ const verdict = validateStepEntry(rest, kind);
7349
+ if (verdict.code !== "UNKNOWN_KEY" || verdict.key === void 0) break;
7350
+ unknown.push(verdict.key);
7351
+ Reflect.deleteProperty(rest, verdict.key);
7352
+ }
7353
+ return unknown;
7354
+ }
6705
7355
  function routeTargets(spec) {
6706
7356
  if (spec === void 0) return [];
6707
7357
  const targets = /* @__PURE__ */ new Set();
@@ -6772,10 +7422,10 @@ function buildConnectionGraph(config) {
6772
7422
  function checkCompatibility(flowName, conn, errors, warnings) {
6773
7423
  const fromOuts = Object.entries(conn.from.examples).filter(([, ex]) => hasComparableOut(ex.out)).map(([name, ex]) => ({ name, value: ex.out }));
6774
7424
  const toIns = Object.entries(conn.to.examples).filter(([, ex]) => ex.in !== void 0 && !ex.command).map(([name, ex]) => ({ name, value: ex.in }));
6775
- const path20 = `flows.${flowName}.${conn.from.type}s.${conn.from.name} \u2192 ${conn.to.type}s.${conn.to.name}`;
7425
+ const path23 = `flows.${flowName}.${conn.from.type}s.${conn.from.name} \u2192 ${conn.to.type}s.${conn.to.name}`;
6776
7426
  if (fromOuts.length === 0 || toIns.length === 0) {
6777
7427
  warnings.push({
6778
- path: path20,
7428
+ path: path23,
6779
7429
  message: "Cannot check compatibility: missing out or in examples",
6780
7430
  suggestion: "Add out examples to the source step or in examples to the target step",
6781
7431
  code: "EXAMPLES_INCOMPLETE"
@@ -6789,7 +7439,7 @@ function checkCompatibility(flowName, conn, errors, warnings) {
6789
7439
  );
6790
7440
  if (!hasMatch) {
6791
7441
  errors.push({
6792
- path: path20,
7442
+ path: path23,
6793
7443
  message: "No compatible out/in pair found between connected steps",
6794
7444
  code: "INCOMPATIBLE_EXAMPLES"
6795
7445
  });
@@ -6799,21 +7449,21 @@ function isStepOut(value) {
6799
7449
  return Array.isArray(value) && value.every((entry) => Array.isArray(entry) && typeof entry[0] === "string");
6800
7450
  }
6801
7451
  function returnedEvents(value) {
6802
- if (isObject6(value)) return [isObject6(value.event) ? value.event : value];
7452
+ if (isObject7(value)) return [isObject7(value.event) ? value.event : value];
6803
7453
  if (Array.isArray(value)) return value.flatMap(returnedEvents);
6804
7454
  return [];
6805
7455
  }
6806
7456
  function outEvents(out) {
6807
7457
  if (isStepOut(out)) {
6808
7458
  return out.flatMap(([head, first]) => {
6809
- if (head === "elb") return isObject6(first) ? [first] : [];
7459
+ if (head === "elb") return isObject7(first) ? [first] : [];
6810
7460
  if (head === "return") return returnedEvents(first);
6811
7461
  return [];
6812
7462
  });
6813
7463
  }
6814
7464
  if (Array.isArray(out) || typeof out === "string")
6815
7465
  return out.length > 0 ? [out] : [];
6816
- return isObject6(out) && Object.keys(out).length > 0 ? [out] : [];
7466
+ return isObject7(out) && Object.keys(out).length > 0 ? [out] : [];
6817
7467
  }
6818
7468
  function hasComparableOut(out) {
6819
7469
  return outEvents(out).length > 0;
@@ -6850,17 +7500,17 @@ function lintFlowRoutes(flowName, flow, errors, warnings) {
6850
7500
  check(dest.next, `${at}.destinations.${name}.next`);
6851
7501
  }
6852
7502
  }
6853
- function routeAt(spec, path20) {
7503
+ function routeAt(spec, path23) {
6854
7504
  let current = spec;
6855
- for (const key of path20) {
7505
+ for (const key of path23) {
6856
7506
  if (typeof key === "number")
6857
7507
  current = Array.isArray(current) ? current[key] : void 0;
6858
- else current = isObject6(current) ? current[key] : void 0;
7508
+ else current = isObject7(current) ? current[key] : void 0;
6859
7509
  }
6860
7510
  return current;
6861
7511
  }
6862
7512
  function lintRoute(spec, position, known, errors, warnings) {
6863
- const where = (path20) => path20.length > 0 ? `${position}.${path20.join(".")}` : position;
7513
+ const where = (path23) => path23.length > 0 ? `${position}.${path23.join(".")}` : position;
6864
7514
  const reported = /* @__PURE__ */ new Set();
6865
7515
  const once = (key) => {
6866
7516
  if (reported.has(key)) return false;
@@ -6880,7 +7530,7 @@ function lintRoute(spec, position, known, errors, warnings) {
6880
7530
  if (node.stop && !node.match && node.kind !== "many") {
6881
7531
  const self = routeAt(spec, node.path);
6882
7532
  const list = routeAt(spec, node.path.slice(0, -1));
6883
- if (isObject6(self) && self.stop === true && typeof last === "number" && Array.isArray(list) && last < list.length - 1 && once(`stop:${where(node.path)}`)) {
7533
+ if (isObject7(self) && self.stop === true && typeof last === "number" && Array.isArray(list) && last < list.length - 1 && once(`stop:${where(node.path)}`)) {
6884
7534
  const at = where(node.path);
6885
7535
  warnings.push({
6886
7536
  path: at,
@@ -6937,38 +7587,73 @@ function resolveStepContracts(entries, contract) {
6937
7587
  if (contract) {
6938
7588
  try {
6939
7589
  resolved = resolveContracts(contract);
6940
- } catch {
6941
- return void 0;
7590
+ } catch (error) {
7591
+ return {
7592
+ ok: false,
7593
+ message: `the root contract does not resolve (${error instanceof Error ? error.message : String(error)})`
7594
+ };
6942
7595
  }
6943
7596
  }
6944
7597
  const sources = [];
6945
- for (const entry of entries) {
6946
- if (isObject6(entry)) {
7598
+ for (const [index, entry] of entries.entries()) {
7599
+ if (isObject7(entry)) {
6947
7600
  sources.push(entry);
6948
7601
  continue;
6949
7602
  }
6950
- if (typeof entry !== "string") return void 0;
6951
- const match = entry.match(REF_CONTRACT);
6952
- if (!match || !(match[1] in resolved)) return void 0;
7603
+ const match = typeof entry === "string" ? entry.match(REF_CONTRACT) : null;
7604
+ if (!match)
7605
+ return {
7606
+ ok: false,
7607
+ index,
7608
+ message: "an entry must be a $contract reference or a JSON Schema"
7609
+ };
7610
+ if (!(match[1] in resolved))
7611
+ return {
7612
+ ok: false,
7613
+ index,
7614
+ message: `contract "${match[1]}" is not defined in the root contract (defined: ${Object.keys(resolved).join(", ") || "none"})`
7615
+ };
6953
7616
  const rule = match[2] ? getByPath(resolved[match[1]], match[2]) : resolved[match[1]];
6954
- if (!isObject6(rule)) return void 0;
7617
+ if (!isObject7(rule))
7618
+ return {
7619
+ ok: false,
7620
+ index,
7621
+ message: `${match[0]} does not point at a contract rule object`
7622
+ };
6955
7623
  sources.push(rule);
6956
7624
  }
6957
- return sources;
6958
- }
6959
- function checkValidateStepExamples(stepPath, step, contract, errors, warnings, strict) {
6960
- if (!step.examples) return;
6961
- const settings = isObject6(step.config) && isObject6(step.config.settings) ? step.config.settings : {};
6962
- if (settings.contract !== void 0 && !Array.isArray(settings.contract))
7625
+ return { ok: true, sources };
7626
+ }
7627
+ function checkValidateStepExamples(stepPath, step, contract, errors, warnings, strict, skipped, resolvedSettings) {
7628
+ const settings = resolvedSettings ?? (isObject7(step.config) && isObject7(step.config.settings) ? step.config.settings : {});
7629
+ const contractPath = `${stepPath}.config.settings.contract`;
7630
+ if (settings.contract !== void 0 && !Array.isArray(settings.contract)) {
7631
+ if (step.examples)
7632
+ skipped?.push({
7633
+ path: stepPath,
7634
+ check: "flow:contract-examples",
7635
+ reason: "config.settings.contract is not a list, so the step cannot be judged statically",
7636
+ code: "CONTRACT_NOT_STATIC"
7637
+ });
7638
+ return;
7639
+ }
7640
+ const resolution = Array.isArray(settings.contract) ? resolveStepContracts(settings.contract, contract) : { ok: true, sources: [] };
7641
+ if (!resolution.ok) {
7642
+ errors.push({
7643
+ path: resolution.index === void 0 ? contractPath : `${contractPath}.${resolution.index}`,
7644
+ message: `The validate step's contract does not resolve: ${resolution.message}`,
7645
+ code: "UNRESOLVED_CONTRACT"
7646
+ });
6963
7647
  return;
6964
- const contracts = Array.isArray(settings.contract) ? resolveStepContracts(settings.contract, contract) : [];
6965
- if (!contracts) return;
7648
+ }
7649
+ if (!step.examples) return;
7650
+ const contracts = resolution.sources;
6966
7651
  const mode = settings.mode === "strict" ? "strict" : "pass";
6967
7652
  const format = settings.format === true;
6968
- const output = isObject6(settings.output) ? settings.output : {};
7653
+ const output = isObject7(settings.output) ? settings.output : {};
6969
7654
  const isValidPath = typeof output.isValid === "string" ? output.isValid : "source.valid";
6970
7655
  for (const [exName, example] of Object.entries(step.examples)) {
6971
- if (example.command || !isObject6(example.in)) continue;
7656
+ if (example.command || !isObject7(example.in)) continue;
6972
7657
  if (example.out === void 0) continue;
6973
7658
  const event = example.in;
6974
7659
  const verdict = validateEventAgainstContract(event, void 0, {
@@ -6990,13 +7675,13 @@ function checkValidateStepExamples(stepPath, step, contract, errors, warnings, s
6990
7675
  if (agrees) continue;
6991
7676
  const reason = verdict.isValid ? "the event satisfies the contract" : `the event breaks the contract (${verdict.errors.map((e) => `${e.path || "/"}: ${e.message}`).join("; ")})`;
6992
7677
  const expected = mode === "strict" && !verdict.isValid ? 'drops it (["return", false])' : `passes it on${isValidPath ? ` with ${isValidPath} ${verdict.isValid}` : ""}`;
6993
- const path20 = `${stepPath}.examples.${exName}.out`;
7678
+ const path23 = `${stepPath}.examples.${exName}.out`;
6994
7679
  const message = `Example out disagrees with the validate step: ${reason}, so with mode "${mode}" the step ${expected}.`;
6995
7680
  if (strict) {
6996
- errors.push({ path: path20, message, code: "CONTRACT_VIOLATION" });
7681
+ errors.push({ path: path23, message, code: "CONTRACT_VIOLATION" });
6997
7682
  } else {
6998
7683
  warnings.push({
6999
- path: path20,
7684
+ path: path23,
7000
7685
  message,
7001
7686
  suggestion: "Make the example out show what the validate step does with its settings.",
7002
7687
  code: "CONTRACT_VIOLATION"
@@ -7006,14 +7691,14 @@ function checkValidateStepExamples(stepPath, step, contract, errors, warnings, s
7006
7691
  }
7007
7692
 
7008
7693
  // src/commands/validate/validators/mapping.ts
7009
- import { isObject as isObject7 } from "@walkeros/core";
7694
+ import { isObject as isObject8 } from "@walkeros/core";
7010
7695
  import { schemas as schemas5 } from "@walkeros/core/dev";
7011
7696
  var { RuleSchema } = schemas5.MappingSchemas;
7012
7697
  function isRule(value) {
7013
- return isObject7(value) && RuleSchema.safeParse(value).success;
7698
+ return isObject8(value) && RuleSchema.safeParse(value).success;
7014
7699
  }
7015
7700
  function isNestedActions(key, value) {
7016
- if (key.includes(" ") || !isObject7(value)) return false;
7701
+ if (key.includes(" ") || !isObject8(value)) return false;
7017
7702
  const rules = Object.values(value);
7018
7703
  return rules.length > 0 && rules.every(
7019
7704
  (rule) => Array.isArray(rule) ? rule.length > 0 && rule.every(isRule) : isRule(rule)
@@ -7023,7 +7708,7 @@ function validateMapping(input) {
7023
7708
  const errors = [];
7024
7709
  const warnings = [];
7025
7710
  const details = {};
7026
- if (!isObject7(input)) {
7711
+ if (!isObject8(input)) {
7027
7712
  errors.push({
7028
7713
  path: "root",
7029
7714
  message: "Mapping must be an object with event patterns as keys",
@@ -7083,83 +7768,43 @@ function validateMapping(input) {
7083
7768
  }
7084
7769
 
7085
7770
  // src/commands/validate/validators/entry.ts
7086
- import Ajv from "ajv";
7087
- import { fetchPackageSchema, isObject as isObject8 } from "@walkeros/core";
7088
- var CLIENT_HEADER = "walkeros-cli/4.7.0-next-1790722899482";
7089
- function pointerSegments(pointer) {
7090
- if (pointer === "") return [];
7091
- return pointer.split("/").slice(1).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
7092
- }
7093
- function valueAt(root, segments) {
7094
- let current = root;
7095
- for (const segment of segments) {
7096
- if (Array.isArray(current)) current = current[Number(segment)];
7097
- else if (isObject8(current)) current = current[segment];
7098
- else return void 0;
7099
- }
7100
- return current;
7101
- }
7102
- async function checkEntrySettings(target, errors, skipped, packages) {
7103
- const at = `flows.${target.flow}.${target.section}.${target.key}`;
7104
- const packageName = typeof target.entry.package === "string" ? target.entry.package : "";
7105
- if (!packageName) {
7106
- skipped.push({
7107
- path: at,
7108
- check: "entry:settings",
7109
- reason: "No package field, so there is no settings schema to fetch",
7110
- code: "NO_PACKAGE"
7111
- });
7112
- return;
7113
- }
7114
- packages.push({ path: at, package: packageName });
7115
- let settingsSchema;
7116
- try {
7117
- const info = await fetchPackageSchema(packageName, {
7118
- client: CLIENT_HEADER
7119
- });
7120
- settingsSchema = info.schemas?.settings;
7121
- } catch (error) {
7122
- skipped.push({
7123
- path: at,
7124
- check: "entry:settings",
7125
- reason: `Schema for ${packageName} could not be fetched: ${error instanceof Error ? error.message : "Unknown error"}`,
7126
- code: "SCHEMA_UNAVAILABLE"
7127
- });
7128
- return;
7129
- }
7130
- if (!isObject8(settingsSchema)) {
7131
- skipped.push({
7132
- path: at,
7133
- check: "entry:settings",
7134
- reason: `Package ${packageName} has no settings schema`,
7135
- code: "NO_SETTINGS_SCHEMA"
7136
- });
7137
- return;
7138
- }
7139
- const config = isObject8(target.entry.config) ? target.entry.config : {};
7140
- const settings = config.settings ?? {};
7141
- const ajv = new Ajv({ allErrors: true, validateFormats: false });
7142
- const validate2 = ajv.compile(settingsSchema);
7143
- if (validate2(settings)) return;
7144
- for (const e of validate2.errors || []) {
7145
- const segments = pointerSegments(e.instancePath);
7146
- const error = {
7147
- path: [`${at}.config.settings`, ...segments].join("."),
7148
- message: e.message || "Unknown error",
7149
- code: "ENTRY_SCHEMA",
7150
- keyword: e.keyword
7151
- };
7152
- if (segments.length > 0) error.value = valueAt(settings, segments);
7153
- errors.push(error);
7154
- }
7155
- }
7156
- async function validateEntry(path20, flowConfig, options = {}) {
7157
- const resolved = resolveScope(flowConfig, { flow: options.flow, path: path20 });
7771
+ async function validateEntry(path23, flowConfig, options = {}) {
7772
+ const resolved = resolveScope(flowConfig, { flow: options.flow, path: path23 });
7158
7773
  const errors = [...resolved.errors];
7159
7774
  const skipped = [];
7775
+ const deferred = [];
7160
7776
  const packages = [];
7777
+ const context2 = createSettingsContext(options.configDir);
7778
+ const file = isFlowJson2(flowConfig) ? flowConfig : void 0;
7779
+ const resolutions = /* @__PURE__ */ new Map();
7161
7780
  for (const target of resolved.entries) {
7162
- await checkEntrySettings(target, errors, skipped, packages);
7781
+ const at = `flows.${target.flow}.${target.section}.${target.key}`;
7782
+ let resolution = resolutions.get(target.flow);
7783
+ if (!resolution && file) {
7784
+ resolution = resolveFlow2(file, target.flow);
7785
+ resolutions.set(target.flow, resolution);
7786
+ if (!resolution.ok) errors.push(resolution.error);
7787
+ }
7788
+ if (!resolution?.ok) {
7789
+ skipped.push({
7790
+ path: at,
7791
+ check: "entry:settings",
7792
+ reason: `flows.${target.flow} does not resolve, so its settings cannot be checked`,
7793
+ code: "GATED_BY_ERRORS"
7794
+ });
7795
+ continue;
7796
+ }
7797
+ const outcome = await checkStepSettings(
7798
+ target,
7799
+ resolution.flow,
7800
+ file?.flows[target.flow],
7801
+ "entry:settings",
7802
+ context2
7803
+ );
7804
+ errors.push(...outcome.findings);
7805
+ skipped.push(...outcome.skipped);
7806
+ deferred.push(...outcome.deferred);
7807
+ if (outcome.checked) packages.push(outcome.checked);
7163
7808
  }
7164
7809
  return {
7165
7810
  valid: errors.length === 0,
@@ -7169,18 +7814,105 @@ async function validateEntry(path20, flowConfig, options = {}) {
7169
7814
  details: {
7170
7815
  scope: describeScope(resolved, ["entry:settings"]),
7171
7816
  skipped,
7817
+ deferred,
7172
7818
  ...packages.length > 0 ? { packages } : {}
7173
7819
  }
7174
7820
  };
7175
7821
  }
7176
7822
 
7177
7823
  // src/commands/validate/types.ts
7824
+ var VALIDATE_CODES = [
7825
+ // Input and options
7826
+ "INPUT_ERROR",
7827
+ "SERIALIZATION_ERROR",
7828
+ "OPTION_NOT_APPLICABLE",
7829
+ // Scope (--flow, --path)
7830
+ "FLOW_NOT_FOUND",
7831
+ "ENTRY_NOT_FOUND",
7832
+ "AMBIGUOUS_ENTRY",
7833
+ "UNSUPPORTED_SECTION",
7834
+ "INVALID_PATH",
7835
+ // Flow file
7836
+ "SCHEMA_VALIDATION",
7837
+ "CONFIG_WARNING",
7838
+ "EMPTY_FLOWS",
7839
+ "UNKNOWN_KEY",
7840
+ "CONFLICT",
7841
+ "MISSING_PACKAGE",
7842
+ "OBSOLETE_CODE_STRING",
7843
+ "INVALID_IMPORT",
7844
+ "INVALID_CODE_SHAPE",
7845
+ "PACKAGE_VERSION_MISSING",
7846
+ "DEPRECATED_PACKAGE",
7847
+ "UNKNOWN_ROUTE_TARGET",
7848
+ "ROUTE_DEAD_CODE",
7849
+ "ROUTE_EMPTY_MANY",
7850
+ "ROUTE_SINGLE_MANY",
7851
+ "ROUTE_FIRST_MATCH",
7852
+ "INCOMPATIBLE_EXAMPLES",
7853
+ "EXAMPLES_INCOMPLETE",
7854
+ "CONTRACT_VIOLATION",
7855
+ "MAPPING_DOT_KEY",
7856
+ "FLOW_REF_WARNING",
7857
+ "FLOW_CYCLE",
7858
+ "VAR_CYCLE",
7859
+ // Structure (the bundler's own preflight, validateFlowStructure)
7860
+ "INVALID_COMPONENT_NAME",
7861
+ "INVALID_REFERENCE",
7862
+ "STORE_REFERENCE_NOT_FOUND",
7863
+ // Resolution (getFlowSettings, as the bundler resolves the flow)
7864
+ "UNRESOLVED_REFERENCE",
7865
+ "MISSING_ENV",
7866
+ "WEB_SECRET_REF",
7867
+ "UNRESOLVED_CONTRACT",
7868
+ // Entry settings against the package schema
7869
+ "ENTRY_SCHEMA",
7870
+ // Contract
7871
+ "NO_CONTRACT_SECTION",
7872
+ "INVALID_CONTRACT",
7873
+ "FLAT_CONTRACT_SHAPE",
7874
+ "INVALID_CONTRACT_ENTRY",
7875
+ "UNKNOWN_CONTRACT_KEY",
7876
+ "INVALID_TAGGING",
7877
+ "INVALID_SCHEMA",
7878
+ "INVALID_EXTENDS",
7879
+ "INVALID_SECTION",
7880
+ "INVALID_EVENTS",
7881
+ "CIRCULAR_EXTENDS",
7882
+ "INVALID_ENTITY_KEY",
7883
+ "INVALID_ENTITY",
7884
+ "INVALID_ACTION_KEY",
7885
+ "INVALID_SCHEMA_ENTRY",
7886
+ // Event
7887
+ "NOT_AN_OBJECT",
7888
+ "MISSING_EVENT_NAME",
7889
+ "EMPTY_EVENT_NAME",
7890
+ "INVALID_EVENT_NAME",
7891
+ // Mapping
7892
+ "INVALID_MAPPING_TYPE",
7893
+ "INVALID_EVENT_PATTERN",
7894
+ "INVALID_RULE_TYPE",
7895
+ "CATCH_ALL_NOT_LAST",
7896
+ // Skips (details.skipped[].code)
7897
+ "GATED_BY_ERRORS",
7898
+ "CONTRACT_NOT_STATIC",
7899
+ "NO_PACKAGE",
7900
+ "NO_SETTINGS_SCHEMA",
7901
+ "SCHEMA_UNAVAILABLE"
7902
+ ];
7178
7903
  var USAGE_CODES = ["OPTION_NOT_APPLICABLE"];
7179
7904
 
7180
7905
  // src/commands/validate/index.ts
7181
7906
  function inapplicableOptions(type, options) {
7182
- if (type === "flow") return [];
7183
7907
  const errors = [];
7908
+ if (options.offline === true && (type !== "flow" || options.path)) {
7909
+ errors.push({
7910
+ path: "options.offline",
7911
+ message: type === "flow" ? "--offline removes the package settings check, which is all --path checks; drop one of them" : `--offline applies to a whole flow file and does not apply to -t ${type}`,
7912
+ code: "OPTION_NOT_APPLICABLE"
7913
+ });
7914
+ }
7915
+ if (type === "flow") return errors;
7184
7916
  if (options.path !== void 0) {
7185
7917
  errors.push({
7186
7918
  path: "options.path",
@@ -7197,6 +7929,31 @@ function inapplicableOptions(type, options) {
7197
7929
  }
7198
7930
  return errors;
7199
7931
  }
7932
+ function validateContractInput(input) {
7933
+ if (!isFlowJson2(input)) return validateContract(input);
7934
+ const contract = isObject9(input) ? input.contract : void 0;
7935
+ if (contract === void 0) {
7936
+ return {
7937
+ valid: false,
7938
+ type: "contract",
7939
+ errors: [
7940
+ {
7941
+ path: "contract",
7942
+ message: "The flow file has no contract section to validate",
7943
+ code: "NO_CONTRACT_SECTION"
7944
+ }
7945
+ ],
7946
+ warnings: [],
7947
+ details: {}
7948
+ };
7949
+ }
7950
+ return validateContract(contract);
7951
+ }
7952
+ function configDirOf(input) {
7953
+ if (typeof input !== "string") return void 0;
7954
+ const candidate = path20.resolve(input);
7955
+ return fs20.existsSync(candidate) && fs20.statSync(candidate).isFile() ? path20.dirname(candidate) : void 0;
7956
+ }
7200
7957
  function withDocumentScope(result, check) {
7201
7958
  return {
7202
7959
  ...result,
@@ -7223,18 +7980,32 @@ async function validate(type, input, options = {}) {
7223
7980
  details: { scope: { flows: [], checks: [] }, skipped: [] }
7224
7981
  };
7225
7982
  }
7983
+ const result = await validateByType(type, input, resolved, options);
7984
+ return options.strict === true ? escalateStrict(result) : result;
7985
+ }
7986
+ function escalateStrict(result) {
7987
+ const skipped = result.details.skipped ?? [];
7988
+ if (result.warnings.length === 0 && skipped.length === 0) return result;
7989
+ return { ...result, valid: false };
7990
+ }
7991
+ async function validateByType(type, input, resolved, options) {
7226
7992
  switch (type) {
7227
7993
  case "contract":
7228
- return withDocumentScope(validateContract(resolved), "contract");
7994
+ return withDocumentScope(validateContractInput(resolved), "contract");
7229
7995
  case "event":
7230
7996
  return withDocumentScope(validateEvent2(resolved), "event");
7231
7997
  case "flow":
7232
7998
  if (options.path !== void 0) {
7233
- return validateEntry(options.path, resolved, { flow: options.flow });
7999
+ return validateEntry(options.path, resolved, {
8000
+ flow: options.flow,
8001
+ configDir: options.configDir ?? configDirOf(input)
8002
+ });
7234
8003
  }
7235
- return validateFlow(resolved, {
8004
+ return validateFlowWithPackages(resolved, {
7236
8005
  flow: options.flow,
7237
- strict: options.strict
8006
+ strict: options.strict,
8007
+ offline: options.offline,
8008
+ configDir: options.configDir ?? configDirOf(input)
7238
8009
  });
7239
8010
  case "mapping":
7240
8011
  return withDocumentScope(validateMapping(resolved), "mapping");
@@ -7255,6 +8026,7 @@ function formatScope(result) {
7255
8026
  );
7256
8027
  }
7257
8028
  parts.push(`${scope.checks.length} check(s): ${scope.checks.join(", ")}`);
8029
+ if (scope.offline) parts.push("offline: package settings not checked");
7258
8030
  return `Scope: ${parts.join("; ")}`;
7259
8031
  }
7260
8032
  function formatResult(result, options) {
@@ -7293,7 +8065,13 @@ function formatResult(result, options) {
7293
8065
  chalk2.gray(` - skipped ${skip.check} at ${skip.path}: ${skip.reason}`)
7294
8066
  );
7295
8067
  }
7296
- if (result.valid && skipped.length === 0) {
8068
+ if (options.exitCode === 2) {
8069
+ lines.push(
8070
+ chalk2.red(
8071
+ ` \u2717 Failed under --strict: ${result.warnings.length} warning(s) and ${skipped.length} skipped check(s) count as failures`
8072
+ )
8073
+ );
8074
+ } else if (result.valid && skipped.length === 0) {
7297
8075
  lines.push(chalk2.green(` \u2713 All checks passed`));
7298
8076
  } else if (result.valid) {
7299
8077
  lines.push(
@@ -7326,16 +8104,18 @@ async function validateCommand(options) {
7326
8104
  const result = await validate(options.type, input, {
7327
8105
  flow: options.flow,
7328
8106
  path: options.path,
7329
- strict: options.strict
8107
+ strict: options.strict,
8108
+ offline: options.offline
7330
8109
  });
8110
+ exitCode = exitCodeOf(result, options.strict === true);
7331
8111
  if (!options.silent || options.output) {
7332
8112
  const formatted = formatResult(result, {
7333
8113
  json: options.json,
7334
- verbose: options.verbose
8114
+ verbose: options.verbose,
8115
+ exitCode
7335
8116
  });
7336
8117
  await writeResult(formatted + "\n", { output: options.output });
7337
8118
  }
7338
- exitCode = exitCodeOf(result, options.strict === true);
7339
8119
  } catch (error) {
7340
8120
  const errorMessage = getErrorMessage(error);
7341
8121
  if (options.json) {
@@ -7347,7 +8127,7 @@ async function validateCommand(options) {
7347
8127
  { path: "input", message: errorMessage, code: "INPUT_ERROR" }
7348
8128
  ],
7349
8129
  warnings: [],
7350
- details: {}
8130
+ details: { scope: { flows: [], checks: [] }, skipped: [] }
7351
8131
  },
7352
8132
  null,
7353
8133
  2
@@ -7365,10 +8145,10 @@ function exitCodeOf(result, strict) {
7365
8145
  (e) => USAGE_CODES.some((usageCode) => usageCode === e.code)
7366
8146
  ))
7367
8147
  return 3;
7368
- if (!result.valid) return 1;
8148
+ if (result.errors.length > 0) return 1;
7369
8149
  const skipped = result.details.skipped ?? [];
7370
8150
  if (strict && (result.warnings.length > 0 || skipped.length > 0)) return 2;
7371
- return 0;
8151
+ return result.valid ? 0 : 1;
7372
8152
  }
7373
8153
 
7374
8154
  // src/commands/login/index.ts
@@ -7386,7 +8166,7 @@ async function openInBrowser(url) {
7386
8166
  await open(url);
7387
8167
  }
7388
8168
  function delay3(ms) {
7389
- return new Promise((resolve3) => setTimeout(resolve3, ms));
8169
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
7390
8170
  }
7391
8171
  async function loginCommand(options) {
7392
8172
  const logger = createCLILogger(options);
@@ -7450,14 +8230,11 @@ async function completeDeviceLogin(deviceCode, options = {}) {
7450
8230
  break;
7451
8231
  attempts += 1;
7452
8232
  await delay3(intervalMs);
7453
- const poll = await pollDeviceToken(
7454
- appUrl,
7455
- deviceCode,
7456
- fetchFn,
7457
- AbortSignal.timeout(Math.max(1, deadline - Date.now()))
7458
- );
8233
+ const signal = AbortSignal.timeout(Math.max(1, deadline - Date.now()));
8234
+ const poll = await pollDeviceToken(appUrl, deviceCode, fetchFn, signal);
7459
8235
  if (poll.status === "pending") {
7460
8236
  waiting = "pending";
8237
+ if (signal.aborted) break;
7461
8238
  continue;
7462
8239
  }
7463
8240
  if (poll.status === "slow_down") {
@@ -7559,18 +8336,18 @@ async function logout() {
7559
8336
  import createClient from "openapi-fetch";
7560
8337
 
7561
8338
  // src/core/contract.ts
7562
- import { createHash } from "crypto";
8339
+ import { createHash as createHash2 } from "crypto";
7563
8340
  import semver4 from "semver";
7564
8341
  var bakedContractVersion = true ? "4.7.0" : PLACEHOLDER;
7565
8342
  var bakedContractHash = true ? "1758bf6b37ad72eb37dd8176f240ae64b16c5b1f9da376cab6357027c835a833" : "";
7566
- function isRecord3(value) {
8343
+ function isRecord4(value) {
7567
8344
  return value !== null && typeof value === "object" && !Array.isArray(value);
7568
8345
  }
7569
8346
  function canonicalize(value) {
7570
8347
  if (Array.isArray(value)) {
7571
8348
  return value.map(canonicalize);
7572
8349
  }
7573
- if (isRecord3(value)) {
8350
+ if (isRecord4(value)) {
7574
8351
  const result = {};
7575
8352
  for (const key of Object.keys(value).sort()) {
7576
8353
  result[key] = canonicalize(value[key]);
@@ -7580,7 +8357,7 @@ function canonicalize(value) {
7580
8357
  return value;
7581
8358
  }
7582
8359
  function stripInfoVersion(doc) {
7583
- if (!isRecord3(doc) || !isRecord3(doc.info)) {
8360
+ if (!isRecord4(doc) || !isRecord4(doc.info)) {
7584
8361
  return doc;
7585
8362
  }
7586
8363
  const restInfo = {};
@@ -7591,7 +8368,7 @@ function stripInfoVersion(doc) {
7591
8368
  }
7592
8369
  function canonicalContractHash(doc) {
7593
8370
  const canonical = canonicalize(stripInfoVersion(doc));
7594
- return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
8371
+ return createHash2("sha256").update(JSON.stringify(canonical)).digest("hex");
7595
8372
  }
7596
8373
  var HEALTH_TIMEOUT_MS = 5e3;
7597
8374
  async function fetchHealth(baseUrl) {
@@ -7601,7 +8378,7 @@ async function fetchHealth(baseUrl) {
7601
8378
  });
7602
8379
  const body = await res.json().catch(() => void 0);
7603
8380
  const result = { reachable: true };
7604
- if (isRecord3(body)) {
8381
+ if (isRecord4(body)) {
7605
8382
  const record = body;
7606
8383
  if (typeof record.status === "string") result.status = record.status;
7607
8384
  if (typeof record.appVersion === "string")
@@ -8051,19 +8828,17 @@ async function getFlowCommand(flowId, options) {
8051
8828
  );
8052
8829
  }
8053
8830
  async function createFlowCommand(name, options) {
8054
- const content = options.content ? JSON.parse(options.content) : JSON.parse(await readFlowStdin());
8055
- await handleResult2(
8056
- () => createFlow({ name, content, projectId: options.project }),
8057
- options
8058
- );
8831
+ await handleResult2(async () => {
8832
+ const content = options.content ? await loadFlowContent(options.content) : JSON.parse(await readFlowStdin());
8833
+ return createFlow({ name, content, projectId: options.project });
8834
+ }, options);
8059
8835
  }
8060
8836
  async function updateFlowCommand(flowId, options) {
8061
- const content = options.content ? JSON.parse(options.content) : void 0;
8062
8837
  await handleResult2(
8063
- () => updateFlow({
8838
+ async () => updateFlow({
8064
8839
  flowId,
8065
8840
  name: options.name,
8066
- content,
8841
+ content: options.content ? await loadFlowContent(options.content) : void 0,
8067
8842
  projectId: options.project
8068
8843
  }),
8069
8844
  options
@@ -8081,6 +8856,9 @@ async function duplicateFlowCommand(flowId, options) {
8081
8856
  options
8082
8857
  );
8083
8858
  }
8859
+ function loadFlowContent(input) {
8860
+ return loadJsonConfig(input);
8861
+ }
8084
8862
  async function readFlowStdin() {
8085
8863
  if (!isStdinPiped()) {
8086
8864
  throw new Error("Content required: use --content or pipe via stdin");
@@ -8231,7 +9009,7 @@ async function deploySettings(options) {
8231
9009
  if (e instanceof ApiError && e.retryable) {
8232
9010
  const waitSeconds = Math.min(e.retryAfterSeconds ?? 5, 60);
8233
9011
  options.onStatus?.("rate_limited", `retrying in ${waitSeconds}s`);
8234
- await new Promise((resolve3) => setTimeout(resolve3, waitSeconds * 1e3));
9012
+ await new Promise((resolve5) => setTimeout(resolve5, waitSeconds * 1e3));
8235
9013
  response = await triggerDeploy();
8236
9014
  } else {
8237
9015
  throw e;
@@ -8355,32 +9133,7 @@ async function getDeploymentCommand(flowId, options) {
8355
9133
  }
8356
9134
 
8357
9135
  // src/commands/deployments/index.ts
8358
- import { getPlatform as getPlatform4, isObject as isObject9 } from "@walkeros/core";
8359
-
8360
- // src/version.ts
8361
- import { readFileSync as readFileSync2 } from "fs";
8362
- import { fileURLToPath } from "url";
8363
- import { dirname as dirname3, join as join4 } from "path";
8364
- var versionFilename = fileURLToPath(import.meta.url);
8365
- var versionDirname = dirname3(versionFilename);
8366
- function findPackageJson() {
8367
- const paths = [
8368
- join4(versionDirname, "../package.json"),
8369
- // dist/ or src/
8370
- join4(versionDirname, "../../package.json")
8371
- // src/core/ (not used, but safe)
8372
- ];
8373
- for (const p of paths) {
8374
- try {
8375
- return readFileSync2(p, "utf-8");
8376
- } catch {
8377
- }
8378
- }
8379
- return JSON.stringify({ version: "0.0.0" });
8380
- }
8381
- var VERSION = JSON.parse(findPackageJson()).version;
8382
-
8383
- // src/commands/deployments/index.ts
9136
+ import { getPlatform as getPlatform4, isObject as isObject10 } from "@walkeros/core";
8384
9137
  async function listDeployments(options = {}) {
8385
9138
  const id = options.projectId ?? requireProjectId();
8386
9139
  const params = new URLSearchParams();
@@ -8541,8 +9294,8 @@ async function createDeployCommand(config, options) {
8541
9294
  const flowName = options.flow ?? Object.keys(flows)[0];
8542
9295
  if (!flowName) throw new Error("No flows found in config");
8543
9296
  const flowSettings = flows[flowName];
8544
- if (!isObject9(flowSettings)) throw new Error("Invalid flow config");
8545
- const platform = isObject9(flowSettings.config) ? flowSettings.config.platform : void 0;
9297
+ if (!isObject10(flowSettings)) throw new Error("Invalid flow config");
9298
+ const platform = isObject10(flowSettings.config) ? flowSettings.config.platform : void 0;
8546
9299
  if (platform !== "web" && platform !== "server")
8547
9300
  throw new Error(
8548
9301
  `Flow "${flowName}" must have config.platform set to "web" or "server"`
@@ -8664,14 +9417,14 @@ async function feedbackCommand(text) {
8664
9417
  }
8665
9418
  }
8666
9419
  function promptUser(question) {
8667
- return new Promise((resolve3) => {
9420
+ return new Promise((resolve5) => {
8668
9421
  const rl = createInterface({
8669
9422
  input: process.stdin,
8670
9423
  output: process.stderr
8671
9424
  });
8672
9425
  rl.question(question, (answer) => {
8673
9426
  rl.close();
8674
- resolve3(answer);
9427
+ resolve5(answer);
8675
9428
  });
8676
9429
  });
8677
9430
  }
@@ -8744,12 +9497,12 @@ function telemetryDisableCommand() {
8744
9497
  }
8745
9498
 
8746
9499
  // src/index.ts
8747
- import { validateFlowStructure } from "@walkeros/core/dev";
9500
+ import { validateFlowStructure as validateFlowStructure2 } from "@walkeros/core/dev";
8748
9501
 
8749
9502
  // src/commands/bundle/wrap.ts
8750
- import * as path18 from "path";
9503
+ import * as path21 from "path";
8751
9504
  import { fileURLToPath as fileURLToPath2 } from "url";
8752
- import fs18 from "fs-extra";
9505
+ import fs21 from "fs-extra";
8753
9506
  import * as esbuild3 from "esbuild";
8754
9507
  function extractDevExternals(skeletonText) {
8755
9508
  const pattern = /import\(\s*['"]([^'"]+\/dev)['"]\s*\)/g;
@@ -8761,13 +9514,13 @@ function extractDevExternals(skeletonText) {
8761
9514
  return Array.from(found);
8762
9515
  }
8763
9516
  function getNodeResolutionPaths() {
8764
- const here = path18.dirname(fileURLToPath2(import.meta.url));
9517
+ const here = path21.dirname(fileURLToPath2(import.meta.url));
8765
9518
  const candidates = [];
8766
9519
  let dir = here;
8767
9520
  for (let i = 0; i < 8; i++) {
8768
- const candidate = path18.join(dir, "node_modules");
9521
+ const candidate = path21.join(dir, "node_modules");
8769
9522
  candidates.push(candidate);
8770
- const parent = path18.dirname(dir);
9523
+ const parent = path21.dirname(dir);
8771
9524
  if (parent === dir) break;
8772
9525
  dir = parent;
8773
9526
  }
@@ -8794,11 +9547,11 @@ async function wrapSkeleton(options) {
8794
9547
  `Invalid previewOrigin "${options.preview.previewOrigin}". Must be a bare hostname matching /^[a-z0-9.-]+$/.`
8795
9548
  );
8796
9549
  }
8797
- if (!await fs18.pathExists(skeletonPath)) {
9550
+ if (!await fs21.pathExists(skeletonPath)) {
8798
9551
  throw new Error(`wrapSkeleton: skeleton not found at ${skeletonPath}`);
8799
9552
  }
8800
- const absoluteSkeletonPath = path18.resolve(skeletonPath);
8801
- const skeletonText = await fs18.readFile(absoluteSkeletonPath, "utf-8");
9553
+ const absoluteSkeletonPath = path21.resolve(skeletonPath);
9554
+ const skeletonText = await fs21.readFile(absoluteSkeletonPath, "utf-8");
8802
9555
  const devExternals = extractDevExternals(skeletonText);
8803
9556
  const entryText = platform === "browser" ? generateWrapEntry(absoluteSkeletonPath, {
8804
9557
  ...windowCollector ? { windowCollector } : {},
@@ -8809,10 +9562,10 @@ async function wrapSkeleton(options) {
8809
9562
  ...options.observe ? { observe: options.observe } : {}
8810
9563
  }) : generateWrapEntryServer(absoluteSkeletonPath);
8811
9564
  const entryDir = await tmpRunDir("wrap");
8812
- const entryPath = path18.join(entryDir, "flow.mjs");
9565
+ const entryPath = path21.join(entryDir, "flow.mjs");
8813
9566
  try {
8814
- await fs18.writeFile(entryPath, entryText);
8815
- await fs18.ensureDir(path18.dirname(outputPath));
9567
+ await fs21.writeFile(entryPath, entryText);
9568
+ await fs21.ensureDir(path21.dirname(outputPath));
8816
9569
  const esbuildOptions = {
8817
9570
  entryPoints: [entryPath],
8818
9571
  bundle: true,
@@ -8853,16 +9606,16 @@ async function wrapSkeleton(options) {
8853
9606
  }
8854
9607
  await esbuild3.build(esbuildOptions);
8855
9608
  } finally {
8856
- await fs18.remove(entryDir).catch(() => {
9609
+ await fs21.remove(entryDir).catch(() => {
8857
9610
  });
8858
9611
  }
8859
9612
  }
8860
9613
 
8861
9614
  // src/commands/bundle/manifest.ts
8862
9615
  import crypto2 from "crypto";
8863
- import path19 from "path";
8864
- import fs19 from "fs-extra";
8865
- import { FlowReferenceError } from "@walkeros/core";
9616
+ import path22 from "path";
9617
+ import fs22 from "fs-extra";
9618
+ import { FlowReferenceError as FlowReferenceError2 } from "@walkeros/core";
8866
9619
 
8867
9620
  // src/schemas/build-manifest.ts
8868
9621
  import { z as z3 } from "@walkeros/core/dev";
@@ -9015,6 +9768,66 @@ var BuildResultSchema = z3.object({
9015
9768
  }).optional()
9016
9769
  }).meta({ id: "BuildResult", title: "walkeros bundle --manifest result" });
9017
9770
 
9771
+ // src/commands/bundle/fetch-retry.ts
9772
+ var BACKOFF_MS = [1e3, 3e3];
9773
+ function defaultSleep(ms) {
9774
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
9775
+ }
9776
+ function isTransientStatus(status) {
9777
+ return status >= 500 || status === 429;
9778
+ }
9779
+ function causeDetail(error) {
9780
+ if (!("cause" in error)) return void 0;
9781
+ const cause = error.cause;
9782
+ if (typeof cause !== "object" || cause === null) return void 0;
9783
+ if ("code" in cause && typeof cause.code === "string") return cause.code;
9784
+ if ("name" in cause && typeof cause.name === "string" && cause.name !== "Error") {
9785
+ return cause.name;
9786
+ }
9787
+ return void 0;
9788
+ }
9789
+ function describeFetchError(failure, timeoutMs) {
9790
+ if (failure instanceof Response) return `HTTP ${failure.status}`;
9791
+ if (typeof failure !== "object" || failure === null) return "fetch failed";
9792
+ if ("name" in failure && failure.name === "TimeoutError") {
9793
+ return timeoutMs === void 0 ? "timed out" : `timed out after ${timeoutMs / 1e3}s`;
9794
+ }
9795
+ const detail = causeDetail(failure);
9796
+ if (detail) return `fetch failed (${detail})`;
9797
+ return "fetch failed";
9798
+ }
9799
+ async function fetchWithRetry(url, init, options) {
9800
+ const { label, timeoutMs, warn, sleep = defaultSleep } = options;
9801
+ const maxAttempts = BACKOFF_MS.length + 1;
9802
+ for (let attempt = 1; ; attempt++) {
9803
+ let failure;
9804
+ try {
9805
+ const response = await fetch(url, {
9806
+ ...init,
9807
+ signal: AbortSignal.timeout(timeoutMs)
9808
+ });
9809
+ if (response.ok) return response;
9810
+ failure = response;
9811
+ } catch (error) {
9812
+ failure = error;
9813
+ }
9814
+ const reason = describeFetchError(failure, timeoutMs);
9815
+ if (failure instanceof Response && !isTransientStatus(failure.status)) {
9816
+ throw new Error(`${label} failed: ${reason}`);
9817
+ }
9818
+ const delay4 = BACKOFF_MS[attempt - 1];
9819
+ if (delay4 === void 0) {
9820
+ throw new Error(
9821
+ `${label} failed after ${maxAttempts} attempts: ${reason}`
9822
+ );
9823
+ }
9824
+ warn?.(
9825
+ `${label} attempt ${attempt}/${maxAttempts} failed: ${reason}, retrying in ${delay4 / 1e3}s`
9826
+ );
9827
+ await sleep(delay4);
9828
+ }
9829
+ }
9830
+
9018
9831
  // src/commands/bundle/manifest.ts
9019
9832
  var BUILD_MANIFEST_ENV = "BUILD_MANIFEST_URL";
9020
9833
  var BuildError = class extends Error {
@@ -9063,7 +9876,7 @@ function assertNoLocalPaths(loaded) {
9063
9876
  }
9064
9877
  function classify(error, phase) {
9065
9878
  if (error instanceof BuildError) return error.code;
9066
- if (error instanceof FlowReferenceError) {
9879
+ if (error instanceof FlowReferenceError2) {
9067
9880
  return error.code === "MISSING_ENV" ? "MISSING_BUILD_ENV" : "WEB_SECRET_REF";
9068
9881
  }
9069
9882
  if (error instanceof UnsupportedPackageSpecError) {
@@ -9074,20 +9887,17 @@ function classify(error, phase) {
9074
9887
  function isHttpUrl(source) {
9075
9888
  return source.startsWith("http://") || source.startsWith("https://");
9076
9889
  }
9077
- async function readManifestSource(source) {
9890
+ async function readManifestSource(source, retry) {
9078
9891
  let text;
9079
9892
  if (isHttpUrl(source)) {
9080
- const response = await fetch(source, {
9081
- signal: AbortSignal.timeout(3e4)
9082
- });
9083
- if (!response.ok) {
9084
- throw new Error(
9085
- `Manifest fetch failed: ${response.status} ${sanitizeUrl(source)}`
9086
- );
9087
- }
9893
+ const response = await fetchWithRetry(
9894
+ source,
9895
+ {},
9896
+ { ...retry, label: "Manifest download", timeoutMs: 3e4 }
9897
+ );
9088
9898
  text = await response.text();
9089
9899
  } else {
9090
- text = await fs19.readFile(source, "utf-8");
9900
+ text = await fs22.readFile(source, "utf-8");
9091
9901
  }
9092
9902
  return JSON.parse(text);
9093
9903
  }
@@ -9098,46 +9908,42 @@ function contentTypeFor(outputName) {
9098
9908
  }
9099
9909
  return "application/javascript";
9100
9910
  }
9101
- async function put(url, body, contentType, headers = {}) {
9102
- const attempt = async () => {
9103
- try {
9104
- return await fetch(url, {
9911
+ async function put(url, body, contentType, headers, retry) {
9912
+ try {
9913
+ await fetchWithRetry(
9914
+ url,
9915
+ {
9105
9916
  method: "PUT",
9106
9917
  body,
9107
- headers: { ...headers, "Content-Type": contentType },
9108
- signal: AbortSignal.timeout(6e4)
9109
- });
9110
- } catch {
9111
- return void 0;
9112
- }
9113
- };
9114
- let response = await attempt();
9115
- if (!response || response.status >= 500) response = await attempt();
9116
- if (!response || !response.ok) {
9117
- throw new BuildError(
9118
- "UPLOAD_FAILED",
9119
- `Upload failed: ${response ? response.status : "network error"} ${sanitizeUrl(url)}`
9918
+ headers: { ...headers, "Content-Type": contentType }
9919
+ },
9920
+ { ...retry, label: "Upload", timeoutMs: 6e4 }
9120
9921
  );
9922
+ } catch (error) {
9923
+ throw new BuildError("UPLOAD_FAILED", getErrorMessage(error));
9121
9924
  }
9122
9925
  }
9123
- async function download(url, dest) {
9124
- const response = await fetch(url, { signal: AbortSignal.timeout(6e4) });
9125
- if (!response.ok) {
9126
- throw new BuildError(
9127
- "BUILD_FAILED",
9128
- `Skeleton fetch failed: ${response.status} ${sanitizeUrl(url)}`
9926
+ async function download(url, dest, retry) {
9927
+ let response;
9928
+ try {
9929
+ response = await fetchWithRetry(
9930
+ url,
9931
+ {},
9932
+ { ...retry, label: "Skeleton download", timeoutMs: 6e4 }
9129
9933
  );
9934
+ } catch (error) {
9935
+ throw new BuildError("BUILD_FAILED", getErrorMessage(error));
9130
9936
  }
9131
- await fs19.writeFile(dest, Buffer.from(await response.arrayBuffer()));
9937
+ await fs22.writeFile(dest, Buffer.from(await response.arrayBuffer()));
9132
9938
  }
9133
- async function buildArtifact(manifest, configPath, artifact, workDir, index, outputs) {
9134
- const output = path19.join(workDir, "out", artifact.outputName);
9135
- await fs19.ensureDir(path19.dirname(output));
9939
+ async function buildArtifact(manifest, configPath, artifact, workDir, index, outputs, retry) {
9940
+ const output = path22.join(workDir, "out", artifact.outputName);
9941
+ await fs22.ensureDir(path22.dirname(output));
9136
9942
  if (artifact.target === "wrap") {
9137
9943
  let skeletonPath;
9138
9944
  if ("url" in artifact.skeleton) {
9139
- skeletonPath = path19.join(workDir, `skeleton-${index}.mjs`);
9140
- await download(artifact.skeleton.url, skeletonPath);
9945
+ skeletonPath = path22.join(workDir, `skeleton-${index}.mjs`);
9946
+ await download(artifact.skeleton.url, skeletonPath, retry);
9141
9947
  } else {
9142
9948
  const built = outputs.get(artifact.skeleton.artifact);
9143
9949
  if (!built) {
@@ -9156,8 +9962,8 @@ async function buildArtifact(manifest, configPath, artifact, workDir, index, out
9156
9962
  });
9157
9963
  return output;
9158
9964
  }
9159
- const scratch = path19.join(workDir, `cli-${index}`);
9160
- await fs19.ensureDir(scratch);
9965
+ const scratch = path22.join(workDir, `cli-${index}`);
9966
+ await fs22.ensureDir(scratch);
9161
9967
  await bundle(configPath, {
9162
9968
  ...manifest.flowName ? { flowName: manifest.flowName } : {},
9163
9969
  target: artifact.target,
@@ -9170,14 +9976,15 @@ async function buildArtifact(manifest, configPath, artifact, workDir, index, out
9170
9976
  });
9171
9977
  return output;
9172
9978
  }
9173
- async function runBuildManifest(source) {
9979
+ async function runBuildManifest(source, retry = {}) {
9980
+ const report = (url, result2) => reportResult(url, result2, retry);
9174
9981
  const result = { ok: false, toolchain: VERSION, artifacts: [] };
9175
9982
  const fail = (code, message, outputName) => {
9176
9983
  result.error = outputName ? { code, message, outputName } : { code, message };
9177
9984
  };
9178
9985
  let raw;
9179
9986
  try {
9180
- raw = await readManifestSource(source);
9987
+ raw = await readManifestSource(source, retry);
9181
9988
  } catch (error) {
9182
9989
  fail("INVALID_MANIFEST", getErrorMessage(error));
9183
9990
  return { result, reported: false };
@@ -9204,10 +10011,10 @@ async function runBuildManifest(source) {
9204
10011
  }
9205
10012
  const workDir = await tmpRunDir("build");
9206
10013
  try {
9207
- const configPath = path19.join(workDir, "flow.json");
10014
+ const configPath = path22.join(workDir, "flow.json");
9208
10015
  if (manifest.flowConfig) {
9209
10016
  try {
9210
- await fs19.writeJson(configPath, manifest.flowConfig);
10017
+ await fs22.writeJson(configPath, manifest.flowConfig);
9211
10018
  const loaded = loadBundleConfig(manifest.flowConfig, {
9212
10019
  configPath,
9213
10020
  flowName: manifest.flowName,
@@ -9231,15 +10038,17 @@ async function runBuildManifest(source) {
9231
10038
  artifact,
9232
10039
  workDir,
9233
10040
  index,
9234
- outputs
10041
+ outputs,
10042
+ retry
9235
10043
  );
9236
10044
  outputs.set(artifact.outputName, output);
9237
- const content = await fs19.readFile(output);
10045
+ const content = await fs22.readFile(output);
9238
10046
  await put(
9239
10047
  artifact.putUrl,
9240
10048
  content,
9241
10049
  artifact.contentType ?? contentTypeFor(artifact.outputName),
9242
- artifact.headers
10050
+ artifact.headers ?? {},
10051
+ retry
9243
10052
  );
9244
10053
  result.artifacts.push({
9245
10054
  target: artifact.target,
@@ -9262,13 +10071,13 @@ async function runBuildManifest(source) {
9262
10071
  result.ok = true;
9263
10072
  return { result, reported: await report(manifest.resultPutUrl, result) };
9264
10073
  } finally {
9265
- await fs19.remove(workDir).catch(() => {
10074
+ await fs22.remove(workDir).catch(() => {
9266
10075
  });
9267
10076
  }
9268
10077
  }
9269
- async function report(url, result) {
10078
+ async function reportResult(url, result, retry) {
9270
10079
  try {
9271
- await put(url, JSON.stringify(result), "application/json");
10080
+ await put(url, JSON.stringify(result), "application/json", {}, retry);
9272
10081
  return true;
9273
10082
  } catch {
9274
10083
  return false;
@@ -9476,7 +10285,7 @@ async function endObserveSession(options) {
9476
10285
  }
9477
10286
 
9478
10287
  // src/commands/hub/index.ts
9479
- async function readJson2(response, fallback) {
10288
+ async function readJson3(response, fallback) {
9480
10289
  if (!response.ok) {
9481
10290
  const body = await response.json().catch(() => ({}));
9482
10291
  throwApiResponseError(response, body, fallback);
@@ -9492,7 +10301,7 @@ async function listReleases(options) {
9492
10301
  const response = await apiFetch(
9493
10302
  `/api/projects/${pid}/flows/${options.flowId}/releases?${params.toString()}`
9494
10303
  );
9495
- return readJson2(response, "Failed to list releases");
10304
+ return readJson3(response, "Failed to list releases");
9496
10305
  }
9497
10306
  async function getRelease(options) {
9498
10307
  const pid = options.projectId ?? requireProjectId();
@@ -9500,7 +10309,7 @@ async function getRelease(options) {
9500
10309
  const response = await apiFetch(
9501
10310
  `/api/projects/${pid}/flows/${options.flowId}/releases/${encodeURIComponent(segment)}`
9502
10311
  );
9503
- return readJson2(response, "Failed to read release");
10312
+ return readJson3(response, "Failed to read release");
9504
10313
  }
9505
10314
  async function listStepHistory(options) {
9506
10315
  const pid = options.projectId ?? requireProjectId();
@@ -9510,7 +10319,7 @@ async function listStepHistory(options) {
9510
10319
  const response = await apiFetch(
9511
10320
  `/api/projects/${pid}/flows/${options.flowId}/releases/step-history?${params.toString()}`
9512
10321
  );
9513
- return readJson2(response, "Failed to read step history");
10322
+ return readJson3(response, "Failed to read step history");
9514
10323
  }
9515
10324
  async function setReleaseRationale(options) {
9516
10325
  const pid = options.projectId ?? requireProjectId();
@@ -9525,7 +10334,7 @@ async function setReleaseRationale(options) {
9525
10334
  })
9526
10335
  }
9527
10336
  );
9528
- return readJson2(response, "Failed to write release rationale");
10337
+ return readJson3(response, "Failed to write release rationale");
9529
10338
  }
9530
10339
  async function listThreads(options) {
9531
10340
  const pid = options.projectId ?? requireProjectId();
@@ -9540,7 +10349,7 @@ async function listThreads(options) {
9540
10349
  const response = await apiFetch(
9541
10350
  `/api/projects/${pid}/flows/${options.flowId}/threads?${params.toString()}`
9542
10351
  );
9543
- return readJson2(response, "Failed to list threads");
10352
+ return readJson3(response, "Failed to list threads");
9544
10353
  }
9545
10354
  async function createThread(options) {
9546
10355
  const pid = options.projectId ?? requireProjectId();
@@ -9557,7 +10366,7 @@ async function createThread(options) {
9557
10366
  })
9558
10367
  }
9559
10368
  );
9560
- return readJson2(response, "Failed to open thread");
10369
+ return readJson3(response, "Failed to open thread");
9561
10370
  }
9562
10371
  async function addThreadMessage(options) {
9563
10372
  const pid = options.projectId ?? requireProjectId();
@@ -9569,7 +10378,7 @@ async function addThreadMessage(options) {
9569
10378
  body: JSON.stringify({ text: options.text })
9570
10379
  }
9571
10380
  );
9572
- return readJson2(response, "Failed to add message");
10381
+ return readJson3(response, "Failed to add message");
9573
10382
  }
9574
10383
  async function listKnowledge(options) {
9575
10384
  const pid = options.projectId ?? requireProjectId();
@@ -9582,11 +10391,11 @@ async function listKnowledge(options) {
9582
10391
  const response = await apiFetch(
9583
10392
  `/api/projects/${pid}/knowledge?${params.toString()}`
9584
10393
  );
9585
- return readJson2(response, "Failed to read knowledge");
10394
+ return readJson3(response, "Failed to read knowledge");
9586
10395
  }
9587
10396
 
9588
10397
  // src/commands/frames/index.ts
9589
- async function readJson3(response, fallback) {
10398
+ async function readJson4(response, fallback) {
9590
10399
  if (!response.ok) {
9591
10400
  const body = await response.json().catch(() => ({}));
9592
10401
  throwApiResponseError(response, body, fallback);
@@ -9596,7 +10405,7 @@ async function readJson3(response, fallback) {
9596
10405
  async function listFrames(options = {}) {
9597
10406
  const pid = options.projectId ?? requireProjectId();
9598
10407
  const response = await apiFetch(`/api/projects/${pid}/frames`);
9599
- return readJson3(response, "Failed to list frames");
10408
+ return readJson4(response, "Failed to list frames");
9600
10409
  }
9601
10410
  async function listPageFrames(options) {
9602
10411
  const pid = options.projectId ?? requireProjectId();
@@ -9604,14 +10413,14 @@ async function listPageFrames(options) {
9604
10413
  const response = await apiFetch(
9605
10414
  `/api/projects/${pid}/frames?${params.toString()}`
9606
10415
  );
9607
- return readJson3(response, "Failed to list page frames");
10416
+ return readJson4(response, "Failed to list page frames");
9608
10417
  }
9609
10418
  async function getFrame(options) {
9610
10419
  const pid = options.projectId ?? requireProjectId();
9611
10420
  const response = await apiFetch(
9612
10421
  `/api/projects/${pid}/frames/${encodeURIComponent(options.frameId)}`
9613
10422
  );
9614
- return readJson3(response, "Failed to read frame");
10423
+ return readJson4(response, "Failed to read frame");
9615
10424
  }
9616
10425
 
9617
10426
  // src/commands/secrets/index.ts
@@ -9881,6 +10690,7 @@ export {
9881
10690
  BuildWrapArtifactSchema,
9882
10691
  BuildWrapOptionsSchema,
9883
10692
  DeploymentAmbiguityError,
10693
+ VALIDATE_CODES,
9884
10694
  VERSION,
9885
10695
  addThreadMessage,
9886
10696
  annotateErrorWithDrift,
@@ -9968,7 +10778,6 @@ export {
9968
10778
  loginCommand,
9969
10779
  logout,
9970
10780
  logoutCommand,
9971
- maskKnownNumbers,
9972
10781
  mergeAuthHeaders,
9973
10782
  parseSSEEvents,
9974
10783
  publicFetch,
@@ -10007,7 +10816,7 @@ export {
10007
10816
  updateSecret,
10008
10817
  validate,
10009
10818
  validateCommand,
10010
- validateFlowStructure,
10819
+ validateFlowStructure2 as validateFlowStructure,
10011
10820
  whoami,
10012
10821
  whoamiCommand,
10013
10822
  wrapSkeleton,