├── .editorconfig
├── .eslintrc
├── .gitignore
├── .prettierignore
├── LICENSE
├── README.md
├── bin
└── create-strv-app.js
├── logo.svg
├── package.json
├── src
├── Generator.js
├── PluginAPI.js
├── create.js
├── plugins
│ ├── commitlint
│ │ └── index.js
│ ├── cypress
│ │ ├── index.js
│ │ └── templates
│ │ │ └── e2e
│ │ │ ├── .eslintrc.js
│ │ │ ├── plugins
│ │ │ └── index.js
│ │ │ ├── specs
│ │ │ └── test.js
│ │ │ └── support
│ │ │ ├── commands.js
│ │ │ └── index.js
│ ├── dotfiles
│ │ ├── index.js
│ │ └── template
│ │ │ ├── _editorconfig
│ │ │ └── _gitignore
│ ├── eslint
│ │ └── index.js
│ ├── firebase
│ │ ├── index.js
│ │ └── templates
│ │ │ └── firebase.json
│ ├── git-hooks
│ │ └── index.js
│ ├── graphql
│ │ └── index.js
│ ├── heroku
│ │ └── index.js
│ ├── prettier
│ │ └── index.js
│ ├── redux
│ │ ├── index.js
│ │ └── templates
│ │ │ ├── modules
│ │ │ └── index.js
│ │ │ └── store.js
│ ├── styled-components
│ │ ├── index.js
│ │ └── templates
│ │ │ ├── _document.js
│ │ │ └── global.js
│ ├── stylelint
│ │ └── index.js
│ └── typescript
│ │ ├── index.js
│ │ └── template
│ │ └── tsconfig.json
├── readmes
│ ├── default.md
│ ├── index.js
│ └── styled-components.md
└── utils
│ ├── banner.js
│ ├── merge.js
│ └── sort-object.js
├── templates
├── .eslintrc.js
├── SPA
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ └── manifest.json
│ ├── readme.md
│ └── src
│ │ ├── config.js
│ │ ├── index.js
│ │ ├── serviceWorker.js
│ │ ├── styles
│ │ ├── colors.js
│ │ └── fonts.js
│ │ └── views
│ │ └── index.js
└── SSR
│ ├── components
│ └── head.js
│ ├── next.config.js
│ ├── package.json
│ ├── pages
│ └── index.js
│ ├── readme.md
│ └── static
│ └── favicon.ico
├── thumbnail.gif
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [COMMIT_EDITMSG]
12 | max_line_length = 70
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "@strv/javascript/environments/nodejs/v8",
5 | "@strv/javascript/environments/nodejs/optional",
6 | "prettier"
7 | ],
8 | "parserOptions": {
9 | "ecmaVersion": 2018
10 | },
11 | "rules": {
12 | "no-sync": 0,
13 | "no-console": 0,
14 | "no-process-exit": 0,
15 | "react/prop-types": 0,
16 | "no-process-env": 0
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # production
5 | dist
6 | build
7 |
8 | # testing
9 | .nyc_output
10 | coverage
11 |
12 | # logs
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 |
17 | # misc
18 | .DS_Store
19 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | package.json
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Pavel Prichodko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | By running one command this command line interface automatically generates a new project according to STRV's best practices.
4 |
5 | ## Usage
6 |
7 | ```bash
8 | npx create-strv-app
9 | ```
10 |
11 | alternatively
12 |
13 | ```bash
14 | yarn create strv-app
15 | ```
16 |
17 | ## Author
18 |
19 | Pavel Prichodko ([@prchdk](https://twitter.com/prchdk))
20 |
21 | ## License
22 |
23 | [MIT](https://github.com/strvcom/create-strv-app/blob/docs/readme/LICENSE)
24 |
--------------------------------------------------------------------------------
/bin/create-strv-app.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict'
4 |
5 | const updateNotifier = require('update-notifier')
6 | const meow = require('meow')
7 | const chalk = require('chalk')
8 |
9 | const create = require('../src/create')
10 | const pkg = require('../package')
11 |
12 | updateNotifier({ pkg }).notify()
13 | require('please-upgrade-node')(pkg)
14 |
15 | const cli = meow(`
16 | ${chalk.gray('Usage')}
17 |
18 | ${chalk.gray('$')} create-strv-app ${chalk.yellow('[project-directory]')}
19 | `)
20 |
21 | create(cli.input[0] || '.')
22 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-strv-app",
3 | "description": "Set up new app in line with STRV's best practices in one command",
4 | "version": "2.0.2",
5 | "author": "Pavel Prichodko ",
6 | "bugs": {
7 | "url": "https://github.com/prichodko/create-strv-app/issues"
8 | },
9 | "keywords": [
10 | "create-react-app",
11 | "react",
12 | "strv"
13 | ],
14 | "engines": {
15 | "node": ">=8.3.0"
16 | },
17 | "files": [
18 | "bin",
19 | "src",
20 | "templates"
21 | ],
22 | "bin": "./bin/create-strv-app.js",
23 | "repository": "prichodko/create-strv-app",
24 | "license": "MIT",
25 | "scripts": {
26 | "lint": "eslint bin src",
27 | "lint:templates": "eslint templates",
28 | "format": "prettier --write '*/**/*.{css,md,json,js}'"
29 | },
30 | "prettier": {
31 | "semi": false,
32 | "singleQuote": true,
33 | "trailingComma": "es5"
34 | },
35 | "lint-staged": {
36 | "*.js": [
37 | "eslint --fix",
38 | "prettier --write",
39 | "git add"
40 | ],
41 | "*.{json,css,md}": [
42 | "prettier --write",
43 | "git add"
44 | ]
45 | },
46 | "husky": {
47 | "hooks": {
48 | "pre-commit": "lint-staged"
49 | }
50 | },
51 | "dependencies": {
52 | "@babel/parser": "^7.2.2",
53 | "chalk": "^2.4.1",
54 | "execa": "^1.0.0",
55 | "fs-extra": "^7.0.1",
56 | "globby": "^8.0.1",
57 | "inquirer": "^6.2.1",
58 | "isbinaryfile": "^3.0.3",
59 | "lodash.isarray": "^4.0.0",
60 | "lodash.merge": "^4.6.1",
61 | "lodash.mergewith": "^4.6.1",
62 | "log-symbols": "^2.2.0",
63 | "meow": "^5.0.0",
64 | "mri": "^1.1.1",
65 | "ms": "^2.1.1",
66 | "ora": "^3.0.0",
67 | "please-upgrade-node": "^3.1.1",
68 | "recast": "^0.16.1",
69 | "sade": "^1.4.1",
70 | "sort-keys": "^2.0.0",
71 | "update-notifier": "^2.5.0",
72 | "validate-npm-package-name": "^3.0.0"
73 | },
74 | "devDependencies": {
75 | "@commitlint/cli": "^7.2.1",
76 | "@commitlint/config-conventional": "^7.1.2",
77 | "@strv/eslint-config-javascript": "^9.1.1",
78 | "@types/jest": "^23.3.10",
79 | "@types/next": "^7.0.5",
80 | "@types/node": "^10.12.15",
81 | "@types/react": "^16.7.17",
82 | "@types/react-dom": "^16.0.11",
83 | "@types/react-redux": "^6.0.11",
84 | "@types/react-router-dom": "^4.3.1",
85 | "@types/styled-components": "^4.1.4",
86 | "@types/webpack-env": "^1.13.6",
87 | "@zeit/next-typescript": "^1.1.1",
88 | "babel-eslint": "^10.0.1",
89 | "babel-plugin-styled-components": "^1.10.0",
90 | "eslint": "^5.10.0",
91 | "eslint-config-prettier": "^3.3.0",
92 | "eslint-plugin-cypress": "^2.1.3",
93 | "husky": "^1.2.1",
94 | "lint-staged": "^8.1.0",
95 | "prettier": "^1.15.3",
96 | "react-redux": "^6.0.0",
97 | "redux": "^4.0.1",
98 | "redux-devtools-extension": "^2.13.7",
99 | "styled-components": "^4.1.2",
100 | "stylelint": "^9.9.0",
101 | "stylelint-config-standard": "^18.2.0",
102 | "stylelint-config-styled-components": "^0.1.1",
103 | "stylelint-processor-styled-components": "^1.5.1",
104 | "typescript": "^3.2.2"
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Generator.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 |
5 | const fs = require('fs-extra')
6 |
7 | const sortObject = require('./utils/sort-object')
8 | const readmes = require('./readmes')
9 | const PluginAPI = require('./PluginAPI')
10 |
11 | class Generator {
12 | constructor({ name, type, plugins, targetDir }) {
13 | this.name = name
14 | this.type = type
15 | this.plugins = plugins
16 | this.targetDir = targetDir
17 |
18 | this.pkg = {
19 | name,
20 | version: '0.1.0',
21 | private: true,
22 | author: '',
23 | scripts: {},
24 | devDependencies: {},
25 | dependencies: {},
26 | }
27 | this.babel = null
28 | this.copyFiles = {}
29 | this.writeFiles = {}
30 | this.completeCbs = []
31 | }
32 |
33 | sortPackageJson() {
34 | this.pkg.dependencies = sortObject(this.pkg.dependencies)
35 | this.pkg.devDependencies = sortObject(this.pkg.devDependencies)
36 | this.pkg.scripts = sortObject(this.pkg.scripts, [
37 | 'start',
38 | 'dev',
39 | 'build',
40 | 'test',
41 | 'e2e',
42 | 'lint',
43 | 'deploy',
44 | ])
45 | this.pkg = sortObject(this.pkg, [
46 | 'name',
47 | 'version',
48 | 'private',
49 | 'description',
50 | 'author',
51 | 'scripts',
52 | 'dependencies',
53 | 'devDependencies',
54 | 'babel',
55 | 'lint-staged',
56 | 'husky',
57 | 'prettier',
58 | 'eslintConfig',
59 | 'stylelint',
60 | 'browserslist',
61 | 'jest',
62 | ])
63 | }
64 |
65 | copyFileTree() {
66 | return Promise.all(
67 | Object.keys(this.copyFiles).map(async name => {
68 | const filePath = path.join(this.targetDir, name)
69 | await fs.ensureDir(path.dirname(filePath))
70 | await fs.copy(this.copyFiles[name], filePath)
71 | })
72 | )
73 | }
74 |
75 | writeFileTree() {
76 | return Promise.all(
77 | Object.keys(this.writeFiles).map(async name => {
78 | const filePath = path.join(this.targetDir, name)
79 | await fs.ensureDir(path.dirname(filePath))
80 | await fs.writeFile(filePath, this.writeFiles[name])
81 | })
82 | )
83 | }
84 |
85 | renderReadme(templateDir) {
86 | const PLACEHOLDERS = [{ re: '', value: this.name }]
87 |
88 | const filtered = readmes.filter(readme => {
89 | if (!readme.plugin) {
90 | return true
91 | }
92 |
93 | return Object.keys(this.plugins).includes(readme.plugin)
94 | })
95 |
96 | // add readme from template at the beginning
97 | filtered.unshift({ file: path.resolve(templateDir, 'readme.md') })
98 |
99 | const result = filtered.reduce((acc, { file }) => {
100 | let content = fs.readFileSync(file, 'utf8')
101 | PLACEHOLDERS.forEach(p => {
102 | const re = new RegExp(p.re, 'ug')
103 | content = content.replace(re, p.value)
104 | })
105 | acc = acc.concat(content, '\n')
106 | return acc
107 | }, '')
108 |
109 | const filePath = path.resolve(this.targetDir, 'readme.md')
110 | return fs.writeFile(filePath, result, 'utf8')
111 | }
112 |
113 | onComplete() {
114 | this.completeCbs.forEach(cb => cb())
115 | }
116 |
117 | async generate() {
118 | const templateDir = path.resolve(__dirname, '../templates', this.type)
119 |
120 | const templatePkgJson = fs.readJsonSync(
121 | path.resolve(templateDir, 'package.json')
122 | )
123 |
124 | this.pkg = {
125 | ...this.pkg,
126 | ...templatePkgJson,
127 | }
128 |
129 | const api = new PluginAPI(this)
130 | api.copy(templateDir)
131 |
132 | this.plugins.forEach(plugin => plugin.apply(api))
133 |
134 | this.sortPackageJson()
135 |
136 | this.writeFiles['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
137 | if (this.babel) {
138 | this.writeFiles['.babelrc'] = JSON.stringify(this.babel, null, 2) + '\n'
139 | }
140 |
141 | // we provide our own
142 | delete this.copyFiles['package.json']
143 | delete this.copyFiles['readme.md']
144 |
145 | await this.copyFileTree()
146 | await this.writeFileTree()
147 | await this.renderReadme(templateDir)
148 | }
149 | }
150 |
151 | module.exports = Generator
152 |
--------------------------------------------------------------------------------
/src/PluginAPI.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 |
5 | const globby = require('globby')
6 | const chalk = require('chalk')
7 |
8 | const merge = require('./utils/merge')
9 | const { devDependencies } = require('../package.json')
10 |
11 | const isString = val => typeof val === 'string'
12 |
13 | class PluginAPI {
14 | constructor(generator) {
15 | this.generator = generator
16 | }
17 |
18 | is(type) {
19 | return this.generator.type === type
20 | }
21 |
22 | hasPlugin(id) {
23 | return this.generator.plugins.some(plugin => plugin.name === id)
24 | }
25 |
26 | updatePackageJson(fields) {
27 | this.generator.pkg = merge(this.generator.pkg, fields)
28 | }
29 |
30 | updateBabel(fields) {
31 | if (this.generator.babel === null) {
32 | this.generator.babel = {
33 | presets: this.is('SPA')
34 | ? ['@strv/react-scripts/babel']
35 | : ['next/babel'],
36 | }
37 | }
38 |
39 | this.generator.babel = merge(this.generator.babel, fields)
40 | }
41 |
42 | addDependency(name) {
43 | const dep = devDependencies[name]
44 | if (dep) {
45 | this.updatePackageJson({ dependencies: { [name]: dep } })
46 | } else {
47 | console.log()
48 | console.log(
49 | chalk.red(
50 | `Please add ${dep} to create-strv-app package.json dependencies.`
51 | )
52 | )
53 | }
54 | }
55 |
56 | addDevDependency(name) {
57 | const dep = devDependencies[name]
58 | if (dep) {
59 | this.updatePackageJson({ devDependencies: { [name]: dep } })
60 | } else {
61 | console.log()
62 | console.log(
63 | chalk.red(
64 | `Please add ${dep} to create-strv-app package.json devDependencies.`
65 | )
66 | )
67 | }
68 | }
69 |
70 | onCreateComplete(cb) {
71 | this.generator.completeCbs.push(cb)
72 | }
73 |
74 | copy(source) {
75 | if (isString(source)) {
76 | const sourceFiles = globby.sync(['**/*'], { cwd: source })
77 |
78 | for (const file of sourceFiles) {
79 | let filename = path.basename(file)
80 |
81 | // dotfiles are ignored on npm, they should be prefixed with underscore
82 | // we convert them back to dotfiles during generating
83 | if (filename.charAt(0) === '_') {
84 | filename = `.${filename.slice(1)}`
85 | }
86 |
87 | const targetPath = path.join(path.dirname(file), filename)
88 | const sourcePath = path.resolve(source, file)
89 |
90 | this.generator.copyFiles[targetPath] = sourcePath
91 | }
92 | } else {
93 | source(this.generator.copyFiles)
94 | }
95 | }
96 |
97 | render(source) {
98 | source(this.generator.writeFiles)
99 | }
100 | }
101 |
102 | module.exports = PluginAPI
103 |
--------------------------------------------------------------------------------
/src/create.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 |
5 | const ms = require('ms')
6 | const execa = require('execa')
7 | const fs = require('fs-extra')
8 | const { prompt } = require('inquirer')
9 | const validateName = require('validate-npm-package-name')
10 | const chalk = require('chalk')
11 | const ora = require('ora')
12 |
13 | const Generator = require('./Generator')
14 | const banner = require('./utils/banner')
15 |
16 | const dotfiles = require('./plugins/dotfiles')
17 | const eslint = require('./plugins/eslint')
18 | const prettier = require('./plugins/prettier')
19 | const stylelint = require('./plugins/stylelint')
20 | const gitHooks = require('./plugins/git-hooks')
21 | const commitlint = require('./plugins/commitlint')
22 | const styledComponents = require('./plugins/styled-components')
23 | const redux = require('./plugins/redux')
24 | const typescript = require('./plugins/typescript')
25 | // const firebase = require('./plugins/firebase')
26 | // const heroku = require('./plugins/heroku')
27 | // const cypress = require('./plugins/cypress')
28 |
29 | // const isTest = process.env.TEST
30 |
31 | const DEFAULT_PLUGINS = [
32 | {
33 | name: 'dotfiles',
34 | apply: dotfiles,
35 | },
36 | {
37 | name: 'ESLint',
38 | apply: eslint,
39 | },
40 | {
41 | name: 'Prettier',
42 | apply: prettier,
43 | },
44 | {
45 | name: 'git-hooks',
46 | apply: gitHooks,
47 | },
48 | {
49 | name: 'stylelint',
50 | apply: stylelint,
51 | },
52 | ]
53 |
54 | let spinner = ora({
55 | color: 'red',
56 | })
57 |
58 | async function create() {
59 | const start = Date.now()
60 |
61 | banner()
62 |
63 | const { name } = await prompt({
64 | name: 'name',
65 | message: 'What is the name of the project?',
66 | validate: value => {
67 | const { errors } = validateName(value)
68 | if (errors) {
69 | errors.unshift(`Invalid package name: ${value}`)
70 | return errors
71 | .map(e => e.charAt(0).toUpperCase() + e.substring(1))
72 | .join(`\n${chalk.red('>>')} `)
73 | }
74 |
75 | const targetDir = path.resolve(value)
76 | if (fs.existsSync(targetDir)) {
77 | return `Target directory ${chalk.cyan(targetDir)} already exists.`
78 | }
79 |
80 | return true
81 | },
82 | })
83 |
84 | const { type } = await prompt({
85 | name: 'type',
86 | type: 'list',
87 | message: 'What type of application do you need?',
88 | choices: [
89 | { name: 'Single-page', value: 'SPA' },
90 | { name: 'Server-rendered', value: 'SSR' },
91 | // { name: 'Static', value: 'Static' },
92 | ],
93 | })
94 |
95 | const { packageManager } = await prompt({
96 | name: 'packageManager',
97 | message: 'What is your preferred package manager?',
98 | type: 'list',
99 | choices: [{ name: 'Yarn', value: 'yarn' }, 'npm'],
100 | })
101 |
102 | const { useTypeScript } = await prompt({
103 | name: 'useTypeScript',
104 | type: 'confirm',
105 | message: 'Do you want to use TypeScript?',
106 | })
107 |
108 | const { optionalPlugins } = await prompt({
109 | name: 'optionalPlugins',
110 | message: 'Add some optional plugins?',
111 | type: 'checkbox',
112 | choices: [
113 | {
114 | name: 'styled-components',
115 | value: { name: 'styled-components', apply: styledComponents },
116 | },
117 | {
118 | name: 'redux',
119 | value: { name: 'redux', apply: redux },
120 | },
121 | {
122 | name: 'commitlint',
123 | value: { name: 'commitlint', apply: commitlint },
124 | },
125 | ],
126 | })
127 |
128 | const targetDir = path.resolve(name)
129 | const plugins = optionalPlugins.concat(DEFAULT_PLUGINS)
130 | if (useTypeScript) {
131 | plugins.push({ name: 'TypeScript', apply: typescript })
132 | }
133 |
134 | // run the generator
135 | spinner.start('Generating project')
136 | const generator = new Generator({ name, type, plugins, targetDir })
137 | await generator.generate()
138 | spinner.succeed(`Project generated at ${chalk.blue(targetDir)}`)
139 |
140 | // install dependencies
141 | spinner.start(`Installing dependencies with ${chalk.blue(packageManager)}`)
142 | await execa(packageManager, ['install'], { cwd: targetDir })
143 | spinner.succeed(`Dependencies installed with ${chalk.blue(packageManager)}`)
144 |
145 | // intialize git
146 | spinner.start('Initializing git')
147 | await execa('git', ['init'], { cwd: targetDir })
148 | await execa('git', ['add', '-A'], { cwd: targetDir })
149 | await execa('git', ['commit', '-m', 'Initial commit'], {
150 | cwd: targetDir,
151 | })
152 | await execa('node', ['node_modules/husky/husky.js', 'install'], {
153 | cwd: targetDir,
154 | })
155 | spinner.succeed(`${chalk.blue('git')} initialized`)
156 |
157 | spinner.succeed(`Project created in ${chalk.blue(ms(Date.now() - start))} 🎉`)
158 |
159 | generator.onComplete()
160 | }
161 |
162 | // eslint-disable-next-line
163 | module.exports = (...args) => {
164 | create().catch(error => {
165 | spinner.fail(`Error: ${error.message}`)
166 | // if (process.env.DEBUG) {
167 | console.log(error)
168 | // }
169 | })
170 | }
171 |
--------------------------------------------------------------------------------
/src/plugins/commitlint/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | api.addDevDependency('@commitlint/cli')
5 | api.addDevDependency('@commitlint/config-conventional')
6 | api.updatePackageJson({
7 | commitlint: {
8 | extends: ['@commitlint/config-conventional'],
9 | },
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/src/plugins/cypress/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | api.addDevDependency('cypress')
5 | api.addDevDependency('eslint-plugin-cypress')
6 | api.updatePackageJson({
7 | scripts: {
8 | 'test:e2e': 'cypress run',
9 | 'cypress:open': 'cypress open',
10 | },
11 | })
12 | api.copy(require.resolve('./templates'))
13 | }
14 |
--------------------------------------------------------------------------------
/src/plugins/cypress/templates/e2e/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['cypress'],
3 | env: {
4 | mocha: true,
5 | 'cypress/globals': true,
6 | },
7 | rules: {
8 | strict: 'off',
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/src/plugins/cypress/templates/e2e/plugins/index.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/guides/guides/plugins-guide.html
2 |
3 | module.exports = (on, config) => {
4 | return Object.assign({}, config, {
5 | fixturesFolder: 'tests/e2e/fixtures',
6 | integrationFolder: 'tests/e2e/specs',
7 | screenshotsFolder: 'tests/e2e/screenshots',
8 | videosFolder: 'tests/e2e/videos',
9 | supportFile: 'tests/e2e/support/index.js',
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/src/plugins/cypress/templates/e2e/specs/test.js:
--------------------------------------------------------------------------------
1 | // https://docs.cypress.io/api/introduction/api.html
2 |
3 | describe('My First Test', () => {
4 | it('Visits the app root url', () => {
5 | cy.visit('/')
6 | cy.contains('h1', 'Welcome to Your Vue.js App')
7 | })
8 | })
9 |
--------------------------------------------------------------------------------
/src/plugins/cypress/templates/e2e/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/src/plugins/cypress/templates/e2e/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/src/plugins/dotfiles/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 |
5 | module.exports = api => {
6 | api.copy(path.resolve(__dirname, './template'))
7 | }
8 |
--------------------------------------------------------------------------------
/src/plugins/dotfiles/template/_editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [COMMIT_EDITMSG]
12 | max_line_length = 70
--------------------------------------------------------------------------------
/src/plugins/dotfiles/template/_gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | node_modules
3 |
4 | # production
5 | dist
6 | build
7 | .next
8 |
9 | # testing
10 | .nyc_output
11 | coverage
12 |
13 | # logs
14 | *.log
15 |
16 | # misc
17 | .DS_Store
18 |
--------------------------------------------------------------------------------
/src/plugins/eslint/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | if (!api.is('SPA')) {
5 | api.addDevDependency('eslint')
6 | api.addDevDependency('babel-eslint')
7 | }
8 | api.addDevDependency('@strv/eslint-config-javascript')
9 | api.addDevDependency('eslint-config-prettier')
10 |
11 | api.updatePackageJson({
12 | scripts: {
13 | lint: 'eslint .',
14 | },
15 | eslintConfig: {
16 | extends: [
17 | '@strv/javascript/environments/react/v16',
18 | 'prettier',
19 | 'prettier/react',
20 | ],
21 | root: true,
22 | env: {
23 | browser: true,
24 | commonjs: true,
25 | es6: true,
26 | jest: true,
27 | node: true,
28 | },
29 | parser: 'babel-eslint',
30 | parserOptions: {
31 | ecmaVersion: 2018,
32 | sourceType: 'module',
33 | ecmaFeatures: {
34 | jsx: true,
35 | },
36 | },
37 | },
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/src/plugins/firebase/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | api.copy(require.resolve('./templates'))
5 | }
6 |
--------------------------------------------------------------------------------
/src/plugins/firebase/templates/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "build",
4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
5 | "rewrites": [
6 | {
7 | "source": "**",
8 | "destination": "/index.html"
9 | }
10 | ],
11 | "headers": [
12 | {
13 | "source": "/service-worker.js",
14 | "headers": [{ "key": "Cache-Control", "value": "no-cache" }]
15 | }
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/plugins/git-hooks/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | api.addDevDependency('husky')
5 | api.addDevDependency('lint-staged')
6 |
7 | const hasTS = api.hasPlugin('TypeScript')
8 | const hasSC = api.hasPlugin('styled-components')
9 |
10 | api.updatePackageJson({
11 | 'lint-staged': {
12 | '*.js': [
13 | 'eslint --fix',
14 | ...(hasSC ? ['stylelint'] : []),
15 | 'prettier --write',
16 | 'git add',
17 | ],
18 | ...(hasTS && {
19 | '*.{ts,tsx}': [
20 | ...(hasSC ? ['stylelint'] : []),
21 | 'prettier --write',
22 | 'git add',
23 | ],
24 | }),
25 | '*.{json,md}': ['prettier --write', 'git add'],
26 | '*.css': ['stylelint', 'prettier --write', 'git add'],
27 | },
28 | husky: {
29 | hooks: {
30 | 'pre-commit': 'lint-staged',
31 | },
32 | },
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/src/plugins/graphql/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // eslint-disable-next-line
4 | module.exports = api => {}
5 |
--------------------------------------------------------------------------------
/src/plugins/heroku/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | // eslint-disable-next-line
4 | module.exports = api => {}
5 |
--------------------------------------------------------------------------------
/src/plugins/prettier/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | api.addDevDependency('prettier')
5 |
6 | const ext = api.hasPlugin('TypeScript') ? 'ts,tsx' : 'js'
7 |
8 | api.updatePackageJson({
9 | scripts: {
10 | format: `prettier --write '*/**/*.{${ext},css,md,json}'`,
11 | },
12 | prettier: {
13 | semi: false,
14 | singleQuote: true,
15 | trailingComma: 'es5',
16 | },
17 | })
18 |
19 | api.render(files => {
20 | files['.prettierignore'] = 'package.json'
21 | if (!api.is('SPA')) {
22 | files['.prettierignore'] += '\n.next'
23 | }
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/src/plugins/redux/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | api.addDependency('redux')
5 | api.addDependency('react-redux')
6 | }
7 |
8 | // api.addDependency('redux-saga')
9 | // api.addDependency('redux-observable')
10 | // api.addDependency('rxjs')
11 |
12 | // rematch
13 | // import { init } from '@rematch/core'
14 | // import models from './models'
15 |
16 | // export default initialState => {
17 | // const store = init({
18 | // models,
19 | // redux: {
20 | // initialState
21 | // rootReducers: {
22 | // 'RESET': (state, action) => undefined,
23 | // }
24 | // }
25 | // })
26 |
27 | // export const { dispatch } = store
28 | // export default store
29 |
30 | // redux-observable
31 | // import { createEpicMiddleware } from 'redux-observable';
32 | // import { rootEpic } from './modules'
33 | // export const rootEpic = combineEpics(
34 | // pingEpic,
35 | // fetchUserEpic
36 | // );
37 | // const epicMiddleware = createEpicMiddleware()
38 | // epicMiddleware.run(rootEpic);
39 |
40 | // redux-saga
41 | // const sagas = []
42 | // export default function* rootSaga() {
43 | // yield sagas.map(saga => fork(saga))
44 | // }
45 | // import { rootSaga } from './modules'
46 | // import createSagaMiddleware from 'redux-saga'
47 | // const sagaMiddleware = createSagaMiddleware()
48 | // sagaMiddleware.run(rootSaga)
49 |
50 | // import { Provider, connect } from 'react-redux'
51 | // import createStore from './store'
52 | // const store = createStore()
53 | //
54 | //
55 |
--------------------------------------------------------------------------------
/src/plugins/redux/templates/modules/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | export const rootReducer = combineReducers()
4 |
--------------------------------------------------------------------------------
/src/plugins/redux/templates/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux'
2 | import { composeWithDevTools } from 'redux-devtools-extension'
3 |
4 | import { rootReducer } from '../modules'
5 |
6 | const middlewares = []
7 |
8 | export default initialState => {
9 | const store = createStore(
10 | rootReducer,
11 | initialState,
12 | composeWithDevTools(applyMiddleware(...middlewares))
13 | )
14 |
15 | if (module.hot) {
16 | module.hot.accept('./modules/index.js', () => {
17 | store.replaceReducer(rootReducer)
18 | })
19 | }
20 |
21 | return store
22 | }
23 |
--------------------------------------------------------------------------------
/src/plugins/styled-components/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | api.addDependency('styled-components')
5 | api.addDevDependency('babel-plugin-styled-components')
6 |
7 | api.updateBabel({
8 | plugins: api.is('SPA')
9 | ? ['babel-plugin-styled-components']
10 | : [['babel-plugin-styled-components', { ssr: true }]],
11 | })
12 |
13 | api.copy(files => {
14 | const dest = api.is('SPA') ? 'src/styles/global.js' : 'styles/global.js'
15 | files[dest] = require.resolve('./templates/global.js')
16 | })
17 |
18 | if (!api.is('SPA')) {
19 | api.copy(files => {
20 | files['pages/_document.js'] = require.resolve('./templates/_document.js')
21 | })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/plugins/styled-components/templates/_document.js:
--------------------------------------------------------------------------------
1 | import Document, { Head, Main, NextScript } from 'next/document'
2 | import { ServerStyleSheet } from 'styled-components'
3 |
4 | export default class MyDocument extends Document {
5 | static getInitialProps({ renderPage }) {
6 | const sheet = new ServerStyleSheet()
7 | const page = renderPage(App => props =>
8 | sheet.collectStyles()
9 | )
10 | const styleTags = sheet.getStyleElement()
11 | return { ...page, styleTags }
12 | }
13 |
14 | render() {
15 | return (
16 |
17 | {this.props.styleTags}
18 |
19 |
20 |
21 |
22 |
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/plugins/styled-components/templates/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components'
2 |
3 | const GlobalStyle = createGlobalStyle``
4 |
5 | export default GlobalStyle
6 |
--------------------------------------------------------------------------------
/src/plugins/stylelint/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = api => {
4 | api.addDevDependency('stylelint')
5 | api.addDevDependency('stylelint-config-standard')
6 |
7 | if (api.hasPlugin('styled-components')) {
8 | api.addDevDependency('stylelint-processor-styled-components')
9 | api.addDevDependency('stylelint-config-styled-components')
10 | api.updatePackageJson({
11 | stylelint: {
12 | processors: ['stylelint-processor-styled-components'],
13 | extends: [
14 | 'stylelint-config-standard',
15 | 'stylelint-config-styled-components',
16 | ],
17 | rules: {},
18 | },
19 | })
20 | } else {
21 | api.updatePackageJson({
22 | stylelint: {
23 | extends: 'stylelint-config-standard',
24 | rules: {},
25 | },
26 | })
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/plugins/typescript/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const chalk = require('chalk')
5 |
6 | module.exports = api => {
7 | api.addDevDependency('typescript')
8 | api.addDevDependency('@types/node')
9 | api.addDevDependency('@types/react-dom')
10 |
11 | if (api.is('SPA')) {
12 | api.addDevDependency('@types/jest')
13 | api.addDevDependency('@types/react-router-dom')
14 | api.addDevDependency('@types/webpack-env')
15 | } else {
16 | api.addDevDependency('@types/next')
17 | api.addDependency('@zeit/next-typescript')
18 | api.onCreateComplete(() => {
19 | console.log()
20 | console.log(
21 | `To Finish ${chalk.blue(
22 | 'TypeScript'
23 | )} setup you will need to update next.config.js
24 | https://github.com/zeit/next-plugins/tree/master/packages/next-typescript
25 | `
26 | )
27 | })
28 | }
29 |
30 | api.updatePackageJson({
31 | scripts: {
32 | 'type-check': 'tsc',
33 | },
34 | })
35 |
36 | if (api.hasPlugin('styled-components')) {
37 | api.addDevDependency('@types/styled-components')
38 | }
39 |
40 | if (api.hasPlugin('redux')) {
41 | api.addDevDependency('@types/react-redux')
42 | }
43 |
44 | api.copy(path.resolve('./template'))
45 | }
46 |
--------------------------------------------------------------------------------
/src/plugins/typescript/template/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "esModuleInterop": true,
5 | "allowSyntheticDefaultImports": true,
6 | "jsx": "preserve",
7 | "lib": ["dom", "es2017"],
8 | "module": "esnext",
9 | "moduleResolution": "node",
10 | "noEmit": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noUnusedLocals": true,
13 | "noUnusedParameters": true,
14 | "preserveConstEnums": true,
15 | "removeComments": false,
16 | "skipLibCheck": true,
17 | "sourceMap": true,
18 | "strict": true,
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "target": "esnext"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/readmes/default.md:
--------------------------------------------------------------------------------
1 | ## Code quality
2 |
3 | ### Prettier
4 |
5 | ### ESLint
6 |
7 | ### Git hooks
8 |
--------------------------------------------------------------------------------
/src/readmes/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 |
5 | const getPath = name => path.resolve(__dirname, name)
6 |
7 | // order matters here
8 | module.exports = [
9 | { file: getPath('./default.md') },
10 | {
11 | plugin: 'styled-components',
12 | file: getPath('./styled-components.md'),
13 | },
14 | ]
15 |
--------------------------------------------------------------------------------
/src/readmes/styled-components.md:
--------------------------------------------------------------------------------
1 | ### styled-components
2 |
--------------------------------------------------------------------------------
/src/utils/banner.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = () =>
4 | console.log(`
5 | ███████╗████████╗██████╗ ██╗ ██╗
6 | ██╔════╝╚══██╔══╝██╔══██╗██║ ██║
7 | ███████╗ ██║ ██████╔╝██║ ██║
8 | ╚════██║ ██║ ██╔══██╗╚██╗ ██╔╝
9 | ███████║ ██║ ██║ ██║ ╚████╔╝
10 | ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝
11 | `)
12 |
--------------------------------------------------------------------------------
/src/utils/merge.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const mergeWith = require('lodash.mergewith')
4 | const isArray = require('lodash.isarray')
5 |
6 | module.exports = (objA, objB) =>
7 | mergeWith(objA, objB, (objValue, srcValue) => {
8 | if (isArray(objValue)) {
9 | return objValue.concat(srcValue)
10 | }
11 | })
12 |
--------------------------------------------------------------------------------
/src/utils/sort-object.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = (obj, keyOrder) => {
4 | const res = {}
5 |
6 | if (keyOrder) {
7 | keyOrder.forEach(key => {
8 | res[key] = obj[key]
9 | delete obj[key]
10 | })
11 | }
12 |
13 | const keys = Object.keys(obj)
14 |
15 | keys.sort()
16 | keys.forEach(key => {
17 | res[key] = obj[key]
18 | })
19 |
20 | return res
21 | }
22 |
--------------------------------------------------------------------------------
/templates/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | '@strv/javascript/environments/react/v16',
4 | 'prettier',
5 | 'prettier/react',
6 | ],
7 | env: {
8 | browser: true,
9 | commonjs: true,
10 | es6: true,
11 | jest: true,
12 | node: true,
13 | },
14 | parser: 'babel-eslint',
15 | parserOptions: {
16 | ecmaVersion: 2018,
17 | sourceType: 'module',
18 | ecmaFeatures: {
19 | jsx: true,
20 | },
21 | },
22 | rules: {
23 | 'import/no-unresolved': 0,
24 | 'react/prop-types': 0,
25 | },
26 | }
27 |
--------------------------------------------------------------------------------
/templates/SPA/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "homepage": "/",
3 | "scripts": {
4 | "start": "react-scripts start",
5 | "build": "react-scripts build",
6 | "analyze": "react-scripts analyze",
7 | "test": "react-scripts test --env=jsdom"
8 | },
9 | "dependencies": {
10 | "@strv/react-scripts": "^2.0.0",
11 | "react": "^16.6.3",
12 | "react-dom": "^16.6.3",
13 | "react-router-dom": "^4.3.1",
14 | "sanitize.css": "^8.0.0"
15 | },
16 | "browserslist": [
17 | ">0.2%",
18 | "not dead",
19 | "not ie <= 11",
20 | "not op_mini all"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/templates/SPA/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/create-strv-app/1dd3b2aac37c12294946756e6b575d789d249b6a/templates/SPA/public/favicon.ico
--------------------------------------------------------------------------------
/templates/SPA/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/templates/SPA/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/templates/SPA/readme.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 | ## Getting started
4 |
5 | ## Development
6 |
7 | ```bash
8 | npm start
9 | ```
10 |
11 | or
12 |
13 | ```bash
14 | yarn start
15 | ```
16 |
17 | ## Production
18 |
19 | ```bash
20 | npm build
21 | ```
22 |
23 | or
24 |
25 | ```bash
26 | yarn build
27 | ```
28 |
29 | > See `scripts` field inside package.json for available commands
30 |
--------------------------------------------------------------------------------
/templates/SPA/src/config.js:
--------------------------------------------------------------------------------
1 | export default {}
2 |
--------------------------------------------------------------------------------
/templates/SPA/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import { BrowserRouter as Router } from 'react-router-dom'
4 | import 'sanitize.css'
5 |
6 | import App from './views'
7 | import * as serviceWorker from './serviceWorker'
8 |
9 | const render = () => {
10 | ReactDOM.render(
11 |
12 |
13 | ,
14 | document.getElementById('root')
15 | )
16 | }
17 |
18 | if (module.hot) {
19 | module.hot.accept('./views', render)
20 | }
21 |
22 | render()
23 |
24 | // If you want your app to work offline and load faster, you can change
25 | // unregister() to register() below. Note this comes with some pitfalls.
26 | // Learn more about service workers: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
27 | serviceWorker.unregister()
28 |
--------------------------------------------------------------------------------
/templates/SPA/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | )
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config)
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | )
48 | })
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config)
52 | }
53 | })
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing
63 | if (installingWorker == null) {
64 | return
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | )
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration)
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.')
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration)
90 | }
91 | }
92 | }
93 | }
94 | }
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error)
98 | })
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type')
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload()
115 | })
116 | })
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config)
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | )
126 | })
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister()
133 | })
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/templates/SPA/src/styles/colors.js:
--------------------------------------------------------------------------------
1 | export default {}
2 |
--------------------------------------------------------------------------------
/templates/SPA/src/styles/fonts.js:
--------------------------------------------------------------------------------
1 | export default {}
2 |
--------------------------------------------------------------------------------
/templates/SPA/src/views/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class App extends Component {
4 | render() {
5 | return (
6 |
7 |
Hello, World!
8 |
9 | )
10 | }
11 | }
12 |
13 | export default App
14 |
--------------------------------------------------------------------------------
/templates/SSR/components/head.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import NextHead from 'next/head'
3 |
4 | const Head = ({
5 | title = 'STRV - Next.js template',
6 | description = '',
7 | ogUrl = '',
8 | ogImage = '',
9 | }) => (
10 |
11 |
12 | {title}
13 |
14 |
15 |
16 | {/* */}
17 | {/* */}
18 | {/* */}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 |
31 | export default Head
32 |
--------------------------------------------------------------------------------
/templates/SSR/next.config.js:
--------------------------------------------------------------------------------
1 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
2 |
3 | const { ANALYZE } = process.env
4 |
5 | module.exports = {
6 | webpack: (config, { isServer }) => {
7 | if (ANALYZE) {
8 | config.plugins.push(
9 | new BundleAnalyzerPlugin({
10 | analyzerMode: 'server',
11 | analyzerPort: isServer ? 8888 : 8889,
12 | openAnalyzer: true,
13 | })
14 | )
15 | }
16 |
17 | return config
18 | },
19 | }
20 |
--------------------------------------------------------------------------------
/templates/SSR/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "next",
4 | "build": "next build",
5 | "start": "next start",
6 | "analyze": "ANALYZE=1 next build"
7 | },
8 | "dependencies": {
9 | "next": "^7.0.2",
10 | "react": "^16.6.3",
11 | "react-dom": "^16.6.3"
12 | },
13 | "devDependencies": {
14 | "webpack-bundle-analyzer": "^3.0.3"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/templates/SSR/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Head from '../components/head'
3 |
4 | export default () => (
5 |
6 |
7 | Hello, world!
8 |
9 | )
10 |
--------------------------------------------------------------------------------
/templates/SSR/readme.md:
--------------------------------------------------------------------------------
1 | #
2 |
--------------------------------------------------------------------------------
/templates/SSR/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/create-strv-app/1dd3b2aac37c12294946756e6b575d789d249b6a/templates/SSR/static/favicon.ico
--------------------------------------------------------------------------------
/thumbnail.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/strvcom/create-strv-app/1dd3b2aac37c12294946756e6b575d789d249b6a/thumbnail.gif
--------------------------------------------------------------------------------