propr-cli 0.8.4 → 0.8.5

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.
@@ -10,7 +10,7 @@ export { validateRelayUrl } from './validateRelayUrl.js';
10
10
  // Export the hosted propr-routing service default URLs (one source of truth for
11
11
  // the webhook.propr.dev host shared by the CLI, the daemon dialer, and the
12
12
  // boot/check prerequisite validators)
13
- export { DEFAULT_PROPR_ROUTING_URL, DEFAULT_PROPR_GH_RELAY_URL, } from './proprServiceUrls.js';
13
+ export { DEFAULT_PROPR_ROUTING_URL, DEFAULT_PROPR_GH_RELAY_URL, DEFAULT_PROPR_UI_ORIGIN, PROPR_UI_PROXY_SUFFIX, DEFAULT_CLOUDFLARED_IMAGE, proprInstanceProxyUrl, isValidProprInstanceId, isProprProxyUrl, proprTunnelEndpoints, } from './proprServiceUrls.js';
14
14
  // Export routing URL validation (shared by intake prerequisites and the daemon
15
15
  // routing service so the boot/CLI checks and the dialer agree on one policy)
16
16
  export { validateRoutingUrl } from './validateRoutingUrl.js';
@@ -25,6 +25,7 @@ export { validateIntakeModePrerequisites, } from './intakeModePrerequisites.js';
25
25
  // Export shared Redis status keys (one source of truth for cross-process status
26
26
  // keys so the daemon publisher, API status route, and CLI cannot drift)
27
27
  export { ROUTING_STATUS_REDIS_KEY } from './statusKeys.js';
28
+ export { PROPR_VERSION, PROPR_API_COMPATIBILITY, PROPR_UI_COMPATIBILITY, PROPR_UI_SUPPORTED_API_COMPATIBILITY, getProprCompatibilityMetadata, evaluateProprApiCompatibility, } from './proprCompatibility.js';
28
29
  export { shortHash, buildDynamicLlmLabel, MAX_GITHUB_LABEL_LENGTH } from './labelUtils.js';
29
30
  // Export the default review guidance (the overridable part of the /review prompt)
30
31
  export { DEFAULT_REVIEW_GUIDANCE } from './reviewPrompt.js';
