├── .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 |
--------------------------------------------------------------------------------