@solcreek/cli 0.4.14 → 0.4.15

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.
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Factory for resource subcommands (creek db, creek storage, creek cache).
3
+ *
4
+ * All resource kinds share the same CRUD shape:
5
+ * ls, create, attach, detach, rename, delete
6
+ *
7
+ * Only the `kind` value and display labels differ.
8
+ */
9
+ import { defineCommand } from "citty";
10
+ import consola from "consola";
11
+ import { CreekClient } from "@solcreek/sdk";
12
+ import { getToken, getApiUrl } from "../utils/config.js";
13
+ import { globalArgs, resolveJsonMode, jsonOutput, AUTH_BREADCRUMBS, } from "../utils/output.js";
14
+ function requireToken(jsonMode) {
15
+ const token = getToken();
16
+ if (!token) {
17
+ if (jsonMode)
18
+ jsonOutput({ ok: false, error: "not_authenticated" }, 1, AUTH_BREADCRUMBS);
19
+ consola.error("Not authenticated. Run `creek login` first.");
20
+ process.exit(1);
21
+ }
22
+ return token;
23
+ }
24
+ async function findByName(client, name, kind) {
25
+ const { resources } = await client.listResources();
26
+ return resources.find((r) => r.name === name && r.kind === kind) ?? null;
27
+ }
28
+ export function createResourceCommand(opts) {
29
+ const { kind, label, defaultBinding } = opts;
30
+ const cmdName = kind === "database" ? "db" : kind === "cache" ? "cache" : kind;
31
+ const ls = defineCommand({
32
+ meta: { name: "ls", description: `List ${label}s in the current team` },
33
+ args: { ...globalArgs },
34
+ async run({ args }) {
35
+ const jsonMode = resolveJsonMode(args);
36
+ const token = requireToken(jsonMode);
37
+ const client = new CreekClient(getApiUrl(), token);
38
+ const { resources } = await client.listResources();
39
+ const filtered = resources.filter((r) => r.kind === kind);
40
+ if (jsonMode)
41
+ jsonOutput({ ok: true, [kind === "database" ? "databases" : kind]: filtered }, 0);
42
+ if (filtered.length === 0) {
43
+ consola.info(`No ${label}s. Run \`creek ${cmdName} create <name>\` to provision one.`);
44
+ return;
45
+ }
46
+ consola.info(`${filtered.length} ${label}(s):\n`);
47
+ for (const r of filtered) {
48
+ const backing = r.cfResourceId ? `cf:${r.cfResourceType}/${r.cfResourceId.slice(0, 8)}` : "unprovisioned";
49
+ consola.log(` ${r.name.padEnd(24)} ${backing} ${r.status}`);
50
+ }
51
+ },
52
+ });
53
+ const create = defineCommand({
54
+ meta: { name: "create", description: `Create a new team ${label}. Backing CF resource is auto-provisioned.` },
55
+ args: {
56
+ name: { type: "positional", description: `${label} name (lowercase, dash/underscore, ≤63 chars)`, required: true },
57
+ ...globalArgs,
58
+ },
59
+ async run({ args }) {
60
+ const jsonMode = resolveJsonMode(args);
61
+ const token = requireToken(jsonMode);
62
+ const client = new CreekClient(getApiUrl(), token);
63
+ try {
64
+ const created = await client.createResource({ kind, name: args.name });
65
+ if (jsonMode)
66
+ jsonOutput({ ok: true, resource: created }, 0);
67
+ consola.success(`Created ${label} "${created.name}" (${created.id.slice(0, 8)})`);
68
+ consola.info(` Attach with: creek ${cmdName} attach ${created.name} --to <project> --as ${defaultBinding}`);
69
+ }
70
+ catch (err) {
71
+ const msg = err instanceof Error ? err.message : String(err);
72
+ if (jsonMode)
73
+ jsonOutput({ ok: false, error: "create_failed", message: msg }, 1);
74
+ consola.error(msg);
75
+ process.exit(1);
76
+ }
77
+ },
78
+ });
79
+ const attach = defineCommand({
80
+ meta: { name: "attach", description: `Attach a ${label} to a project under the given ENV var name` },
81
+ args: {
82
+ name: { type: "positional", description: `${label} name (as shown by \`creek ${cmdName} ls\`)`, required: true },
83
+ to: { type: "string", description: "Project slug to attach to", required: true },
84
+ as: { type: "string", description: `ENV var name (uppercase, default: ${defaultBinding})`, default: defaultBinding },
85
+ ...globalArgs,
86
+ },
87
+ async run({ args }) {
88
+ const jsonMode = resolveJsonMode(args);
89
+ const token = requireToken(jsonMode);
90
+ const client = new CreekClient(getApiUrl(), token);
91
+ const resource = await findByName(client, args.name, kind);
92
+ if (!resource) {
93
+ const msg = `No ${label} named "${args.name}"`;
94
+ if (jsonMode)
95
+ jsonOutput({ ok: false, error: "not_found", message: msg }, 1);
96
+ consola.error(msg);
97
+ process.exit(1);
98
+ }
99
+ try {
100
+ const binding = await client.attachBinding(args.to, { resourceId: resource.id, bindingName: args.as });
101
+ if (jsonMode)
102
+ jsonOutput({ ok: true, binding }, 0);
103
+ consola.success(`Attached "${resource.name}" → ${args.to} as env.${args.as}`);
104
+ }
105
+ catch (err) {
106
+ const msg = err instanceof Error ? err.message : String(err);
107
+ if (jsonMode)
108
+ jsonOutput({ ok: false, error: "attach_failed", message: msg }, 1);
109
+ consola.error(msg);
110
+ process.exit(1);
111
+ }
112
+ },
113
+ });
114
+ const detach = defineCommand({
115
+ meta: { name: "detach", description: `Remove a ${label} binding from a project` },
116
+ args: {
117
+ name: { type: "positional", description: `${label} name`, required: true },
118
+ from: { type: "string", description: "Project slug to detach from", required: true },
119
+ as: { type: "string", description: `ENV var name (default: ${defaultBinding})`, default: defaultBinding },
120
+ ...globalArgs,
121
+ },
122
+ async run({ args }) {
123
+ const jsonMode = resolveJsonMode(args);
124
+ const token = requireToken(jsonMode);
125
+ const client = new CreekClient(getApiUrl(), token);
126
+ try {
127
+ await client.detachBinding(args.from, args.as);
128
+ if (jsonMode)
129
+ jsonOutput({ ok: true }, 0);
130
+ consola.success(`Detached env.${args.as} from ${args.from}`);
131
+ }
132
+ catch (err) {
133
+ const msg = err instanceof Error ? err.message : String(err);
134
+ if (jsonMode)
135
+ jsonOutput({ ok: false, error: "detach_failed", message: msg }, 1);
136
+ consola.error(msg);
137
+ process.exit(1);
138
+ }
139
+ },
140
+ });
141
+ const rename = defineCommand({
142
+ meta: { name: "rename", description: `Rename a ${label} (stable UUID is preserved; all bindings keep working)` },
143
+ args: {
144
+ name: { type: "positional", description: `Current ${label} name`, required: true },
145
+ to: { type: "string", description: "New name", required: true },
146
+ ...globalArgs,
147
+ },
148
+ async run({ args }) {
149
+ const jsonMode = resolveJsonMode(args);
150
+ const token = requireToken(jsonMode);
151
+ const client = new CreekClient(getApiUrl(), token);
152
+ const resource = await findByName(client, args.name, kind);
153
+ if (!resource) {
154
+ const msg = `No ${label} named "${args.name}"`;
155
+ if (jsonMode)
156
+ jsonOutput({ ok: false, error: "not_found", message: msg }, 1);
157
+ consola.error(msg);
158
+ process.exit(1);
159
+ }
160
+ try {
161
+ const renamed = await client.renameResource(resource.id, args.to);
162
+ if (jsonMode)
163
+ jsonOutput({ ok: true, resource: renamed }, 0);
164
+ consola.success(`Renamed "${args.name}" → "${args.to}"`);
165
+ }
166
+ catch (err) {
167
+ const msg = err instanceof Error ? err.message : String(err);
168
+ if (jsonMode)
169
+ jsonOutput({ ok: false, error: "rename_failed", message: msg }, 1);
170
+ consola.error(msg);
171
+ process.exit(1);
172
+ }
173
+ },
174
+ });
175
+ const del = defineCommand({
176
+ meta: { name: "delete", description: `Delete a ${label}. Fails if any project still binds to it — detach first.` },
177
+ args: {
178
+ name: { type: "positional", description: `${label} name`, required: true },
179
+ ...globalArgs,
180
+ },
181
+ async run({ args }) {
182
+ const jsonMode = resolveJsonMode(args);
183
+ const token = requireToken(jsonMode);
184
+ const client = new CreekClient(getApiUrl(), token);
185
+ const resource = await findByName(client, args.name, kind);
186
+ if (!resource) {
187
+ const msg = `No ${label} named "${args.name}"`;
188
+ if (jsonMode)
189
+ jsonOutput({ ok: false, error: "not_found", message: msg }, 1);
190
+ consola.error(msg);
191
+ process.exit(1);
192
+ }
193
+ try {
194
+ await client.deleteResource(resource.id);
195
+ if (jsonMode)
196
+ jsonOutput({ ok: true }, 0);
197
+ consola.success(`Deleted ${label} "${args.name}"`);
198
+ }
199
+ catch (err) {
200
+ const msg = err instanceof Error ? err.message : String(err);
201
+ if (jsonMode)
202
+ jsonOutput({ ok: false, error: "delete_failed", message: msg }, 1);
203
+ consola.error(msg);
204
+ process.exit(1);
205
+ }
206
+ },
207
+ });
208
+ return defineCommand({
209
+ meta: {
210
+ name: cmdName,
211
+ description: `Manage team-owned ${label}s. Each ${label} is a stable, renameable resource that can be attached to one or more projects.`,
212
+ },
213
+ subCommands: { ls, create, attach, detach, rename, delete: del },
214
+ });
215
+ }
216
+ //# sourceMappingURL=resource-cmd.js.map
@@ -0,0 +1,2 @@
1
+ export declare const storageCommand: import("citty").CommandDef<import("citty").ArgsDef>;
2
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1,7 @@
1
+ import { createResourceCommand } from "./resource-cmd.js";
2
+ export const storageCommand = createResourceCommand({
3
+ kind: "storage",
4
+ label: "storage bucket",
5
+ defaultBinding: "STORAGE",
6
+ });
7
+ //# sourceMappingURL=storage.js.map
package/dist/index.js CHANGED
@@ -20,6 +20,9 @@ import { opsCommand } from "./commands/ops.js";
20
20
  import { queueCommand } from "./commands/queue.js";
