@simple-auth-kit/cli 1.1.0 → 1.3.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 CHANGED
@@ -22,7 +22,8 @@ npx @simple-auth-kit/cli add <combo> [--workspaces] # install a specific combo
22
22
  npx @simple-auth-kit/cli add # guided picker, same flow as init (no config write)
23
23
  npx @simple-auth-kit/cli update [--check] # re-installs whatever add last recorded — no args needed
24
24
  npx @simple-auth-kit/cli diff # shows the actual content diff for every file that differs from the registry (api combos only)
25
- npx @simple-auth-kit/cli # full command/flag reference + available combos
25
+ npx @simple-auth-kit/cli # bare, in a real terminal: same guided picker as `add`
26
+ npx @simple-auth-kit/cli --help # full command/flag reference + available combos
26
27
  ```
27
28
 
28
29
  `--into <path>` targets any directory (defaults to the current one). In a real terminal,
@@ -30,20 +31,40 @@ without `--force` or `--check`, a file that's changed locally since install prom
30
31
  per file — before it's overwritten; outside a TTY (CI/scripts) it's left alone by default and
31
32
  reported at the end, same as `--force` always applying and `--check` never writing anything.
32
33
 
33
- **Dependencies are installed for you**, shadcn-`add`-style — no separate `npm install` step.
34
- The package manager is auto-detected from a lockfile already in the target directory (npm if
35
- none found); override it with `--pm <npm|pnpm|yarn|bun>`, or skip the install entirely with
36
- `--skip-install` if you'd rather review `package.json` first.
34
+ **Dependencies are installed for you**, shadcn-`add`-style — no separate `npm install` step, and
35
+ version-pinned to match what each combo is actually built and tested against (not just bare
36
+ package names left to resolve to "latest"). The package manager is auto-detected from a lockfile
37
+ already in the target directory; with no lockfile to go on (a brand new directory, most
38
+ commonly) a real terminal is asked which one to use. Override either with
39
+ `--pm <npm|pnpm|yarn|bun>`, or skip the install entirely with `--skip-install` if you'd rather
40
+ review `package.json` first.
37
41
 
38
42
  ## Available combos
39
43
 
40
- | Kind | Combo | Installs as |
41
- |---|---|---|
42
- | `api` | `nestjs-prisma`, `nestjs-drizzle`, `express-prisma`, `express-drizzle` | merged into `src/lib/auth` of an existing project (the Prisma/Drizzle config + schema land at the project root instead — see below) |
43
- | `admin` | `admin-nextjs`, `admin-react` | a whole new standalone app, scaffolded at the target (refuses a non-empty target unless `--force`) |
44
- | `mobile` | `mobile-expo`, `mobile-bare-rn` | a whole new standalone app, scaffolded at the target (same non-empty-target rule) |
44
+ Add `--workspaces` to any of these for the workspaces variant.
45
45
 
46
- Add `--workspaces` to any `api`/`admin`/`mobile` combo for the workspaces variant.
46
+ **`api`** merged into `src/lib/auth` of an existing project (the Prisma/Drizzle config +
47
+ schema land at the project root instead — see below):
48
+ ```bash
49
+ npx @simple-auth-kit/cli add nestjs-prisma --into .
50
+ npx @simple-auth-kit/cli add nestjs-drizzle --into .
51
+ npx @simple-auth-kit/cli add express-prisma --into .
52
+ npx @simple-auth-kit/cli add express-drizzle --into .
53
+ ```
54
+
55
+ **`admin`** — a whole new standalone app, scaffolded at the target (refuses a non-empty target
56
+ unless `--force`):
57
+ ```bash
58
+ npx @simple-auth-kit/cli add admin-nextjs --into ./admin
59
+ npx @simple-auth-kit/cli add admin-react --into ./admin
60
+ ```
61
+
62
+ **`mobile`** — a whole new standalone app, scaffolded at the target (same non-empty-target
63
+ rule):
64
+ ```bash
65
+ npx @simple-auth-kit/cli add mobile-expo --into ./mobile
66
+ npx @simple-auth-kit/cli add mobile-bare-rn --into ./mobile
67
+ ```
47
68
 
48
69
  ## Your own database models are safe
49
70
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-auth-kit/cli",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "license": "MIT",
5
5
  "description": "simple-auth-kit add <combo> — copies registry source (backend combos, admin consoles, mobile apps) into a consumer repo, shadcn-CLI-style. Nothing is ever installed as a runtime dependency of the consumer.",
6
6
  "bin": {
package/registry.json CHANGED
@@ -2,8 +2,8 @@
2
2
  "core": {
3
3
  "dir": "core",
4
4
  "peerDependencies": [
5
- "argon2",
6
- "jose"
5
+ "argon2@^0.45.1",
6
+ "jose@^6.2.8"
7
7
  ]
8
8
  },
9
9
  "variants": {
@@ -26,18 +26,18 @@
26
26
  "workspaces"
27
27
  ],
28
28
  "peerDependencies": [
29
- "@nestjs/common",
30
- "@nestjs/core",
31
- "@nestjs/platform-express",
32
- "@nestjs/swagger",
33
- "@nestjs/throttler",
34
- "reflect-metadata",
35
- "rxjs",
36
- "express",
37
- "@prisma/client",
38
- "@prisma/adapter-pg",
39
- "prisma",
40
- "@casl/ability"
29
+ "@nestjs/common@^11.1.28",
30
+ "@nestjs/core@^11.1.28",
31
+ "@nestjs/platform-express@^11.1.28",
32
+ "@nestjs/swagger@^11.4.6",
33
+ "@nestjs/throttler@^6.5.0",
34
+ "reflect-metadata@^0.2.2",
35
+ "rxjs@^7.8.2",
36
+ "express@^5.2.1",
37
+ "@prisma/client@^7.9.1",
38
+ "@prisma/adapter-pg@^7.9.1",
39
+ "prisma@^7.9.1",
40
+ "@casl/ability@^7.0.1"
41
41
  ],
42
42
  "postInstall": [
43
43
  "Set DATABASE_URL in .env (see .env.example)",
@@ -66,17 +66,17 @@
66
66
  "workspaces"
67
67
  ],
68
68
  "peerDependencies": [
69
- "@nestjs/common",
70
- "@nestjs/core",
71
- "@nestjs/platform-express",
72
- "@nestjs/swagger",
73
- "reflect-metadata",
74
- "rxjs",
75
- "express",
76
- "drizzle-orm",
77
- "pg",
78
- "drizzle-kit",
79
- "@casl/ability"
69
+ "@nestjs/common@^11.1.28",
70
+ "@nestjs/core@^11.1.28",
71
+ "@nestjs/platform-express@^11.1.28",
72
+ "@nestjs/swagger@^11.4.6",
73
+ "reflect-metadata@^0.2.2",
74
+ "rxjs@^7.8.2",
75
+ "express@^5.2.1",
76
+ "drizzle-orm@^0.45.2",
77
+ "pg@^8.23.0",
78
+ "drizzle-kit@^0.31.10",
79
+ "@casl/ability@^7.0.1"
80
80
  ],
81
81
  "postInstall": [
82
82
  "Set DATABASE_URL in .env (see .env.example)",
@@ -104,12 +104,12 @@
104
104
  "workspaces"
105
105
  ],
106
106
  "peerDependencies": [
107
- "express",
108
- "@prisma/client",
109
- "@prisma/adapter-pg",
110
- "prisma",
111
- "swagger-ui-express",
112
- "@casl/ability"
107
+ "express@^5.2.1",
108
+ "@prisma/client@^7.9.1",
109
+ "@prisma/adapter-pg@^7.9.1",
110
+ "prisma@^7.9.1",
111
+ "swagger-ui-express@^5.0.1",
112
+ "@casl/ability@^7.0.1"
113
113
  ],
114
114
  "postInstall": [
115
115
  "Set DATABASE_URL in .env (see .env.example)",
@@ -138,12 +138,12 @@
138
138
  "workspaces"
139
139
  ],
140
140
  "peerDependencies": [
141
- "express",
142
- "drizzle-orm",
143
- "pg",
144
- "drizzle-kit",
145
- "swagger-ui-express",
146
- "@casl/ability"
141
+ "express@^5.2.1",
142
+ "drizzle-orm@^0.45.2",
143
+ "pg@^8.23.0",
144
+ "drizzle-kit@^0.31.10",
145
+ "swagger-ui-express@^5.0.1",
146
+ "@casl/ability@^7.0.1"
147
147
  ],
148
148
  "postInstall": [
149
149
  "Set DATABASE_URL in .env (see .env.example)",
@@ -43,7 +43,7 @@ const KIND_LABELS: Record<Kind, string> = { api: "API (backend)", admin: "Admin
43
43
  const KIND_FRAMEWORK_NOUN: Record<Kind, string> = { api: "API stack", admin: "admin framework", mobile: "mobile framework" };
44
44
 
45
45
  /** Flags that take no value. Everything else consumes the next argv entry. */
46
- const BOOLEAN_FLAGS = new Set(["workspaces", "force", "check", "config-only", "skip-install"]);
46
+ const BOOLEAN_FLAGS = new Set(["workspaces", "force", "check", "config-only", "skip-install", "help"]);
47
47
 
48
48
  interface SimpleAuthKitConfig {
49
49
  path: string;
@@ -183,18 +183,41 @@ async function resolveForcePaths(runDry: () => Promise<{ result: CopyResult }>,
183
183
  const PACKAGE_MANAGERS = ["npm", "pnpm", "yarn", "bun"] as const;
184
184
  type PackageManager = (typeof PACKAGE_MANAGERS)[number];
185
185
 
186
- /** Detected from whichever lockfile is already sitting in targetRoot — npm if none match,
187
- * matching what a fresh `npm install`-first project would have. Overridden by `--pm` when
188
- * given, so e.g. a first-ever install (no lockfile yet to detect from) can still pick pnpm. */
189
- function detectPackageManager(targetRoot: string, flags: Record<string, string | true>): PackageManager {
186
+ /** Detected from whichever lockfile is already sitting in targetRoot — unambiguous, so used
187
+ * without asking. Returns null when nothing on disk says which tool to use and `--pm` wasn't
188
+ * given either, leaving it to the caller to ask (interactive) or default (non-interactive). */
189
+ function detectPackageManagerFromLockfile(targetRoot: string): PackageManager | null {
190
+ if (existsSync(join(targetRoot, "pnpm-lock.yaml"))) return "pnpm";
191
+ if (existsSync(join(targetRoot, "yarn.lock"))) return "yarn";
192
+ if (existsSync(join(targetRoot, "bun.lockb")) || existsSync(join(targetRoot, "bun.lock"))) return "bun";
193
+ if (existsSync(join(targetRoot, "package-lock.json"))) return "npm";
194
+ return null;
195
+ }
196
+
197
+ /** `--pm` wins outright. Otherwise: a lockfile already in targetRoot is unambiguous, so it's used
198
+ * silently. Only when neither says anything — most commonly a scaffold install into an empty
199
+ * directory — and this is a real terminal do we ask; a non-interactive run in that same situation
200
+ * still falls back to npm rather than blocking. */
201
+ async function resolvePackageManager(targetRoot: string, flags: Record<string, string | true>): Promise<PackageManager> {
190
202
  const requested = flagString(flags.pm);
191
203
  if (requested) {
192
204
  if ((PACKAGE_MANAGERS as readonly string[]).includes(requested)) return requested as PackageManager;
193
205
  console.error(`--pm "${requested}" isn't one of ${PACKAGE_MANAGERS.join(", ")} — falling back to auto-detection.`);
