@walkeros/cli 4.6.0-next-1788817472881 → 4.6.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
@@ -272,6 +272,7 @@ import {
272
272
  chmodSync,
273
273
  renameSync
274
274
  } from "fs";
275
+ import { randomBytes } from "crypto";
275
276
  import { join } from "path";
276
277
  import { homedir } from "os";
277
278
  function getConfigDir() {
@@ -295,10 +296,18 @@ function replaceConfigFile(config) {
295
296
  const dir = getConfigDir();
296
297
  mkdirSync(dir, { recursive: true, mode: 448 });
297
298
  const configPath = getConfigPath();
298
- const tempPath = `${configPath}.tmp`;
299
- writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 384 });
300
- chmodSync(tempPath, 384);
301
- renameSync(tempPath, configPath);
299
+ const tempPath = `${configPath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
300
+ try {
301
+ writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 384 });
302
+ chmodSync(tempPath, 384);
303
+ renameSync(tempPath, configPath);
304
+ } catch (error) {
305
+ try {
306
+ unlinkSync(tempPath);
307
+ } catch {
308
+ }
309
+ throw error;
310
+ }
302
311
  }
303
312
  function writeConfig(config) {
304
313
  replaceConfigFile({ ...readConfig() ?? {}, ...config });
@@ -339,7 +348,7 @@ function getFeedbackPreference() {
339
348
  function setDefaultProject(projectId) {
340
349
  const config = readConfig();
341
350
  if (!config) {
342
- throw new Error("Not authenticated. Run `walkeros login` first.");
351
+ throw new Error("Not authenticated. Run `walkeros auth login` first.");
343
352
  }
344
353
  writeConfig({ ...config, defaultProjectId: projectId });
345
354
  }
@@ -376,6 +385,28 @@ var init_config_file = __esm({
376
385
  }
377
386
  });
378
387
 
388
+ // src/lib/secure-url.ts
389
+ function isLoopback(hostname) {
390
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
391
+ }
392
+ function requireSecureUrl(url) {
393
+ let parsed;
394
+ try {
395
+ parsed = new URL(url);
396
+ } catch {
397
+ return url;
398
+ }
399
+ if (parsed.protocol !== "http:" || isLoopback(parsed.hostname)) return url;
400
+ throw new Error(
401
+ `Refusing to send walkerOS credentials over plain http to ${url}. Use https, or localhost / 127.0.0.1 / [::1] for local development.`
402
+ );
403
+ }
404
+ var init_secure_url = __esm({
405
+ "src/lib/secure-url.ts"() {
406
+ "use strict";
407
+ }
408
+ });
409
+
379
410
  // src/lib/config-lock.ts
380
411
  import { closeSync, mkdirSync as mkdirSync2, openSync, statSync, unlinkSync as unlinkSync2 } from "fs";
381
412
  function getConfigLockPath() {
@@ -462,10 +493,11 @@ function describe(response, body) {
462
493
  return `HTTP ${response.status}`;
463
494
  }
464
495
  function post(fetchFn, url, form, signal) {
465
- return fetchFn(url, {
496
+ return fetchFn(requireSecureUrl(url), {
466
497
  method: "POST",
467
498
  headers: { ...FORM_HEADERS },
468
499
  body: new URLSearchParams(form).toString(),
500
+ redirect: "error",
469
501
  ...signal ? { signal } : {}
470
502
  });
471
503
  }
@@ -490,7 +522,8 @@ async function startDeviceAuthorization(appUrl, fetchFn = globalThis.fetch) {
490
522
  // RFC 8707. The token comes back bound to the API, so a leaked CLI token
491
523
  // cannot be replayed against the MCP resource.
492
524
  resource: `${appUrl}/api`
493
- }
525
+ },
526
+ AbortSignal.timeout(DEVICE_AUTHORIZATION_TIMEOUT_MS)
494
527
  );
495
528
  const body = await readJson(response);
496
529
  if (!response.ok) throw new Error(describe(response, body));
@@ -580,15 +613,17 @@ async function revokeRefreshToken(appUrl, refreshToken, fetchFn = globalThis.fet
580
613
  } catch {
581
614
  }
582
615
  }
583
- var CLI_CLIENT_ID, CLI_SCOPE, DEVICE_CODE_GRANT, REFRESH_TIMEOUT_MS, REVOKE_TIMEOUT_MS, FORM_HEADERS, DeviceAuthorizationSchema, TokenSchema, ErrorSchema;
616
+ var CLI_CLIENT_ID, CLI_SCOPE, DEVICE_CODE_GRANT, REFRESH_TIMEOUT_MS, REVOKE_TIMEOUT_MS, DEVICE_AUTHORIZATION_TIMEOUT_MS, FORM_HEADERS, DeviceAuthorizationSchema, TokenSchema, ErrorSchema;
584
617
  var init_oauth_client = __esm({
585
618
  "src/core/oauth-client.ts"() {
586
619
  "use strict";
620
+ init_secure_url();
587
621
  CLI_CLIENT_ID = "walkeros-cli";
588
622
  CLI_SCOPE = "read write offline_access";
589
623
  DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
590
624
  REFRESH_TIMEOUT_MS = 1e4;
591
625
  REVOKE_TIMEOUT_MS = 5e3;
626
+ DEVICE_AUTHORIZATION_TIMEOUT_MS = 1e4;
592
627
  FORM_HEADERS = {
593
628
  "Content-Type": "application/x-www-form-urlencoded",
594
629
  Accept: "application/json"
@@ -618,7 +653,7 @@ function noticeLegacyToken() {
618
653
  if (legacyNoticeShown) return;
619
654
  legacyNoticeShown = true;
620
655
  process.stderr.write(
621
- "walkerOS: using a static token from your config. Run `walkeros login` to switch to a session that refreshes automatically.\n"
656
+ "walkerOS: using a static token from your config. Run `walkeros auth login` to switch to a session that refreshes automatically.\n"
622
657
  );
623
658
  }
624
659
  function isFresh(config, nowMs) {
@@ -754,6 +789,7 @@ function buildHeaders(token, headers) {
754
789
  async function apiFetch(path20, init) {
755
790
  const baseUrl = resolveAppUrl();
756
791
  const token = await resolveAccessToken();
792
+ if (token) requireSecureUrl(baseUrl);
757
793
  return fetch(`${baseUrl}${path20}`, {
758
794
  ...init,
759
795
  headers: buildHeaders(token, init?.headers)
@@ -776,6 +812,7 @@ async function deployFetch(path20, init) {
776
812
  throw new Error(
777
813
  "No authentication token available. Set WALKEROS_DEPLOY_TOKEN or run walkeros auth login."
778
814
  );
815
+ requireSecureUrl(baseUrl);
779
816
  return fetch(`${baseUrl}${path20}`, {
780
817
  ...init,
781
818
  headers: buildHeaders(token, init?.headers)
@@ -785,6 +822,7 @@ var init_http = __esm({
785
822
  "src/core/http.ts"() {
786
823
  "use strict";
787
824
  init_config_file();
825
+ init_secure_url();
788
826
  init_auth();
789
827
  init_client_context();
790
828
  }
@@ -810,6 +848,7 @@ function isAppOrigin(url) {
810
848
  }
811
849
  async function fetchContentString(url) {
812
850
  const token = isAppOrigin(url) ? await resolveAccessToken() : null;
851
+ if (token) requireSecureUrl(url);
813
852
  const response = await fetch(url, {
814
853
  headers: mergeAuthHeaders(token)
815
854
  });
@@ -884,6 +923,7 @@ var init_utils = __esm({
884
923
  init_http();
885
924
  init_auth();
886
925
  init_config_file();
926
+ init_secure_url();
887
927
  }
888
928
  });
889
929
 
@@ -6467,7 +6507,7 @@ async function fetchConfig(options) {
6467
6507
  init_cache();
6468
6508
 
6469
6509
  // src/runtime/heartbeat.ts
6470
- import { randomBytes } from "crypto";
6510
+ import { randomBytes as randomBytes2 } from "crypto";
6471
6511
 
6472
6512
  // src/version.ts
6473
6513
  import { readFileSync as readFileSync4 } from "fs";
@@ -6558,7 +6598,7 @@ function snapshotDestinations(status) {
6558
6598
  }
6559
6599
  return result;
6560
6600
  }
6561
- var instanceId = randomBytes(8).toString("hex");
6601
+ var instanceId = randomBytes2(8).toString("hex");
6562
6602
  function getInstanceId() {
6563
6603
  return instanceId;
6564
6604
  }
@@ -8601,7 +8641,7 @@ function validateMapping(input) {
8601
8641
  // src/commands/validate/validators/entry.ts
8602
8642
  import Ajv from "ajv";
8603
8643
  import { fetchPackageSchema } from "@walkeros/core";
8604
- var CLIENT_HEADER = "walkeros-cli/4.6.0-next-1788817472881";
8644
+ var CLIENT_HEADER = "walkeros-cli/4.6.0";
8605
8645
  var SECTIONS = ["destinations", "sources", "transformers"];
8606
8646
  function resolveEntry(path20, flowConfig) {
8607
8647
  const flows = flowConfig.flows;
@@ -8855,12 +8895,14 @@ async function validateCommand(options) {
8855
8895
  init_cli_logger();
8856
8896
  init_oauth_client();
8857
8897
  init_config_file();
8898
+ init_secure_url();
8858
8899
  import { z as z8 } from "zod";
8859
8900
  var POLL_TIMEOUT_BUFFER_MS = 5e3;
8860
8901
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
8861
8902
  var MIN_SERVER_POLL_INTERVAL_MS = 1e3;
8862
8903
  var DEFAULT_POLL_TIMEOUT_MS = 9e5;
8863
8904
  var SLOW_DOWN_STEP_MS = 5e3;
8905
+ var WHOAMI_TIMEOUT_MS = 1e4;
8864
8906
  var TIMED_OUT = "Authorization timed out. Please try again.";
8865
8907
  var WhoamiSchema = z8.object({ email: z8.string().min(1) });
8866
8908
  async function openInBrowser(url) {
@@ -8897,7 +8939,8 @@ async function loginCommand(options) {
8897
8939
  async function fetchEmail(appUrl, accessToken, fetchFn) {
8898
8940
  try {
8899
8941
  const response = await fetchFn(`${appUrl}/api/auth/whoami`, {
8900
- headers: { Authorization: `Bearer ${accessToken}` }
8942
+ headers: { Authorization: `Bearer ${accessToken}` },
8943
+ signal: AbortSignal.timeout(WHOAMI_TIMEOUT_MS)
8901
8944
  });
8902
8945
  if (!response.ok) return void 0;
8903
8946
  const parsed = WhoamiSchema.safeParse(await response.json());
@@ -8921,7 +8964,7 @@ async function persistSession(appUrl, tokens, fetchFn) {
8921
8964
  }
8922
8965
  async function completeDeviceLogin(deviceCode, options = {}) {
8923
8966
  const fetchFn = options.fetch ?? globalThis.fetch;
8924
- const appUrl = options.url || resolveAppUrl();
8967
+ const appUrl = requireSecureUrl(options.url || resolveAppUrl());
8925
8968
  const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS);
8926
8969
  let intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
8927
8970
  let attempts = 0;
@@ -8954,7 +8997,7 @@ async function completeDeviceLogin(deviceCode, options = {}) {
8954
8997
  }
8955
8998
  async function login(options = {}) {
8956
8999
  const fetchFn = options.fetch ?? globalThis.fetch;
8957
- const appUrl = options.url || resolveAppUrl();
9000
+ const appUrl = requireSecureUrl(options.url || resolveAppUrl());
8958
9001
  let authorization;
8959
9002
  try {
8960
9003
  authorization = await startDeviceAuthorization(appUrl, fetchFn);
@@ -9010,10 +9053,14 @@ init_oauth_client();
9010
9053
  init_config_file();
9011
9054
  async function logoutCommand(options) {
9012
9055
  const logger = createCLILogger(options);
9013
- const { deleted } = await logout();
9056
+ const { deleted, superseded } = await logout();
9014
9057
  const configPath = getConfigPath();
9015
9058
  if (options.json) {
9016
- logger.json({ success: true, deleted });
9059
+ logger.json({ success: true, deleted, superseded });
9060
+ } else if (superseded) {
9061
+ logger.info(
9062
+ "A newer session was stored while logging out, and was kept. Run `walkeros auth logout` again to remove it."
9063
+ );
9017
9064
  } else if (deleted) {
9018
9065
  logger.info(`Logged out. Session removed from ${configPath}`);
9019
9066
  } else {
@@ -9021,18 +9068,24 @@ async function logoutCommand(options) {
9021
9068
  }
9022
9069
  process.exit(0);
9023
9070
  }
9071
+ function sameSession(before, after) {
9072
+ return before?.accessToken === after.accessToken && before?.refreshToken === after.refreshToken && before?.token === after.token;
9073
+ }
9024
9074
  async function logout() {
9025
- const config = readConfig();
9026
- const appUrl = resolveAppUrl();
9027
- if (config?.refreshToken) {
9028
- await revokeRefreshToken(appUrl, config.refreshToken);
9075
+ const before = readConfig();
9076
+ if (before?.refreshToken) {
9077
+ await revokeRefreshToken(resolveAppUrl(), before.refreshToken);
9029
9078
  }
9030
- return { deleted: deleteConfig() };
9079
+ const after = readConfig();
9080
+ if (after && !sameSession(before, after))
9081
+ return { deleted: false, superseded: true };
9082
+ return { deleted: deleteConfig(), superseded: false };
9031
9083
  }
9032
9084
 
9033
9085
  // src/core/api-client.ts
9034
9086
  init_auth();
9035
9087
  init_config_file();
9088
+ init_secure_url();
9036
9089
  init_client_context();
9037
9090
  import createClient from "openapi-fetch";
9038
9091
 
@@ -9040,8 +9093,8 @@ import createClient from "openapi-fetch";
9040
9093
  init_config_file();
9041
9094
  import { createHash } from "crypto";
9042
9095
  import semver4 from "semver";
9043
- var bakedContractVersion = true ? "4.5.1" : PLACEHOLDER;
9044
- var bakedContractHash = true ? "335b9bd11acaf7c516e3fcd47c74b4ab212032375e00f2b58b8459f4cb30e3a6" : "";
9096
+ var bakedContractVersion = true ? "4.6.1" : PLACEHOLDER;
9097
+ var bakedContractHash = true ? "c348310c2c401af1d10bfb117f676f7cf15ce3a1d9fcee1284eb415bad2d99a1" : "";
9045
9098
  function isRecord3(value) {
9046
9099
  return value !== null && typeof value === "object" && !Array.isArray(value);
9047
9100
  }
@@ -9073,9 +9126,9 @@ function canonicalContractHash(doc) {
9073
9126
  return createHash("sha256").update(JSON.stringify(canonical)).digest("hex");
9074
9127
  }
9075
9128
  var HEALTH_TIMEOUT_MS = 5e3;
9076
- async function fetchHealth() {
9129
+ async function fetchHealth(baseUrl) {
9077
9130
  try {
9078
- const res = await fetch(`${resolveAppUrl()}/api/health`, {
9131
+ const res = await fetch(`${baseUrl ?? resolveAppUrl()}/api/health`, {
9079
9132
  signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS)
9080
9133
  });
9081
9134
  const body = await res.json().catch(() => void 0);
@@ -9098,7 +9151,7 @@ async function fetchHealth() {
9098
9151
  async function compareContract(input = {}) {
9099
9152
  const bakedVersion = input.bakedVersion ?? bakedContractVersion;
9100
9153
  const bakedHash = input.bakedHash ?? bakedContractHash;
9101
- const health = await fetchHealth();
9154
+ const health = await fetchHealth(input.baseUrl);
9102
9155
  if (!health.reachable || health.contractVersion === void 0 || health.contractHash === void 0) {
9103
9156
  return { verdict: "unknown", bakedVersion };
9104
9157
  }
@@ -9166,7 +9219,8 @@ function createApiClient() {
9166
9219
  async onRequest({ request }) {
9167
9220
  const token = await resolveAccessToken();
9168
9221
  if (!token)
9169
- throw new Error("Not authenticated. Run `walkeros login` first.");
9222
+ throw new Error("Not authenticated. Run `walkeros auth login` first.");
9223
+ requireSecureUrl(request.url);
9170
9224
  request.headers.set("Authorization", `Bearer ${token}`);
9171
9225
  return request;
9172
9226
  },