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/README.md +44 -7
- package/dist/app-storage.js +154 -0
- package/dist/index.js +866 -67
- package/dist/manifest.js +2 -2
- package/dist/orgadmin.js +20 -8
- package/dist/update.js +112 -0
- package/package.json +4 -1
- package/static/sdk.js +752 -51
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-
|
|
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:
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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.
|
|
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
|
}
|