@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/CHANGELOG.md +18 -9
- package/dist/cli.js +78 -25
- package/dist/examples/docker-compose.runner.yml +2 -0
- package/dist/index.d.ts +669 -489
- package/dist/index.js +82 -28
- package/dist/index.js.map +1 -1
- package/examples/docker-compose.runner.yml +2 -0
- package/openapi/spec.json +1051 -731
- package/package.json +8 -8
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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 =
|
|
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
|
|
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
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
await revokeRefreshToken(appUrl, config.refreshToken);
|
|
9075
|
+
const before = readConfig();
|
|
9076
|
+
if (before?.refreshToken) {
|
|
9077
|
+
await revokeRefreshToken(resolveAppUrl(), before.refreshToken);
|
|
9029
9078
|
}
|
|
9030
|
-
|
|
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.
|
|
9044
|
-
var bakedContractHash = true ? "
|
|
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
|
},
|