@simple-auth-kit/cli 1.4.2 → 1.5.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/package.json +1 -1
- package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
- package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
- package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
- package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
- package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
- package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
- package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
- package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
- package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
- package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
- package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
- package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
- package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
- package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
- package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
- package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
- package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
- package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
- package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
- package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
- package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
- package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
- package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
- package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
- package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
- package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
- package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
- package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
- package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
- package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
- package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
- package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
- package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
- package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
- package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
- package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
- package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
- package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
- package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
- package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
- package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
- package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
- package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
- package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
- package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
- package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
- package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
- package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
- package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
- package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
- package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
- package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
- package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
- package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
- package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
- package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
- package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
- package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
- package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
- package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
- package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
- package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
- package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
- package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
- package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
- package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
- package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
- package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
- package/registry.json +6 -4
- package/simple-auth-kit.ts +582 -124
- package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
- package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
- package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
- package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
- package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
- package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
- package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
- package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
- package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
- package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
- package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
- package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
- package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
- package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
package/simple-auth-kit.ts
CHANGED
|
@@ -17,13 +17,27 @@
|
|
|
17
17
|
// non-interactive/scripted use.
|
|
18
18
|
import { existsSync } from "node:fs";
|
|
19
19
|
import { spawnSync } from "node:child_process";
|
|
20
|
-
import { basename, dirname, join,
|
|
20
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
21
21
|
import { access, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import { createTwoFilesPatch } from "diff";
|
|
24
24
|
import prompts from "prompts";
|
|
25
|
-
import {
|
|
26
|
-
|
|
25
|
+
import {
|
|
26
|
+
copyDir,
|
|
27
|
+
copyOneFile,
|
|
28
|
+
CopyOptions,
|
|
29
|
+
CopyResult,
|
|
30
|
+
DiffEntry,
|
|
31
|
+
NEVER_COPY,
|
|
32
|
+
pruneRemovedFiles,
|
|
33
|
+
SCAFFOLD_NEVER_COPY,
|
|
34
|
+
sha256,
|
|
35
|
+
} from "./lib/copy.js";
|
|
36
|
+
import {
|
|
37
|
+
reconcileManifest,
|
|
38
|
+
renameNative,
|
|
39
|
+
type NativeIdentity,
|
|
40
|
+
} from "./lib/rename-native.js";
|
|
27
41
|
|
|
28
42
|
const CLI_DIR = dirname(fileURLToPath(import.meta.url));
|
|
29
43
|
// Two contexts, and they disagree about where the registry is:
|
|
@@ -32,18 +46,35 @@ const CLI_DIR = dirname(fileURLToPath(import.meta.url));
|
|
|
32
46
|
// materialized right before `npm publish`/`npm pack`).
|
|
33
47
|
// - monorepo dev checkout: this file lives at packages/cli/, so the real registry/ is two
|
|
34
48
|
// levels up, at the repo root.
|
|
35
|
-
const REGISTRY_ROOT = existsSync(join(CLI_DIR, "registry"))
|
|
49
|
+
const REGISTRY_ROOT = existsSync(join(CLI_DIR, "registry"))
|
|
50
|
+
? join(CLI_DIR, "registry")
|
|
51
|
+
: resolve(CLI_DIR, "..", "..", "registry");
|
|
36
52
|
const CONFIG_FILENAME = ".simple-auth-kit.json";
|
|
37
53
|
const DEFAULT_VARIANT = "base";
|
|
38
54
|
|
|
39
55
|
type Kind = "api" | "admin" | "mobile";
|
|
40
56
|
type InstallMode = "merge" | "scaffold";
|
|
41
57
|
|
|
42
|
-
const KIND_LABELS: Record<Kind, string> = {
|
|
43
|
-
|
|
58
|
+
const KIND_LABELS: Record<Kind, string> = {
|
|
59
|
+
api: "API (backend)",
|
|
60
|
+
admin: "Admin console",
|
|
61
|
+
mobile: "Mobile app",
|
|
62
|
+
};
|
|
63
|
+
const KIND_FRAMEWORK_NOUN: Record<Kind, string> = {
|
|
64
|
+
api: "API stack",
|
|
65
|
+
admin: "admin framework",
|
|
66
|
+
mobile: "mobile framework",
|
|
67
|
+
};
|
|
44
68
|
|
|
45
69
|
/** Flags that take no value. Everything else consumes the next argv entry. */
|
|
46
|
-
const BOOLEAN_FLAGS = new Set([
|
|
70
|
+
const BOOLEAN_FLAGS = new Set([
|
|
71
|
+
"workspaces",
|
|
72
|
+
"force",
|
|
73
|
+
"check",
|
|
74
|
+
"config-only",
|
|
75
|
+
"skip-install",
|
|
76
|
+
"help",
|
|
77
|
+
]);
|
|
47
78
|
|
|
48
79
|
interface SimpleAuthKitConfig {
|
|
49
80
|
path: string;
|
|
@@ -83,12 +114,21 @@ interface AuthLock {
|
|
|
83
114
|
files?: Record<string, string>;
|
|
84
115
|
}
|
|
85
116
|
|
|
86
|
-
const DEFAULT_CONFIG: SimpleAuthKitConfig = {
|
|
117
|
+
const DEFAULT_CONFIG: SimpleAuthKitConfig = {
|
|
118
|
+
path: "src/lib/auth",
|
|
119
|
+
alias: "@/lib/auth",
|
|
120
|
+
ignore: [],
|
|
121
|
+
};
|
|
87
122
|
|
|
88
123
|
const comboKind = (combo: ComboEntry): Kind => combo.kind ?? "api";
|
|
89
|
-
const comboInstallMode = (combo: ComboEntry): InstallMode =>
|
|
90
|
-
|
|
91
|
-
|
|
124
|
+
const comboInstallMode = (combo: ComboEntry): InstallMode =>
|
|
125
|
+
combo.installMode ?? (comboKind(combo) === "api" ? "merge" : "scaffold");
|
|
126
|
+
|
|
127
|
+
function parseArgs(argv: string[]): {
|
|
128
|
+
command?: string;
|
|
129
|
+
positional: string[];
|
|
130
|
+
flags: Record<string, string | true>;
|
|
131
|
+
} {
|
|
92
132
|
const [command, ...rest] = argv;
|
|
93
133
|
const positional: string[] = [];
|
|
94
134
|
const flags: Record<string, string | true> = {};
|
|
@@ -109,9 +149,15 @@ function parseArgs(argv: string[]): { command?: string; positional: string[]; fl
|
|
|
109
149
|
return { command, positional, flags };
|
|
110
150
|
}
|
|
111
151
|
|
|
112
|
-
const flagString = (value: string | true | undefined): string | undefined =>
|
|
152
|
+
const flagString = (value: string | true | undefined): string | undefined =>
|
|
153
|
+
typeof value === "string" ? value : undefined;
|
|
113
154
|
const flagList = (value: string | true | undefined): string[] | undefined =>
|
|
114
|
-
typeof value === "string"
|
|
155
|
+
typeof value === "string"
|
|
156
|
+
? value
|
|
157
|
+
.split(",")
|
|
158
|
+
.map((s) => s.trim())
|
|
159
|
+
.filter(Boolean)
|
|
160
|
+
: undefined;
|
|
115
161
|
|
|
116
162
|
async function loadRegistry(): Promise<Registry> {
|
|
117
163
|
return JSON.parse(await readFile(join(CLI_DIR, "registry.json"), "utf8"));
|
|
@@ -128,7 +174,9 @@ async function loadConfig(targetRoot: string): Promise<SimpleAuthKitConfig> {
|
|
|
128
174
|
|
|
129
175
|
async function loadLock(targetRoot: string): Promise<AuthLock> {
|
|
130
176
|
try {
|
|
131
|
-
return JSON.parse(
|
|
177
|
+
return JSON.parse(
|
|
178
|
+
await readFile(join(targetRoot, "auth.lock.json"), "utf8"),
|
|
179
|
+
);
|
|
132
180
|
} catch {
|
|
133
181
|
return {};
|
|
134
182
|
}
|
|
@@ -163,18 +211,29 @@ async function isEmptyDir(dir: string): Promise<boolean> {
|
|
|
163
211
|
* reported at the end by printInstallSummary) outside a TTY or under --check, since there's
|
|
164
212
|
* nothing useful to prompt into either case.
|
|
165
213
|
*/
|
|
166
|
-
async function resolveForcePaths(
|
|
167
|
-
|
|
214
|
+
async function resolveForcePaths(
|
|
215
|
+
runDry: () => Promise<{ result: CopyResult }>,
|
|
216
|
+
flags: Record<string, string | true>,
|
|
217
|
+
): Promise<Set<string>> {
|
|
218
|
+
if (flags.force === true || flags.check === true || !isTTY())
|
|
219
|
+
return new Set();
|
|
168
220
|
|
|
169
221
|
const { result } = await runDry();
|
|
170
222
|
if (!result.skipped.length) return new Set();
|
|
171
223
|
|
|
172
|
-
console.log(
|
|
224
|
+
console.log(
|
|
225
|
+
`\n${result.skipped.length} file(s) have changed locally since the last install:`,
|
|
226
|
+
);
|
|
173
227
|
const picked = await ask<string[]>({
|
|
174
228
|
type: "multiselect",
|
|
175
229
|
name: "paths",
|
|
176
|
-
message:
|
|
177
|
-
|
|
230
|
+
message:
|
|
231
|
+
"Overwrite any of these with the latest version? (unselected ones are left alone)",
|
|
232
|
+
choices: result.skipped.map((file) => ({
|
|
233
|
+
title: file,
|
|
234
|
+
value: file,
|
|
235
|
+
selected: false,
|
|
236
|
+
})),
|
|
178
237
|
instructions: false,
|
|
179
238
|
});
|
|
180
239
|
return new Set(picked ?? []);
|
|
@@ -186,10 +245,16 @@ type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
|
186
245
|
/** Detected from whichever lockfile is already sitting in targetRoot — unambiguous, so used
|
|
187
246
|
* without asking. Returns null when nothing on disk says which tool to use and `--pm` wasn't
|
|
188
247
|
* given either, leaving it to the caller to ask (interactive) or default (non-interactive). */
|
|
189
|
-
function detectPackageManagerFromLockfile(
|
|
248
|
+
function detectPackageManagerFromLockfile(
|
|
249
|
+
targetRoot: string,
|
|
250
|
+
): PackageManager | null {
|
|
190
251
|
if (existsSync(join(targetRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
191
252
|
if (existsSync(join(targetRoot, "yarn.lock"))) return "yarn";
|
|
192
|
-
if (
|
|
253
|
+
if (
|
|
254
|
+
existsSync(join(targetRoot, "bun.lockb")) ||
|
|
255
|
+
existsSync(join(targetRoot, "bun.lock"))
|
|
256
|
+
)
|
|
257
|
+
return "bun";
|
|
193
258
|
if (existsSync(join(targetRoot, "package-lock.json"))) return "npm";
|
|
194
259
|
return null;
|
|
195
260
|
}
|
|
@@ -198,11 +263,17 @@ function detectPackageManagerFromLockfile(targetRoot: string): PackageManager |
|
|
|
198
263
|
* silently. Only when neither says anything — most commonly a scaffold install into an empty
|
|
199
264
|
* directory — and this is a real terminal do we ask; a non-interactive run in that same situation
|
|
200
265
|
* still falls back to npm rather than blocking. */
|
|
201
|
-
async function resolvePackageManager(
|
|
266
|
+
async function resolvePackageManager(
|
|
267
|
+
targetRoot: string,
|
|
268
|
+
flags: Record<string, string | true>,
|
|
269
|
+
): Promise<PackageManager> {
|
|
202
270
|
const requested = flagString(flags.pm);
|
|
203
271
|
if (requested) {
|
|
204
|
-
if ((PACKAGE_MANAGERS as readonly string[]).includes(requested))
|
|
205
|
-
|
|
272
|
+
if ((PACKAGE_MANAGERS as readonly string[]).includes(requested))
|
|
273
|
+
return requested as PackageManager;
|
|
274
|
+
console.error(
|
|
275
|
+
`--pm "${requested}" isn't one of ${PACKAGE_MANAGERS.join(", ")} — falling back to auto-detection.`,
|
|
276
|
+
);
|
|
206
277
|
}
|
|
207
278
|
|
|
208
279
|
const detected = detectPackageManagerFromLockfile(targetRoot);
|
|
@@ -229,13 +300,19 @@ async function resolvePackageManager(targetRoot: string, flags: Record<string, s
|
|
|
229
300
|
* to add (an update that touched no files has nothing worth re-installing for). `--pm
|
|
230
301
|
* <npm|pnpm|yarn|bun>` picks the tool explicitly instead of auto-detecting/asking.
|
|
231
302
|
*/
|
|
232
|
-
async function installDependencies(
|
|
303
|
+
async function installDependencies(
|
|
304
|
+
targetRoot: string,
|
|
305
|
+
deps: string[],
|
|
306
|
+
flags: Record<string, string | true>,
|
|
307
|
+
): Promise<void> {
|
|
233
308
|
if (flags["skip-install"] === true) return;
|
|
234
309
|
|
|
235
310
|
const pm = await resolvePackageManager(targetRoot, flags);
|
|
236
311
|
// "install everything in package.json" (zero deps named) is the same bare verb across all
|
|
237
312
|
// four; naming specific packages is "install <pkgs>" for npm, "add <pkgs>" for the others.
|
|
238
|
-
const args = deps.length
|
|
313
|
+
const args = deps.length
|
|
314
|
+
? [pm === "npm" ? "install" : "add", ...deps]
|
|
315
|
+
: ["install"];
|
|
239
316
|
|
|
240
317
|
console.log(`\nInstalling dependencies (${pm})...`);
|
|
241
318
|
let result = spawnSync(pm, args, { cwd: targetRoot, stdio: "inherit" });
|
|
@@ -247,12 +324,19 @@ async function installDependencies(targetRoot: string, deps: string[], flags: Re
|
|
|
247
324
|
// a later `update` (a version bump re-flags a build), so approve unconditionally and retry
|
|
248
325
|
// once rather than just telling the consumer to do it by hand each time — `approve-builds
|
|
249
326
|
// --all` is a no-op when nothing is pending.
|
|
250
|
-
console.log(
|
|
251
|
-
|
|
327
|
+
console.log(
|
|
328
|
+
`\n${pm} blocked some install scripts — running "pnpm approve-builds --all" and retrying...`,
|
|
329
|
+
);
|
|
330
|
+
spawnSync(pm, ["approve-builds", "--all"], {
|
|
331
|
+
cwd: targetRoot,
|
|
332
|
+
stdio: "inherit",
|
|
333
|
+
});
|
|
252
334
|
result = spawnSync(pm, args, { cwd: targetRoot, stdio: "inherit" });
|
|
253
335
|
}
|
|
254
336
|
if (result.status !== 0) {
|
|
255
|
-
console.error(
|
|
337
|
+
console.error(
|
|
338
|
+
`\n${pm} install exited with an error — run it yourself: cd ${targetRoot} && ${pm} ${args.join(" ")}`,
|
|
339
|
+
);
|
|
256
340
|
}
|
|
257
341
|
}
|
|
258
342
|
|
|
@@ -264,14 +348,23 @@ async function installDependencies(targetRoot: string, deps: string[], flags: Re
|
|
|
264
348
|
* the fresh-project entry point, not just a config file. Pass --config-only to get the old
|
|
265
349
|
* behavior back (write the config and stop there, no install).
|
|
266
350
|
*/
|
|
267
|
-
async function cmdInit(
|
|
351
|
+
async function cmdInit(
|
|
352
|
+
targetRoot: string,
|
|
353
|
+
flags: Record<string, string | true>,
|
|
354
|
+
) {
|
|
268
355
|
const config: SimpleAuthKitConfig = {
|
|
269
356
|
path: flagString(flags.path) ?? DEFAULT_CONFIG.path,
|
|
270
357
|
alias: flagString(flags.alias) ?? DEFAULT_CONFIG.alias,
|
|
271
358
|
ignore: [],
|
|
272
359
|
};
|
|
273
|
-
await writeFile(
|
|
274
|
-
|
|
360
|
+
await writeFile(
|
|
361
|
+
join(targetRoot, CONFIG_FILENAME),
|
|
362
|
+
JSON.stringify(config, null, 2) + "\n",
|
|
363
|
+
"utf8",
|
|
364
|
+
);
|
|
365
|
+
console.log(
|
|
366
|
+
`Wrote ${CONFIG_FILENAME} — combos will install into ${config.path} (import alias ${config.alias})`,
|
|
367
|
+
);
|
|
275
368
|
|
|
276
369
|
if (flags["config-only"] === true) return;
|
|
277
370
|
|
|
@@ -279,29 +372,44 @@ async function cmdInit(targetRoot: string, flags: Record<string, string | true>)
|
|
|
279
372
|
await cmdCreate(targetRoot, flags);
|
|
280
373
|
}
|
|
281
374
|
|
|
282
|
-
function resolveVariant(
|
|
375
|
+
function resolveVariant(
|
|
376
|
+
combo: ComboEntry,
|
|
377
|
+
comboName: string,
|
|
378
|
+
requested: string,
|
|
379
|
+
): string | null {
|
|
283
380
|
if (combo.variants.length === 0) {
|
|
284
|
-
console.error(
|
|
381
|
+
console.error(
|
|
382
|
+
`Combo "${comboName}" has not been migrated to the variant layout yet (see registry/README.md) — nothing to install.`,
|
|
383
|
+
);
|
|
285
384
|
return null;
|
|
286
385
|
}
|
|
287
386
|
if (!combo.variants.includes(requested)) {
|
|
288
|
-
console.error(
|
|
387
|
+
console.error(
|
|
388
|
+
`Combo "${comboName}" has no "${requested}" variant. Available: ${combo.variants.join(", ")}`,
|
|
389
|
+
);
|
|
289
390
|
return null;
|
|
290
391
|
}
|
|
291
392
|
return requested;
|
|
292
393
|
}
|
|
293
394
|
|
|
294
395
|
function requestedVariant(flags: Record<string, string | true>): string {
|
|
295
|
-
return flags.workspaces === true
|
|
396
|
+
return flags.workspaces === true
|
|
397
|
+
? "workspaces"
|
|
398
|
+
: (flagString(flags.variant) ?? DEFAULT_VARIANT);
|
|
296
399
|
}
|
|
297
400
|
|
|
298
|
-
function printInstallSummary(
|
|
401
|
+
function printInstallSummary(
|
|
402
|
+
result: CopyResult,
|
|
403
|
+
pruned: { removed: string[]; keptModified: string[] },
|
|
404
|
+
) {
|
|
299
405
|
if (result.updated.length) {
|
|
300
406
|
console.log(`\nUpdated (new or changed since last install):`);
|
|
301
407
|
for (const file of result.updated) console.log(` ${file}`);
|
|
302
408
|
}
|
|
303
409
|
if (result.skipped.length) {
|
|
304
|
-
console.log(
|
|
410
|
+
console.log(
|
|
411
|
+
`\nLeft alone — modified since the last install (re-run with --force to overwrite):`,
|
|
412
|
+
);
|
|
305
413
|
for (const file of result.skipped) console.log(` ${file}`);
|
|
306
414
|
}
|
|
307
415
|
if (result.ignored.length) {
|
|
@@ -313,13 +421,18 @@ function printInstallSummary(result: CopyResult, pruned: { removed: string[]; ke
|
|
|
313
421
|
for (const file of pruned.removed) console.log(` ${file}`);
|
|
314
422
|
}
|
|
315
423
|
if (pruned.keptModified.length) {
|
|
316
|
-
console.log(
|
|
424
|
+
console.log(
|
|
425
|
+
`\nNo longer part of this install, but modified locally, so kept (delete by hand if you don't want them):`,
|
|
426
|
+
);
|
|
317
427
|
for (const file of pruned.keptModified) console.log(` ${file}`);
|
|
318
428
|
}
|
|
319
429
|
}
|
|
320
430
|
|
|
321
431
|
function printPostInstallNotes(combo: ComboEntry, variant: string) {
|
|
322
|
-
const notes = [
|
|
432
|
+
const notes = [
|
|
433
|
+
...combo.postInstall,
|
|
434
|
+
...(combo.variantPostInstall?.[variant] ?? []),
|
|
435
|
+
];
|
|
323
436
|
if (notes.length) {
|
|
324
437
|
console.log(`\nNext steps:`);
|
|
325
438
|
for (const note of notes) console.log(` - ${note}`);
|
|
@@ -334,11 +447,23 @@ function printPostInstallNotes(combo: ComboEntry, variant: string) {
|
|
|
334
447
|
* to schema.prisma's own location, so moving prisma.config.ts/prisma/ to the project root also
|
|
335
448
|
* moves the generated client. The registry source's `src/*.ts` files import it as
|
|
336
449
|
* `"../generated/prisma/client.js"` — correct for the registry's own dev/typecheck loop (where
|
|
337
|
-
* nothing has moved)
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
|
|
341
|
-
|
|
450
|
+
* nothing has moved) — and installMerge rewrites that literal string in every copied file to
|
|
451
|
+
* `@/prisma/client.js` instead of a computed relative path: the generated client always lands at
|
|
452
|
+
* the project root regardless of how deep the importing file sits (unlike `@/lib/auth/core`,
|
|
453
|
+
* whose depth actually varies with `--path`), so a fixed alias is both simpler and stable across
|
|
454
|
+
* installs. The consumer adds `"@/prisma/*": ["./generated/prisma/*"]` to their tsconfig `paths`
|
|
455
|
+
* once (see postInstall) — the CLI never writes tsconfig itself, same as `@/lib/auth`. Drizzle has
|
|
456
|
+
* no equivalent generated artifact to redirect. */
|
|
457
|
+
const ORM_LAYOUTS: {
|
|
458
|
+
configFile: string;
|
|
459
|
+
dataDir: string;
|
|
460
|
+
generatedClientImport?: string;
|
|
461
|
+
}[] = [
|
|
462
|
+
{
|
|
463
|
+
configFile: "prisma.config.ts",
|
|
464
|
+
dataDir: "prisma",
|
|
465
|
+
generatedClientImport: "../generated/prisma/client.js",
|
|
466
|
+
},
|
|
342
467
|
{ configFile: "drizzle.config.ts", dataDir: "drizzle" },
|
|
343
468
|
];
|
|
344
469
|
|
|
@@ -358,7 +483,13 @@ interface MergePlan {
|
|
|
358
483
|
|
|
359
484
|
/** Everything about a merge-mode install that doesn't depend on force/dryRun/forcePaths —
|
|
360
485
|
* computed once, shared by installMerge's real run and cmdDiff's read-only one. */
|
|
361
|
-
async function buildMergePlan(
|
|
486
|
+
async function buildMergePlan(
|
|
487
|
+
combo: ComboEntry,
|
|
488
|
+
variant: string,
|
|
489
|
+
targetRoot: string,
|
|
490
|
+
flags: Record<string, string | true>,
|
|
491
|
+
previous: Record<string, string>,
|
|
492
|
+
): Promise<MergePlan> {
|
|
362
493
|
const config = await loadConfig(targetRoot);
|
|
363
494
|
const installPath = flagString(flags.path) ?? config.path;
|
|
364
495
|
const alias = flagString(flags.alias) ?? config.alias;
|
|
@@ -374,19 +505,35 @@ async function buildMergePlan(combo: ComboEntry, variant: string, targetRoot: st
|
|
|
374
505
|
}
|
|
375
506
|
return null;
|
|
376
507
|
})();
|
|
377
|
-
const skipFromShared = orm
|
|
378
|
-
|
|
508
|
+
const skipFromShared = orm
|
|
509
|
+
? new Set([...NEVER_COPY, orm.configFile])
|
|
510
|
+
: NEVER_COPY;
|
|
511
|
+
const skipFromVariant = orm
|
|
512
|
+
? new Set([...NEVER_COPY, orm.dataDir])
|
|
513
|
+
: NEVER_COPY;
|
|
379
514
|
|
|
380
515
|
const extraRewrites = orm?.generatedClientImport
|
|
381
516
|
? [
|
|
382
517
|
{
|
|
383
518
|
from: orm.generatedClientImport,
|
|
384
|
-
to:
|
|
519
|
+
to: "@/prisma/client.js",
|
|
385
520
|
},
|
|
386
521
|
]
|
|
387
522
|
: undefined;
|
|
388
523
|
|
|
389
|
-
return {
|
|
524
|
+
return {
|
|
525
|
+
destRoot,
|
|
526
|
+
installPath,
|
|
527
|
+
alias,
|
|
528
|
+
sharedDir,
|
|
529
|
+
variantDir,
|
|
530
|
+
orm,
|
|
531
|
+
skipFromShared,
|
|
532
|
+
skipFromVariant,
|
|
533
|
+
extraRewrites,
|
|
534
|
+
config,
|
|
535
|
+
previous,
|
|
536
|
+
};
|
|
390
537
|
}
|
|
391
538
|
|
|
392
539
|
/** The actual copy pass for a merge-mode install — shared by installMerge (writes for real, or
|
|
@@ -405,9 +552,22 @@ async function runMergeCopy(
|
|
|
405
552
|
plan: MergePlan,
|
|
406
553
|
registry: Registry,
|
|
407
554
|
targetRoot: string,
|
|
408
|
-
opts: {
|
|
409
|
-
|
|
410
|
-
|
|
555
|
+
opts: {
|
|
556
|
+
force: boolean;
|
|
557
|
+
forcePaths: Set<string>;
|
|
558
|
+
dryRun: boolean;
|
|
559
|
+
collectDiffs?: DiffEntry[];
|
|
560
|
+
},
|
|
561
|
+
): Promise<{
|
|
562
|
+
result: CopyResult;
|
|
563
|
+
pruned: { removed: string[]; keptModified: string[] };
|
|
564
|
+
}> {
|
|
565
|
+
const result: CopyResult = {
|
|
566
|
+
manifest: {},
|
|
567
|
+
skipped: [],
|
|
568
|
+
ignored: [],
|
|
569
|
+
updated: [],
|
|
570
|
+
};
|
|
411
571
|
const copyOpts: CopyOptions = {
|
|
412
572
|
aliasFrom: "@/lib/auth/core",
|
|
413
573
|
aliasTo: `${plan.alias}/core`,
|
|
@@ -421,33 +581,117 @@ async function runMergeCopy(
|
|
|
421
581
|
collectDiffs: opts.collectDiffs,
|
|
422
582
|
};
|
|
423
583
|
|
|
424
|
-
await copyDir(
|
|
425
|
-
|
|
426
|
-
|
|
584
|
+
await copyDir(
|
|
585
|
+
join(REGISTRY_ROOT, registry.core.dir),
|
|
586
|
+
join(plan.destRoot, "core"),
|
|
587
|
+
copyOpts,
|
|
588
|
+
plan.destRoot,
|
|
589
|
+
result,
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
// shared/src/ and variants/<variant>/src/ are the registry's own internal source layout
|
|
593
|
+
// (parallel to core/, prisma.config.ts, etc.) — copied flat into destRoot rather than
|
|
594
|
+
// preserving that extra "src" level, so a consumer who installed at the default
|
|
595
|
+
// "src/lib/auth" doesn't end up with a redundant "src/lib/auth/src/...". Order matters:
|
|
596
|
+
// shared fully applied (its own src/, then everything else in shared/), then the variant
|
|
597
|
+
// overlaid the same way, so a variant file with the same name still wins.
|
|
598
|
+
const skipShared = new Set([...plan.skipFromShared, "src"]);
|
|
599
|
+
const skipVariant = new Set([...plan.skipFromVariant, "src"]);
|
|
600
|
+
if (await pathExists(join(plan.sharedDir, "src"))) {
|
|
601
|
+
await copyDir(
|
|
602
|
+
join(plan.sharedDir, "src"),
|
|
603
|
+
plan.destRoot,
|
|
604
|
+
{ ...copyOpts, neverCopy: plan.skipFromShared },
|
|
605
|
+
plan.destRoot,
|
|
606
|
+
result,
|
|
607
|
+
);
|
|
608
|
+
}
|
|
609
|
+
await copyDir(
|
|
610
|
+
plan.sharedDir,
|
|
611
|
+
plan.destRoot,
|
|
612
|
+
{ ...copyOpts, neverCopy: skipShared },
|
|
613
|
+
plan.destRoot,
|
|
614
|
+
result,
|
|
615
|
+
);
|
|
616
|
+
if (await pathExists(join(plan.variantDir, "src"))) {
|
|
617
|
+
await copyDir(
|
|
618
|
+
join(plan.variantDir, "src"),
|
|
619
|
+
plan.destRoot,
|
|
620
|
+
{ ...copyOpts, neverCopy: plan.skipFromVariant },
|
|
621
|
+
plan.destRoot,
|
|
622
|
+
result,
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
await copyDir(
|
|
626
|
+
plan.variantDir,
|
|
627
|
+
plan.destRoot,
|
|
628
|
+
{ ...copyOpts, neverCopy: skipVariant },
|
|
629
|
+
plan.destRoot,
|
|
630
|
+
result,
|
|
631
|
+
);
|
|
427
632
|
|
|
428
633
|
if (plan.orm) {
|
|
429
|
-
await copyOneFile(
|
|
430
|
-
|
|
634
|
+
await copyOneFile(
|
|
635
|
+
join(plan.sharedDir, plan.orm.configFile),
|
|
636
|
+
join(targetRoot, plan.orm.configFile),
|
|
637
|
+
plan.destRoot,
|
|
638
|
+
copyOpts,
|
|
639
|
+
result,
|
|
640
|
+
);
|
|
641
|
+
await copyDir(
|
|
642
|
+
join(plan.variantDir, plan.orm.dataDir),
|
|
643
|
+
join(targetRoot, plan.orm.dataDir),
|
|
644
|
+
copyOpts,
|
|
645
|
+
plan.destRoot,
|
|
646
|
+
result,
|
|
647
|
+
);
|
|
431
648
|
}
|
|
432
649
|
|
|
433
650
|
// Switching variants has to remove the old variant's files, or the project ends up with both wired in.
|
|
434
|
-
const pruned = await pruneRemovedFiles(plan.destRoot, plan.previous, result, {
|
|
651
|
+
const pruned = await pruneRemovedFiles(plan.destRoot, plan.previous, result, {
|
|
652
|
+
force: opts.force,
|
|
653
|
+
ignore: plan.config.ignore,
|
|
654
|
+
dryRun: opts.dryRun,
|
|
655
|
+
});
|
|
435
656
|
return { result, pruned };
|
|
436
657
|
}
|
|
437
658
|
|
|
438
|
-
async function installMerge(
|
|
659
|
+
async function installMerge(
|
|
660
|
+
comboName: string,
|
|
661
|
+
combo: ComboEntry,
|
|
662
|
+
variant: string,
|
|
663
|
+
registry: Registry,
|
|
664
|
+
targetRoot: string,
|
|
665
|
+
flags: Record<string, string | true>,
|
|
666
|
+
) {
|
|
439
667
|
const lock = await loadLock(targetRoot);
|
|
440
668
|
const previous = lock.files ?? {};
|
|
441
669
|
const force = flags.force === true;
|
|
442
670
|
const checkOnly = flags.check === true;
|
|
443
671
|
|
|
444
|
-
const plan = await buildMergePlan(
|
|
672
|
+
const plan = await buildMergePlan(
|
|
673
|
+
combo,
|
|
674
|
+
variant,
|
|
675
|
+
targetRoot,
|
|
676
|
+
flags,
|
|
677
|
+
previous,
|
|
678
|
+
);
|
|
445
679
|
|
|
446
680
|
if (checkOnly) {
|
|
447
|
-
const { result, pruned } = await runMergeCopy(plan, registry, targetRoot, {
|
|
448
|
-
|
|
681
|
+
const { result, pruned } = await runMergeCopy(plan, registry, targetRoot, {
|
|
682
|
+
force,
|
|
683
|
+
forcePaths: new Set(),
|
|
684
|
+
dryRun: true,
|
|
685
|
+
});
|
|
686
|
+
console.log(
|
|
687
|
+
`\nCheck only — nothing written. "${comboName}" (${variant} variant) in ${plan.installPath} (alias ${plan.alias}):`,
|
|
688
|
+
);
|
|
449
689
|
printInstallSummary(result, pruned);
|
|
450
|
-
const silent =
|
|
690
|
+
const silent =
|
|
691
|
+
!result.updated.length &&
|
|
692
|
+
!result.skipped.length &&
|
|
693
|
+
!pruned.removed.length &&
|
|
694
|
+
!pruned.keptModified.length;
|
|
451
695
|
if (silent) console.log(`\nUp to date — nothing would change.`);
|
|
452
696
|
return;
|
|
453
697
|
}
|
|
@@ -455,19 +699,45 @@ async function installMerge(comboName: string, combo: ComboEntry, variant: strin
|
|
|
455
699
|
// Interactive + no --force: find locally-modified conflicts first (a dry run, nothing written),
|
|
456
700
|
// and let the user pick which — if any — to overwrite anyway, shadcn-"this file already exists"
|
|
457
701
|
// style, instead of the non-interactive default of silently leaving all of them alone.
|
|
458
|
-
const forcePaths = await resolveForcePaths(
|
|
459
|
-
|
|
702
|
+
const forcePaths = await resolveForcePaths(
|
|
703
|
+
() =>
|
|
704
|
+
runMergeCopy(plan, registry, targetRoot, {
|
|
705
|
+
force: false,
|
|
706
|
+
forcePaths: new Set(),
|
|
707
|
+
dryRun: true,
|
|
708
|
+
}),
|
|
709
|
+
flags,
|
|
710
|
+
);
|
|
711
|
+
const { result, pruned } = await runMergeCopy(plan, registry, targetRoot, {
|
|
712
|
+
force,
|
|
713
|
+
forcePaths,
|
|
714
|
+
dryRun: false,
|
|
715
|
+
});
|
|
460
716
|
|
|
461
717
|
await writeFile(
|
|
462
718
|
join(targetRoot, "auth.lock.json"),
|
|
463
|
-
JSON.stringify(
|
|
719
|
+
JSON.stringify(
|
|
720
|
+
{
|
|
721
|
+
...lock,
|
|
722
|
+
combo: comboName,
|
|
723
|
+
variant,
|
|
724
|
+
installedAt: new Date().toISOString(),
|
|
725
|
+
files: result.manifest,
|
|
726
|
+
},
|
|
727
|
+
null,
|
|
728
|
+
2,
|
|
729
|
+
) + "\n",
|
|
464
730
|
"utf8",
|
|
465
731
|
);
|
|
466
732
|
|
|
467
|
-
console.log(
|
|
733
|
+
console.log(
|
|
734
|
+
`\nInstalled "${comboName}" (${variant} variant) into ${plan.installPath} (alias ${plan.alias})`,
|
|
735
|
+
);
|
|
468
736
|
printInstallSummary(result, pruned);
|
|
469
737
|
|
|
470
|
-
const peerDeps = [
|
|
738
|
+
const peerDeps = [
|
|
739
|
+
...new Set([...registry.core.peerDependencies, ...combo.peerDependencies]),
|
|
740
|
+
];
|
|
471
741
|
console.log(`\nPeer dependencies: ${peerDeps.join(" ")}`);
|
|
472
742
|
if (result.updated.length) {
|
|
473
743
|
await installDependencies(targetRoot, peerDeps, flags);
|
|
@@ -480,7 +750,13 @@ async function installMerge(comboName: string, combo: ComboEntry, variant: strin
|
|
|
480
750
|
/** "scaffold" install — admin/mobile apps materialized as a whole standalone project directly
|
|
481
751
|
* into targetRoot: no core layer, no alias rewrite, package.json is real content (copied and
|
|
482
752
|
* name/description-templated), not the registry's own dev wiring. */
|
|
483
|
-
async function installScaffold(
|
|
753
|
+
async function installScaffold(
|
|
754
|
+
comboName: string,
|
|
755
|
+
combo: ComboEntry,
|
|
756
|
+
variant: string,
|
|
757
|
+
targetRoot: string,
|
|
758
|
+
flags: Record<string, string | true>,
|
|
759
|
+
) {
|
|
484
760
|
const lock = await loadLock(targetRoot);
|
|
485
761
|
const previous = lock.files ?? {};
|
|
486
762
|
const force = flags.force === true;
|
|
@@ -498,24 +774,65 @@ async function installScaffold(comboName: string, combo: ComboEntry, variant: st
|
|
|
498
774
|
|
|
499
775
|
const comboDir = join(REGISTRY_ROOT, combo.dir);
|
|
500
776
|
|
|
501
|
-
const runCopy = async (opts: {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
777
|
+
const runCopy = async (opts: {
|
|
778
|
+
force: boolean;
|
|
779
|
+
forcePaths: Set<string>;
|
|
780
|
+
dryRun: boolean;
|
|
781
|
+
}) => {
|
|
782
|
+
const result: CopyResult = {
|
|
783
|
+
manifest: {},
|
|
784
|
+
skipped: [],
|
|
785
|
+
ignored: [],
|
|
786
|
+
updated: [],
|
|
787
|
+
};
|
|
788
|
+
const copyOpts = {
|
|
789
|
+
previous,
|
|
790
|
+
force: opts.force,
|
|
791
|
+
forcePaths: opts.forcePaths,
|
|
792
|
+
neverCopy: SCAFFOLD_NEVER_COPY,
|
|
793
|
+
dryRun: opts.dryRun,
|
|
794
|
+
};
|
|
795
|
+
await copyDir(
|
|
796
|
+
join(comboDir, combo.sharedDir ?? "shared"),
|
|
797
|
+
targetRoot,
|
|
798
|
+
copyOpts,
|
|
799
|
+
targetRoot,
|
|
800
|
+
result,
|
|
801
|
+
);
|
|
802
|
+
await copyDir(
|
|
803
|
+
join(comboDir, combo.variantsDir ?? "variants", variant),
|
|
804
|
+
targetRoot,
|
|
805
|
+
copyOpts,
|
|
806
|
+
targetRoot,
|
|
807
|
+
result,
|
|
808
|
+
);
|
|
809
|
+
const pruned = await pruneRemovedFiles(targetRoot, previous, result, {
|
|
810
|
+
force: opts.force,
|
|
811
|
+
dryRun: opts.dryRun,
|
|
812
|
+
});
|
|
507
813
|
return { result, pruned };
|
|
508
814
|
};
|
|
509
815
|
|
|
510
816
|
// Same shadcn-style "this file already exists, overwrite?" prompt as installMerge — see
|
|
511
817
|
// resolveForcePaths. A no-op dry run on a genuinely fresh install (nothing to conflict with).
|
|
512
|
-
const forcePaths = await resolveForcePaths(
|
|
513
|
-
|
|
818
|
+
const forcePaths = await resolveForcePaths(
|
|
819
|
+
() => runCopy({ force: false, forcePaths: new Set(), dryRun: true }),
|
|
820
|
+
flags,
|
|
821
|
+
);
|
|
822
|
+
const { result, pruned } = await runCopy({
|
|
823
|
+
force,
|
|
824
|
+
forcePaths,
|
|
825
|
+
dryRun: false,
|
|
826
|
+
});
|
|
514
827
|
|
|
515
828
|
const appName = flagString(flags.name) ?? basename(targetRoot);
|
|
516
829
|
|
|
517
830
|
if (combo.nativeIdentity) {
|
|
518
|
-
const renamed = await renameNative(
|
|
831
|
+
const renamed = await renameNative(
|
|
832
|
+
targetRoot,
|
|
833
|
+
combo.nativeIdentity,
|
|
834
|
+
appName,
|
|
835
|
+
);
|
|
519
836
|
result.manifest = reconcileManifest(result.manifest, renamed);
|
|
520
837
|
}
|
|
521
838
|
|
|
@@ -533,11 +850,23 @@ async function installScaffold(comboName: string, combo: ComboEntry, variant: st
|
|
|
533
850
|
|
|
534
851
|
await writeFile(
|
|
535
852
|
join(targetRoot, "auth.lock.json"),
|
|
536
|
-
JSON.stringify(
|
|
853
|
+
JSON.stringify(
|
|
854
|
+
{
|
|
855
|
+
...lock,
|
|
856
|
+
combo: comboName,
|
|
857
|
+
variant,
|
|
858
|
+
installedAt: new Date().toISOString(),
|
|
859
|
+
files: result.manifest,
|
|
860
|
+
},
|
|
861
|
+
null,
|
|
862
|
+
2,
|
|
863
|
+
) + "\n",
|
|
537
864
|
"utf8",
|
|
538
865
|
);
|
|
539
866
|
|
|
540
|
-
console.log(
|
|
867
|
+
console.log(
|
|
868
|
+
`\nGenerated "${appName}" (${comboName}, ${variant} variant) into ${targetRoot}`,
|
|
869
|
+
);
|
|
541
870
|
printInstallSummary(result, pruned);
|
|
542
871
|
if (result.updated.length) {
|
|
543
872
|
// Dependencies are already declared in package.json — no specific packages to name, just
|
|
@@ -549,7 +878,14 @@ async function installScaffold(comboName: string, combo: ComboEntry, variant: st
|
|
|
549
878
|
printPostInstallNotes(combo, variant);
|
|
550
879
|
}
|
|
551
880
|
|
|
552
|
-
async function installCombo(
|
|
881
|
+
async function installCombo(
|
|
882
|
+
comboName: string,
|
|
883
|
+
combo: ComboEntry,
|
|
884
|
+
variant: string,
|
|
885
|
+
registry: Registry,
|
|
886
|
+
targetRoot: string,
|
|
887
|
+
flags: Record<string, string | true>,
|
|
888
|
+
) {
|
|
553
889
|
if (comboInstallMode(combo) === "scaffold") {
|
|
554
890
|
await installScaffold(comboName, combo, variant, targetRoot, flags);
|
|
555
891
|
} else {
|
|
@@ -557,11 +893,17 @@ async function installCombo(comboName: string, combo: ComboEntry, variant: strin
|
|
|
557
893
|
}
|
|
558
894
|
}
|
|
559
895
|
|
|
560
|
-
async function cmdAdd(
|
|
896
|
+
async function cmdAdd(
|
|
897
|
+
comboName: string,
|
|
898
|
+
targetRoot: string,
|
|
899
|
+
flags: Record<string, string | true>,
|
|
900
|
+
) {
|
|
561
901
|
const registry = await loadRegistry();
|
|
562
902
|
const combo = registry.combos[comboName];
|
|
563
903
|
if (!combo) {
|
|
564
|
-
console.error(
|
|
904
|
+
console.error(
|
|
905
|
+
`Unknown combo "${comboName}". Available: ${Object.keys(registry.combos).join(", ")}`,
|
|
906
|
+
);
|
|
565
907
|
process.exitCode = 1;
|
|
566
908
|
return;
|
|
567
909
|
}
|
|
@@ -575,15 +917,22 @@ async function cmdAdd(comboName: string, targetRoot: string, flags: Record<strin
|
|
|
575
917
|
await installCombo(comboName, combo, variant, registry, targetRoot, flags);
|
|
576
918
|
}
|
|
577
919
|
|
|
578
|
-
function combosByKind(
|
|
579
|
-
|
|
920
|
+
function combosByKind(
|
|
921
|
+
registry: Registry,
|
|
922
|
+
kind: Kind,
|
|
923
|
+
): Array<[string, ComboEntry]> {
|
|
924
|
+
return Object.entries(registry.combos).filter(
|
|
925
|
+
([, combo]) => comboKind(combo) === kind,
|
|
926
|
+
);
|
|
580
927
|
}
|
|
581
928
|
|
|
582
929
|
const isTTY = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
583
930
|
|
|
584
931
|
/** Wraps a single `prompts()` call so a cancel (Ctrl+C) surfaces as `null` instead of `undefined`,
|
|
585
932
|
* which prompts itself doesn't distinguish from "field asked but left empty". */
|
|
586
|
-
async function ask<T>(
|
|
933
|
+
async function ask<T>(
|
|
934
|
+
question: Parameters<typeof prompts>[0] & { name: string },
|
|
935
|
+
): Promise<T | null> {
|
|
587
936
|
const res = await prompts(question as never);
|
|
588
937
|
const value = (res as Record<string, unknown>)[question.name as string];
|
|
589
938
|
return value === undefined ? null : (value as T);
|
|
@@ -595,7 +944,14 @@ async function ask<T>(question: Parameters<typeof prompts>[0] & { name: string }
|
|
|
595
944
|
* choice applies to every kind picked this run, matching how `variants` is already a single
|
|
596
945
|
* cross-cutting concept rather than per-combo.
|
|
597
946
|
*/
|
|
598
|
-
async function resolvePlan(
|
|
947
|
+
async function resolvePlan(
|
|
948
|
+
registry: Registry,
|
|
949
|
+
flags: Record<string, string | true>,
|
|
950
|
+
): Promise<Array<{
|
|
951
|
+
comboName: string;
|
|
952
|
+
combo: ComboEntry;
|
|
953
|
+
variant: string;
|
|
954
|
+
}> | null> {
|
|
599
955
|
const flagKinds = flagList(flags.kind) as Kind[] | undefined;
|
|
600
956
|
const flagFrameworks = flagList(flags.framework);
|
|
601
957
|
const interactive = isTTY();
|
|
@@ -608,7 +964,10 @@ async function resolvePlan(registry: Registry, flags: Record<string, string | tr
|
|
|
608
964
|
type: "multiselect",
|
|
609
965
|
name: "kinds",
|
|
610
966
|
message: "What would you like to generate?",
|
|
611
|
-
choices: (Object.keys(KIND_LABELS) as Kind[]).map((value) => ({
|
|
967
|
+
choices: (Object.keys(KIND_LABELS) as Kind[]).map((value) => ({
|
|
968
|
+
title: KIND_LABELS[value],
|
|
969
|
+
value,
|
|
970
|
+
})),
|
|
612
971
|
min: 1,
|
|
613
972
|
instructions: false,
|
|
614
973
|
});
|
|
@@ -618,7 +977,9 @@ async function resolvePlan(registry: Registry, flags: Record<string, string | tr
|
|
|
618
977
|
}
|
|
619
978
|
kinds = picked;
|
|
620
979
|
} else {
|
|
621
|
-
console.error(
|
|
980
|
+
console.error(
|
|
981
|
+
"Non-interactive session: pass --kind api,admin,mobile (comma-separated), or a specific combo name (simple-auth-kit add <combo>).",
|
|
982
|
+
);
|
|
622
983
|
return null;
|
|
623
984
|
}
|
|
624
985
|
|
|
@@ -642,7 +1003,9 @@ async function resolvePlan(registry: Registry, flags: Record<string, string | tr
|
|
|
642
1003
|
|
|
643
1004
|
let comboName = flagFrameworks?.[i];
|
|
644
1005
|
if (comboName && !available.some(([name]) => name === comboName)) {
|
|
645
|
-
const match = available.find(
|
|
1006
|
+
const match = available.find(
|
|
1007
|
+
([name]) => name === comboName || name.endsWith(`-${comboName}`),
|
|
1008
|
+
);
|
|
646
1009
|
comboName = match?.[0];
|
|
647
1010
|
}
|
|
648
1011
|
|
|
@@ -662,7 +1025,9 @@ async function resolvePlan(registry: Registry, flags: Record<string, string | tr
|
|
|
662
1025
|
}
|
|
663
1026
|
comboName = picked;
|
|
664
1027
|
} else {
|
|
665
|
-
console.error(
|
|
1028
|
+
console.error(
|
|
1029
|
+
`Multiple "${kind}" combos available (${available.map(([name]) => name).join(", ")}) — pass --framework to disambiguate.`,
|
|
1030
|
+
);
|
|
666
1031
|
return null;
|
|
667
1032
|
}
|
|
668
1033
|
}
|
|
@@ -682,7 +1047,12 @@ async function resolvePlan(registry: Registry, flags: Record<string, string | tr
|
|
|
682
1047
|
} else if (requestedVariantFlag === "base") {
|
|
683
1048
|
wantsWorkspaces = false;
|
|
684
1049
|
} else if (interactive) {
|
|
685
|
-
const picked = await ask<boolean>({
|
|
1050
|
+
const picked = await ask<boolean>({
|
|
1051
|
+
type: "confirm",
|
|
1052
|
+
name: "workspaces",
|
|
1053
|
+
message: "Include workspaces support?",
|
|
1054
|
+
initial: false,
|
|
1055
|
+
});
|
|
686
1056
|
if (picked === null) {
|
|
687
1057
|
console.log("Cancelled.");
|
|
688
1058
|
return null;
|
|
@@ -692,16 +1062,27 @@ async function resolvePlan(registry: Registry, flags: Record<string, string | tr
|
|
|
692
1062
|
wantsWorkspaces = false;
|
|
693
1063
|
}
|
|
694
1064
|
|
|
695
|
-
const selections: Array<{
|
|
1065
|
+
const selections: Array<{
|
|
1066
|
+
comboName: string;
|
|
1067
|
+
combo: ComboEntry;
|
|
1068
|
+
variant: string;
|
|
1069
|
+
}> = [];
|
|
696
1070
|
for (const pick of picks) {
|
|
697
|
-
const variant = resolveVariant(
|
|
1071
|
+
const variant = resolveVariant(
|
|
1072
|
+
pick.combo,
|
|
1073
|
+
pick.comboName,
|
|
1074
|
+
wantsWorkspaces ? "workspaces" : DEFAULT_VARIANT,
|
|
1075
|
+
);
|
|
698
1076
|
if (!variant) return null;
|
|
699
1077
|
selections.push({ ...pick, variant });
|
|
700
1078
|
}
|
|
701
1079
|
return selections;
|
|
702
1080
|
}
|
|
703
1081
|
|
|
704
|
-
async function cmdCreate(
|
|
1082
|
+
async function cmdCreate(
|
|
1083
|
+
targetRoot: string,
|
|
1084
|
+
flags: Record<string, string | true>,
|
|
1085
|
+
) {
|
|
705
1086
|
const registry = await loadRegistry();
|
|
706
1087
|
const selections = await resolvePlan(registry, flags);
|
|
707
1088
|
if (!selections) {
|
|
@@ -723,8 +1104,18 @@ async function cmdCreate(targetRoot: string, flags: Record<string, string | true
|
|
|
723
1104
|
// (they don't have their own package.json identity), but two scaffold apps both literally
|
|
724
1105
|
// named e.g. "combo-test" would collide if anything ever treats them as sibling packages
|
|
725
1106
|
// (a pnpm/npm workspace, for one). Suffix with the combo name once there's more than one.
|
|
726
|
-
const installFlags =
|
|
727
|
-
|
|
1107
|
+
const installFlags =
|
|
1108
|
+
namespaced && flagString(flags.name)
|
|
1109
|
+
? { ...flags, name: `${flagString(flags.name)}-${comboName}` }
|
|
1110
|
+
: flags;
|
|
1111
|
+
await installCombo(
|
|
1112
|
+
comboName,
|
|
1113
|
+
combo,
|
|
1114
|
+
variant,
|
|
1115
|
+
registry,
|
|
1116
|
+
installRoot,
|
|
1117
|
+
installFlags,
|
|
1118
|
+
);
|
|
728
1119
|
}
|
|
729
1120
|
}
|
|
730
1121
|
|
|
@@ -735,10 +1126,15 @@ async function cmdCreate(targetRoot: string, flags: Record<string, string | true
|
|
|
735
1126
|
* anything you have is left alone and reported, exactly like a first install. Pass `--force`
|
|
736
1127
|
* yourself if you really do want to overwrite local edits.
|
|
737
1128
|
*/
|
|
738
|
-
async function cmdUpdate(
|
|
1129
|
+
async function cmdUpdate(
|
|
1130
|
+
targetRoot: string,
|
|
1131
|
+
flags: Record<string, string | true>,
|
|
1132
|
+
) {
|
|
739
1133
|
const lock = await loadLock(targetRoot);
|
|
740
1134
|
if (!lock.combo || !lock.variant) {
|
|
741
|
-
console.error(
|
|
1135
|
+
console.error(
|
|
1136
|
+
`No auth.lock.json (or it's missing combo/variant) in ${targetRoot} — nothing to update. Use "add <combo>" for a first install.`,
|
|
1137
|
+
);
|
|
742
1138
|
process.exitCode = 1;
|
|
743
1139
|
return;
|
|
744
1140
|
}
|
|
@@ -746,7 +1142,9 @@ async function cmdUpdate(targetRoot: string, flags: Record<string, string | true
|
|
|
746
1142
|
const registry = await loadRegistry();
|
|
747
1143
|
const combo = registry.combos[lock.combo];
|
|
748
1144
|
if (!combo) {
|
|
749
|
-
console.error(
|
|
1145
|
+
console.error(
|
|
1146
|
+
`auth.lock.json names combo "${lock.combo}", which no longer exists in this registry.`,
|
|
1147
|
+
);
|
|
750
1148
|
process.exitCode = 1;
|
|
751
1149
|
return;
|
|
752
1150
|
}
|
|
@@ -758,12 +1156,16 @@ async function cmdUpdate(targetRoot: string, flags: Record<string, string | true
|
|
|
758
1156
|
}
|
|
759
1157
|
|
|
760
1158
|
if (flags.check === true && comboInstallMode(combo) === "scaffold") {
|
|
761
|
-
console.error(
|
|
1159
|
+
console.error(
|
|
1160
|
+
`--check isn't supported for "${lock.combo}" (a scaffold-mode combo) — only merge-mode (api) combos support a dry run.`,
|
|
1161
|
+
);
|
|
762
1162
|
process.exitCode = 1;
|
|
763
1163
|
return;
|
|
764
1164
|
}
|
|
765
1165
|
|
|
766
|
-
console.log(
|
|
1166
|
+
console.log(
|
|
1167
|
+
`Updating "${lock.combo}" (${variant} variant) in ${targetRoot}${flags.force === true ? " — --force: local edits will be overwritten" : ""}`,
|
|
1168
|
+
);
|
|
767
1169
|
await installCombo(lock.combo, combo, variant, registry, targetRoot, flags);
|
|
768
1170
|
}
|
|
769
1171
|
|
|
@@ -776,7 +1178,9 @@ async function cmdUpdate(targetRoot: string, flags: Record<string, string | true
|
|
|
776
1178
|
async function cmdDiff(targetRoot: string) {
|
|
777
1179
|
const lock = await loadLock(targetRoot);
|
|
778
1180
|
if (!lock.combo || !lock.variant) {
|
|
779
|
-
console.error(
|
|
1181
|
+
console.error(
|
|
1182
|
+
`No auth.lock.json (or it's missing combo/variant) in ${targetRoot} — nothing installed to diff.`,
|
|
1183
|
+
);
|
|
780
1184
|
process.exitCode = 1;
|
|
781
1185
|
return;
|
|
782
1186
|
}
|
|
@@ -784,13 +1188,17 @@ async function cmdDiff(targetRoot: string) {
|
|
|
784
1188
|
const registry = await loadRegistry();
|
|
785
1189
|
const combo = registry.combos[lock.combo];
|
|
786
1190
|
if (!combo) {
|
|
787
|
-
console.error(
|
|
1191
|
+
console.error(
|
|
1192
|
+
`auth.lock.json names combo "${lock.combo}", which no longer exists in this registry.`,
|
|
1193
|
+
);
|
|
788
1194
|
process.exitCode = 1;
|
|
789
1195
|
return;
|
|
790
1196
|
}
|
|
791
1197
|
|
|
792
1198
|
if (comboInstallMode(combo) === "scaffold") {
|
|
793
|
-
console.error(
|
|
1199
|
+
console.error(
|
|
1200
|
+
`diff isn't supported yet for "${lock.combo}" (a scaffold-mode combo) — only merge-mode (api) combos support it.`,
|
|
1201
|
+
);
|
|
794
1202
|
process.exitCode = 1;
|
|
795
1203
|
return;
|
|
796
1204
|
}
|
|
@@ -801,21 +1209,41 @@ async function cmdDiff(targetRoot: string) {
|
|
|
801
1209
|
return;
|
|
802
1210
|
}
|
|
803
1211
|
|
|
804
|
-
const plan = await buildMergePlan(
|
|
1212
|
+
const plan = await buildMergePlan(
|
|
1213
|
+
combo,
|
|
1214
|
+
variant,
|
|
1215
|
+
targetRoot,
|
|
1216
|
+
{},
|
|
1217
|
+
lock.files ?? {},
|
|
1218
|
+
);
|
|
805
1219
|
const diffs: DiffEntry[] = [];
|
|
806
1220
|
// force:true so a locally-modified file's difference is captured too — diff wants to show
|
|
807
1221
|
// everything that differs, not just what a real (non---force) install would apply; dryRun:true
|
|
808
1222
|
// so nothing is written.
|
|
809
|
-
await runMergeCopy(plan, registry, targetRoot, {
|
|
1223
|
+
await runMergeCopy(plan, registry, targetRoot, {
|
|
1224
|
+
force: true,
|
|
1225
|
+
forcePaths: new Set(),
|
|
1226
|
+
dryRun: true,
|
|
1227
|
+
collectDiffs: diffs,
|
|
1228
|
+
});
|
|
810
1229
|
|
|
811
1230
|
if (!diffs.length) {
|
|
812
|
-
console.log(
|
|
1231
|
+
console.log(
|
|
1232
|
+
`No differences — every tracked file in "${lock.combo}" (${variant} variant) matches the current registry.`,
|
|
1233
|
+
);
|
|
813
1234
|
return;
|
|
814
1235
|
}
|
|
815
1236
|
|
|
816
1237
|
console.log(`${diffs.length} file(s) differ from the current registry:`);
|
|
817
1238
|
for (const d of diffs) {
|
|
818
|
-
const patch = createTwoFilesPatch(
|
|
1239
|
+
const patch = createTwoFilesPatch(
|
|
1240
|
+
d.path,
|
|
1241
|
+
d.path,
|
|
1242
|
+
d.oldContent ?? "",
|
|
1243
|
+
d.newContent,
|
|
1244
|
+
"installed",
|
|
1245
|
+
"registry",
|
|
1246
|
+
);
|
|
819
1247
|
process.stdout.write(`\n${patch}`);
|
|
820
1248
|
}
|
|
821
1249
|
}
|
|
@@ -823,25 +1251,50 @@ async function cmdDiff(targetRoot: string) {
|
|
|
823
1251
|
async function printUsage(exitCode: number) {
|
|
824
1252
|
const registry = await loadRegistry();
|
|
825
1253
|
console.log("Usage: simple-auth-kit <init|add|update|diff> [...]");
|
|
826
|
-
console.log(
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
console.log(
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
console.log(
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
console.log(
|
|
836
|
-
|
|
1254
|
+
console.log(
|
|
1255
|
+
` init [--config-only] [--kind ...] [--into <path>] (fresh setup: guided "what do you need" picker, like bare "add")`,
|
|
1256
|
+
);
|
|
1257
|
+
console.log(
|
|
1258
|
+
` --config-only: just write .simple-auth-kit.json and stop, no install`,
|
|
1259
|
+
);
|
|
1260
|
+
console.log(
|
|
1261
|
+
` add <combo> [--workspaces] [--force] [--skip-install] [--into <path>] [--path <dir>] [--alias <alias>]`,
|
|
1262
|
+
);
|
|
1263
|
+
console.log(
|
|
1264
|
+
` add [--kind api,admin,mobile] [--framework <name>,...] [--workspaces] [--into <path>] [--name <appName>]`,
|
|
1265
|
+
);
|
|
1266
|
+
console.log(
|
|
1267
|
+
` (bare "add", or "add" with --kind but no combo, launches a guided prompt for whatever's missing)`,
|
|
1268
|
+
);
|
|
1269
|
+
console.log(
|
|
1270
|
+
` update [--check] [--force] [--skip-install] [--into <path>] (re-installs whatever combo+variant auth.lock.json already records)`,
|
|
1271
|
+
);
|
|
1272
|
+
console.log(
|
|
1273
|
+
` --check: report what would change, without writing anything (merge-mode combos only)`,
|
|
1274
|
+
);
|
|
1275
|
+
console.log(
|
|
1276
|
+
` diff [--into <path>] (shows the actual content diff for every tracked file that differs from the current registry — read-only; merge-mode combos only)`,
|
|
1277
|
+
);
|
|
1278
|
+
console.log(
|
|
1279
|
+
` In a TTY, without --force or --check: a file changed locally since install prompts to overwrite, per file.`,
|
|
1280
|
+
);
|
|
1281
|
+
console.log(
|
|
1282
|
+
` --skip-install: don't run the package manager after copying files — the default is to install for you, shadcn-\`add\`-style.`,
|
|
1283
|
+
);
|
|
1284
|
+
console.log(
|
|
1285
|
+
` --pm <npm|pnpm|yarn|bun>: which package manager to install with — default: detected from a lockfile in the target directory, npm if none found.`,
|
|
1286
|
+
);
|
|
837
1287
|
console.log(`\nAvailable combos:`);
|
|
838
1288
|
for (const kind of ["api", "admin", "mobile"] as Kind[]) {
|
|
839
1289
|
const names = combosByKind(registry, kind).map(([name]) => name);
|
|
840
|
-
if (names.length)
|
|
1290
|
+
if (names.length)
|
|
1291
|
+
console.log(` ${KIND_LABELS[kind]}: ${names.join(", ")}`);
|
|
841
1292
|
}
|
|
842
1293
|
console.log(`\nVariants (choose one at install time):`);
|
|
843
1294
|
for (const [name, variant] of Object.entries(registry.variants)) {
|
|
844
|
-
console.log(
|
|
1295
|
+
console.log(
|
|
1296
|
+
` ${name}${variant.flag ? ` (${variant.flag})` : " (default)"} — ${variant.description}`,
|
|
1297
|
+
);
|
|
845
1298
|
}
|
|
846
1299
|
process.exitCode = exitCode;
|
|
847
1300
|
}
|
|
@@ -850,7 +1303,12 @@ async function main() {
|
|
|
850
1303
|
const { command, positional, flags } = parseArgs(process.argv.slice(2));
|
|
851
1304
|
const targetRoot = resolve(process.cwd(), flagString(flags.into) ?? ".");
|
|
852
1305
|
|
|
853
|
-
if (
|
|
1306
|
+
if (
|
|
1307
|
+
command === "--help" ||
|
|
1308
|
+
command === "-h" ||
|
|
1309
|
+
command === "help" ||
|
|
1310
|
+
flags.help === true
|
|
1311
|
+
) {
|
|
854
1312
|
await printUsage(0);
|
|
855
1313
|
return;
|
|
856
1314
|
}
|