@camox/cli 0.8.0 → 0.9.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.
@@ -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";
@@ -10,6 +10,7 @@ import path from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
  import * as p from "@clack/prompts";
12
12
  import { command, constant } from "@optique/core/primitives";
13
+ import slugify from "slugify";
13
14
  import http from "node:http";
14
15
  import os from "node:os";
15
16
  //#region \0rolldown/runtime.js
@@ -172,7 +173,7 @@ async function authenticateUser() {
172
173
  async function getOrAuthenticate() {
173
174
  const stored = readAuthToken();
174
175
  if (stored) {
175
- const { verifySession } = await import("./api-BB93iDN7.mjs");
176
+ const { verifySession } = await import("./api-CB7vjDU7.mjs");
176
177
  if (await verifySession(stored.token)) {
177
178
  p.log.info(`Authenticated as ${stored.name}`);
178
179
  return stored;
@@ -202,9 +203,6 @@ const pmCommands = {
202
203
  dev: "yarn dev"
203
204
  }
204
205
  };
205
- function slugify(name) {
206
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
207
- }
208
206
  function copyDir(src, dest, replacements) {
209
207
  fs.mkdirSync(dest, { recursive: true });
210
208
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
@@ -266,7 +264,10 @@ async function promptCreateOrganization(token) {
266
264
  }
267
265
  });
268
266
  if (p.isCancel(orgName)) return onCancel();
269
- const org = await createOrganization(token, orgName, slugify(orgName));
267
+ const org = await createOrganization(token, orgName, slugify(orgName, {
268
+ lower: true,
269
+ strict: true
270
+ }));
270
271
  p.log.success(`Created organization: ${org.name}`);
271
272
  return org.id;
272
273
  }
@@ -275,40 +276,63 @@ async function init() {
275
276
  const stored = readAuthToken();
276
277
  if (stored) p.log.info(`Welcome back, ${stored.name}!`);
277
278
  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);
279
+ const name = await 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
+ if (p.isCancel(name)) return onCancel();
295
287
  const auth = await getOrAuthenticate();
296
288
  const orgId = await selectOrCreateOrganization(auth.token);
289
+ let projectSlug;
290
+ while (true) {
291
+ const slugInput = await p.text({
292
+ message: "Project slug",
293
+ initialValue: slugify(name, {
294
+ lower: true,
295
+ strict: true
296
+ }) || "my-site",
297
+ validate: (value) => {
298
+ if (!value.trim()) return "Slug is required";
299
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value)) return "Slug must be lowercase alphanumeric with hyphens";
300
+ }
301
+ });
302
+ if (p.isCancel(slugInput)) return onCancel();
303
+ const s = p.spinner();
304
+ s.start("Checking slug availability...");
305
+ const { available } = await checkSlugAvailability(auth.token, slugInput);
306
+ if (available) {
307
+ s.stop("Slug is available!");
308
+ projectSlug = slugInput;
309
+ break;
310
+ }
311
+ s.stop(`Slug "${slugInput}" is already taken. Please choose another.`);
312
+ }
313
+ const projectPath = await p.text({
314
+ message: "Project path",
315
+ initialValue: `./${projectSlug}`,
316
+ validate: (value) => {
317
+ if (!value.trim()) return "Path is required";
318
+ const resolved = path.resolve(value);
319
+ if (fs.existsSync(resolved) && fs.readdirSync(resolved).length > 0) return "Directory is not empty";
320
+ }
321
+ });
322
+ if (p.isCancel(projectPath)) return onCancel();
323
+ const resolvedPath = projectPath;
324
+ const targetDir = path.resolve(resolvedPath);
297
325
  const s0 = p.spinner();
298
326
  s0.start("Creating project...");
299
327
  let project;
300
328
  try {
301
- project = await createProject(auth.token, result.name, orgId);
329
+ project = await createProject(auth.token, name, projectSlug, orgId);
302
330
  s0.stop(`Project created with slug: ${project.slug}`);
303
331
  } catch (err) {
304
332
  s0.stop("Failed to create project.");
305
333
  p.log.error(err instanceof Error ? err.message : "Unknown error");
306
334
  process.exit(1);
307
335
  }
308
- if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
309
- p.cancel(`Directory ${targetDir} is not empty.`);
310
- process.exit(1);
311
- }
312
336
  const selected = await p.select({
313
337
  message: "Which package manager?",
314
338
  options: [
@@ -335,7 +359,7 @@ async function init() {
335
359
  const s = p.spinner();
336
360
  s.start("Scaffolding project...");
337
361
  copyDir(path.resolve(__dirname, "..", "template"), targetDir, {
338
- "{{projectName}}": result.name,
362
+ "{{projectName}}": name,
339
363
  "{{projectSlug}}": project.slug,
340
364
  "{{camoxVersion}}": ownPkg.version
341
365
  });
@@ -363,7 +387,7 @@ src/routeTree.gen.ts
363
387
  s.stop("Project scaffolded!");
364
388
  function dropIntoProject() {
365
389
  const shell = process.env.SHELL || "/bin/bash";
366
- p.log.info(`Dropping you into ${result.path}`);
390
+ p.log.info(`Dropping you into ${resolvedPath}`);
367
391
  spawnSync(shell, [], {
368
392
  cwd: targetDir,
369
393
  stdio: "inherit"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camox/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
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.0"
29
30
  },
30
31
  "scripts": {
31
32
  "build": "tsdown",
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- import { a as verifySession } from "./api-57fjJbHn.mjs";
3
- export { verifySession };