@mediamonks/canopy-cli 0.5.0 → 0.6.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/{db-BW1ZolWn.mjs → db-BpnjQUkF.mjs} +42 -3
- package/dist/{deploy-alC2--pZ.mjs → deploy-DdSSL227.mjs} +29 -2
- package/dist/dev-DV5ud7AJ.mjs +2 -0
- package/dist/{dev-CQQhMOoD.mjs → dev-OxeYndd1.mjs} +24 -9
- package/dist/docs/ci.md +1 -3
- package/dist/docs/cli.md +3 -4
- package/dist/docs/data.md +5 -4
- package/dist/docs/deploy.md +1 -3
- package/dist/docs/dev.md +13 -11
- package/dist/docs/getting-started.md +1 -3
- package/dist/docs/infrastructure.md +27 -15
- package/dist/docs/migrations.md +166 -0
- package/dist/docs/porting-nextjs.md +232 -0
- package/dist/docs/sdk-ai.md +103 -0
- package/dist/docs/sdk-auth.md +57 -0
- package/dist/docs/sdk-database.md +126 -0
- package/dist/docs/sdk-files.md +82 -0
- package/dist/docs/sdk-services.md +58 -0
- package/dist/docs/sdk.md +113 -0
- package/dist/docs/static-serving.md +4 -1
- package/dist/docs/troubleshooting.md +1 -3
- package/dist/{docs-DvSPpSIR.mjs → docs-Cept8t_3.mjs} +23 -10
- package/dist/index.mjs +20 -20
- package/dist/{local-C5-GWJcx.mjs → local-D9OVEHDg.mjs} +363 -39
- package/dist/{login-Bz-rwlFF.mjs → login-Cb8D8a1Z.mjs} +125 -4
- package/dist/migrations-B0kJarjr.mjs +50 -0
- package/dist/{open-DnlYDqrU.mjs → open-BmgYBSdR.mjs} +2 -2
- package/dist/{package-Bi5FfVSP.mjs → package-D1wDsLSU.mjs} +1 -1
- package/dist/{projects-ijfe7Ffx.mjs → projects-CoGh8m9A.mjs} +17 -9
- package/dist/{site-BJjoePQS.mjs → site-DQnysZ3W.mjs} +8 -1
- package/dist/skill/canopy/SKILL.md +18 -1
- package/dist/{skill-IAAa2EkK.mjs → skill-192EdixC.mjs} +2 -2
- package/dist/templates/worker/AGENTS.md +3 -0
- package/dist/{token-BeF9zczg.mjs → token-CWgp_Lqe.mjs} +2 -2
- package/dist/{ui-DThlyHgo.mjs → ui-BhzMeKQl.mjs} +1 -1
- package/dist/{upgrade-DNxR-zh7.mjs → upgrade-BrHSSg2e.mjs} +2 -2
- package/dist/{usage-DQpH7A3X.mjs → usage-CYQRLXwf.mjs} +2 -2
- package/package.json +3 -3
- package/dist/dev-BJ5xEwqW.mjs +0 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { l as tryReadCanopyJson } from "./site-DQnysZ3W.mjs";
|
|
2
|
+
import { o as apiDataRequest } from "./ui-BhzMeKQl.mjs";
|
|
3
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
//#region src/migrations.ts
|
|
6
|
+
/**
|
|
7
|
+
* The CLI side of schema migrations (docs/13-migrations-and-drizzle.md):
|
|
8
|
+
* reads the project's migration files (canopy.json "migrations" dir, e.g.
|
|
9
|
+
* Drizzle's `drizzle-kit generate` output) and sends them to the platform's
|
|
10
|
+
* migrate primitive. Shared by `canopy db migrate`, the deploy step, and the
|
|
11
|
+
* `--local` sandbox boot.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* The project's migration files in apply order, or undefined when canopy.json
|
|
15
|
+
* configures no "migrations" dir. A configured-but-unreadable dir is an error;
|
|
16
|
+
* a readable dir with no .sql files is an empty list.
|
|
17
|
+
*/
|
|
18
|
+
function readMigrationFiles(dir) {
|
|
19
|
+
const migrationsPath = tryReadCanopyJson(dir)?.migrations;
|
|
20
|
+
if (!migrationsPath) return void 0;
|
|
21
|
+
const migrationsDir = resolve(dir, migrationsPath);
|
|
22
|
+
let entries;
|
|
23
|
+
try {
|
|
24
|
+
entries = readdirSync(migrationsDir, { withFileTypes: true });
|
|
25
|
+
} catch {
|
|
26
|
+
throw new Error(`Migrations directory not found: ${migrationsDir} (canopy.json "migrations").`);
|
|
27
|
+
}
|
|
28
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => ({
|
|
29
|
+
name: entry.name,
|
|
30
|
+
sql: readFileSync(join(migrationsDir, entry.name), "utf8")
|
|
31
|
+
})).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
32
|
+
}
|
|
33
|
+
/** Run (or dry-run) the project's migrations against its live database. */
|
|
34
|
+
function migrateProject(slug, migrations, plan = false) {
|
|
35
|
+
return apiDataRequest("POST", "/db/migrate", slug, {
|
|
36
|
+
migrations,
|
|
37
|
+
plan
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** One-line-per-fact summary shared by `db migrate` and the deploy step. */
|
|
41
|
+
function describeMigrateResult(result) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
for (const name of result.appliedNow) lines.push(`applied ${name}`);
|
|
44
|
+
if (result.appliedNow.length === 0 && result.pending.length === 0) lines.push(`nothing pending (${result.applied.length} applied previously)`);
|
|
45
|
+
if (result.snapshot !== void 0) lines.push(`pre-migrate snapshot: ${result.snapshot} (canopy db restore --snapshot …)`);
|
|
46
|
+
if (result.missing.length > 0) lines.push(`note: ${result.missing.length} journaled migration(s) have no local file`);
|
|
47
|
+
return lines;
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { migrateProject as n, readMigrationFiles as r, describeMigrateResult as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as reportErrors, t as openBrowser } from "./ui-
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject, s as projectUrl } from "./site-DQnysZ3W.mjs";
|
|
2
|
+
import { n as reportErrors, t as openBrowser } from "./ui-BhzMeKQl.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/open.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { c as apiRequest, i as unwrap, n as reportErrors, r as requireTty } from "./ui-
|
|
1
|
+
import { l as tryReadCanopyJson, n as siteConfig, s as projectUrl } from "./site-DQnysZ3W.mjs";
|
|
2
|
+
import { c as apiRequest, i as unwrap, n as reportErrors, r as requireTty } from "./ui-BhzMeKQl.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join, relative, resolve, sep } from "node:path";
|
|
@@ -75,7 +75,12 @@ function writeCanopyJson(dir, fields) {
|
|
|
75
75
|
writeFileSync(join(dir, "canopy.json"), `${JSON.stringify(next, null, 2)}\n`);
|
|
76
76
|
}
|
|
77
77
|
function slugify(input) {
|
|
78
|
-
|
|
78
|
+
let slug = input.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/(^-+|-+$)/g, "").slice(0, 40);
|
|
79
|
+
while (slug.includes("-dot-")) slug = slug.replaceAll("-dot-", "-");
|
|
80
|
+
return slug;
|
|
81
|
+
}
|
|
82
|
+
function shouldScaffoldCreate(requested, cwd = process.cwd()) {
|
|
83
|
+
return requested ?? !existsSync(join(cwd, "canopy.json"));
|
|
79
84
|
}
|
|
80
85
|
const createCommand = defineCommand({
|
|
81
86
|
meta: {
|
|
@@ -98,7 +103,6 @@ const createCommand = defineCommand({
|
|
|
98
103
|
},
|
|
99
104
|
scaffold: {
|
|
100
105
|
type: "boolean",
|
|
101
|
-
default: true,
|
|
102
106
|
description: "Scaffold ./<slug>/ from the starter template",
|
|
103
107
|
negativeDescription: "Only register — no files written"
|
|
104
108
|
}
|
|
@@ -118,12 +122,16 @@ const createCommand = defineCommand({
|
|
|
118
122
|
message: "Slug",
|
|
119
123
|
defaultValue: slugify(name),
|
|
120
124
|
placeholder: slugify(name),
|
|
121
|
-
validate: (value) =>
|
|
125
|
+
validate: (value) => {
|
|
126
|
+
if (value && !/^[a-z0-9][a-z0-9-]*$/.test(value)) return "Lowercase letters, digits and dashes only.";
|
|
127
|
+
if (value?.includes("-dot-")) return `"-dot-" is reserved for preview hostnames.`;
|
|
128
|
+
}
|
|
122
129
|
})).trim();
|
|
123
130
|
slug ||= slugify(name);
|
|
131
|
+
const scaffold = shouldScaffoldCreate(args.scaffold);
|
|
124
132
|
let template = TEMPLATES.find((t) => t.id === (args.template ?? "static"));
|
|
125
133
|
if (!template) throw new Error(`Unknown template "${args.template}" — available: ${TEMPLATES.map((t) => t.id).join(", ")}.`);
|
|
126
|
-
if (
|
|
134
|
+
if (scaffold && !args.template && process.stdin.isTTY) {
|
|
127
135
|
const id = unwrap(await p.select({
|
|
128
136
|
message: "Template",
|
|
129
137
|
options: TEMPLATES.filter((t) => !t.hidden).map((t) => ({
|
|
@@ -136,7 +144,7 @@ const createCommand = defineCommand({
|
|
|
136
144
|
}
|
|
137
145
|
if (template.hidden) p.log.warn("Per-project Workers are not deployable yet (Phase 2) — `canopy deploy` publishes the frontend only for now.");
|
|
138
146
|
const targetDir = resolve(slug);
|
|
139
|
-
if (
|
|
147
|
+
if (scaffold && existsSync(targetDir)) throw new Error(`Directory ./${slug} already exists — remove it, or use --no-scaffold to only register.`);
|
|
140
148
|
const spin = p.spinner();
|
|
141
149
|
spin.start(`Registering ${slug}…`);
|
|
142
150
|
let project;
|
|
@@ -150,7 +158,7 @@ const createCommand = defineCommand({
|
|
|
150
158
|
throw err;
|
|
151
159
|
}
|
|
152
160
|
spin.stop(`Created ${project.name} — ${projectUrl(project.slug)}`);
|
|
153
|
-
if (
|
|
161
|
+
if (scaffold) {
|
|
154
162
|
scaffoldTemplate(template.id, targetDir, {
|
|
155
163
|
name,
|
|
156
164
|
slug
|
|
@@ -166,7 +174,7 @@ const createCommand = defineCommand({
|
|
|
166
174
|
];
|
|
167
175
|
p.note(steps.join("\n"), `Scaffolded ./${slug}/ (${template.id}) — next`);
|
|
168
176
|
p.outro(`Live URL (after first deploy): ${projectUrl(project.slug)}`);
|
|
169
|
-
} else p.outro("Registered without scaffolding — add a canopy.json to deploy an existing app.");
|
|
177
|
+
} else p.outro(existsSync(join(process.cwd(), "canopy.json")) ? "Registered without scaffolding — the existing canopy.json was left unchanged." : "Registered without scaffolding — add a canopy.json to deploy an existing app.");
|
|
170
178
|
})
|
|
171
179
|
});
|
|
172
180
|
/** Empty enough to scaffold into: missing, or holding only dotfiles (.git, .agents…). */
|
|
@@ -185,6 +185,13 @@ function tryReadCanopyJson(dir) {
|
|
|
185
185
|
}
|
|
186
186
|
return JSON.parse(raw);
|
|
187
187
|
}
|
|
188
|
+
/** The validated dialect lock from canopy.json, if the project set one. */
|
|
189
|
+
function projectDialect(dir) {
|
|
190
|
+
const dialect = tryReadCanopyJson(dir)?.dialect;
|
|
191
|
+
if (dialect === void 0) return void 0;
|
|
192
|
+
if (dialect !== "sqlite" && dialect !== "postgres") throw new Error(`canopy.json "dialect" must be "sqlite" or "postgres", not ${JSON.stringify(dialect)}.`);
|
|
193
|
+
return dialect;
|
|
194
|
+
}
|
|
188
195
|
function readCanopyJson(dir) {
|
|
189
196
|
const config = tryReadCanopyJson(dir);
|
|
190
197
|
if (config === void 0) throw new Error(`No canopy.json in ${dir} — run from a project created by \`canopy create\`, add one ({"slug": "<slug>", "dist": "dist"}), or pass --project <slug>.`);
|
|
@@ -251,4 +258,4 @@ function siteHeaderLine(cliVersion) {
|
|
|
251
258
|
return `canopy ${cliVersion} · ${c.envOverride || !c.profileId ? c.apiUrl : `${c.profileId} (${c.projectDomain ?? new URL(c.apiUrl).hostname})`}`;
|
|
252
259
|
}
|
|
253
260
|
//#endregion
|
|
254
|
-
export {
|
|
261
|
+
export { isProfileId as _, projectDirArgs as a, resolveProject as c, listBakedProfiles as d, readConfigFile as f, getBakedProfile as g, writeConfigFile as h, projectDialect as i, tryReadCanopyJson as l, upsertProfileAuth as m, siteConfig as n, projectFlagArgs as o, setActiveProfile as p, siteHeaderLine as r, projectUrl as s, setInvocationProfile as t, clearAllProfileTokens as u };
|
|
@@ -91,9 +91,26 @@ truth and never go stale.
|
|
|
91
91
|
- Deploy: `canopy deploy` (add `--build` if the project has a build step)
|
|
92
92
|
- Roll back: `canopy rollback`
|
|
93
93
|
- Open the live site: `canopy open`
|
|
94
|
-
- Manage data: `canopy db pull | snapshot | snapshots | restore`
|
|
94
|
+
- Manage data: `canopy db pull | snapshot | snapshots | restore | migrate`
|
|
95
95
|
- CI credentials: `canopy token create | list | revoke`
|
|
96
96
|
|
|
97
|
+
## Custom tables & migrations (devs; Drizzle recommended)
|
|
98
|
+
|
|
99
|
+
The document API (`canopy.db.collection(…)`) needs no schema — prefer it. When
|
|
100
|
+
a project defines its **own tables** (raw `canopy.db.sql()` / Drizzle), use the
|
|
101
|
+
migrations flow — read `canopy docs migrations` first. The rules that prevent
|
|
102
|
+
confusing failures:
|
|
103
|
+
|
|
104
|
+
- Pin `"dialect"` and `"migrations"` in `canopy.json`; put ordered `.sql`
|
|
105
|
+
files in that directory (Drizzle: `npx drizzle-kit generate` writes them).
|
|
106
|
+
- **There is no `DATABASE_URL` and never will be.** Never run
|
|
107
|
+
`drizzle-kit push`/`migrate`/`studio` or Drizzle's runtime `migrate()` —
|
|
108
|
+
generating is local, applying is Canopy's job: `canopy deploy` applies
|
|
109
|
+
pending migrations automatically (`canopy db migrate` applies without a
|
|
110
|
+
deploy; `canopy dev --local` applies to the sandbox on boot).
|
|
111
|
+
- Never edit an applied migration (the platform refuses edited history) and
|
|
112
|
+
never reference `_canopy_*` tables.
|
|
113
|
+
|
|
97
114
|
## Choosing a starter template (`canopy create` / `canopy init`)
|
|
98
115
|
|
|
99
116
|
Both `canopy create` and `canopy init` scaffold from the same three templates.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as version } from "./package-
|
|
2
|
-
import { n as reportErrors } from "./ui-
|
|
1
|
+
import { t as version } from "./package-D1wDsLSU.mjs";
|
|
2
|
+
import { n as reportErrors } from "./ui-BhzMeKQl.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join, relative } from "node:path";
|
|
@@ -39,6 +39,9 @@ the [Canopy dashboard](https://dashboard.thanarie.dev).
|
|
|
39
39
|
writes never reach the platform. Prefer it for destructive experiments.
|
|
40
40
|
- `canopy deploy` — builds (`npm run build`) and publishes; live (behind
|
|
41
41
|
company login) at https://{{slug}}.thanarie.dev.
|
|
42
|
+
- Defining your own tables (raw `sql()` / Drizzle)? Use the migrations flow —
|
|
43
|
+
`canopy docs migrations`. Never invent a `DATABASE_URL` (none exists):
|
|
44
|
+
generate migrations locally, and `canopy deploy` applies them.
|
|
42
45
|
|
|
43
46
|
## Boundaries
|
|
44
47
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import { c as apiRequest, n as reportErrors } from "./ui-
|
|
1
|
+
import { a as projectDirArgs, c as resolveProject, o as projectFlagArgs } from "./site-DQnysZ3W.mjs";
|
|
2
|
+
import { c as apiRequest, n as reportErrors } from "./ui-BhzMeKQl.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
const tokenCommand = defineCommand({
|
|
5
5
|
meta: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as PACKAGE_NAME } from "./version-check-Cuh6HaLX.mjs";
|
|
2
|
-
import { n as reportErrors } from "./ui-
|
|
2
|
+
import { n as reportErrors } from "./ui-BhzMeKQl.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -112,7 +112,7 @@ const upgradeCommand = defineCommand({
|
|
|
112
112
|
const spec = `${PACKAGE_NAME}@${args.version ?? "latest"}`;
|
|
113
113
|
const command = pmCommand(pm);
|
|
114
114
|
const cmd = installArgs(pm, spec);
|
|
115
|
-
const { skillInstalled } = await import("./skill-
|
|
115
|
+
const { skillInstalled } = await import("./skill-192EdixC.mjs");
|
|
116
116
|
const refreshSkill = skillInstalled(process.cwd());
|
|
117
117
|
console.log(`Upgrading via ${command}: ${command} ${cmd.join(" ")}`);
|
|
118
118
|
await run(command, cmd);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { c as apiRequest, l as withContext, n as reportErrors } from "./ui-
|
|
1
|
+
import { c as resolveProject, o as projectFlagArgs } from "./site-DQnysZ3W.mjs";
|
|
2
|
+
import { c as apiRequest, l as withContext, n as reportErrors } from "./ui-BhzMeKQl.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
//#region src/usage.ts
|
|
5
5
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mediamonks/canopy-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Canopy CLI — create, develop and deploy Canopy projects.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"canopy": "./dist/index.mjs"
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"esbuild": "^0.28.1",
|
|
21
21
|
"hono": "^4.8.0",
|
|
22
22
|
"vite-server": "npm:vite@^7",
|
|
23
|
-
"@mediamonks/canopy": "0.0.
|
|
24
|
-
"@mediamonks/canopy
|
|
23
|
+
"@mediamonks/canopy-data-plane": "0.0.2",
|
|
24
|
+
"@mediamonks/canopy": "0.0.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "^26.1.1",
|
package/dist/dev-BJ5xEwqW.mjs
DELETED