├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── README.md ├── app ├── head.tsx ├── layout.tsx └── page.tsx ├── common ├── http.ts ├── server.ts └── utilities.ts ├── components ├── DefaultLayout.module.scss ├── DefaultLayout.tsx └── DefaultMetaTags.tsx ├── data ├── db.ts └── query.ts ├── global.scss ├── modules ├── cors.ts ├── object-assign.ts └── vary.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages └── api │ └── index.ts ├── public ├── favicon-16x16.png ├── favicon-32x32.png └── favicon.ico ├── scripts ├── example.js └── index.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .next 2 | .nova 3 | .env 4 | .env.local 5 | .env-custom-development 6 | .env-development 7 | .env-textile 8 | .env-production 9 | .DS_STORE 10 | DS_STORE 11 | yarn.lock 12 | node_modules 13 | dist 14 | analytics.txt 15 | package-lock.json 16 | 17 | /**/*/.DS_STORE 18 | /**/*/node_modules 19 | /**/*/.next 20 | /**/*/.data -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth":180, 3 | "tabWidth":2, 4 | "useTabs":false, 5 | "semi":true, 6 | "singleQuote":true, 7 | "trailingComma":"es5", 8 | "bracketSpacing":true, 9 | "jsxBracketSameLine":false, 10 | "arrowParens":"always", 11 | "requirePragma":false, 12 | "insertPragma":false, 13 | "proseWrap":"preserve", 14 | "parser":"babel", 15 | "overrides": [ 16 | { 17 | "files": "*.js", 18 | "options": { 19 | "parser": "babel" 20 | } 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DEPRECATION NOTICE 2 | 3 | This template is no longer up to date. For an updated template, either as a team or individually, we encourage you to explore our [latest template](https://github.com/internet-development/nextjs-sass-starter) produced by [INTDEV](https://internet.dev). Thank you for your interest in our work! 4 | 5 | # NEXT-SASS 6 | 7 | Why would I use this? 8 | 9 | - Quickly start a project with TypeScript, SASS, and NextJS. 10 | - Convenient global CSS reset file. 11 | - You got tired of using CSS-in-JS. 12 | - You want to make a website quickly. 13 | 14 | ### Setup (MacOS) 15 | 16 | Start by cloning the repository, or by clicking on **Use this template** above. 17 | 18 | Then run the server 19 | 20 | ```sh 21 | npm install 22 | npm run dev 23 | ``` 24 | 25 | Go to `http://localhost:3005` in your browser of choice. Enjoy! 26 | 27 | ### Scripts 28 | 29 | If you need to run node script without running the server, use this example to get started 30 | 31 | ```sh 32 | npm run script example 33 | ``` 34 | 35 | ### Env Variables 36 | 37 | If you want to connect to a Postgres database, something I do all the time, provide the following `.env` file. `5432` is the default Postgres port. 38 | 39 | ```sh 40 | DOCUMENT_DATABASE_NAME=xxxx 41 | DOCUMENT_DATABASE_USERNAME=xxxx 42 | DOCUMENT_DATABASE_HOST=xxxx 43 | DOCUMENT_DATABASE_PORT=5432 44 | DOCUMENT_DATABASE_PASSWORD=xxxx 45 | ``` 46 | 47 | ### Contact 48 | 49 | If you have questions ping me on Twitter, [@wwwjim](https://www.twitter.com/wwwjim). 50 | -------------------------------------------------------------------------------- /app/head.tsx: -------------------------------------------------------------------------------- 1 | import DefaultMetaTags from '@components/DefaultMetaTags'; 2 | 3 | export default async function Head({ params }) { 4 | const title = 'example'; 5 | const description = 'CHANGEME: description for your application using next-sass'; 6 | const url = 'CHANGEME: your-production-url.tld'; 7 | 8 | // SUMMARY_LARGE_IMAGE: 1500x785 9 | return ( 10 | <> 11 | {title} 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function RootLayout({ children }: { children: React.ReactNode }) { 2 | return ( 3 | 4 | {children} 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import '@root/global.scss'; 2 | 3 | import DefaultLayout from '@components/DefaultLayout'; 4 | 5 | export default async function Page(props) { 6 | return Hello World; 7 | } 8 | -------------------------------------------------------------------------------- /common/http.ts: -------------------------------------------------------------------------------- 1 | const REQUEST_HEADERS = { 2 | Accept: 'application/json', 3 | 'Content-Type': 'application/json', 4 | }; 5 | 6 | const getHeaders = (key) => { 7 | return { ...REQUEST_HEADERS, Authorization: `Bearer ${key}` }; 8 | }; 9 | 10 | export async function placeholder(key) { 11 | const response = await fetch('/api', { 12 | method: 'GET', 13 | headers: getHeaders(key), 14 | }); 15 | 16 | const json = await response.json(); 17 | return json; 18 | } 19 | -------------------------------------------------------------------------------- /common/server.ts: -------------------------------------------------------------------------------- 1 | import Cors from '@modules/cors'; 2 | 3 | export function initMiddleware(middleware) { 4 | return (req, res) => 5 | new Promise((resolve, reject) => { 6 | middleware(req, res, (result) => { 7 | if (result instanceof Error) { 8 | return reject(result); 9 | } 10 | return resolve(result); 11 | }); 12 | }); 13 | } 14 | 15 | export const cors = initMiddleware( 16 | Cors({ 17 | methods: ['GET', 'POST', 'OPTIONS'], 18 | }) 19 | ); 20 | -------------------------------------------------------------------------------- /common/utilities.ts: -------------------------------------------------------------------------------- 1 | const hasOwn = {}.hasOwnProperty; 2 | const protocolAndDomainRE = /^(?:\w+:)?\/\/(\S+)$/; 3 | const localhostDomainRE = /^localhost[\:?\d]*(?:[^\:?\d]\S*)?$/; 4 | const nonLocalhostDomainRE = /^[^\s\.]+\.\S{2,}$/; 5 | 6 | export const noop = () => {}; 7 | 8 | export const pluralize = (text, count) => { 9 | return count > 1 || count === 0 ? `${text}s` : text; 10 | }; 11 | 12 | export function toDateISOString(data: string) { 13 | const date = new Date(data); 14 | return date.toLocaleDateString('en-US', { 15 | weekday: 'long', 16 | day: 'numeric', 17 | month: 'long', 18 | year: 'numeric', 19 | }); 20 | } 21 | 22 | export const elide = (string, length = 140, emptyState = '...') => { 23 | if (isEmpty(string)) { 24 | return emptyState; 25 | } 26 | 27 | if (string.length < length) { 28 | return string.trim(); 29 | } 30 | 31 | return `${string.substring(0, length)}...`; 32 | }; 33 | 34 | export function bytesToSize(bytes: number, decimals: number = 2) { 35 | if (bytes === 0) return '0 Bytes'; 36 | 37 | const k = 1000; 38 | const dm = decimals < 0 ? 0 : decimals; 39 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 40 | 41 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 42 | 43 | return `${(bytes / Math.pow(k, i)).toFixed(dm)} ${sizes[i]}`; 44 | } 45 | 46 | export function isEmpty(text: any) { 47 | // NOTE(jim): If a number gets passed in, it isn't considered empty for zero. 48 | if (text === 0) { 49 | return false; 50 | } 51 | 52 | if (!text) { 53 | return true; 54 | } 55 | 56 | if (typeof text === 'object') { 57 | return true; 58 | } 59 | 60 | if (text.length === 0) { 61 | return true; 62 | } 63 | 64 | text = text.toString(); 65 | 66 | return Boolean(!text.trim()); 67 | } 68 | 69 | export function createSlug(text: any) { 70 | if (isEmpty(text)) { 71 | return 'untitled'; 72 | } 73 | 74 | const a = 'æøåàáäâèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'; 75 | const b = 'aoaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'; 76 | const p = new RegExp(a.split('').join('|'), 'g'); 77 | 78 | return text 79 | .toString() 80 | .toLowerCase() 81 | .replace(/\s+/g, '-') // Replace spaces with - 82 | .replace(p, (c) => b.charAt(a.indexOf(c))) // Replace special chars 83 | .replace(/&/g, '-and-') // Replace & with 'and' 84 | .replace(/[^\w\-]+/g, '') // Remove all non-word chars 85 | .replace(/\-\-+/g, '-') // Replace multiple - with single - 86 | .replace(/^-+/, '') // Trim - from start of text 87 | .replace(/-+$/, ''); // Trim - from end of text 88 | } 89 | 90 | export function isUrl(string: any) { 91 | if (typeof string !== 'string') { 92 | return false; 93 | } 94 | 95 | var match = string.match(protocolAndDomainRE); 96 | if (!match) { 97 | return false; 98 | } 99 | 100 | var everythingAfterProtocol = match[1]; 101 | if (!everythingAfterProtocol) { 102 | return false; 103 | } 104 | 105 | if (localhostDomainRE.test(everythingAfterProtocol) || nonLocalhostDomainRE.test(everythingAfterProtocol)) { 106 | return true; 107 | } 108 | 109 | return false; 110 | } 111 | 112 | export function debounce(fn: (...args: Args) => void, delay: number) { 113 | let timeoutID: number | undefined; 114 | let lastArgs: Args | undefined; 115 | 116 | const run = () => { 117 | if (lastArgs) { 118 | fn(...lastArgs); 119 | lastArgs = undefined; 120 | } 121 | }; 122 | 123 | const debounced = (...args: Args) => { 124 | clearTimeout(timeoutID); 125 | lastArgs = args; 126 | timeoutID = window.setTimeout(run, delay); 127 | }; 128 | 129 | debounced.flush = () => { 130 | clearTimeout(timeoutID); 131 | }; 132 | 133 | return debounced; 134 | } 135 | 136 | export function classNames(...args: any[]): string { 137 | var classes = []; 138 | 139 | for (var i = 0; i < arguments.length; i++) { 140 | var arg = arguments[i]; 141 | if (!arg) continue; 142 | 143 | var argType = typeof arg; 144 | 145 | if (argType === 'string' || argType === 'number') { 146 | classes.push(arg); 147 | } else if (Array.isArray(arg)) { 148 | if (arg.length) { 149 | var inner = classNames.apply(null, arg); 150 | if (inner) { 151 | classes.push(inner); 152 | } 153 | } 154 | } else if (argType === 'object') { 155 | if (arg.toString !== Object.prototype.toString) { 156 | classes.push(arg.toString()); 157 | } else { 158 | for (var key in arg) { 159 | if (hasOwn.call(arg, key) && arg[key]) { 160 | classes.push(key); 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | return classes.join(' '); 168 | } 169 | -------------------------------------------------------------------------------- /components/DefaultLayout.module.scss: -------------------------------------------------------------------------------- 1 | .body { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /components/DefaultLayout.tsx: -------------------------------------------------------------------------------- 1 | import styles from '@components/DefaultLayout.module.scss'; 2 | 3 | import * as React from 'react'; 4 | 5 | export default function App(props) { 6 | return
{props.children}
; 7 | } 8 | -------------------------------------------------------------------------------- /components/DefaultMetaTags.tsx: -------------------------------------------------------------------------------- 1 | export default function DefaultMetaTags() { 2 | return ( 3 | <> 4 | 5 | 6 | 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /data/db.ts: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV !== 'production') { 2 | require('dotenv').config(); 3 | } 4 | 5 | import knex from 'knex'; 6 | 7 | const ssl = process.env.EXAMPLE_DATABASE_HOST === '127.0.0.1' ? false : true; 8 | 9 | const DB = knex({ 10 | client: 'pg', 11 | connection: { 12 | ssl: ssl, 13 | port: Number(process.env.EXAMPLE_DATABASE_PORT), 14 | host: process.env.EXAMPLE_DATABASE_HOST, 15 | database: process.env.EXAMPLE_DATABASE_NAME, 16 | user: process.env.EXAMPLE_DATABASE_USERNAME, 17 | password: process.env.EXAMPLE_DATABASE_PASSWORD, 18 | }, 19 | }); 20 | 21 | export default DB; 22 | -------------------------------------------------------------------------------- /data/query.ts: -------------------------------------------------------------------------------- 1 | import DB from '@data/db'; 2 | 3 | function print({ address, copy }) { 4 | console.log(`\x1b[1m\x1b[37m\[${address}\]\x1b[0m : ${copy}`); 5 | } 6 | 7 | export const runQuery = async ({ queryFn, errorFn, label }) => { 8 | let response; 9 | try { 10 | response = await queryFn(); 11 | } catch (e) { 12 | response = errorFn(e); 13 | } 14 | 15 | print({ address: `database-query`, copy: label }); 16 | return response; 17 | }; 18 | 19 | export const example = async (options) => { 20 | return await runQuery({ 21 | label: 'EXAMPLE', 22 | queryFn: async () => { 23 | const query: any = await DB.insert(options) 24 | .into('EXAMPLE_CHANGE_ME') 25 | .returning('*'); 26 | 27 | const index = query ? query.pop() : null; 28 | return index; 29 | }, 30 | errorFn: async (e) => { 31 | return { 32 | error: 'EXAMPLE', 33 | source: e, 34 | }; 35 | }, 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /global.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | div, 4 | span, 5 | applet, 6 | object, 7 | iframe, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | p, 15 | blockquote, 16 | pre, 17 | a, 18 | abbr, 19 | acronym, 20 | address, 21 | big, 22 | cite, 23 | code, 24 | del, 25 | dfn, 26 | em, 27 | img, 28 | ins, 29 | kbd, 30 | q, 31 | s, 32 | samp, 33 | small, 34 | strike, 35 | strong, 36 | sub, 37 | sup, 38 | tt, 39 | var, 40 | b, 41 | u, 42 | i, 43 | center, 44 | dl, 45 | dt, 46 | dd, 47 | ol, 48 | ul, 49 | li, 50 | fieldset, 51 | form, 52 | label, 53 | legend, 54 | table, 55 | caption, 56 | tbody, 57 | tfoot, 58 | thead, 59 | tr, 60 | th, 61 | td, 62 | article, 63 | aside, 64 | canvas, 65 | details, 66 | embed, 67 | figure, 68 | figcaption, 69 | footer, 70 | header, 71 | hgroup, 72 | menu, 73 | nav, 74 | output, 75 | ruby, 76 | section, 77 | summary, 78 | time, 79 | mark, 80 | audio, 81 | video { 82 | box-sizing: border-box; 83 | vertical-align: baseline; 84 | margin: 0; 85 | padding: 0; 86 | border: 0; 87 | } 88 | 89 | article, 90 | aside, 91 | details, 92 | figcaption, 93 | figure, 94 | footer, 95 | header, 96 | hgroup, 97 | menu, 98 | nav, 99 | section { 100 | display: block; 101 | } 102 | 103 | html, 104 | body { 105 | --color-background: #ffffff; 106 | --color-text: #000000; 107 | 108 | font-size: 16px; 109 | background: var(--color-background); 110 | color: var(--color-text); 111 | font-family: -apple-system, BlinkMacSystemFont, helvetica neue, helvetica, sans-serif; 112 | scrollbar-width: none; 113 | -ms-overflow-style: -ms-autohiding-scrollbar; 114 | 115 | ::-webkit-scrollbar { 116 | display: none; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /modules/cors.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | 'use strict'; 4 | 5 | import assign from '@modules/object-assign'; 6 | import vary from '@modules/vary'; 7 | 8 | var defaults = { 9 | origin: '*', 10 | methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', 11 | preflightContinue: false, 12 | optionsSuccessStatus: 204, 13 | }; 14 | 15 | function isString(s) { 16 | return typeof s === 'string' || s instanceof String; 17 | } 18 | 19 | function isOriginAllowed(origin, allowedOrigin) { 20 | if (Array.isArray(allowedOrigin)) { 21 | for (var i = 0; i < allowedOrigin.length; ++i) { 22 | if (isOriginAllowed(origin, allowedOrigin[i])) { 23 | return true; 24 | } 25 | } 26 | return false; 27 | } else if (isString(allowedOrigin)) { 28 | return origin === allowedOrigin; 29 | } else if (allowedOrigin instanceof RegExp) { 30 | return allowedOrigin.test(origin); 31 | } else { 32 | return !!allowedOrigin; 33 | } 34 | } 35 | 36 | function configureOrigin(options, req) { 37 | var requestOrigin = req.headers.origin, 38 | headers = [], 39 | isAllowed; 40 | 41 | if (!options.origin || options.origin === '*') { 42 | // allow any origin 43 | headers.push([ 44 | { 45 | key: 'Access-Control-Allow-Origin', 46 | value: '*', 47 | }, 48 | ]); 49 | } else if (isString(options.origin)) { 50 | // fixed origin 51 | headers.push([ 52 | { 53 | key: 'Access-Control-Allow-Origin', 54 | value: options.origin, 55 | }, 56 | ]); 57 | headers.push([ 58 | { 59 | key: 'Vary', 60 | value: 'Origin', 61 | }, 62 | ]); 63 | } else { 64 | isAllowed = isOriginAllowed(requestOrigin, options.origin); 65 | // reflect origin 66 | headers.push([ 67 | { 68 | key: 'Access-Control-Allow-Origin', 69 | value: isAllowed ? requestOrigin : false, 70 | }, 71 | ]); 72 | headers.push([ 73 | { 74 | key: 'Vary', 75 | value: 'Origin', 76 | }, 77 | ]); 78 | } 79 | 80 | return headers; 81 | } 82 | 83 | function configureMethods(options) { 84 | var methods = options.methods; 85 | if (methods.join) { 86 | methods = options.methods.join(','); // .methods is an array, so turn it into a string 87 | } 88 | return { 89 | key: 'Access-Control-Allow-Methods', 90 | value: methods, 91 | }; 92 | } 93 | 94 | function configureCredentials(options) { 95 | if (options.credentials === true) { 96 | return { 97 | key: 'Access-Control-Allow-Credentials', 98 | value: 'true', 99 | }; 100 | } 101 | return null; 102 | } 103 | 104 | function configureAllowedHeaders(options, req) { 105 | var allowedHeaders = options.allowedHeaders || options.headers; 106 | var headers = []; 107 | 108 | if (!allowedHeaders) { 109 | allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers 110 | headers.push([ 111 | { 112 | key: 'Vary', 113 | value: 'Access-Control-Request-Headers', 114 | }, 115 | ]); 116 | } else if (allowedHeaders.join) { 117 | allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string 118 | } 119 | if (allowedHeaders && allowedHeaders.length) { 120 | headers.push([ 121 | { 122 | key: 'Access-Control-Allow-Headers', 123 | value: allowedHeaders, 124 | }, 125 | ]); 126 | } 127 | 128 | return headers; 129 | } 130 | 131 | function configureExposedHeaders(options) { 132 | var headers = options.exposedHeaders; 133 | if (!headers) { 134 | return null; 135 | } else if (headers.join) { 136 | headers = headers.join(','); // .headers is an array, so turn it into a string 137 | } 138 | if (headers && headers.length) { 139 | return { 140 | key: 'Access-Control-Expose-Headers', 141 | value: headers, 142 | }; 143 | } 144 | return null; 145 | } 146 | 147 | function configureMaxAge(options) { 148 | var maxAge = (typeof options.maxAge === 'number' || options.maxAge) && options.maxAge.toString(); 149 | if (maxAge && maxAge.length) { 150 | return { 151 | key: 'Access-Control-Max-Age', 152 | value: maxAge, 153 | }; 154 | } 155 | return null; 156 | } 157 | 158 | function applyHeaders(headers, res) { 159 | for (var i = 0, n = headers.length; i < n; i++) { 160 | var header = headers[i]; 161 | if (header) { 162 | if (Array.isArray(header)) { 163 | applyHeaders(header, res); 164 | } else if (header.key === 'Vary' && header.value) { 165 | vary(res, header.value); 166 | } else if (header.value) { 167 | res.setHeader(header.key, header.value); 168 | } 169 | } 170 | } 171 | } 172 | 173 | function cors(options, req, res, next) { 174 | var headers = [], 175 | method = req.method && req.method.toUpperCase && req.method.toUpperCase(); 176 | 177 | if (method === 'OPTIONS') { 178 | // preflight 179 | headers.push(configureOrigin(options, req)); 180 | headers.push(configureCredentials(options)); 181 | headers.push(configureMethods(options)); 182 | headers.push(configureAllowedHeaders(options, req)); 183 | headers.push(configureMaxAge(options)); 184 | headers.push(configureExposedHeaders(options)); 185 | applyHeaders(headers, res); 186 | 187 | if (options.preflightContinue) { 188 | next(); 189 | } else { 190 | // Safari (and potentially other browsers) need content-length 0, 191 | // for 204 or they just hang waiting for a body 192 | res.statusCode = options.optionsSuccessStatus; 193 | res.setHeader('Content-Length', '0'); 194 | res.end(); 195 | } 196 | } else { 197 | // actual response 198 | headers.push(configureOrigin(options, req)); 199 | headers.push(configureCredentials(options)); 200 | headers.push(configureExposedHeaders(options)); 201 | applyHeaders(headers, res); 202 | next(); 203 | } 204 | } 205 | 206 | function middlewareWrapper(o) { 207 | // if options are static (either via defaults or custom options passed in), wrap in a function 208 | var optionsCallback = null; 209 | if (typeof o === 'function') { 210 | optionsCallback = o; 211 | } else { 212 | optionsCallback = function (req, cb) { 213 | cb(null, o); 214 | }; 215 | } 216 | 217 | return function corsMiddleware(req, res, next) { 218 | optionsCallback(req, function (err, options) { 219 | if (err) { 220 | next(err); 221 | } else { 222 | var corsOptions = assign({}, defaults, options); 223 | var originCallback = null; 224 | if (corsOptions.origin && typeof corsOptions.origin === 'function') { 225 | originCallback = corsOptions.origin; 226 | } else if (corsOptions.origin) { 227 | originCallback = function (origin, cb) { 228 | cb(null, corsOptions.origin); 229 | }; 230 | } 231 | 232 | if (originCallback) { 233 | originCallback(req.headers.origin, function (err2, origin) { 234 | if (err2 || !origin) { 235 | next(err2); 236 | } else { 237 | corsOptions.origin = origin; 238 | cors(corsOptions, req, res, next); 239 | } 240 | }); 241 | } else { 242 | next(); 243 | } 244 | } 245 | }); 246 | }; 247 | } 248 | 249 | // can pass either an options hash, an options delegate, or nothing 250 | module.exports = middlewareWrapper; 251 | 252 | export default middlewareWrapper; 253 | -------------------------------------------------------------------------------- /modules/object-assign.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | /* 4 | object-assign 5 | (c) Sindre Sorhus 6 | @license MIT 7 | */ 8 | 9 | 'use strict'; 10 | 11 | /* eslint-disable no-unused-vars */ 12 | var getOwnPropertySymbols = Object.getOwnPropertySymbols; 13 | var hasOwnProperty = Object.prototype.hasOwnProperty; 14 | var propIsEnumerable = Object.prototype.propertyIsEnumerable; 15 | 16 | function toObject(val) { 17 | if (val === null || val === undefined) { 18 | throw new TypeError('Object.assign cannot be called with null or undefined'); 19 | } 20 | 21 | return Object(val); 22 | } 23 | 24 | function shouldUseNative() { 25 | try { 26 | if (!Object.assign) { 27 | return false; 28 | } 29 | 30 | // Detect buggy property enumeration order in older V8 versions. 31 | 32 | // https://bugs.chromium.org/p/v8/issues/detail?id=4118 33 | var test1 = new String('abc'); // eslint-disable-line no-new-wrappers 34 | // @ts-ignore 35 | test1[5] = 'de'; 36 | if (Object.getOwnPropertyNames(test1)[0] === '5') { 37 | return false; 38 | } 39 | 40 | // https://bugs.chromium.org/p/v8/issues/detail?id=3056 41 | var test2 = {}; 42 | for (var i = 0; i < 10; i++) { 43 | test2['_' + String.fromCharCode(i)] = i; 44 | } 45 | var order2 = Object.getOwnPropertyNames(test2).map(function (n) { 46 | return test2[n]; 47 | }); 48 | if (order2.join('') !== '0123456789') { 49 | return false; 50 | } 51 | 52 | // https://bugs.chromium.org/p/v8/issues/detail?id=3056 53 | var test3 = {}; 54 | 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { 55 | test3[letter] = letter; 56 | }); 57 | if (Object.keys(Object.assign({}, test3)).join('') !== 'abcdefghijklmnopqrst') { 58 | return false; 59 | } 60 | 61 | return true; 62 | } catch (err) { 63 | // We don't expect any of the above to throw, but better to be safe. 64 | return false; 65 | } 66 | } 67 | 68 | const assign = shouldUseNative() 69 | ? Object.assign 70 | : function (target, source) { 71 | var from; 72 | var to = toObject(target); 73 | var symbols; 74 | 75 | for (var s = 1; s < arguments.length; s++) { 76 | from = Object(arguments[s]); 77 | 78 | for (var key in from) { 79 | if (hasOwnProperty.call(from, key)) { 80 | to[key] = from[key]; 81 | } 82 | } 83 | 84 | if (getOwnPropertySymbols) { 85 | symbols = getOwnPropertySymbols(from); 86 | for (var i = 0; i < symbols.length; i++) { 87 | if (propIsEnumerable.call(from, symbols[i])) { 88 | to[symbols[i]] = from[symbols[i]]; 89 | } 90 | } 91 | } 92 | } 93 | 94 | return to; 95 | }; 96 | 97 | export default assign; 98 | -------------------------------------------------------------------------------- /modules/vary.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | /*! 4 | * vary 5 | * Copyright(c) 2014-2017 Douglas Christopher Wilson 6 | * MIT Licensed 7 | */ 8 | 9 | 'use strict'; 10 | 11 | /** 12 | * Module exports. 13 | */ 14 | 15 | module.exports = vary; 16 | module.exports.append = append; 17 | 18 | /** 19 | * RegExp to match field-name in RFC 7230 sec 3.2 20 | * 21 | * field-name = token 22 | * token = 1*tchar 23 | * tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" 24 | * / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" 25 | * / DIGIT / ALPHA 26 | * ; any VCHAR, except delimiters 27 | */ 28 | 29 | var FIELD_NAME_REGEXP = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/; 30 | 31 | /** 32 | * Append a field to a vary header. 33 | * 34 | * @param {String} header 35 | * @param {String|Array} field 36 | * @return {String} 37 | * @public 38 | */ 39 | 40 | function append(header, field) { 41 | if (typeof header !== 'string') { 42 | throw new TypeError('header argument is required'); 43 | } 44 | 45 | if (!field) { 46 | throw new TypeError('field argument is required'); 47 | } 48 | 49 | // get fields array 50 | var fields = !Array.isArray(field) ? parse(String(field)) : field; 51 | 52 | // assert on invalid field names 53 | for (var j = 0; j < fields.length; j++) { 54 | if (!FIELD_NAME_REGEXP.test(fields[j])) { 55 | throw new TypeError('field argument contains an invalid header name'); 56 | } 57 | } 58 | 59 | // existing, unspecified vary 60 | if (header === '*') { 61 | return header; 62 | } 63 | 64 | // enumerate current values 65 | var val = header; 66 | var vals = parse(header.toLowerCase()); 67 | 68 | // unspecified vary 69 | if (fields.indexOf('*') !== -1 || vals.indexOf('*') !== -1) { 70 | return '*'; 71 | } 72 | 73 | for (var i = 0; i < fields.length; i++) { 74 | var fld = fields[i].toLowerCase(); 75 | 76 | // append value (case-preserving) 77 | if (vals.indexOf(fld) === -1) { 78 | vals.push(fld); 79 | val = val ? val + ', ' + fields[i] : fields[i]; 80 | } 81 | } 82 | 83 | return val; 84 | } 85 | 86 | /** 87 | * Parse a vary header into an array. 88 | * 89 | * @param {String} header 90 | * @return {Array} 91 | * @private 92 | */ 93 | 94 | function parse(header) { 95 | var end = 0; 96 | var list = []; 97 | var start = 0; 98 | 99 | // gather tokens 100 | for (var i = 0, len = header.length; i < len; i++) { 101 | switch (header.charCodeAt(i)) { 102 | case 0x20 /* */: 103 | if (start === end) { 104 | start = end = i + 1; 105 | } 106 | break; 107 | case 0x2c /* , */: 108 | list.push(header.substring(start, end)); 109 | start = end = i + 1; 110 | break; 111 | default: 112 | end = i + 1; 113 | break; 114 | } 115 | } 116 | 117 | // final token 118 | list.push(header.substring(start, end)); 119 | 120 | return list; 121 | } 122 | 123 | /** 124 | * Mark that a request is varied on a header field. 125 | * 126 | * @param {Object} res 127 | * @param {String|Array} field 128 | * @public 129 | */ 130 | 131 | function vary(res, field) { 132 | if (!res || !res.getHeader || !res.setHeader) { 133 | // quack quack 134 | throw new TypeError('res argument is required'); 135 | } 136 | 137 | // get existing header 138 | var val = res.getHeader('Vary') || ''; 139 | var header = Array.isArray(val) ? val.join(', ') : String(val); 140 | 141 | // set new header 142 | if ((val = append(header, field))) { 143 | res.setHeader('Vary', val); 144 | } 145 | } 146 | 147 | export default vary; 148 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {import('next').NextConfig} 3 | */ 4 | const nextConfig = { 5 | experimental: { 6 | appDir: true, 7 | }, 8 | }; 9 | 10 | module.exports = nextConfig; 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-sass", 3 | "engines": { 4 | "node": ">=18" 5 | }, 6 | "license": "MIT", 7 | "version": "0.0.7", 8 | "scripts": { 9 | "dev": "next -p 3005", 10 | "build": "next build", 11 | "start": "next start", 12 | "script": "ts-node -O '{\"module\":\"commonjs\"}' scripts/index.js" 13 | }, 14 | "dependencies": { 15 | "dotenv": "^16.0.3", 16 | "knex": "^2.4.2", 17 | "next": "13.1.6", 18 | "pg": "^8.9.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "sass": "1.58.0" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^18.11.18", 25 | "@types/react": "^18.0.27", 26 | "ts-node": "^10.9.1", 27 | "typescript": "^4.9.5" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /pages/api/index.ts: -------------------------------------------------------------------------------- 1 | import * as Server from '@common/server'; 2 | 3 | // NOTE(jim): 4 | // CORS API example. 5 | export default async function apiIndex(req, res) { 6 | await Server.cors(req, res); 7 | 8 | res.json({ succes: true }); 9 | } 10 | -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/application-research/next-sass/87eae017a895727f0a52d3a37d379936ab541fec/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/application-research/next-sass/87eae017a895727f0a52d3a37d379936ab541fec/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/application-research/next-sass/87eae017a895727f0a52d3a37d379936ab541fec/public/favicon.ico -------------------------------------------------------------------------------- /scripts/example.js: -------------------------------------------------------------------------------- 1 | import DB from '../data/db'; 2 | 3 | const NAME = `example.js`; 4 | 5 | console.log(`RUNNING: ${NAME}`); 6 | 7 | const createRun = DB.schema.createTable('market', function(table) { 8 | table 9 | .uuid('id') 10 | .primary() 11 | .unique() 12 | .notNullable() 13 | .defaultTo(DB.raw('uuid_generate_v4()')); 14 | table.string('text').nullable(); 15 | table.jsonb('data').nullable(); 16 | table 17 | .timestamp('created_at') 18 | .notNullable() 19 | .defaultTo(DB.raw('now()')); 20 | table 21 | .timestamp('updated_at') 22 | .notNullable() 23 | .defaultTo(DB.raw('now()')); 24 | table.timestamp('deleted_at').nullable(); 25 | }); 26 | 27 | async function run() { 28 | await Promise.all([createRun]); 29 | console.log(`FINISHED: ${NAME}`); 30 | process.exit(0); 31 | } 32 | 33 | run(); 34 | -------------------------------------------------------------------------------- /scripts/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./' + process.argv[2] + '.js'); 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@root/*": ["./*"], 6 | "@common/*": ["./common/*"], 7 | "@data/*": ["./data/*"], 8 | "@components/*": ["./components/*"], 9 | "@pages/*": ["./pages/*"], 10 | "@modules/*": ["./modules/*"] 11 | }, 12 | "target": "es5", 13 | "lib": ["dom", "dom.iterable", "esnext"], 14 | "allowJs": true, 15 | "skipLibCheck": true, 16 | "strict": false, 17 | "forceConsistentCasingInFileNames": true, 18 | "noEmit": true, 19 | "esModuleInterop": true, 20 | "module": "esnext", 21 | "moduleResolution": "node", 22 | "resolveJsonModule": true, 23 | "isolatedModules": true, 24 | "jsx": "preserve", 25 | "incremental": true, 26 | "plugins": [ 27 | { 28 | "name": "next" 29 | } 30 | ] 31 | }, 32 | "exclude": ["node_modules", "**/*.spec.ts"], 33 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"] 34 | } 35 | --------------------------------------------------------------------------------