194
206
  }
195
- if (existsSync(join(targetRoot, "pnpm-lock.yaml"))) return "pnpm";
196
- if (existsSync(join(targetRoot, "yarn.lock"))) return "yarn";
197
- if (existsSync(join(targetRoot, "bun.lockb")) || existsSync(join(targetRoot, "bun.lock"))) return "bun";
207
+
208
+ const detected = detectPackageManagerFromLockfile(targetRoot);
209
+ if (detected) return detected;
210
+
211
+ if (isTTY()) {
212
+ const picked = await ask<PackageManager>({
213
+ type: "select",
214
+ name: "pm",
215
+ message: "Which package manager?",
216
+ choices: PACKAGE_MANAGERS.map((value) => ({ title: value, value })),
217
+ initial: 0,
218
+ });
219
+ if (picked) return picked;
220
+ }
198
221
  return "npm";
199
222
  }
200
223
 
@@ -204,12 +227,12 @@ function detectPackageManager(targetRoot: string, flags: Record<string, string |
204
227
  * package.json" (the scaffold-mode case — dependencies are already declared, nothing to name).
205
228
  * Skipped entirely under `--skip-install`, or when `deps` is non-empty but there's nothing new
206
229
  * to add (an update that touched no files has nothing worth re-installing for). `--pm
207
- * <npm|pnpm|yarn|bun>` picks the tool explicitly instead of auto-detecting it from a lockfile.
230
+ * <npm|pnpm|yarn|bun>` picks the tool explicitly instead of auto-detecting/asking.
208
231
  */
209
- function installDependencies(targetRoot: string, deps: string[], flags: Record<string, string | true>): void {
232
+ async function installDependencies(targetRoot: string, deps: string[], flags: Record<string, string | true>): Promise<void> {
210
233
  if (flags["skip-install"] === true) return;
211
234
 
212
- const pm = detectPackageManager(targetRoot, flags);
235
+ const pm = await resolvePackageManager(targetRoot, flags);
213
236
  // "install everything in package.json" (zero deps named) is the same bare verb across all
214
237
  // four; naming specific packages is "install <pkgs>" for npm, "add <pkgs>" for the others.
215
238
  const args = deps.length ? [pm === "npm" ? "install" : "add", ...deps] : ["install"];
@@ -435,7 +458,7 @@ async function installMerge(comboName: string, combo: ComboEntry, variant: strin
435
458
  const peerDeps = [...new Set([...registry.core.peerDependencies, ...combo.peerDependencies])];
436
459
  console.log(`\nPeer dependencies: ${peerDeps.join(" ")}`);
437
460
  if (result.updated.length) {
438
- installDependencies(targetRoot, peerDeps, flags);
461
+ await installDependencies(targetRoot, peerDeps, flags);
439
462
  } else if (flags["skip-install"] !== true) {
440
463
  console.log(`(nothing changed this run — skipping install)`);
441
464
  }
@@ -507,7 +530,7 @@ async function installScaffold(comboName: string, combo: ComboEntry, variant: st
507
530
  if (result.updated.length) {
508
531
  // Dependencies are already declared in package.json — no specific packages to name, just
509
532
  // "install whatever's there" (installDependencies with an empty list does exactly that).
510
- installDependencies(targetRoot, [], flags);
533
+ await installDependencies(targetRoot, [], flags);
511
534
  } else if (flags["skip-install"] !== true) {
512
535
  console.log(`\n(nothing changed this run — skipping install)`);
513
536
  }
@@ -785,10 +808,47 @@ async function cmdDiff(targetRoot: string) {
785
808
  }
786
809
  }
787
810
 
811
+ async function printUsage(exitCode: number) {
812
+ const registry = await loadRegistry();
813
+ console.log("Usage: simple-auth-kit <init|add|update|diff> [...]");
814
+ console.log(` init [--config-only] [--kind ...] [--into <path>] (fresh setup: guided "what do you need" picker, like bare "add")`);
815
+ console.log(` --config-only: just write .simple-auth-kit.json and stop, no install`);
816
+ console.log(` add <combo> [--workspaces] [--force] [--skip-install] [--into <path>] [--path <dir>] [--alias <alias>]`);
817
+ console.log(` add [--kind api,admin,mobile] [--framework <name>,...] [--workspaces] [--into <path>] [--name <appName>]`);
818
+ console.log(` (bare "add", or "add" with --kind but no combo, launches a guided prompt for whatever's missing)`);
819
+ console.log(` update [--check] [--force] [--skip-install] [--into <path>] (re-installs whatever combo+variant auth.lock.json already records)`);
820
+ console.log(` --check: report what would change, without writing anything (merge-mode combos only)`);
821
+ console.log(` diff [--into <path>] (shows the actual content diff for every tracked file that differs from the current registry — read-only; merge-mode combos only)`);
822
+ console.log(` In a TTY, without --force or --check: a file changed locally since install prompts to overwrite, per file.`);
823
+ console.log(` --skip-install: don't run the package manager after copying files — the default is to install for you, shadcn-\`add\`-style.`);
824
+ console.log(` --pm <npm|pnpm|yarn|bun>: which package manager to install with — default: detected from a lockfile in the target directory, npm if none found.`);
825
+ console.log(`\nAvailable combos:`);
826
+ for (const kind of ["api", "admin", "mobile"] as Kind[]) {
827
+ const names = combosByKind(registry, kind).map(([name]) => name);
828
+ if (names.length) console.log(` ${KIND_LABELS[kind]}: ${names.join(", ")}`);
829
+ }
830
+ console.log(`\nVariants (choose one at install time):`);
831
+ for (const [name, variant] of Object.entries(registry.variants)) {
832
+ console.log(` ${name}${variant.flag ? ` (${variant.flag})` : " (default)"} — ${variant.description}`);
833
+ }
834
+ process.exitCode = exitCode;
835
+ }
836
+
788
837
  async function main() {
789
838
  const { command, positional, flags } = parseArgs(process.argv.slice(2));
790
839
  const targetRoot = resolve(process.cwd(), flagString(flags.into) ?? ".");
791
840
 
841
+ if (command === "--help" || command === "-h" || command === "help" || flags.help === true) {
842
+ await printUsage(0);
843
+ return;
844
+ }
845
+ if (!command && isTTY()) {
846
+ // Bare `npx @simple-auth-kit/cli` in a real terminal — go straight to the guided picker
847
+ // (same flow as `add` with no combo) instead of just printing help text.
848
+ await cmdCreate(targetRoot, flags);
849
+ return;
850
+ }
851
+
792
852
  switch (command) {
793
853
  case "init":
794
854
  await cmdInit(targetRoot, flags);
@@ -809,31 +869,8 @@ async function main() {
809
869
  case "diff":
810
870
  await cmdDiff(targetRoot);
811
871
  break;
812
- default: {
813
- const registry = await loadRegistry();
814
- console.log("Usage: simple-auth-kit <init|add|update|diff> [...]");
815
- console.log(` init [--config-only] [--kind ...] [--into <path>] (fresh setup: guided "what do you need" picker, like bare "add")`);
816
- console.log(` --config-only: just write .simple-auth-kit.json and stop, no install`);
817
- console.log(` add <combo> [--workspaces] [--force] [--skip-install] [--into <path>] [--path <dir>] [--alias <alias>]`);
818
- console.log(` add [--kind api,admin,mobile] [--framework <name>,...] [--workspaces] [--into <path>] [--name <appName>]`);
819
- console.log(` (bare "add", or "add" with --kind but no combo, launches a guided prompt for whatever's missing)`);
820
- console.log(` update [--check] [--force] [--skip-install] [--into <path>] (re-installs whatever combo+variant auth.lock.json already records)`);
821
- console.log(` --check: report what would change, without writing anything (merge-mode combos only)`);
822
- console.log(` diff [--into <path>] (shows the actual content diff for every tracked file that differs from the current registry — read-only; merge-mode combos only)`);
823
- console.log(` In a TTY, without --force or --check: a file changed locally since install prompts to overwrite, per file.`);
824
- console.log(` --skip-install: don't run the package manager after copying files — the default is to install for you, shadcn-\`add\`-style.`);
825
- console.log(` --pm <npm|pnpm|yarn|bun>: which package manager to install with — default: detected from a lockfile in the target directory, npm if none found.`);
826
- console.log(`\nAvailable combos:`);
827
- for (const kind of ["api", "admin", "mobile"] as Kind[]) {
828
- const names = combosByKind(registry, kind).map(([name]) => name);
829
- if (names.length) console.log(` ${KIND_LABELS[kind]}: ${names.join(", ")}`);
830
- }
831
- console.log(`\nVariants (choose one at install time):`);
832
- for (const [name, variant] of Object.entries(registry.variants)) {
833
- console.log(` ${name}${variant.flag ? ` (${variant.flag})` : " (default)"} — ${variant.description}`);
834
- }
835
- process.exitCode = command ? 1 : 0;
836
- }
872
+ default:
873
+ await printUsage(command ? 1 : 0);
837
874
  }
838
875
  }
839
876