Skip to content

Instantly share code, notes, and snippets.

@fanboykun
Last active October 21, 2024 16:46
Show Gist options
  • Save fanboykun/27c112b9a265b60eb37cace1d05691a2 to your computer and use it in GitHub Desktop.
Save fanboykun/27c112b9a265b60eb37cace1d05691a2 to your computer and use it in GitHub Desktop.
Sveltekit Artisan
/**
* CLI Tool for generating various types of files and folders for a SvelteKit project.
*
* This script automates the creation of common SvelteKit components such as layouts, pages, groups, components,
* and API routes, with support for generating additional files with flags.
*
* # Usage
*
* Run the script using `node`:
*
* ```bash
* node artisan make:<command> <name> [--flag]
* ```
*
* ## Available Commands
* - `layout` : Generates a new layout folder and corresponding files (`+layout.svelte`, `+layout.server.ts`).
* - `page` : Generates a new page folder and corresponding files (`+page.svelte`, `+page.server.ts`).
* - `component` : Generates a new component file (`<name>.svelte`).
* - `group` : Generates a new group folder and files (same as pages, but in a grouped folder).
* - `api` : Generates a new API route (`<name>/+server.ts`).
*
* ## Flags
* - `--all` : When used with `layout`, `group`, or `page`, generates both `+layout.svelte` and `+layout.server.ts` or `+page.svelte` and `+page.server.ts`.
* - `--complete`: When used with `layout`, `group`, or `page`, generates `+layout.svelte`, `+layout.server.ts`, and `+layout.ts` or `+page.svelte`, `+page.server.ts`, and `+page.ts`.
* - `--resource`: When used with `api`, generates a full resource-based API route (`+server.ts`, `+server.get.ts`, `+server.post.ts`).
*
* ## Examples
*
* ### 1. Create a new layout folder with default files
* ```bash
* node artisan make:layout LayoutName
* ```
*
* ### 2. Create a new page folder with the `--all` flag
* ```bash
* node artisan make:page PageName --all
* ```
*
* ### 3. Create a new component
* ```bash
* node artisan make:component MyComponent
* ```
*
* ### 4. Create a new API route with the `--resource` flag
* ```bash
* node artisan make:api MyResource --resource
* ```
*
* ## Notes
* - If you do not pass a valid command, the script will return an error message.
* - The created file or folder will be placed in the relevant directory, based on the command (e.g., `src/routes` for pages, `src/lib/components` for components).
*/
"use strict";
import * as fs from "fs";
/**
* @constant {Object} SETTINGS - Configuration settings for paths, commands, file names, and flags.
* @property {string} component_path - Path for creating components.
* @property {string} layout_path - Path for creating layouts.
* @property {string} page_path - Path for creating pages.
* @property {string} group_path - Path for creating groups.
* @property {string} api_path - Path for creating API routes.
* @property {string[]} avaibale_commands - List of available commands (layout, page, component, group, api).
* @property {string} base_layout_file_name - Base file name for layout files.
* @property {string} base_layout_file_server_name - Base file name for layout server-side files.
* @property {string} base_page_file_name - Base file name for page files.
* @property {string} base_page_file_server_name - Base file name for page server-side files.
* @property {string} base_api_file_name - Base file name for API route files.
* @property {string} base_extension - Default extension for TypeScript files.
* @property {string} component_extenstion - Default extension for Svelte component files.
* @property {string} page_extenstion - Default extension for Svelte page files.
* @property {string[]} available_flags - List of valid flags (--all, --complete, --resource).
* @property {string[]} applied_flag - Currently applied flags during command execution.
*/
const SETTINGS = {
component_path: 'src/lib/components',
layout_path: 'src/routes',
page_path: 'src/routes',
group_path: 'src/routes',
api_path: 'src/routes/api',
avaibale_commands: ['layout', 'page', 'component', 'group', 'api'],
base_layout_file_name: '+layout',
base_layout_file_server_name: '+layout.server',
base_page_file_name: '+page',
base_page_file_server_name: '+page.server',
base_api_file_name: '+server',
base_extension: '.ts',
component_extenstion: '.svelte',
page_extenstion: '.svelte',
available_flags: ['--all', '--complete', '--resource'],
applied_flag: []
}
/**
* Main function, the entry point of the script.
* Handles parsing command-line arguments and executing the corresponding action.
* @returns {boolean} - Returns true if the process is successful, false otherwise.
*/
function main() {
try {
const command = checkAndGetCommandFromArgs();
if (!command) {
showError();
return false;
}
const isFlagValid = ensureFlagIsValid(command);
if (!isFlagValid) {
showError();
return false;
}
ensureFolderExist(command);
const name = getFileOrFolderName();
let isSuccess = false;
switch (command) {
case "layout":
isSuccess = handleGenerateLayout(name);
break;
case "component":
isSuccess = handleGenerateComponent(name);
break;
case "page":
isSuccess = handleGeneratePage(name);
break;
case "group":
isSuccess = handleGenerateGroup(name);
break;
case "api":
isSuccess = handleGenerateApi(name);
break;
default:
console.log('Command does not exist.');
}
return isSuccess;
} catch (err) {
console.error(err);
return false;
}
}
/**
* Ensures that the flags provided are valid for the specified command.
* @param {string} command - The command being executed.
* @returns {boolean} - Returns true if the flag is valid, false otherwise.
*/
function ensureFlagIsValid(command) {
const args = process.argv.slice(4);
if (args.length === 0) return true;
if (args.length > 1) {
console.log('Only one flag can be applied at a time.');
return false;
}
let isValid = false;
for (const flag of SETTINGS.available_flags) {
if (flag === args[0]) {
isValid = applyFlag(flag, command);
}
}
return isValid;
}
/**
* Applies a valid flag for the corresponding command.
* @param {string} flag - The flag being applied.
* @param {string} command - The command being executed.
* @returns {boolean} - Returns true if the flag was successfully applied, false otherwise.
*/
function applyFlag(flag, command) {
const command_schema = [
{
command: ['page', 'group', 'layout'],
flag: ['--all', '--complete']
},
{
command: ['api'],
flag: ['--resource']
}
];
let isValid = false;
for (const schema of command_schema) {
if (schema.command.includes(command)) {
if (schema.flag.includes(flag)) {
SETTINGS.applied_flag.push(flag);
isValid = true;
} else {
console.log('Flag does not apply to the current command.');
}
}
}
return isValid;
}
/**
* Ensures that the required base folder exists for the given command.
* Creates the folder if it does not exist.
* @param {string} command - The command being executed.
*/
function ensureFolderExist(command) {
const paths = {
component: SETTINGS.component_path,
layout: SETTINGS.layout_path,
page: SETTINGS.page_path,
group: SETTINGS.group_path,
api: SETTINGS.api_path
};
const targetPath = paths[command];
if (targetPath && !fs.existsSync(targetPath)) {
fs.mkdirSync(targetPath);
}
}
/**
* Retrieves the command from the process arguments and checks if it is valid.
* @returns {string|undefined} - Returns the command if valid, undefined otherwise.
*/
function checkAndGetCommandFromArgs() {
if (process.argv.length < 4) return undefined;
const arg = process.argv[2];
const checkArg = arg.split(':');
return SETTINGS.avaibale_commands.includes(checkArg[1]) ? checkArg[1] : undefined;
}
/**
* Retrieves the file or folder name from the process arguments.
* @returns {string} - Returns the name for the file or folder.
*/
function getFileOrFolderName() {
return process.argv[3];
}
/**
* Generates layout files (`+layout.svelte` and `+layout.server.ts`).
* Flags:
* - `--all`: Adds `+page.svelte` and `+page.server.ts`.
* - `--complete`: Adds `+page.svelte`, `+page.server.ts`, and `+page.ts`.
* @param {string} name - The layout name.
* @returns {boolean} - Returns true if the files were successfully created, false otherwise.
* @example node artisan make:layout LayoutName
*/
function handleGenerateLayout(name) {
try {
const { fullPath } = ensureFolderAndFilePath(name, SETTINGS.layout_path);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath);
}
if (SETTINGS.applied_flag.length === 0) {
const page_1 = `${fullPath}/${SETTINGS.base_layout_file_name}${SETTINGS.component_extenstion}`;
const page_2 = `${fullPath}/${SETTINGS.base_layout_file_server_name}${SETTINGS.base_extension}`;
return createFiles(page_1, page_2);
} else {
return runAllAndCompleteFlag(fullPath);
}
} catch (err) {
console.error(err);
return false;
}
}
/**
* Generates layout files (`+page.svelte` and `+page.server.ts`).
* Flags:
* - `--all`: Adds `+layout.svelte` and `+layout.server.ts`.
* - `--complete`: Adds `+layout.svelte`, `+layout.server.ts`, and `+page.ts`.
* @param {string} name - The Page name.
* @returns {boolean} - Returns true if the files were successfully created, false otherwise.
* @example node artisan make:page PageName
*/
function handleGeneratePage(name) {
try {
const { fullPath } = ensureFolderAndFilePath(name, SETTINGS.page_path)
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath);
}
if(SETTINGS.applied_flag.length == 0) {
const page_1 = `${fullPath}/${SETTINGS.base_page_file_name}${SETTINGS.component_extenstion}`
const page_2 = `${fullPath}/${SETTINGS.base_page_file_server_name}${SETTINGS.base_extension}`
return createFiles(page_1, page_2)
} else {
return runAllAndCompleteFlag(fullPath)
}
} catch (err) {
console.error(err);
return false
}
}
/**
* Generates group folder
* Flags:
* - `--all`: Adds `+page.svelte`, `+page.server.ts`, `+layout.svelte` and `+layout.server.ts`.
* - `--complete`: Adds `+page.svelte`, `+page.server.ts`, `+layout.svelte` and `+layout.server.ts` and `+page.ts`.
* @param {string} name - The layout name.
* @returns {boolean} - Returns true if the files were successfully created, false otherwise.
* @example node artisan make:group GroupName
*/
function handleGenerateGroup(name) {
try {
let { fullPath } = ensureFolderAndFilePath(name, SETTINGS.group_path)
const lastPathBeforeName = fullPath.split('/')
const pop = lastPathBeforeName.pop()
fullPath = `${lastPathBeforeName.join('/')}/(${pop})`
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath);
}
if(SETTINGS.applied_flag.length == 0) { return true }
else { return runAllAndCompleteFlag(fullPath) }
} catch (err) {
console.error(err);
return false
}
}
/**
* Generates Route API endpoint / (folder & file server.ts)
* Flags:
* - `--all`: Adds all functions boilerplate handler in the file.
* @param {string} name - The API endpoint name.
* @returns {boolean} - Returns true if the files were successfully created, false otherwise.
* @example node artisan make:api EndpointName
*/
function handleGenerateApi(name) {
try {
const { fullPath } = ensureFolderAndFilePath(name, SETTINGS.api_path)
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath);
}
if(SETTINGS.applied_flag.length == 0) {
const api = `${fullPath}/${SETTINGS.base_api_file_name}${SETTINGS.base_extension}`
return createFiles(api)
}
} catch (err) {
console.error(err);
return false
}
}
/**
* Generates Component file
* @param {string} name - The Component name.
* @returns {boolean} - Returns true if the files were successfully created, false otherwise.
* @example node artisan make:component ComponentName
*/
function handleGenerateComponent(name) {
try {
if(name.includes('.')){
console.log('component name creation must not include extension')
return false
}
const { file_name, fullPath } = ensureFolderAndFilePath(name, SETTINGS.component_path, false )
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath);
}
const component = `${fullPath}/${file_name}${SETTINGS.component_extenstion}`
return createFiles(component)
} catch (err) {
console.error(err);
return false
}
}
/**
* Creates one or more files at the specified paths.
* @param {...string} files - The file paths to be created.
* @returns {boolean} - Returns true if the files were successfully created, false otherwise.
*/
function createFiles(...files) {
try {
for (const file of files) {
if (!fs.existsSync(file)) {
fs.appendFileSync(file, '');
console.log(`Success creating ${file}`);
} else {
console.log(`File ${file} already exists`);
}
}
return true;
} catch (err) {
console.error(err);
return false;
}
}
/**
* Generates the folder and file path, ensuring any necessary parent directories are created.
* @param {string} name - The file or folder name.
* @param {string} base_path - The base path for the files or folders.
* @param {boolean} [include_file_name_in_path=true] - Whether to include the file name in the final path.
* @returns {Object} - Returns an object with the `file_name` and `fullPath`.
*/
function ensureFolderAndFilePath(name, base_path, include_file_name_in_path = true) {
let file_name = name;
let fullPath = `${base_path}/${name}`;
const regex = /\//;
if (regex.test(name)) {
const sanitized = name.split('/').filter(Boolean);
if (sanitized.length === 1) {
file_name = sanitized[0];
fullPath = include_file_name_in_path ? `${base_path}/${file_name}` : `${base_path}`;
} else {
const pathToCreate = `${base_path}/${sanitized.join('/')}`;
// fs.mkdirSync(pathToCreate, { recursive: true });
file_name = sanitized[sanitized.length - 1];
fullPath = include_file_name_in_path ? pathToCreate : `${base_path}/${sanitized.slice(0, sanitized.length - 1).join('/')}`
}
} else {
if(!include_file_name_in_path) {
file_name = name
fullPath = base_path
}
}
return { file_name, fullPath };
}
/**
* Generate files based on applied flag
* @param {string} fullPath
* @returns boolean
*/
function runAllAndCompleteFlag(fullPath) {
let hasFileCreated = false
const listFiles = {
layout: `${fullPath}/${SETTINGS.base_layout_file_name}${SETTINGS.component_extenstion}`,
layout_server: `${fullPath}/${SETTINGS.base_layout_file_server_name}${SETTINGS.base_extension}`,
page: `${fullPath}/${SETTINGS.base_page_file_name}${SETTINGS.component_extenstion}`,
page_server: `${fullPath}/${SETTINGS.base_page_file_server_name}${SETTINGS.base_extension}`,
page_ts: `${fullPath}/${SETTINGS.base_page_file_name}${SETTINGS.base_extension}`,
}
if( SETTINGS.applied_flag == '--all' ) {
hasFileCreated = createFiles(listFiles.layout, listFiles.layout_server, listFiles.page, listFiles.page_server)
} else if ( SETTINGS.applied_flag == '--complete' ) {
hasFileCreated = createFiles(listFiles.layout, listFiles.layout_server, listFiles.page, listFiles.page_server, listFiles.page_ts)
}
return hasFileCreated
}
/**
* Prints an error message to the console.
*/
function showError() {
console.log('Please use a valid command');
}
main();
@fanboykun
Copy link
Author

