@saacms/cli 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/README.md +23 -0
- package/dist/commands/codegen.d.ts +29 -0
- package/dist/commands/codegen.d.ts.map +1 -0
- package/dist/commands/codegen.js +24 -0
- package/dist/commands/dev.d.ts +54 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +26 -0
- package/dist/commands/doctor.d.ts +55 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +25 -0
- package/dist/commands/init.d.ts +35 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +106 -0
- package/dist/commands/logs.d.ts +55 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +30 -0
- package/dist/commands/migrate.d.ts +95 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +81 -0
- package/dist/commands/publish.d.ts +53 -0
- package/dist/commands/publish.d.ts.map +1 -0
- package/dist/commands/publish.js +32 -0
- package/dist/main.d.ts +13 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +1634 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @saacms/cli
|
|
2
|
+
|
|
3
|
+
The `saacms` binary. A thin orchestrator over codegen, migrations, publish, log fetching, and environment health checks. Per ADR 0011, it is **not** a server — the host framework's own dev command runs the show.
|
|
4
|
+
|
|
5
|
+
## Surface
|
|
6
|
+
|
|
7
|
+
- `saacms init` — bootstrap saacms into an existing host project (v1 alpha: Astro only).
|
|
8
|
+
- `saacms codegen [--watch]` — Effect Schema → Drizzle / OpenAPI / TS-types / Puck-fields projections.
|
|
9
|
+
- `saacms dev` — sugar that runs the host's `dev` script alongside `saacms codegen --watch`.
|
|
10
|
+
- `saacms migrate generate | run | check` — DB and content migrations.
|
|
11
|
+
- `saacms publish` — commit drafts + generated routes to the developer's Git repo.
|
|
12
|
+
- `saacms logs <deploy-id>` — fetch deploy/runtime logs.
|
|
13
|
+
- `saacms doctor` — environment health report.
|
|
14
|
+
|
|
15
|
+
## Reading order (relevant ADRs)
|
|
16
|
+
|
|
17
|
+
- [0011 — Dev workflow: piggyback the host's dev server](../../docs/adr/0011-dev-workflow.md)
|
|
18
|
+
- [0012 — Migrations: schema (DB) and content (Page JSON)](../../docs/adr/0012-migrations-schema-and-content.md)
|
|
19
|
+
- [0024 — v1 alpha scope: Astro + Cloudflare Pages + D1 + R2](../../docs/adr/0024-v1-alpha-scope-astro-cloudflare.md)
|
|
20
|
+
|
|
21
|
+
## Status
|
|
22
|
+
|
|
23
|
+
Scaffold. Sub-commands are wired through citty; handlers print what they would do and reference the relevant ADR. No real work is performed yet.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms codegen` — Effect Schema -> Drizzle / OpenAPI / TS-types / Puck-fields
|
|
3
|
+
* projections. Output goes to `.saacms/` (gitignored). Per ADR 0005 + 0011.
|
|
4
|
+
*
|
|
5
|
+
* Reads the user's `saacms.config.ts` (default export from `defineConfig`) and
|
|
6
|
+
* emits four artefacts per Collection plus one aggregate OpenAPI document. The
|
|
7
|
+
* generators live in `@saacms/core`; this command composes them and handles I/O.
|
|
8
|
+
*
|
|
9
|
+
* Per-Collection errors are isolated: if one Collection's Schema can't project,
|
|
10
|
+
* we log the error, set `process.exitCode = 1`, and continue with the next.
|
|
11
|
+
*/
|
|
12
|
+
export declare const codegenCommand: import("citty").CommandDef<{
|
|
13
|
+
cwd: {
|
|
14
|
+
type: "string";
|
|
15
|
+
description: string;
|
|
16
|
+
required: false;
|
|
17
|
+
};
|
|
18
|
+
out: {
|
|
19
|
+
type: "string";
|
|
20
|
+
description: string;
|
|
21
|
+
required: false;
|
|
22
|
+
};
|
|
23
|
+
watch: {
|
|
24
|
+
type: "boolean";
|
|
25
|
+
description: string;
|
|
26
|
+
default: false;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
//# sourceMappingURL=codegen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/commands/codegen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA8KH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;EAmEzB,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms codegen` — Effect Schema -> Drizzle / OpenAPI / TS-types / Puck-fields
|
|
3
|
+
* projections. Output goes to `.saacms/` (gitignored). Per ADR 0005 + 0011.
|
|
4
|
+
*
|
|
5
|
+
* Scaffold only. The generators live in `@saacms/core` and are tracked in
|
|
6
|
+
* ADR 0005; this command will compose them once they land.
|
|
7
|
+
*/
|
|
8
|
+
import { defineCommand } from "citty";
|
|
9
|
+
export const codegenCommand = defineCommand({
|
|
10
|
+
meta: {
|
|
11
|
+
name: "codegen",
|
|
12
|
+
description: "Run Schema -> Drizzle / OpenAPI / TS-types / Puck-fields projections (writes to .saacms/).",
|
|
13
|
+
},
|
|
14
|
+
args: {
|
|
15
|
+
watch: {
|
|
16
|
+
type: "boolean",
|
|
17
|
+
description: "Re-run on changes to saacms/**/*.ts.",
|
|
18
|
+
default: false,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
run({ args }) {
|
|
22
|
+
console.log(`saacms codegen — watch=${args.watch ? "on" : "off"} — not implemented; tracking ADR 0005.`);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms dev` — DX sugar that runs the host framework's own dev command in
|
|
3
|
+
* parallel with `saacms codegen --watch`. Per ADR 0011, saacms is **not** a
|
|
4
|
+
* server; the host's normal dev command serves /admin and /api/saacms/* by
|
|
5
|
+
* virtue of saacms's runtime handler being mounted into the host's routing.
|
|
6
|
+
*
|
|
7
|
+
* Supervision contract (v0.1):
|
|
8
|
+
* - Both children inherit the parent's stdio by default.
|
|
9
|
+
* - SIGINT / SIGTERM on the parent → forward to both children, then wait
|
|
10
|
+
* for them to exit, then exit with the host-dev's exit code.
|
|
11
|
+
* - If host-dev exits before any signal → kill the codegen watcher, exit
|
|
12
|
+
* with host-dev's code.
|
|
13
|
+
* - If the codegen watcher exits non-zero while host-dev is still running,
|
|
14
|
+
* warn but do NOT kill host-dev (codegen failures are non-fatal during
|
|
15
|
+
* dev iteration).
|
|
16
|
+
*
|
|
17
|
+
* The body is factored into `startDev()` so tests (and future programmatic
|
|
18
|
+
* callers) can drive it without going through citty / process.exit.
|
|
19
|
+
*
|
|
20
|
+
* NOTE on signal-forwarding: SIGINT/SIGTERM end-to-end behaviour is awkward
|
|
21
|
+
* to assert in-process (you'd kill the test runner). It is documented here
|
|
22
|
+
* and left as a manual integration test. TODO: figure out a hermetic harness.
|
|
23
|
+
*/
|
|
24
|
+
export type DevStdio = "inherit" | "pipe";
|
|
25
|
+
export interface StartDevOptions {
|
|
26
|
+
cwd?: string;
|
|
27
|
+
noCodegen?: boolean;
|
|
28
|
+
/** "inherit" (default) hands stdout/stderr straight to the parent's tty.
|
|
29
|
+
* "pipe" captures both streams and returns the concatenated text on the
|
|
30
|
+
* result — used by the test suite. */
|
|
31
|
+
stdio?: DevStdio;
|
|
32
|
+
}
|
|
33
|
+
export interface DevResult {
|
|
34
|
+
hostExitCode: number;
|
|
35
|
+
codegenExitCode: number | null;
|
|
36
|
+
hostStdout: string;
|
|
37
|
+
hostStderr: string;
|
|
38
|
+
codegenStdout: string;
|
|
39
|
+
codegenStderr: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function startDev(opts?: StartDevOptions): Promise<DevResult>;
|
|
42
|
+
export declare const devCommand: import("citty").CommandDef<{
|
|
43
|
+
cwd: {
|
|
44
|
+
type: "string";
|
|
45
|
+
description: string;
|
|
46
|
+
required: false;
|
|
47
|
+
};
|
|
48
|
+
"no-codegen": {
|
|
49
|
+
type: "boolean";
|
|
50
|
+
description: string;
|
|
51
|
+
default: false;
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
54
|
+
//# sourceMappingURL=dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AA6EH,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAEzC,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;2CAEuC;IACvC,KAAK,CAAC,EAAE,QAAQ,CAAA;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;IACrB,aAAa,EAAE,MAAM,CAAA;CACtB;AAOD,wBAAsB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,SAAS,CAAC,CA0H7E;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;EAwBrB,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms dev` — DX sugar that runs the host framework's own dev command in
|
|
3
|
+
* parallel with `saacms codegen --watch`. Per ADR 0011, saacms is **not** a
|
|
4
|
+
* server; the host's normal dev command serves /admin and /api/saacms/* by
|
|
5
|
+
* virtue of saacms's runtime handler being mounted into the host's routing.
|
|
6
|
+
*
|
|
7
|
+
* Scaffold only — concurrent process orchestration lands later.
|
|
8
|
+
*/
|
|
9
|
+
import { defineCommand } from "citty";
|
|
10
|
+
export const devCommand = defineCommand({
|
|
11
|
+
meta: {
|
|
12
|
+
name: "dev",
|
|
13
|
+
description: "Run the host's dev command alongside `saacms codegen --watch` (DX sugar; not a server).",
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
cwd: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "Working directory. Defaults to process.cwd().",
|
|
19
|
+
required: false,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
run({ args: _args }) {
|
|
23
|
+
console.log("saacms dev launches the host's dev command + `saacms codegen --watch` (not implemented yet).");
|
|
24
|
+
console.log("Per ADR 0011: saacms is not a server. The host serves /admin and /api/saacms/*.");
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms doctor` — environment health report. Checks that the user's
|
|
3
|
+
* project has every binding, tool, and adapter saacms expects to find,
|
|
4
|
+
* and reports each in the "What / Why / Fix / Learn more" format mandated
|
|
5
|
+
* by ADR 0022 ("Errors with fix-link").
|
|
6
|
+
*
|
|
7
|
+
* The check ordering is contractual: cheap/foundational checks first
|
|
8
|
+
* (runtime, package.json) so later checks (host, peers, storage) can
|
|
9
|
+
* assume their preconditions were already surfaced to the user.
|
|
10
|
+
*/
|
|
11
|
+
type CheckStatus = "pass" | "info" | "warn" | "fail";
|
|
12
|
+
export interface CheckResult {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly status: CheckStatus;
|
|
15
|
+
readonly message: string;
|
|
16
|
+
readonly why?: string;
|
|
17
|
+
readonly fix?: string;
|
|
18
|
+
readonly learn?: string;
|
|
19
|
+
}
|
|
20
|
+
interface Summary {
|
|
21
|
+
pass: number;
|
|
22
|
+
info: number;
|
|
23
|
+
warn: number;
|
|
24
|
+
fail: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse a `major.minor.patch` string (ignoring any pre-release/build suffix)
|
|
28
|
+
* into a numeric triple. Non-numeric segments collapse to 0 so a garbage
|
|
29
|
+
* version is treated as "ancient" and fails the check loudly rather than
|
|
30
|
+
* silently passing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseSemver(version: string): [number, number, number];
|
|
33
|
+
/** True iff `version` is >= `MIN_BUN` under semver ordering. */
|
|
34
|
+
export declare function isBunVersionOk(version: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Run the full ordered checklist against `cwd`. Pure: returns results, never
|
|
37
|
+
* touches stdout or `process.exitCode` (the command wrapper does that). This
|
|
38
|
+
* makes the checklist directly unit-testable.
|
|
39
|
+
*/
|
|
40
|
+
export declare function runChecks(cwd: string): CheckResult[];
|
|
41
|
+
export declare function summarize(results: readonly CheckResult[]): Summary;
|
|
42
|
+
export declare const doctorCommand: import("citty").CommandDef<{
|
|
43
|
+
cwd: {
|
|
44
|
+
type: "string";
|
|
45
|
+
description: string;
|
|
46
|
+
required: false;
|
|
47
|
+
};
|
|
48
|
+
json: {
|
|
49
|
+
type: "boolean";
|
|
50
|
+
description: string;
|
|
51
|
+
default: false;
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;AAEpD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,UAAU,OAAO;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AA0ED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAOrE;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CASvD;AAID;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,EAAE,CAiKpD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,OAAO,CAIlE;AAmBD,eAAO,MAAM,aAAa;;;;;;;;;;;EA+BxB,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms doctor` — environment health report. Checks that the user's
|
|
3
|
+
* project has every binding, credential, and tool saacms expects to find,
|
|
4
|
+
* and reports each in the "What / Why / Fix / Learn more" format mandated
|
|
5
|
+
* by ADR 0022 ("Errors with fix-link").
|
|
6
|
+
*
|
|
7
|
+
* Scaffold only.
|
|
8
|
+
*/
|
|
9
|
+
import { defineCommand } from "citty";
|
|
10
|
+
export const doctorCommand = defineCommand({
|
|
11
|
+
meta: {
|
|
12
|
+
name: "doctor",
|
|
13
|
+
description: "Report environment health: bindings, credentials, host adapter, storage adapter, runtime version.",
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
json: {
|
|
17
|
+
type: "boolean",
|
|
18
|
+
description: "Emit machine-readable JSON instead of the human-readable report.",
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
run({ args }) {
|
|
23
|
+
console.log(`saacms doctor${args.json ? " --json" : ""} — would report environment health (bindings, credentials, host + storage adapter wiring).`);
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms init` — bootstrap saacms into an existing host-framework project.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR 0024, v1 alpha only recognises **Astro** as a host. Other hosts
|
|
5
|
+
* (Next.js, SvelteKit, Nuxt) are deferred to v1.x. The handler detects the
|
|
6
|
+
* host by inspecting `package.json` and framework-specific config files in
|
|
7
|
+
* the current working directory.
|
|
8
|
+
*
|
|
9
|
+
* Two modes:
|
|
10
|
+
* - `--dry-run` : log the intended actions (the v0.1 behaviour).
|
|
11
|
+
* - default (no --dry-run) : actually scaffold the project on disk.
|
|
12
|
+
*/
|
|
13
|
+
export declare const initCommand: import("citty").CommandDef<{
|
|
14
|
+
host: {
|
|
15
|
+
type: "string";
|
|
16
|
+
description: string;
|
|
17
|
+
required: false;
|
|
18
|
+
};
|
|
19
|
+
storage: {
|
|
20
|
+
type: "string";
|
|
21
|
+
description: string;
|
|
22
|
+
default: string;
|
|
23
|
+
};
|
|
24
|
+
cwd: {
|
|
25
|
+
type: "string";
|
|
26
|
+
description: string;
|
|
27
|
+
required: false;
|
|
28
|
+
};
|
|
29
|
+
"dry-run": {
|
|
30
|
+
type: "boolean";
|
|
31
|
+
description: string;
|
|
32
|
+
default: false;
|
|
33
|
+
};
|
|
34
|
+
}>;
|
|
35
|
+
//# sourceMappingURL=init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA6HH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;EA0HtB,CAAA"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms init` — bootstrap saacms into an existing host-framework project.
|
|
3
|
+
*
|
|
4
|
+
* Per ADR 0024, v1 alpha only recognises **Astro** as a host. Other hosts
|
|
5
|
+
* (Next.js, SvelteKit, Nuxt) are deferred to v1.x. The handler detects the
|
|
6
|
+
* host by inspecting `package.json` and framework-specific config files in
|
|
7
|
+
* the current working directory.
|
|
8
|
+
*
|
|
9
|
+
* This is a scaffold: it logs what it *would* do rather than touching the
|
|
10
|
+
* filesystem. Real init lands alongside `@saacms/host-astro`.
|
|
11
|
+
*/
|
|
12
|
+
import { defineCommand } from "citty";
|
|
13
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
const ASTRO_CONFIG_FILES = [
|
|
16
|
+
"astro.config.ts",
|
|
17
|
+
"astro.config.mjs",
|
|
18
|
+
"astro.config.js",
|
|
19
|
+
"astro.config.cjs",
|
|
20
|
+
];
|
|
21
|
+
const NEXTJS_CONFIG_FILES = [
|
|
22
|
+
"next.config.ts",
|
|
23
|
+
"next.config.mjs",
|
|
24
|
+
"next.config.js",
|
|
25
|
+
];
|
|
26
|
+
const SVELTEKIT_CONFIG_FILES = ["svelte.config.js", "svelte.config.ts"];
|
|
27
|
+
const NUXT_CONFIG_FILES = ["nuxt.config.ts", "nuxt.config.js"];
|
|
28
|
+
function readPackageJson(cwd) {
|
|
29
|
+
const path = join(cwd, "package.json");
|
|
30
|
+
if (!existsSync(path))
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function hasDep(pkg, name) {
|
|
40
|
+
if (!pkg)
|
|
41
|
+
return false;
|
|
42
|
+
return Boolean(pkg.dependencies?.[name] ??
|
|
43
|
+
pkg.devDependencies?.[name] ??
|
|
44
|
+
pkg.peerDependencies?.[name]);
|
|
45
|
+
}
|
|
46
|
+
function hasAnyFile(cwd, files) {
|
|
47
|
+
return files.some((file) => existsSync(join(cwd, file)));
|
|
48
|
+
}
|
|
49
|
+
function detectHost(cwd) {
|
|
50
|
+
const pkg = readPackageJson(cwd);
|
|
51
|
+
if (hasAnyFile(cwd, ASTRO_CONFIG_FILES) || hasDep(pkg, "astro"))
|
|
52
|
+
return "astro";
|
|
53
|
+
if (hasAnyFile(cwd, NEXTJS_CONFIG_FILES) || hasDep(pkg, "next"))
|
|
54
|
+
return "nextjs";
|
|
55
|
+
if (hasAnyFile(cwd, SVELTEKIT_CONFIG_FILES) || hasDep(pkg, "@sveltejs/kit"))
|
|
56
|
+
return "sveltekit";
|
|
57
|
+
if (hasAnyFile(cwd, NUXT_CONFIG_FILES) || hasDep(pkg, "nuxt"))
|
|
58
|
+
return "nuxt";
|
|
59
|
+
return "unknown";
|
|
60
|
+
}
|
|
61
|
+
export const initCommand = defineCommand({
|
|
62
|
+
meta: {
|
|
63
|
+
name: "init",
|
|
64
|
+
description: "Bootstrap saacms into an existing host-framework project (v1 alpha: Astro only).",
|
|
65
|
+
},
|
|
66
|
+
args: {
|
|
67
|
+
host: {
|
|
68
|
+
type: "string",
|
|
69
|
+
description: "Host framework to scaffold for. Defaults to auto-detection from package.json + config files. v1 alpha only accepts 'astro'.",
|
|
70
|
+
required: false,
|
|
71
|
+
},
|
|
72
|
+
storage: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "Storage adapter to wire into saacms.config.ts. v1 alpha defaults to 'r2' (Cloudflare R2).",
|
|
75
|
+
default: "r2",
|
|
76
|
+
},
|
|
77
|
+
cwd: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "Working directory to scaffold into. Defaults to process.cwd().",
|
|
80
|
+
required: false,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
run({ args }) {
|
|
84
|
+
const cwd = args.cwd ?? process.cwd();
|
|
85
|
+
const host = args.host ?? detectHost(cwd);
|
|
86
|
+
const storage = args.storage;
|
|
87
|
+
console.log(`saacms init — target dir: ${cwd}`);
|
|
88
|
+
console.log(`detected host: ${host}`);
|
|
89
|
+
console.log(`storage adapter: ${storage}`);
|
|
90
|
+
if (host !== "astro") {
|
|
91
|
+
throw new Error(`init for host '${host}' not supported in v1 alpha. ` +
|
|
92
|
+
"Per ADR 0024, v1 alpha only ships @saacms/host-astro. " +
|
|
93
|
+
"Other hosts (Next.js, SvelteKit, Nuxt) are deferred to v1.x. " +
|
|
94
|
+
"If this is an Astro project, pass --host=astro explicitly.");
|
|
95
|
+
}
|
|
96
|
+
console.log("would create: saacms.config.ts (root)");
|
|
97
|
+
console.log("would create: saacms/collections/.gitkeep");
|
|
98
|
+
console.log("would create: saacms/blocks/.gitkeep");
|
|
99
|
+
console.log("would create: saacms/pages/.gitkeep");
|
|
100
|
+
console.log("would create: src/pages/api/saacms/[...slug].ts (mount point)");
|
|
101
|
+
console.log("would create: src/pages/admin/[...slug].astro (admin mount)");
|
|
102
|
+
console.log("would add: predeploy script -> 'saacms migrate check --strict'");
|
|
103
|
+
console.log(`would install peer deps: @saacms/core @saacms/host-astro @saacms/storage-${storage}`);
|
|
104
|
+
console.log("scaffold only — no filesystem changes performed yet.");
|
|
105
|
+
},
|
|
106
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms logs <deploy-id>` — fetch build / runtime logs for a given deploy.
|
|
3
|
+
* Per ADR 0024, v1 alpha targets Cloudflare Pages, so logs are pulled via
|
|
4
|
+
* the Cloudflare API. The deploy-log reader subsystem inside the runtime
|
|
5
|
+
* also surfaces these into the admin UI.
|
|
6
|
+
*
|
|
7
|
+
* The Cloudflare transport is injected (`LogsDeps.fetchFn`) so the whole
|
|
8
|
+
* command is unit-testable against canned `Response` objects with no real
|
|
9
|
+
* network. `runLogs` is pure w.r.t. process state: it returns the intended
|
|
10
|
+
* exit code and the citty `run()` wrapper applies it to `process.exitCode`.
|
|
11
|
+
*/
|
|
12
|
+
export interface LogsArgs {
|
|
13
|
+
readonly deployId: string;
|
|
14
|
+
readonly follow?: boolean;
|
|
15
|
+
readonly project?: string;
|
|
16
|
+
readonly account?: string;
|
|
17
|
+
readonly token?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface LogsDeps {
|
|
20
|
+
readonly fetchFn?: typeof fetch;
|
|
21
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Fetch (and optionally follow) the logs for a Cloudflare Pages deployment.
|
|
25
|
+
* Returns the intended process exit code (0 success, 1 failure/canceled).
|
|
26
|
+
*/
|
|
27
|
+
export declare function runLogs(args: LogsArgs, deps?: LogsDeps): Promise<number>;
|
|
28
|
+
export declare const logsCommand: import("citty").CommandDef<{
|
|
29
|
+
deployId: {
|
|
30
|
+
type: "positional";
|
|
31
|
+
description: string;
|
|
32
|
+
required: true;
|
|
33
|
+
};
|
|
34
|
+
follow: {
|
|
35
|
+
type: "boolean";
|
|
36
|
+
description: string;
|
|
37
|
+
default: false;
|
|
38
|
+
};
|
|
39
|
+
project: {
|
|
40
|
+
type: "string";
|
|
41
|
+
description: string;
|
|
42
|
+
required: false;
|
|
43
|
+
};
|
|
44
|
+
account: {
|
|
45
|
+
type: "string";
|
|
46
|
+
description: string;
|
|
47
|
+
required: false;
|
|
48
|
+
};
|
|
49
|
+
token: {
|
|
50
|
+
type: "string";
|
|
51
|
+
description: string;
|
|
52
|
+
required: false;
|
|
53
|
+
};
|
|
54
|
+
}>;
|
|
55
|
+
//# sourceMappingURL=logs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAuCH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAA;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/C;AA2ED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,QAAQ,EACd,IAAI,GAAE,QAAa,GAClB,OAAO,CAAC,MAAM,CAAC,CA8CjB;AAED,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;EAyCtB,CAAA"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms logs <deploy-id>` — fetch build / runtime logs for a given deploy.
|
|
3
|
+
* Per ADR 0024, v1 alpha targets Cloudflare Pages, so logs are pulled via
|
|
4
|
+
* the Cloudflare API. The deploy-log reader subsystem inside the runtime
|
|
5
|
+
* also surfaces these into the admin UI.
|
|
6
|
+
*
|
|
7
|
+
* Scaffold only.
|
|
8
|
+
*/
|
|
9
|
+
import { defineCommand } from "citty";
|
|
10
|
+
export const logsCommand = defineCommand({
|
|
11
|
+
meta: {
|
|
12
|
+
name: "logs",
|
|
13
|
+
description: "Fetch build + runtime logs for a deploy (v1 alpha: Cloudflare Pages).",
|
|
14
|
+
},
|
|
15
|
+
args: {
|
|
16
|
+
deployId: {
|
|
17
|
+
type: "positional",
|
|
18
|
+
description: "Deploy identifier as reported by Cloudflare Pages.",
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
follow: {
|
|
22
|
+
type: "boolean",
|
|
23
|
+
description: "Stream logs (tail mode) instead of one-shot fetch.",
|
|
24
|
+
default: false,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
run({ args }) {
|
|
28
|
+
console.log(`saacms logs ${args.deployId}${args.follow ? " --follow" : ""} — would call the Cloudflare API and stream deploy logs (ADR 0024).`);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms migrate` — DB and content migrations. Per ADR 0012:
|
|
3
|
+
*
|
|
4
|
+
* - `generate` projects each Collection's Effect Schema to D1 DDL, compares it
|
|
5
|
+
* against the last snapshot, and emits a SQL migration file per new/changed
|
|
6
|
+
* Collection into `saacms/migrations/db/`. The v1 MVP emits the full desired
|
|
7
|
+
* `CREATE TABLE IF NOT EXISTS` (idempotent), NOT an ALTER-diff — true diffing
|
|
8
|
+
* is a v1.x dispatch.
|
|
9
|
+
* - `run` applies pending migrations against the configured target. Still a
|
|
10
|
+
* documented stub: it needs a live D1 binding / `wrangler` invocation, which
|
|
11
|
+
* is a v1.x dispatch.
|
|
12
|
+
* - `check` is a read-only predeploy gate: it reports per-Collection drift and
|
|
13
|
+
* (with --strict) exits non-zero. It never writes anything.
|
|
14
|
+
*/
|
|
15
|
+
declare const generateSubCommand: import("citty").CommandDef<{
|
|
16
|
+
name: {
|
|
17
|
+
type: "positional";
|
|
18
|
+
description: string;
|
|
19
|
+
required: false;
|
|
20
|
+
};
|
|
21
|
+
cwd: {
|
|
22
|
+
type: "string";
|
|
23
|
+
description: string;
|
|
24
|
+
required: false;
|
|
25
|
+
};
|
|
26
|
+
rename: {
|
|
27
|
+
type: "string";
|
|
28
|
+
description: string;
|
|
29
|
+
required: false;
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
32
|
+
interface ExecResult {
|
|
33
|
+
readonly code: number;
|
|
34
|
+
readonly stdout: string;
|
|
35
|
+
readonly stderr: string;
|
|
36
|
+
}
|
|
37
|
+
export interface MigrateRunDeps {
|
|
38
|
+
readonly exec?: (cmd: string[], cwd: string) => Promise<ExecResult>;
|
|
39
|
+
}
|
|
40
|
+
export interface MigrateRunArgs {
|
|
41
|
+
readonly cwd?: string;
|
|
42
|
+
readonly target?: string;
|
|
43
|
+
readonly database?: string;
|
|
44
|
+
readonly dry?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Apply pending `saacms/migrations/db/*.sql` files to a Cloudflare D1
|
|
48
|
+
* database via `wrangler d1 execute`, idempotently. Already-applied files
|
|
49
|
+
* are tracked in `.saacms/migrations/applied.json` so re-runs skip them; the
|
|
50
|
+
* ledger is persisted after EACH success so a mid-batch failure never loses
|
|
51
|
+
* the record of what already applied. Returns the intended exit code.
|
|
52
|
+
*/
|
|
53
|
+
declare function runMigrateRun(args: MigrateRunArgs, deps?: MigrateRunDeps): Promise<number>;
|
|
54
|
+
declare const runSubCommand: import("citty").CommandDef<{
|
|
55
|
+
target: {
|
|
56
|
+
type: "string";
|
|
57
|
+
description: string;
|
|
58
|
+
default: string;
|
|
59
|
+
};
|
|
60
|
+
cwd: {
|
|
61
|
+
type: "string";
|
|
62
|
+
description: string;
|
|
63
|
+
required: false;
|
|
64
|
+
};
|
|
65
|
+
database: {
|
|
66
|
+
type: "string";
|
|
67
|
+
description: string;
|
|
68
|
+
required: false;
|
|
69
|
+
};
|
|
70
|
+
dry: {
|
|
71
|
+
type: "boolean";
|
|
72
|
+
description: string;
|
|
73
|
+
default: false;
|
|
74
|
+
};
|
|
75
|
+
}>;
|
|
76
|
+
declare const checkSubCommand: import("citty").CommandDef<{
|
|
77
|
+
strict: {
|
|
78
|
+
type: "boolean";
|
|
79
|
+
description: string;
|
|
80
|
+
default: false;
|
|
81
|
+
};
|
|
82
|
+
cwd: {
|
|
83
|
+
type: "string";
|
|
84
|
+
description: string;
|
|
85
|
+
required: false;
|
|
86
|
+
};
|
|
87
|
+
"break-glass": {
|
|
88
|
+
type: "string";
|
|
89
|
+
description: string;
|
|
90
|
+
required: false;
|
|
91
|
+
};
|
|
92
|
+
}>;
|
|
93
|
+
export { generateSubCommand, runSubCommand, checkSubCommand, runMigrateRun };
|
|
94
|
+
export declare const migrateCommand: import("citty").CommandDef<import("citty").ArgsDef>;
|
|
95
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAsMH,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;EA8JtB,CAAA;AAIF,UAAU,UAAU;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,CACd,GAAG,EAAE,MAAM,EAAE,EACb,GAAG,EAAE,MAAM,KACR,OAAO,CAAC,UAAU,CAAC,CAAA;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CACvB;AA6CD;;;;;;GAMG;AACH,iBAAe,aAAa,CAC1B,IAAI,EAAE,cAAc,EACpB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,MAAM,CAAC,CAiFjB;AAED,QAAA,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;EAsCjB,CAAA;AAEF,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;EAsInB,CAAA;AAEF,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,CAAA;AAE5E,eAAO,MAAM,cAAc,qDAUzB,CAAA"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `saacms migrate` — DB and content migrations. Per ADR 0012:
|
|
3
|
+
*
|
|
4
|
+
* - `generate` diffs Effect Schemas against the last snapshot and emits
|
|
5
|
+
* Drizzle SQL files into `saacms/migrations/db/` and content-transform TS
|
|
6
|
+
* files into `saacms/migrations/content/`.
|
|
7
|
+
* - `run` applies pending migrations against the configured target.
|
|
8
|
+
* - `check` exits non-zero on pending migrations or detected Schema drift;
|
|
9
|
+
* intended for `predeploy` CI use.
|
|
10
|
+
*
|
|
11
|
+
* Scaffold only. Each sub-command logs what it would do.
|
|
12
|
+
*/
|
|
13
|
+
import { defineCommand } from "citty";
|
|
14
|
+
const generateSubCommand = defineCommand({
|
|
15
|
+
meta: {
|
|
16
|
+
name: "generate",
|
|
17
|
+
description: "Diff Schemas and emit DB + content migration files (saacms/migrations/{db,content}/).",
|
|
18
|
+
},
|
|
19
|
+
args: {
|
|
20
|
+
name: {
|
|
21
|
+
type: "positional",
|
|
22
|
+
description: "Human-readable name for the migration (becomes the file slug).",
|
|
23
|
+
required: false,
|
|
24
|
+
},
|
|
25
|
+
rename: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Explicit rename hint, e.g. --rename=posts.body:posts.content. Skips the interactive prompt.",
|
|
28
|
+
required: false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
run({ args }) {
|
|
32
|
+
const name = args.name ?? "<unnamed>";
|
|
33
|
+
console.log(`saacms migrate generate '${name}' — would diff schemas and emit migrations (ADR 0012).`);
|
|
34
|
+
if (args.rename) {
|
|
35
|
+
console.log(` rename hint: ${args.rename}`);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const runSubCommand = defineCommand({
|
|
40
|
+
meta: {
|
|
41
|
+
name: "run",
|
|
42
|
+
description: "Apply pending DB + content migrations against the configured target.",
|
|
43
|
+
},
|
|
44
|
+
args: {
|
|
45
|
+
target: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "Migration target: 'local' (default) or 'remote'.",
|
|
48
|
+
default: "local",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
run({ args }) {
|
|
52
|
+
console.log(`saacms migrate run --target=${args.target} — would apply pending migrations (ADR 0012).`);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const checkSubCommand = defineCommand({
|
|
56
|
+
meta: {
|
|
57
|
+
name: "check",
|
|
58
|
+
description: "Exit non-zero if migrations are pending or Schema drift is detected. Use as a `predeploy` step.",
|
|
59
|
+
},
|
|
60
|
+
args: {
|
|
61
|
+
strict: {
|
|
62
|
+
type: "boolean",
|
|
63
|
+
description: "Treat any pending migration or Schema drift as an error.",
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
run({ args }) {
|
|
68
|
+
console.log(`saacms migrate check --strict=${args.strict} — would compare Schema fingerprint to migration ledger (ADR 0012).`);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
export const migrateCommand = defineCommand({
|
|
72
|
+
meta: {
|
|
73
|
+
name: "migrate",
|
|
74
|
+
description: "Generate, run, or check DB + content migrations (per ADR 0012).",
|
|
75
|
+
},
|
|
76
|
+
subCommands: {
|
|
77
|
+
generate: generateSubCommand,
|
|
78
|
+
run: runSubCommand,
|
|
79
|
+
check: checkSubCommand,
|
|
80
|
+
},
|
|
81
|
+
});
|