@@ -0,0 +1,70 @@
1
+ // Public ProPR version surfaced to the hosted UI via `/api/compatibility`. This
2
+ // must track the release version. The shared package is bundled for the browser
3
+ // (no fs/JSON-import of package.json available within rootDir), so it is kept as
4
+ // a constant rather than read from package.json at runtime. A release bump that
5
+ // updates packages/shared/package.json or docker/launcher/manifest.json but
6
+ // forgets this constant is caught by the drift test in
7
+ // test/orchestratorProprUrlsDrift.test.ts, which asserts all three agree.
8
+ export const PROPR_VERSION = '0.8.5';
9
+ // Bump this only when the API/UI contract changes in a way the hosted UI must
10
+ // account for. Patch releases that do not change the browser-facing contract can
11
+ // keep the same compatibility version.
12
+ export const PROPR_API_COMPATIBILITY = '2026-06-27';
13
+ export const PROPR_UI_COMPATIBILITY = PROPR_API_COMPATIBILITY;
14
+ export const PROPR_UI_SUPPORTED_API_COMPATIBILITY = [PROPR_API_COMPATIBILITY];
15
+ export function getProprCompatibilityMetadata() {
16
+ return {
17
+ version: PROPR_VERSION,
18
+ apiCompatibility: PROPR_API_COMPATIBILITY,
19
+ uiCompatibility: PROPR_UI_COMPATIBILITY,
20
+ };
21
+ }
22
+ export function evaluateProprApiCompatibility(input) {
23
+ const apiCompatibility = input.apiCompatibility?.trim() || null;
24
+ const apiVersion = input.version?.trim() || null;
25
+ if (!apiCompatibility) {
26
+ return {
27
+ compatible: false,
28
+ apiCompatibility,
29
+ apiVersion,
30
+ reason: 'missing',
31
+ message: 'This ProPR instance does not publish API compatibility metadata. Update the local ProPR stack before using the hosted UI.',
32
+ };
33
+ }
34
+ if (PROPR_UI_SUPPORTED_API_COMPATIBILITY.includes(apiCompatibility)) {
35
+ return { compatible: true, apiCompatibility, apiVersion };
36
+ }
37
+ // PROPR_UI_SUPPORTED_API_COMPATIBILITY currently holds a single value, so
38
+ // oldest === newest and only the too_old / too_new branches below can fire (the
39
+ // final `unsupported` branch is unreachable today). These are forward-looking:
40
+ // once the UI supports a range with gaps, an in-range-but-unsupported value can
41
+ // occur and the `unsupported` branch covers it.
42
+ const oldestSupported = PROPR_UI_SUPPORTED_API_COMPATIBILITY[0];
43
+ const newestSupported = PROPR_UI_SUPPORTED_API_COMPATIBILITY[PROPR_UI_SUPPORTED_API_COMPATIBILITY.length - 1];
44
+ if (apiCompatibility < oldestSupported) {
45
+ return {
46
+ compatible: false,
47
+ apiCompatibility,
48
+ apiVersion,
49
+ reason: 'too_old',
50
+ message: `This ProPR instance is too old for the hosted UI. Update the local ProPR stack to API compatibility ${oldestSupported} or newer.`,
51
+ };
52
+ }
53
+ if (apiCompatibility > newestSupported) {
54
+ return {
55
+ compatible: false,
56
+ apiCompatibility,
57
+ apiVersion,
58
+ reason: 'too_new',
59
+ message: `This ProPR instance is newer than the hosted UI supports. Update the hosted UI or use the matching local UI for API compatibility ${apiCompatibility}.`,
60
+ };
61
+ }
62
+ return {
63
+ compatible: false,
64
+ apiCompatibility,
65
+ apiVersion,
66
+ reason: 'unsupported',
67
+ message: `This hosted UI supports API compatibility ${PROPR_UI_SUPPORTED_API_COMPATIBILITY.join(', ')}, but the local ProPR instance reports ${apiCompatibility}.`,
68
+ };
69
+ }
70
+ //# sourceMappingURL=proprCompatibility.js.map
@@ -24,4 +24,101 @@ export const DEFAULT_PROPR_ROUTING_URL = 'wss://webhook.propr.dev';
24
24
  * directly to this value.
25
25
  */
26
26
  export const DEFAULT_PROPR_GH_RELAY_URL = 'https://webhook.propr.dev/v1';
