1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Compare commits

...

25 Commits

Author SHA1 Message Date
munja
8c872bde92 chore(release): 0.0.28 2022-02-16 21:14:53 +00:00
Allan Bowe
f953472efd Merge pull request #62 from sasjs/headerfix
feat: default macros and bumping core
2022-02-16 23:07:31 +02:00
munja
f10138b0f2 fix: moving core 2022-02-16 21:05:22 +00:00
munja
6f19d3d0ea feat: default macros and bumping core 2022-02-16 21:03:16 +00:00
munja
a7facb005a chore: updating README with correct clientid 2022-02-16 14:52:56 +00:00
munja
88acf9df5d chore(release): 0.0.27 2022-02-16 14:00:34 +00:00
Allan Bowe
b0880b142a Merge pull request #60 from sasjs/errfix
feat: removing stpsrv_header and updating README with auth details
2022-02-16 15:59:56 +02:00
munja
d3674c7f94 feat: removing stpsrv_header and updating README with auth details 2022-02-16 13:19:50 +00:00
Saad Jutt
adccca6c7f chore: updated README.md 2022-02-15 21:53:54 +05:00
Yury Shkoda
8b83ccc4c2 Merge pull request #51 from sasjs/cli-issue-1108
feat: return json response with the log ob job execution
2022-02-15 11:08:17 +03:00
Yury Shkoda
556944b1d5 chore(git): Merge remote-tracking branch 'origin/main' into cli-issue-1108 2022-02-15 10:25:38 +03:00
Saad Jutt
b14e07ee6e chore(release): 0.0.26 2022-02-15 04:09:55 +05:00
Muhammad Saad
048bd9f78c Merge pull request #57 from sasjs/final-release-should-also-has-https-server
fix: release should also has https protocol
2022-02-15 03:07:17 +04:00
Saad Jutt
d7e1aca7e3 fix: refactored + removed unused package 2022-02-15 04:04:38 +05:00
Saad Jutt
de47d78a00 chore: Merge branch 'main' into final-release-should-also-has-https-server 2022-02-15 03:12:28 +05:00
JahanzaibRao15
58b6f439b3 Fixed vulnerabilities and remove unused or redundant dependencies (#55)
* chore: fix vulnerabilities and remove unused or redundant dependencies

* chore: Added start script and vulnerabilities screenshot

* fix: quick fix

* fix: converted to typescript + bugs removed

Co-authored-by: jahanzaibrao-dev <jahanzaib@tetrahex.com>
Co-authored-by: Saad Jutt <ihsan.distranger@gmail.com>
2022-02-15 03:11:03 +05:00
Saad Jutt
0cfe724ffa fix: release should also has https protocol 2022-02-14 19:12:37 +05:00
Yury Shkoda
fde4bc051d chore(execution): roll back changes related to returnLog var 2022-02-14 15:43:56 +03:00
Muhammad Saad
367b0f1f89 Merge pull request #54 from sasjs/token-expiry-updated
fix: updated token expiry times
2022-02-11 21:05:31 +04:00
Saad Jutt
d17a3dd590 fix: updated token expiry times 2022-02-11 21:54:27 +05:00
munja
bee5deed2a chore(release): 0.0.25 2022-02-11 17:30:39 +01:00
Muhammad Saad
e6e46838b3 Merge pull request #53 from sasjs/corebump
fix: adding global macvar and bumping sasjs/core with additional server support
2022-02-11 20:28:16 +04:00
munja
404f1ec059 fix: adding global macvar and bumping sasjs/core with additional server support 2022-02-11 17:24:55 +01:00
Yury Shkoda
f2000a1227 chore: fix typos and remove unused code 2022-02-10 09:07:14 +03:00
Yury Shkoda
bf5767eadf feat(stp-execution): add returnLog option to execution query 2022-02-10 09:06:29 +03:00
31 changed files with 6800 additions and 39066 deletions

View File

@@ -2,6 +2,42 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.0.28](https://github.com/sasjs/server/compare/v0.0.27...v0.0.28) (2022-02-16)
### Features
* default macros and bumping core ([6f19d3d](https://github.com/sasjs/server/commit/6f19d3d0ea3815815f246a3e455495c72c8604c7))
### Bug Fixes
* moving core ([f10138b](https://github.com/sasjs/server/commit/f10138b0f2005a958f63cb3a8351e1afa52f086a))
### [0.0.27](https://github.com/sasjs/server/compare/v0.0.26...v0.0.27) (2022-02-16)
### Features
* removing stpsrv_header and updating README with auth details ([d3674c7](https://github.com/sasjs/server/commit/d3674c7f9449d77977e482cd63ccdf7e974fa838))
* **stp-execution:** add returnLog option to execution query ([bf5767e](https://github.com/sasjs/server/commit/bf5767eadfb87f7ed902659347a18361a6a6c74b))
### [0.0.26](https://github.com/sasjs/server/compare/v0.0.25...v0.0.26) (2022-02-14)
### Bug Fixes
* refactored + removed unused package ([d7e1aca](https://github.com/sasjs/server/commit/d7e1aca7e33c3264c784d406fa766e29a6b15ae9))
* release should also has https protocol ([0cfe724](https://github.com/sasjs/server/commit/0cfe724ffa089b84a9f8bca49c9033b56f51c9cb))
* updated token expiry times ([d17a3dd](https://github.com/sasjs/server/commit/d17a3dd5900d5eb88120af8575e3fc7c2cb71ed6))
### [0.0.25](https://github.com/sasjs/server/compare/v0.0.24...v0.0.25) (2022-02-11)
### Bug Fixes
* adding global macvar and bumping sasjs/core with additional server support ([404f1ec](https://github.com/sasjs/server/commit/404f1ec0593a027ed5e84b1d6a84cb9f2d09d99e))
### [0.0.24](https://github.com/sasjs/server/compare/v0.0.23...v0.0.24) (2022-02-11)

View File

@@ -9,18 +9,19 @@ SASjs Server provides a NodeJS wrapper for calling the SAS binary executable. It
One major benefit of using SASjs Server (alongside other components of the SASjs framework such as the [CLI](https://cli.sasjs.io), [Adapter](https://adapter.sasjs.io) and [Core](https://core.sasjs.io) library) is that the projects you create can be very easily ported to SAS 9 (Stored Process server) or Viya (Job Execution server).
SASjs Server is available in two modes - Desktop (without authentication) and Server (with authentiation, and a database)
## Desktop Version
### Manual Installation
Download the relevant package from the [releases](https://github.com/sasjs/server/releases) page
Next, trigger by double clicking (windows) or executing from commandline.
You are presented with two prompts:
* Location of your `sas.exe` / `sas.sh` executable
* Path to a filesystem location for Stored Programs and temporary files
- Location of your `sas.exe` / `sas.sh` executable
- Path to a filesystem location for Stored Programs and temporary files
## Programmatic Installation
@@ -33,7 +34,7 @@ unzip linux.zip
The app can then be launched with `./api-linux` and prompts followed.
When launching the app, it will make use of specific environment variables. These can be set in the following places:
When launching the app, it will make use of specific environment variables. These can be set in the following places:
- Configured globally in /etc/environment file
- Export in terminal or shell script (`export VAR=VALUE`)
@@ -50,26 +51,44 @@ DRIVE_PATH=./tmp
Setting these prompts variables will avoid the need for prompts.
Normally the server process will stop when your terminal dies. To keep it going you can use the npm package [forever](https://www.npmjs.com/package/forever) (`npm i -g forever`) as follows:
Normally the server process will stop when your terminal dies. To keep it going you can use the npm package [pm2](https://www.npmjs.com/package/pm2) (`npm install pm2@latest -g`) as follows:
```bash
export SAS_PATH=/opt/sas9/SASHome/SASFoundation/9.4/sasexe/sas
export PORT=5001
export DRIVE_PATH=./tmp
forever start -c "./api-linux" ./
pm2 start api-linux
```
To get the log files:
To get the logs (and some usefull commands):
```bash
forever list
# grap log file link
tail -f LOGFILE
pm2 [list|ls|status]
pm2 logs
pm2 logs --lines 200
```
To stop:
Managing processes:
```
forever stop <pid>
pm2 restart app_name
pm2 reload app_name
pm2 stop app_name
pm2 delete app_name
```
Instead of `app_name` you can pass:
- `all` to act on all processes
- `id` to act on a specific process id
## Server Version
The following credentials can be used for the initial connection to SASjs/server. It is recommended to change these on first use.
* CLIENTID: `clientID1`
* USERNAME: `secretuser`
* PASSWORD: `secretpassword`

View File

@@ -1,5 +1,8 @@
MODE=[desktop|server] default considered as desktop
CORS=[disable|enable] default considered as disable
PROTOCOL=[http|https] default considered as http
PRIVATE_KEY=privkey.pem
FULL_CHAIN=fullchain.pem
PORT=[5000] default value is 5000
PORT_WEB=[port for sasjs web component(react)] default value is 3000
ACCESS_TOKEN_SECRET=<secret>

8637
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,10 @@
"scripts": {
"initial": "npm run swagger && npm run compileSysInit",
"prestart": "npm run initial",
"prestart:prod": "npm run initial",
"prebuild": "npm run initial",
"start": "nodemon ./src/server.ts",
"start:prod": "nodemon ./src/prod-server.ts",
"build": "rimraf build && tsc",
"swagger": "tsoa spec",
"semantic-release": "semantic-release -d",
"prepare": "[ -d .git ] && git config core.hooksPath ./.git-hooks || true",
"test": "mkdir -p tmp && mkdir -p ../web/build && jest --silent --coverage",
"lint:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
@@ -46,7 +43,7 @@
},
"author": "4GL Ltd",
"dependencies": {
"@sasjs/core": "3.11.1",
"@sasjs/core": "4.9.0",
"@sasjs/utils": "2.34.1",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
@@ -58,7 +55,7 @@
"morgan": "^1.10.0",
"multer": "^1.4.3",
"swagger-ui-express": "^4.1.6",
"tsoa": "^3.14.0"
"tsoa": "3.14.1"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
@@ -79,7 +76,6 @@
"pkg": "^5.4.1",
"prettier": "^2.3.1",
"rimraf": "^3.0.2",
"semantic-release": "^17.4.3",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",
"ts-node": "^10.0.0",

View File

@@ -398,7 +398,7 @@ components:
bearerFormat: JWT
info:
title: api
version: 0.0.1
version: 0.0.2
description: 'Api of SASjs server'
contact:
name: '4GL Ltd'

View File

@@ -4,10 +4,24 @@
@details This program is inserted into every sasjs/server program invocation,
_before_ any user-provided content.
A number of useful CORE macros are also compiled below, so that they can be
available "out of the box".
<h4> SAS Macros </h4>
@li mcf_stpsrv_header.sas
@li mf_getuser.sas
@li mf_getvarlist.sas
@li mf_mkdir.sas
@li mf_nobs.sas
@li mf_uid.sas
@li mfs_httpheader.sas
@li mp_dirlist.sas
@li mp_ds2ddl.sas
@li mp_ds2md.sas
@li mp_getdbml.sas
@li mp_init.sas
@li mp_makedata.sas
@li mp_zip.sas
**/
%mcf_stpsrv_header(wrap=YES, insert_cmplib=YES)

View File

@@ -153,7 +153,7 @@ export class DriveController {
}
const getFileTree = () => {
const tree = new ExecutionController().buildDirectorytree()
const tree = new ExecutionController().buildDirectoryTree()
return { status: 'success', tree }
}

View File

@@ -5,11 +5,15 @@ import { readFile, fileExists, createFile, moveFile } from '@sasjs/utils'
import { PreProgramVars, TreeNode } from '../../types'
import { generateFileUploadSasCode, getTmpFilesFolderPath } from '../../utils'
export interface ExecutionVars {
[key: string]: string | number | undefined
}
export class ExecutionController {
async executeFile(
programPath: string,
preProgramVariables: PreProgramVars,
vars: { [key: string]: string | number | undefined },
vars: ExecutionVars,
otherArgs?: any,
returnJson?: boolean
) {
@@ -29,7 +33,7 @@ export class ExecutionController {
async executeProgram(
program: string,
preProgramVariables: PreProgramVars,
vars: { [key: string]: string | number | undefined },
vars: ExecutionVars,
otherArgs?: any,
returnJson?: boolean
) {
@@ -55,6 +59,7 @@ export class ExecutionController {
`${computed}%let ${key}=${vars[key]};\n`,
''
)
const preProgramVarStatments = `
%let _sasjs_tokenfile=${tokenFile};
%let _sasjs_username=${preProgramVariables?.username};
@@ -66,7 +71,7 @@ export class ExecutionController {
%let _metauser=&_sasjs_username;
%let sasjsprocessmode=Stored Program;
%global SYSPROCESSMODE SYSTCPIPHOSTNAME;
%global SYSPROCESSMODE SYSTCPIPHOSTNAME SYSHOSTINFOLONG;
%macro _sasjs_server_init();
%if "&SYSPROCESSMODE"="" %then %let SYSPROCESSMODE=&sasjsprocessmode;
%if "&SYSTCPIPHOSTNAME"="" %then %let SYSTCPIPHOSTNAME=&_sasjs_apiserverurl;
@@ -138,7 +143,7 @@ ${program}`
: webout
}
buildDirectorytree() {
buildDirectoryTree() {
const root: TreeNode = {
name: 'files',
relativePath: '',

View File

@@ -18,7 +18,7 @@ export class FileUploadController {
//It will intercept request and generate unique uuid to be used as a subfolder name
//that will store the files uploaded
public preuploadMiddleware = async (req: any, res: any, next: any) => {
public preUploadMiddleware = async (req: any, res: any, next: any) => {
let session
const sessionController = getSessionController()

View File

@@ -1,7 +1,7 @@
import express from 'express'
import path from 'path'
import { Request, Security, Route, Tags, Post, Body, Get, Query } from 'tsoa'
import { ExecutionController } from './internal'
import { ExecutionController, ExecutionVars } from './internal'
import { PreProgramVars } from '../types'
import { getTmpFilesFolderPath, makeFilesNamesMap } from '../utils'
@@ -66,7 +66,7 @@ const executeReturnRaw = async (
req: express.Request,
_program: string
): Promise<string> => {
const query = req.query as { [key: string]: string | number | undefined }
const query = req.query as ExecutionVars
const sasCodePath =
path
.join(getTmpFilesFolderPath(), _program)

View File

@@ -1,21 +0,0 @@
import path from 'path'
import { readFileSync } from 'fs'
import * as https from 'https'
import appPromise from './app'
const keyPath = path.join('..', 'certificates', 'privkey.pem')
const certPath = path.join('..', 'certificates', 'fullchain.pem')
const key = readFileSync(keyPath)
const cert = readFileSync(certPath)
appPromise.then((app) => {
const httpsServer = https.createServer({ key, cert }, app)
const sasJsPort = process.env.PORT ?? 5000
httpsServer.listen(sasJsPort, () => {
console.log(
`⚡️[server]: Server is running at https://localhost:${sasJsPort}`
)
})
})

View File

@@ -1,6 +1,5 @@
import express from 'express'
import { SessionController } from '../../controllers'
import { authenticateAccessToken } from '../../middlewares'
const sessionRouter = express.Router()

View File

@@ -1,5 +1,5 @@
import express from 'express'
import { executeProgramRawValidation, runSASValidation } from '../../utils'
import { executeProgramRawValidation } from '../../utils'
import { STPController } from '../../controllers/'
import { FileUploadController } from '../../controllers/internal'
@@ -26,7 +26,7 @@ stpRouter.get('/execute', async (req, res) => {
stpRouter.post(
'/execute',
fileUploadController.preuploadMiddleware,
fileUploadController.preUploadMiddleware,
fileUploadController.getMulterUploadObject().any(),
async (req: any, res: any) => {
const { error: errQ, value: query } = executeProgramRawValidation(req.query)

View File

@@ -1,10 +1,26 @@
import appPromise from './app'
import { createServer } from 'https'
appPromise.then((app) => {
import appPromise from './app'
import { getCertificates } from './utils'
appPromise.then(async (app) => {
const protocol = process.env.PROTOCOL ?? 'http'
const sasJsPort = process.env.PORT ?? 5000
app.listen(sasJsPort, () => {
console.log(
`⚡️[server]: Server is running at http://localhost:${sasJsPort}`
)
})
if (protocol !== 'https') {
app.listen(sasJsPort, () => {
console.log(
`⚡️[server]: Server is running at http://localhost:${sasJsPort}`
)
})
} else {
const { key, cert } = await getCertificates()
const httpsServer = createServer({ key, cert }, app)
httpsServer.listen(sasJsPort, () => {
console.log(
`⚡️[server]: Server is running at https://localhost:${sasJsPort}`
)
})
}
})

View File

@@ -3,5 +3,5 @@ import { InfoJWT } from '../types'
export const generateAccessToken = (data: InfoJWT) =>
jwt.sign(data, process.env.ACCESS_TOKEN_SECRET as string, {
expiresIn: '1h'
expiresIn: '1day'
})

View File

@@ -3,5 +3,5 @@ import { InfoJWT } from '../types'
export const generateRefreshToken = (data: InfoJWT) =>
jwt.sign(data, process.env.REFRESH_TOKEN_SECRET as string, {
expiresIn: '1day'
expiresIn: '30 days'
})

View File

@@ -0,0 +1,33 @@
import path from 'path'
import { fileExists, getString, readFile } from '@sasjs/utils'
export const getCertificates = async () => {
const { PRIVATE_KEY, FULL_CHAIN } = process.env
const keyPath = PRIVATE_KEY ?? (await getFileInput('Private Key (PEM)'))
const certPath = FULL_CHAIN ?? (await getFileInput('Full Chain (PEM)'))
const key = await readFile(keyPath)
const cert = await readFile(certPath)
return { key, cert }
}
const getFileInput = async (filename: string): Promise<string> => {
const validator = async (filePath: string) => {
if (!filePath) return `Path to ${filename} is required.`
if (!(await fileExists(path.join(process.cwd(), filePath)))) {
return 'No file found at provided path.'
}
return true
}
const targetName = await getString(
`Please enter path to ${filename} (relative path): `,
validator
)
return targetName
}

View File

@@ -3,6 +3,7 @@ export * from './file'
export * from './generateAccessToken'
export * from './generateAuthCode'
export * from './generateRefreshToken'
export * from './getCertificates'
export * from './getDesktopFields'
export * from './removeTokensInDB'
export * from './saveTokensInDB'

1567
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
{
"name": "server",
"version": "0.0.24",
"version": "0.0.28",
"description": "NodeJS wrapper for calling the SAS binary executable",
"repository": "https://github.com/sasjs/server",
"scripts": {
"server": "npm run server:prepare && npm run server:start",
"server:prepare": "cd web && npm ci && npm run build && cd ../api && npm ci && cd ..",
"server:start": "cd api && npm run start:prod",
"server:start": "cd api && npm run start",
"release": "standard-version",
"lint-api:fix": "npx prettier --write \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",
"lint-api": "npx prettier --check \"api/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"",

4
web/.babelrc Normal file
View File

@@ -0,0 +1,4 @@
{
"presets": ["@babel/env", "@babel/react", "@babel/preset-typescript"],
"plugins": ["@babel/plugin-proposal-class-properties"]
}

View File

@@ -1,2 +1,2 @@
REACT_APP_PORT_API=[place sasjs server port] default value is 5000
REACT_APP_CLIENT_ID=<place clientId here>
PORT_API=[place sasjs server port] default value is 5000
CLIENT_ID=<place clientId here>

35256
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,8 @@
"version": "0.1.0",
"private": true,
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"start": "npx webpack-dev-server --config webpack.dev.ts --hot",
"build": "npx webpack --config webpack.prod.ts"
},
"dependencies": {
"@emotion/react": "^11.4.1",
@@ -22,19 +20,43 @@
"@types/jest": "^26.0.24",
"@types/node": "^12.20.28",
"@types/react": "^17.0.27",
"@types/react-dom": "^17.0.9",
"axios": "^0.22.0",
"axios": "^0.24.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "^5.3.0",
"react-scripts": "4.0.3",
"typescript": "^4.4.3"
"react-router-dom": "^5.3.0"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/node": "^7.16.0",
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@babel/preset-env": "^7.16.4",
"@babel/preset-react": "^7.16.0",
"@babel/preset-typescript": "^7.16.0",
"@types/dotenv-webpack": "^7.0.3",
"@types/prismjs": "^1.16.6",
"@types/react": "^17.0.37",
"@types/react-dom": "^17.0.11",
"@types/react-router-dom": "^5.3.1",
"babel-loader": "^8.2.3",
"babel-plugin-prismjs": "^2.1.0",
"prettier": "^2.4.1"
"copy-webpack-plugin": "^10.0.0",
"css-loader": "^6.5.1",
"dotenv-webpack": "^7.1.0",
"eslint": "^8.5.0",
"eslint-config-react-app": "^7.0.0",
"eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "5.5.0",
"path": "0.12.7",
"prettier": "^2.4.1",
"sass": "^1.44.0",
"sass-loader": "^12.3.0",
"style-loader": "^3.3.1",
"ts-loader": "^9.2.6",
"typescript": "^4.5.2",
"webpack": "5.64.3",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "4.7.4"
},
"eslintConfig": {
"extends": [

View File

@@ -8,11 +8,10 @@ const headers = {
Accept: 'application/json',
'Content-Type': 'application/json'
}
const { NODE_ENV, REACT_APP_PORT_API } = process.env
const NODE_ENV = process.env.NODE_ENV
const PORT_API = process.env.PORT_API
const baseUrl =
NODE_ENV === 'development'
? `http://localhost:${REACT_APP_PORT_API ?? 5000}`
: ''
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
const getAuthCode = async (credentials: any) => {
return fetch(`${baseUrl}/SASjsApi/auth/authorize`, {
@@ -46,7 +45,7 @@ const Login = ({ setTokens, getCodeOnly }: any) => {
error = false
setErrorMessage('')
e.preventDefault()
let { REACT_APP_CLIENT_ID: clientId } = process.env
let clientId = process.env.CLIENT_ID
if (getCodeOnly) {
const params = new URLSearchParams(location.search)

View File

@@ -36,11 +36,10 @@ export default function useTokens() {
}
}
const { NODE_ENV, REACT_APP_PORT_API } = process.env
const NODE_ENV = process.env.NODE_ENV
const PORT_API = process.env.PORT_API
const baseUrl =
NODE_ENV === 'development'
? `http://localhost:${REACT_APP_PORT_API ?? 5000}`
: ''
NODE_ENV === 'development' ? `http://localhost:${PORT_API ?? 5000}` : ''
const isAbsoluteURLRegex = /^(?:\w+:)\/\//

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="icon" href="favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="SASjs Server Web Interface" />
@@ -10,7 +10,7 @@
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="manifest" href="manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.

60
web/webpack.common.ts Normal file
View File

@@ -0,0 +1,60 @@
import path from 'path'
import { Configuration } from 'webpack'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import CopyPlugin from 'copy-webpack-plugin'
import dotenv from 'dotenv-webpack'
const config: Configuration = {
entry: path.join(__dirname, 'src', 'index.tsx'),
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader',
options: {
compilerOptions: {
noEmit: false
}
}
}
]
},
{
test: /\.css$/,
exclude: ['/node_modules/'],
use: ['style-loader', 'css-loader']
},
{
test: /\.scss$/,
exclude: ['/node_modules/'],
use: ['style-loader', 'css-loader', 'sass-loader']
},
{
test: /\.(jpg|jpeg|png|gif|mp3|svg)$/,
use: ['file-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'index.html')
}),
new CopyPlugin({
patterns: [{ from: 'public' }]
}),
new dotenv()
]
}
export default config

28
web/webpack.dev.ts Normal file
View File

@@ -0,0 +1,28 @@
import path from 'path'
import { Configuration as WebpackConfiguration } from 'webpack'
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server'
import { merge } from 'webpack-merge'
import common from './webpack.common'
interface Configuration extends WebpackConfiguration {
devServer?: WebpackDevServerConfiguration
}
const devConfig: Configuration = merge(common, {
mode: 'development',
output: {
path: path.join(__dirname, 'build'),
filename: 'index.bundle.js',
publicPath: '/'
},
devServer: {
static: {
directory: path.join(__dirname, 'build')
},
historyApiFallback: true,
port: 3000
}
})
export default devConfig

19
web/webpack.prod.ts Normal file
View File

@@ -0,0 +1,19 @@
import path from 'path'
import { Configuration } from 'webpack'
import { merge } from 'webpack-merge'
import common from './webpack.common'
const prodConfig: Configuration = merge(common, {
mode: 'production',
output: {
path: path.join(__dirname, 'build'),
filename: 'index.bundle.js',
publicPath: './'
},
performance: {
hints: false
}
})
export default prodConfig