@camox/cli 0.8.0 → 0.9.1

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,3 @@
1
+ #!/usr/bin/env node
2
+ import { o as verifySession } from "./api-QINint5w.mjs";
3
+ export { verifySession };
@@ -52,11 +52,15 @@ async function setActiveOrganization(token, organizationId) {
52
52
  });
53
53
  if (!res.ok) throw new Error(`Failed to set active organization: ${res.status}`);
54
54
  }
55
- async function createProject(token, name, organizationId) {
55
+ async function checkSlugAvailability(token, slug) {
56
+ return createRpcClient(token).projects.checkSlugAvailability({ slug });
57
+ }
58
+ async function createProject(token, name, slug, organizationId) {
56
59
  return await createRpcClient(token).projects.create({
57
60
  name,
61
+ slug,
58
62
  organizationId
59
63
  });
60
64
  }
61
65
  //#endregion
62
- export { verifySession as a, setActiveOrganization as i, createProject as n, listOrganizations as r, createOrganization as t };
66
+ export { setActiveOrganization as a, listOrganizations as i, createOrganization as n, verifySession as o, createProject as r, checkSlugAvailability as t };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as setActiveOrganization, n as createProject, r as listOrganizations, t as createOrganization } from "./api-57fjJbHn.mjs";
2
+ import { a as setActiveOrganization, i as listOrganizations, n as createOrganization, r as createProject, t as checkSlugAvailability } from "./api-QINint5w.mjs";
3
3
  import { object, or } from "@optique/core/constructs";
4
4
  import { message } from "@optique/core/message";
5
5
  import { defineProgram } from "@optique/core/program";
@@ -9,7 +9,9 @@ import fs from "node:fs";
9
9
  import path from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import * as p from "@clack/prompts";
12
+ import { log } from "@clack/prompts";
12
13
  import { command, constant } from "@optique/core/primitives";
14
+ import slugify from "slugify";
13
15
  import http from "node:http";
14
16
  import os from "node:os";
15
17
  //#region \0rolldown/runtime.js
