vue3plate-auth-sanctum
Version:
Vue 3 authentication package for vue3plate boilerplate that integrates with Laravel Sanctum
353 lines (281 loc) • 14.1 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
class Vue3PlateAuthInstaller {
constructor() {
this.projectRoot = process.cwd();
this.packageRoot = path.resolve(__dirname, '..');
this.stubsPath = path.join(this.packageRoot, 'stubs');
// Colors for console output
this.colors = {
green: '\x1b[32m',
yellow: '\x1b[33m',
red: '\x1b[31m',
blue: '\x1b[34m',
reset: '\x1b[0m'
};
}
log(message, color = 'reset') {
console.log(`${this.colors[color]}${message}${this.colors.reset}`);
}
async install() {
try {
this.log('🚀 Installing Vue3Plate Authentication with Sanctum...', 'blue');
// Check if this is a Vue3Plate project
if (!this.isVue3PlateProject()) {
this.log('❌ This doesn\'t appear to be a Vue3Plate project!', 'red');
return;
}
// Copy authentication components
await this.copyAuthComponents();
// Copy authentication views
await this.copyAuthViews();
// Copy authentication utilities
await this.copyAuthUtils();
// Copy and merge store modules
await this.copyAuthStore();
// Copy composables
await this.copyComposables();
// Update router with auth routes
await this.updateRouter();
// Update main.js with auth initialization
await this.updateMainJs();
// Create auth configuration
await this.createAuthConfig();
this.log('✅ Vue3Plate Authentication installed successfully!', 'green');
this.log('📖 Please check README.md for setup instructions.', 'yellow');
} catch (error) {
this.log(`❌ Installation failed: ${error.message}`, 'red');
process.exit(1);
}
}
isVue3PlateProject() {
const packageJsonPath = path.join(this.projectRoot, 'package.json');
if (!fs.existsSync(packageJsonPath)) return false;
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
// Check for Vue3Plate project indicators
const isVuePlateProject = packageJson.name && (
packageJson.name.includes('vue3plate') ||
packageJson.name.includes('vueplate') ||
packageJson.name === 'vueplate'
);
// Also check for Vue 3 dependencies as additional confirmation
const hasVue3 = packageJson.dependencies && packageJson.dependencies.vue;
const hasVueRouter = packageJson.dependencies && packageJson.dependencies['vue-router'];
const hasVuex = packageJson.dependencies && packageJson.dependencies.vuex;
return (isVuePlateProject || (hasVue3 && hasVueRouter && hasVuex)) &&
fs.existsSync(path.join(this.projectRoot, 'src'));
}
async copyAuthComponents() {
this.log('📦 Copying authentication components...', 'blue');
const srcComponents = path.join(this.packageRoot, 'src', 'components');
const destComponents = path.join(this.projectRoot, 'src', 'components', 'auth');
await this.copyDirectory(srcComponents, destComponents);
}
async copyAuthViews() {
this.log('📄 Copying authentication views...', 'blue');
const srcViews = path.join(this.packageRoot, 'src', 'views');
const destViews = path.join(this.projectRoot, 'src', 'views', 'auth');
await this.copyDirectory(srcViews, destViews);
}
async copyAuthStore() {
this.log('🏪 Setting up authentication store...', 'blue');
const srcStore = path.join(this.packageRoot, 'src', 'store', 'auth.js');
const destStore = path.join(this.projectRoot, 'src', 'store', 'modules', 'auth.js');
// Ensure modules directory exists
const modulesDir = path.dirname(destStore);
if (!fs.existsSync(modulesDir)) {
fs.mkdirSync(modulesDir, { recursive: true });
}
// Copy the auth store file and fix import paths
if (fs.existsSync(srcStore)) {
let authStoreContent = fs.readFileSync(srcStore, 'utf8');
// Fix import paths for the new location (store/modules/auth.js)
authStoreContent = authStoreContent.replace(
"import { authApi } from '../utils/api.js'",
"import { authApi } from '../../utils/api.js'"
);
// Don't overwrite if file already exists
if (!fs.existsSync(destStore)) {
fs.writeFileSync(destStore, authStoreContent);
this.log(`Created auth store: ${destStore}`, 'green');
} else {
this.log(`Skipping ${destStore} (already exists)`, 'yellow');
}
}
// Update main store to include auth module
await this.updateMainStore();
}
async copyComposables() {
this.log('🔧 Copying authentication composables...', 'blue');
const srcComposables = path.join(this.packageRoot, 'src', 'composables');
const destComposables = path.join(this.projectRoot, 'src', 'composables');
await this.copyDirectory(srcComposables, destComposables);
}
async copyAuthUtils() {
this.log('⚙️ Copying authentication utilities...', 'blue');
const srcUtils = path.join(this.packageRoot, 'src', 'utils');
const destUtils = path.join(this.projectRoot, 'src', 'utils');
// Ensure utils directory exists
if (!fs.existsSync(destUtils)) {
fs.mkdirSync(destUtils, { recursive: true });
}
await this.copyDirectory(srcUtils, destUtils);
}
async updateRouter() {
this.log('🛣️ Updating router with auth routes...', 'blue');
const routerPath = path.join(this.projectRoot, 'src', 'router', 'index.js');
if (fs.existsSync(routerPath)) {
let routerContent = fs.readFileSync(routerPath, 'utf8');
// Add auth routes import
const authImport = "import authRoutes from './auth.js';\n";
if (!routerContent.includes("import authRoutes from './auth.js'")) {
routerContent = routerContent.replace(
/import.*from.*vue-router.*;?\n/,
match => match + authImport
);
}
// Add auth routes to routes array
const routesPattern = /routes:\s*\[([\s\S]*?)\]/;
if (routesPattern.test(routerContent) && !routerContent.includes('...authRoutes')) {
routerContent = routerContent.replace(
routesPattern,
(match, routes) => {
const trimmedRoutes = routes.trim();
const lastChar = trimmedRoutes.slice(-1);
const needsComma = lastChar !== '' && lastChar !== ',';
return `routes: [${routes}${needsComma ? ',' : ''}\n ...authRoutes\n ]`;
}
);
}
fs.writeFileSync(routerPath, routerContent);
}
// Copy auth routes file
const srcAuthRoutes = path.join(this.stubsPath, 'router', 'auth.js');
const destAuthRoutes = path.join(this.projectRoot, 'src', 'router', 'auth.js');
await this.copyFile(srcAuthRoutes, destAuthRoutes);
}
async updateMainJs() {
this.log('⚙️ Updating main.js with auth initialization...', 'blue');
const mainJsPath = path.join(this.projectRoot, 'src', 'main.js');
if (fs.existsSync(mainJsPath)) {
let mainContent = fs.readFileSync(mainJsPath, 'utf8');
// Add auth composable import
const authImport = "import { useAuth } from './composables/useAuth.js';\nimport authConfig from './config/auth.js';\n";
if (!mainContent.includes("import { useAuth }") && !mainContent.includes("from './composables/useAuth.js'")) {
// Add imports after existing imports
const lastImportMatch = mainContent.match(/import.*from.*['"]\s*$/gm);
if (lastImportMatch) {
const lastImport = lastImportMatch[lastImportMatch.length - 1];
mainContent = mainContent.replace(lastImport, lastImport + '\n' + authImport);
} else {
mainContent = authImport + '\n' + mainContent;
}
}
// Add auth initialization before app mount
const initCall = "\n// Initialize authentication\nconst { initializeAuth } = useAuth(authConfig);\ninitializeAuth();\n";
if (!mainContent.includes("initializeAuth()") && !mainContent.includes("// Initialize authentication")) {
mainContent = mainContent.replace(
/app\.mount\(['"]#app['"]\)/,
initCall + "\napp.mount('#app')"
);
}
// Only write if we made changes
const originalContent = fs.readFileSync(mainJsPath, 'utf8');
if (mainContent !== originalContent) {
fs.writeFileSync(mainJsPath, mainContent);
this.log('Updated main.js with auth initialization', 'green');
} else {
this.log('main.js already has auth initialization', 'yellow');
}
}
}
async updateMainStore() {
const storePath = path.join(this.projectRoot, 'src', 'store', 'index.js');
if (fs.existsSync(storePath)) {
let storeContent = fs.readFileSync(storePath, 'utf8');
// Add auth module import
const authImport = "import auth from './modules/auth.js';\n";
if (!storeContent.includes("import auth from './modules/auth.js'")) {
storeContent = authImport + storeContent;
}
// Add auth module to modules
if (!storeContent.includes('auth,') && storeContent.includes('modules:')) {
storeContent = storeContent.replace(
/modules:\s*{/,
'modules: {\n auth,'
);
}
fs.writeFileSync(storePath, storeContent);
}
}
async createAuthConfig() {
this.log('⚙️ Creating authentication configuration...', 'blue');
const configStub = path.join(this.stubsPath, 'config', 'auth.js');
const configDest = path.join(this.projectRoot, 'src', 'config', 'auth.js');
// Ensure config directory exists
const configDir = path.dirname(configDest);
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
await this.copyFile(configStub, configDest);
}
async copyDirectory(src, dest, visited = new Set()) {
if (!fs.existsSync(src)) return;
// Resolve real path to handle symbolic links
const realSrc = fs.realpathSync(src);
// Check for circular references
if (visited.has(realSrc)) {
this.log(`Skipping circular reference: ${src}`, 'yellow');
return;
}
visited.add(realSrc);
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const items = fs.readdirSync(src);
for (const item of items) {
// Skip problematic directories
if (item === 'node_modules' || item === '.git' || item.startsWith('.')) {
continue;
}
const srcPath = path.join(src, item);
const destPath = path.join(dest, item);
try {
const stats = fs.lstatSync(srcPath);
// Skip symbolic links to avoid infinite recursion
if (stats.isSymbolicLink()) {
this.log(`Skipping symbolic link: ${srcPath}`, 'yellow');
continue;
}
if (stats.isDirectory()) {
await this.copyDirectory(srcPath, destPath, visited);
} else {
await this.copyFile(srcPath, destPath);
}
} catch (error) {
this.log(`Error processing ${srcPath}: ${error.message}`, 'yellow');
continue;
}
}
visited.delete(realSrc);
}
async copyFile(src, dest) {
if (!fs.existsSync(src)) {
this.log(`Warning: Source file ${src} not found`, 'yellow');
return;
}
// Don't overwrite existing files, ask user or skip
if (fs.existsSync(dest)) {
this.log(`Skipping ${dest} (already exists)`, 'yellow');
return;
}
fs.copyFileSync(src, dest);
}
}
// Run installer
const installer = new Vue3PlateAuthInstaller();
installer.install().catch(console.error);