UNPKG

@saiforceone/dirt-cli

Version:

Official CLI Utility for the D.I.R.T stack

330 lines 14 kB
import os, { platform } from 'node:os'; import { chmod, mkdir, rename, unlink } from 'node:fs/promises'; import path from 'node:path'; import { $, execaCommand } from 'execa'; import { BASE_PY_FILENAME, DEV_PY_FILENAME, DIRT_SETTINGS_FOLDER, GIT_IGNORE_FILENAME, GIT_IGNORE_TEMPLATE_FILE, MANAGE_PY_FILENAME, MANAGE_PY_MODE, PIPENV_COMMAND, SETTINGS_PY_FILE, STATIC_FILES_FOLDER_NAME, STATIC_FOLDER_NAME, STDIO_OPTS, } from '../../constants/djangoConstants.js'; import { standardOutputBuilder } from '../../utils/standardOutputBuilder.js'; import { copyAssets, copyDjangoHTMLTemplates, copyDjangoSettings, copyInertiaDefaults, createDjangoProject, getVirtualEnvLocation, installDependencies, writeBaseSettings, writeDatabaseSettings, writeDevSettings, writeInertiaViewsFile, } from '../../utils/djangoUtils.js'; import ConsoleLogger from '../../utils/ConsoleLogger.js'; import { MESSAGE_COPYING_DIRT_FILES, MESSAGE_COPYING_HTML_TEMPLATES, MESSAGE_DIRT_FILES_COPIED, MESSAGE_DIRT_TEMPLATES_COPIED, MESSAGE_SECRET_KEY_SET, MESSAGE_SETTING_SECRET_KEY, } from '../../constants/strings.js'; import { generateSecretKey } from '../../utils/generateSecretKey.js'; import { normalizeWinFilePath } from '../../utils/fileUtils.js'; import { LOCAL_ASSET_BUILDERS_PATH } from '../../constants/index.js'; import { checkDestinationExistence } from '../shared/coreHelpers.js'; async function executeCommand(commandString) { const output = standardOutputBuilder(); try { if (platform() === 'win32') { await $(STDIO_OPTS) `${commandString}`; } else { // exec for anything non-windows await execaCommand(commandString).stdout?.pipe(process.stdout); } output.success = true; return output; } catch (e) { output.result = 'Failed to execute command'; output.error = e.message; return output; } } /** * @async * @description executes windows-specific commands to scaffold the Django application * @param {ScaffoldOptions} options * @param {string} destination */ export async function scaffoldDjangoProcess(options, destination) { const { projectName, verboseLogs: useVerboseLogs } = options; const output = standardOutputBuilder(); // 1. init pipenv's shell if (os.platform() === 'win32') { try { await $(STDIO_OPTS) `${PIPENV_COMMAND}`; } catch (e) { output.result = 'Failed to start virtual environment. Will exit now'; output.error = e.toString(); return output; } } else { try { await execaCommand(PIPENV_COMMAND).stdout?.pipe(process.stdout); } catch (e) { if (useVerboseLogs) ConsoleLogger.printMessage(e.message, 'error'); output.result = `Failed to exec pipenv command: ${e.message}`; return output; } } // 2. install dependencies const installDepsResult = await installDependencies(); if (!installDepsResult.success) { if (useVerboseLogs) ConsoleLogger.printOutput(installDepsResult); return installDepsResult; } // 3. get venv location const pipenvLocResult = await getVirtualEnvLocation(); if (useVerboseLogs) ConsoleLogger.printOutput(pipenvLocResult); if (!pipenvLocResult.success) { return pipenvLocResult; } // 4. build path to python executable const pipenvLoc = String(pipenvLocResult.result).trim(); const pythonExecutable = os.platform() === 'win32' ? path.join(pipenvLoc, 'Scripts', 'python.exe') : path.join(pipenvLoc, 'bin', 'python3'); if (useVerboseLogs) ConsoleLogger.printMessage(`Using python executable: ${pythonExecutable}`); // 5. create django project try { const createDjangoProjResult = await createDjangoProject(projectName, pythonExecutable); if (useVerboseLogs) ConsoleLogger.printOutput(createDjangoProjResult); if (!createDjangoProjResult.success) return createDjangoProjResult; } catch (e) { output.error = e.message; return output; } // 6. copy django if (useVerboseLogs) ConsoleLogger.printMessage(MESSAGE_COPYING_DIRT_FILES); const copyDjangoFilesResult = await copyDjangoSettings(destination); if (useVerboseLogs) ConsoleLogger.printOutput(copyDjangoFilesResult); if (!copyDjangoFilesResult.success) return copyDjangoFilesResult; if (useVerboseLogs) ConsoleLogger.printMessage(MESSAGE_DIRT_FILES_COPIED); // Copy Django Base Templates if (useVerboseLogs) ConsoleLogger.printMessage(MESSAGE_COPYING_HTML_TEMPLATES); const copyDjangoTemplateFilesResult = await copyDjangoHTMLTemplates({ destinationBase: destination, frontend: options.frontend, }); if (useVerboseLogs) ConsoleLogger.printOutput(copyDjangoTemplateFilesResult); if (!copyDjangoTemplateFilesResult.success) return copyDjangoTemplateFilesResult; if (useVerboseLogs) ConsoleLogger.printMessage(MESSAGE_DIRT_TEMPLATES_COPIED); // 7. Secret key if (useVerboseLogs) ConsoleLogger.printMessage(MESSAGE_SETTING_SECRET_KEY); // 7.1 generate key const secretKey = generateSecretKey(); // 7.2 Build path const devSettingsPath = path.join(destination, DIRT_SETTINGS_FOLDER, DEV_PY_FILENAME); // 7.3 write secret key const secretKeyResult = await writeDevSettings(secretKey, projectName, devSettingsPath); if (useVerboseLogs) ConsoleLogger.printOutput(secretKeyResult); if (!secretKeyResult.success) return secretKeyResult; if (useVerboseLogs) ConsoleLogger.printMessage(MESSAGE_SECRET_KEY_SET); // Database if (options['databaseOption'] !== 'None') { if (useVerboseLogs) ConsoleLogger.printMessage('Applying database settings...'); const databaseResult = await writeDatabaseSettings(options.projectName, devSettingsPath, options['databaseOption']); if (useVerboseLogs) ConsoleLogger.printOutput(databaseResult); if (!databaseResult.success) return databaseResult; } // update base settings file const baseSettingsPath = path.join(destination, DIRT_SETTINGS_FOLDER, BASE_PY_FILENAME); if (useVerboseLogs) ConsoleLogger.printMessage('Updating Django application base settings...'); const baseSettingsResult = await writeBaseSettings(projectName, baseSettingsPath); if (!baseSettingsResult.success) return baseSettingsResult; if (useVerboseLogs) ConsoleLogger.printMessage('Successfully updated Django application base settings', 'success'); // 7.4 delete generated settings file if (useVerboseLogs) ConsoleLogger.printMessage("Removing default settings.py file (we won't need it anymore, trust me...)"); const originalSettingsFilePath = path.join(destination, projectName, SETTINGS_PY_FILE); try { await unlink(originalSettingsFilePath); if (useVerboseLogs) ConsoleLogger.printMessage('Removed default settings file', 'success'); } catch (e) { if (useVerboseLogs) ConsoleLogger.printMessage(e.message, 'error'); output.error = e.message; return output; } // copy template tags try { if (useVerboseLogs) ConsoleLogger.printMessage('Copying template tags'); } catch (e) { if (useVerboseLogs) ConsoleLogger.printMessage(e.message, 'error'); output.error = e.message; return output; } // rename git ignore file const originalIgnorePath = path.join(destination, GIT_IGNORE_TEMPLATE_FILE); const newIgnorePath = path.join(destination, GIT_IGNORE_FILENAME); if (useVerboseLogs) ConsoleLogger.printMessage('Renaming ignore file...'); try { await rename(originalIgnorePath, newIgnorePath); } catch (e) { output.error = e.message; return output; } if (useVerboseLogs) ConsoleLogger.printMessage('Ignore file was renamed. You may update this .gitignore file as you see fit', 'success'); // overwrite urls.py and views.py in base project const projectPath = path.join(destination, projectName); if (useVerboseLogs) ConsoleLogger.printMessage('Copying default D.I.R.T Stack Inertia files...'); const cpInertiaResult = await copyInertiaDefaults(projectPath); if (!cpInertiaResult.success) return cpInertiaResult; if (useVerboseLogs) ConsoleLogger.printMessage('Successfully copied files', 'success'); if (useVerboseLogs) ConsoleLogger.printMessage('Making project runnable by updating manage.py permissions...'); // change permissions of manage.py so that we can run it // check if on windows on *Nix const managePyPath = path.join(destination, MANAGE_PY_FILENAME); try { await chmod(managePyPath, MANAGE_PY_MODE); } catch (e) { if (useVerboseLogs) ConsoleLogger.printMessage(`Error changing manage.py permissions: ${e.message}`, 'error'); output.error = e.message; return output; } if (useVerboseLogs) ConsoleLogger.printMessage('Permissions updated. Project now runnable', 'success'); // create additional folders if (useVerboseLogs) ConsoleLogger.printMessage('Creating static folder....'); let staticFolderPath = path.join(destination, STATIC_FOLDER_NAME); let staticFilesPath = path.join(destination, STATIC_FILES_FOLDER_NAME); if (platform() === 'win32') { staticFolderPath = normalizeWinFilePath(staticFolderPath); staticFilesPath = normalizeWinFilePath(staticFilesPath); } try { await mkdir(staticFolderPath); await mkdir(staticFilesPath); } catch (e) { if (useVerboseLogs) ConsoleLogger.printMessage(`Failed to make static folder with error: ${e.message}`, 'error'); output.error = e.message; return output; } if (useVerboseLogs) ConsoleLogger.printMessage('Static folder created', 'success'); // copy build-script if (useVerboseLogs) ConsoleLogger.printMessage('Copying local asset builder scripts...'); const copyAssetBuilderResult = await copyAssets(LOCAL_ASSET_BUILDERS_PATH, destination); if (!copyAssetBuilderResult.success) { if (useVerboseLogs) ConsoleLogger.printOutput(copyAssetBuilderResult); return copyAssetBuilderResult; } if (useVerboseLogs) ConsoleLogger.printOutput(copyAssetBuilderResult); // finally, return output output.success = true; return output; } /** * @description Executes the process to create a django application copying the * necessary files to target locations * @param destinationBase * @param appName * @param frontendOption * @param logType */ export async function createDjangoApp(destinationBase, appName, frontendOption, logType) { const output = standardOutputBuilder(); try { // check destination existence let targetPath = path.join(destinationBase, appName); if (logType === 'noisyLogs') { ConsoleLogger.printMessage(`attempting to scaffold app at target path: ${targetPath}`); } if (platform() === 'win32') targetPath = normalizeWinFilePath(targetPath); if (checkDestinationExistence(targetPath).success) { output.error = 'Controller already exists. Exiting...'; output.result = `Controller with name: [${appName}] already exists. Exiting...';`; return output; } // get the path to the python executable const envLocationResult = await getVirtualEnvLocation(); if (!envLocationResult.success) { output.error = envLocationResult.error; return output; } const envPath = envLocationResult.result.trim(); const pythonExecPath = platform() === 'win32' ? normalizeWinFilePath(path.join(envPath, 'Scripts', 'python.exe')) : path.join(envPath, 'bin', 'python3'); if (logType === 'noisyLogs') ConsoleLogger.printMessage(`python path: ${pythonExecPath}`); // execute command to create the django application if (os.platform() === 'win32') { try { await $(STDIO_OPTS) `python manage.py startapp ${appName}`; } catch (e) { output.error = e.message; return output; } } else { try { // execute the command const commandString = `${pythonExecPath} manage.py startapp ${appName}`; if (logType === 'noisyLogs') { ConsoleLogger.printMessage(`using command string: ${commandString}`); ConsoleLogger.printMessage(`running from: ${process.cwd()}`); } await execaCommand(commandString); // exec write files const writeFilesResult = await writeInertiaViewsFile(destinationBase, appName, frontendOption, logType); if (logType === 'noisyLogs') ConsoleLogger.printOutput(writeFilesResult); } catch (e) { if (logType === 'noisyLogs') ConsoleLogger.printMessage(`failed to execute command with error: ${e.toString()}`); output.error = e.message; return output; } } return output; } catch (e) { if (logType === 'noisyLogs') ConsoleLogger.printMessage(e.toString(), 'error'); output.error = e.message; return output; } } //# sourceMappingURL=commonHelpers.js.map