fanboykun commented Oct 21, 2024

Sveltekit Artisan

CLI Tool for generating various types of files and folders for a SvelteKit project, Just like in Laravel Artisan

This script automates the creation of common SvelteKit components such as layouts, pages, groups, components,
and API routes, with support for generating additional files with flags.

Instalation

  • Create a artisan.js file in your project's root directory
  • Copy the code and paste it to the artisan.js file

Usage

Run the script using node:

node artisan make:<command> <name> [--flag]

Available Commands

  • layout : Generates a new layout folder and corresponding files (+layout.svelte, +layout.server.ts).
  • page : Generates a new page folder and corresponding files (+page.svelte, +page.server.ts).
  • component : Generates a new component file (<name>.svelte).
  • group : Generates a new group folder and files (same as pages, but in a grouped folder).
  • api : Generates a new API route (<name>/+server.ts).

Flags

  • --all : When used with layout, group, or page, generates both +layout.svelte and +layout.server.ts or +page.svelte and +page.server.ts.
  • --complete: When used with layout, group, or page, generates +layout.svelte, +layout.server.ts, and +layout.ts or +page.svelte, +page.server.ts, and +page.ts.
  • --resource: When used with api, generates a full resource-based API route (+server.ts, +server.get.ts, +server.post.ts).

Examples

1. Create a new layout folder with default files

node artisan make:layout LayoutName

2. Create a new page folder with the --all flag

node artisan make:page PageName --all

3. Create a new component

node artisan make:component MyComponent

4. Create a new API route with the --resource flag

node artisan make:api MyResource --resource

Notes

  • If you do not pass a valid command, the script will return an error message.
  • The created file or folder will be placed in the relevant directory, based on the command (e.g., src/routes for pages, src/lib/components for components).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment