@rashidee/co2 1.3.15 → 1.3.16

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.
Binary file
Binary file
package/dist/index.js CHANGED
@@ -6809,7 +6809,7 @@ var {
6809
6809
  // package.json
6810
6810
  var package_default = {
6811
6811
  name: "@rashidee/co2",
6812
- version: "1.3.15",
6812
+ version: "1.3.16",
6813
6813
  description: "Compound Context Studio \u2014 self-hosted team context authoring for CO2 projects",
6814
6814
  type: "module",
6815
6815
  license: "MIT",
@@ -22015,6 +22015,46 @@ function stopCicdApp(projectId) {
22015
22015
  }
22016
22016
 
22017
22017
  // ../server/src/features/cicd/app-proxy.ts
22018
+ var PROXY_BASE_SHIM = `<script data-co2-proxy-base-shim>(function () {
22019
+ var m = location.pathname.match(/^(.*?\\/app-proxy)(?:\\/|$)/)
22020
+ if (!m) return
22021
+ var prefix = m[1]
22022
+ function reroot(p) {
22023
+ return p.charAt(0) === '/' && p.charAt(1) !== '/' && p.indexOf(prefix + '/') !== 0 && p !== prefix ? prefix + p : p
22024
+ }
22025
+ var origFetch = window.fetch.bind(window)
22026
+ window.fetch = function (input, init) {
22027
+ if (typeof input === 'string') input = reroot(input)
22028
+ else if (input instanceof URL && input.host === location.host)
22029
+ input = new URL(reroot(input.pathname) + input.search + input.hash, location.origin)
22030
+ return origFetch(input, init)
22031
+ }
22032
+ var OrigWS = window.WebSocket
22033
+ function PatchedWS(url, protocols) {
22034
+ try {
22035
+ var u = new URL(url, location.href)
22036
+ if (u.host === location.host) {
22037
+ u.pathname = reroot(u.pathname)
22038
+ u.protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
22039
+ url = u.toString()
22040
+ }
22041
+ } catch (e) { /* malformed URL \u2014 let the real constructor throw */ }
22042
+ return protocols === undefined ? new OrigWS(url) : new OrigWS(url, protocols)
22043
+ }
22044
+ PatchedWS.prototype = OrigWS.prototype
22045
+ PatchedWS.CONNECTING = OrigWS.CONNECTING
22046
+ PatchedWS.OPEN = OrigWS.OPEN
22047
+ PatchedWS.CLOSING = OrigWS.CLOSING
22048
+ PatchedWS.CLOSED = OrigWS.CLOSED
22049
+ window.WebSocket = PatchedWS
22050
+ document.addEventListener('click', function (e) {
22051
+ var t = e.target
22052
+ var a = t && t.closest ? t.closest('a[href]') : null
22053
+ if (!a) return
22054
+ var href = a.getAttribute('href')
22055
+ if (href) a.setAttribute('href', reroot(href))
22056
+ }, true)
22057
+ })()</script>`;
22018
22058
  async function proxyCicdAppRequest(db, workspacePath, projectId, prefix, subPath, req) {
22019
22059
  const location = resolveCicdApp(db, workspacePath, projectId);
22020
22060
  if (!location.present) throw svcError6("CICD_APP_MISSING", 404);
@@ -22047,6 +22087,13 @@ async function proxyCicdAppRequest(db, workspacePath, projectId, prefix, subPath
22047
22087
  if (redirectTo?.startsWith("/") && redirectTo !== prefix && !redirectTo.startsWith(`${prefix}/`)) {
22048
22088
  responseHeaders.set("location", `${prefix}${redirectTo}`);
22049
22089
  }
22090
+ const contentType = upstream.headers.get("content-type") ?? "";
22091
+ if (contentType.includes("text/html")) {
22092
+ const html = await upstream.text();
22093
+ const headTag = html.match(/<head[^>]*>/i);
22094
+ const patched = headTag ? html.replace(headTag[0], `${headTag[0]}${PROXY_BASE_SHIM}`) : `${PROXY_BASE_SHIM}${html}`;
22095
+ return new Response(patched, { status: upstream.status, headers: responseHeaders });
22096
+ }
22050
22097
  return new Response(upstream.body, { status: upstream.status, headers: responseHeaders });
22051
22098
  }
22052
22099
  var CICD_PROXY_UPGRADE_RE = /^\/api\/cicd\/(\d+)\/app-proxy(\/.*)$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rashidee/co2",
3
- "version": "1.3.15",
3
+ "version": "1.3.16",
4
4
  "description": "Compound Context Studio — self-hosted team context authoring for CO2 projects",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -111,18 +111,49 @@ port, application count) and STOP — the studio starts/embeds the app.
111
111
  once at boot). Never cache the parsed config in a top-level `const`.
112
112
  - The page is bare: no header, sidebar or footer (it is iframed by the studio). Light theme,
113
113
  one nav tab per application; the tab body is that application's live log.
114
- - **Path-prefix proxy awareness (MANDATORY)**: the studio embeds the page through its
115
- same-origin reverse proxy at `/api/cicd/<projectId>/app-proxy/` — root-absolute client URLs
116
- (`fetch('/api/state')`, `href="/infra"`, `ws://host/ws`) escape the prefix, hit the studio
117
- instead of this app and render an empty page. The client script MUST derive a base path once —
118
- `const BASE = location.pathname.endsWith('/') ? location.pathname.slice(0,-1) : location.pathname`
119
- (on the `/infra` page additionally strip the trailing `/infra`) — and prefix EVERY fetch
120
- (`fetch(BASE+'/api/state')`), the Infrastructure link (`BASE+'/infra'`) and the log WebSocket.
121
- The WebSocket URL must also follow the page scheme:
122
- `(location.protocol==='https:'?'wss://':'ws://')+location.host+BASE+'/ws?app=...'` a
123
- hardcoded `ws://` is blocked as mixed content when the studio is served over HTTPS.
114
+ - **Path-prefix proxy awareness (MANDATORY — copy the template below verbatim)**: the studio
115
+ embeds the page through its same-origin reverse proxy at `/api/cicd/<projectId>/app-proxy/` —
116
+ root-absolute client URLs (`fetch('/api/state')`, `href="/infra"`, `ws://host/ws`) escape the
117
+ prefix, hit the studio instead of this app and render an EMPTY BLACK PAGE in the studio.
118
+ Every generated page's client `<script>` MUST begin with this exact bootstrap block:
119
+
120
+ ```js
121
+ // Proxy-aware base path the studio serves this page at /api/cicd/<id>/app-proxy/ (main)
122
+ // and .../app-proxy/infra; direct access serves / and /infra. All client URLs derive from it.
123
+ // Regex-free ON PURPOSE see the template-literal escaping rule below.
124
+ var _p = location.pathname
125
+ if (_p.endsWith('/infra')) _p = _p.slice(0, -6)
126
+ if (_p.endsWith('/')) _p = _p.slice(0, -1)
127
+ var BASE = _p
128
+ function apiUrl(path) { return BASE + path } // apiUrl('/api/state')
129
+ function wsUrl(path) { // wsUrl('/ws?app=x')
130
+ return (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + BASE + path
131
+ }
132
+ ```
133
+
134
+ and then NEVER write a root-absolute URL anywhere in client code:
135
+ - every fetch goes through `apiUrl(...)` — `fetch(apiUrl('/api/state'))`, never `fetch('/api/state')`;
136
+ - the log stream socket is `new WebSocket(wsUrl('/ws?app='+encodeURIComponent(name)))` —
137
+ never a hardcoded `ws://` (mixed-content-blocked when the studio is served over HTTPS);
138
+ - the Infrastructure link is set from script (`infralink.href = BASE + '/infra'`), never a
139
+ literal `href="/infra"`.
124
140
  Server-side routes stay unprefixed (`/api/state`, `/ws`, `/infra`) — the studio proxy strips
125
141
  the prefix before forwarding.
142
+ - **Template-literal escaping in client code (MANDATORY)**: the page HTML/JS is emitted from a
143
+ template literal inside `server.js`, where every backslash is consumed once when the template
144
+ cooks — a client-side regex like `.replace(/\/$/, '')` reaches the browser as
145
+ `.replace(//$/, '')`, the `//` starts a comment, and the WHOLE client script dies with a
146
+ SyntaxError (blank page both direct AND embedded; this exact regression has shipped before).
147
+ Client code inside the template must therefore contain NO backslashes at all: use string
148
+ methods (`endsWith`, `slice`, `split`, `indexOf`) instead of regex literals, and avoid `\n`
149
+ in client strings (concatenate with real newlines or use `String.fromCharCode(10)`).
150
+ - **Syntax self-check gate (MANDATORY before flipping the generation COMPLETED)**:
151
+ 1. `node --check server.js` (validates the server code);
152
+ 2. start the server briefly, fetch `/` and `/infra`, extract every `<script>…</script>` block
153
+ and run `new Function(script)` on each — any SyntaxError means the page renders blank;
154
+ 3. grep the client sections — any remaining `fetch('/`, `href="/`, `'ws://'` or backslash
155
+ inside the page template is a defect; fix via `apiUrl`/`wsUrl`/`BASE` and the escaping
156
+ rule first. Only then flip `CICD_GENERATION.md` to COMPLETED.
126
157
  - Infrastructure page: when `infra` is non-empty the main page shows an "Infrastructure" link
127
158
  that opens the separate `/infra` page in a new tab — a status-only list of supporting
128
159
  3rd-party services, each with a live TCP-reachability dot (polled) and a console link when a