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 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 `http://api.127.0.0.1.nip.io`),
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
@@ -5,20 +5,74 @@
5
5
  import { formatTable } from "./db.js";
6
6
  export class ConnectorError extends Error {
7
7
  }
8
- // Render the connector list as a table. The `description` column is only added
9
- // when at least one connector actually has one, so the common (description-less)
10
- // case stays narrow.
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 columns = hasDescription
14
- ? ["name", "auth_type", "allowed_methods", "description"]
15
- : ["name", "auth_type", "allowed_methods"];
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 base = [c.name, c.auth_type, (c.allowed_methods ?? []).join(", ")];
18
- return hasDescription ? [...base, c.description ?? ""] : base;
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 = "http://api.127.0.0.1.nip.io";
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 service connectors / proxy one call
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "railcode",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Developer CLI for the multi-tenant Railcode platform: log in, scaffold, and deploy static apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",