railcode 0.1.26 → 0.1.28

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/manifest.js CHANGED
@@ -48,7 +48,7 @@ const SAVED_QUERY_NAME = /^[a-z0-9_]{1,80}$/;
48
48
  // Mirrors the server's _PERSONAL_CONNECTOR_RE exactly. The server is
49
49
  // authoritative; this exists so `railcode deploy` can reject a bad manifest
50
50
  // locally instead of after a round trip.
51
- const PERSONAL_CONNECTOR = /^[a-z0-9][a-z0-9_]{0,63}(?::(?:\*|[A-Z0-9_]{1,120}))?$/;
51
+ const PERSONAL_CONNECTOR = /^[a-z0-9][a-z0-9_]{0,63}(?::(?:\*|[A-Za-z0-9_]{1,120}))?$/;
52
52
  // PyYAML's YAML 1.1 plain-scalar resolver: a bare (unquoted) scalar matching one
53
53
  // of these resolves to a non-string (bool/null/int/float/timestamp), and the
54
54
  // server rejects a non-string wherever the manifest wants a name. The quirks are
@@ -653,7 +653,7 @@ function validateDoc(doc, lines) {
653
653
  doc.agents = [...new Set(doc.agents)].sort();
654
654
  for (const entry of doc.personal_connectors) {
655
655
  if (!PERSONAL_CONNECTOR.test(entry)) {
656
- throw new ManifestParseError(`"${entry}" is not a valid personal_connectors entry — use "gmail", "gmail:*", or "gmail:GMAIL_SEND_EMAIL"`, lineOf(entry));
656
+ throw new ManifestParseError(`"${entry}" is not a valid personal_connectors entry — use "gmail", "gmail:*", or "gmail:send_email"`, lineOf(entry));
657
657
  }
658
658
  }
659
659
  doc.personal_connectors = [...new Set(doc.personal_connectors)].sort();
package/dist/orgadmin.js CHANGED
@@ -178,14 +178,26 @@ export function renderEffective(eff, who) {
178
178
  return lines.join("\n");
179
179
  }
180
180
  export function appTable(apps) {
181
- return formatTable(["slug", "name", "status", "access", "your_role", "manage"], apps.map((a) => [
182
- a.app_slug,
183
- a.name,
184
- a.status,
185
- a.access_mode,
186
- a.your_role ?? "-",
187
- a.can_manage ? "yes" : "no",
188
- ]));
181
+ // The `archived` column only appears when something in the list actually IS
182
+ // archived (i.e. `apps list --archived/--all`), so the default listing is
183
+ // unchanged for the many people who never archive anything.
184
+ const showArchived = apps.some((a) => a.archived_at);
185
+ const columns = ["slug", "name", "status", "access", "your_role", "manage"];
186
+ if (showArchived)
187
+ columns.push("archived");
188
+ return formatTable(columns, apps.map((a) => {
189
+ const row = [
190
+ a.app_slug,
191
+ a.name,
192
+ a.status,
193
+ a.access_mode,
194
+ a.your_role ?? "-",
195
+ a.can_manage ? "yes" : "no",
196
+ ];
197
+ if (showArchived)
198
+ row.push(a.archived_at ? "yes" : "-");
199
+ return row;
200
+ }));
189
201
  }
190
202
  // Resolve an app reference — a slug (preferred) or a UUID — against a fetched app
191
203
  // list.
package/dist/update.js ADDED
@@ -0,0 +1,112 @@
1
+ // Self-update support for the `railcode` CLI.
2
+ //
3
+ // Every command run (for a human at an interactive terminal) checks — at most
4
+ // once every 6 hours — whether a newer version is published on the npm registry
5
+ // *within the current major* and, if so, installs it globally and tells the
6
+ // user. The throttle timestamp lives in `~/.railcode/update-check.json`.
7
+ //
8
+ // This file holds only the pure, side-effect-free helpers (version math, the
9
+ // throttle predicate, installer detection) plus the registry fetch, so they can
10
+ // be unit-tested in isolation. The orchestration that reads/writes the state
11
+ // file and spawns the installer lives in `index.ts`.
12
+ export const UPDATE_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
13
+ // Parse a `MAJOR.MINOR.PATCH[-prerelease][+build]` string (a leading `v` is
14
+ // tolerated). Returns null for anything that isn't a plain release triple.
15
+ export function parseSemver(input) {
16
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(input.trim());
17
+ if (!match)
18
+ return null;
19
+ return {
20
+ major: Number(match[1]),
21
+ minor: Number(match[2]),
22
+ patch: Number(match[3]),
23
+ prerelease: match[4] ?? null,
24
+ };
25
+ }
26
+ // Compare release precedence by (major, minor, patch). Prerelease ordering is
27
+ // intentionally ignored — we exclude prereleases from update candidates.
28
+ export function compareSemver(a, b) {
29
+ if (a.major !== b.major)
30
+ return a.major - b.major;
31
+ if (a.minor !== b.minor)
32
+ return a.minor - b.minor;
33
+ return a.patch - b.patch;
34
+ }
35
+ // From all published versions, pick the highest stable release that shares the
36
+ // current major and is strictly newer than it. Prereleases and other majors are
37
+ // skipped, so we never auto-jump across a breaking major boundary. Returns the
38
+ // raw version string to install, or null when already up to date.
39
+ export function pickLatestSameMajor(current, versions) {
40
+ const cur = parseSemver(current);
41
+ if (!cur)
42
+ return null;
43
+ let best = null;
44
+ for (const raw of versions) {
45
+ const sv = parseSemver(raw);
46
+ if (!sv)
47
+ continue;
48
+ if (sv.prerelease)
49
+ continue; // stable releases only
50
+ if (sv.major !== cur.major)
51
+ continue; // stay within the current major
52
+ if (compareSemver(sv, cur) <= 0)
53
+ continue; // must be strictly newer
54
+ if (!best || compareSemver(sv, best.sv) > 0)
55
+ best = { raw, sv };
56
+ }
57
+ return best ? best.raw : null;
58
+ }
59
+ // Decide whether enough time has passed to check again. Missing or unparseable
60
+ // timestamps always check (and get rewritten with a valid one).
61
+ export function shouldCheckForUpdate(lastCheckedIso, nowMs, intervalMs = UPDATE_CHECK_INTERVAL_MS) {
62
+ if (!lastCheckedIso)
63
+ return true;
64
+ const last = Date.parse(lastCheckedIso);
65
+ if (Number.isNaN(last))
66
+ return true;
67
+ return nowMs - last >= intervalMs;
68
+ }
69
+ // Best-effort detection of the global package manager that owns this install, so
70
+ // the self-update uses the same tool the user installed with. We look at the
71
+ // install path first, then the `npm_config_user_agent` a manager sets when it
72
+ // spawns a script. Defaults to npm (the documented install path).
73
+ export function detectGlobalInstaller(installPath, userAgent) {
74
+ const path = installPath.replace(/\\/g, "/").toLowerCase();
75
+ const ua = (userAgent ?? "").toLowerCase();
76
+ if (ua.startsWith("pnpm") || /\/\.?pnpm\//.test(path)) {
77
+ return { manager: "pnpm", command: "pnpm", args: (spec) => ["add", "-g", spec] };
78
+ }
79
+ if (ua.startsWith("yarn") || /\/\.?yarn\//.test(path)) {
80
+ return { manager: "yarn", command: "yarn", args: (spec) => ["global", "add", spec] };
81
+ }
82
+ if (ua.startsWith("bun") || /\/\.bun\//.test(path)) {
83
+ return { manager: "bun", command: "bun", args: (spec) => ["add", "-g", spec] };
84
+ }
85
+ return { manager: "npm", command: "npm", args: (spec) => ["install", "-g", spec] };
86
+ }
87
+ // Fetch every published version of `pkg` from an npm-compatible registry. Uses
88
+ // the abbreviated packument (much smaller than the full doc) and a hard timeout,
89
+ // and returns [] on any failure — the caller treats "couldn't check" as "no
90
+ // update", never an error.
91
+ export async function fetchPackageVersions(registryBase, pkg, timeoutMs = 4000) {
92
+ const base = registryBase.replace(/\/+$/, "");
93
+ const url = `${base}/${encodeURIComponent(pkg).replace(/^%40/, "@")}`;
94
+ const controller = new AbortController();
95
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
96
+ try {
97
+ const res = await fetch(url, {
98
+ headers: { accept: "application/vnd.npm.install-v1+json" },
99
+ signal: controller.signal,
100
+ });
101
+ if (!res.ok)
102
+ return [];
103
+ const body = (await res.json());
104
+ return body.versions ? Object.keys(body.versions) : [];
105
+ }
106
+ catch {
107
+ return [];
108
+ }
109
+ finally {
110
+ clearTimeout(timer);
111
+ }
112
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "railcode",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
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",
@@ -27,5 +27,8 @@
27
27
  "devDependencies": {
28
28
  "@types/node": "^24.0.0",
29
29
  "typescript": "^5.9.0"
30
+ },
31
+ "dependencies": {
32
+ "yaml": "^2.9.0"
30
33
  }
31
34
  }