@@ -172,7 +174,7 @@ async function authenticateUser() {
172
174
  async function getOrAuthenticate() {
173
175
  const stored = readAuthToken();
174
176
  if (stored) {
175
- const { verifySession } = await import("./api-BB93iDN7.mjs");
177
+ const { verifySession } = await import("./api-CB7vjDU7.mjs");
176
178
  if (await verifySession(stored.token)) {
177
179
  p.log.info(`Authenticated as ${stored.name}`);
178
180
  return stored;
@@ -202,9 +204,6 @@ const pmCommands = {
202
204
  dev: "yarn dev"
203
205
  }
204
206
  };
205
- function slugify(name) {
206
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
207
- }
208
207
  function copyDir(src, dest, replacements) {
209
208
  fs.mkdirSync(dest, { recursive: true });
210
209
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
@@ -266,7 +265,10 @@ async function promptCreateOrganization(token) {
266
265
  }
267
266
  });
268
267
  if (p.isCancel(orgName)) return onCancel();
269
- const org = await createOrganization(token, orgName, slugify(orgName));
268
+ const org = await createOrganization(token, orgName, slugify(orgName, {
269
+ lower: true,
270
+ strict: true
271
+ }));
270
272
  p.log.success(`Created organization: ${org.name}`);
271
273
  return org.id;
272
274
  }
@@ -275,40 +277,63 @@ async function init() {
275
277
  const stored = readAuthToken();
276
278
  if (stored) p.log.info(`Welcome back, ${stored.name}!`);
277
279
  p.log.info("Let's create your Camox application.");
278
- const result = await p.group({
279
- name: () => p.text({
280
- message: "Project display name",
281
- placeholder: "My Website",
282
- validate: (value) => {
283
- if (!value.trim()) return "Project name is required";
284
- }
285
- }),
286
- path: ({ results }) => p.text({
287
- message: "Project path",
288
- initialValue: `./${slugify(results.name ?? "") || "my-site"}`,
289
- validate: (value) => {
290
- if (!value.trim()) return "Path is required";
291
- }
292
- })
293
- }, { onCancel });
294
- const targetDir = path.resolve(result.path);
280
+ const name = await p.text({
281
+ message: "Project display name",
282
+ placeholder: "My Website",
283
+ validate: (value) => {
284
+ if (!value.trim()) return "Project name is required";
285
+ }
286
+ });
287
+ if (p.isCancel(name)) return onCancel();
295
288
  const auth = await getOrAuthenticate();
296
289
  const orgId = await selectOrCreateOrganization(auth.token);
290
+ let projectSlug;
291
+ while (true) {
292
+ const slugInput = await p.text({
293
+ message: "Project slug",
294
+ initialValue: slugify(name, {
295
+ lower: true,
296
+ strict: true
297
+ }) || "my-site",
298
+ validate: (value) => {
299
+ if (!value.trim()) return "Slug is required";
300
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) return "Slug must be lowercase alphanumeric with hyphens";
301
+ }
302
+ });
303
+ if (p.isCancel(slugInput)) return onCancel();
304
+ const s = p.spinner();
305
+ s.start("Checking slug availability...");
306
+ const { available } = await checkSlugAvailability(auth.token, slugInput);
307
+ if (available) {
308
+ s.stop("Slug is available!");
309
+ projectSlug = slugInput;
310
+ break;
311
+ }
312
+ s.stop(`Slug "${slugInput}" is already taken. Please choose another.`);
313
+ }
314
+ const projectPath = await p.text({
315
+ message: "Project path",
316
+ initialValue: `./${projectSlug}`,
317
+ validate: (value) => {
318
+ if (!value.trim()) return "Path is required";
319
+ const resolved = path.resolve(value);
320
+ if (fs.existsSync(resolved) && fs.readdirSync(resolved).length > 0) return "Directory is not empty";
321
+ }
322
+ });
323
+ if (p.isCancel(projectPath)) return onCancel();
324
+ const resolvedPath = projectPath;
325
+ const targetDir = path.resolve(resolvedPath);
297
326
  const s0 = p.spinner();
298
327
  s0.start("Creating project...");
299
328
  let project;
300
329
  try {
301
- project = await createProject(auth.token, result.name, orgId);
330
+ project = await createProject(auth.token, name, projectSlug, orgId);
302
331
  s0.stop(`Project created with slug: ${project.slug}`);
303
332
  } catch (err) {
304
333
  s0.stop("Failed to create project.");
305
334
  p.log.error(err instanceof Error ? err.message : "Unknown error");
306
335
  process.exit(1);
307
336
  }
308
- if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
309
- p.cancel(`Directory ${targetDir} is not empty.`);
310
- process.exit(1);
311
- }
312
337
  const selected = await p.select({
313
338
  message: "Which package manager?",
314
339
  options: [
@@ -335,7 +360,7 @@ async function init() {
335
360
  const s = p.spinner();
336
361
  s.start("Scaffolding project...");
337
362
  copyDir(path.resolve(__dirname, "..", "template"), targetDir, {
338
- "{{projectName}}": result.name,
363
+ "{{projectName}}": name,
339
364
  "{{projectSlug}}": project.slug,
340
365
  "{{camoxVersion}}": ownPkg.version
341
366
  });
@@ -363,7 +388,7 @@ src/routeTree.gen.ts
363
388
  s.stop("Project scaffolded!");
364
389
  function dropIntoProject() {
365
390
  const shell = process.env.SHELL || "/bin/bash";
366
- p.log.info(`Dropping you into ${result.path}`);
391
+ p.log.info(`Dropping you into ${resolvedPath}`);
367
392
  spawnSync(shell, [], {
368
393
  cwd: targetDir,
369
394
  stdio: "inherit"
@@ -432,13 +457,14 @@ var logout_exports = /* @__PURE__ */ __exportAll({
432
457
  const parser = command("logout", object({ command: constant("logout") }));
433
458
  const handler = logout;
434
459
  function logout() {
460
+ p.intro("camox logout");
435
461
  const token = readAuthToken();
436
462
  if (!token) {
437
- console.log("Not logged in.");
463
+ log.error("Not logged in.");
438
464
  return;
439
465
  }
440
466
  removeAuthToken();
441
- console.log(`Logged out from ${token.name}.`);
467
+ p.log.success(`Logged out from ${token.name}.`);
442
468
  }
443
469
  //#endregion
444
470
  //#region src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camox/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "bin": {
5
5
  "camox": "./dist/index.mjs"
6
6
  },
@@ -18,14 +18,15 @@
18
18
  "@optique/core": "*",
19
19
  "@optique/run": "*",
20
20
  "@orpc/client": "^1.13.14",
21
- "@orpc/server": "^1.13.14"
21
+ "@orpc/server": "^1.13.14",
22
+ "slugify": "^1.6.9"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/node": "^24.12.2",
25
26
  "@typescript/native-preview": "7.0.0-dev.20260412.1",
26
27
  "oxlint": "^0.15.0",
27
28
  "tsdown": "^0.21.8",
28
- "@camox/api-contract": "0.8.0"
29
+ "@camox/api-contract": "0.9.1"
29
30
  },
30
31
  "scripts": {
31
32
  "build": "tsdown",
@@ -129,4 +129,7 @@ body {
129
129
  body {
130
130
  @apply bg-background text-foreground;
131
131
  }
132
+ a:focus-visible {
133
+ @apply border-ring ring-ring/50 rounded-md ring-3 outline-none;
134
+ }
132
135
  }
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { a as verifySession } from "./api-57fjJbHn.mjs";
3
- export { verifySession };