27
+ /**
28
+ * Origin of the hosted Propr UI (https://app.propr.dev). This is where the
29
+ * managed control plane is served from; a local stack exposes its own UI on a
30
+ * tunnel under {@link PROPR_UI_PROXY_SUFFIX} so the hosted UI can reach it.
31
+ */
32
+ export const DEFAULT_PROPR_UI_ORIGIN = 'https://app.propr.dev';
33
+ /**
34
+ * DNS suffix for per-instance UI/API tunnel hostnames. Each local stack with an
35
+ * instance id is reachable at `https://<instanceId>.proxy.propr.dev`, so the
36
+ * hosted UI at {@link DEFAULT_PROPR_UI_ORIGIN} can discover and address it.
37
+ */
38
+ export const PROPR_UI_PROXY_SUFFIX = 'proxy.propr.dev';
39
+ /**
40
+ * Default Cloudflare Tunnel image used to expose the local stack's UI/API to
41
+ * the hosted control plane when a UI tunnel is enabled. This is only a fallback:
42
+ * the launcher prefers the `cloudflared` entry pinned in the stack manifest
43
+ * (docker/launcher/manifest.json). Keep this tag in sync with that manifest pin
44
+ * so the effective default is the same regardless of which source supplies it.
45
+ */
46
+ export const DEFAULT_CLOUDFLARED_IMAGE = 'cloudflare/cloudflared:2024.12.2';
47
+ /**
48
+ * Whether an instance id is usable as a single DNS label in the per-instance
49
+ * proxy hostname (`<id>.proxy.propr.dev`). Enforces the standard label rules:
50
+ * 1–63 characters, ASCII letters/digits/hyphens only, and no leading or
51
+ * trailing hyphen. This rejects spaces, slashes, dots, underscores, and other
52
+ * characters that would produce an invalid or ambiguous hostname.
53
+ */
54
+ export function isValidProprInstanceId(instanceId) {
55
+ const id = (instanceId ?? '').trim();
56
+ return /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(id);
57
+ }
58
+ /**
59
+ * Derive the public API/UI URL for a local stack from its instance id, using
60
+ * the shared {@link PROPR_UI_PROXY_SUFFIX}. Returns `https://abc123.proxy.propr.dev`
61
+ * for instance id `abc123`. Returns `undefined` for a missing/blank id — or an
62
+ * id that is not a valid DNS label (see {@link isValidProprInstanceId}) — so
63
+ * callers can fall back to an explicit URL or a local-development default
64
+ * rather than emitting a malformed hostname. The id is lowercased so a
65
+ * mixed-case instance id yields a canonical hostname (DNS is case-insensitive).
66
+ */
67
+ export function proprInstanceProxyUrl(instanceId) {
68
+ const id = (instanceId ?? '').trim();
69
+ if (!isValidProprInstanceId(id))
70
+ return undefined;
71
+ return `https://${id.toLowerCase()}.${PROPR_UI_PROXY_SUFFIX}`;
72
+ }
73
+ /**
74
+ * Whether a URL is a hosted per-instance proxy URL (`https://<id>.proxy.propr.dev`).
75
+ * propr-routing only forwards `/api/*` and `/socket.io/*` on these hosts, so the
76
+ * tunnel base URL must be one of them. Requires https and *exactly one* valid
77
+ * instance-id label in front of the shared {@link PROPR_UI_PROXY_SUFFIX} — a
78
+ * multi-label host like `foo.bar.proxy.propr.dev` is rejected because routing
79
+ * only addresses a single instance label. It must also be a bare origin: a
80
+ * non-root path, query, or fragment (e.g. `https://abc.proxy.propr.dev/api`) is
81
+ * rejected because {@link proprTunnelEndpoints} appends `/api/...` itself and a
82
+ * base path would double it up (`.../api/api/status`). Returns false for a
83
+ * malformed URL.
84
+ */
85
+ export function isProprProxyUrl(url) {
86
+ if (!url)
87
+ return false;
88
+ try {
89
+ const { protocol, hostname, pathname, search, hash } = new URL(url);
90
+ if (protocol !== 'https:')
91
+ return false;
92
+ // Must be a bare origin — the tunnel endpoint helpers own the path suffix.
93
+ // Trailing slashes (`/`, `//`) are tolerated (callers trim them); any real
94
+ // path segment, query, or fragment is rejected so a base path can't double
95
+ // up the appended `/api/...`.
96
+ if (/[^/]/.test(pathname) || search || hash)
97
+ return false;
98
+ const suffix = `.${PROPR_UI_PROXY_SUFFIX}`;
99
+ if (!hostname.endsWith(suffix))
100
+ return false;
101
+ // The portion before the suffix must be a single valid DNS label (no dots),
102
+ // so isValidProprInstanceId rejects nested hosts like `a.b.proxy.propr.dev`.
103
+ return isValidProprInstanceId(hostname.slice(0, -suffix.length));
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }
109
+ /**
110
+ * The concrete endpoints the hosted UI reaches through the tunnel base URL.
111
+ * propr-routing only allows `/api/*` and `/socket.io/*`, so the base (root) URL
112
+ * itself intentionally returns 404 — it is NOT a health target. Use `apiStatus`
113
+ * to probe liveness. The base is normalized (trailing slashes trimmed) so the
114
+ * derived paths never double up a slash.
115
+ */
116
+ export function proprTunnelEndpoints(baseUrl) {
117
+ const base = baseUrl.replace(/\/+$/, '');
118
+ return {
119
+ apiStatus: `${base}/api/status`,
120
+ socketIo: `${base}/socket.io/`,
121
+ root: `${base}/`,
122
+ };
123
+ }
27
124
  //# sourceMappingURL=proprServiceUrls.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "propr-cli",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "CLI for interacting with the ProPR backend",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,5 +27,5 @@
27
27
  "code-review",
28
28
  "automation"
29
29
  ],
30
- "license": "ISC"
30
+ "license": "Apache-2.0"
31
31
  }