diff --git a/create-sails-generator/LICENSE b/create-sails-generator/LICENSE new file mode 100644 index 0000000..770d3b4 --- /dev/null +++ b/create-sails-generator/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 The Sailscasts Company + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/create-sails-generator/README b/create-sails-generator/README new file mode 100644 index 0000000..c823cec --- /dev/null +++ b/create-sails-generator/README @@ -0,0 +1,41 @@ +# create-sails-generator: A Sails Generator for The Boring JavaScript Stack + +This generator is specifically designed for The Boring JavaScript Stack, providing a plethora of commands for code generation and more to expedite your development process with The Boring JavaScript Stack. + +## Features + +- **Code Generation**: Quickly scaffold boilerplate code for pages, actions, and more. +- **Enhanced CLI Commands**: Extensive CLI commands tailored for Sails.js development. +- **Saves Time and Effort**: Automate repetitive tasks, saving you valuable development time. + +## Installation + +To use Create-Sails-Generator, you'll first need to have Node.js and npm installed on your machine. Then, follow these simple steps: + +1. Install Create-Sails-Generator globally via npm: + +```sh +npm i create-sails-generator --save-dev +``` + +2. Once installed, you can access the generator commands using the `sails generate` command. + +## Usage + +### Generating Files + +You can generate various files using the generator with simple commands. For instance, to create a new page, use: + +```sh +sails generate page dashboard +``` + +[Check out the docs](https://docs.sailscasts.com/boring-stack/generator) for more information on usage. + +## License + +`create-sails-generator` is licensed under the [MIT License](LICENSE.md). + +## About + +Create-Sails-Generator is maintained by The Sailscasts Company. It is part of [The Boring JavaScript Stack](https://docs.sailscasts.com/boring-stack) - The reliable full-stack JavaScript stack. diff --git a/create-sails-generator/generators/bad-request/index.js b/create-sails-generator/generators/bad-request/index.js new file mode 100644 index 0000000..b43c1b2 --- /dev/null +++ b/create-sails-generator/generators/bad-request/index.js @@ -0,0 +1,24 @@ +const path = require('path') +/** + * Generates responses/badRequest.js file + */ +module.exports = { + before: function (scope, done) { + if (scope.force !== false) { + scope.force = true + } + scope.relPath = 'badRequest.js' + return done() + }, + after: function (scope, done) { + console.log() + console.log(`Successfully generated ${scope.relPath}`) + console.log(' •-', `api/responses/${scope.relPath}`) + console.log() + return done() + }, + targets: { + './api/responses/:relPath': { copy: 'badRequest.js' } + }, + templatesDirectory: path.resolve(__dirname, './templates') +} diff --git a/create-sails-generator/generators/bad-request/templates/badRequest.js b/create-sails-generator/generators/bad-request/templates/badRequest.js new file mode 100644 index 0000000..142fcd6 --- /dev/null +++ b/create-sails-generator/generators/bad-request/templates/badRequest.js @@ -0,0 +1,95 @@ +/** + * badRequest.js + * + * A custom response. + * + * Example usage: + * ``` + * return res.badRequest(); + * // -or- + * return res.badRequest(optionalData); + * ``` + * + * Or with actions2: + * ``` + * exits: { + * somethingHappened: { + * responseType: 'badRequest' + * } + * } + * ``` + * + * ``` + * throw 'somethingHappened'; + * // -or- + * throw { somethingHappened: optionalData } + * ``` + */ + +module.exports = function badRequest(optionalData) { + // Get access to `req` and `res` + const req = this.req + const res = this.res + + // Define the status code to send in the response. + const statusCodeToSet = 400 + + // Check if it's an Inertia request + if (req.header('X-Inertia')) { + if (optionalData && optionalData.problems) { + const errors = {} + optionalData.problems.forEach((problem) => { + if (typeof problem === 'object') { + Object.keys(problem).forEach((propertyName) => { + const sanitizedProblem = problem[propertyName].replace(/\.$/, '') // Trim trailing dot + if (!errors[propertyName]) { + errors[propertyName] = [sanitizedProblem] + } else { + errors[propertyName].push(sanitizedProblem) + } + }) + } else { + const regex = /"(.*?)"/ + const matches = problem.match(regex) + + if (matches && matches.length > 1) { + const propertyName = matches[1] + const sanitizedProblem = problem + .replace(/"([^"]+)"/, '$1') + .replace('\n', '') + .replace('·', '') + .trim() + if (!errors[propertyName]) { + errors[propertyName] = [sanitizedProblem] + } else { + errors[propertyName].push(sanitizedProblem) + } + } + } + }) + req.session.errors = errors + return res.redirect(303, 'back') + } + } + + // If not an Inertia request, perform the normal badRequest response + if (optionalData === undefined) { + sails.log.info('Ran custom response: res.badRequest()') + return res.sendStatus(statusCodeToSet) + } else if (_.isError(optionalData)) { + sails.log.info( + 'Custom response `res.badRequest()` called with an Error:', + optionalData + ) + + if (!_.isFunction(optionalData.toJSON)) { + if (process.env.NODE_ENV === 'production') { + return res.sendStatus(statusCodeToSet) + } else { + return res.status(statusCodeToSet).send(optionalData.stack) + } + } + } else { + return res.status(statusCodeToSet).send(optionalData) + } +} diff --git a/create-sails-generator/generators/inertia-redirect/index.js b/create-sails-generator/generators/inertia-redirect/index.js new file mode 100644 index 0000000..14ed58e --- /dev/null +++ b/create-sails-generator/generators/inertia-redirect/index.js @@ -0,0 +1,24 @@ +const path = require('path') +/** + * Generates responses/inertia.js file + */ +module.exports = { + before: function (scope, done) { + if (scope.force !== false) { + scope.force = true + } + scope.relPath = 'inertiaRedirect.js' + return done() + }, + after: function (scope, done) { + console.log() + console.log(`Successfully generated ${scope.relPath}`) + console.log(' •-', `api/responses/${scope.relPath}`) + console.log() + return done() + }, + targets: { + './api/responses/:relPath': { copy: 'inertiaRedirect.js' } + }, + templatesDirectory: path.resolve(__dirname, './templates') +} diff --git a/create-sails-generator/generators/inertia-redirect/templates/inertiaRedirect.js b/create-sails-generator/generators/inertia-redirect/templates/inertiaRedirect.js new file mode 100644 index 0000000..dc44d1f --- /dev/null +++ b/create-sails-generator/generators/inertia-redirect/templates/inertiaRedirect.js @@ -0,0 +1,20 @@ +// @ts-nocheck + +const inertiaHeaders = { + INERTIA: 'X-Inertia', + LOCATION: 'X-Inertia-Location' +} + +module.exports = function inertiaRedirect(url) { + const req = this.req + const res = this.res + + if (req.get(inertiaHeaders.INERTIA)) { + res.set(inertiaHeaders.LOCATION, url) + } + + return res.redirect( + ['PUT', 'PATCH', 'DELETE'].includes(req.method) ? 303 : 409, + url + ) +} diff --git a/create-sails-generator/generators/inertia/index.js b/create-sails-generator/generators/inertia/index.js new file mode 100644 index 0000000..3646708 --- /dev/null +++ b/create-sails-generator/generators/inertia/index.js @@ -0,0 +1,24 @@ +const path = require('path') +/** + * Generates responses/inertia.js file + */ +module.exports = { + before: function (scope, done) { + if (scope.force !== false) { + scope.force = true + } + scope.relPath = 'inertia.js' + return done() + }, + after: function (scope, done) { + console.log() + console.log(`Successfully generated ${scope.relPath}`) + console.log(' •-', `api/responses/${scope.relPath}`) + console.log() + return done() + }, + targets: { + './api/responses/:relPath': { copy: 'inertia.js' } + }, + templatesDirectory: path.resolve(__dirname, './templates') +} diff --git a/create-sails-generator/generators/inertia/templates/inertia.js b/create-sails-generator/generators/inertia/templates/inertia.js new file mode 100644 index 0000000..f514298 --- /dev/null +++ b/create-sails-generator/generators/inertia/templates/inertia.js @@ -0,0 +1,72 @@ +// @ts-nocheck +const { encode } = require('querystring') +module.exports = function inertia(data) { + const req = this.req + const res = this.res + const sails = req._sails + + const sharedProps = sails.inertia.sharedProps + const sharedViewData = sails.inertia.sharedViewData + const rootView = sails.config.inertia.rootView + + const allProps = { + ...sharedProps, + ...data.props + } + + const allViewData = { + ...sharedViewData, + ...data.viewData + } + + let url = req.url || req.originalUrl + const assetVersion = sails.config.inertia.version + const currentVersion = + typeof assetVersion === 'function' ? assetVersion() : assetVersion + + const page = { + component: data.page, + version: currentVersion, + props: allProps, + url + } + + // Implements inertia partial reload. See https://inertiajs.com/partial-reload + if ( + req.get(inertiaHeaders.PARTIAL_DATA) && + req.get(inertiaHeaders.PARTIAL_COMPONENT) === page.component + ) { + const only = req.get(inertiaHeaders.PARTIAL_DATA).split(',') + page.props = only.length ? getPartialData(data.props, only) : page.props + } + + const queryParams = req.query + if (req.method === 'GET' && Object.keys(queryParams).length) { + // Keep original request query params + url += `?${encode(queryParams)}` + } + + if (req.get(inertiaHeaders.INERTIA)) { + res.set(inertiaHeaders.INERTIA, true) + res.set('Vary', 'Accept') + return res.json(page) + } else { + // Implements full page reload + return res.view(rootView, { + page, + viewData: allViewData + }) + } +} + +function getPartialData(props, only = []) { + return Object.assign({}, ...only.map((key) => ({ [key]: props[key] }))) +} + +const inertiaHeaders = { + INERTIA: 'X-Inertia', + VERSION: 'X-Inertia-Version', + PARTIAL_DATA: 'X-Inertia-Partial-Data', + PARTIAL_COMPONENT: 'X-Inertia-Partial-Component', + LOCATION: 'X-Inertia-Location' +} diff --git a/create-sails-generator/generators/page/index.js b/create-sails-generator/generators/page/index.js new file mode 100644 index 0000000..812d5c6 --- /dev/null +++ b/create-sails-generator/generators/page/index.js @@ -0,0 +1,70 @@ +const path = require('path') +const getUiFramework = require('../../utils/get-ui-framework') +const getComponentName = require('../../utils/get-component-name') +const getFileExtensionForUi = require('../../utils/get-file-extension-for-ui') +const getActionName = require('../../utils/get-action-name') +/** + * Generates responses/inertia.js file + */ + +module.exports = { + before: function (scope, done) { + const appPackageJSON = require(`${scope.topLvlRootPath}/package.json`) + const uiFramework = getUiFramework(appPackageJSON) + let roughName + + if (scope.name) { + roughName = scope.name + } + + if (scope.args[0] && typeof scope.args[0] == 'string') { + roughName = scope.args[0] + } + + if (!roughName) { + return done( + new Error( + 'Missing argument: Please provide a name for the new page.\n' + + '(e.g. `profile` or `sign-up`).' + ) + ) + } + + // Replace backslashes with proper slashes. + // (This is crucial for Windows compatibility.) + roughName = roughName.replace(/\\/g, '/') + + scope.pageRelPath = roughName.replace(/\.+/g, '/') + scope.pagePath = scope.pageRelPath + console.log(scope.pagePath) + scope.pageRelPath += getFileExtensionForUi(uiFramework) + scope.uiFramework = uiFramework + if (uiFramework == 'react') { + scope.componentName = getComponentName(roughName) + } + + scope.actionRelPath = getActionName(roughName) + scope.actionRelPath += '.js' + scope.actionFriendlyName = `View ${scope.pagePath}` + scope.actionDescription = `Display ${scope.pagePath} page` + + return done() + }, + after: function (scope, done) { + console.log() + + console.log(`Successfully generated ${scope.pageRelPath}`) + console.log(' •-', `assets/js/pages/${scope.pageRelPath}`) + + console.log(`Successfully generated ${scope.actionRelPath}`) + console.log(' •-', `api/controllers/${scope.actionRelPath}`) + + console.log() + return done() + }, + targets: { + './assets/js/pages/:pageRelPath': { template: 'page.template' }, + './api/controllers/:actionRelPath': { template: 'action.template' } + }, + templatesDirectory: path.resolve(__dirname, './templates') +} diff --git a/create-sails-generator/generators/page/templates/action.template b/create-sails-generator/generators/page/templates/action.template new file mode 100644 index 0000000..f725da7 --- /dev/null +++ b/create-sails-generator/generators/page/templates/action.template @@ -0,0 +1,15 @@ +module.exports = { + friendlyName: <%= util.inspect(actionFriendlyName) %>, + + description: <%= util.inspect(actionDescription) %>, + + exits: { + success: { + responseType: 'inertia' + } + }, + + fn: async function () { + return { page: <%= util.inspect(pagePath) %> } + } +} diff --git a/create-sails-generator/generators/page/templates/page.template b/create-sails-generator/generators/page/templates/page.template new file mode 100644 index 0000000..e680276 --- /dev/null +++ b/create-sails-generator/generators/page/templates/page.template @@ -0,0 +1,19 @@ +<% if (uiFramework == 'vue') {%> + + + +<% } else if (uiFramework == 'react') { %> +import { Head, Link, useForm, usePage } from '@inertiajs/react' + +export default function <%= componentName %>(props) { + return
+} +<% } else if (uiFramework == 'svelte') { %> + + + +<% } %> diff --git a/create-sails-generator/index.js b/create-sails-generator/index.js new file mode 100644 index 0000000..e69de29 diff --git a/create-sails-generator/package.json b/create-sails-generator/package.json new file mode 100644 index 0000000..8c164c5 --- /dev/null +++ b/create-sails-generator/package.json @@ -0,0 +1,22 @@ +{ + "name": "create-sails-generator", + "version": "0.0.1", + "description": "Sails generator for The Boring JavaScript Stack.", + "scripts": { + "test": "node --test" + }, + "keywords": [ + "The Boring JavaScript Stack", + "sails", + "senerator" + ], + "author": "Kelvin Omereshone