21
21
  import { logsCommand } from "./commands/logs.js";
22
22
  import { doctorCommand } from "./commands/doctor.js";
23
+ import { dbCommand } from "./commands/db.js";
24
+ import { storageCommand } from "./commands/storage.js";
25
+ import { cacheCommand } from "./commands/cache.js";
23
26
  const __dirname = dirname(fileURLToPath(import.meta.url));
24
27
  const cliPkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
25
28
  // Read version from the "creek" facade package (what users install),
@@ -53,6 +56,9 @@ const main = defineCommand({
53
56
  claim: claimCommand,
54
57
  env: envCommand,
55
58
  queue: queueCommand,
59
+ db: dbCommand,
60
+ storage: storageCommand,
61
+ cache: cacheCommand,
56
62
  domains: domainsCommand,
57
63
  rollback: rollbackCommand,
58
64
  ops: opsCommand,
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Build log emitter for the CLI.
3
+ *
4
+ * Accumulates structured ndjson lines during a `creek deploy` run and
5
+ * POSTs them to control-plane once the deployment reaches a terminal
6
+ * state. The content is high-level phase markers — install / build /
7
+ * bundle / upload / activate — not the full stdout of subprocesses.
8
+ * Capturing the user's build stdout is a Phase 2 concern; for now
9
+ * the terminal is still where they see npm output live.
10
+ */
11
+ type Step = "clone" | "detect" | "install" | "build" | "bundle" | "upload" | "provision" | "activate" | "cleanup";
12
+ type Stream = "stdout" | "stderr" | "creek";
13
+ type Level = "debug" | "info" | "warn" | "error" | "fatal";
14
+ export interface BuildLogLine {
15
+ ts: number;
16
+ step: Step;
17
+ stream: Stream;
18
+ level: Level;
19
+ msg: string;
20
+ code?: string;
21
+ }
22
+ export declare class BuildLogEmitter {
23
+ private lines;
24
+ readonly startedAt: number;
25
+ log(step: Step, level: Level, msg: string, opts?: {
26
+ stream?: Stream;
27
+ code?: string;
28
+ }): void;
29
+ info(step: Step, msg: string, code?: string): void;
30
+ warn(step: Step, msg: string, code?: string): void;
31
+ error(step: Step, msg: string, code?: string): void;
32
+ toNdjson(): string;
33
+ get count(): number;
34
+ }
35
+ export {};
36
+ //# sourceMappingURL=build-log.d.ts.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Build log emitter for the CLI.
3
+ *
4
+ * Accumulates structured ndjson lines during a `creek deploy` run and
5
+ * POSTs them to control-plane once the deployment reaches a terminal
6
+ * state. The content is high-level phase markers — install / build /
7
+ * bundle / upload / activate — not the full stdout of subprocesses.
8
+ * Capturing the user's build stdout is a Phase 2 concern; for now
9
+ * the terminal is still where they see npm output live.
10
+ */
11
+ export class BuildLogEmitter {
12
+ lines = [];
13
+ startedAt = Date.now();
14
+ log(step, level, msg, opts) {
15
+ this.lines.push({
16
+ ts: Date.now(),
17
+ step,
18
+ stream: opts?.stream ?? "creek",
19
+ level,
20
+ msg,
21
+ ...(opts?.code ? { code: opts.code } : {}),
22
+ });
23
+ }
24
+ info(step, msg, code) {
25
+ this.log(step, "info", msg, { code });
26
+ }
27
+ warn(step, msg, code) {
28
+ this.log(step, "warn", msg, { code });
29
+ }
30
+ error(step, msg, code) {
31
+ this.log(step, "error", msg, { code });
32
+ }
33
+ toNdjson() {
34
+ return this.lines.map((l) => JSON.stringify(l)).join("\n");
35
+ }
36
+ get count() {
37
+ return this.lines.length;
38
+ }
39
+ }
40
+ //# sourceMappingURL=build-log.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solcreek/cli",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "description": "CLI for the Creek deployment platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,7 @@
33
33
  "esbuild": "^0.25.0",
34
34
  "smol-toml": "^1.3.1",
35
35
  "ws": "^8.20.0",
36
- "@solcreek/sdk": "0.4.6"
36
+ "@solcreek/sdk": "0.4.7"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@testing-library/dom": "^10.4.1",