@mediamonks/canopy-cli 0.0.1 → 0.1.0
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/api-CyJ-wBZh.mjs +40 -0
- package/dist/config-C-z7st1X.mjs +33 -0
- package/dist/{db-BBcgT1md.mjs → db-CtUG4vfG.mjs} +3 -3
- package/dist/{deploy-BfhAlb91.mjs → deploy-N88l4Kt3.mjs} +7 -6
- package/dist/{dev-BB7CwqoI.mjs → dev-DTGW0lm0.mjs} +6 -5
- package/dist/dev-D_GlUULk.mjs +2 -0
- package/dist/index.mjs +24 -14
- package/dist/{local-DhufX0eJ.mjs → local-951zc9qJ.mjs} +2 -2
- package/dist/{login-DrXKOEmr.mjs → login-DsWvYc8C.mjs} +2 -1
- package/dist/{open-DFWvrLFc.mjs → open-xLiGUz_W.mjs} +2 -2
- package/dist/{project-CrrpLEPs.mjs → project-CpiPLNby.mjs} +1 -1
- package/dist/projects-DJT_mJDR.mjs +228 -0
- package/dist/templates/spa/.github/workflows/canopy-deploy.yml +34 -0
- package/dist/templates/spa/AGENTS.md +43 -0
- package/dist/templates/spa/README.md +14 -0
- package/dist/templates/spa/_gitignore +4 -0
- package/dist/templates/spa/canopy.json +5 -0
- package/dist/templates/spa/index.html +12 -0
- package/dist/templates/spa/package.json +21 -0
- package/dist/templates/spa/src/app.tsx +25 -0
- package/dist/templates/spa/src/canopy.ts +5 -0
- package/dist/templates/spa/src/main.tsx +10 -0
- package/dist/templates/spa/src/style.css +29 -0
- package/dist/templates/spa/tsconfig.json +16 -0
- package/dist/templates/spa/vite.config.ts +7 -0
- package/dist/templates/static/AGENTS.md +41 -0
- package/dist/templates/static/README.md +9 -0
- package/dist/templates/static/_gitignore +2 -0
- package/dist/templates/static/canopy.json +4 -0
- package/dist/templates/static/index.html +21 -0
- package/dist/templates/static/main.js +9 -0
- package/dist/templates/static/style.css +29 -0
- package/dist/templates/worker/.github/workflows/canopy-deploy.yml +34 -0
- package/dist/templates/worker/AGENTS.md +51 -0
- package/dist/templates/worker/README.md +18 -0
- package/dist/templates/worker/_gitignore +4 -0
- package/dist/templates/worker/canopy.json +6 -0
- package/dist/templates/worker/index.html +12 -0
- package/dist/templates/worker/package.json +22 -0
- package/dist/templates/worker/src/app.tsx +25 -0
- package/dist/templates/worker/src/canopy.ts +5 -0
- package/dist/templates/worker/src/main.tsx +10 -0
- package/dist/templates/worker/src/style.css +29 -0
- package/dist/templates/worker/tsconfig.json +16 -0
- package/dist/templates/worker/vite.config.ts +7 -0
- package/dist/templates/worker/worker/index.ts +29 -0
- package/dist/token-rTD9jxPZ.mjs +75 -0
- package/dist/ui-C8VuPzGp.mjs +40 -0
- package/dist/upgrade-TbMOjkLY.mjs +92 -0
- package/dist/version-check-CoWgQs2h.mjs +128 -0
- package/package.json +4 -4
- package/dist/api-BEY4O4ap.mjs +0 -36
- package/dist/dev-Bvy21srH.mjs +0 -2
- package/dist/projects-f_KocDae.mjs +0 -295
- package/dist/ui-Cl5P_yQ6.mjs +0 -72
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { App } from "./app.tsx";
|
|
4
|
+
import "./style.css";
|
|
5
|
+
|
|
6
|
+
createRoot(document.getElementById("root") as HTMLElement).render(
|
|
7
|
+
<StrictMode>
|
|
8
|
+
<App />
|
|
9
|
+
</StrictMode>,
|
|
10
|
+
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, sans-serif;
|
|
3
|
+
color-scheme: light dark;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
display: grid;
|
|
9
|
+
place-items: center;
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
main {
|
|
14
|
+
max-width: 40rem;
|
|
15
|
+
padding: 2rem;
|
|
16
|
+
text-align: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.status {
|
|
20
|
+
font-size: 0.9rem;
|
|
21
|
+
opacity: 0.7;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.placeholder {
|
|
25
|
+
border: 1px dashed color-mix(in srgb, currentColor 30%, transparent);
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
padding: 1.5rem;
|
|
28
|
+
opacity: 0.6;
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"noUncheckedIndexedAccess": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src", "vite.config.ts"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# {{name}} — a Canopy static app
|
|
2
|
+
|
|
3
|
+
A no-build web app on [Canopy](https://dashboard.thanarie.dev), Monks' managed
|
|
4
|
+
platform for internal projects. Everything is served behind company login;
|
|
5
|
+
there is no infrastructure to manage and none should be added.
|
|
6
|
+
|
|
7
|
+
## Canopy skill
|
|
8
|
+
|
|
9
|
+
The full SDK reference (auth, db, blob, ai) and platform guardrails ship as an
|
|
10
|
+
agent skill, kept out of this scaffold so it stays current. Install it before
|
|
11
|
+
building features:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npx skills add mediamonks/canopy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If that fails the skill isn't published yet — the SDK surface is documented on
|
|
18
|
+
the [Canopy dashboard](https://dashboard.thanarie.dev).
|
|
19
|
+
|
|
20
|
+
## Working on this project
|
|
21
|
+
|
|
22
|
+
- Plain html/css/js in this directory — no bundler, no install step. The
|
|
23
|
+
Canopy SDK is imported straight from the platform, so it always matches the
|
|
24
|
+
API version: `import { createCanopy } from "/_api/v1/sdk.js";`
|
|
25
|
+
- `canopy dev` — local dev server; its `/_api` proxies to the real platform.
|
|
26
|
+
That is LIVE project data: read freely, but take a `canopy db snapshot`
|
|
27
|
+
before risky data changes.
|
|
28
|
+
- `canopy dev --local [--pull]` — offline sandbox with local data instead;
|
|
29
|
+
writes never reach the platform. Prefer it for destructive experiments.
|
|
30
|
+
- `canopy deploy` — publish; live (behind company login) at
|
|
31
|
+
https://{{slug}}.thanarie.dev.
|
|
32
|
+
|
|
33
|
+
## Boundaries
|
|
34
|
+
|
|
35
|
+
- Do not add auth — the platform authenticates every request before it
|
|
36
|
+
reaches this app.
|
|
37
|
+
- Do not store secrets anywhere in this project, and never call external APIs
|
|
38
|
+
that need them from the browser — all backend needs go through the Canopy
|
|
39
|
+
SDK (`canopy.auth`, `canopy.db`, `canopy.blob`, `canopy.ai`).
|
|
40
|
+
- Keep it a static site: no SSR, no custom servers, no databases outside
|
|
41
|
+
`canopy.db`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
A [Canopy](https://dashboard.thanarie.dev) project. Live (behind company
|
|
4
|
+
login) at https://{{slug}}.thanarie.dev after the first `canopy deploy`.
|
|
5
|
+
|
|
6
|
+
- `canopy dev` — develop locally against the live platform (real data!)
|
|
7
|
+
- `canopy dev --local [--pull]` — offline sandbox with local data instead
|
|
8
|
+
- `canopy deploy` — publish the current directory
|
|
9
|
+
- `AGENTS.md` — project guardrails and skill setup for coding agents
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{name}}</title>
|
|
7
|
+
<link rel="stylesheet" href="./style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<main>
|
|
11
|
+
<h1>{{name}}</h1>
|
|
12
|
+
<p class="status" id="status">connecting to Canopy…</p>
|
|
13
|
+
<section id="app">
|
|
14
|
+
<p class="placeholder">
|
|
15
|
+
Start building — edit <code>main.js</code>, then run <code>canopy deploy</code>.
|
|
16
|
+
</p>
|
|
17
|
+
</section>
|
|
18
|
+
</main>
|
|
19
|
+
<script type="module" src="./main.js"></script>
|
|
20
|
+
</body>
|
|
21
|
+
</html>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The Canopy SDK is served by the platform itself — nothing to install.
|
|
2
|
+
// Auth (auth), data (db), files (blob) and AI (ai) are all live — see
|
|
3
|
+
// AGENTS.md for how to get the full SDK reference.
|
|
4
|
+
import { createCanopy } from "/_api/v1/sdk.js";
|
|
5
|
+
|
|
6
|
+
const canopy = createCanopy();
|
|
7
|
+
|
|
8
|
+
const user = await canopy.auth.user();
|
|
9
|
+
document.getElementById("status").textContent = `signed in as ${user.email}`;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, sans-serif;
|
|
3
|
+
color-scheme: light dark;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
display: grid;
|
|
9
|
+
place-items: center;
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
main {
|
|
14
|
+
max-width: 40rem;
|
|
15
|
+
padding: 2rem;
|
|
16
|
+
text-align: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.status {
|
|
20
|
+
font-size: 0.9rem;
|
|
21
|
+
opacity: 0.7;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.placeholder {
|
|
25
|
+
border: 1px dashed color-mix(in srgb, currentColor 30%, transparent);
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
padding: 1.5rem;
|
|
28
|
+
opacity: 0.6;
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Deploy to Canopy on every push to main. GitHub's runners are the build
|
|
2
|
+
# farm — the platform owns no build infrastructure.
|
|
3
|
+
#
|
|
4
|
+
# Needs a CANOPY_DEPLOY_TOKEN repo secret: create a deploy token in the
|
|
5
|
+
# dashboard (project → Settings → Deploy tokens) or with `canopy token
|
|
6
|
+
# create`, then add it via `gh secret set CANOPY_DEPLOY_TOKEN` or the repo's
|
|
7
|
+
# Settings → Secrets. Until the secret is set, this workflow skips itself
|
|
8
|
+
# instead of failing the build.
|
|
9
|
+
name: Deploy to Canopy
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
push:
|
|
13
|
+
branches: [main]
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
deploy:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
env:
|
|
19
|
+
CANOPY_DEPLOY_TOKEN: ${{ secrets.CANOPY_DEPLOY_TOKEN }}
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: 22
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
if: env.CANOPY_DEPLOY_TOKEN != ''
|
|
27
|
+
run: |
|
|
28
|
+
if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
29
|
+
- name: Deploy
|
|
30
|
+
if: env.CANOPY_DEPLOY_TOKEN != ''
|
|
31
|
+
run: npm exec --yes @mediamonks/canopy-cli deploy
|
|
32
|
+
- name: Skipped (no deploy token)
|
|
33
|
+
if: env.CANOPY_DEPLOY_TOKEN == ''
|
|
34
|
+
run: echo "CANOPY_DEPLOY_TOKEN secret not set — skipping deploy."
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# {{name}} — a Canopy React SPA with a backend Worker
|
|
2
|
+
|
|
3
|
+
A Vite + React single-page app plus a per-project backend Worker on
|
|
4
|
+
[Canopy](https://dashboard.thanarie.dev), Monks' managed platform for internal
|
|
5
|
+
projects. Everything is served behind company login; there is no
|
|
6
|
+
infrastructure to manage and none should be added.
|
|
7
|
+
|
|
8
|
+
> **Phase 2 preview:** per-project Workers are not deployable yet.
|
|
9
|
+
> `canopy deploy` publishes the frontend only; `worker/` is picked up
|
|
10
|
+
> automatically once the platform supports it.
|
|
11
|
+
|
|
12
|
+
## Canopy skill
|
|
13
|
+
|
|
14
|
+
The full SDK reference (auth, db, blob, ai) and platform guardrails ship as an
|
|
15
|
+
agent skill, kept out of this scaffold so it stays current. Install it before
|
|
16
|
+
building features:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
npx skills add mediamonks/canopy
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
If that fails the skill isn't published yet — the SDK surface is documented on
|
|
23
|
+
the [Canopy dashboard](https://dashboard.thanarie.dev).
|
|
24
|
+
|
|
25
|
+
## Working on this project
|
|
26
|
+
|
|
27
|
+
- Frontend: a standard Vite + React + TypeScript app in `src/`; `npm install`
|
|
28
|
+
once, then develop with `canopy dev` (it runs Vite with this project's
|
|
29
|
+
`vite.config.ts` and proxies `/_api` to the platform).
|
|
30
|
+
- Backend: Hono-style handlers in `worker/index.ts`, serving `/api/*`. It runs
|
|
31
|
+
behind the Canopy gateway (requests arrive authenticated) and reaches the
|
|
32
|
+
platform through a service binding — same SDK, same budgets and data
|
|
33
|
+
isolation as the browser.
|
|
34
|
+
- The SDK comes from npm (`@mediamonks/canopy`) — the browser's shared
|
|
35
|
+
instance lives in `src/canopy.ts`.
|
|
36
|
+
- `canopy dev` uses LIVE project data: read freely, but take a
|
|
37
|
+
`canopy db snapshot` before risky data changes.
|
|
38
|
+
- `canopy dev --local [--pull]` — offline sandbox with local data instead;
|
|
39
|
+
writes never reach the platform. Prefer it for destructive experiments.
|
|
40
|
+
- `canopy deploy` — builds (`npm run build`) and publishes; live (behind
|
|
41
|
+
company login) at https://{{slug}}.thanarie.dev.
|
|
42
|
+
|
|
43
|
+
## Boundaries
|
|
44
|
+
|
|
45
|
+
- Do not add auth — the platform authenticates every request before it
|
|
46
|
+
reaches this app or the Worker.
|
|
47
|
+
- Do not store secrets anywhere in this project — platform access goes
|
|
48
|
+
through the Canopy SDK (`canopy.auth`, `canopy.db`, `canopy.blob`,
|
|
49
|
+
`canopy.ai`) in both the browser and the Worker.
|
|
50
|
+
- The Worker is the only backend: no SSR, no long-running processes, no
|
|
51
|
+
databases outside `canopy.db`.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
A [Canopy](https://dashboard.thanarie.dev) project — Vite + React with a
|
|
4
|
+
backend Worker (`worker/`). Live (behind company login) at
|
|
5
|
+
https://{{slug}}.thanarie.dev after the first `canopy deploy`.
|
|
6
|
+
|
|
7
|
+
> Per-project Workers are a Canopy Phase 2 capability: `canopy deploy`
|
|
8
|
+
> publishes the frontend only for now — `worker/` deploys automatically once
|
|
9
|
+
> the platform supports it.
|
|
10
|
+
|
|
11
|
+
- `npm install` — once, then:
|
|
12
|
+
- `canopy dev` — develop locally against the live platform (real data!)
|
|
13
|
+
- `canopy dev --local [--pull]` — offline sandbox with local data instead
|
|
14
|
+
- `canopy deploy` — build and publish `dist/`
|
|
15
|
+
- `AGENTS.md` — project guardrails and skill setup for coding agents
|
|
16
|
+
|
|
17
|
+
Push-to-main deploys are prepared in `.github/workflows/canopy-deploy.yml`
|
|
18
|
+
(needs a `CANOPY_DEPLOY_TOKEN` repo secret — see the workflow file).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>{{name}}</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{slug}}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "canopy dev",
|
|
7
|
+
"build": "tsc && vite build"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@mediamonks/canopy": "^0.0.1",
|
|
11
|
+
"hono": "^4.8.0",
|
|
12
|
+
"react": "^19.1.0",
|
|
13
|
+
"react-dom": "^19.1.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^19.1.0",
|
|
17
|
+
"@types/react-dom": "^19.1.0",
|
|
18
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
19
|
+
"typescript": "^5.8.0",
|
|
20
|
+
"vite": "^7.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { canopy } from "./canopy.ts";
|
|
3
|
+
|
|
4
|
+
export function App() {
|
|
5
|
+
const [status, setStatus] = useState("connecting to Canopy…");
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
canopy.auth
|
|
9
|
+
.user()
|
|
10
|
+
.then((user) => setStatus(`signed in as ${user.email}`))
|
|
11
|
+
.catch(() => setStatus("could not reach Canopy — is `canopy dev` running?"));
|
|
12
|
+
}, []);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<main>
|
|
16
|
+
<h1>{"{{name}}"}</h1>
|
|
17
|
+
<p className="status">{status}</p>
|
|
18
|
+
<section>
|
|
19
|
+
<p className="placeholder">
|
|
20
|
+
Start building — edit <code>src/app.tsx</code>, then run <code>canopy deploy</code>.
|
|
21
|
+
</p>
|
|
22
|
+
</section>
|
|
23
|
+
</main>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { StrictMode } from "react";
|
|
2
|
+
import { createRoot } from "react-dom/client";
|
|
3
|
+
import { App } from "./app.tsx";
|
|
4
|
+
import "./style.css";
|
|
5
|
+
|
|
6
|
+
createRoot(document.getElementById("root") as HTMLElement).render(
|
|
7
|
+
<StrictMode>
|
|
8
|
+
<App />
|
|
9
|
+
</StrictMode>,
|
|
10
|
+
);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: system-ui, sans-serif;
|
|
3
|
+
color-scheme: light dark;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
body {
|
|
7
|
+
margin: 0;
|
|
8
|
+
display: grid;
|
|
9
|
+
place-items: center;
|
|
10
|
+
min-height: 100vh;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
main {
|
|
14
|
+
max-width: 40rem;
|
|
15
|
+
padding: 2rem;
|
|
16
|
+
text-align: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.status {
|
|
20
|
+
font-size: 0.9rem;
|
|
21
|
+
opacity: 0.7;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.placeholder {
|
|
25
|
+
border: 1px dashed color-mix(in srgb, currentColor 30%, transparent);
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
padding: 1.5rem;
|
|
28
|
+
opacity: 0.6;
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"noUncheckedIndexedAccess": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src", "worker", "vite.config.ts"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project's own backend Worker (Hono-style handlers). It runs behind the
|
|
3
|
+
* Canopy gateway — every request is already authenticated — and reaches the
|
|
4
|
+
* platform (db/blob/ai) through the CANOPY service binding, so budgets and
|
|
5
|
+
* data isolation still apply.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: per-project Workers are a Canopy Phase 2 capability. `canopy deploy`
|
|
8
|
+
* does not upload this Worker yet — the frontend deploys like any SPA, and
|
|
9
|
+
* this directory is picked up automatically once the platform supports it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createCanopy } from "@mediamonks/canopy";
|
|
13
|
+
import { Hono } from "hono";
|
|
14
|
+
|
|
15
|
+
interface Env {
|
|
16
|
+
// Service binding to the platform API (canopy-api), scoped to this project.
|
|
17
|
+
CANOPY: { fetch: typeof fetch };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const app = new Hono<{ Bindings: Env }>();
|
|
21
|
+
|
|
22
|
+
// Routes under /api/* reach this Worker; everything else serves the SPA.
|
|
23
|
+
app.get("/api/hello", async (c) => {
|
|
24
|
+
const canopy = createCanopy({ fetch: c.env.CANOPY.fetch.bind(c.env.CANOPY) });
|
|
25
|
+
const user = await canopy.auth.user();
|
|
26
|
+
return c.json({ message: `Hello from the backend, ${user.name}!` });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export default app;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { t as apiRequest } from "./api-CyJ-wBZh.mjs";
|
|
2
|
+
import { i as resolveProject, n as projectFlagArgs, t as projectDirArgs } from "./project-CpiPLNby.mjs";
|
|
3
|
+
import { n as reportErrors } from "./ui-C8VuPzGp.mjs";
|
|
4
|
+
import { defineCommand } from "citty";
|
|
5
|
+
const tokenCommand = defineCommand({
|
|
6
|
+
meta: {
|
|
7
|
+
name: "token",
|
|
8
|
+
description: "Manage deploy tokens for push-to-main deploys (CI)"
|
|
9
|
+
},
|
|
10
|
+
subCommands: {
|
|
11
|
+
create: defineCommand({
|
|
12
|
+
meta: {
|
|
13
|
+
name: "create",
|
|
14
|
+
description: "Create a deploy token for CI"
|
|
15
|
+
},
|
|
16
|
+
args: {
|
|
17
|
+
...projectDirArgs,
|
|
18
|
+
name: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Label shown in the dashboard, e.g. \"GitHub Actions\""
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
run: reportErrors("token create", async ({ args }) => {
|
|
24
|
+
const { slug } = resolveProject(args);
|
|
25
|
+
const { token } = await apiRequest("POST", `/projects/${slug}/deploy-tokens`, { name: args.name });
|
|
26
|
+
console.log(`Deploy token for ${slug} (shown once — store it now):\n`);
|
|
27
|
+
console.log(` ${token}\n`);
|
|
28
|
+
console.log("Add it to the GitHub repo as secret CANOPY_DEPLOY_TOKEN");
|
|
29
|
+
console.log("(gh secret set CANOPY_DEPLOY_TOKEN, or repo Settings → Secrets).");
|
|
30
|
+
console.log("Pushes to main then deploy via .github/workflows/canopy-deploy.yml.");
|
|
31
|
+
})
|
|
32
|
+
}),
|
|
33
|
+
list: defineCommand({
|
|
34
|
+
meta: {
|
|
35
|
+
name: "list",
|
|
36
|
+
description: "List the project's deploy tokens"
|
|
37
|
+
},
|
|
38
|
+
args: { ...projectDirArgs },
|
|
39
|
+
run: reportErrors("token list", async ({ args }) => {
|
|
40
|
+
const { slug } = resolveProject(args);
|
|
41
|
+
const { tokens } = await apiRequest("GET", `/projects/${slug}/deploy-tokens`);
|
|
42
|
+
if (tokens.length === 0) {
|
|
43
|
+
console.log(`No deploy tokens for ${slug} — create one with \`canopy token create\`.`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const t of tokens) console.log(`${t.id} ${t.name ?? "(unnamed)"} created ${t.created_at} by ${t.created_by ?? "?"}`);
|
|
47
|
+
})
|
|
48
|
+
}),
|
|
49
|
+
revoke: defineCommand({
|
|
50
|
+
meta: {
|
|
51
|
+
name: "revoke",
|
|
52
|
+
description: "Revoke a deploy token (id from `canopy token list`)"
|
|
53
|
+
},
|
|
54
|
+
args: {
|
|
55
|
+
id: {
|
|
56
|
+
type: "positional",
|
|
57
|
+
description: "Token id to revoke",
|
|
58
|
+
required: true
|
|
59
|
+
},
|
|
60
|
+
...projectFlagArgs,
|
|
61
|
+
dir: {
|
|
62
|
+
type: "string",
|
|
63
|
+
description: "Project directory (defaults to the current directory)"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
run: reportErrors("token revoke", async ({ args }) => {
|
|
67
|
+
const { slug } = resolveProject(args);
|
|
68
|
+
await apiRequest("DELETE", `/projects/${slug}/deploy-tokens/${args.id}`);
|
|
69
|
+
console.log(`Revoked ${args.id} — deploys using it stop working immediately.`);
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
//#endregion
|
|
75
|
+
export { tokenCommand };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { cancel, isCancel } from "@clack/prompts";
|
|
3
|
+
//#region src/ui.ts
|
|
4
|
+
/**
|
|
5
|
+
* Shared terminal UX plumbing: clack prompt unwrapping and uniform
|
|
6
|
+
* command error reporting.
|
|
7
|
+
*/
|
|
8
|
+
/** Unwrap a clack prompt result, exiting cleanly when the user cancels (ctrl-c). */
|
|
9
|
+
function unwrap(result) {
|
|
10
|
+
if (isCancel(result)) {
|
|
11
|
+
cancel("Aborted.");
|
|
12
|
+
process.exit(130);
|
|
13
|
+
}
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
/** Wrap a command action so failures print as `canopy <name>: <message>` and exit 1. */
|
|
17
|
+
function reportErrors(name, action) {
|
|
18
|
+
return async (...args) => {
|
|
19
|
+
try {
|
|
20
|
+
await action(...args);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.error(`canopy ${name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Guard interactive prompts: bail with a actionable message on non-TTY runs. */
|
|
28
|
+
function requireTty(hint) {
|
|
29
|
+
if (!process.stdin.isTTY) throw new Error(`This needs an interactive terminal — ${hint}`);
|
|
30
|
+
}
|
|
31
|
+
/** Open a URL in the default browser, fire-and-forget. */
|
|
32
|
+
function openBrowser(url) {
|
|
33
|
+
spawn(process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open", [url], {
|
|
34
|
+
stdio: "ignore",
|
|
35
|
+
detached: true,
|
|
36
|
+
shell: process.platform === "win32"
|
|
37
|
+
}).on("error", () => {}).unref();
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { unwrap as i, reportErrors as n, requireTty as r, openBrowser as t };
|