@drmhse/authos-cli 0.1.1 → 0.1.2
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/dist/bin.js +653 -16
- package/dist/index.d.ts +6 -1
- package/dist/index.js +653 -16
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -70,6 +70,18 @@ function getAdapterPackage(framework) {
|
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
function readPackageJson(cwd) {
|
|
74
|
+
const packageJsonPath = path2__namespace.join(cwd, "package.json");
|
|
75
|
+
if (!fs__namespace.existsSync(packageJsonPath)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const content = fs__namespace.readFileSync(packageJsonPath, "utf8");
|
|
80
|
+
return JSON.parse(content);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
73
85
|
function writeFile(filePath, content) {
|
|
74
86
|
const dir = path2__namespace.dirname(filePath);
|
|
75
87
|
if (!fs__namespace.existsSync(dir)) {
|
|
@@ -137,6 +149,31 @@ function getFrameworkName(framework) {
|
|
|
137
149
|
return "Unknown";
|
|
138
150
|
}
|
|
139
151
|
}
|
|
152
|
+
function detectTailwind(cwd) {
|
|
153
|
+
const packageJson = readPackageJson(cwd);
|
|
154
|
+
if (!packageJson) return false;
|
|
155
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
156
|
+
return !!(deps["tailwindcss"] || deps["@tailwindcss/vite"] || deps["@tailwindcss/typography"]);
|
|
157
|
+
}
|
|
158
|
+
function hasTailwindConfig(cwd) {
|
|
159
|
+
const configFiles = [
|
|
160
|
+
"tailwind.config.js",
|
|
161
|
+
"tailwind.config.ts",
|
|
162
|
+
"tailwind.config.cjs",
|
|
163
|
+
"tailwind.config.mjs",
|
|
164
|
+
"tailwind.config.cts"
|
|
165
|
+
];
|
|
166
|
+
return configFiles.some((file) => fs__namespace.existsSync(path2__namespace.join(cwd, file)));
|
|
167
|
+
}
|
|
168
|
+
function isTailwindConfigured(cwd) {
|
|
169
|
+
return detectTailwind(cwd) || hasTailwindConfig(cwd);
|
|
170
|
+
}
|
|
171
|
+
function detectStyling(cwd) {
|
|
172
|
+
if (isTailwindConfigured(cwd)) {
|
|
173
|
+
return "tailwind";
|
|
174
|
+
}
|
|
175
|
+
return "unknown";
|
|
176
|
+
}
|
|
140
177
|
|
|
141
178
|
// src/commands/init.ts
|
|
142
179
|
async function initCommand(options = {}) {
|
|
@@ -995,6 +1032,510 @@ async function handleSignOut() {
|
|
|
995
1032
|
</div>
|
|
996
1033
|
</template>
|
|
997
1034
|
`;
|
|
1035
|
+
var loginFormReactCssModules = `"use client";
|
|
1036
|
+
|
|
1037
|
+
import { useState } from "react";
|
|
1038
|
+
import { useAuthOS } from "@drmhse/authos-react";
|
|
1039
|
+
import { AuthErrorCodes } from "@drmhse/sso-sdk";
|
|
1040
|
+
import styles from "./LoginForm.module.css";
|
|
1041
|
+
|
|
1042
|
+
interface LoginFormProps {
|
|
1043
|
+
onSuccess?: () => void;
|
|
1044
|
+
redirectTo?: string;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
export function LoginForm({ onSuccess, redirectTo }: LoginFormProps) {
|
|
1048
|
+
const { client } = useAuthOS();
|
|
1049
|
+
const [email, setEmail] = useState("");
|
|
1050
|
+
const [password, setPassword] = useState("");
|
|
1051
|
+
const [error, setError] = useState<string | null>(null);
|
|
1052
|
+
const [loading, setLoading] = useState(false);
|
|
1053
|
+
const [mfaRequired, setMfaRequired] = useState(false);
|
|
1054
|
+
const [mfaCode, setMfaCode] = useState("");
|
|
1055
|
+
const [mfaToken, setMfaToken] = useState<string | null>(null);
|
|
1056
|
+
|
|
1057
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
1058
|
+
e.preventDefault();
|
|
1059
|
+
setError(null);
|
|
1060
|
+
setLoading(true);
|
|
1061
|
+
|
|
1062
|
+
try {
|
|
1063
|
+
const response = await client.auth.login({ email, password });
|
|
1064
|
+
|
|
1065
|
+
if (response.mfa_required) {
|
|
1066
|
+
setMfaRequired(true);
|
|
1067
|
+
setMfaToken(response.mfa_token || null);
|
|
1068
|
+
} else {
|
|
1069
|
+
onSuccess?.();
|
|
1070
|
+
if (redirectTo) {
|
|
1071
|
+
window.location.href = redirectTo;
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
} catch (err: unknown) {
|
|
1075
|
+
if (err && typeof err === "object" && "errorCode" in err) {
|
|
1076
|
+
const apiError = err as { errorCode: string; message: string };
|
|
1077
|
+
if (apiError.errorCode === AuthErrorCodes.MFA_REQUIRED) {
|
|
1078
|
+
setMfaRequired(true);
|
|
1079
|
+
} else {
|
|
1080
|
+
setError(apiError.message || "Login failed");
|
|
1081
|
+
}
|
|
1082
|
+
} else {
|
|
1083
|
+
setError("An unexpected error occurred");
|
|
1084
|
+
}
|
|
1085
|
+
} finally {
|
|
1086
|
+
setLoading(false);
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
|
|
1090
|
+
const handleMfaSubmit = async (e: React.FormEvent) => {
|
|
1091
|
+
e.preventDefault();
|
|
1092
|
+
setError(null);
|
|
1093
|
+
setLoading(true);
|
|
1094
|
+
|
|
1095
|
+
try {
|
|
1096
|
+
await client.auth.verifyMfa({ code: mfaCode, mfa_token: mfaToken! });
|
|
1097
|
+
onSuccess?.();
|
|
1098
|
+
if (redirectTo) {
|
|
1099
|
+
window.location.href = redirectTo;
|
|
1100
|
+
}
|
|
1101
|
+
} catch (err: unknown) {
|
|
1102
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
1103
|
+
setError((err as { message: string }).message || "Invalid MFA code");
|
|
1104
|
+
} else {
|
|
1105
|
+
setError("Invalid MFA code");
|
|
1106
|
+
}
|
|
1107
|
+
} finally {
|
|
1108
|
+
setLoading(false);
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
if (mfaRequired) {
|
|
1113
|
+
return (
|
|
1114
|
+
<form onSubmit={handleMfaSubmit} className={styles.form}>
|
|
1115
|
+
<div className={styles.header}>
|
|
1116
|
+
<h2 className={styles.title}>Two-Factor Authentication</h2>
|
|
1117
|
+
<p className={styles.subtitle}>Enter the code from your authenticator app</p>
|
|
1118
|
+
</div>
|
|
1119
|
+
|
|
1120
|
+
{error && <div className={styles.error}>{error}</div>}
|
|
1121
|
+
|
|
1122
|
+
<div className={styles.field}>
|
|
1123
|
+
<label htmlFor="mfa-code" className={styles.label}>Verification Code</label>
|
|
1124
|
+
<input
|
|
1125
|
+
id="mfa-code"
|
|
1126
|
+
type="text"
|
|
1127
|
+
inputMode="numeric"
|
|
1128
|
+
autoComplete="one-time-code"
|
|
1129
|
+
value={mfaCode}
|
|
1130
|
+
onChange={(e) => setMfaCode(e.target.value)}
|
|
1131
|
+
className={styles.input}
|
|
1132
|
+
placeholder="000000"
|
|
1133
|
+
maxLength={6}
|
|
1134
|
+
required
|
|
1135
|
+
/>
|
|
1136
|
+
</div>
|
|
1137
|
+
|
|
1138
|
+
<button type="submit" disabled={loading} className={styles.button}>
|
|
1139
|
+
{loading ? "Verifying..." : "Verify"}
|
|
1140
|
+
</button>
|
|
1141
|
+
|
|
1142
|
+
<button type="button" onClick={() => setMfaRequired(false)} className={styles.link}>
|
|
1143
|
+
Back to login
|
|
1144
|
+
</button>
|
|
1145
|
+
</form>
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
return (
|
|
1150
|
+
<form onSubmit={handleSubmit} className={styles.form}>
|
|
1151
|
+
<div className={styles.header}>
|
|
1152
|
+
<h2 className={styles.title}>Sign In</h2>
|
|
1153
|
+
<p className={styles.subtitle}>Enter your credentials to continue</p>
|
|
1154
|
+
</div>
|
|
1155
|
+
|
|
1156
|
+
{error && <div className={styles.error}>{error}</div>}
|
|
1157
|
+
|
|
1158
|
+
<div className={styles.field}>
|
|
1159
|
+
<label htmlFor="email" className={styles.label}>Email</label>
|
|
1160
|
+
<input
|
|
1161
|
+
id="email"
|
|
1162
|
+
type="email"
|
|
1163
|
+
autoComplete="email"
|
|
1164
|
+
value={email}
|
|
1165
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
1166
|
+
className={styles.input}
|
|
1167
|
+
placeholder="you@example.com"
|
|
1168
|
+
required
|
|
1169
|
+
/>
|
|
1170
|
+
</div>
|
|
1171
|
+
|
|
1172
|
+
<div className={styles.field}>
|
|
1173
|
+
<label htmlFor="password" className={styles.label}>Password</label>
|
|
1174
|
+
<input
|
|
1175
|
+
id="password"
|
|
1176
|
+
type="password"
|
|
1177
|
+
autoComplete="current-password"
|
|
1178
|
+
value={password}
|
|
1179
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
1180
|
+
className={styles.input}
|
|
1181
|
+
placeholder="Your password"
|
|
1182
|
+
required
|
|
1183
|
+
/>
|
|
1184
|
+
</div>
|
|
1185
|
+
|
|
1186
|
+
<button type="submit" disabled={loading} className={styles.button}>
|
|
1187
|
+
{loading ? "Signing in..." : "Sign In"}
|
|
1188
|
+
</button>
|
|
1189
|
+
</form>
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
`;
|
|
1193
|
+
var loginFormCssModulesStyles = `.form {
|
|
1194
|
+
display: flex;
|
|
1195
|
+
flex-direction: column;
|
|
1196
|
+
gap: 1rem;
|
|
1197
|
+
width: 100%;
|
|
1198
|
+
max-width: 24rem;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
.header {
|
|
1202
|
+
text-align: center;
|
|
1203
|
+
margin-bottom: 1.5rem;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
.title {
|
|
1207
|
+
font-size: 1.25rem;
|
|
1208
|
+
font-weight: 600;
|
|
1209
|
+
margin: 0;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
.subtitle {
|
|
1213
|
+
color: #6b7280;
|
|
1214
|
+
font-size: 0.875rem;
|
|
1215
|
+
margin-top: 0.25rem;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
.field {
|
|
1219
|
+
display: flex;
|
|
1220
|
+
flex-direction: column;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.label {
|
|
1224
|
+
font-size: 0.875rem;
|
|
1225
|
+
font-weight: 500;
|
|
1226
|
+
margin-bottom: 0.25rem;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
.input {
|
|
1230
|
+
width: 100%;
|
|
1231
|
+
padding: 0.5rem 0.75rem;
|
|
1232
|
+
border: 1px solid #e2e8f0;
|
|
1233
|
+
border-radius: 0.375rem;
|
|
1234
|
+
font-size: 1rem;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
.input:focus {
|
|
1238
|
+
outline: none;
|
|
1239
|
+
border-color: #3b82f6;
|
|
1240
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
.button {
|
|
1244
|
+
width: 100%;
|
|
1245
|
+
padding: 0.5rem 1rem;
|
|
1246
|
+
background-color: #2563eb;
|
|
1247
|
+
color: white;
|
|
1248
|
+
border: none;
|
|
1249
|
+
border-radius: 0.375rem;
|
|
1250
|
+
font-weight: 500;
|
|
1251
|
+
cursor: pointer;
|
|
1252
|
+
font-size: 1rem;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.button:hover {
|
|
1256
|
+
background-color: #1d4ed8;
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.button:disabled {
|
|
1260
|
+
opacity: 0.5;
|
|
1261
|
+
cursor: not-allowed;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
.error {
|
|
1265
|
+
background-color: #fef2f2;
|
|
1266
|
+
color: #dc2626;
|
|
1267
|
+
padding: 0.5rem 1rem;
|
|
1268
|
+
border-radius: 0.375rem;
|
|
1269
|
+
font-size: 0.875rem;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
.link {
|
|
1273
|
+
background: none;
|
|
1274
|
+
border: none;
|
|
1275
|
+
color: #6b7280;
|
|
1276
|
+
font-size: 0.875rem;
|
|
1277
|
+
cursor: pointer;
|
|
1278
|
+
text-align: center;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
.link:hover {
|
|
1282
|
+
text-decoration: underline;
|
|
1283
|
+
}
|
|
1284
|
+
`;
|
|
1285
|
+
var loginFormVueScoped = `<script setup lang="ts">
|
|
1286
|
+
import { ref } from "vue";
|
|
1287
|
+
import { useAuthOS } from "@drmhse/authos-vue";
|
|
1288
|
+
import { AuthErrorCodes } from "@drmhse/sso-sdk";
|
|
1289
|
+
|
|
1290
|
+
interface Props {
|
|
1291
|
+
redirectTo?: string;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
const props = defineProps<Props>();
|
|
1295
|
+
const emit = defineEmits<{
|
|
1296
|
+
success: [];
|
|
1297
|
+
}>();
|
|
1298
|
+
|
|
1299
|
+
const { client } = useAuthOS();
|
|
1300
|
+
|
|
1301
|
+
const email = ref("");
|
|
1302
|
+
const password = ref("");
|
|
1303
|
+
const error = ref<string | null>(null);
|
|
1304
|
+
const loading = ref(false);
|
|
1305
|
+
const mfaRequired = ref(false);
|
|
1306
|
+
const mfaCode = ref("");
|
|
1307
|
+
const mfaToken = ref<string | null>(null);
|
|
1308
|
+
|
|
1309
|
+
async function handleSubmit() {
|
|
1310
|
+
error.value = null;
|
|
1311
|
+
loading.value = true;
|
|
1312
|
+
|
|
1313
|
+
try {
|
|
1314
|
+
const response = await client.auth.login({
|
|
1315
|
+
email: email.value,
|
|
1316
|
+
password: password.value,
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
if (response.mfa_required) {
|
|
1320
|
+
mfaRequired.value = true;
|
|
1321
|
+
mfaToken.value = response.mfa_token || null;
|
|
1322
|
+
} else {
|
|
1323
|
+
emit("success");
|
|
1324
|
+
if (props.redirectTo) {
|
|
1325
|
+
window.location.href = props.redirectTo;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
} catch (err: unknown) {
|
|
1329
|
+
if (err && typeof err === "object" && "errorCode" in err) {
|
|
1330
|
+
const apiError = err as { errorCode: string; message: string };
|
|
1331
|
+
if (apiError.errorCode === AuthErrorCodes.MFA_REQUIRED) {
|
|
1332
|
+
mfaRequired.value = true;
|
|
1333
|
+
} else {
|
|
1334
|
+
error.value = apiError.message || "Login failed";
|
|
1335
|
+
}
|
|
1336
|
+
} else {
|
|
1337
|
+
error.value = "An unexpected error occurred";
|
|
1338
|
+
}
|
|
1339
|
+
} finally {
|
|
1340
|
+
loading.value = false;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
async function handleMfaSubmit() {
|
|
1345
|
+
error.value = null;
|
|
1346
|
+
loading.value = true;
|
|
1347
|
+
|
|
1348
|
+
try {
|
|
1349
|
+
await client.auth.verifyMfa({
|
|
1350
|
+
code: mfaCode.value,
|
|
1351
|
+
mfa_token: mfaToken.value!,
|
|
1352
|
+
});
|
|
1353
|
+
emit("success");
|
|
1354
|
+
if (props.redirectTo) {
|
|
1355
|
+
window.location.href = props.redirectTo;
|
|
1356
|
+
}
|
|
1357
|
+
} catch (err: unknown) {
|
|
1358
|
+
if (err && typeof err === "object" && "message" in err) {
|
|
1359
|
+
error.value = (err as { message: string }).message || "Invalid MFA code";
|
|
1360
|
+
} else {
|
|
1361
|
+
error.value = "Invalid MFA code";
|
|
1362
|
+
}
|
|
1363
|
+
} finally {
|
|
1364
|
+
loading.value = false;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function backToLogin() {
|
|
1369
|
+
mfaRequired.value = false;
|
|
1370
|
+
mfaCode.value = "";
|
|
1371
|
+
error.value = null;
|
|
1372
|
+
}
|
|
1373
|
+
</script>
|
|
1374
|
+
|
|
1375
|
+
<template>
|
|
1376
|
+
<form v-if="mfaRequired" @submit.prevent="handleMfaSubmit" class="form">
|
|
1377
|
+
<div class="header">
|
|
1378
|
+
<h2 class="title">Two-Factor Authentication</h2>
|
|
1379
|
+
<p class="subtitle">Enter the code from your authenticator app</p>
|
|
1380
|
+
</div>
|
|
1381
|
+
|
|
1382
|
+
<div v-if="error" class="error">{{ error }}</div>
|
|
1383
|
+
|
|
1384
|
+
<div class="field">
|
|
1385
|
+
<label for="mfa-code" class="label">Verification Code</label>
|
|
1386
|
+
<input
|
|
1387
|
+
id="mfa-code"
|
|
1388
|
+
v-model="mfaCode"
|
|
1389
|
+
type="text"
|
|
1390
|
+
inputmode="numeric"
|
|
1391
|
+
autocomplete="one-time-code"
|
|
1392
|
+
class="input"
|
|
1393
|
+
placeholder="000000"
|
|
1394
|
+
maxlength="6"
|
|
1395
|
+
required
|
|
1396
|
+
/>
|
|
1397
|
+
</div>
|
|
1398
|
+
|
|
1399
|
+
<button type="submit" :disabled="loading" class="button">
|
|
1400
|
+
{{ loading ? "Verifying..." : "Verify" }}
|
|
1401
|
+
</button>
|
|
1402
|
+
|
|
1403
|
+
<button type="button" @click="backToLogin" class="link">Back to login</button>
|
|
1404
|
+
</form>
|
|
1405
|
+
|
|
1406
|
+
<form v-else @submit.prevent="handleSubmit" class="form">
|
|
1407
|
+
<div class="header">
|
|
1408
|
+
<h2 class="title">Sign In</h2>
|
|
1409
|
+
<p class="subtitle">Enter your credentials to continue</p>
|
|
1410
|
+
</div>
|
|
1411
|
+
|
|
1412
|
+
<div v-if="error" class="error">{{ error }}</div>
|
|
1413
|
+
|
|
1414
|
+
<div class="field">
|
|
1415
|
+
<label for="email" class="label">Email</label>
|
|
1416
|
+
<input
|
|
1417
|
+
id="email"
|
|
1418
|
+
v-model="email"
|
|
1419
|
+
type="email"
|
|
1420
|
+
autocomplete="email"
|
|
1421
|
+
class="input"
|
|
1422
|
+
placeholder="you@example.com"
|
|
1423
|
+
required
|
|
1424
|
+
/>
|
|
1425
|
+
</div>
|
|
1426
|
+
|
|
1427
|
+
<div class="field">
|
|
1428
|
+
<label for="password" class="label">Password</label>
|
|
1429
|
+
<input
|
|
1430
|
+
id="password"
|
|
1431
|
+
v-model="password"
|
|
1432
|
+
type="password"
|
|
1433
|
+
autocomplete="current-password"
|
|
1434
|
+
class="input"
|
|
1435
|
+
placeholder="Your password"
|
|
1436
|
+
required
|
|
1437
|
+
/>
|
|
1438
|
+
</div>
|
|
1439
|
+
|
|
1440
|
+
<button type="submit" :disabled="loading" class="button">
|
|
1441
|
+
{{ loading ? "Signing in..." : "Sign In" }}
|
|
1442
|
+
</button>
|
|
1443
|
+
</form>
|
|
1444
|
+
</template>
|
|
1445
|
+
|
|
1446
|
+
<style scoped>
|
|
1447
|
+
.form {
|
|
1448
|
+
display: flex;
|
|
1449
|
+
flex-direction: column;
|
|
1450
|
+
gap: 1rem;
|
|
1451
|
+
width: 100%;
|
|
1452
|
+
max-width: 24rem;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
.header {
|
|
1456
|
+
text-align: center;
|
|
1457
|
+
margin-bottom: 1.5rem;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
.title {
|
|
1461
|
+
font-size: 1.25rem;
|
|
1462
|
+
font-weight: 600;
|
|
1463
|
+
margin: 0;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
.subtitle {
|
|
1467
|
+
color: #6b7280;
|
|
1468
|
+
font-size: 0.875rem;
|
|
1469
|
+
margin-top: 0.25rem;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
.field {
|
|
1473
|
+
display: flex;
|
|
1474
|
+
flex-direction: column;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
.label {
|
|
1478
|
+
font-size: 0.875rem;
|
|
1479
|
+
font-weight: 500;
|
|
1480
|
+
margin-bottom: 0.25rem;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
.input {
|
|
1484
|
+
width: 100%;
|
|
1485
|
+
padding: 0.5rem 0.75rem;
|
|
1486
|
+
border: 1px solid #e2e8f0;
|
|
1487
|
+
border-radius: 0.375rem;
|
|
1488
|
+
font-size: 1rem;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
.input:focus {
|
|
1492
|
+
outline: none;
|
|
1493
|
+
border-color: #3b82f6;
|
|
1494
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
.button {
|
|
1498
|
+
width: 100%;
|
|
1499
|
+
padding: 0.5rem 1rem;
|
|
1500
|
+
background-color: #2563eb;
|
|
1501
|
+
color: white;
|
|
1502
|
+
border: none;
|
|
1503
|
+
border-radius: 0.375rem;
|
|
1504
|
+
font-weight: 500;
|
|
1505
|
+
cursor: pointer;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
.button:hover {
|
|
1509
|
+
background-color: #1d4ed8;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
.button:disabled {
|
|
1513
|
+
opacity: 0.5;
|
|
1514
|
+
cursor: not-allowed;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.error {
|
|
1518
|
+
background-color: #fef2f2;
|
|
1519
|
+
color: #dc2626;
|
|
1520
|
+
padding: 0.5rem 1rem;
|
|
1521
|
+
border-radius: 0.375rem;
|
|
1522
|
+
font-size: 0.875rem;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
.link {
|
|
1526
|
+
background: none;
|
|
1527
|
+
border: none;
|
|
1528
|
+
color: #6b7280;
|
|
1529
|
+
font-size: 0.875rem;
|
|
1530
|
+
cursor: pointer;
|
|
1531
|
+
text-align: center;
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
.link:hover {
|
|
1535
|
+
text-decoration: underline;
|
|
1536
|
+
}
|
|
1537
|
+
</style>
|
|
1538
|
+
`;
|
|
998
1539
|
var templates = {
|
|
999
1540
|
"login-form": {
|
|
1000
1541
|
name: "Login Form",
|
|
@@ -1003,11 +1544,33 @@ var templates = {
|
|
|
1003
1544
|
{
|
|
1004
1545
|
name: "LoginForm",
|
|
1005
1546
|
content: {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1547
|
+
tailwind: {
|
|
1548
|
+
react: loginFormReact,
|
|
1549
|
+
next: loginFormReact,
|
|
1550
|
+
vue: loginFormVue,
|
|
1551
|
+
nuxt: loginFormVue,
|
|
1552
|
+
unknown: loginFormReact
|
|
1553
|
+
},
|
|
1554
|
+
"css-modules": {
|
|
1555
|
+
react: loginFormReactCssModules,
|
|
1556
|
+
next: loginFormReactCssModules,
|
|
1557
|
+
vue: loginFormVueScoped,
|
|
1558
|
+
nuxt: loginFormVueScoped,
|
|
1559
|
+
unknown: loginFormReactCssModules
|
|
1560
|
+
},
|
|
1561
|
+
none: {
|
|
1562
|
+
react: loginFormReact,
|
|
1563
|
+
// Fallback to tailwind for now
|
|
1564
|
+
next: loginFormReact,
|
|
1565
|
+
vue: loginFormVue,
|
|
1566
|
+
nuxt: loginFormVue,
|
|
1567
|
+
unknown: loginFormReact
|
|
1568
|
+
}
|
|
1569
|
+
},
|
|
1570
|
+
extraFiles: {
|
|
1571
|
+
"css-modules": [
|
|
1572
|
+
{ name: "LoginForm.module.css", content: loginFormCssModulesStyles }
|
|
1573
|
+
]
|
|
1011
1574
|
}
|
|
1012
1575
|
}
|
|
1013
1576
|
]
|
|
@@ -1019,11 +1582,28 @@ var templates = {
|
|
|
1019
1582
|
{
|
|
1020
1583
|
name: "OrganizationSwitcher",
|
|
1021
1584
|
content: {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1585
|
+
tailwind: {
|
|
1586
|
+
react: orgSwitcherReact,
|
|
1587
|
+
next: orgSwitcherReact,
|
|
1588
|
+
vue: orgSwitcherVue,
|
|
1589
|
+
nuxt: orgSwitcherVue,
|
|
1590
|
+
unknown: orgSwitcherReact
|
|
1591
|
+
},
|
|
1592
|
+
"css-modules": {
|
|
1593
|
+
react: orgSwitcherReact,
|
|
1594
|
+
// Will add CSS modules variant later
|
|
1595
|
+
next: orgSwitcherReact,
|
|
1596
|
+
vue: orgSwitcherVue,
|
|
1597
|
+
nuxt: orgSwitcherVue,
|
|
1598
|
+
unknown: orgSwitcherReact
|
|
1599
|
+
},
|
|
1600
|
+
none: {
|
|
1601
|
+
react: orgSwitcherReact,
|
|
1602
|
+
next: orgSwitcherReact,
|
|
1603
|
+
vue: orgSwitcherVue,
|
|
1604
|
+
nuxt: orgSwitcherVue,
|
|
1605
|
+
unknown: orgSwitcherReact
|
|
1606
|
+
}
|
|
1027
1607
|
}
|
|
1028
1608
|
}
|
|
1029
1609
|
]
|
|
@@ -1035,11 +1615,28 @@ var templates = {
|
|
|
1035
1615
|
{
|
|
1036
1616
|
name: "UserProfile",
|
|
1037
1617
|
content: {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1618
|
+
tailwind: {
|
|
1619
|
+
react: userProfileReact,
|
|
1620
|
+
next: userProfileReact,
|
|
1621
|
+
vue: userProfileVue,
|
|
1622
|
+
nuxt: userProfileVue,
|
|
1623
|
+
unknown: userProfileReact
|
|
1624
|
+
},
|
|
1625
|
+
"css-modules": {
|
|
1626
|
+
react: userProfileReact,
|
|
1627
|
+
// Will add CSS modules variant later
|
|
1628
|
+
next: userProfileReact,
|
|
1629
|
+
vue: userProfileVue,
|
|
1630
|
+
nuxt: userProfileVue,
|
|
1631
|
+
unknown: userProfileReact
|
|
1632
|
+
},
|
|
1633
|
+
none: {
|
|
1634
|
+
react: userProfileReact,
|
|
1635
|
+
next: userProfileReact,
|
|
1636
|
+
vue: userProfileVue,
|
|
1637
|
+
nuxt: userProfileVue,
|
|
1638
|
+
unknown: userProfileReact
|
|
1639
|
+
}
|
|
1043
1640
|
}
|
|
1044
1641
|
}
|
|
1045
1642
|
]
|
|
@@ -1077,6 +1674,26 @@ async function addCommand(templateName, options = {}) {
|
|
|
1077
1674
|
} else {
|
|
1078
1675
|
log.info(`Detected ${getFrameworkName(framework)} project`);
|
|
1079
1676
|
}
|
|
1677
|
+
let styling = detectStyling(cwd);
|
|
1678
|
+
if (styling === "unknown") {
|
|
1679
|
+
const response = await prompts2__default.default({
|
|
1680
|
+
type: "select",
|
|
1681
|
+
name: "styling",
|
|
1682
|
+
message: "How do you want to style this component?",
|
|
1683
|
+
choices: [
|
|
1684
|
+
{ title: "Tailwind CSS (utility classes)", value: "tailwind" },
|
|
1685
|
+
{ title: "CSS Modules (.module.css)", value: "css-modules" },
|
|
1686
|
+
{ title: "Unstyled (headless)", value: "none" }
|
|
1687
|
+
]
|
|
1688
|
+
});
|
|
1689
|
+
if (!response.styling) {
|
|
1690
|
+
log.error("Command cancelled.");
|
|
1691
|
+
process.exit(1);
|
|
1692
|
+
}
|
|
1693
|
+
styling = response.styling;
|
|
1694
|
+
} else {
|
|
1695
|
+
log.info(`Using Tailwind CSS styling (detected)`);
|
|
1696
|
+
}
|
|
1080
1697
|
if (!templateName) {
|
|
1081
1698
|
const availableTemplates = getAvailableTemplates();
|
|
1082
1699
|
const choices = availableTemplates.map((name) => {
|
|
@@ -1127,9 +1744,29 @@ async function addCommand(templateName, options = {}) {
|
|
|
1127
1744
|
continue;
|
|
1128
1745
|
}
|
|
1129
1746
|
}
|
|
1130
|
-
const content = file.content[framework];
|
|
1747
|
+
const content = file.content[styling]?.[framework] ?? file.content["none"]?.[framework] ?? "";
|
|
1131
1748
|
writeFile(filePath, content);
|
|
1132
1749
|
log.success(`Created ${path2__namespace.relative(cwd, filePath)}`);
|
|
1750
|
+
const extraFiles = file.extraFiles?.[styling];
|
|
1751
|
+
if (extraFiles) {
|
|
1752
|
+
for (const extra of extraFiles) {
|
|
1753
|
+
const extraPath = path2__namespace.join(componentsDir, extra.name);
|
|
1754
|
+
if (fileExists(extraPath) && !options.force) {
|
|
1755
|
+
const response = await prompts2__default.default({
|
|
1756
|
+
type: "confirm",
|
|
1757
|
+
name: "overwrite",
|
|
1758
|
+
message: `${extra.name} already exists. Overwrite?`,
|
|
1759
|
+
initial: false
|
|
1760
|
+
});
|
|
1761
|
+
if (!response.overwrite) {
|
|
1762
|
+
log.warn(`Skipped ${extra.name}`);
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
writeFile(extraPath, extra.content);
|
|
1767
|
+
log.success(`Created ${path2__namespace.relative(cwd, extraPath)}`);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1133
1770
|
}
|
|
1134
1771
|
console.log("\n");
|
|
1135
1772
|
log.success(`Added ${template.name} component!`);
|