railcode 0.1.10 → 0.1.11
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/README.md +1 -1
- package/dist/connectors.js +62 -8
- package/dist/index.js +51 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ railcode deploy Build (if configured) and deploy the app here
|
|
|
23
23
|
railcode db <list|query> ... List data connectors / run read-only SQL
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
- **`login`** prompts for the server URL (default `
|
|
26
|
+
- **`login`** prompts for the server URL (default `https://api.railcode.app`),
|
|
27
27
|
email, and password. It exchanges credentials for a short-lived JWT, mints a
|
|
28
28
|
personal API token, resolves your organization, and saves everything to
|
|
29
29
|
`~/.railcode/config.json` (dir `0700`, file `0600`). The JWT is used once and
|
package/dist/connectors.js
CHANGED
|
@@ -5,20 +5,74 @@
|
|
|
5
5
|
import { formatTable } from "./db.js";
|
|
6
6
|
export class ConnectorError extends Error {
|
|
7
7
|
}
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
8
|
+
// What goes in the optional `docs` column: which kinds of documentation a connector
|
|
9
|
+
// exposes (pull the content with `railcode connector docs <name>`).
|
|
10
|
+
function docsCell(c) {
|
|
11
|
+
const parts = [];
|
|
12
|
+
if (c.has_docs)
|
|
13
|
+
parts.push("api");
|
|
14
|
+
if (c.has_openapi)
|
|
15
|
+
parts.push("openapi");
|
|
16
|
+
return parts.join(", ");
|
|
17
|
+
}
|
|
18
|
+
// Render the connector list as a table. The `description` and `docs` columns are each
|
|
19
|
+
// only added when at least one connector actually has that, so the common (bare) case
|
|
20
|
+
// stays narrow.
|
|
11
21
|
export function connectorTable(connectors) {
|
|
12
22
|
const hasDescription = connectors.some((c) => (c.description ?? "").length > 0);
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
23
|
+
const hasDocs = connectors.some((c) => c.has_docs || c.has_openapi);
|
|
24
|
+
const columns = ["name", "auth_type", "allowed_methods"];
|
|
25
|
+
if (hasDescription)
|
|
26
|
+
columns.push("description");
|
|
27
|
+
if (hasDocs)
|
|
28
|
+
columns.push("docs");
|
|
16
29
|
const rows = connectors.map((c) => {
|
|
17
|
-
const
|
|
18
|
-
|
|
30
|
+
const row = [c.name, c.auth_type, (c.allowed_methods ?? []).join(", ")];
|
|
31
|
+
if (hasDescription)
|
|
32
|
+
row.push(c.description ?? "");
|
|
33
|
+
if (hasDocs)
|
|
34
|
+
row.push(docsCell(c));
|
|
35
|
+
return row;
|
|
19
36
|
});
|
|
20
37
|
return formatTable(columns, rows);
|
|
21
38
|
}
|
|
39
|
+
// What `--openapi` emits for one connector: the inline spec verbatim if present,
|
|
40
|
+
// otherwise the spec URL, otherwise null (no spec configured).
|
|
41
|
+
export function connectorOpenapiOutput(docs) {
|
|
42
|
+
if (docs.openapi_spec_inline)
|
|
43
|
+
return docs.openapi_spec_inline;
|
|
44
|
+
if (docs.openapi_spec_link)
|
|
45
|
+
return docs.openapi_spec_link;
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
// Human render of a connector's docs bundle: identity line, then usage instructions,
|
|
49
|
+
// API docs (link or inline), and the OpenAPI spec (link or inline) when present.
|
|
50
|
+
export function renderConnectorDocs(docs) {
|
|
51
|
+
const methods = (docs.allowed_methods ?? []).join(", ") || "(all)";
|
|
52
|
+
const sections = [`${docs.name} (${docs.auth_type}) — methods: ${methods}`];
|
|
53
|
+
if (docs.description)
|
|
54
|
+
sections.push(docs.description);
|
|
55
|
+
const hasAny = docs.usage_instructions ||
|
|
56
|
+
docs.api_docs_link ||
|
|
57
|
+
docs.api_docs_inline ||
|
|
58
|
+
docs.openapi_spec_link ||
|
|
59
|
+
docs.openapi_spec_inline;
|
|
60
|
+
if (!hasAny) {
|
|
61
|
+
sections.push(`No documentation is configured for connector "${docs.name}".`);
|
|
62
|
+
return sections.join("\n\n");
|
|
63
|
+
}
|
|
64
|
+
if (docs.usage_instructions)
|
|
65
|
+
sections.push(`Usage instructions:\n${docs.usage_instructions}`);
|
|
66
|
+
if (docs.api_docs_link)
|
|
67
|
+
sections.push(`API docs: ${docs.api_docs_link}`);
|
|
68
|
+
else if (docs.api_docs_inline)
|
|
69
|
+
sections.push(`API docs (inline):\n${docs.api_docs_inline}`);
|
|
70
|
+
if (docs.openapi_spec_link)
|
|
71
|
+
sections.push(`OpenAPI spec: ${docs.openapi_spec_link}`);
|
|
72
|
+
else if (docs.openapi_spec_inline)
|
|
73
|
+
sections.push(`OpenAPI spec (inline):\n${docs.openapi_spec_inline}`);
|
|
74
|
+
return sections.join("\n\n");
|
|
75
|
+
}
|
|
22
76
|
// `--connector <name>` is required for `fetch`. Reject a missing value or a bare
|
|
23
77
|
// flag with a usage message.
|
|
24
78
|
export function parseConnectorOption(raw) {
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
12
12
|
import { fileURLToPath } from "node:url";
|
|
13
13
|
import { KvQueryError, kvCount, kvQuery, } from "./dev/kv-query.js";
|
|
14
14
|
import { DbError, formatTable, inferEngine, parseEngine, parseSqlParams, pickSqlSource, } from "./db.js";
|
|
15
|
-
import { ConnectorError, buildConnectorRequest, connectorTable, parseConnectorOption, parseMethodOption, pickConnectorBody, proxyExitCode, renderProxyResponse, } from "./connectors.js";
|
|
15
|
+
import { ConnectorError, buildConnectorRequest, connectorOpenapiOutput, connectorTable, parseConnectorOption, parseMethodOption, pickConnectorBody, proxyExitCode, renderConnectorDocs, renderProxyResponse, } from "./connectors.js";
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
// Constants
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
@@ -20,7 +20,7 @@ const RAILCODE_HOME = process.env.RAILCODE_HOME || join(homedir(), ".railcode");
|
|
|
20
20
|
const CONFIG_PATH = join(RAILCODE_HOME, "config.json");
|
|
21
21
|
const CLI_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
22
22
|
const VERSION = readCliPackageVersion();
|
|
23
|
-
const DEFAULT_API_URL = "
|
|
23
|
+
const DEFAULT_API_URL = "https://api.railcode.app";
|
|
24
24
|
const MANIFEST_NAME = "railcode.json";
|
|
25
25
|
const CLI_AUTH_CALLBACK_PATH = "/railcode-cli/callback";
|
|
26
26
|
const CLI_AUTH_TIMEOUT_MS = 5 * 60 * 1000;
|
|
@@ -35,7 +35,7 @@ Usage:
|
|
|
35
35
|
railcode init <app> [--template static|react]
|
|
36
36
|
railcode design-system Print your org's design-system guidance (markdown)
|
|
37
37
|
railcode db <list|query> ... List data connectors / run read-only SQL
|
|
38
|
-
railcode connector <list|fetch> ... List
|
|
38
|
+
railcode connector <list|docs|fetch> ... List connectors / show API docs / proxy a call
|
|
39
39
|
railcode --version
|
|
40
40
|
railcode --help
|
|
41
41
|
|
|
@@ -951,14 +951,19 @@ const CONNECTOR_HELP = `railcode connector — service connectors
|
|
|
951
951
|
|
|
952
952
|
Usage:
|
|
953
953
|
railcode connector list List this org's service connectors
|
|
954
|
+
railcode connector docs <name> [opts] Show how to call a connector's API
|
|
954
955
|
railcode connector fetch <path> --connector <name> [opts]
|
|
955
956
|
Proxy one HTTP call through a connector
|
|
956
957
|
|
|
957
|
-
Aliases: connector = connectors; list = ls, connectors; fetch = request.
|
|
958
|
+
Aliases: connector = connectors; list = ls, connectors; docs = doc; fetch = request.
|
|
958
959
|
|
|
959
960
|
List options:
|
|
960
961
|
--json Print the raw connector array
|
|
961
962
|
|
|
963
|
+
Docs options:
|
|
964
|
+
--json Print the raw docs object
|
|
965
|
+
--openapi Print only the OpenAPI spec (inline text, else its URL)
|
|
966
|
+
|
|
962
967
|
Fetch options:
|
|
963
968
|
--connector <name> Connector to call (required)
|
|
964
969
|
--method <verb> HTTP method (default GET; must be allowed by the connector)
|
|
@@ -979,6 +984,10 @@ async function commandConnector(args) {
|
|
|
979
984
|
case "connectors":
|
|
980
985
|
await commandConnectorList(args);
|
|
981
986
|
return;
|
|
987
|
+
case "docs":
|
|
988
|
+
case "doc":
|
|
989
|
+
await commandConnectorDocs(args);
|
|
990
|
+
return;
|
|
982
991
|
case "fetch":
|
|
983
992
|
case "request":
|
|
984
993
|
await commandConnectorFetch(args);
|
|
@@ -1043,12 +1052,50 @@ async function commandConnectorFetch(args) {
|
|
|
1043
1052
|
// it, but exit non-zero so scripts can tell success from failure.
|
|
1044
1053
|
process.exitCode = proxyExitCode(envelope);
|
|
1045
1054
|
}
|
|
1055
|
+
async function commandConnectorDocs(args) {
|
|
1056
|
+
assertKnownConnectorOptions(args, ["json", "openapi", "apiUrl"]);
|
|
1057
|
+
const name = args.rest[1];
|
|
1058
|
+
if (!name) {
|
|
1059
|
+
throw new CliError("Usage: railcode connector docs <name>. Run `railcode connector list` to see names.", 2);
|
|
1060
|
+
}
|
|
1061
|
+
if (args.rest.length > 2) {
|
|
1062
|
+
throw new CliError(`railcode connector docs takes a single connector name (got "${args.rest[2]}").`, 2);
|
|
1063
|
+
}
|
|
1064
|
+
if (args.options.json && args.options.openapi) {
|
|
1065
|
+
throw new CliError("Pass either --json or --openapi, not both.", 2);
|
|
1066
|
+
}
|
|
1067
|
+
const config = requireLogin(args);
|
|
1068
|
+
const app = await resolveExistingApp(config, requireAppSlug());
|
|
1069
|
+
const docs = await fetchServiceConnectorDocs(config, app.uuid, name);
|
|
1070
|
+
if (args.options.json) {
|
|
1071
|
+
console.log(JSON.stringify(docs, null, 2));
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
if (args.options.openapi) {
|
|
1075
|
+
const spec = connectorOpenapiOutput(docs);
|
|
1076
|
+
if (spec === null) {
|
|
1077
|
+
throw new CliError(`Connector "${name}" has no OpenAPI spec.`, 1);
|
|
1078
|
+
}
|
|
1079
|
+
console.log(spec);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
console.log(renderConnectorDocs(docs));
|
|
1083
|
+
}
|
|
1046
1084
|
async function fetchServiceConnectors(config, appUuid) {
|
|
1047
1085
|
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/apps/${appUuid}/service-connectors`);
|
|
1048
1086
|
if (resp.status === 401)
|
|
1049
1087
|
await reLoginOrThrow();
|
|
1050
1088
|
return (await readJsonOrThrow(resp, "List service connectors"));
|
|
1051
1089
|
}
|
|
1090
|
+
async function fetchServiceConnectorDocs(config, appUuid, name) {
|
|
1091
|
+
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/apps/${appUuid}/service-connectors/${encodeURIComponent(name)}`);
|
|
1092
|
+
if (resp.status === 401)
|
|
1093
|
+
await reLoginOrThrow();
|
|
1094
|
+
if (resp.status === 404) {
|
|
1095
|
+
throw new CliError(`Connector "${name}" not found (or not enabled) in your org. Run \`railcode connector list\`.`, 1);
|
|
1096
|
+
}
|
|
1097
|
+
return (await readJsonOrThrow(resp, "Connector docs"));
|
|
1098
|
+
}
|
|
1052
1099
|
async function runConnectorRequest(config, appUuid, body) {
|
|
1053
1100
|
const resp = await authedFetch(config, `/api/organizations/${config.orgUuid}/apps/${appUuid}/service-connectors/request`, {
|
|
1054
1101
|
method: "POST",
|