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