railcode 0.1.6 → 0.1.8
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 +207 -1
- package/package.json +1 -1
- package/static/sdk.js +35 -0
package/dist/index.js
CHANGED
|
@@ -26,8 +26,11 @@ Usage:
|
|
|
26
26
|
railcode login [--api-url <url>]
|
|
27
27
|
railcode deploy [--private | --public]
|
|
28
28
|
railcode access [public|private|restricted] [--users <a,b>]
|
|
29
|
+
railcode network
|
|
29
30
|
railcode db list
|
|
30
31
|
railcode db query "<sql>" [--connection <name>] [--engine <postgres|mysql>]
|
|
32
|
+
railcode connector list
|
|
33
|
+
railcode connector fetch <name> <path> [--method <verb>] [--body <data>]
|
|
31
34
|
railcode get design-system
|
|
32
35
|
railcode init <app>
|
|
33
36
|
railcode upgrade
|
|
@@ -61,9 +64,16 @@ async function main() {
|
|
|
61
64
|
case "access":
|
|
62
65
|
await commandAccess(args);
|
|
63
66
|
return;
|
|
67
|
+
case "network":
|
|
68
|
+
await commandNetwork(args);
|
|
69
|
+
return;
|
|
64
70
|
case "db":
|
|
65
71
|
await commandDb(args);
|
|
66
72
|
return;
|
|
73
|
+
case "connector":
|
|
74
|
+
case "connectors":
|
|
75
|
+
await commandConnector(args);
|
|
76
|
+
return;
|
|
67
77
|
case "get":
|
|
68
78
|
await commandGet(args);
|
|
69
79
|
return;
|
|
@@ -625,7 +635,8 @@ async function handleLocalApi(ctx, request, response, url) {
|
|
|
625
635
|
await handleLocalFiles(ctx, request, response, path);
|
|
626
636
|
return;
|
|
627
637
|
}
|
|
628
|
-
if (path === "/connections"
|
|
638
|
+
if ((path === "/connections" || path === "/service-connectors") &&
|
|
639
|
+
(!ctx.token || !ctx.apiBase)) {
|
|
629
640
|
sendJson(response, 200, []);
|
|
630
641
|
return;
|
|
631
642
|
}
|
|
@@ -1599,6 +1610,70 @@ function appUrlFromOrigin(app, origin) {
|
|
|
1599
1610
|
url.hash = "";
|
|
1600
1611
|
return url.toString();
|
|
1601
1612
|
}
|
|
1613
|
+
const NETWORK_HELP = `Usage:
|
|
1614
|
+
railcode network Show the platform default network mode for new apps
|
|
1615
|
+
|
|
1616
|
+
Network mode is the per-app egress policy, enforced as a CSP:
|
|
1617
|
+
open No CSP — the app may fetch any origin.
|
|
1618
|
+
restricted External calls only via the Railcode proxy (connect-src 'self').
|
|
1619
|
+
sandboxed Same-origin only — no inline <script>, no external CDN/fonts.
|
|
1620
|
+
|
|
1621
|
+
Admins set the default and flip individual apps from the admin console; the CLI
|
|
1622
|
+
only reports the default so you know how to build a new app.
|
|
1623
|
+
`;
|
|
1624
|
+
const NETWORK_MODE_BUILD_HINTS = {
|
|
1625
|
+
open: "Build freely: direct fetch() to any origin is allowed.",
|
|
1626
|
+
restricted: "Don't fetch external origins directly — route them through the Railcode proxy. Inline scripts and CDN assets are fine.",
|
|
1627
|
+
sandboxed: "Same-origin only: no external fetch, no inline <script>, no external CDN/font links. Use the SDK and bundle every asset locally.",
|
|
1628
|
+
};
|
|
1629
|
+
async function commandNetwork(args) {
|
|
1630
|
+
if (booleanOption(args, "help") || args.rest[0] === "help") {
|
|
1631
|
+
console.log(NETWORK_HELP);
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
if (args.rest.length > 0) {
|
|
1635
|
+
throw new CliError("railcode network shows the platform default. Admins flip individual apps from the admin console.", 2);
|
|
1636
|
+
}
|
|
1637
|
+
rejectUnknownOptions(args, ["help", "json", "apiUrl"], "network");
|
|
1638
|
+
const config = loadConfig();
|
|
1639
|
+
const manifestApiUrl = readRailcodeManifest(process.cwd())?.manifest.deploy?.apiUrl;
|
|
1640
|
+
const authUrl = await resolveRequiredAuthOrigin({
|
|
1641
|
+
config,
|
|
1642
|
+
args,
|
|
1643
|
+
action: "Network mode",
|
|
1644
|
+
fallbackApiUrl: manifestApiUrl,
|
|
1645
|
+
missingUrlHint: "Pass --api-url <url>, set deploy.apiUrl in railcode.json, or set RAILCODE_API_URL.",
|
|
1646
|
+
});
|
|
1647
|
+
const apiUrl = canonicalApiOrigin(authUrl);
|
|
1648
|
+
let authedConfig = await ensureApiToken({ ...config, apiUrl: authUrl }, authUrl, "Network mode");
|
|
1649
|
+
let response = await fetchNetworkDefault(apiUrl, authedConfig);
|
|
1650
|
+
if (response.status === 401 && !process.env.RAILCODE_API_TOKEN && process.stdin.isTTY) {
|
|
1651
|
+
console.log("Saved login expired. Log in again to continue.");
|
|
1652
|
+
const retryConfig = { ...authedConfig, apiUrl: authUrl };
|
|
1653
|
+
delete retryConfig.apiToken;
|
|
1654
|
+
delete retryConfig.apiTokenPrefix;
|
|
1655
|
+
delete retryConfig.cookies;
|
|
1656
|
+
saveConfig(retryConfig);
|
|
1657
|
+
authedConfig = await ensureApiToken(retryConfig, authUrl, "Network mode");
|
|
1658
|
+
response = await fetchNetworkDefault(apiUrl, authedConfig);
|
|
1659
|
+
}
|
|
1660
|
+
const view = (await readDbResponse(response, "Network mode"));
|
|
1661
|
+
printNetworkDefault(view, booleanOption(args, "json"));
|
|
1662
|
+
}
|
|
1663
|
+
function fetchNetworkDefault(apiUrl, config) {
|
|
1664
|
+
return dbFetch(apiUrl, config, "/v1/network-default", { redirect: "manual" });
|
|
1665
|
+
}
|
|
1666
|
+
function printNetworkDefault(view, json) {
|
|
1667
|
+
if (json) {
|
|
1668
|
+
console.log(JSON.stringify(view, null, 2));
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
const mode = view.network_mode || "restricted";
|
|
1672
|
+
console.log(`Platform default network mode: ${mode}`);
|
|
1673
|
+
const hint = NETWORK_MODE_BUILD_HINTS[mode];
|
|
1674
|
+
if (hint)
|
|
1675
|
+
console.log(hint);
|
|
1676
|
+
}
|
|
1602
1677
|
const DB_ENGINES = new Set(["postgres", "mysql"]);
|
|
1603
1678
|
const DB_HELP = `Query Railcode data connectors.
|
|
1604
1679
|
|
|
@@ -1836,6 +1911,137 @@ function printSqlResult(result, asJson) {
|
|
|
1836
1911
|
const count = typeof result.rowcount === "number" ? result.rowcount : rows.length;
|
|
1837
1912
|
console.log(`${columns.length > 0 ? "\n" : ""}${count} ${count === 1 ? "row" : "rows"}${result.truncated ? " (truncated to first 1000)" : ""}`);
|
|
1838
1913
|
}
|
|
1914
|
+
const CONNECTOR_HELP = `Call admin-configured HTTP service connectors (Stripe, PostHog, …).
|
|
1915
|
+
|
|
1916
|
+
Usage:
|
|
1917
|
+
railcode connector list
|
|
1918
|
+
railcode connector fetch <name> <path> [options]
|
|
1919
|
+
|
|
1920
|
+
Commands:
|
|
1921
|
+
list List service connectors you can call (name + auth type).
|
|
1922
|
+
fetch <name> <path> Proxy an HTTP request through a connector.
|
|
1923
|
+
|
|
1924
|
+
Fetch options:
|
|
1925
|
+
--method <verb> HTTP method (default GET). Must be allowed by the connector.
|
|
1926
|
+
--body <data> Raw request body (the connector sets the Content-Type).
|
|
1927
|
+
--file <path> Read the request body from a file instead.
|
|
1928
|
+
--json Print the raw JSON envelope instead of the body.
|
|
1929
|
+
|
|
1930
|
+
Options:
|
|
1931
|
+
--api-url <url> Railcode auth/API URL. Defaults to saved config or railcode.json.
|
|
1932
|
+
|
|
1933
|
+
Examples:
|
|
1934
|
+
railcode connector list
|
|
1935
|
+
railcode connector fetch stripe /v1/charges
|
|
1936
|
+
railcode connector fetch stripe /v1/charges --method POST --body "amount=100¤cy=usd"
|
|
1937
|
+
`;
|
|
1938
|
+
async function commandConnector(args) {
|
|
1939
|
+
const subcommand = args.rest[0];
|
|
1940
|
+
const rest = { ...args, rest: args.rest.slice(1) };
|
|
1941
|
+
switch (subcommand) {
|
|
1942
|
+
case "list":
|
|
1943
|
+
case "ls":
|
|
1944
|
+
await commandConnectorList(rest);
|
|
1945
|
+
return;
|
|
1946
|
+
case "fetch":
|
|
1947
|
+
case "request":
|
|
1948
|
+
await commandConnectorFetch(rest);
|
|
1949
|
+
return;
|
|
1950
|
+
case undefined:
|
|
1951
|
+
case "help":
|
|
1952
|
+
console.log(CONNECTOR_HELP);
|
|
1953
|
+
return;
|
|
1954
|
+
default:
|
|
1955
|
+
if (booleanOption(args, "help")) {
|
|
1956
|
+
console.log(CONNECTOR_HELP);
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
throw new CliError(`Unknown connector command: ${subcommand}\n\n${CONNECTOR_HELP}`, 2);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
async function commandConnectorList(args) {
|
|
1963
|
+
if (booleanOption(args, "help") || args.rest[0] === "help") {
|
|
1964
|
+
console.log(CONNECTOR_HELP);
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
rejectUnknownOptions(args, ["help", "json", "apiUrl"], "connector list");
|
|
1968
|
+
if (args.rest.length > 0) {
|
|
1969
|
+
throw new CliError("railcode connector list does not accept positional arguments.", 2);
|
|
1970
|
+
}
|
|
1971
|
+
const response = await dbAuthedRequest(args, "Service connectors list", fetchServiceConnectors);
|
|
1972
|
+
const connectors = (await readDbResponse(response, "Service connectors list"));
|
|
1973
|
+
printServiceConnectors(connectors, booleanOption(args, "json"));
|
|
1974
|
+
}
|
|
1975
|
+
async function commandConnectorFetch(args) {
|
|
1976
|
+
if (booleanOption(args, "help") || args.rest[0] === "help") {
|
|
1977
|
+
console.log(CONNECTOR_HELP);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
rejectUnknownOptions(args, ["help", "json", "apiUrl", "method", "body", "file"], "connector fetch");
|
|
1981
|
+
const name = args.rest[0];
|
|
1982
|
+
const path = args.rest[1];
|
|
1983
|
+
if (!name || !path) {
|
|
1984
|
+
throw new CliError("Usage: railcode connector fetch <name> <path> [--method <verb>] [--body <data>].", 2);
|
|
1985
|
+
}
|
|
1986
|
+
if (args.rest.length > 2) {
|
|
1987
|
+
throw new CliError("railcode connector fetch takes a single connector name and path.", 2);
|
|
1988
|
+
}
|
|
1989
|
+
if (!path.startsWith("/")) {
|
|
1990
|
+
throw new CliError("The connector path must start with '/'.", 2);
|
|
1991
|
+
}
|
|
1992
|
+
const method = (stringOption(args, "method") || "GET").trim().toUpperCase();
|
|
1993
|
+
const body = resolveConnectorBody(args);
|
|
1994
|
+
const requestBody = { connector: name, method, path, body };
|
|
1995
|
+
const response = await dbAuthedRequest(args, "Connector request", (apiUrl, config) => postConnectorRequest(apiUrl, config, requestBody));
|
|
1996
|
+
const result = (await readDbResponse(response, "Connector request", "Run `railcode connector list` to see connectors you can call."));
|
|
1997
|
+
printConnectorResponse(result, booleanOption(args, "json"));
|
|
1998
|
+
}
|
|
1999
|
+
function resolveConnectorBody(args) {
|
|
2000
|
+
const file = stringOption(args, "file");
|
|
2001
|
+
const inline = stringOption(args, "body");
|
|
2002
|
+
if (file && inline !== undefined) {
|
|
2003
|
+
throw new CliError("Pass --body or --file, not both.", 2);
|
|
2004
|
+
}
|
|
2005
|
+
if (file) {
|
|
2006
|
+
const path = resolve(file);
|
|
2007
|
+
if (!existsSync(path))
|
|
2008
|
+
throw new CliError(`Body file not found: ${path}`, 1);
|
|
2009
|
+
return readFileSync(path, "utf8");
|
|
2010
|
+
}
|
|
2011
|
+
return inline;
|
|
2012
|
+
}
|
|
2013
|
+
function fetchServiceConnectors(apiUrl, config) {
|
|
2014
|
+
return dbFetch(apiUrl, config, "/v1/service-connectors", { redirect: "manual" });
|
|
2015
|
+
}
|
|
2016
|
+
function postConnectorRequest(apiUrl, config, body) {
|
|
2017
|
+
return dbFetch(apiUrl, config, "/v1/service-connectors/request", {
|
|
2018
|
+
method: "POST",
|
|
2019
|
+
headers: { "Content-Type": "application/json" },
|
|
2020
|
+
body: JSON.stringify(body),
|
|
2021
|
+
redirect: "manual",
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
function printServiceConnectors(connectors, asJson) {
|
|
2025
|
+
if (asJson) {
|
|
2026
|
+
console.log(JSON.stringify(connectors, null, 2));
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
if (!connectors || connectors.length === 0) {
|
|
2030
|
+
console.log("No service connectors available.");
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
console.log(formatTable(["NAME", "AUTH", "METHODS"], connectors.map((c) => [c.name, c.auth_type ?? "none", (c.allowed_methods ?? []).join(",")])));
|
|
2034
|
+
}
|
|
2035
|
+
function printConnectorResponse(result, asJson) {
|
|
2036
|
+
if (asJson) {
|
|
2037
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
const status = result.status ?? 0;
|
|
2041
|
+
console.log(`${status} ${result.ok ? "OK" : "ERROR"}${result.truncated ? " (truncated)" : ""}`);
|
|
2042
|
+
if (result.body)
|
|
2043
|
+
console.log(result.body);
|
|
2044
|
+
}
|
|
1839
2045
|
function rejectUnknownOptions(args, allowed, label) {
|
|
1840
2046
|
const set = new Set(allowed);
|
|
1841
2047
|
const unknown = Object.keys(args.options).filter((option) => !set.has(option));
|
package/package.json
CHANGED
package/static/sdk.js
CHANGED
|
@@ -371,6 +371,39 @@
|
|
|
371
371
|
var dataConnectors = () => track("connections", "dataConnectors()", () => call("GET", "/connections"));
|
|
372
372
|
var databaseConnectors = dataConnectors;
|
|
373
373
|
|
|
374
|
+
// src/service-connectors.ts
|
|
375
|
+
var PATH_LABEL_MAX = 60;
|
|
376
|
+
var toResponse = (env) => {
|
|
377
|
+
const body = env.body ?? "";
|
|
378
|
+
return {
|
|
379
|
+
status: env.status,
|
|
380
|
+
ok: env.ok,
|
|
381
|
+
headers: env.headers || {},
|
|
382
|
+
truncated: Boolean(env.truncated),
|
|
383
|
+
text: () => Promise.resolve(body),
|
|
384
|
+
json: () => Promise.resolve(JSON.parse(body))
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
var request = (name, path, opts) => {
|
|
388
|
+
const method = (opts?.method || "GET").toUpperCase();
|
|
389
|
+
const label = `connector(${q(name)}).fetch(${q(method)} ${q(shorten(path, PATH_LABEL_MAX))})`;
|
|
390
|
+
return track(
|
|
391
|
+
"connector",
|
|
392
|
+
label,
|
|
393
|
+
() => call("POST", "/service-connectors/request", {
|
|
394
|
+
json: { connector: name, method, path, body: opts?.body }
|
|
395
|
+
}).then(toResponse)
|
|
396
|
+
);
|
|
397
|
+
};
|
|
398
|
+
var connector = (name) => ({
|
|
399
|
+
fetch: (path, opts) => request(name, path, opts)
|
|
400
|
+
});
|
|
401
|
+
var serviceConnectors = () => track(
|
|
402
|
+
"service-connectors",
|
|
403
|
+
"serviceConnectors()",
|
|
404
|
+
() => call("GET", "/service-connectors")
|
|
405
|
+
);
|
|
406
|
+
|
|
374
407
|
// src/design-system.ts
|
|
375
408
|
var designSystem = () => track("design-system", "designSystem()", async () => {
|
|
376
409
|
const response = await call("GET", "/config/design-system");
|
|
@@ -437,6 +470,8 @@
|
|
|
437
470
|
window.mysql = mysql;
|
|
438
471
|
window.dataConnectors = dataConnectors;
|
|
439
472
|
window.databaseConnectors = databaseConnectors;
|
|
473
|
+
window.connector = connector;
|
|
474
|
+
window.serviceConnectors = serviceConnectors;
|
|
440
475
|
window.designSystem = designSystem;
|
|
441
476
|
window.me = me;
|
|
442
477
|
window.appUsers = appUsers;
|