diff --git a/.gitignore b/.gitignore index 65221e93f..ccbc71287 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ dist lib +.nx + # Logs logs *.log @@ -49,6 +51,8 @@ node_modules .node_repl_history .dist +# ignore temp created for pushing prod changes to devleop +lerna-temp.json # IDE .idea *.DS_Store diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 000000000..d0a778429 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 4b410d508..6c49dc1c8 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,23 +13,24 @@ pipeline { string(name: 'CONNECTION_ID', defaultValue: 'test', description: 'connection id', trim: true) string(name: 'WORKSPACE_ID', defaultValue: 'fullstack-pro', description: 'workspace id', trim: true) string(name: 'UNIQUE_NAME', defaultValue: 'default', description: 'chart name', trim: true) - string(name: 'VERSION', defaultValue: 'v1', description: 'version of the deployment', trim: true) + string(name: 'VERSION', defaultValue: 'v3', description: 'version of the deployment', trim: true) string(name: 'HEMERA_LOG_LEVEL', defaultValue: 'info', description: 'log level for hemera') string(name: 'LOG_LEVEL', defaultValue: 'info', description: 'log level') string(name: 'DEPLOYMENT_PATH', defaultValue: '/servers', description: 'folder path to load helm charts') - string(name: 'PUBLISH_BRANCH', defaultValue: 'devpublish', description: 'publish branch') + string(name: 'PUBLISH_BRANCH', defaultValue: 'devpublish3', description: 'the publish branch for packages release') string(name: 'EXCLUDE_SETTING_NAMESPACE_FILTER', defaultValue: 'brigade', description: 'exclude setting namespace that matches search string') string(name: 'GIT_CREDENTIAL_ID', defaultValue: 'fullstack-pro-github-deploy-key', description: 'jenkins credential id of git deploy secret') + string(name: 'BUILD_MODULE_TO_INCLUDE', defaultValue: '@sample-stack*', description: 'build env') string(name: 'REPOSITORY_SSH_URL', defaultValue: 'git@github.com:CDEBase/fullstack-pro.git', description: 'ssh url of the git repository') - string(name: 'REPOSITORY_BRANCH', defaultValue: 'develop', description: 'the branch of repository') - string(name: 'DEVELOP_BRANCH', defaultValue: 'develop', description: 'Develop branch as default for the development.') + string(name: 'REPOSITORY_BRANCH', defaultValue: 'develop3', description: 'the branch with changes') + string(name: 'DEVELOP_BRANCH', defaultValue: 'develop3', description: 'the branch for the development') string(name: 'MASTER_BRANCH', defaultValue: 'master', description: 'Master branch as default branch for production.') // by default first value of the choice will be choosen choice choices: ['auto', 'force'], description: 'Choose merge strategy', name: 'NPM_PUBLISH_STRATEGY' choice choices: ['yarn', 'npm'], description: 'Choose build strategy', name: 'BUILD_STRATEGY' choice choices: ['0.7.9','0.7.7', '0.6.0'], description: 'Choose Idestack chart version', name: 'IDESTACK_CHART_VERSION' - choice choices: ['nodejs16', 'nodejs14'], description: 'Choose NodeJS version', name: 'NODEJS_TOOL_VERSION' + choice choices: ['nodejs20', 'nodejs18', 'nodejs22'], description: 'Choose NodeJS version', name: 'NODEJS_TOOL_VERSION' choice choices: ['buildOnly', 'buildAndTest', 'buildAndPublish', 'mobileBuild', 'mobilePreview', 'mobilePreviewLocal', 'mobilePreviewSubmit', 'mobileProd', 'mobileProdSubmit', 'devDeployOnly', 'stageDeploy', 'stageDeployOnly', 'prodDeploy', 'prodDeployOnly', 'allenv'], description: 'Where to deploy micro services?', name: 'ENV_CHOICE' choice choices: ['all', 'ios', 'android' ], description: 'Mobile type if it is mobile build?', name: 'MOBILE_CHOICE' booleanParam (defaultValue: false, description: 'Skip production release approval', name: 'SKIP_RELEASE_APPROVAL') @@ -41,7 +42,7 @@ pipeline { environment { BUILD_COMMAND = getBuildCommand() NAMESPACE = "${params.BASE_NAMESPACE}-${params.VERSION}" - PYTHON='/usr/bin/python' + PYTHON = '/usr/bin/python' GCR_KEY = credentials('jenkins-gcr-login-key') EXPO_TOKEN = credentials('expo_cdmbase_token') GIT_PR_BRANCH_NAME = getGitPrBranchName() @@ -54,69 +55,65 @@ pipeline { } stages { - stage('define environment') { steps { - // skip the build if ends with `[skip ci]` which is equivalent to regex `.*\[skip ci\]$` + // skip the build if ends with `[skip ci]` which is equivalent to regex `.*\[skip ci\\]\\s` scmSkip(deleteBuild: true, skipPattern:'.*\\[skip ci\\]\\s') - checkout([$class: 'GitSCM', branches: [[name: '*/'+ params.REPOSITORY_BRANCH]], - doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'WipeWorkspace']], - submoduleCfg: [], userRemoteConfigs: [[credentialsId: params.GIT_CREDENTIAL_ID, url: params.REPOSITORY_SSH_URL]]]) + checkout([$class: 'GitSCM', branches: [[name: '*/' + params.REPOSITORY_BRANCH]], + doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'WipeWorkspace']], + submoduleCfg: [], userRemoteConfigs: [[credentialsId: params.GIT_CREDENTIAL_ID, url: params.REPOSITORY_SSH_URL]]]) sh "git checkout ${env.GIT_PR_BRANCH_NAME}" } } - stage('Unlock secrets'){ //unlock keys for all runs - environment{ deployment_env = 'dev' } - steps{ + stage('Unlock secrets') { //unlock keys for all runs + environment { deployment_env = 'dev' } + steps { sh ''' - gpg --import /tmp/gpg-public-key/gpg-public-key.pub - gpg --import /tmp/gpg-private-key/gpg-private-key.key - git-crypt unlock + gpg --import /tmp/gpg-public-key/gpg-public-key.pub + gpg --import /tmp/gpg-private-key/gpg-private-key.key + git-crypt unlock ''' load "./jenkins_variables.groovy" - // if we need to load stag configuration for different location. - // sh "curl -H 'Authorization: token ${env.GITHUB_ACCESS_TOKEN}' -H 'Accept: application/vnd.github.v3.raw' -O -L https://raw.githubusercontent.com/cdmbase/kube-orchestration/master/idestack/values-stage.yaml" } } // Install packages. If // a. any branch // b. ENV_CHOICE set not selected `dev`, `stage` or `prod` - stage ('Install git repository'){ - steps{ - sh """ - echo "what is docker git version $GIT_BRANCH_NAME -- ${params.ENV_CHOICE}" - ${params.BUILD_STRATEGY} install - ${params.BUILD_STRATEGY} run lerna - """ - } + stage('Install git repository') { + steps { + sh """ + echo "what is docker git version $GIT_BRANCH_NAME -- ${params.ENV_CHOICE}" + ${params.BUILD_STRATEGY} install + """ + } } - stage ('Mobile Build'){ + stage('Mobile Build') { when { expression { params.ENV_CHOICE == 'mobileBuild' || params.ENV_CHOICE == 'mobilePreview' || params.ENV_CHOICE == 'mobilePreviewLocal' || params.ENV_CHOICE == 'mobilePreviewSubmit' || params.ENV_CHOICE == 'mobileProd' || params.ENV_CHOICE == 'mobileProdSubmit' } } - steps{ - sshagent (credentials: [params.GIT_CREDENTIAL_ID]) { + steps { + sshagent(credentials: [params.GIT_CREDENTIAL_ID]) { sh """ - rm .npmrc - lerna exec --scope=*mobile-device ${params.BUILD_STRATEGY} ${env.BUILD_COMMAND} - git checkout -- .npmrc - yarn gitcommit - git pull origin ${params.REPOSITORY_BRANCH} - git push origin ${params.REPOSITORY_BRANCH} + rm .npmrc + npx lerna exec --scope=*mobile-device ${params.BUILD_STRATEGY} ${env.BUILD_COMMAND} + git checkout -- .npmrc + yarn gitcommit + git pull origin ${params.REPOSITORY_BRANCH} + git push origin ${params.REPOSITORY_BRANCH} """ } } } // Run build for all cases except when ENV_CHOICE is 'buildAndPublish' and `dev`, `stage` or `prod` - stage ('Build Packages'){ + stage('Build Packages') { when { expression { params.ENV_CHOICE == 'buildOnly' || params.ENV_CHOICE == 'buildAndTest' || params.ENV_CHOICE == 'buildAndPublish' } } - steps{ + steps { sh """ ${params.BUILD_STRATEGY} run build """ @@ -124,11 +121,11 @@ pipeline { } // Test build for all cases except when ENV_CHOICE is 'buildAndPublish' and `dev`, `stage` or `prod` - stage ('Test Packages'){ + stage('Test Packages') { when { expression { params.ENV_CHOICE == 'buildAndTest' } } - steps{ + steps { sh """ ${params.BUILD_STRATEGY} run test """ @@ -137,17 +134,16 @@ pipeline { // if PR is from branch other than `develop` then merge to `develop` if we chose ENV_CHOICE as 'buildAndPublish'. // Skip this stage. Future implementation. - stage ('Merge PR, Install, Build'){ + stage('Merge PR, Install, Build') { when { expression { params.ENV_CHOICE == '1' } } - steps{ + steps { sh """ git checkout ${params.DEVELOP_BRANCH} git merge ${env.GIT_PR_BRANCH_NAME} -m 'auto merging ${params.GIT_PR_BRANCH_NAME} \r\n[skip ci]' git push origin ${params.DEVELOP_BRANCH} ${params.BUILD_STRATEGY} install - ${params.BUILD_STRATEGY} run lerna ${params.BUILD_STRATEGY} run build """ script { @@ -159,16 +155,16 @@ pipeline { // publish packages to npm repository. // commit new package-lock.json that might get generated during install // Build will be ignore with tag '[skip ci]' - stage ('Publish Packages'){ + stage('Publish Packages') { when { expression { GIT_BRANCH_NAME == params.DEVELOP_BRANCH } - expression { params.ENV_CHOICE == 'buildOnly' || params.ENV_CHOICE == 'buildAndPublish' } + expression { params.ENV_CHOICE == 'buildOnly' || params.ENV_CHOICE == 'buildAndPublish' } } - steps{ + steps { script { - GIT_BRANCH_NAME=params.PUBLISH_BRANCH + GIT_BRANCH_NAME = params.PUBLISH_BRANCH } - sshagent (credentials: [params.GIT_CREDENTIAL_ID]) { + sshagent(credentials: [params.GIT_CREDENTIAL_ID]) { sh """ git add -A git diff --staged --quiet || git commit -am 'auto build [skip ci] \r\n' @@ -182,16 +178,16 @@ pipeline { } } - stage('Docker login'){ - steps{ + stage('Docker login') { + steps { sh 'cat "$GCR_KEY" | docker login -u _json_key --password-stdin https://gcr.io' } } stage('Dev Docker Images') { options { - timeout(time: params.BUILD_TIME_OUT, unit: 'MINUTES') - } + timeout(time: params.BUILD_TIME_OUT, unit: 'MINUTES') + } when { // Docker build need be performed in PUBLISH branch only expression { GIT_BRANCH_NAME == params.PUBLISH_BRANCH } @@ -199,46 +195,67 @@ pipeline { } // Below variable is only set to load all (variables, functions) from jenkins_variables.groovy file. - environment{ deployment_env = 'dev' } - steps{ - load "./jenkins_variables.groovy" - script { - def servers = getDirs(pwd() + params.DEPLOYMENT_PATH) - def parallelStagesMap = servers.collectEntries { - ["${it}" : generateBuildStage(it)] + environment { + deployment_env = 'dev' + BUILD_MODULE_TO_INCLUDE = "${params.BUILD_MODULE_TO_INCLUDE}" + } + steps { + load "./jenkins_variables.groovy" + script { + def servers = getDirs(pwd() + params.DEPLOYMENT_PATH) + def frontendProjects = servers.findAll { it.startsWith('frontend-') } + def otherProjects = servers - frontendProjects + + // Create parallel stages for non-frontend projects + def parallelStagesMap = otherProjects.collectEntries { + ["${it}" : generateBuildStage(it)] + } + + // Run the first frontend project in parallel with others + if (frontendProjects.size() > 0) { + parallelStagesMap["${frontendProjects[0]}"] = generateBuildStage(frontendProjects[0]) + frontendProjects.remove(0) + } + + // Run non-frontend projects in parallel + parallel parallelStagesMap + + // Run remaining frontend projects sequentially + frontendProjects.each { + stage("${it}") { + runBuildStage(it) } - parallel parallelStagesMap } } + } } // Below are dev stages stage('Dev deployment') { - environment{ - deployment_env = 'dev' + environment { + deployment_env = 'dev' } when { - expression { GIT_BRANCH_NAME == params.PUBLISH_BRANCH || GIT_BRANCH_NAME == params.DEVELOP_BRANCH } + expression { GIT_BRANCH_NAME == params.PUBLISH_BRANCH || GIT_BRANCH_NAME == params.DEVELOP_BRANCH } expression { params.ENV_CHOICE == 'buildOnly' || params.ENV_CHOICE == 'devDeployOnly' } beforeInput true } steps { - withKubeConfig([credentialsId: 'kubernetes-dev-cluster-r1', serverUrl: "https://34.74.64.165"]) { - sh """ + withKubeConfig([credentialsId: 'kubernetes-dev-cluster-r1', serverUrl: "https://34.74.64.165"]) { + sh """ helm repo add stable https://charts.helm.sh/stable helm repo add incubator https://charts.helm.sh/incubator helm repo add kube-orchestration https://"""+ GITHUB_HELM_REPO_TOKEN +"""@raw.githubusercontent.com/cdmbase/kube-orchestration/develop/helm-packages helm repo update - """ + """ script { - nameSpaceCheck = sh(script: "kubectl get ns | tr '\\n' ','", returnStdout: true) if (!nameSpaceCheck.contains(env.NAMESPACE)) { sh "kubectl create ns " + env.NAMESPACE } def servers = getDirs(pwd() + params.DEPLOYMENT_PATH) def parallelStagesMap = servers.collectEntries { - ["${it}" : generateStage(it, deployment_env)] + ["${it}" : generateStage(it, deployment_env)] } parallel parallelStagesMap } @@ -247,33 +264,32 @@ pipeline { } // End of dev deployment code block. // Only master branch will be merged - stage ('Merge Develop to master & Install'){ + stage('Merge Develop to master & Install') { when { expression { GIT_BRANCH_NAME == params.MASTER_BRANCH } expression { params.ENV_CHOICE == 'stageDeploy' || params.ENV_CHOICE == 'prodDeploy' } } - steps{ + steps { sh """ git add -A git diff --staged --quiet || git commit -am 'pre merge to master \r\n[skip ci]' git checkout ${params.REPOSITORY_BRANCH} git merge origin/${params.DEVELOP_BRANCH} -m 'auto merging ${params.DEVELOP_BRANCH} \r\n[skip ci]' ${params.BUILD_STRATEGY} install - ${params.BUILD_STRATEGY} run lerna """ script { GIT_BRANCH_NAME = params.REPOSITORY_BRANCH } } } - + // Run build for all cases except when ENV_CHOICE is 'buildAndPublish' and `dev`, `stage` or `prod` - stage ('Prod Build Packages'){ + stage('Prod Build Packages') { when { expression { GIT_BRANCH_NAME == params.MASTER_BRANCH } expression { params.ENV_CHOICE == 'stageDeploy' || params.ENV_CHOICE == 'prodDeploy' } } - steps{ + steps { sh """ ${params.BUILD_STRATEGY} run build """ @@ -283,16 +299,16 @@ pipeline { // publish packages to npm repository. // commit new package-lock.json that might get generated during install // Build will be ignore with tag '[skip ci]' - stage ('Prod Publish Packages'){ + stage('Prod Publish Packages') { when { expression { GIT_BRANCH_NAME == params.MASTER_BRANCH } expression { params.ENV_CHOICE == 'stageDeploy' || params.ENV_CHOICE == 'prodDeploy' } } - steps{ + steps { script { - GIT_BRANCH_NAME=params.PUBLISH_BRANCH + GIT_BRANCH_NAME = params.PUBLISH_BRANCH } - sshagent (credentials: [params.GIT_CREDENTIAL_ID]) { + sshagent(credentials: [params.GIT_CREDENTIAL_ID]) { sh """ git add -A git diff --staged --quiet || git commit -am 'auto build [skip ci]\r\n' @@ -305,12 +321,12 @@ pipeline { } } } - + // Build Docker containers for production. stage('Prod Docker Images') { options { - timeout(time: params.BUILD_TIME_OUT, unit: 'MINUTES') - } + timeout(time: params.BUILD_TIME_OUT, unit: 'MINUTES') + } when { // required to be in Publish branch to build docker expression { GIT_BRANCH_NAME == params.PUBLISH_BRANCH } @@ -318,25 +334,46 @@ pipeline { } // Below variable is only set to load all (variables, functions) from jenkins_variables.groovy file. - environment{ deployment_env = 'prod' } - steps{ - load "./jenkins_variables.groovy" - script { - def servers = getDirs(pwd() + params.DEPLOYMENT_PATH) - def parallelStagesMap = servers.collectEntries { - ["${it}" : generateBuildStage(it)] + environment { deployment_env = 'prod' } + steps { + load "./jenkins_variables.groovy" + script { + def servers = getDirs(pwd() + params.DEPLOYMENT_PATH) + def frontendProjects = servers.findAll { it.startsWith('frontend-') } + def otherProjects = servers - frontendProjects + + // Create parallel stages for non-frontend projects + def parallelStagesMap = otherProjects.collectEntries { + ["${it}" : generateBuildStage(it)] + } + + // First frontend project + if (frontendProjects) { + parallelStagesMap["${frontendProjects[0]}"] = generateBuildStage(frontendProjects[0]) + frontendProjects.remove(0) + } + + // Run non-frontend projects in parallel + parallel parallelStagesMap + + // Run remaining frontend projects sequentially + frontendProjects.each { + stage("${it}") { + steps { + runBuildStage(it) + } } - parallel parallelStagesMap } } + } } // End of production docker build. // Below are stage code block stage('Stage Deployment') { options { - timeout(time: 300, unit: 'SECONDS') - } - environment{ + timeout(time: 300, unit: 'SECONDS') + } + environment { deployment_env = 'stage' } when { @@ -348,7 +385,6 @@ pipeline { steps { load "./jenkins_variables.groovy" withKubeConfig([credentialsId: 'kubernetes-staging-cluster', serverUrl: 'https://34.139.244.149']) { - sh """ helm repo add stable https://charts.helm.sh/stable helm repo add incubator https://charts.helm.sh/incubator @@ -376,38 +412,25 @@ pipeline { expression { params.SKIP_RELEASE_APPROVAL == false } } options { - // Optionally, let's add a timeout that we don't allow ancient - // builds to be released. - timeout time: 900, unit: 'SECONDS' + timeout time: 900, unit: 'SECONDS' } steps { - // Optionally, send some notifications to the approver before - // asking for input. You can't do that with the input directive - // without using an extra stage. slackSend (color: '#2596BE', message: "Approval Needed for Production Release: Job '${env.JOB_NAME}' BUILD NUMBER: '${env.BUILD_NUMBER}' to be approved. Click <${env.RUN_DISPLAY_URL}|here> to approve it.", channel: 'idestack-automation') - // The input statement has to go to a script block because we - // want to assign the result to an environment variable. As we - // want to stay as declarative as possible, we put noting but - // this into the script block. script { - // Assign the 'DO_RELEASE' environment variable that is going - // to be used in the next stage. - env.DO_RELEASE = input message: 'Want to deploy fullstack-pro on prod cluster?', - parameters:[choice(choices: ['yes', 'no'], description: 'Deploy branch in Production?', name: 'PROD_DEPLOYMENT')] + env.DO_RELEASE = input message: 'Want to deploy fullstack-pro on prod cluster?', + parameters: [choice(choices: ['yes', 'no'], description: 'Deploy branch in Production?', name: 'PROD_DEPLOYMENT')] } - // In case you approved multiple pipeline runs in parallel, this - // milestone would kill the older runs and prevent deploying - // older releases over newer ones. milestone 1 } } + // Below are production stages stage('Prod Deployment') { options { timeout(time: 300, unit: 'SECONDS') } - environment{ + environment { deployment_env = 'prod' } when { @@ -418,17 +441,8 @@ pipeline { } steps { - // Make sure that only one release can happen at a time. lock('release') { - // As using the first milestone only would introduce a race - // condition (assume that the older build would enter the - // milestone first, but the lock second) and Jenkins does - // not support inter-stage locks yet, we need a second - // milestone to make sure that older builds don't overwrite - // newer ones. milestone 2 - - // Now do the actual work here load "./jenkins_variables.groovy" withKubeConfig([credentialsId: 'kubernetes-prod-cluster-r1', serverUrl: 'https://35.229.71.215']) { sh """ @@ -440,7 +454,7 @@ pipeline { script { nameSpaceCheck = sh(script: "kubectl get ns | tr '\\n' ','", returnStdout: true) if (!nameSpaceCheck.contains(env.NAMESPACE)) { sh "kubectl create ns " + env.NAMESPACE } - + def servers = getDirs(pwd() + params.DEPLOYMENT_PATH) def parallelStagesMap = servers.collectEntries { ["${it}" : generateStage(it, deployment_env)] @@ -459,35 +473,35 @@ pipeline { always { deleteDir() } - success{ + success { slackSend (color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME}' BUILD NUMBER: '${env.BUILD_NUMBER}' Job success. click <${env.RUN_DISPLAY_URL}|here> to see the log.", channel: 'idestack-automation') } - failure{ + failure { slackSend (color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME}' BUILD NUMBER: '${env.BUILD_NUMBER}' Job failed. click <${env.RUN_DISPLAY_URL}|here> to see the log.", channel: 'idestack-automation') } } } -def getBuildCommand(){ - if(params.ENV_CHOICE == 'mobileBuild'){ +def getBuildCommand() { + if (params.ENV_CHOICE == 'mobileBuild') { return 'build:auto' } - if(params.ENV_CHOICE == 'mobilePreview'){ + if (params.ENV_CHOICE == 'mobilePreview') { return 'build:preview:' + params.MOBILE_CHOICE } - if(params.ENV_CHOICE == 'mobilePreviewLocal'){ + if (params.ENV_CHOICE == 'mobilePreviewLocal') { return 'build:previewLocal:' + params.MOBILE_CHOICE } - if(params.ENV_CHOICE == 'mobilePreviewSubmit'){ + if (params.ENV_CHOICE == 'mobilePreviewSubmit') { return 'build:previewSubmit:' + params.MOBILE_CHOICE } - if(params.ENV_CHOICE == 'mobileProd'){ + if (params.ENV_CHOICE == 'mobileProd') { return 'build:prod:' + params.MOBILE_CHOICE } - if(params.ENV_CHOICE == 'mobileProdSubmit'){ + if (params.ENV_CHOICE == 'mobileProdSubmit') { return 'build:prodSubmit:' + params.MOBILE_CHOICE } - if(params.ENABLE_DEBUG.toBoolean()){ + if (params.ENABLE_DEBUG.toBoolean()) { return 'build:debug' } else { return 'build' @@ -495,18 +509,15 @@ def getBuildCommand(){ } def getGitPrBranchName() { - // The branch name could be in the BRANCH_NAME or GIT_BRANCH variable depending on the type of job - //def branchName = env.BRANCH_NAME ? env.BRANCH_NAME : env.GIT_BRANCH - //return branchName || ghprbSourceBranch - if(env.ghprbSourceBranch){ + if (env.ghprbSourceBranch) { return env.ghprbSourceBranch } else { return params.REPOSITORY_BRANCH } } -def getGitBranchName(){ // we can place some conditions in future - if(env.ghprbSourceBranch){ +def getGitBranchName() { + if (env.ghprbSourceBranch) { return env.ghprbSourceBranch } else { return params.REPOSITORY_BRANCH @@ -515,23 +526,23 @@ def getGitBranchName(){ // we can place some conditions in future @NonCPS //TODO: Fix below get method for Jenkins slave if possible. -def getDirs1(path){ +def getDirs1(path) { def currentDir = new File(path) def dirs = [] currentDir.eachDir() { - dirs << it.name + dirs << it.name } return dirs } // Below function to work in Jenkins slave -def getDirs(path){ - def currentDir = sh(script: "ls -CF "+path+" | tr '/' ' '", returnStdout: true) - def dirs = [] - (currentDir.split()).each { - dirs << "${it}" - } - return dirs +def getDirs(path) { + def currentDir = sh(script: "ls -CF " + path + " | tr '/' ' '", returnStdout: true) + def dirs = [] + (currentDir.split()).each { + dirs << "${it}" + } + return dirs } def generateStage(server, environmentType) { @@ -544,15 +555,15 @@ def generateStage(server, environmentType) { def version = getVersion(pwd() + params.DEPLOYMENT_PATH + "/${server}/package.json") def valuesFile = "values-${environmentType}.yaml" // deploy anything matching `*backend-server` or `*frontend-server` to use idestack chart - try{ + try { if ("${server}".endsWith("backend-server") | "${server}".endsWith("frontend-server")) { echo "add deployment flag to - ${server} " - if ("${server}".endsWith("frontend-server")){ + if ("${server}".endsWith("frontend-server")) { deployment_flag = " --set backend.enabled='false' --set external.enabled='true'" } - if ("${server}".endsWith("backend-server")){ + if ("${server}".endsWith("backend-server")) { deployment_flag = " --set frontend.enabled='false' --set external.enabled='false' --set ingress.enabled=false " } @@ -560,7 +571,7 @@ def generateStage(server, environmentType) { helm upgrade -i \ ${server} \ -f "${valuesFile}" \ - ${namespace}\ + ${namespace} \ ${deployment_flag} \ --set frontend.image="${REPOSITORY_SERVER}/${name}" \ --set frontend.imageTag=${version} \ @@ -572,12 +583,11 @@ def generateStage(server, environmentType) { --set VERSION=${VERSION} \ --version=${IDESTACK_CHART_VERSION} \ kube-orchestration/idestack - """ - + """ } else { sh """ cd .${params.DEPLOYMENT_PATH}/${server} - helm dependency update charts/chart/ + helm dependency update charts/chart/ helm upgrade -i \ ${server}-api \ -f "charts/chart/${valuesFile}" \ @@ -587,7 +597,6 @@ def generateStage(server, environmentType) { --set VERSION=${VERSION} \ charts/chart """ - } } catch (Exception err) { slackSend (color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME}' BUILD NUMBER: '${env.BUILD_NUMBER}' Job failed in stage deployment ${server}. click <${env.RUN_DISPLAY_URL}|here> to see the log. Error: ${err.toString()}", channel: 'idestack-automation') @@ -602,35 +611,59 @@ def generateStage(server, environmentType) { def generateBuildStage(server) { return { stage("stage: ${server}") { - try{ - echo "This is ${server}." - def name = getName(pwd() + params.DEPLOYMENT_PATH + "/${server}/package.json") - def version = getVersion(pwd() + params.DEPLOYMENT_PATH + "/${server}/package.json") - sh """ - lerna exec --scope=*${server} ${params.BUILD_STRATEGY} run docker:${env.BUILD_COMMAND}; - docker tag ${name}:${version} ${REPOSITORY_SERVER}/${name}:${version} - docker push ${REPOSITORY_SERVER}/${name}:${version} - docker rmi ${REPOSITORY_SERVER}/${name}:${version} - """ + try { + echo "This is ${server}." + def name = getName(pwd() + params.DEPLOYMENT_PATH + "/${server}/package.json") + def version = getVersion(pwd() + params.DEPLOYMENT_PATH + "/${server}/package.json") + + buildAndPushDockerImage(server, name, version) } catch (e) { - slackSend (color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME}' BUILD NUMBER: '${env.BUILD_NUMBER}' Job failed in stage docker-build ${server}. click <${env.RUN_DISPLAY_URL}|here> to see the log. Error: ${e}", channel: 'idestack-automation') + slackSend(color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME}' BUILD NUMBER: '${env.BUILD_NUMBER}' Job failed in stage docker-build ${server}. click <${env.RUN_DISPLAY_URL}|here> to see the log. Error: ${e}", channel: 'idestack-automation') throw(e) } } } } +def runBuildStage(server) { + try { + echo "This is ${server}." + def name = getName(pwd() + params.DEPLOYMENT_PATH + "/${server}/package.json") + def version = getVersion(pwd() + params.DEPLOYMENT_PATH + "/${server}/package.json") + + buildAndPushDockerImage(server, name, version) + } catch (e) { + slackSend(color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME}' BUILD NUMBER: '${env.BUILD_NUMBER}' Job failed in stage docker-build ${server}. click <${env.RUN_DISPLAY_URL}|here> to see the log. Error: ${e}", channel: 'idestack-automation') + throw(e) + } +} + +def buildAndPushDockerImage(server, name, version) { + def imageExists = sh(script: "docker manifest inspect ${REPOSITORY_SERVER}/${name}:${version} > /dev/null 2>&1 && echo 'true' || echo 'false'", returnStdout: true).trim() + + if (imageExists == 'true') { + echo "Docker image ${REPOSITORY_SERVER}/${name}:${version} already exists. Skipping build." + } else { + sh """ + npx lerna exec --scope=*${server} ${params.BUILD_STRATEGY} run docker:${env.BUILD_COMMAND}; + docker tag ${name}:${version} ${REPOSITORY_SERVER}/${name}:${version} + docker push ${REPOSITORY_SERVER}/${name}:${version} + docker rmi ${REPOSITORY_SERVER}/${name}:${version} + """ + } +} + import groovy.json.JsonSlurper -def getVersion(json_file_path){ +def getVersion(json_file_path) { def inputFile = readFile(json_file_path) def InputJSON = new JsonSlurper().parseText(inputFile) def version = InputJSON.version return version } -def getName(json_file_path){ +def getName(json_file_path) { def inputFile = readFile(json_file_path) def InputJSON = new JsonSlurper().parseText(inputFile) def name = InputJSON.name return name -} +} \ No newline at end of file diff --git a/build.config.js b/build.config.js index 1b43a6d95..7362c5863 100644 --- a/build.config.js +++ b/build.config.js @@ -1,35 +1,32 @@ /* eslint-disable no-nested-ternary */ /* eslint-disable no-underscore-dangle */ -process.env.ENV_FILE !== null && require('dotenv').config({ path: process.env.ENV_FILE }); +// process.env.ENV_FILE !== null && require('dotenv').config({ path: process.env.ENV_FILE }); const __API_SERVER_PORT__ = process.env.GRAPHQL_URL ? new URL(process.env.GRAPHQL_URL).port : 8080; const __WEB_SERVER_PORT__ = process.env.LOCAL_BACKEND_URL ? new URL(process.env.LOCAL_BACKEND_URL).port : 3000; -const __WEB_DEV_SERVER_PORT__ = process.env.SSR - ? 3010 - : process.env.CLIENT_URL - ? new URL(process.env.CLIENT_URL).port - : 3000; +const __WEB_DEV_SERVER_PORT__ = + process.env.SSR === 'true' ? 3010 : process.env.CLIENT_URL ? new URL(process.env.CLIENT_URL).port : 3000; const __SERVER_PROTOCOL__ = 'http'; -const __SERVER_HOST__ = 'localhost'; +const __LOCAL_SERVER_HOST__ = 'localhost'; const __GRAPHQL_ENDPOINT__ = process.env.GRAPHQL_URL ? new URL(process.env.GRAPHQL_URL).pathname : '/graphql'; const config = { __SERVER__: false, __CLIENT__: true, - __SSR__: process.env.NODE_ENV === 'production', // enableing SSR only in Production as in Dev we have a issue + __SSR_BACKEND__: process.env.SSR_BACKEND, + __SSR__: process.env.SSR === 'true', __DEBUGGING__: false, __TEST__: false, __WEB_DEV_SERVER_PORT__, __GRAPHQL_ENDPOINT__, - __SERVER_HOST__, + __LOCAL_SERVER_HOST__, __API_SERVER_PORT__, __API_URL__: process.env.API_URL || - `${__SERVER_PROTOCOL__}://${__SERVER_HOST__}:${__API_SERVER_PORT__}${__GRAPHQL_ENDPOINT__}`, + `${__SERVER_PROTOCOL__}://${__LOCAL_SERVER_HOST__}:${__API_SERVER_PORT__}${__GRAPHQL_ENDPOINT__}`, __WEBSITE_URL__: - process.env.WEBSITE_URL || `${__SERVER_PROTOCOL__}://${__SERVER_HOST__}:${__WEB_DEV_SERVER_PORT__}`, + process.env.WEBSITE_URL || `${__SERVER_PROTOCOL__}://${__LOCAL_SERVER_HOST__}:${__WEB_DEV_SERVER_PORT__}`, __BACKEND_URL__: - process.env.LOCAL_BACKEND_URL || `${__SERVER_PROTOCOL__}://${__SERVER_HOST__}:${__WEB_SERVER_PORT__}`, + process.env.LOCAL_BACKEND_URL || `${__SERVER_PROTOCOL__}://${__LOCAL_SERVER_HOST__}:${__WEB_SERVER_PORT__}`, }; -console.log('---CONFIG', config); module.exports = config; diff --git a/build.config.mjs b/build.config.mjs new file mode 100644 index 000000000..fe8d098ba --- /dev/null +++ b/build.config.mjs @@ -0,0 +1,37 @@ +/* eslint-disable no-nested-ternary */ +/* eslint-disable no-underscore-dangle */ +import { config as dotenvConfig } from 'dotenv-esm'; + +if (process.env.ENV_FILE !== null) { + dotenvConfig({ path: process.env.ENV_FILE }); +} + +const __API_SERVER_PORT__ = process.env.GRAPHQL_URL ? new URL(process.env.GRAPHQL_URL).port : 8080; +const __WEB_SERVER_PORT__ = process.env.LOCAL_BACKEND_URL ? new URL(process.env.LOCAL_BACKEND_URL).port : 3000; +const __WEB_DEV_SERVER_PORT__ = + process.env.SSR === 'true' ? 3010 : process.env.CLIENT_URL ? new URL(process.env.CLIENT_URL).port : 3000; +const __SERVER_PROTOCOL__ = 'http'; +const __LOCAL_SERVER_HOST__ = 'localhost'; +const __GRAPHQL_ENDPOINT__ = process.env.GRAPHQL_URL ? new URL(process.env.GRAPHQL_URL).pathname : '/graphql'; + +const config = { + __SERVER__: false, + __CLIENT__: true, + __SSR_BACKEND__: process.env.SSR_BACKEND, + __SSR__: process.env.SSR === 'true', + __DEBUGGING__: false, + __TEST__: false, + __WEB_DEV_SERVER_PORT__, + __GRAPHQL_ENDPOINT__, + __LOCAL_SERVER_HOST__, + __API_SERVER_PORT__, + __API_URL__: + process.env.API_URL || + `${__SERVER_PROTOCOL__}://${__LOCAL_SERVER_HOST__}:${__API_SERVER_PORT__}${__GRAPHQL_ENDPOINT__}`, + __WEBSITE_URL__: + process.env.WEBSITE_URL || `${__SERVER_PROTOCOL__}://${__LOCAL_SERVER_HOST__}:${__WEB_DEV_SERVER_PORT__}`, + __BACKEND_URL__: + process.env.LOCAL_BACKEND_URL || `${__SERVER_PROTOCOL__}://${__LOCAL_SERVER_HOST__}:${__WEB_SERVER_PORT__}`, +}; + +export default config; diff --git a/codegen.yml b/codegen.yml index b41f7b577..e90837c72 100755 --- a/codegen.yml +++ b/codegen.yml @@ -18,6 +18,7 @@ generates: withMutationFn: false withHOC: false withComponent: false + noGraphQLTag: true plugins: - add: content: /* tslint:disable */ @@ -33,8 +34,9 @@ generates: config: withMutationFn: false withHOC: false - withComponent: true + withComponent: false withHooks: true + noGraphQLTag: true preset: import-types-preset presetConfig: typesPath: "../generated-models" diff --git a/config/development/dev.env.sample b/config/development/dev.env.sample index c57da82b9..e25f8af4d 100755 --- a/config/development/dev.env.sample +++ b/config/development/dev.env.sample @@ -1,6 +1,10 @@ # # Create `dev.env` file and load following required values # +# Build-time Variables +BUILD_MODULE_TO_INCLUDE=@sample-stack/counter-module-browser|@sample-stack/assets|@sample-stack/platform-browser + + NATS_URL=nats://localhost:4222/ NATS_USER=test NATS_PW=test diff --git a/config/staging/docker-staging.env.sample b/config/staging/docker-staging.env.sample new file mode 100644 index 000000000..76579b284 --- /dev/null +++ b/config/staging/docker-staging.env.sample @@ -0,0 +1,26 @@ +### +### To connect from docker to localhost, if you are using a Mac host, you can use - +# +# HOSTNAME= docker.for.mac.host.internal +# Or +# +# HOSTNAME = docker.for.mac.localhost +###docker.for.mac.localhost + +NATS_URL=nats://docker.for.mac.localhost:4222/ +NATS_USER=test +NATS_PW=test +GRAPHQL_URL=http://docker.for.mac.localhost:8080/graphql +LOCAL_GRAPHQL_URL=http://docker.for.mac.localhost:8080/graphql +CLIENT_URL=http://localhost:3010 +ZIPKIN_URL=test +ZIPKIN_PORT=test +LOG_LEVEL=trace +MONGO_URL=mongodb://docker.for.mac.localhost:27017/sample-stack +REDIS_CLUSTER_URL='[{"port":6379,"host":"docker.for.mac.localhost"}]' +REDIS_URL=redis://docker.for.mac.localhost:6379 +REDIS_CLUSTER_ENABLED=false +REDIS_SENTINEL_ENABLED=false +BACKEND_URL=http://localhost:8080 +CONNECTION_ID=v1 +LOCAL_BACKEND_URL=http://localhost:3010 \ No newline at end of file diff --git a/config/staging/staging.env.sample b/config/staging/staging.env.sample new file mode 100755 index 000000000..cda4af70b --- /dev/null +++ b/config/staging/staging.env.sample @@ -0,0 +1,20 @@ +# +# Create `dev.env` file and load following required values +# +NATS_URL=nats://localhost:4222/ +NATS_USER=test +NATS_PW=test +GRAPHQL_URL=http://localhost:8080/graphql +LOCAL_GRAPHQL_URL=http://localhost:8080/graphql +CLIENT_URL=http://localhost:3011 +ZIPKIN_URL=test +ZIPKIN_PORT=test +LOG_LEVEL=trace +MONGO_URL=mongodb://localhost:27017/sample-stack +REDIS_CLUSTER_URL='[{"port":6379,"host":"localhost"}]' +REDIS_URL=redis://localhost:6379 +REDIS_CLUSTER_ENABLED=false +REDIS_SENTINEL_ENABLED=false +BACKEND_URL=http://localhost:8080 +CONNECTION_ID=v1 +LOCAL_BACKEND_URL=http://localhost:3000 \ No newline at end of file diff --git a/jest-mongodb-config.js b/jest-mongodb-config.js index efdef0e3f..a6da3a105 100644 --- a/jest-mongodb-config.js +++ b/jest-mongodb-config.js @@ -1,12 +1,13 @@ module.exports = { mongodbMemoryServerOptions: { - instance: { - dbName: 'jest' - }, - binary: { - version: '4.0.12', // Version of MongoDB - skipMD5: true - }, - autoStart: false - } - }; \ No newline at end of file + instance: { + dbName: 'jest', + storageEngine: 'wiredTiger', + }, + binary: { + version: '4.0.27', // Version of MongoDB + skipMD5: true, + }, + autoStart: false, + }, +}; diff --git a/jest.config.base.js b/jest.config.base.js index 45161c219..5335374a0 100755 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -2,6 +2,45 @@ /* eslint-disable @typescript-eslint/no-var-requires */ const { defaults } = require('jest-config'); +const packagesToTransform = [ + '@apollo/client', + '@common-stack/client-core', + '@common-stack/client-react', + '@common-stack/core', + '@admin-layout/client', + '@common-stack/components-pro', + '@common-stack/server-core', + '@common-stack/cache-api-server', + '@common-stack/remix-router-redux', + '@cdmbase/redux-auth-wrapper', + '@cdmbase/remix-redis-session', + '@cdm-logger/server', + '@cdm-logger/core', + '@cdm-logger/client', + '@files-stack/server-core', + '@vscode-alt/monaco-editor', + '@workbench-stack/core', + '@workbench-stack/platform-server', + 'abortable-rx', + 'lodash-es', + 'sort-keys', + 'is-plain-obj', + 'query-string', + 'decode-uri-component', + 'split-on-first', + 'filter-obj', + 'react-dnd-html5-backend', + 'react-sortable-tree', + 'react-dnd', + 'dnd-core', +]; + +const generateTransformIgnorePattern = (packages) => { + const escapedPackages = packages.map((pkg) => pkg.replace(/\//g, '\\/')); + return `/node_modules/(?!(${escapedPackages.join('|')})/).+\\.js$`; +}; +const transformIgnorePattern = generateTransformIgnorePattern(packagesToTransform); + module.exports = { testEnvironment: 'node', setupFiles: [ @@ -10,11 +49,11 @@ module.exports = { ], preset: 'ts-jest', testMatch: null, - testRegex: '.*test*\\.(ts|tsx|js)$', - testPathIgnorePatterns: ['/node_modules/', '/dist/'], + testRegex: '.*test\\.(ts|tsx|js)$', + testPathIgnorePatterns: ['/node_modules/', '/lib', '/dist/'], transform: { '\\.(gql)$': 'jest-transform-graphql', - '\\.(graphql|graphqls)$': 'jest-raw-loader', + '\\.(graphql|graphqls)$': '@glen/jest-raw-loader', '\\.(ts|tsx)$': 'ts-jest', // Use our custom transformer only for the *.js and *.jsx files '\\.(js|jsx)?$': './transform.js', @@ -39,7 +78,7 @@ module.exports = { // because we don't need to use any kind of tree shaking right?! '^lodash-es$': '/node_modules/lodash/index.js', }, - transformIgnorePatterns: ['/node_modules/(?!(babel-runtime|antd)).*/', '/node_modules/(?!lodash-es/.*)'], + transformIgnorePatterns: [transformIgnorePattern], clearMocks: true, verbose: true, // projects: [''], // TODO need to test with it https://github.com/bryan-hunter/yarn-workspace-lerna-monorepo/blob/master/jest.config.base.js diff --git a/jest.config.base.mjs b/jest.config.base.mjs new file mode 100644 index 000000000..435ae8c55 --- /dev/null +++ b/jest.config.base.mjs @@ -0,0 +1,94 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable @typescript-eslint/no-var-requires */ +import { defaults } from 'jest-config'; + +const packagesToTransform = [ + '@apollo/client', + '@apollo/server', + '@graphql-tools/schema', + '@graphql-tools/mock', + '@common-stack/client-core', + '@common-stack/client-react', + '@common-stack/core', + '@common-stack/server-core', + '@common-stack/cache-api-server', + '@common-stack/remix-router-redux', + '@common-stack/graphql-api', + '@cdmbase/graphql-type-uri', + '@cdm-logger/server', + '@cdm-logger/core', + '@cdm-logger/client', + '@files-stack/core', + '@files-stack/server-core', + '@vscode-alt/monaco-editor', + '@workbench-stack/core', + '@workbench-stack/platform-server', + 'graphql', + 'abortable-rx', + 'lodash-es', + 'sort-keys', + 'is-plain-obj', + 'query-string', + 'decode-uri-component', + 'split-on-first', + 'filter-obj', + 'react-dnd-html5-backend', + 'react-sortable-tree', + 'react-dnd', + 'dnd-core', +]; + +const generateTransformIgnorePattern = (packages) => { + const escapedPackages = packages.map((pkg) => pkg.replace(/\//g, '\\/')); + return `/node_modules/(?!(${escapedPackages.join('|')})/).+\\.js$`; +}; +const transformIgnorePattern = generateTransformIgnorePattern(packagesToTransform); + +export default { + testEnvironment: 'node', + setupFiles: [ + // needed for UI to mock canvas load + // "jest-canvas-mock" + ], + extensionsToTreatAsEsm: ['.ts', '.tsx'], + preset: 'ts-jest', + testMatch: null, + testRegex: '.*test\\.(ts|tsx|js)$', + testPathIgnorePatterns: ['/node_modules/', '/lib', '/dist/'], + transform: { + '\\.(gql)$': 'jest-transform-graphql', + '\\.(graphql|graphqls)$': '@glen/jest-raw-loader', + '\\.(ts|tsx)$': 'ts-jest', + // // Use our custom transformer only for the *.js and *.jsx files + '\\.(js|jsx)?$': './transform.mjs', + // future need to test with + // "^.+\\.(js|jsx|ts|tsx)$": "./transform.js", + '.+\\.(css|styl|less|sass|scss)$': 'jest-css-modules-transform', + }, + roots: ['packages', 'packages-modules', 'servers'], + moduleFileExtensions: [...defaults.moduleFileExtensions, 'json', 'gql', 'graphql'], + moduleNameMapper: { + '^__mocks__/(.*)$': '/../../__mocks__/$1', + // we'll use commonjs version of lodash for tests 👌 + // because we don't need to use any kind of tree shaking right?! + '^lodash-es$': '/node_modules/lodash/index.js', + }, + transformIgnorePatterns: [transformIgnorePattern], + clearMocks: true, + verbose: true, + // projects: [''], // TODO need to test with it https://github.com/bryan-hunter/yarn-workspace-lerna-monorepo/blob/master/jest.config.base.js + coverageDirectory: '/coverage/', + coveragePathIgnorePatterns: ['/build/', '/lib/', '/dist/', '/node_modules/'], + globals: { + __BACKEND_URL__: 'http://localhost:3010', + __GRAPHQL_URL__: 'http://localhost:8085/graphql', + 'ts-jest': { + // tsConfig: "/src/__tests__/tsconfig.json", + // https://github.com/kulshekhar/ts-jest/issues/766 + diagnostics: { + warnOnly: true, + }, + // "skipBabel": true + }, + }, +}; diff --git a/jest.config.mongodb.mjs b/jest.config.mongodb.mjs new file mode 100644 index 000000000..ffb542d95 --- /dev/null +++ b/jest.config.mongodb.mjs @@ -0,0 +1,3 @@ +export default { + preset: '@shelf/jest-mongodb', +}; diff --git a/lerna.json b/lerna.json index 491f38a14..759884a8b 100755 --- a/lerna.json +++ b/lerna.json @@ -1,42 +1,48 @@ { - "$schema": "./node_modules/lerna/schemas/lerna-schema.json", - "changelog": true, - "command": { - "publish": { - "registry": "https://registry.npmjs.org", - "graphType": "all", - "allowBranch": [ - "publish", - "devpublish", - "devpublishn2" - ], - "message": "chore(release): publish", - "ignoreChanges": [ - "**/__fixtures__/**", - "**/__tests__/**", - "**/*.md", - "**/example/**" - ] - }, - "version": { - "allowBranch": [ - "master", - "develop", - "publish", - "devpublish", - "devpublishn2" - ], - "private": true, - "conventionalCommits": true, - "message": "chore: release package(s)" - } + "$schema": "./node_modules/lerna/schemas/lerna-schema.json", + "changelog": true, + "command": { + "publish": { + "registry": "https://registry.npmjs.org", + "allowBranch": [ + "publish", + "devpublish", + "devpublish3", + "devpublishn2", + "devpublish4", + "devpublish5" + ], + "message": "chore(release): publish", + "ignoreChanges": [ + "**/__fixtures__/**", + "**/__tests__/**", + "**/*.md", + "**/example/**" + ] }, - "npmClient": "yarn", - "packages": [ - "packages-modules/**", - "packages/**", - "servers/*", - "portable-devices/*" - ], - "version": "0.0.0" + "version": { + "allowBranch": [ + "master", + "develop", + "publish", + "devpublish", + "devpublish3", + "devpublishn2", + "devpublish4", + "devpublish5" + ], + "private": true, + "conventionalCommits": true, + "message": "chore: release package(s)" + } + }, + "npmClient": "yarn", + "useNx": true, + "packages": [ + "packages-modules/**", + "packages/**", + "servers/*", + "portable-devices/*" + ], + "version": "5.0.1" } \ No newline at end of file diff --git a/lint-staged.config.js b/lint-staged.config.js index 253c221f7..7c6c56a4b 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -1,4 +1,5 @@ module.exports = { '*.{js,jsx,ts,tsx,json,md}': ['prettier --write', 'git add'], // '*.{ts,tsx}': ['eslint --fix'], // this can be tested -}; \ No newline at end of file + 'package.json': ['sort-package-json', 'prettier --write'], +}; diff --git a/nx.json b/nx.json index 1469d49dc..a357ab822 100644 --- a/nx.json +++ b/nx.json @@ -1,33 +1,25 @@ { - "tasksRunnerOptions": { - "default": { - "runner": "nx/tasks-runners/default", - "options": { - "cacheableOperations": [ - "build" - ] - } - } - }, - "namedInputs": { - "adminide": [ - "{projectRoot}/packages/**/*", - "{projectRoot}/packages-modules/**/*" - ] - }, - "targetDefaults": { - "build": { - "dependsOn": [ - "^build" - ], - "inputs": [ - "adminide" - ] + "tasksRunnerOptions": { + "default": { + "runner": "@nx-aws-plugin/nx-aws-cache", + "options": { + "runtimeCacheInputs": ["node --version"], + "awsAccessKeyId": "xxxx", + "awsSecretAccessKey": "xxxx", + "awsRegion": "ca-central-1", + "awsBucket": "clockbook-screenshot" + } + } }, - "test": { - "dependsOn": [ - "build" - ] + "targetDefaults": { + "build": { + "dependsOn": ["^build"], + "inputs": ["default", "^default", "{projectRoot}/src/**/*"], + "cache": true, + "outputs": ["{projectRoot}/lib"] + }, + "test": { + "dependsOn": ["^test"] + } } - } } diff --git a/package.json b/package.json index 834fe34bd..172c0610f 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sample-stack", - "version": "0.12.4", + "version": "0.14.0", "private": true, "homepage": "https://github.com/cdmbase/fullstack-pro#readme", "bugs": { @@ -43,9 +43,8 @@ "db:migrate": "knex migrate:latest --cwd . --knexfile ./servers/backend-server/knexfile.js", "db:migrate:rollback": "knex migrate:rollback --cwd . --knexfile ./servers/backend-server/knexfile.js", "db:seed": "yarn db:migrate && knex seed:run --cwd . --knexfile ./servers/backend-server/knexfile.js", - "setBranchEnv": "cross-env REPOSITORY_BRANCH=${1:-$REPOSITORY_BRANCH} PUBLISH_BRANCH=${2:-$PUBLISH_BRANCH}", "predevpublish": "if ! git show-ref --verify --quiet refs/remotes/origin/$PUBLISH_BRANCH; then git checkout -b $PUBLISH_BRANCH && git push -u origin $PUBLISH_BRANCH; fi && git fetch origin $PUBLISH_BRANCH && git checkout $PUBLISH_BRANCH && git pull origin $PUBLISH_BRANCH && git merge -s recursive -X theirs $REPOSITORY_BRANCH -m \"merge from $REPOSITORY_BRANCH\" && yarn gitcommit && node tools/update-dependency-version.js && yarn gitcommit", - "devpublish": "lerna publish prerelease --ignore-scripts --exact", + "devpublish": "lerna publish prerelease --ignore-scripts --exact", "postdevpublish": "git checkout $REPOSITORY_BRANCH", "devpublish:auto": "yarn devpublish -- --yes", "devpublish:force": "yarn devpublish -- --force-publish=* --yes", @@ -62,29 +61,34 @@ "jest": "./node_modules/.bin/jest", "lerna": "lerna bootstrap", "prelernapublish": "git checkout publish && git pull origin publish && git merge -s recursive -X theirs master -m 'merge from master' && yarn gitcommit && node tools/update-dependency-version.js && yarn gitcommit", - "lernapublish": "lerna publish --ignore-scripts --cd-version=patch", + "lernapublish": "lerna publish --ignore-scripts --cd-version=patch && yarn update-lerna-on-develop", "postlernapublish": "git checkout master", "lint": "eslint --ext js --ext ts --ext md", "lint:ci": "yarn lint . --format junit", "lint:fix": "yarn lint -- --fix", "lint:md": "markdownlint", "lintx": "yarn lint ./packages/**/src/**/*.ts ./packages-modules/**/src/**/*.ts", + "prepare": "husky", "prodBuild": "cross-env NODE_ENV=production babel-node --presets es2015 tools/webpack.run", "publish": "yarn lernapublish", "publish:auto": "yarn lernapublish --yes", "publish:force": "yarn publish:forceManual --yes", "publish:forceManual": "yarn lernapublish --force-publish=*", "publish:push": "yarn prelernapublish && git push origin publish && yarn postlernapublish", - "startWeb": "lerna run --scope='{*frontend-server,*backend-server}' --parallel watch --stream", + "setBranchEnv": "cross-env REPOSITORY_BRANCH=${1:-$REPOSITORY_BRANCH} PUBLISH_BRANCH=${2:-$PUBLISH_BRANCH}", + "sort-packages": "node tools/sortPackageJson.mjs && prettier --write package.json **/**/*/package.json **/*/package.json", "start": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env yarn startWeb", - "start:envSSR": "cross-env SSR=true NODE_ENV=development ENV_FILE=../../config/development/dev.env yarn startWeb", + "start:SSR": "concurrently --names \"BACKEND,FRONTEND\" -c \"bgBlue.bold,bgMagenta.bold\" \"lerna run --scope='*backend-server' start\" \"lerna run --scope='*frontend-server' start:SSR\"", + "start:devSSR": "concurrently --names \"BACKEND,FRONTEND\" -c \"bgBlue.bold,bgMagenta.bold\" \"lerna run --scope='*backend-server' watch\" \"lerna run --scope='*frontend-server' start:devSSR\"", "start:test": "cross-env NODE_ENV=test ENV_FILE=../../config/test/test.env yarn startWeb", + "startWeb": "lerna run --scope='{*frontend-server,*backend-server}' --parallel watch --stream", "test": "cross-env ENV_FILE=config/test/test.env jest", "posttest": "yarn lint", "test:watch": "npm test -- --watch", "pretravis": "yarn compile", "travis": "istanbul cover -x \"*.test.js\" _mocha -- --timeout 5000 --full-trace ./test/tests.js", "posttravis": "yarn lint", + "update-lerna-on-develop": "git checkout publish && git pull && cp lerna.json ../lerna-temp.json && git checkout develop && mv ../lerna-temp.json lerna.json && git commit -am 'Update lerna.json' && git push", "watch": "lerna exec --no-sort --ignore *server --ignore *device --ignore *browser-extension --stream --parallel -- webpack --watch", "watch-packages": "lerna exec --no-sort --scope @sample-stack/platform* --scope @sample-stack/react-shared-components --scope @sample-stack/core --stream --parallel 'webpack --watch'", "zen:build": "cross-env NODE_ENV=production zen build", @@ -98,32 +102,22 @@ "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, - "lint-staged": { - "*.md": [ - "yarn format:md", - "git add" - ], - "*.{js,jsx,ts,tsx}": [ - "eslint --fix", - "git add" - ] - }, "resolutions": { - "@apollo/client": "~3.7.1", + "@apollo/client": "^3.9.0", "@types/react": "^18.0.25", "@types/react-dom": "^18.0.8", - "chokidar": "^3.5.3", "html-to-text": "^8.0.0", - "react": "18.0.0", - "react-dom": "18.0.0", - "react-native-gesture-handler": "~2.5.0" + "react": "18.3.0-canary-c3048aab4-20240326", + "react-dom": "18.3.0-canary-c3048aab4-20240326", + "react-native": "0.72.10", + "react-native-gesture-handler": "~2.12.0" }, "dependencies": { - "dataloader": "^2.1.0", - "graphql": "^15.0.0", + "graphql": "^16.0.0", "graphql-tag": "^2.12.6" }, "devDependencies": { + "@apollo/utils.keyvadapter": "^3.1.0", "@babel/cli": "^7.19.3", "@babel/core": "^7.20.2", "@babel/plugin-proposal-class-properties": "^7.18.6", @@ -151,24 +145,30 @@ "@babel/preset-typescript": "^7.18.6", "@babel/register": "^7.18.9", "@babel/runtime": "^7.20.1", - "@common-stack/env-list-loader": "0.5.8", - "@graphql-codegen/add": "^2.0.2", - "@graphql-codegen/cli": "^1.21.8", - "@graphql-codegen/fragment-matcher": "^2.0.1", - "@graphql-codegen/import-types-preset": "^1.18.6", - "@graphql-codegen/near-operation-file-preset": "^1.18.6", - "@graphql-codegen/typescript": "^1.23.0", - "@graphql-codegen/typescript-graphql-files-modules": "^1.18.1", - "@graphql-codegen/typescript-operations": "^1.18.4", - "@graphql-codegen/typescript-react-apollo": "^2.3.1", - "@graphql-codegen/typescript-resolvers": "^1.20.0", + "@common-stack/env-list-loader": "5.0.6-alpha.3", + "@common-stack/generate-plugin": "6.0.1-alpha.0", + "@emotion/babel-plugin": "^11.11.0", + "@graphql-codegen/add": "^5.0.2", + "@graphql-codegen/cli": "^5.0.2", + "@graphql-codegen/fragment-matcher": "^5.0.2", + "@graphql-codegen/import-types-preset": "^3.0.0", + "@graphql-codegen/near-operation-file-preset": "^3.0.0", + "@graphql-codegen/typescript": "^4.0.6", + "@graphql-codegen/typescript-graphql-files-modules": "^3.0.0", + "@graphql-codegen/typescript-operations": "^4.2.0", + "@graphql-codegen/typescript-react-apollo": "^4.3.0", + "@graphql-codegen/typescript-resolvers": "^4.0.6", "@loadable/babel-plugin": "^5.13.2", "@loadable/webpack-plugin": "^5.15.2", + "@nx-aws-plugin/nx-aws-cache": "^3.2.2", + "@nx/workspace": "^19.5.1", "@open-wc/building-rollup": "^2.0.2", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.8", "@redux-devtools/core": "^3.13.1", "@redux-devtools/dock-monitor": "^3.0.1", "@redux-devtools/log-monitor": "^4.0.1", + "@remix-run/dev": "^2.9.2", + "@remix-run/react": "^2.9.2", "@rollup/plugin-graphql": "2.0.2", "@rollup/plugin-image": "^3.0.1", "@rollup/plugin-json": "^5.0.2", @@ -183,23 +183,18 @@ "@types/classnames": "^2.3.1", "@types/cors": "2.8.12", "@types/enzyme": "^3.10.12", - "@types/express": "^4.17.14", + "@types/express": "^4.17.20", "@types/hoist-non-react-statics": "^3.3.1", - "@types/ioredis": "^5.0.0", "@types/isomorphic-fetch": "0.0.36", - "@types/jest": "^29.2.2", + "@types/jest": "^29.4.0", "@types/lodash-es": "^4.17.6", "@types/minimist": "^1.2.2", - "@types/node": "^18.11.9", + "@types/node": "18.16.9", "@types/prop-types": "^15.7.5", - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.8", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", "@types/react-helmet": "^6.1.5", - "@types/react-loadable": "^5.5.6", "@types/react-redux": "^7.1.24", - "@types/react-router": "^5.1.19", - "@types/react-router-config": "^5.0.6", - "@types/react-router-dom": "^5.3.3", "@types/react-test-renderer": "^18.0.0", "@types/redux-logger": "^3.0.9", "@types/semver": "^7.3.13", @@ -207,13 +202,11 @@ "@types/webpack": "^5.28.0", "@types/webpack-env": "^1.18.0", "@types/zen-observable": "^0.8.3", - "@typescript-eslint/eslint-plugin": "^5.42.1", - "@typescript-eslint/eslint-plugin-tslint": "^5.42.1", - "@typescript-eslint/parser": "^5.42.1", + "@typescript-eslint/eslint-plugin": "^6.7.4", + "@typescript-eslint/parser": "^6.7.4", + "@web/rollup-plugin-copy": "^0.5.1", "@webpack-cli/serve": "^1.7.0", - "@wojtekmaj/enzyme-adapter-react-17": "^0.7.0", "autoprefixer": "^10.4.13", - "awesome-typescript-loader": "^5.2.1", "babel-core": "^7.0.0-bridge.0", "babel-eslint": "^10.1.0", "babel-jest": "^29.3.0", @@ -232,25 +225,28 @@ "cross-env": "^7.0.3", "css-loader": "^6.7.1", "csstype": "^3.1.1", - "dotenv-safe": "^8.2.0", + "dotenv": "^16.4.5", + "dotenv-esm": "^16.0.3-4", + "dotenv-safe": "^9.0.0", "dotenv-webpack": "^8.0.1", "enzyme": "^3.11.0", "esbuild": "^0.15.13", - "eslint": "^8.27.0", - "eslint-config-airbnb-typescript": "^17.0.0", - "eslint-config-prettier": "^8.5.0", + "esbuild-loader": "^4.0.3", + "eslint": "^8.38.0", + "eslint-config-airbnb-typescript": "^18.0.0", + "eslint-config-prettier": "^9.1.0", "eslint-loader": "^4.0.2", "eslint-plugin-graphql": "^4.0.0", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jest": "^27.1.4", - "eslint-plugin-jsdoc": "^39.6.2", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jest": "^28.2.0", + "eslint-plugin-jsdoc": "^48.2.3", "eslint-plugin-json": "^3.1.0", "eslint-plugin-jsonc": "^2.5.0", - "eslint-plugin-jsx-a11y": "^6.6.1", - "eslint-plugin-markdown": "^3.0.0", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-markdown": "^4.0.1", "eslint-plugin-no-null": "^1.0.2", - "eslint-plugin-prettier": "^4.2.1", - "eslint-plugin-react": "^7.31.10", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-simple-import-sort": "^8.0.0", "express": "^4.18.2", @@ -261,23 +257,22 @@ "html-loader": "^4.2.0", "html-webpack-plugin": "^5.5.0", "http-proxy-middleware": "^2.0.6", - "husky": "^8.0.2", + "husky": "^9.1.5", "ignore-loader": "^0.1.2", "ip": "^1.1.8", "isomorphic-style-loader": "^5.3.2", "istanbul": "1.0.0-alpha.2", - "jest": "^29.3.0", + "jest": "^29.4.1", "jest-css-modules-transform": "^4.4.2", "jest-dom": "^4.0.0", "jest-junit": "^14.0.1", "jest-matcher-utils": "^29.2.2", - "jest-raw-loader": "^1.0.1", "jest-transform-graphql": "^2.1.0", "jsdom": "^20.0.2", - "lerna": "^6.1.0", + "lerna": "8", "less": "^4.1.3", "less-loader": "^11.0.0", - "lint-staged": "^13.0.3", + "lint-staged": "^15.2.7", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "lodash-webpack-plugin": "^0.11.6", @@ -311,13 +306,14 @@ "remap-istanbul": "^0.13.0", "resolve-url-loader": "^5.0.0", "rimraf": "^3.0.2", - "rollup": "^3.2.5", - "rollup-plugin-esbuild": "^5.0.0", + "rollup": "^4.13.0", + "rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-string": "^3.0.0", "sass-loader": "^13.1.0", "shelljs": "^0.8.5", "simple-git": "^3.14.1", "sinon": "^14.0.2", + "sort-package-json": "^2.10.0", "source-list-map": "^2.0.1", "source-map-loader": "^4.0.1", "source-map-support": "^0.5.21", @@ -326,15 +322,12 @@ "style-loader": "^3.3.1", "svg-url-loader": "^8.0.0", "tcomb": "^3.2.29", - "ts-jest": "^29.0.3", + "ts-jest": "^29.1.0", "ts-loader": "^9.4.1", "ts-node": "^10.9.1", "tslib": "^2.4.1", - "type-fest": "^3.2.0", "typedoc": "^0.23.20", - "typescript": "~4.8.4", - "uglify-es": "^3.3.9", - "uglifyjs-webpack-plugin": "^2.2.0", + "typescript": "^5.1.6", "url-loader": "^4.1.1", "wait-on": "^6.0.1", "webpack": "^5.74.0", @@ -348,10 +341,10 @@ "ws": "^8.11.0" }, "engines": { - "node": ">=14.17.3 < 17.0.0", + "node": ">=14.17.3 < 23.0.0", "yarn": ">=1.22" }, "cacheDirectories": [ ".cache" ] -} \ No newline at end of file +} diff --git a/packages-modules/counter/browser/package.json b/packages-modules/counter/browser/package.json index b67e6bd67..a5205225a 100755 --- a/packages-modules/counter/browser/package.json +++ b/packages-modules/counter/browser/package.json @@ -6,6 +6,7 @@ "author": "CDMBase LLC", "main": "lib/index.js", "typings": "lib/index.d.ts", + "type": "module", "scripts": { "build": "yarn build:clean && yarn build:lib", "build:clean": "rimraf lib", @@ -19,17 +20,16 @@ "watch": "yarn build:lib:watch" }, "dependencies": { - "@sample-stack/platform-browser": "0.0.1", + "@sample-stack/platform-browser": "link:../../../packages/sample-platform/browser", + "@remix-run/react": "^2.10.3", "antd": "~5.1.7" }, - "devDependencies": {}, "peerDependencies": { "@common-stack/client-react": "*", "@rollup/plugin-graphql": "*", "@rollup/plugin-image": "*", "@rollup/plugin-typescript": "*", "react": "*", - "react-native": "*", "react-redux": "*", "react-router": "*", "react-router-dom": "*", @@ -44,4 +44,4 @@ "typescript": { "definition": "lib/index.d.ts" } -} \ No newline at end of file +} diff --git a/packages-modules/counter/browser/rollup.config.mjs b/packages-modules/counter/browser/rollup.config.mjs index 78e79fc1c..d898dffb0 100644 --- a/packages-modules/counter/browser/rollup.config.mjs +++ b/packages-modules/counter/browser/rollup.config.mjs @@ -1,27 +1,18 @@ -import graphql from '@rollup/plugin-graphql'; -import image from '@rollup/plugin-image'; -import typescript from '@rollup/plugin-typescript'; -import { string } from 'rollup-plugin-string'; - -const bundle = (config) => ({ - ...config, - input: 'src/index.ts', - // marking all node modules as external - external: (id) => !/^[./]/.test(id), -}); -const globals = { react: 'React' }; +import { createRollupConfig } from '../../../rollup.config.base.mjs'; +import json from '@rollup/plugin-json'; +// Define any additional plugins specific to this bundle +const additionalPlugins = [ + json() +]; +// Use the createRollupConfig function to merge the base and specific configurations export default [ - bundle({ + createRollupConfig({ + input: ['src/index.ts'], plugins: [ - image(), - graphql({ - include: '**/*.gql', - }), - string({ - include: '**/*.graphql', - }), - typescript({ noEmitOnError: true }), + // Spread in additional plugins specific to this config + ...additionalPlugins, + ], output: [ { @@ -33,8 +24,8 @@ export default [ sourcemap: true, preserveModules: true, chunkFileNames: '[name]-[hash].[format].js', - globals, + globals: { react: 'React' }, }, ], }), -]; +]; \ No newline at end of file diff --git a/packages-modules/counter/browser/src/apollo-server-n-client/components/CounterView.tsx b/packages-modules/counter/browser/src/apollo-server-n-client/components/CounterView.tsx index 5beef8ce3..746d29291 100755 --- a/packages-modules/counter/browser/src/apollo-server-n-client/components/CounterView.tsx +++ b/packages-modules/counter/browser/src/apollo-server-n-client/components/CounterView.tsx @@ -39,9 +39,8 @@ const CounterView = ({ {renderMetaData()}

- Current counter, is {counter?.amount} and cached data. This is being stored - server-side in the database and using Apollo subscription for - real-time updates. + {`Current counter, is ${counter?.amount} and cached data. This is being stored + server-side in the database and using Apollo subscription for real-time updates.`}

- Current reduxCount, is {reduxCount}. This is being stored - client-side with Redux. -

+ {`Current reduxCount, is ${reduxCount}. This is being stored client-side with Redux.`} +

- - -); +const CounterComponent: React.FC = (props) => { + const dispatch = useDispatch(); + + return ( +
+ + {`Counter: ${props.count}`} + + + +
+ ) +}; interface StateProps { count: number; diff --git a/packages-modules/counter/browser/src/connected-react-router/components/Hello.tsx b/packages-modules/counter/browser/src/redux-first-history/components/Hello.tsx similarity index 100% rename from packages-modules/counter/browser/src/connected-react-router/components/Hello.tsx rename to packages-modules/counter/browser/src/redux-first-history/components/Hello.tsx diff --git a/packages-modules/counter/browser/src/redux-first-history/components/HelloChild.tsx b/packages-modules/counter/browser/src/redux-first-history/components/HelloChild.tsx new file mode 100755 index 000000000..fbb25a9ae --- /dev/null +++ b/packages-modules/counter/browser/src/redux-first-history/components/HelloChild.tsx @@ -0,0 +1,27 @@ + +import * as React from 'react'; +import { useLocation, Link } from 'react-router-dom'; +import { CONNECTED_REACT_ROUTER_ROUTES_TYPES } from '../constants'; + +export const HelloChild = () => { + const { pathname, search, hash } = useLocation(); + + return ( +
+ Hello-Child +
    +
  • with query string
  • +
  • with hash
  • +
+
+ pathname: {pathname} +
+
+ search: {search} +
+
+ hash: {hash} +
+
+ ); +} diff --git a/packages-modules/counter/browser/src/connected-react-router/components/Home.tsx b/packages-modules/counter/browser/src/redux-first-history/components/Home.tsx similarity index 100% rename from packages-modules/counter/browser/src/connected-react-router/components/Home.tsx rename to packages-modules/counter/browser/src/redux-first-history/components/Home.tsx diff --git a/packages-modules/counter/browser/src/connected-react-router/components/NavBar.tsx b/packages-modules/counter/browser/src/redux-first-history/components/NavBar.tsx similarity index 100% rename from packages-modules/counter/browser/src/connected-react-router/components/NavBar.tsx rename to packages-modules/counter/browser/src/redux-first-history/components/NavBar.tsx diff --git a/packages-modules/counter/browser/src/connected-react-router/components/NoMatch.tsx b/packages-modules/counter/browser/src/redux-first-history/components/NoMatch.tsx similarity index 100% rename from packages-modules/counter/browser/src/connected-react-router/components/NoMatch.tsx rename to packages-modules/counter/browser/src/redux-first-history/components/NoMatch.tsx diff --git a/packages-modules/counter/browser/src/connected-react-router/compute.tsx b/packages-modules/counter/browser/src/redux-first-history/compute.tsx similarity index 60% rename from packages-modules/counter/browser/src/connected-react-router/compute.tsx rename to packages-modules/counter/browser/src/redux-first-history/compute.tsx index 922c0e86a..81c59e29e 100755 --- a/packages-modules/counter/browser/src/connected-react-router/compute.tsx +++ b/packages-modules/counter/browser/src/redux-first-history/compute.tsx @@ -1,32 +1,25 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import * as React from 'react'; import { IMenuPosition, IRoute } from '@common-stack/client-react'; -import { lazy } from '@loadable/component' -import { getFilteredMenus, getFilteredRoutes } from '../utils'; +import { getFilteredMenus, getFilteredRoutes } from '../utils/menu'; import { CONNECTED_REACT_ROUTER_ROUTES_TYPES } from './constants'; - -const Dashboard = lazy(() => import('../common/components/Dashboard')); -const Counter = lazy(() => import('./components/Counter')); -const Hello = lazy(() => import('./components/Hello')); - -export const counterPageStore: IRoute[] = [ - { - exact: false, - icon: 'export', - component: Dashboard, - position: IMenuPosition.MIDDLE, - name: 'Connected React Router', - key: CONNECTED_REACT_ROUTER_ROUTES_TYPES.HOME, - path: CONNECTED_REACT_ROUTER_ROUTES_TYPES.HOME, - }, +export const counterPageStore = [ + // { + // exact: false, + // icon: 'export', + // component: () => import('../common/components/Dashboard'), + // position: IMenuPosition.MIDDLE, + // name: 'Redux First History', + // key: CONNECTED_REACT_ROUTER_ROUTES_TYPES.HOME, + // path: CONNECTED_REACT_ROUTER_ROUTES_TYPES.HOME, + // }, { exact: true, icon: 'export', name: 'Hello', - component: Hello, + component: () => import('./components/Hello'), position: IMenuPosition.MIDDLE, key: CONNECTED_REACT_ROUTER_ROUTES_TYPES.HELLO, path: CONNECTED_REACT_ROUTER_ROUTES_TYPES.HELLO, @@ -35,7 +28,7 @@ export const counterPageStore: IRoute[] = [ exact: true, icon: 'export', name: 'Counter', - component: Counter, + component: () => import('./components/Counter'), position: IMenuPosition.MIDDLE, key: CONNECTED_REACT_ROUTER_ROUTES_TYPES.COUNTER, path: CONNECTED_REACT_ROUTER_ROUTES_TYPES.COUNTER, diff --git a/packages-modules/counter/browser/src/redux-first-history/constants/action-types.ts b/packages-modules/counter/browser/src/redux-first-history/constants/action-types.ts new file mode 100755 index 000000000..c76427a70 --- /dev/null +++ b/packages-modules/counter/browser/src/redux-first-history/constants/action-types.ts @@ -0,0 +1,4 @@ +export const enum CONNECTED_REACT_ROUTER_ACTION_TYPES { + INCREMENT = '@redux-first-history/INCREMENT', + DECREMENT = '@redux-first-history/DECREMENT', +} diff --git a/packages-modules/counter/browser/src/connected-react-router/constants/index.ts b/packages-modules/counter/browser/src/redux-first-history/constants/index.ts similarity index 100% rename from packages-modules/counter/browser/src/connected-react-router/constants/index.ts rename to packages-modules/counter/browser/src/redux-first-history/constants/index.ts diff --git a/packages-modules/counter/browser/src/redux-first-history/constants/routes-types.ts b/packages-modules/counter/browser/src/redux-first-history/constants/routes-types.ts new file mode 100755 index 000000000..89ec17f63 --- /dev/null +++ b/packages-modules/counter/browser/src/redux-first-history/constants/routes-types.ts @@ -0,0 +1,6 @@ +export enum CONNECTED_REACT_ROUTER_ROUTES_TYPES { + ROOT = '/', + HOME = '/redux-first-history', + HELLO = '/redux-first-history/hello', + COUNTER = '/redux-first-history/counter', +} diff --git a/packages-modules/counter/browser/src/connected-react-router/electron-module.tsx b/packages-modules/counter/browser/src/redux-first-history/electron-module.tsx similarity index 94% rename from packages-modules/counter/browser/src/connected-react-router/electron-module.tsx rename to packages-modules/counter/browser/src/redux-first-history/electron-module.tsx index c719458db..1034328fe 100644 --- a/packages-modules/counter/browser/src/connected-react-router/electron-module.tsx +++ b/packages-modules/counter/browser/src/redux-first-history/electron-module.tsx @@ -2,7 +2,7 @@ import { Feature } from '@common-stack/client-react'; import { IMenuPosition, IRoute } from '@common-stack/client-react'; import Counter from './components/Counter'; -import { connectedReactRouterCounter } from './redux'; +import connectedReactRouterCounter from './redux'; import { CONNECTED_REACT_ROUTER_ROUTES_TYPES } from './constants'; import { getFilteredRoutes } from '../utils'; diff --git a/packages-modules/counter/browser/src/connected-react-router/index.electron.ts b/packages-modules/counter/browser/src/redux-first-history/index.electron.ts similarity index 100% rename from packages-modules/counter/browser/src/connected-react-router/index.electron.ts rename to packages-modules/counter/browser/src/redux-first-history/index.electron.ts diff --git a/packages-modules/counter/browser/src/connected-react-router/index.ts b/packages-modules/counter/browser/src/redux-first-history/index.ts similarity index 100% rename from packages-modules/counter/browser/src/connected-react-router/index.ts rename to packages-modules/counter/browser/src/redux-first-history/index.ts diff --git a/packages-modules/counter/browser/src/connected-react-router/interfaces/index.ts b/packages-modules/counter/browser/src/redux-first-history/interfaces/index.ts similarity index 100% rename from packages-modules/counter/browser/src/connected-react-router/interfaces/index.ts rename to packages-modules/counter/browser/src/redux-first-history/interfaces/index.ts diff --git a/packages-modules/counter/browser/src/redux-first-history/interfaces/state.ts b/packages-modules/counter/browser/src/redux-first-history/interfaces/state.ts new file mode 100755 index 000000000..82e84abd0 --- /dev/null +++ b/packages-modules/counter/browser/src/redux-first-history/interfaces/state.ts @@ -0,0 +1,4 @@ +export interface State { + connectedReactRouterCounter: number; + router: any; +} diff --git a/packages-modules/counter/browser/src/connected-react-router/module.tsx b/packages-modules/counter/browser/src/redux-first-history/module.tsx similarity index 89% rename from packages-modules/counter/browser/src/connected-react-router/module.tsx rename to packages-modules/counter/browser/src/redux-first-history/module.tsx index cfa4f99c9..d51765bc4 100755 --- a/packages-modules/counter/browser/src/connected-react-router/module.tsx +++ b/packages-modules/counter/browser/src/redux-first-history/module.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { Feature } from '@common-stack/client-react'; import Counter from './components/Counter'; import NavBar from './components/NavBar'; -import { connectedReactRouterCounter } from './redux'; +import connectedReactRouterCounter from './redux'; import { filteredRoutes, filteredMenus } from './compute'; export default new Feature({ diff --git a/packages-modules/counter/browser/src/redux-first-history/redux/index.ts b/packages-modules/counter/browser/src/redux-first-history/redux/index.ts new file mode 100755 index 000000000..066116799 --- /dev/null +++ b/packages-modules/counter/browser/src/redux-first-history/redux/index.ts @@ -0,0 +1,29 @@ +// Import createSlice from Redux Toolkit +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type * as rtk from "@reduxjs/toolkit"; + +// Define initial state +const initialState = 0; + +// Create a slice for the counter with `createSlice` +const connectedReactRouterCounterSlice = createSlice({ + name: 'redux-data', // A name, used in action types + initialState, + reducers: { + // The name of the reducer serves as the name of the action + increment(state) { + return state + 1; + }, + decrement(state) { + return state - 1; + }, + }, +}); + +// Export the reducer +const { reducer, actions } = connectedReactRouterCounterSlice; + +// Export actions +export const { increment, decrement } = actions; + +export default reducer; diff --git a/packages-modules/counter/browser/src/utils/menu.ts b/packages-modules/counter/browser/src/utils/menu.ts index 9ab4aaaf1..67ecc596e 100755 --- a/packages-modules/counter/browser/src/utils/menu.ts +++ b/packages-modules/counter/browser/src/utils/menu.ts @@ -1,34 +1,35 @@ -export const getFilteredMenus = (accountPageStore, selectedMenu) => - accountPageStore - .map((item) => { - if (selectedMenu.indexOf(item.key) !== -1) { - const { path, component, ...rest } = item; - return { - [path]: { name: rest.tab, ...rest }, - }; +const filterStore = (store, selected) => { + const cloned = [...store]; + cloned.forEach((item) => { + if (Array.isArray(item.routes)) { + item.routes = filterStore(item.routes, selected); + if (item.routes.length < 1) { + delete item.routes; } - }) - .filter((valid) => valid); + } + }); + + return cloned.filter((item) => Array.isArray(item.routes) || selected.indexOf(item.key) !== -1); +}; + +export const getFilteredMenus = (accountPageStore, selectedMenu) => + filterStore(accountPageStore, selectedMenu).map((item) => { + const { path, component, ...rest } = item; + return { + [path]: { name: rest.tab, ...rest }, + }; + }); export const getFilteredRoutes = (accountPageStore, selectedRoutes) => - accountPageStore - .map((item) => { - if (selectedRoutes.indexOf(item.key) !== -1) { - const { path } = item; - return { - [path]: item, - }; - } - return null; - }) - .filter((valid) => valid); + filterStore(accountPageStore, selectedRoutes).map((item) => { + const { path } = item; + return { + [path]: item, + }; + }); export const getFilteredTabs = (accountPageStore, selectedTabs) => - accountPageStore - .map((item) => { - if (selectedTabs.indexOf(item.key) !== -1) { - const { component, ...rest } = item; - return rest; - } - }) - .filter((valid) => valid); + filterStore(accountPageStore, selectedTabs).map((item) => { + const { component, ...rest } = item; + return rest; + }); diff --git a/packages-modules/counter/browser/tsconfig.json b/packages-modules/counter/browser/tsconfig.json index 88468a68b..c3f23862e 100755 --- a/packages-modules/counter/browser/tsconfig.json +++ b/packages-modules/counter/browser/tsconfig.json @@ -1,10 +1,6 @@ { "extends": "../../../tsconfig.json", "compilerOptions": { - "allowSyntheticDefaultImports": true, - "experimentalDecorators": true, - "esModuleInterop": true, - "skipLibCheck": true, "rootDir": "./src", "outDir": "lib", "declarationDir": "lib", @@ -27,6 +23,5 @@ ], "include": [ "src", - "./typings/*.d.ts" ] } \ No newline at end of file diff --git a/packages-modules/counter/electron/package.json b/packages-modules/counter/electron/package.json index b3fddf8cc..7405fc5c6 100755 --- a/packages-modules/counter/electron/package.json +++ b/packages-modules/counter/electron/package.json @@ -19,14 +19,7 @@ "watch": "yarn build:lib:watch" }, "dependencies": { - "@sample-stack/counter-module-browser": "0.0.1" - }, - "devDependencies": { - "@open-wc/building-rollup": "^1.10.0", - "@rollup/plugin-graphql": "1.0.0", - "@rollup/plugin-image": "^2.0.6", - "@rollup/plugin-typescript": "^6.1.0", - "rollup": "latest" + "@sample-stack/counter-module-browser": "link:../browser" }, "peerDependencies": { "@common-stack/client-react": "*", @@ -46,4 +39,4 @@ "typescript": { "definition": "lib/index.d.ts" } -} \ No newline at end of file +} diff --git a/packages-modules/counter/electron/src/epics/count-tray-updater.ts b/packages-modules/counter/electron/src/epics/count-tray-updater.ts index 2910586a4..d7b2fa0d4 100644 --- a/packages-modules/counter/electron/src/epics/count-tray-updater.ts +++ b/packages-modules/counter/electron/src/epics/count-tray-updater.ts @@ -3,7 +3,7 @@ import { ofType } from 'redux-observable'; import { Observable, of } from 'rxjs'; import { distinctUntilChanged, map, tap, exhaustMap, pluck, catchError, filter } from 'rxjs/operators'; import { ElectronTypes } from '@common-stack/client-core'; -import { CONNECTED_REACT_ROUTER_ACTION_TYPES } from '@sample-stack/counter-module-browser/lib/connected-react-router/constants/action-types'; +import { CONNECTED_REACT_ROUTER_ACTION_TYPES } from '@sample-stack/counter-module-browser/lib/redux-first-history/constants/action-types'; export const onCountChangedEpic = ( action$: Observable, diff --git a/packages-modules/counter/electron/src/index.ts b/packages-modules/counter/electron/src/index.ts index f16f0a440..096f8be30 100755 --- a/packages-modules/counter/electron/src/index.ts +++ b/packages-modules/counter/electron/src/index.ts @@ -1,5 +1,5 @@ import { Feature } from '@common-stack/client-react'; -import { connectedReactRouterCounter } from '@sample-stack/counter-module-browser/lib/connected-react-router/redux/reducers/counter'; +import connectedReactRouterCounter from '@sample-stack/counter-module-browser/lib/redux-first-history/redux/index.js'; import { onCountChangedEpic } from './epics'; const ElectronMainModule = new Feature({ diff --git a/packages-modules/counter/mobile/package.json b/packages-modules/counter/mobile/package.json index 3c655b4b0..7d433a83b 100755 --- a/packages-modules/counter/mobile/package.json +++ b/packages-modules/counter/mobile/package.json @@ -19,7 +19,7 @@ "watch": "yarn build:lib:watch" }, "dependencies": { - "@sample-stack/platform-browser": "0.0.1", + "@sample-stack/platform-browser": "link:../../../packages/sample-platform/browser", "antd": "~5.1.7" }, "peerDependencies": { @@ -38,4 +38,4 @@ "typescript": { "definition": "lib/index.d.ts" } -} \ No newline at end of file +} diff --git a/packages-modules/counter/mobile/src/common/components/Dashboard.tsx b/packages-modules/counter/mobile/src/common/components/Dashboard.tsx index 7205da255..64d51611f 100755 --- a/packages-modules/counter/mobile/src/common/components/Dashboard.tsx +++ b/packages-modules/counter/mobile/src/common/components/Dashboard.tsx @@ -1,4 +1,3 @@ import * as React from 'react'; -import { renderRoutes } from 'react-router-config'; -export const Dashboard = (props) => <>{renderRoutes(props.route.routes, { matchPath: props.route.path })}; +export const Dashboard = (props) => <>{}; diff --git a/packages-modules/counter/mobile/src/common/interfaces/context.ts b/packages-modules/counter/mobile/src/common/interfaces/context.ts index acb8051de..85f0ff167 100755 --- a/packages-modules/counter/mobile/src/common/interfaces/context.ts +++ b/packages-modules/counter/mobile/src/common/interfaces/context.ts @@ -1,5 +1,5 @@ import { DataProxy } from '@apollo/client/cache'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; export interface MyContext { cache: DataProxy; diff --git a/packages-modules/counter/mobile/src/connected-react-router/interfaces/state.ts b/packages-modules/counter/mobile/src/connected-react-router/interfaces/state.ts index f5de723d6..82e84abd0 100755 --- a/packages-modules/counter/mobile/src/connected-react-router/interfaces/state.ts +++ b/packages-modules/counter/mobile/src/connected-react-router/interfaces/state.ts @@ -1,6 +1,4 @@ -import { RouterState } from 'connected-react-router'; - export interface State { connectedReactRouterCounter: number; - router: RouterState; + router: any; } diff --git a/packages-modules/counter/server/src/dataloader/cache.ts b/packages-modules/counter/server/src/dataloader/cache.ts index ff3baebbd..a784be7b9 100644 --- a/packages-modules/counter/server/src/dataloader/cache.ts +++ b/packages-modules/counter/server/src/dataloader/cache.ts @@ -1,5 +1,5 @@ -import * as DataLoader from 'dataloader'; -import { KeyValueCache } from 'apollo-server-caching'; +import DataLoader from 'dataloader'; +import { KeyValueCache } from '@apollo/utils.keyvaluecache'; import { logger } from '@cdm-logger/server'; import { config } from '../config'; import { ICounterService } from '../interfaces'; @@ -14,9 +14,7 @@ export const setupCaching = ({ cache: KeyValueCache; }) => { const loader = new DataLoader( - (args) => { - return (counterService.counterQuery() as Promise).then((data) => [data]); - }, + (args) => (counterService.counterQuery() as Promise).then((data) => [data]), { batch: false }, ); const cachedCounterService: ICounterService = {} as ICounterService; diff --git a/packages-modules/counter/server/src/dataloader/counter-dataloader.ts b/packages-modules/counter/server/src/dataloader/counter-dataloader.ts index 4f7301a0a..43bb2a771 100644 --- a/packages-modules/counter/server/src/dataloader/counter-dataloader.ts +++ b/packages-modules/counter/server/src/dataloader/counter-dataloader.ts @@ -1,8 +1,6 @@ -import { DataSource, DataSourceConfig } from 'apollo-datasource'; import { ApolloError } from 'apollo-server-errors'; -import { InMemoryLRUCache } from 'apollo-server-caching'; +import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache'; // import { setupCaching } from './cache'; -import { KeyValueCache } from 'apollo-server-caching'; import { IService, IContext, ICounterService } from '../interfaces'; import { setupCaching } from './cache'; import { Counter } from '../generated-models'; @@ -11,14 +9,12 @@ export interface CacheOptions { ttl?: number; } -export class CounterDataSource extends DataSource implements ICounterService { +export class CounterDataSource implements ICounterService { private context!: IContext; private cacheCounterService: ICounterService; - constructor() { - super(); - } + constructor(private cache?) {} public counterQuery(): Counter | Promise | PromiseLike { return this.cacheCounterService.counterQuery(); @@ -28,7 +24,7 @@ export class CounterDataSource extends DataSource implements ICounterS return this.cacheCounterService.addCounter(); } - public initialize(config: DataSourceConfig) { + public initialize(config) { this.context = config.context; if (!this.context.counterMockService) { throw new ApolloError('Missing TextFileService in the context!'); diff --git a/packages/assets/src/assets/manifest.xjson b/packages/assets/src/assets/manifest.xjson index c25820026..f08579b72 100644 --- a/packages/assets/src/assets/manifest.xjson +++ b/packages/assets/src/assets/manifest.xjson @@ -1,16 +1,7 @@ { "name": "", "icons": [ - { - "src": "android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "android-chrome-256x256.png", - "sizes": "256x256", - "type": "image/png" - } + ], "theme_color": "#ffffff", "background_color": "#ffffff", diff --git a/packages/sample-platform/browser/package.json b/packages/sample-platform/browser/package.json index 548ea408f..aea195d6e 100644 --- a/packages/sample-platform/browser/package.json +++ b/packages/sample-platform/browser/package.json @@ -19,7 +19,7 @@ "watch": "yarn build:lib:watch" }, "dependencies": { - "@sample-stack/core": "0.0.1" + "@sample-stack/core": "link:../../sample-core" }, "publishConfig": { "access": "public" @@ -27,4 +27,4 @@ "typescript": { "definition": "lib/index.d.ts" } -} \ No newline at end of file +} diff --git a/packages/sample-platform/browser/src/containers/PersonList.tsx b/packages/sample-platform/browser/src/containers/PersonList.tsx index c2b62a054..7a73ecb5d 100755 --- a/packages/sample-platform/browser/src/containers/PersonList.tsx +++ b/packages/sample-platform/browser/src/containers/PersonList.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { graphql } from '@apollo/react-hoc'; +import { graphql } from '@apollo/client/react/hoc'; import compose from 'lodash/flowRight'; import { PERSONS_QUERY } from '../graphql'; @@ -9,12 +9,11 @@ export interface IPersonListProps { const PersonListComponent: React.FC = ({ persons }) => (

Persons:

- {persons && persons.map((person, i) =>
{person.name}
)} + {persons && persons.map((person, i) =>
{person.name}
)}
); -export const PersonList: React.ComponentClass<{}> = - compose( - graphql<{}, any, {}, {}>(PERSONS_QUERY), - // flattenProp('data'), - )(PersonListComponent); +export const PersonList: React.ComponentClass<{}> = compose( + graphql<{}, any, {}, {}>(PERSONS_QUERY), + // flattenProp('data'), +)(PersonListComponent); diff --git a/packages/sample-platform/browser/src/containers/ServerCounter.tsx b/packages/sample-platform/browser/src/containers/ServerCounter.tsx index 948ffb7da..9de0e59cf 100755 --- a/packages/sample-platform/browser/src/containers/ServerCounter.tsx +++ b/packages/sample-platform/browser/src/containers/ServerCounter.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; import update from 'immutability-helper'; -import { graphql } from '@apollo/react-hoc'; +import { graphql } from '@apollo/client/react/hoc'; import compose from 'lodash/flowRight'; import { CounterComponent, ICounterProps } from '../components'; -import { COUNT_SUBSCRIPTION, COUNT_QUERY, ADD_COUNT_MUTATION,} from '../graphql'; +import { COUNT_SUBSCRIPTION, COUNT_QUERY, ADD_COUNT_MUTATION } from '../graphql'; import { logger } from '@cdm-logger/client'; type SubscriptionProps = { subscribeToMore: Function; @@ -60,7 +60,6 @@ type SubscriptionProps = { // } // } - // save(amount) { // return () => mutate({ // variables: { amount }, @@ -85,56 +84,59 @@ const updateQueries = { }, }; -export const CounterWithApollo: React.ComponentClass = (compose( +export const CounterWithApollo: React.ComponentClass = compose( graphql<{}, any, {}, {}>(ADD_COUNT_MUTATION, { props: ({ ownProps, mutate }) => ({ save: (amount) => { - return () => mutate({ - variables: { amount }, - // optimisticResponse: { - // __typename: 'Mutation', + return () => + mutate({ + variables: { amount }, + // optimisticResponse: { + // __typename: 'Mutation', - // }, - }); + // }, + }); }, }), }), graphql<{}, any, {}, {}>(ADD_COUNT_MUTATION, { props: ({ ownProps, mutate }) => ({ increment: (amount) => { - return () => mutate({ - variables: { amount }, - // updateQueries, - }); + return () => + mutate({ + variables: { amount }, + // updateQueries, + }); }, }), }), -)(graphql(COUNT_QUERY, { - name: 'countData', - props: ({ countData }: any) => { - const newlog = logger.child({ childName: 'UIController' }); - newlog.debug('count data : (%j)', countData); - return { - subscribeToCount: params => { - // logger.debug('count subscript data (%j)', params); - return countData.subscribeToMore({ - document: COUNT_SUBSCRIPTION, - variables: {}, - updateQuery: (prev: any, { subscriptionData }) => { - const payload = subscriptionData.data && subscriptionData.data.subscribeToWorkspace; - if (!payload) { - return prev; - } - return payload; - }, - }); - }, - counter: countData.count && countData.count.amount, - isLoading: countData.loading, - isSaving: false, - load: () => countData.count.amount, - error: countData.error, - }; - }, -})(CounterComponent as any)) +)( + graphql(COUNT_QUERY, { + name: 'countData', + props: ({ countData }: any) => { + const newlog = logger.child({ childName: 'UIController' }); + newlog.debug('count data : (%j)', countData); + return { + subscribeToCount: (params) => { + // logger.debug('count subscript data (%j)', params); + return countData.subscribeToMore({ + document: COUNT_SUBSCRIPTION, + variables: {}, + updateQuery: (prev: any, { subscriptionData }) => { + const payload = subscriptionData.data && subscriptionData.data.subscribeToWorkspace; + if (!payload) { + return prev; + } + return payload; + }, + }); + }, + counter: countData.count && countData.count.amount, + isLoading: countData.loading, + isSaving: false, + load: () => countData.count.amount, + error: countData.error, + }; + }, + })(CounterComponent as any), ); diff --git a/packages/sample-platform/browser/src/module.ts b/packages/sample-platform/browser/src/module.ts index 546693a14..7d8bdf592 100644 --- a/packages/sample-platform/browser/src/module.ts +++ b/packages/sample-platform/browser/src/module.ts @@ -1,7 +1,7 @@ import { Feature } from '@common-stack/client-react'; import { interfaces } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; import { ClientTypes as BrowserTypes } from '@common-stack/client-core'; import { platformModule } from './inversify-containers'; diff --git a/packages/sample-platform/server/package.json b/packages/sample-platform/server/package.json index 855976981..86dd1ccb6 100644 --- a/packages/sample-platform/server/package.json +++ b/packages/sample-platform/server/package.json @@ -19,7 +19,7 @@ "watch": "yarn build:lib:watch" }, "dependencies": { - "@sample-stack/core": "0.0.1" + "@sample-stack/core": "link:../../sample-core" }, "publishConfig": { "access": "public" @@ -27,4 +27,4 @@ "typescript": { "definition": "lib/index.d.ts" } -} \ No newline at end of file +} diff --git a/packages/sample-store/package.json b/packages/sample-store/package.json index b91fabf98..0173983db 100755 --- a/packages/sample-store/package.json +++ b/packages/sample-store/package.json @@ -19,7 +19,7 @@ "watch": "yarn build:lib:watch" }, "dependencies": { - "@sample-stack/core": "0.0.1", + "@sample-stack/core": "link:../sample-core", "knex": "^2.3.0", "sequelize": "^5.21.3" }, @@ -32,4 +32,4 @@ "typescript": { "definition": "lib/index.d.ts" } -} \ No newline at end of file +} diff --git a/portable-devices/browser-extension/package.json b/portable-devices/browser-extension/package.json index ed18b5679..84cc73ebf 100644 --- a/portable-devices/browser-extension/package.json +++ b/portable-devices/browser-extension/package.json @@ -34,14 +34,14 @@ }, "dependencies": { "@ant-design/icons": "^4.2.2", - "@apollo/client": "~3.7.1", + "@apollo/client": "^3.9.0", "@apollo/react-common": "^3.1.4", - "@cdm-logger/client": "^7.0.12", - "@common-stack/client-core": "0.2.33", - "@common-stack/client-react": "0.2.33", - "@common-stack/components-pro": "^0.3.1-alpha.1", - "@common-stack/core": "0.2.32", - "@common-stack/env-list-loader": "0.5.1-alpha.1", + "@cdm-logger/client": "^9.0.3", + "@common-stack/client-core": "6.0.2-alpha.2", + "@common-stack/client-react": "6.0.6-alpha.0", + "@common-stack/components-pro": "^6.0.2-alpha.2", + "@common-stack/core": "6.0.2-alpha.2", + "@common-stack/env-list-loader": "6.0.2-alpha.2", "@workbench-stack/components": "^2.2.1-alpha.3", "antd": "~4.24.1", "apollo-link-debounce": "^2.1.0", @@ -54,7 +54,6 @@ "body-parser": "^1.18.2", "browser-bunyan": "^1.6.3", "classnames": "^2.2.6", - "connected-react-router": "^6.9.1", "cors": "^2.8.5", "dotenv": "^8.2.0", "envalid": "~7.2.2", @@ -62,25 +61,21 @@ "flat": "^4.0.0", "graphql-tag": "^2.11.0", "graphql-ws": "^5.11.2", - "history": "^4.10.1", "immutability-helper": "^3.0.1", - "inversify": "^5.0.1", + "inversify": "^6.0.2", "isomorphic-fetch": "^2.2.1", "js-cookie": "^2.2.1", "lodash": "^4.17.15", "nanoid": "^1.3.1", "ramda": "^0.26.1", - "react": "18.0.0", - "react-dom": "18.0.0", + "react": "18.2.0", + "react-dom": "18.2.0", "react-helmet": "^6.1.0", "react-highlight": "^0.12.0", "react-highlight-words": "^0.16.0", "react-loadable": "^5.5.0", "react-redux": "^7.1.3", "react-resizable": "^1.10.1", - "react-router": "^5.3.3", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.1.2", "react-transition-group": "^4.3.0", "redux": "^4.0.5", "redux-logger": "^3.0.6", diff --git a/portable-devices/browser-extension/src/config/base-apollo-client.ts b/portable-devices/browser-extension/src/config/base-apollo-client.ts index 054226674..365898ef2 100755 --- a/portable-devices/browser-extension/src/config/base-apollo-client.ts +++ b/portable-devices/browser-extension/src/config/base-apollo-client.ts @@ -5,7 +5,7 @@ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable no-underscore-dangle */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { ApolloClient, ApolloClientOptions, ApolloLink, gql } from '@apollo/client'; +import { ApolloClient, ApolloClientOptions, ApolloLink, gql } from '@apollo/client/index.js'; import { InMemoryCache } from '@apollo/client/cache'; import { HttpLink, createHttpLink } from '@apollo/client/link/http'; import { BatchHttpLink } from '@apollo/client/link/batch-http'; diff --git a/portable-devices/browser-extension/src/config/newtab/client.service.ts b/portable-devices/browser-extension/src/config/newtab/client.service.ts index fbfac81d0..302b0161b 100644 --- a/portable-devices/browser-extension/src/config/newtab/client.service.ts +++ b/portable-devices/browser-extension/src/config/newtab/client.service.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ClientTypes } from '@common-stack/client-core'; import { Container } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; import modules, { container } from '../../modules/popup'; import { createApolloClient } from '../base-apollo-client'; import { PUBLIC_SETTINGS } from '../public-config'; diff --git a/portable-devices/browser-extension/src/config/options/client.service.ts b/portable-devices/browser-extension/src/config/options/client.service.ts index b85eaef62..b4dfede2c 100644 --- a/portable-devices/browser-extension/src/config/options/client.service.ts +++ b/portable-devices/browser-extension/src/config/options/client.service.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ClientTypes } from '@common-stack/client-core'; import { Container } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; import { CdmLogger } from '@cdm-logger/core'; import modules, { container, logger } from '../../modules/popup'; import { createApolloClient } from '../base-apollo-client'; diff --git a/portable-devices/browser-extension/src/config/panel/client.service.ts b/portable-devices/browser-extension/src/config/panel/client.service.ts index bb7d1a918..12bd75983 100644 --- a/portable-devices/browser-extension/src/config/panel/client.service.ts +++ b/portable-devices/browser-extension/src/config/panel/client.service.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ClientTypes } from '@common-stack/client-core'; import { Container } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; import modules, { container } from '../../modules/popup'; import { createApolloClient } from '../base-apollo-client'; import { PUBLIC_SETTINGS } from '../public-config'; diff --git a/portable-devices/browser-extension/src/config/popup/client.service.ts b/portable-devices/browser-extension/src/config/popup/client.service.ts index bb7d1a918..12bd75983 100644 --- a/portable-devices/browser-extension/src/config/popup/client.service.ts +++ b/portable-devices/browser-extension/src/config/popup/client.service.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ClientTypes } from '@common-stack/client-core'; import { Container } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; import modules, { container } from '../../modules/popup'; import { createApolloClient } from '../base-apollo-client'; import { PUBLIC_SETTINGS } from '../public-config'; diff --git a/portable-devices/desktop/package.json b/portable-devices/desktop/package.json index 9525b6098..9d4c97eb8 100644 --- a/portable-devices/desktop/package.json +++ b/portable-devices/desktop/package.json @@ -1,126 +1,121 @@ { - "name": "sample-stack-desktop-device", - "version": "0.0.1", - "private": true, - "description": "App is based on Electron, React, Redux and NodeJS as a back end", - "homepage": "https://github.com/cdmbase/fullstack-pro#readme", - "bugs": { - "url": "https://github.com/cdmbase/fullstack-pro/issues" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/cdmbase/fullstack-pro.git" - }, - "license": "MIT", - "author": { - "name": "CDMBase LLC", - "email": "jteidforyou@gmail.com" - }, - "main": "index.js", - "scripts": { - "build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' NODE_ENV=production electron-webpack", - "build:clean": "rimraf dist", - "electron": "electron dist/main/main.js", - "package": "electron-builder", - "release": "yarn build && electron-builder build --mac", - "release:linux": "yarn build && electron-builder build --linux", - "release:mac": "yarn build && electron-builder build --mac", - "release:win": "electron-builder build --win", - "start": "cross-env NODE_ENV=production electron dist/main/main.js", - "start:dev": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env electron dist/main/main.js", - "start:prod": "cross-env NODE_ENV=production ENV_FILE=../../config/development/dev.env electron dist/main/main.js", - "start:staging": "cross-env NODE_ENV=staging ENV_FILE=../../config/staging/staging.env electron dist/main/main.js", - "test": "echo Skipped.", - "watch": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env electron-webpack dev", - "watch:staging": "cross-env ENV_FILE=../../config/staging/staging.env electron-webpack dev" - }, - "resolutions": { - "webpack-sources": "^1.1.0" - }, - "dependencies": { - "@ant-design/compatible": "^1.0.5", - "@ant-design/icons": "^4.2.2", - "@apollo/client": "~3.7.1", - "@cdm-logger/client": "^7.0.12", - "@cdm-logger/electron": "^7.0.12", - "@cdm-logger/server": "^7.0.12", - "@common-stack/client-core": "0.5.1", - "@common-stack/client-react": "0.5.6", - "@common-stack/core": "0.5.1", - "@common-stack/server-core": "0.5.1", - "@sample-stack/core": "0.0.1", - "@sample-stack/counter-module-browser": "0.0.1", - "@sample-stack/counter-module-electron": "0.0.1", - "@sample-stack/platform-browser": "0.0.1", - "antd": "~5.1.7", - "apollo-link-debounce": "^3.0.0", - "apollo-link-logger": "^2.0.0", - "apollo-server-errors": "^3.3.1", - "check-internet-connected": "^2.0.5", - "classnames": "^2.2.6", - "connected-react-router": "^6.9.1", - "cors": "^2.8.5", - "cross-env": "^7.0.3", - "dotenv": "^8.2.0", - "electron-json-storage": "^2.0.0", - "electron-positioner": "^4.1.0", - "electron-redux": "^1.5.4", - "electron-updater": "4.3.9", - "envalid": "~7.2.2", - "esm": "^3.2.25", - "graphql": "^15.0.0", - "graphql-tag": "^2.11.0", - "graphql-ws": "^5.11.2", - "history": "^4.10.1", - "immutability-helper": "^3.0.1", - "inversify": "^5.0.1", - "inversify-binding-decorators": "^4.0.0", - "isomorphic-fetch": "^2.2.1", - "js-cookie": "^2.2.1", - "lodash": "^4.17.15", - "os-name": "^4.0.0", - "pify": "^2.3.0", - "ramda": "^0.26.1", - "react": "18.0.0", - "react-dom": "18.0.0", - "react-helmet": "^6.1.0", - "react-loadable": "^5.5.0", - "react-redux": "^7.1.3", - "react-router": "^5.3.3", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.3", - "react-transition-group": "^4.3.0", - "redux": "^4.0.5", - "redux-logger": "^3.0.6", - "redux-observable": "^1.2.0", - "redux-persist": "^6.0.0", - "redux-thunk": "^2.3.0", - "reflect-metadata": "^0.1.13", - "reselect": "^4.0.0", - "rxjs": "^6.5.3", - "rxjs-compat": "^6.5.3", - "rxjs-hooks": "^0.5.2", - "source-map-support": "^0.5.19", - "sqlite3": "^5.0.2", - "typeorm": "^0.2.32" - }, - "devDependencies": { - "@jest-runner/electron": "^3.0.1", - "cross-env": "^7.0.3", - "electron": "11.4.10", - "electron-builder": "^22.11.7", - "electron-debug": "^3.1.0", - "electron-devtools-installer": "^3.2.0", - "electron-is": "^3.0.0", - "electron-log": "^4.3.5", - "electron-webpack": "^2.8.2", - "electron-webpack-ts": "^4.0.1", - "pm2": "^5.2.2", - "rimraf": "^3.0.2", - "workspaces-utils": "^1.2.1" - }, - "buildAbout": { - "appName": "Fullstack-Pro", - "apiApp": "https://time-tracker-api.herokuapp.com/" - } -} \ No newline at end of file + "name": "sample-stack-desktop-device", + "version": "0.0.1", + "private": true, + "description": "App is based on Electron, React, Redux and NodeJS as a back end", + "homepage": "https://github.com/cdmbase/fullstack-pro#readme", + "bugs": { + "url": "https://github.com/cdmbase/fullstack-pro/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/cdmbase/fullstack-pro.git" + }, + "license": "MIT", + "author": { + "name": "CDMBase LLC", + "email": "jteidforyou@gmail.com" + }, + "main": "index.js", + "scripts": { + "build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' NODE_ENV=production electron-webpack", + "build:clean": "rimraf dist", + "electron": "electron dist/main/main.js", + "package": "electron-builder", + "release": "yarn build && electron-builder build --mac", + "release:linux": "yarn build && electron-builder build --linux", + "release:mac": "yarn build && electron-builder build --mac", + "release:win": "electron-builder build --win", + "start": "cross-env NODE_ENV=production electron dist/main/main.js", + "start:dev": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env electron dist/main/main.js", + "start:prod": "cross-env NODE_ENV=production ENV_FILE=../../config/development/dev.env electron dist/main/main.js", + "start:staging": "cross-env NODE_ENV=staging ENV_FILE=../../config/staging/staging.env electron dist/main/main.js", + "test": "echo Skipped.", + "watch": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env electron-webpack dev", + "watch:staging": "cross-env ENV_FILE=../../config/staging/staging.env electron-webpack dev" + }, + "resolutions": { + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "@ant-design/compatible": "^1.0.5", + "@ant-design/icons": "^4.2.2", + "@apollo/client": "^3.9.0", + "@cdm-logger/client": "^9.0.3", + "@cdm-logger/electron": "^9.0.3", + "@cdm-logger/server": "^9.0.3", + "@common-stack/client-core": "6.0.2-alpha.2", + "@common-stack/client-react": "6.0.6-alpha.0", + "@common-stack/core": "6.0.2-alpha.2", + "@common-stack/server-core": "6.0.6-alpha.0", + "@sample-stack/core": "link:../../packages/sample-core", + "@sample-stack/counter-module-browser": "link:../../packages-modules/counter/browser", + "@sample-stack/counter-module-electron": "link:../../packages-modules/counter/electron", + "@sample-stack/platform-browser": "link:../../packages/sample-platform/browser", + "antd": "^5.14.0", + "apollo-link-debounce": "^3.0.0", + "apollo-link-logger": "^2.0.0", + "apollo-server-errors": "^3.3.1", + "check-internet-connected": "^2.0.5", + "classnames": "^2.2.6", + "cors": "^2.8.5", + "cross-env": "^7.0.3", + "dotenv": "^8.2.0", + "electron-json-storage": "^2.0.0", + "electron-positioner": "^4.1.0", + "electron-redux": "^1.5.4", + "electron-updater": "4.3.9", + "envalid": "~7.2.2", + "esm": "^3.2.25", + "graphql": "^15.0.0", + "graphql-tag": "^2.11.0", + "graphql-ws": "^5.11.2", + "immutability-helper": "^3.0.1", + "inversify": "^6.0.2", + "inversify-binding-decorators": "^4.0.0", + "isomorphic-fetch": "^2.2.1", + "js-cookie": "^2.2.1", + "lodash": "^4.17.15", + "os-name": "^4.0.0", + "pify": "^2.3.0", + "ramda": "^0.26.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-helmet": "^6.1.0", + "react-loadable": "^5.5.0", + "react-redux": "^7.1.3", + "react-transition-group": "^4.3.0", + "redux": "^4.0.5", + "redux-logger": "^3.0.6", + "redux-observable": "^1.2.0", + "redux-persist": "^6.0.0", + "redux-thunk": "^2.3.0", + "reflect-metadata": "^0.1.13", + "reselect": "^4.0.0", + "rxjs": "^6.5.3", + "rxjs-compat": "^6.5.3", + "rxjs-hooks": "^0.5.2", + "source-map-support": "^0.5.19", + "sqlite3": "^5.0.2", + "typeorm": "^0.2.32" + }, + "devDependencies": { + "@jest-runner/electron": "^3.0.1", + "cross-env": "^7.0.3", + "electron": "11.4.10", + "electron-builder": "^22.11.7", + "electron-debug": "^3.1.0", + "electron-devtools-installer": "^3.2.0", + "electron-is": "^3.0.0", + "electron-log": "^4.3.5", + "electron-webpack": "^2.8.2", + "electron-webpack-ts": "^4.0.1", + "pm2": "^5.2.2", + "rimraf": "^3.0.2", + "workspaces-utils": "^1.2.1" + }, + "buildAbout": { + "appName": "Fullstack-Pro", + "apiApp": "https://time-tracker-api.herokuapp.com/" + } +} diff --git a/portable-devices/desktop/src/common/config/base-apollo-client.ts b/portable-devices/desktop/src/common/config/base-apollo-client.ts index 7f6c9b5f6..58b7ff641 100755 --- a/portable-devices/desktop/src/common/config/base-apollo-client.ts +++ b/portable-devices/desktop/src/common/config/base-apollo-client.ts @@ -2,7 +2,7 @@ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable no-underscore-dangle */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { ApolloClient, ApolloClientOptions, ApolloLink } from '@apollo/client'; +import { ApolloClient, ApolloClientOptions, ApolloLink } from '@apollo/client/index.js'; import { InMemoryCache } from '@apollo/client/cache'; import { HttpLink, createHttpLink } from '@apollo/client/link/http'; import { BatchHttpLink } from '@apollo/client/link/batch-http'; diff --git a/portable-devices/desktop/src/main/config/client.service.ts b/portable-devices/desktop/src/main/config/client.service.ts index a31e3dfb4..a80e70e53 100644 --- a/portable-devices/desktop/src/main/config/client.service.ts +++ b/portable-devices/desktop/src/main/config/client.service.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ClientTypes } from '@common-stack/client-core'; import { interfaces } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; import { CdmLogger } from '@cdm-logger/core'; import modules, { container, logger } from '../modules'; import { createApolloClient } from '../../common/config/base-apollo-client'; diff --git a/portable-devices/desktop/src/renderer/app/Main.tsx b/portable-devices/desktop/src/renderer/app/Main.tsx index 44bd58512..3ad7f01b6 100755 --- a/portable-devices/desktop/src/renderer/app/Main.tsx +++ b/portable-devices/desktop/src/renderer/app/Main.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ApolloProvider } from '@apollo/client'; +import { ApolloProvider } from '@apollo/client/index.js'; import { Provider } from 'react-redux'; import { PluginArea } from '@common-stack/client-react'; import { ConnectedRouter } from 'connected-react-router'; diff --git a/portable-devices/desktop/src/renderer/app/Tray.tsx b/portable-devices/desktop/src/renderer/app/Tray.tsx index cbc4eb566..e1c0ed9f8 100644 --- a/portable-devices/desktop/src/renderer/app/Tray.tsx +++ b/portable-devices/desktop/src/renderer/app/Tray.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { ApolloProvider } from '@apollo/client'; +import { ApolloProvider } from '@apollo/client/index.js'; import { Provider } from 'react-redux'; import { createClientContainer } from '../config/main/client.service'; import { epic$ } from '../config/tray/epic-config'; diff --git a/portable-devices/desktop/src/renderer/config/main/client.service.ts b/portable-devices/desktop/src/renderer/config/main/client.service.ts index 4ce7fc36d..1cddb396c 100644 --- a/portable-devices/desktop/src/renderer/config/main/client.service.ts +++ b/portable-devices/desktop/src/renderer/config/main/client.service.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ClientTypes } from '@common-stack/client-core'; import { Container } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; import { CdmLogger } from '@cdm-logger/core'; import { logger } from '@cdm-logger/client'; import modules, { container } from '../../modules/main'; diff --git a/portable-devices/desktop/src/renderer/config/tray/client.service.ts b/portable-devices/desktop/src/renderer/config/tray/client.service.ts index 103330773..bff0de449 100644 --- a/portable-devices/desktop/src/renderer/config/tray/client.service.ts +++ b/portable-devices/desktop/src/renderer/config/tray/client.service.ts @@ -3,7 +3,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ClientTypes } from '@common-stack/client-core'; import { Container } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { ApolloClient } from '@apollo/client/index.js'; import { CdmLogger } from '@cdm-logger/core'; import { logger } from '@cdm-logger/client'; import modules, { container } from '../../modules/tray'; diff --git a/portable-devices/mobile/.gitignore b/portable-devices/mobile/.gitignore index 73e9e9457..fef5afeaf 100644 --- a/portable-devices/mobile/.gitignore +++ b/portable-devices/mobile/.gitignore @@ -11,3 +11,64 @@ web-build/ # macOS .DS_Store + +# @generated expo-cli sync-e7dcf75f4e856f7b6f3239b3f3a7dd614ee755a8 +# The following patterns were generated by expo-cli + +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof + +# node.js +# +node_modules/ +npm-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +*.keystore +!debug.keystore + +# Bundle artifacts +*.jsbundle + +# CocoaPods +/ios/Pods/ + +# Expo +.expo/ +web-build/ +dist/ + +# @end expo-cli \ No newline at end of file diff --git a/portable-devices/mobile/App.tsx b/portable-devices/mobile/App.tsx index 4d94323f0..592b76d01 100644 --- a/portable-devices/mobile/App.tsx +++ b/portable-devices/mobile/App.tsx @@ -1,4 +1,7 @@ import './src/config/public-config'; import App from './src/App'; +import 'react-native-reanimated'; +import 'react-native-url-polyfill/auto' +import 'react-native-get-random-values'; export default App; diff --git a/portable-devices/mobile/app.json b/portable-devices/mobile/app.json index f5aff8cbb..f3a5d6f09 100644 --- a/portable-devices/mobile/app.json +++ b/portable-devices/mobile/app.json @@ -13,21 +13,6 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" }, - "plugins": [ - [ - "expo-build-properties", - { - "android": { - "compileSdkVersion": 31, - "targetSdkVersion": 31, - "buildToolsVersion": "31.0.0" - }, - "ios": { - "deploymentTarget": "13.0" - } - } - ] - ], "updates": { "fallbackToCacheTimeout": 0 }, diff --git a/portable-devices/mobile/babel.config.js b/portable-devices/mobile/babel.config.js index 75558b534..8537c890b 100644 --- a/portable-devices/mobile/babel.config.js +++ b/portable-devices/mobile/babel.config.js @@ -7,6 +7,7 @@ module.exports = function (api) { 'module:react-native-dotenv', { moduleName: '@env', + "allowUndefined": true, ...(!!process.env.ENV_FILE && {path: process.env.ENV_FILE}) }, ], diff --git a/portable-devices/mobile/index.js b/portable-devices/mobile/index.js index 8c91580d0..06b4e7915 100644 --- a/portable-devices/mobile/index.js +++ b/portable-devices/mobile/index.js @@ -4,6 +4,7 @@ import { registerRootComponent } from 'expo'; import './shim'; import 'react-native-reanimated'; // to fix web crash +import 'react-native-gesture-handler'; import App from './App'; diff --git a/portable-devices/mobile/package.json b/portable-devices/mobile/package.json index 29b9ff568..7a8cffdae 100644 --- a/portable-devices/mobile/package.json +++ b/portable-devices/mobile/package.json @@ -1,162 +1,163 @@ { - "name": "sample-stack-mobile-device", - "version": "0.0.1", - "private": true, - "main": "index.js", - "scripts": { - "android": "expo run:android", - "build": "yarn easBuild --profile development", - "build:all": "yarn build -p all", - "build:web": "cross-env GENERATE_SOURCEMAP=false expo export:web", - "build:android": "yarn build -p android --clear-cache", - "build:auto": "yarn build:all --non-interactive", - "build:clean": "rimraf build .expo .tmp", - "build:configure": "eas build:configure", - "build:ios": "yarn build -p ios --clear-cache", - "build:preview": "yarn easBuild --profile preview", - "build:preview:all": "yarn build:preview -p all --non-interactive", - "build:preview:android": "yarn build:preview -p android --non-interactive", - "build:preview:ios": "yarn build:preview -p ios --non-interactive", - "build:previewLocal": "yarn easBuild --profile previewlocal", - "build:previewLocal:all": "yarn build:previewLocal -p all --non-interactive", - "build:previewLocal:android": "yarn build:previewLocal -p android --non-interactive", - "build:previewLocal:ios": "yarn build:previewLocal -p ios --non-interactive", - "build:previewSubmit": "yarn easBuild --profile previewSubmit --non-interactive", - "build:previewSubmit:ios": "yarn build:previewSubmit -p ios --auto-submit", - "build:previewSubmit:android": "yarn build:previewSubmit -p android --auto-submit", - "build:previewSubmit:all": "yarn build:previewSubmit -p all --auto-submit", - "build:prod": "yarn easBuild --profile production --non-interactive", - "build:prod:all": "yarn build:prod -p all", - "build:prod:android": "yarn build:prod -p android", - "build:prod:ios": "yarn build:prod -p ios", - "build:prod:manual": "yarn easBuild --profile production", - "build:prodSubmit:all": "yarn build:prod:all --auto-submit", - "build:prodSubmit:android": "yarn build:prod:android --auto-submit", - "build:prodSubmit:ios": "yarn build:prod:ios --auto-submit", - "build:stage": "yarn easBuild --profile staging -p ios --non-interactive", - "cli": "node ../../tools/cli", - "eas-build-post-install": "lerna exec --scope=@sample-stack/core --scope=@sample-stack/counter-module-mobile yarn build", - "easBuild": "eas build", - "eslint": "eslint --fix --ext js --ext jsx --ext json src", - "exp-login": "cross-env NODE_ENV=production expo login -u $EXP_USERNAME -p $EXP_PASSWORD --non-interactive", - "exp-publish": "yarn exp-login && yarn expo p --non-interactive", - "ios": "expo run:ios", - "lint": "yarn eslint && yarn tslint", - "start": "expo start --dev-client", - "submit:all": "eas submit -p all", - "submit:android": "eas submit -p android", - "submit:ios": "eas submit -p ios", - "test": "yarn tests && yarn lint", - "tests": "jest", - "tests:watch": "jest --watch", - "tslint": "tslint --fix -p tsconfig.json -c ../../tslint.json", - "watch": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env expo start --clear", - "watch:android": "expo start --android", - "watch:ios": "expo start --ios" - }, - "resolutions": { - "@types/react": "^18.0.0" - }, - "dependencies": { - "@apollo/client": "~3.7.1", - "@cdm-logger/client": "^7.0.12", - "@common-stack/client-core": "^0.5.1", - "@common-stack/client-react": "^0.5.6", - "@common-stack/core": "^0.5.1", - "@expo/vector-icons": "~13.0.0", - "@react-native-async-storage/async-storage": "~1.17.3", - "@react-native-community/cameraroll": "~4.1.2", - "@react-native-community/masked-view": "~0.1.10", - "@react-native-community/netinfo": "9.3.0", - "@react-native-community/picker": "~1.8.1", - "@react-native-community/segmented-control": "~2.2.2", - "@react-native-community/slider": "4.2.3", - "@react-navigation/bottom-tabs": "^6.3.1", - "@react-navigation/material-top-tabs": "^6.2.1", - "@react-navigation/native": "^6.0.10", - "@react-navigation/native-stack": "^6.6.2", - "@react-navigation/stack": "^6.2.1", - "@sample-stack/counter-module-mobile": "0.0.1", - "apollo-link-debounce": "^3.0.0", - "apollo-link-logger": "^2.0.0", - "apollo-server-errors": "^3.3.1", - "big-integer": "^1.6.51", - "connected-react-router": "^6.9.1", - "expo": "~46.0.16", - "expo-asset": "~8.6.1", - "expo-build-properties": "~0.3.0", - "expo-constants": "~13.2.4", - "expo-dev-client": "~1.3.1", - "expo-device": "~4.3.0", - "expo-file-system": "~14.1.0", - "expo-image-picker": "~13.3.1", - "expo-linking": "~3.2.2", - "expo-localization": "~13.1.0", - "expo-notifications": "~0.16.1", - "expo-random": "~12.3.0", - "expo-splash-screen": "~0.16.2", - "expo-status-bar": "~1.4.0", - "expo-updates": "~0.14.6", - "expo-web-browser": "~11.0.0", - "graphql-ws": "^5.11.2", - "history": "^4.10.1", - "immutability-helper": "^3.0.1", - "inversify": "^5.0.1", - "isomorphic-fetch": "^2.2.1", - "lodash": "^4.17.4", - "metro-minify-terser": "^0.56.0", - "minilog": "^3.1.0", - "native-base": "~3.2.2", - "prop-types": "^15.6.0", - "ramda": "^0.26.1", - "react": "18.0.0", - "react-dom": "18.0.0", - "react-helmet": "^6.1.0", - "react-loadable": "^5.5.0", - "react-native": "0.69.6", - "react-native-dotenv": "^3.3.1", - "react-native-gesture-handler": "~2.5.0", - "react-native-keyboard-aware-scroll-view": "^0.9.3", - "react-native-keyboard-spacer": "^0.4.1", - "react-native-maps": "~0.31.1", - "react-native-mime-types": "^2.3.0", - "react-native-modal": "^11.6.1", - "react-native-reanimated": "~2.9.1", - "react-native-safe-area-context": "^4.3.1", - "react-native-screens": "~3.15.0", - "react-native-svg": "~12.3.0", - "react-native-web": "0.18.9", - "react-native-web-maps": "~0.3.0", - "react-redux": "^7.1.3", - "react-router": "^5.3.3", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.3", - "react-router-native": "^5.3.3", - "redux": "^4.0.5", - "redux-logger": "^3.0.6", - "redux-observable": "^1.2.0", - "redux-persist": "^6.0.0", - "redux-thunk": "^2.3.0", - "reselect": "^4.0.0", - "rxjs": "^6.5.3", - "rxjs-compat": "^6.5.3", - "rxjs-hooks": "^0.5.2", - "sentry-expo": "~5.0.0", - "text-encoding-polyfill": "^0.6.7" - }, - "devDependencies": { - "@babel/core": "^7.20.2", - "@expo/config-plugins": "^5.0.0", - "@expo/dev-server": "0.1.120", - "@expo/webpack-config": "~18.0.1", - "@testing-library/react-native": "^9.1.0", - "@types/react-native": "~0.64.12", - "@types/react-native-dotenv": "^0.2.0", - "@types/react-native-keyboard-spacer": "^0.4.1", - "@types/react-router-native": "^5.1.0", - "typescript": "~4.7.3" - }, - "peerDependencies": { - "webpack": "*" - } -} \ No newline at end of file + "name": "sample-stack-mobile-device", + "version": "0.0.1", + "private": true, + "main": "index.js", + "scripts": { + "android": "expo run:android", + "build": "yarn easBuild --profile development", + "build:all": "yarn build -p all", + "build:android": "yarn build -p android --clear-cache", + "build:auto": "yarn build:all --non-interactive", + "build:clean": "rimraf build .expo .tmp", + "build:configure": "eas build:configure", + "build:ios": "yarn build -p ios --clear-cache", + "build:preview": "yarn easBuild --profile preview", + "build:preview:all": "yarn build:preview -p all --non-interactive", + "build:preview:android": "yarn build:preview -p android --non-interactive", + "build:preview:ios": "yarn build:preview -p ios --non-interactive", + "build:previewLocal": "yarn easBuild --profile previewlocal", + "build:previewLocal:all": "yarn build:previewLocal -p all --non-interactive", + "build:previewLocal:android": "yarn build:previewLocal -p android --non-interactive", + "build:previewLocal:ios": "yarn build:previewLocal -p ios --non-interactive", + "build:previewSubmit": "yarn easBuild --profile previewSubmit --non-interactive", + "build:previewSubmit:all": "yarn build:previewSubmit -p all --auto-submit", + "build:previewSubmit:android": "yarn build:previewSubmit -p android --auto-submit", + "build:previewSubmit:ios": "yarn build:previewSubmit -p ios --auto-submit", + "build:prod": "yarn easBuild --profile production --non-interactive", + "build:prod:all": "yarn build:prod -p all", + "build:prod:android": "yarn build:prod -p android", + "build:prod:ios": "yarn build:prod -p ios", + "build:prod:manual": "yarn easBuild --profile production", + "build:prodSubmit:all": "yarn build:prod:all --auto-submit", + "build:prodSubmit:android": "yarn build:prod:android --auto-submit", + "build:prodSubmit:ios": "yarn build:prod:ios --auto-submit", + "build:stage": "yarn easBuild --profile staging -p ios --non-interactive", + "build:web": "cross-env GENERATE_SOURCEMAP=false expo export:web", + "cli": "node ../../tools/cli", + "eas-build-post-install": "lerna exec --scope=@sample-stack/core --scope=@sample-stack/counter-module-mobile yarn build", + "easBuild": "eas build", + "eslint": "eslint --fix --ext js --ext jsx --ext json src", + "exp-login": "cross-env NODE_ENV=production expo login -u $EXP_USERNAME -p $EXP_PASSWORD --non-interactive", + "exp-publish": "yarn exp-login && yarn expo p --non-interactive", + "ios": "expo run:ios", + "lint": "yarn eslint && yarn tslint", + "start": "expo start --dev-client", + "submit:all": "eas submit -p all", + "submit:android": "eas submit -p android", + "submit:ios": "eas submit -p ios", + "test": "yarn tests && yarn lint", + "tests": "jest", + "tests:watch": "jest --watch", + "tslint": "tslint --fix -p tsconfig.json -c ../../tslint.json", + "watch": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env expo start --clear --localhost", + "watch:android": "expo start --android", + "watch:ios": "expo start --ios", + "watch:mobile": "cross-env NODE_ENV=development ENV_FILE=../../config/development/mobile.dev.env expo start --clear" + }, + "resolutions": { + "@expo/config-plugins": "~6.0.0", + "@expo/prebuild-config": "~6.0.0", + "expo-modules-autolinking": "~1.1.0" + }, + "dependencies": { + "@apollo/client": "^3.9.0", + "@cdm-logger/client": "^9.0.3", + "@common-stack/client-core": "^6.0.2-alpha.2", + "@common-stack/client-react": "^6.0.6-alpha.0", + "@common-stack/core": "^6.0.2-alpha.2", + "@expo/vector-icons": "~13.0.0", + "@react-native-async-storage/async-storage": "1.17.11", + "@react-native-community/cameraroll": "~4.1.2", + "@react-native-community/datetimepicker": "6.7.3", + "@react-native-community/masked-view": "~0.1.10", + "@react-native-community/netinfo": "9.3.7", + "@react-native-community/picker": "~1.8.1", + "@react-native-community/segmented-control": "~2.2.2", + "@react-native-community/slider": "4.4.2", + "@react-navigation/bottom-tabs": "^6.3.1", + "@react-navigation/drawer": "6.5.3", + "@react-navigation/native": "^6.0.10", + "@react-navigation/native-stack": "^6.6.2", + "@react-navigation/stack": "^6.2.1", + "@sample-stack/counter-module-mobile": "link:../../packages-modules/counter/mobile", + "apollo-link-debounce": "^3.0.0", + "apollo-link-logger": "^2.0.0", + "apollo-server-errors": "^3.3.1", + "big-integer": "^1.6.51", + "expo": "~48.0.20", + "expo-asset": "~8.9.1", + "expo-build-properties": "~0.6.0", + "expo-constants": "~14.2.1", + "expo-dev-client": "~2.2.1", + "expo-device": "~5.2.1", + "expo-file-system": "~15.2.2", + "expo-image-picker": "~14.1.1", + "expo-linking": "~4.0.1", + "expo-localization": "~14.1.1", + "expo-notifications": "~0.18.1", + "expo-random": "~13.1.1", + "expo-secure-store": "~12.1.1", + "expo-splash-screen": "~0.18.2", + "expo-status-bar": "~1.4.0", + "expo-updates": "~0.16.4", + "expo-web-browser": "~12.1.1", + "graphql-ws": "^5.11.2", + "immutability-helper": "^3.0.1", + "inversify": "^6.0.2", + "isomorphic-fetch": "^2.2.1", + "lodash": "^4.17.4", + "metro-minify-terser": "^0.56.0", + "minilog": "^3.1.0", + "native-base": "~3.4.19", + "prop-types": "^15.6.0", + "query-string": "7.0.1", + "ramda": "^0.26.1", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-helmet": "^6.1.0", + "react-loadable": "^5.5.0", + "react-native": "0.71.14", + "react-native-dotenv": "^3.3.1", + "react-native-gesture-handler": "~2.9.0", + "react-native-get-random-values": "~1.10.0", + "react-native-keyboard-aware-scroll-view": "^0.9.3", + "react-native-keyboard-spacer": "^0.4.1", + "react-native-maps": "1.3.2", + "react-native-mime-types": "^2.3.0", + "react-native-modal": "^11.6.1", + "react-native-pager-view": "6.1.2", + "react-native-reanimated": "~2.14.4", + "react-native-safe-area-context": "4.5.0", + "react-native-screens": "~3.20.0", + "react-native-svg": "13.4.0", + "react-native-url-polyfill": "^2.0.0", + "react-native-web": "~0.18.10", + "react-native-web-maps": "~0.3.0", + "react-redux": "^7.1.3", + "redux": "^4.0.5", + "redux-logger": "^3.0.6", + "redux-observable": "^1.2.0", + "redux-persist": "^6.0.0", + "redux-thunk": "^2.3.0", + "reselect": "^4.0.0", + "rxjs": "^6.5.3", + "rxjs-compat": "^6.5.3", + "rxjs-hooks": "^0.5.2", + "sentry-expo": "~6.2.0", + "text-encoding-polyfill": "^0.6.7" + }, + "devDependencies": { + "@babel/core": "^7.20.2", + "@expo/config-plugins": "^6.0.0", + "@expo/webpack-config": "^18.0.1", + "@testing-library/react-native": "^9.1.0", + "@types/react-native-dotenv": "^0.2.0", + "@types/react-native-keyboard-spacer": "^0.4.1", + "@types/react-router-native": "^5.1.0", + "typescript": "~4.9.5" + }, + "peerDependencies": { + "webpack": "*" + } +} diff --git a/portable-devices/mobile/polyfills/browser.js b/portable-devices/mobile/polyfills/browser.js new file mode 100644 index 000000000..928b792f3 --- /dev/null +++ b/portable-devices/mobile/polyfills/browser.js @@ -0,0 +1,53 @@ +(global => { + + const { navigator } = global; + if (navigator) { + // userAgent + // + // Required by: + // - lib-jitsi-meet/modules/browser/BrowserDetection.js + let userAgent = navigator.userAgent || ''; + + // react-native/version + const { name, version } = require('react-native/package.json'); + let rn = name || 'react-native'; + + version && (rn += `/${version}`); + if (userAgent.indexOf(rn) === -1) { + userAgent = userAgent ? `${rn} ${userAgent}` : rn; + } + + // (OS version) + const os = `(${Platform.OS} ${Platform.Version})`; + + if (userAgent.indexOf(os) === -1) { + userAgent = userAgent ? `${userAgent} ${os}` : os; + } + + navigator.userAgent = userAgent; + } + + // // CallStats + // // + // // Required by: + // // - lib-jitsi-meet + // require('react-native-callstats/csio-polyfill'); + // global.callstats = require('react-native-callstats/callstats'); + + + // // Timers + // // + // // React Native's timers won't run while the app is in the background, this + // // is a known limitation. Replace them with a background-friendly + // // alternative. + // // + // // Required by: + // // - lib-jitsi-meet + // // - Strophe + // global.clearTimeout = BackgroundTimer.clearTimeout.bind(BackgroundTimer); + // global.clearInterval = BackgroundTimer.clearInterval.bind(BackgroundTimer); + // global.setInterval = BackgroundTimer.setInterval.bind(BackgroundTimer); + // global.setTimeout = (fn, ms = 0) => BackgroundTimer.setTimeout(fn, ms); + + +})(global || window || this); // eslint-disable-line no-invalid-this \ No newline at end of file diff --git a/portable-devices/mobile/src/App.tsx b/portable-devices/mobile/src/App.tsx index ecaeb9bdb..03ba54884 100644 --- a/portable-devices/mobile/src/App.tsx +++ b/portable-devices/mobile/src/App.tsx @@ -1,68 +1,64 @@ import React from 'react'; -import { NativeRouter } from 'react-router-native'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { StyleSheet } from 'react-native'; -import { ApolloProvider } from '@apollo/client'; +import { ApolloProvider } from '@apollo/client/index.js'; import { Provider } from 'react-redux'; -import { MainRoute } from './modules/modules'; +import { SlotFillProvider } from '@common-stack/components-pro'; +import { NativeBaseProvider } from 'native-base'; +import { InversifyProvider, PluginArea } from '@common-stack/client-react'; + +import modules, { MainRoute } from './modules/modules'; import { persistStore } from 'redux-persist'; import { PersistGate } from 'redux-persist/integration/react'; -import { ConnectedRouter } from 'connected-react-router'; -import { - createReduxStore, - history, -} from './config/redux-config'; -import { NativeBaseProvider } from 'native-base'; +import { createReduxStore, history } from './config/redux-config'; import { createClientContainer } from './config/client.service'; -const { apolloClient: client, container } = createClientContainer(); - -const store = createReduxStore(); +const { apolloClient: client, container, serviceFunc } = createClientContainer(); +const { store } = createReduxStore(history, client, serviceFunc(), container); export default function App() { - - let persistor = persistStore(store as any); - return ( - - - - - - - - - - - - - - - - ); + let persistor = persistStore(store as any); + return ( + + + + + + + + + + + + + + + + ); } const styles = StyleSheet.create({ - container: { + container: { marginTop: 25, - padding: 10, - }, - header: { + padding: 10, + }, + header: { fontSize: 20, - }, - nav: { + }, + nav: { flexDirection: 'row', - justifyContent: 'space-around', - }, - navItem: { + justifyContent: 'space-around', + }, + navItem: { flex: 1, - alignItems: 'center', - padding: 10, - }, - subNavItem: { + alignItems: 'center', + padding: 10, + }, + subNavItem: { padding: 5, - }, - topic: { + }, + topic: { textAlign: 'center', - fontSize: 15, - }, + fontSize: 15, + }, }); diff --git a/portable-devices/mobile/src/config/base-apollo-client.ts b/portable-devices/mobile/src/config/base-apollo-client.ts index a0a8724d6..6b31074d0 100755 --- a/portable-devices/mobile/src/config/base-apollo-client.ts +++ b/portable-devices/mobile/src/config/base-apollo-client.ts @@ -2,7 +2,7 @@ /* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable no-underscore-dangle */ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ -import { ApolloClient, ApolloClientOptions, ApolloLink } from '@apollo/client'; +import { ApolloClient, ApolloClientOptions, ApolloLink } from '@apollo/client/index.js'; import { InMemoryCache } from '@apollo/client/cache'; import { HttpLink, createHttpLink } from '@apollo/client/link/http'; import { BatchHttpLink } from '@apollo/client/link/batch-http'; @@ -104,7 +104,8 @@ export const createApolloClient = ({ return param; }; - let timedOut, activeSocket; + let timedOut: NodeJS.Timeout; + let activeSocket: unknown; const wsLink = new GraphQLWsLink( createClient({ diff --git a/portable-devices/mobile/src/config/base-redux-config.ts b/portable-devices/mobile/src/config/base-redux-config.ts index 164bfa3fa..7e4388f02 100644 --- a/portable-devices/mobile/src/config/base-redux-config.ts +++ b/portable-devices/mobile/src/config/base-redux-config.ts @@ -14,6 +14,7 @@ import { Action, ReducersMapObject, PreloadedState, + Store, } from 'redux'; import { EpicMiddleware, Epic } from 'redux-observable'; import { persistReducer, PersistConfig } from 'redux-persist'; @@ -88,6 +89,7 @@ export const createReduxStore = ({ ((isDev || isDebug) && isBrowser && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose; const rootReducer = combineReducers(reducers); + const persistedReducer = persistConfig ? persistReducer(persistConfig, rootReducer) : rootReducer; const store = createStore(persistedReducer, initialState, composeEnhancers(...enhancers())); diff --git a/portable-devices/mobile/src/config/client.service.ts b/portable-devices/mobile/src/config/client.service.ts index 8deaf36aa..05adff39c 100644 --- a/portable-devices/mobile/src/config/client.service.ts +++ b/portable-devices/mobile/src/config/client.service.ts @@ -1,47 +1,92 @@ +/* eslint-disable jest/require-hook */ /* eslint-disable no-underscore-dangle */ /* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable import/no-extraneous-dependencies */ import { ClientTypes } from '@common-stack/client-core'; -import { Container } from 'inversify'; -import { ApolloClient } from '@apollo/client'; +import { Container, interfaces } from 'inversify'; +import { ApolloClient, NormalizedCacheObject } from '@apollo/client/index.js'; import { CdmLogger } from '@cdm-logger/core'; -import modules, { container, logger } from '../modules'; +import { merge } from 'lodash'; +import modules, { UtilityClass, logger } from '../modules'; import { createApolloClient } from './base-apollo-client'; import { PUBLIC_SETTINGS } from './public-config'; let __CLIENT_SERVICE__: { apolloClient: ApolloClient; container: Container; - services: any; - logger: CdmLogger.ILogger + serviceFunc: () => any; + logger: CdmLogger.ILogger; }; -export const createClientContainer = () => { - if (__CLIENT_SERVICE__) { - return __CLIENT_SERVICE__; - } - const clientState = modules.getStateParams({ resolverContex: () => modules.createService({}, {}) }); - const { cache, apolloClient } = createApolloClient({ +const initialState = __CLIENT__ ? { ...window.__APOLLO_STATE__ } : {}; +const utility = new UtilityClass(modules); + +const container = modules.createContainers({}) as Container; +container.bind(ClientTypes.Logger).toConstantValue(logger); +container.bind(ClientTypes.UtilityClass).toConstantValue(utility); + +export const createClientContainer = (req?: any, res?: any) => { + logger.debug('Calling CreateClientContainer'); + + // const childContainer = container.createChild(); + const childContainer = container; + + childContainer + .bind(ClientTypes.ApolloClient) + .toDynamicValue((context) => new Error('Too early to bind ApolloClient')) + .inRequestScope(); + childContainer + .bind<() => ApolloClient>(ClientTypes.ApolloClientFactory) + .toFactory>((context: interfaces.Context) => () => { + const newClient = childContainer.get>(ClientTypes.ApolloClient); + return newClient; + }); + const services = merge( + { container: childContainer }, + ...modules.createServiceFunc.map((serviceFunc) => serviceFunc(childContainer)), + ); + const clientState = modules.getStateParams({ + resolverContex: () => services, + container: childContainer, + requestResponsePair: { + req, + res + } + }); + const { apolloClient, cache } = createApolloClient({ httpGraphqlURL: PUBLIC_SETTINGS.GRAPHQL_URL, - httpLocalGraphqlURL: PUBLIC_SETTINGS.LOCAL_GRAPHQL_URL, + httpLocalGraphqlURL: PUBLIC_SETTINGS.LOCAL_GRAPHQL_URL as any, isDev: process.env.NODE_ENV === 'development', isDebug: __DEBUGGING__, isSSR: __SSR__, - scope: 'browser', + scope: __CLIENT__ ? 'browser' : 'server', clientState, getDataIdFromObject: (result) => modules.getDataIdFromObject(result), - initialState: null, + initialState, logger, }); - // attaching the context to client as a workaround. - container.bind(ClientTypes.ApolloClient).toConstantValue(apolloClient); - container.bind(ClientTypes.InMemoryCache).toConstantValue(cache); - const services = modules.createService({}, {}); - (apolloClient as any).container = services; + childContainer + .bind(ClientTypes.InMemoryCache) + .toDynamicValue((context) => cache) + .inRequestScope(); + childContainer + .rebind(ClientTypes.ApolloClient) + .toDynamicValue((context) => apolloClient) + .inRequestScope(); + // const services = serviceFunc(); + const serviceFunc = () => services; + (apolloClient as any).container = services; __CLIENT_SERVICE__ = { - container, + container: childContainer, apolloClient, - services, + serviceFunc, logger, }; + if ((module as any).hot) { + (module as any).hot.dispose(() => { + // Force Apollo to fetch the latest data from the server + delete window.__APOLLO_STATE__; + }); + } return __CLIENT_SERVICE__; }; diff --git a/portable-devices/mobile/src/config/index.ts b/portable-devices/mobile/src/config/index.ts index b2ca98492..54ddc2749 100644 --- a/portable-devices/mobile/src/config/index.ts +++ b/portable-devices/mobile/src/config/index.ts @@ -1,7 +1,9 @@ -import { LOG_LEVEL } from '@env'; +import { + LOG_LEVEL +} from '@env'; const config = { - LOG_LEVEL, + LOG_LEVEL }; export default config; diff --git a/portable-devices/mobile/src/config/redux-config.ts b/portable-devices/mobile/src/config/redux-config.ts index 528ba2340..00450ec90 100644 --- a/portable-devices/mobile/src/config/redux-config.ts +++ b/portable-devices/mobile/src/config/redux-config.ts @@ -1,26 +1,28 @@ import storage from '@react-native-async-storage/async-storage'; import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2'; import { createEpicMiddleware } from 'redux-observable'; -import { connectRouter, routerMiddleware } from 'connected-react-router'; import { REDUX_PERSIST_KEY } from '@common-stack/client-core'; import { createReduxStore as createBaseReduxStore } from './base-redux-config'; import modules, { logger } from '../modules'; import { rootEpic } from './epic-config'; import history from './router-history'; -import { createClientContainer } from './client.service'; export { history }; -const { apolloClient, container, services } = createClientContainer(); -export const epicMiddleware = createEpicMiddleware({ - dependencies: { - apolloClient, - routes: modules.getConfiguredRoutes(), - services, - container, - logger, - }, -}); +export const epicMiddlewareFunc = (apolloClient, services, container) => + createEpicMiddleware({ + dependencies: { + apolloClient, + routes: modules.getConfiguredRoutes(), + services, + container, + logger, + config: { + loadRoot: true, + isMobile: true, + }, + }, + }); export const persistConfig = { key: REDUX_PERSIST_KEY, @@ -33,22 +35,30 @@ export const persistConfig = { * Add any reducers required for this app dirctly in to * `combineReducers` */ -export const createReduxStore = () => { +export const createReduxStore = (history, apolloClient, services, container) => { // middleware - const router = connectRouter(history); - const store = createBaseReduxStore({ scope: 'browser', isDebug: __DEBUGGING__, isDev: process.env.NODE_ENV === 'development', initialState: {}, persistConfig, - middleware: [routerMiddleware(history)], - epicMiddleware, + middleware: [], + epicMiddleware: epicMiddlewareFunc(apolloClient, services, container), rootEpic: rootEpic as any, - reducers: { router, ...modules.reducers }, + reducers: { ...modules.reducers }, }); - container.bind('ReduxStore').toConstantValue(store); + if (container.isBound('ReduxStore')) { + container + .rebind('ReduxStore') + .toDynamicValue(() => store) + .inRequestScope(); + } else { + container + .bind('ReduxStore') + .toDynamicValue(() => store) + .inRequestScope(); + } - return store; + return { store }; }; diff --git a/portable-devices/mobile/src/modules/index.ts b/portable-devices/mobile/src/modules/index.ts index ff3d29cef..c08565f92 100644 --- a/portable-devices/mobile/src/modules/index.ts +++ b/portable-devices/mobile/src/modules/index.ts @@ -1,14 +1,13 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable import/no-cycle */ /* eslint-disable @typescript-eslint/no-var-requires */ -import { ClientTypes } from '@common-stack/client-react'; -import { logger } from '@cdm-logger/client'; +import { ClientLogger } from '@cdm-logger/client'; import modules, { MainRoute } from './modules'; import { navigate } from './navigator'; Object.assign(global, require('../../build.config')); - -class UtilityClass { +export class UtilityClass { // tslint:disable-next-line:no-shadowed-variable constructor(private modules) {} @@ -16,16 +15,14 @@ class UtilityClass { return this.modules.getDataIdFromObject(storeObj); } - public navigate(name: string, params: never){ + public navigate(name: string, params: never) { return navigate(name, params); } } -const utility = new UtilityClass(modules); -// additional bindings to container -const container = modules.createContainers({}) as any; -container.bind(ClientTypes.Logger).toConstantValue(logger); -container.bind(ClientTypes.UtilityClass).toConstantValue(utility); +const logger = ClientLogger.create(process.env.APP_NAME || 'Fullstack-Pro', { + level: (process.env.LOG_LEVEL as any) || 'info', +}); export default modules; -export { MainRoute, container, logger }; +export { MainRoute, logger }; diff --git a/portable-devices/mobile/webpack.config.js b/portable-devices/mobile/webpack.config.js index 813439406..5bcaee619 100644 --- a/portable-devices/mobile/webpack.config.js +++ b/portable-devices/mobile/webpack.config.js @@ -35,6 +35,7 @@ module.exports = async function (env, argv) { new EnvListPlugin.Plugin(), ); config.plugins.push( + new EnvListPlugin.Plugin(), new webpack.DefinePlugin({ __CLIENT__: true, __DEBUGGING__: false, diff --git a/rollup.config.base.mjs b/rollup.config.base.mjs new file mode 100644 index 000000000..20f78f7be --- /dev/null +++ b/rollup.config.base.mjs @@ -0,0 +1,147 @@ +// rollup.config.base.js +import graphql from '@rollup/plugin-graphql'; +import image from '@rollup/plugin-image'; +import typescript from '@rollup/plugin-typescript'; +import { string } from 'rollup-plugin-string'; +import { copy } from '@web/rollup-plugin-copy'; +import modifyLibFilesPlugin from '@common-stack/rollup-vite-utils/lib/rollup/rollupPluginModifyLibFiles.js'; +import generateJsonFromObject from '@common-stack/rollup-vite-utils/lib/rollup/rollupPluginGenerateJson.js'; +import addJsExtensionToImportsPlugin from '@common-stack/rollup-vite-utils/lib/rollup/rollupPluginAddJsExtension.js'; +// Define any additional plugins specific to this bundle +const additionalPlugins = [copy({ patterns: '**/cdm-locales/**/*', rootDir: './src' })]; + +function deepMergeConfigs(baseConfig, specificConfig) { + const mergedConfig = { ...baseConfig, ...specificConfig }; + + // Assuming both configs have a plugins array; adjust logic as needed + if (baseConfig.plugins && specificConfig.plugins) { + mergedConfig.plugins = [...baseConfig.plugins, ...specificConfig.plugins]; + } + + return mergedConfig; +} + +// Base configuration +const baseConfig = { + plugins: [ + image(), + graphql({ include: '**/*.gql' }), + string({ + include: ['**/*.ejs', '**/*.graphql'], + }), + addJsExtensionToImportsPlugin({ + packages: '*', + needToAddIndexJs: [], + excludeImports: ['@emotion/react/jsx-runtime'], + }), + typescript(), // TypeScript at the top as per best practices + modifyLibFilesPlugin({ + include: ['**/**/compute.js'], // Adjust to target specific files or patterns + outputDir: 'lib', // Ensure this matches your actual output directory + }), + generateJsonFromObject({}), + ...additionalPlugins, + ], + external: (id) => !/^[./]/.test(id), + globals: { react: 'React' }, +}; + +// Function to create a configuration by extending the base +function createRollupConfig(overrides) { + return deepMergeConfigs(baseConfig, overrides); +} +function watch(watchOptions, buildMode = '') { + const filename = path.basename(watchOptions.input); + message( + 'note', + `${dt()} Rollup: Watcher Starting - watching for changes starting with: "${filename}" buildMode="${buildMode}"...`, + 'WATCH ', + true, + ); + const watcher = rollup.watch(watchOptions); + + watcher.on('event', (event) => { + // event.code can be one of: + // START — the watcher is (re)starting + // BUNDLE_START — building an individual bundle + // * event.input will be the input options object if present + // * event.output contains an array of the "file" or + // "dir" option values of the generated outputs + // BUNDLE_END — finished building a bundle + // * event.input will be the input options object if present + // * event.output contains an array of the "file" or + // "dir" option values of the generated outputs + // * event.duration is the build duration in milliseconds + // * event.result contains the bundle object that can be + // used to generate additional outputs by calling + // bundle.generate or bundle.write. This is especially + // important when the watch.skipWrite option is used. + // You should call "event.result.close()" once you are done + // generating outputs, or if you do not generate outputs. + // This will allow plugins to clean up resources via the + // "closeBundle" hook. + // END — finished building all bundles + // ERROR — encountered an error while bundling + // * event.error contains the error that was thrown + // * event.result is null for build errors and contains the + // bundle object for output generation errors. As with + // "BUNDLE_END", you should call "event.result.close()" if + // present once you are done. + // If you return a Promise from your event handler, Rollup will wait until the + // Promise is resolved before continuing. + // console.log(`rollup: ${event.code}`) + if (event.code === 'BUNDLE_END') { + const outputFiles = event.output.map((o) => path.basename(o)).join(', .../'); + const msg = `${dt()} Rollup: wrote bundle${event.output.length > 1 ? 's' : ''}: ".../${outputFiles}"`; + if (NOTIFY) { + notifier.notify({ + title: 'React Component Build', + message: msg, + }); + } + // messenger: success, warn, critical, note, log + message('success', msg, 'SUCCESS', true); + } else if (event.code === 'ERROR') { + message('critical', `!!!!!!!!!!!!!!!\nRollup ${event.error}\n!!!!!!!!!!!!!!!\n`, 'ERROR', true); + if (NOTIFY) { + notifier.notify({ + title: 'NotePlan Plugins Build', + message: `An error occurred during build process.\nSee console for more information`, + }); + } + } + }); + + // This will make sure that bundles are properly closed after each run + watcher.on('event', ({ result }) => { + if (result) { + result.close(); + } + }); + + // Additionally, you can hook into the following. Again, return a Promise to + // make Rollup wait at that stage: + watcher.on('change', (id /* , { event } */) => { + const filename = path.basename(id); + message('info', `${dt()} Rollup: file: "${filename}" changed`, 'CHANGE', true); + /* a file was modified */ + }); + watcher.on('restart', () => { + // console.log(`rollup: restarting`) + /* a new run was triggered (usually a watched file change) */ + }); + watcher.on('close', () => { + console.log(`rollup: closing`); + /* the watcher was closed, see below */ + }); + process.on('SIGINT', async function () { + console.log('\n\n'); + console.log(colors.yellow('Quitting...\n')); + if (watcher) { + await watcher.close(); + } + process.exit(); + }); +} + +export { createRollupConfig, baseConfig }; diff --git a/servers/backend-server/Dockerfile b/servers/backend-server/Dockerfile index 5994e4c07..d4aea9390 100755 --- a/servers/backend-server/Dockerfile +++ b/servers/backend-server/Dockerfile @@ -1,5 +1,5 @@ # The official nodejs docker image -FROM node:16.17 +FROM node:20.16-bullseye ENV PYTHON /usr/bin/python @@ -11,6 +11,7 @@ COPY .npmrc /tmp/.npmrc ADD package.json /tmp/package.json RUN set -ex \ && cd /tmp \ + && npm install -g node-gyp \ && yarn config set network-timeout 300000 \ && yarn install \ && rm -f /tmp/.npmrc \ @@ -23,7 +24,7 @@ WORKDIR /home/app # Copy the rest of the files to the container workdir ADD . /home/app -ENV PORT=8080 +ENV PORT=9090 EXPOSE ${PORT} CMD ["yarn", "start"] diff --git a/servers/backend-server/__tests__/test.ts b/servers/backend-server/__tests__/test.ts index 6c69ee903..016c65d90 100644 --- a/servers/backend-server/__tests__/test.ts +++ b/servers/backend-server/__tests__/test.ts @@ -1,44 +1,45 @@ -import { createServer } from "http"; -import express from "express"; -import { ApolloServer, gql } from "apollo-server-express"; -import { ApolloServerPluginDrainHttpServer } from "apollo-server-core"; -import { PubSub } from "graphql-subscriptions"; -import { makeExecutableSchema } from "@graphql-tools/schema"; -import { WebSocketServer } from "ws"; -import { useServer } from "graphql-ws/lib/use/ws"; +import { createServer } from 'http'; +import express from 'express'; +import { gql } from 'graphql'; +import { ApolloServer } from '@apollo/server'; +import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer'; +import { PubSub } from 'graphql-subscriptions'; +import { makeExecutableSchema } from '@graphql-tools/schema'; +import { WebSocketServer } from 'ws'; +import { useServer } from 'graphql-ws/lib/use/ws'; const PORT = 8080; const pubsub = new PubSub(); // Schema definition const typeDefs = gql` - type Query { - currentNumber: Int - } + type Query { + currentNumber: Int + } - type Subscription { - numberIncremented: Int - } + type Subscription { + numberIncremented: Int + } `; // Resolver map const resolvers = { - Query: { - currentNumber() { - return currentNumber; + Query: { + currentNumber() { + return currentNumber; + }, }, - }, - Subscription: { - numberIncremented: { - subscribe: () => pubsub.asyncIterator(["NUMBER_INCREMENTED"]), + Subscription: { + numberIncremented: { + subscribe: () => pubsub.asyncIterator(['NUMBER_INCREMENTED']), + }, }, - }, }; // Create schema, which will be used separately by ApolloServer and // the WebSocket server. const schema = makeExecutableSchema({ typeDefs, resolvers }); -console.log('--schema--', schema) +console.log('--schema--', schema); // Create an Express app and HTTP server; we will attach the WebSocket // server and the ApolloServer to this HTTP server. const app = express(); @@ -46,50 +47,46 @@ const httpServer = createServer(app); // Set up WebSocket server. const wsServer = new WebSocketServer({ - server: httpServer, - path: "/graphql", + server: httpServer, + path: '/graphql', }); const serverCleanup = useServer({ schema }, wsServer); // Set up ApolloServer. const server = new ApolloServer({ - schema, - plugins: [ - // Proper shutdown for the HTTP server. - ApolloServerPluginDrainHttpServer({ httpServer }), + schema, + plugins: [ + // Proper shutdown for the HTTP server. + ApolloServerPluginDrainHttpServer({ httpServer }), - // Proper shutdown for the WebSocket server. - { - async serverWillStart() { - return { - async drainServer() { - await serverCleanup.dispose(); - }, - }; - }, - }, - ], + // Proper shutdown for the WebSocket server. + { + async serverWillStart() { + return { + async drainServer() { + await serverCleanup.dispose(); + }, + }; + }, + }, + ], }); await server.start(); server.applyMiddleware({ app }); // Now that our HTTP server is fully set up, actually listen. httpServer.listen(PORT, () => { - console.log( - `🚀 Query endpoint ready at http://localhost:${PORT}${server.graphqlPath}` - ); - console.log( - `🚀 Subscription endpoint ready at ws://localhost:${PORT}${server.graphqlPath}` - ); + console.log(`🚀 Query endpoint ready at http://localhost:${PORT}${server.graphqlPath}`); + console.log(`🚀 Subscription endpoint ready at ws://localhost:${PORT}${server.graphqlPath}`); }); // In the background, increment a number every second and notify subscribers when // it changes. let currentNumber = 0; function incrementNumber() { - currentNumber++; - pubsub.publish("NUMBER_INCREMENTED", { numberIncremented: currentNumber }); - setTimeout(incrementNumber, 10000); + currentNumber++; + pubsub.publish('NUMBER_INCREMENTED', { numberIncremented: currentNumber }); + setTimeout(incrementNumber, 10000); } // Start incrementing incrementNumber(); diff --git a/servers/backend-server/build.config.mjs b/servers/backend-server/build.config.mjs new file mode 100644 index 000000000..474f1121a --- /dev/null +++ b/servers/backend-server/build.config.mjs @@ -0,0 +1,27 @@ +if (process.env.ENV_FILE !== null) { + import('dotenv').then(({ config }) => config({ path: process.env.ENV_FILE })); +} + +const __API_SERVER_PORT__ = process.env.GRAPHQL_URL ? new URL(process.env.GRAPHQL_URL).port : 8080; +const __SERVER_PROTOCOL__ = 'http'; +const __LOCAL_SERVER_HOST__ = 'localhost'; +const __GRAPHQL_ENDPOINT__ = process.env.GRAPHQL_URL ? new URL(process.env.GRAPHQL_URL).pathname : '/graphql'; + +const config = { + __CLIENT__: false, + __SERVER__: true, + __DEV__: process.env.NODE_ENV !== 'production', + __TEST__: false, + 'process.env.NODE_ENV': process.env.NODE_ENV || 'development', + __GRAPHQL_URL__: process.env.GRAPHQL_URL || '/graphql', + __CDN_URL__: process.env.CDN_URL || '', + __FRONTEND_BUILD_DIR__: process.env.FRONTEND_BUILD_DIR || '../frontend-server/dist/web', + __API_SERVER_PORT__, + __GRAPHQL_ENDPOINT__, + __LOCAL_SERVER_HOST__, + __API_URL__: + process.env.API_URL || + `${__SERVER_PROTOCOL__}://${__LOCAL_SERVER_HOST__}:${__API_SERVER_PORT__}${__GRAPHQL_ENDPOINT__}`, +}; + +export default config; diff --git a/servers/backend-server/config.json b/servers/backend-server/config.json new file mode 100644 index 000000000..50118cb78 --- /dev/null +++ b/servers/backend-server/config.json @@ -0,0 +1,5 @@ +{ + "modules": ["@sample-stack/counter-module-server"], + "devModules": [], + "externalModules": [] +} diff --git a/servers/backend-server/package.json b/servers/backend-server/package.json index c4867554b..631885325 100755 --- a/servers/backend-server/package.json +++ b/servers/backend-server/package.json @@ -1,133 +1,86 @@ { - "name": "sample-stack-backend-server", - "version": "0.0.1", - "private": true, - "description": "Starter kit for apollo server using webpack and typescript", - "keywords": [ - "apollo", - "apollo-server", - "backend", - "express", - "graphiql", - "graphql", - "typescript", - "webpack" - ], - "homepage": "https://github.com/cdmbase/fullstack-pro#readme", - "bugs": { - "url": "https://github.com/cdmbase/fullstack-pro/issues" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/cdmbase/fullstack-pro.git" - }, - "license": "MIT", - "author": "CDMBase LLC", - "main": "dist/index.js", - "typings": "dist/main.d.ts", - "scripts": { - "build": "cross-env NODE_ENV=production webpack", - "build:clean": "rimraf dist .awcache", - "build:dev": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env webpack", - "db:migrate": "knex -- migrate:latest --cwd . --knexfile ./knexfile.js", - "db:migrate:rollback": "knex -- migrate:rollback --cwd . --knexfile ./knexfile.js", - "db:seed": "yarn db:migrate && knex -- seed:run --cwd . --knexfile ./knexfile.js", - "docker:build": "yarn build && docker build . -t $npm_package_name:$npm_package_version", - "docker:build:debug": "yarn build:debug && docker build . -t $npm_package_name:$npm_package_version", - "docker:run": "docker run --env-file ../../config/staging/docker-staging.env -p 8080:8080 -it $npm_package_name:$npm_package_version", - "docker:run:debug": "cross-env NODE_ENV=development docker run --env-file ../../config/staging/docker-staging.env -p 8080:8080 -it $npm_package_name:$npm_package_version", - "proddb:migrate": "NODE_ENV=production yarn db:migrate", - "proddb:migrate:rollback": "NODE_ENV=production yarn db:migrate:rollback", - "proddb:seed": "NODE_ENV=production yarn db:seed", - "prepublish": "yarn build:clean", - "stagedb:migrate": "cross-env ENV_FILE=../../config/test/test.env NODE_ENV=test yarn db:migrate", - "stagedb:migrate:rollback": "cross-env ENV_FILE=../../config/test/test.env NODE_ENV=test yarn db:migrate:rollback", - "stagedb:seed": "cross-env ENV_FILE=../../config/test/test.env NODE_ENV=test yarn db:seed", - "start": "cross-env NODE_ENV=production pm2-runtime dist/index.js", - "start:dev": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env node --harmony dist", - "start:staging": "cross-env NODE_ENV=staging ENV_FILE=../../config/staging/staging.env node --harmony dist", - "start:test": "cross-env NODE_ENV=test ENV_FILE=../../config/test/test.env node --harmony dist", - "test": "jest", - "test:notify": "yarn test:watch -- --notify", - "test:watch": "npm test -- --watch", - "preupver": "npm test", - "upver": "standard-version", - "watch": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env webpack --watch --stats-error-details", - "watch:debug": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env yarn zen:watch -- -v", - "watch:staging": "cross-env NODE_ENV=test ENV_FILE=../../config/staging/staging.env yarn zen:watch", - "watch:test": "cross-env NODE_ENV=test ENV_FILE=../../config/test/test.env yarn zen:watch", - "zen:watch": "zen watch -x" - }, - "resolutions": { - "html-to-text": "^8.0.0" - }, - "dependencies": { - "@apollo/client": "~3.7.1", - "@cdm-logger/server": "^7.0.12", - "@common-stack/core": "0.5.1", - "@common-stack/server-core": "0.5.1", - "@common-stack/store-mongo": "0.5.3", - "@babel/runtime": "^7.20.1", - "@graphql-tools/links": "^8.3.21", - "@graphql-tools/utils": "^8.0.0", - "@graphql-tools/schema": "^8.3.14", - "@graphql-tools/stitch": "^8.7.29", - "@graphql-tools/wrap": "^8.4.20", - "@sample-stack/core": "0.0.1", - "@sample-stack/counter-module-server": "0.0.1", - "@sample-stack/platform-server": "0.0.1", - "@sample-stack/store": "0.0.1", - "apollo-datasource": "^3.3.1", - "apollo-datasource-rest": "^3.3.1", - "apollo-errors": "^1.9.0", - "apollo-server-cache-memcached": "^3.3.1", - "apollo-server-cache-redis": "^3.3.1", - "apollo-server-caching": "^3.3.0", - "apollo-server-core": "^3.11.1", - "apollo-server-errors": "^3.3.1", - "apollo-server-express": "^3.11.1", - "apollo-server-plugin-response-cache": "^3.8.1", - "app-root-path": "^3.0.0", - "body-parser": "^1.19.0", - "cors": "^2.8.5", - "dataloader": "^2.0.0", - "dotenv": "^8.2.0", - "envalid": "~7.2.2", - "esm": "^3.2.25", - "express": "^4.17.1", - "graphql": "^15.0.0", - "graphql-ws": "^5.11.2", - "graphql-bigint": "^1.0.0", - "graphql-nats-subscriptions": "^1.5.0", - "graphql-subscriptions": "^2.0.0", - "graphql-tools": "^8.0.0", - "graphql-type-json": "^0.3.1", - "inversify": "^5.0.1", - "ioredis": "^4.14.0", - "isomorphic-fetch": "^2.2.1", - "lodash": "^4.17.15", - "moleculer": "^0.14.2", - "moleculer-zipkin": "0.2.2", - "mongoose": "^6.3.3", - "morgan": "^1.9.1", - "nats": "^1.3.2", - "react": "18.0.0", - "reflect-metadata": "^0.1.13", - "rxjs": "^6.5.3", - "rxjs-compat": "^6.5.3", - "subscriptions-transport-ws": "^0.11.0", - "universal-cookie-express": "^4.0.1", - "ws": "^8.11.0" - }, - "devDependencies": { - "cross-env": "^7.0.3", - "pm2": "^5.2.2", - "rimraf": "^3.0.2" - }, - "peerDependencies": { - "@cdm-logger/core": "*" - }, - "typescript": { - "definition": "dist/main.d.ts" - } -} \ No newline at end of file + "name": "sample-stack-backend-server", + "version": "0.0.1", + "private": true, + "description": "Starter kit for apollo server using webpack and typescript", + "keywords": [ + "apollo", + "apollo-server", + "backend", + "express", + "graphiql", + "graphql", + "typescript", + "webpack" + ], + "homepage": "https://github.com/cdmbase/fullstack-pro#readme", + "bugs": { + "url": "https://github.com/cdmbase/fullstack-pro/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/cdmbase/fullstack-pro.git" + }, + "license": "MIT", + "author": "CDMBase LLC", + "main": "dist/index.js", + "typings": "dist/main.d.ts", + "scripts": { + "build": "cross-env NODE_ENV=production webpack --config webpack.config.js", + "build:clean": "rimraf dist .awcache", + "build:dev": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env webpack --config webpack.config.js", + "db:migrate": "knex -- migrate:latest --cwd . --knexfile ./knexfile.js", + "db:migrate:rollback": "knex -- migrate:rollback --cwd . --knexfile ./knexfile.js", + "db:seed": "yarn db:migrate && knex -- seed:run --cwd . --knexfile ./knexfile.js", + "docker:build": "yarn build && docker build . -t $npm_package_name:$npm_package_version", + "docker:build:debug": "yarn build:debug && docker build . -t $npm_package_name:$npm_package_version", + "docker:run": "docker run --env-file ../../config/staging/docker-staging.env -p 8080:8080 -it $npm_package_name:$npm_package_version", + "docker:run:debug": "cross-env NODE_ENV=development docker run --env-file ../../config/staging/docker-staging.env -p 8080:8080 -it $npm_package_name:$npm_package_version", + "proddb:migrate": "NODE_ENV=production yarn db:migrate", + "proddb:migrate:rollback": "NODE_ENV=production yarn db:migrate:rollback", + "proddb:seed": "NODE_ENV=production yarn db:seed", + "prepublish": "yarn build:clean", + "stagedb:migrate": "cross-env ENV_FILE=../../config/test/test.env NODE_ENV=test yarn db:migrate", + "stagedb:migrate:rollback": "cross-env ENV_FILE=../../config/test/test.env NODE_ENV=test yarn db:migrate:rollback", + "stagedb:seed": "cross-env ENV_FILE=../../config/test/test.env NODE_ENV=test yarn db:seed", + "start": "cross-env NODE_ENV=production tsx dist/index.js", + "start:dev": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env tsx watch dist/index.js", + "start:staging": "cross-env NODE_ENV=staging ENV_FILE=../../config/staging/staging.env tsx dist/index.js", + "start:test": "cross-env NODE_ENV=test ENV_FILE=../../config/test/test.env tsx dist/index.js", + "test": "jest", + "test:notify": "yarn test:watch -- --notify", + "test:watch": "npm test -- --watch", + "preupver": "npm test", + "upver": "standard-version", + "watch": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env yarn build:dev && yarn start:dev", + "watch:staging": "cross-env NODE_ENV=test ENV_FILE=../../config/staging/staging.env yarn build && yarn start:stage", + "watch:test": "cross-env NODE_ENV=test ENV_FILE=../../config/test/test.env yarn build:test && yarn start:test", + "zen:watch": "zen watch -x" + }, + "resolutions": { + "html-to-text": "^8.0.0" + }, + "dependencies": { + "@apollo/client": "^3.9.0", + "@babel/runtime": "^7.20.1", + "@common-stack/server-stack": "6.0.6-alpha.0", + "@remix-run/node": "^2.8.1", + "@sample-stack/counter-module-server": "link:../../packages-modules/counter/server", + "@sample-stack/platform-server": "link:../../packages/sample-platform/server", + "@sample-stack/store": "link:../../packages/sample-store", + "lodash": "^4.17.15", + "react": "18.2.0", + "rxjs": "^6.5.3", + "rxjs-compat": "^6.5.3" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "esbuild-loader": "^4.2.2", + "pm2": "^5.2.2", + "rimraf": "^3.0.2", + "tsx": "^4.7.0" + }, + "typescript": { + "definition": "dist/main.d.ts" + } +} diff --git a/servers/backend-server/src/api/remote-config.ts b/servers/backend-server/src/api/remote-config.ts deleted file mode 100755 index 7a856b22b..000000000 --- a/servers/backend-server/src/api/remote-config.ts +++ /dev/null @@ -1,11 +0,0 @@ -interface ISchemaConfig { - uri: string; - wsUri: string; -} - -export const remoteSchemaDetails: ISchemaConfig[] = [ - // { - // uri: 'http://localhost:8085/graphql', - // wsUri: 'ws://localhost:8085/graphql', - // }, -]; diff --git a/servers/backend-server/src/api/resolver.ts b/servers/backend-server/src/api/resolver.ts deleted file mode 100755 index 1fec41193..000000000 --- a/servers/backend-server/src/api/resolver.ts +++ /dev/null @@ -1,6 +0,0 @@ -import GraphQLJSON, { GraphQLJSONObject } from 'graphql-type-json'; - -export const resolvers = { - JSON: GraphQLJSON, - JSONObject: GraphQLJSONObject, -}; diff --git a/servers/backend-server/src/api/root-schema.graphqls b/servers/backend-server/src/api/root-schema.graphqls index 240600750..6a7551dc0 100755 --- a/servers/backend-server/src/api/root-schema.graphqls +++ b/servers/backend-server/src/api/root-schema.graphqls @@ -1,13 +1,65 @@ scalar AnyObject +scalar Date +scalar Time +scalar DateTime +scalar Timestamp +scalar URI +scalar URIInput +scalar Observable + scalar JSON scalar JSONObject + +directive @cacheControl(maxAge: Int, scope: CacheControlScope) on FIELD_DEFINITION | OBJECT | INTERFACE +enum CacheControlScope { + PUBLIC + PRIVATE +} + +""" +An object with an ID. +""" +interface Node { + """ + The ID of the node. + """ + id: ID! +} + +# Pagination information. See https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo. +type PageInfo { + # Whether there is a next page of nodes in the connection. + hasNextPage: Boolean! +} + +""" +Represents a null return value. +""" +type EmptyResponse { + # A dummy null value. + alwaysNil: String +} + type FieldError { - field: String! - message: String! + field: String! + message: String! +} + +input Sort { + key: String! + value: SortEnum! +} + +enum SortEnum { + ASC + DESC } type Query { - dummy: Int + """ + Looks up a node by ID. + """ + node(id: ID!): Node } type Mutation { @@ -18,13 +70,47 @@ type Subscription { dummy: Int } -interface Node { - id: ID! +type AdminIdeSettings { + dummy: Int +} + +""" +All Moleculer Topic names are extended from this. +""" +enum MoleculerServiceName { + dummy +} + +interface IResourceUtilizationSettings { + subTopic: String + adminApiNamespace: String } +""" +Input geometry of the location. +List the `longitude` first and then `latitude` + - Validate longitude values are between `-180` and `180` + - Validate latitude values are between `-90` and `90` +""" +input GeoLocation_Input { + type: String = "Point" + coordinates: [Float] +} + +type GeoLocation { + coordinates: [Float] +} + +enum MailTemplateId { + dummy +} + +enum MoleculerCronServiceName { + dummy +} schema { query: Query mutation: Mutation subscription: Subscription -} \ No newline at end of file +} diff --git a/servers/backend-server/src/api/scalar.ts b/servers/backend-server/src/api/scalar.ts deleted file mode 100755 index dbb43d5bc..000000000 --- a/servers/backend-server/src/api/scalar.ts +++ /dev/null @@ -1,16 +0,0 @@ -// add any scalar types -import { GraphQLError, GraphQLScalarType, Kind } from 'graphql'; - -// https://stackoverflow.com/questions/41557536/custom-map-keys-in-graphql-response -export const GraphQLAnyObject = new GraphQLScalarType({ - name: 'AnyObject', - description: 'Any JSON object. This type bypasses type checking.', - serialize: (value) => value, - parseValue: (value) => value, - parseLiteral: (ast) => { - if (ast.kind !== Kind.OBJECT) { - throw new GraphQLError(`Query error: Can only parse object but got a: ${ast.kind}`, [ast]); - } - return ast.fields; - }, -}); diff --git a/servers/backend-server/src/api/schema-builder.ts b/servers/backend-server/src/api/schema-builder.ts deleted file mode 100755 index 366c7f4cc..000000000 --- a/servers/backend-server/src/api/schema-builder.ts +++ /dev/null @@ -1,176 +0,0 @@ -/* eslint-disable class-methods-use-this */ -/* eslint-disable no-useless-constructor */ -/* eslint-disable import/no-extraneous-dependencies */ -import * as fs from 'fs'; -import { GraphQLSchema, OperationDefinitionNode } from 'graphql'; -import { stitchSchemas } from '@graphql-tools/stitch'; -import { makeExecutableSchema } from '@graphql-tools/schema'; -import { linkToExecutor } from '@graphql-tools/links'; -import { introspectSchema, wrapSchema } from '@graphql-tools/wrap'; -// import { transformSchema } from '@graphql-tools/delegate'; -import * as ws from 'ws'; -import { getMainDefinition } from '@apollo/client/utilities'; -import { WebSocketLink } from '@apollo/client/link/ws'; -import { split } from '@apollo/client'; -import { logger } from '@common-stack/server-core'; -import { HttpLink } from '@apollo/client/link/http'; -import * as fetch from 'isomorphic-fetch'; -import { CdmLogger } from '@cdm-logger/core'; -import { remoteSchemaDetails } from './remote-config'; -import rootSchemaDef from './root-schema.graphqls'; -import { resolvers as rootResolver } from './resolver'; -import { attachDirectiveResolvers } from './utils'; - -interface IGraphqlOptions { - schema: string | string[]; - resolvers: any; - directives: any[]; - directiveResolvers: { [key: string]: any}; - logger: CdmLogger.ILogger; -} -export class GatewaySchemaBuilder { - constructor(private options: IGraphqlOptions) {} - - public async build(): Promise { - let gatewaySchema; - let ownSchema; - try { - ownSchema = this.createOwnSchema(); - const remoteSchema = await this.load(); - // techSchema = this.patchSchema(techSchema, 'TechService'); - - gatewaySchema = stitchSchemas({ - subschemas: [ownSchema], - }); - // TODO after updating graphql-tools to v8 - // addErrorLoggingToSchema(schema, { log: (e) => logger.error(e as Error) }); - } catch (err) { - logger.error('[Graphql Schema Errors] when building schema:', err.message); - gatewaySchema = ownSchema; - } - - return gatewaySchema; - } - - private async load() { - const schemas = []; - // eslint-disable-next-line no-plusplus - for (let i = 0; i < remoteSchemaDetails.length; i++) { - // eslint-disable-next-line no-await-in-loop - const schema = await this.loadRemoteSchema(remoteSchemaDetails[i]); - schemas.push(schema); - } - return schemas; - } - - private async createRemoteSchema(service: string, iteration?: number): Promise { - logger.info('Fetch service [%s] iteration [%s]', service, iteration); - const services = remoteSchemaDetails; - if (!services.length) { - console.warn(`Service ${services} is undefined`); - if (iteration && iteration > 2) { - return Promise.reject(`tried upto ${iteration} attempts, now failing...`); - } - return new Promise((resolve, reject) => { - const timeout = iteration ? 1000 * iteration : 1000; - logger.info('Wait for service startup %s', timeout); - setTimeout(() => { - this.createRemoteSchema(service, iteration ? iteration + 1 : 1) - .then(resolve) - .catch(reject); - }, timeout); - }); - } - // instead need to loop it - // https://github.com/j-colter/graphql-gateway/blob/9c64d90a74727d2002d10b06f47e1f4a316070fc/src/schema.js#L50 - const url = services[0].uri; - logger.info('fetch service [%s]', url); - // 1. Create apollo Link that's connected to the underlying GraphQL API - const link = new HttpLink({ uri: url, fetch }); - const executor: any = linkToExecutor(link); - // 2. Retrieve schema definition of the underlying GraphQL API - const remoteSchema = await introspectSchema(link as any); - - // 3. Create the executable schema based on schema definition and Apollo Link - return wrapSchema({ - schema: remoteSchema, - executor, - }); - } - - private async loadRemoteSchema({ uri, wsUri }) { - try { - const httpLink = new HttpLink({ uri, fetch }); - let link = null; - - if (wsUri) { - const wsLink = new WebSocketLink({ - uri: wsUri, - options: { - reconnect: true, - }, - webSocketImpl: ws, - }); - link = split( - // split based on operatino type - ({ query }) => { - const { kind, operation } = getMainDefinition(query) as OperationDefinitionNode; - return kind === 'OperationDefinition' && operation === 'subscription'; - }, - wsLink, - httpLink, - ); - } else { - link = httpLink; - } - const executor: any = linkToExecutor(link); - const remoteSchema = await introspectSchema(link); - const executableSchema = wrapSchema({ - schema: remoteSchema, - executor, - }); - return executableSchema; - } catch (err) { - this.options.logger.error('fetching schema error: ', err); - return {}; - } - } - - // disabled after updating to apollo-client to v3 and graphql-tools to v8 - // private patchSchema(schema: GraphQLSchema, systemName: string) { - // return transformSchema(schema, [ - // new RenameTypes((name: string) => (name === 'StatusInfo' ? `${systemName}StatusInfo` : undefined)), - // new RenameRootFields((_operation: string, name: string) => - // name === 'status' - // ? `${systemName.substring(0, 1).toLowerCase()}${systemName.substring(1)}Status` - // : name, - // ), - // ]); - // } - - private createOwnSchema(): GraphQLSchema { - const typeDefs = [rootSchemaDef, this.options.schema].join('\n'); - if (__DEV__) { - import('../modules/module').then( - ({ ExternalModules }) => { - const externalSchema = ExternalModules?.schemas || ``; - const writeData = `${externalSchema}`; - fs.writeFileSync('./generated-schema.graphql', writeData); - } - ) - } - let mergedSchema = makeExecutableSchema({ - resolvers: [rootResolver, this.options.resolvers], - typeDefs, - resolverValidationOptions: { - requireResolversForResolveType: 'warn', - }, - }); - // mergedSchema = this.options.directives.reduce((curSchema,transform) => transform(curSchema), mergedSchema); - if (this.options.directiveResolvers && Object.keys(this.options.directiveResolvers).length !== 0 ) { - this.options.logger.warn('directiveResolvers deprecated replaced with directives'); - mergedSchema = attachDirectiveResolvers(mergedSchema, this.options.directiveResolvers); - } - return mergedSchema; - } -} diff --git a/servers/backend-server/src/api/utils.ts b/servers/backend-server/src/api/utils.ts deleted file mode 100644 index 45ae046dd..000000000 --- a/servers/backend-server/src/api/utils.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { defaultFieldResolver, GraphQLSchema } from 'graphql'; -import { mapSchema, MapperKind, getDirectives } from '@graphql-tools/utils'; - -export function attachDirectiveResolvers( - schema: GraphQLSchema, - directiveResolvers: {[key: string]: Function}, -): GraphQLSchema { - // ... argument validation ... - - return mapSchema(schema, { - [MapperKind.OBJECT_FIELD]: (fieldConfig) => { - const newFieldConfig = { ...fieldConfig }; - - const directives = getDirectives(schema, fieldConfig); - for (const directive of directives) { - const directiveName = directive.name; - if (directiveResolvers[directiveName]) { - const resolver = directiveResolvers[directiveName]; - const originalResolver = - newFieldConfig.resolve != null ? newFieldConfig.resolve : defaultFieldResolver; - const directiveArgs = directive.args; - newFieldConfig.resolve = (source, originalArgs, context, info) => { - return resolver( - () => - new Promise((resolve, reject) => { - const result = originalResolver(source, originalArgs, context, info); - if (result instanceof Error) { - reject(result); - } - resolve(result); - }), - source, - directiveArgs, - context, - info, - ); - }; - } - } - - return newFieldConfig; - }, - }); -} diff --git a/servers/backend-server/src/config/env-config.ts b/servers/backend-server/src/config/env-config.ts index 1874e8800..584a383d8 100755 --- a/servers/backend-server/src/config/env-config.ts +++ b/servers/backend-server/src/config/env-config.ts @@ -1,28 +1,12 @@ /// -import * as envalid from 'envalid'; +import { str, cleanEnv } from 'envalid'; -const { str, bool, json } = envalid; - -export const config = envalid.cleanEnv(process.env, { +export const config = cleanEnv(process.env, { NODE_ENV: str({ default: 'production', choices: ['production', 'staging', 'development', 'test'] }), - NATS_URL: str({ devDefault: 'nats://localhost:4222/' }), - NATS_USER: str({ devDefault: 'test' }), - NATS_PW: str({ devDefault: 'test' }), - MONGO_URL: str({ devDefault: 'mongodb://localhost:27017/sample-stack' }), - LOG_LEVEL: str({ default: 'info', devDefault: 'trace', choices: ['info', 'debug', 'trace'] }), - REDIS_CLUSTER_URL: json({ - devDefault: '[{"port":6379,"host":"localhost"}]', - example: '[{"port":6379,"host":"localhost"}]', - }), - REDIS_URL: str({ devDefault: 'localhost' }), - REDIS_CLUSTER_ENABLED: bool({ devDefault: false }), - REDIS_SENTINEL_ENABLED: bool({ devDefault: true }), - HEMERA_LOG_LEVEL: str({ default: 'info' }), - BACKEND_URL: str({ devDefault: __BACKEND_URL__ }), - GRAPHQL_URL: str({ devDefault: __GRAPHQL_URL__ }), - CLIENT_URL: str({ devDefault: __BACKEND_URL__ }), CONNECTION_ID: str({ devDefault: 'CONNECTION_ID' }), NAMESPACE: str({ default: 'default' }), + ACTIVITY_NAMESPACE: str({ devDefault: 'default' }), API_NAMESPACE: str({ devDefault: 'default' }), ADMIN_API_NAMESPACE: str({ devDefault: 'default' }), + BACKEND_URL: str({ devDefault: __BACKEND_URL__ }), }); diff --git a/servers/backend-server/src/config/moleculer.config.ts b/servers/backend-server/src/config/moleculer.config.ts deleted file mode 100755 index cb61b4605..000000000 --- a/servers/backend-server/src/config/moleculer.config.ts +++ /dev/null @@ -1,227 +0,0 @@ -/* eslint-disable @typescript-eslint/no-empty-function */ -import { BrokerOptions, Errors } from 'moleculer'; -import { config } from './env-config'; - -/** - * Moleculer ServiceBroker configuration file - * - * More info about options: - * https://moleculer.services/docs/0.14/configuration.html - * - * - * Overwrite options in production: - * ================================ - * You can overwrite any option with environment variables. - * For example to overwrite the 'logLevel', use `LOGLEVEL=warn` env var. - * To overwrite a nested parameter, e.g. retryPolicy.retries, use `RETRYPOLICY_RETRIES=10` env var. - * - * To overwrite broker’s deeply nested default options, which are not presented in 'moleculer.config.ts', - * via environment variables, use the `MOL_` prefix and double underscore `__` for nested properties in .env file. - * For example, to set the cacher prefix to `MYCACHE`, you should declare an env var as `MOL_CACHER__OPTIONS__PREFIX=MYCACHE`. - */ -const brokerConfig: BrokerOptions = { - // Namespace of nodes to segment your nodes on the same network. - namespace: config.NAMESPACE, - // namespace: null, - // Unique node identifier. Must be unique in a namespace. - // nodeID: config.CONNECTION_ID, - - // Enable/disable logging or use custom logger. More info: https://moleculer.services/docs/0.14/logging.html - // Available logger types: 'Console', 'File', 'Pino', 'Winston', 'Bunyan', 'debug', 'Log4js', 'Datadog' - logger: { - type: 'Console', - options: { - // Using colors on the output - colors: true, - // Print module names with different colors (like docker-compose for containers) - moduleColors: false, - // Line formatter. It can be 'json', 'short', - // 'simple', 'full', a `Function` or a template string like '{timestamp} {level} {nodeID}/{mod}: {msg}' - formatter: 'full', - // Custom object printer. If not defined, it uses the `util.inspect` method. - objectPrinter: null, - // Auto-padding the module name in order to messages begin at the same column. - autoPadding: false, - }, - }, - // Default log level for built-in console logger. It can be overwritten in logger options above. - // Available values: trace, debug, info, warn, error, fatal - logLevel: config.LOG_LEVEL as any, - - // Define transporter. - // More info: https://moleculer.services/docs/0.14/networking.html - // Note: During the development, you don't need to define it because all services will be loaded locally. - // In production you can set it via `TRANSPORTER=nats://localhost:4222` environment variable. - transporter: - config.NODE_ENV === 'development' - ? 'TCP' - : { - type: 'NATS', - options: { - url: config.NATS_URL, - user: config.NATS_USER, - pass: config.NATS_PW, - reconnectTimeWait: 1000, - }, - }, - - // Define a cacher. - // More info: https://moleculer.services/docs/0.14/caching.html - // cacher: { - // type: 'Redis', - // enabled: false, - // options: { - - // // Redis settings - // redis: { - - // } - // } - // }, - - // Define a serializer. - // Available values: 'JSON', 'Avro', 'ProtoBuf', 'MsgPack', 'Notepack', 'Thrift'. - // More info: https://moleculer.services/docs/0.13/networking.html - serializer: 'JSON', - - // Number of milliseconds to wait before reject a request with a RequestTimeout error. Disabled: 0 - requestTimeout: 10 * 1000, - - // Retry policy settings. More info: https://moleculer.services/docs/0.13/fault-tolerance.html#Retry - retryPolicy: { - // Enable feature - enabled: false, - // Count of retries - retries: 5, - // First delay in milliseconds. - delay: 100, - // Maximum delay in milliseconds. - maxDelay: 1000, - // Backoff factor for delay. 2 means exponential backoff. - factor: 2, - // A function to check failed requests. - check: (err: Errors.MoleculerRetryableError) => err && !!err.retryable, - }, - - // Limit of calling level. If it reaches the limit, broker will throw an MaxCallLevelError error. (Infinite loop protection) - maxCallLevel: 100, - - // Number of seconds to send heartbeat packet to other nodes. - heartbeatInterval: 5, - // Number of seconds to wait before setting node to unavailable status. - heartbeatTimeout: 15, - - // tslint:disable-next-line:max-line-length - // Tracking requests and waiting for running requests before shutdowning. More info: https://moleculer.services/docs/0.13/fault-tolerance.html - tracking: { - // Enable feature - enabled: false, - // Number of milliseconds to wait before shutdowning the process - shutdownTimeout: 5000, - }, - - // Disable built-in request & emit balancer. (Transporter must support it, as well.) - disableBalancer: false, - - // Settings of Service Registry. More info: https://moleculer.services/docs/0.14/registry.html - registry: { - // Define balancing strategy. More info: https://moleculer.services/docs/0.14/balancing.html - // Available values: 'RoundRobin', 'Random', 'CpuUsage', 'Latency' - strategy: 'RoundRobin', - // Enable local action call preferring. - preferLocal: true, - }, - - // Settings of Circuit Breaker. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Circuit-Breaker - circuitBreaker: { - // Enable feature - enabled: false, - // Threshold value. 0.5 means that 50% should be failed for tripping. - threshold: 0.5, - // Minimum request count. Below it, CB does not trip. - minRequestCount: 20, - // Number of seconds for time window. - windowTime: 60, - // Number of milliseconds to switch from open to half-open state - halfOpenTime: 10 * 1000, - // A function to check failed requests. - check: (err: Errors.MoleculerRetryableError) => err && err.code >= 500, - }, - - // Settings of bulkhead feature. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Bulkhead - bulkhead: { - // Enable feature. - enabled: false, - // Maximum concurrent executions. - concurrency: 10, - // Maximum size of queue - maxQueueSize: 100, - }, - - // Enable action & event parameter validation. More info: https://moleculer.services/docs/0.14/validating.html - validator: true, - - errorHandler: null, - - // Enable/disable built-in metrics function. More info: https://moleculer.services/docs/0.14/metrics.html - metrics: { - enabled: false, - // Available built-in reporters: 'Console', 'CSV', 'Event', 'Prometheus', 'Datadog', 'StatsD' - reporter: { - type: 'Prometheus', - options: { - // HTTP port - port: 3030, - // HTTP URL path - path: '/metrics', - // Default labels which are appended to all metrics labels - defaultLabels: (registry) => ({ - namespace: registry.broker.namespace, - nodeID: registry.broker.nodeID, - }), - }, - }, - }, - - // Enable built-in tracing function. More info: https://moleculer.services/docs/0.14/tracing.html - tracing: { - enabled: true, - // Available built-in exporters: 'Console', 'Datadog', 'Event', 'EventLegacy', 'Jaeger', 'Zipkin' - exporter: { - type: 'Console', // Console exporter is only for development! - options: { - // Custom logger - logger: null, - // Using colors - colors: true, - // Width of row - width: 100, - // Gauge width in the row - gaugeWidth: 40, - }, - }, - }, - - // Register internal services ('$node'). More info: https://moleculer.services/docs/0.13/services.html#Internal-services - internalServices: true, - // Register internal middlewares. More info: https://moleculer.services/docs/0.13/middlewares.html#Internal-middlewares - internalMiddlewares: true, - - // Watch the loaded services and hot reload if they changed. - // You can also enable it in Moleculer Runner with `--hot` argument - hotReload: false, - - // Register custom middlewares - middlewares: [], - - // Called after broker created. - created(broker) {}, - - // Called after broker starte. - started(broker) {}, - - // Called after broker stopped. - stopped(broker) {}, -}; - -export { brokerConfig }; diff --git a/servers/backend-server/src/connectors/connection-broker.ts b/servers/backend-server/src/connectors/connection-broker.ts deleted file mode 100755 index 317b12abf..000000000 --- a/servers/backend-server/src/connectors/connection-broker.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -/* eslint-disable no-unused-expressions */ -/* eslint-disable no-underscore-dangle */ - -import { Transporter, GenericObject } from 'moleculer'; -import { CdmLogger } from '@cdm-logger/core'; -import { MongoConnector } from './mongo-connector'; -import { NatsConnector } from './nats-connector'; -import { RedisConnector } from './redis-connector'; -import { config } from '../config'; -import { GraphqlPubSubConnector } from './graphql-pubsub-connector'; - -type ILogger = CdmLogger.ILogger; - -/** - * Connection broker class - * - * @class ConnectionBroker - */ -export class ConnectionBroker { - private _mongoConnector: MongoConnector; - - private _redisConnector: RedisConnector; - - private _natsConnector: NatsConnector; - - private _graphqlPubsubConnector: GraphqlPubSubConnector; - - /** - * Creates an instance of ConnectionBroker. - * @param {*} options - * @memberof ConnectionBroker - */ - constructor(transporter: string | GenericObject, logger: ILogger) { - if (typeof transporter === 'string') { - if (transporter === 'TCP') { - this._graphqlPubsubConnector = new GraphqlPubSubConnector({ logger, type: 'TCP' }); - } else if (transporter === 'NATS') { - this._natsConnector = new NatsConnector({}); - this._graphqlPubsubConnector = new GraphqlPubSubConnector({ - logger, - type: 'NATS', - client: this._natsConnector, - }); - } - } else if (transporter.type === 'NATS') { - this._natsConnector = new NatsConnector(transporter.options); - this._graphqlPubsubConnector = new GraphqlPubSubConnector({ - logger, - ...transporter, - client: this._natsConnector, - }); - } - - this._mongoConnector = new MongoConnector(config.MONGO_URL); - this._redisConnector = new RedisConnector(); // TODO pass constructor options - } - - public get mongoConnection() { - return this._mongoConnector.connect(); - } - - public get redisDataloaderClient() { - return this._redisConnector.getRedisDataloaderClient(); - } - - public get natsConnection() { - return this._natsConnector.connect(); - } - - public get graphqlPubsub() { - return this._graphqlPubsubConnector.getClient(); - } - - public async stop() { - this._mongoConnector && (await this._mongoConnector.disconnect()); - this._redisConnector && (await this._redisConnector.disconnect()); - this._natsConnector && (await this._natsConnector.disconnect()); - } -} diff --git a/servers/backend-server/src/connectors/graphql-pubsub-connector.ts b/servers/backend-server/src/connectors/graphql-pubsub-connector.ts deleted file mode 100755 index b90324d72..000000000 --- a/servers/backend-server/src/connectors/graphql-pubsub-connector.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* eslint-disable no-return-assign */ -import { PubSub, PubSubEngine } from 'graphql-subscriptions'; -import { NatsPubSub } from 'graphql-nats-subscriptions'; -import { logger } from '@cdm-logger/server'; -import { GenericObject } from 'moleculer'; -import { CdmLogger } from '@cdm-logger/core'; - -type ILogger = CdmLogger.ILogger; - -type PubSubOptions = { - logger: ILogger; -} & GenericObject; - -export class GraphqlPubSubConnector { - private client: PubSubEngine | NatsPubSub; - - private opts: PubSubOptions; - - private logger: ILogger; - - /** - * Creates an instance of GraphqlPubSubConnector. - * @param {*} opts - * @memberof GraphqlPubSubConnector - */ - constructor(opts?: PubSubOptions) { - this.opts = opts; - this.logger = opts.logger.child({ className: 'GraphqlPubSubConnector' }); - } - - public async getClient() { - if (this.opts.type === 'TCP') { - return (this.client = new PubSub()); - } - if (this.opts.type === 'NATS') { - // console.log('--this.copts', this.opts.client) - const natsClient = await this.opts.client.connect(); - return (this.client = new NatsPubSub({ client: natsClient, logger })); - } - this.logger.warn('Did not defined known transporter [%s], return default pubsub', this.opts.type); - return (this.client = new PubSub()); - } -} diff --git a/servers/backend-server/src/connectors/mongo-connector.ts b/servers/backend-server/src/connectors/mongo-connector.ts deleted file mode 100755 index 743b0f831..000000000 --- a/servers/backend-server/src/connectors/mongo-connector.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { createConnection, connection, Connection, ConnectOptions, plugin } from 'mongoose'; -import * as _ from 'lodash'; -import { Db } from 'mongodb'; -import { logger } from '@cdm-logger/server'; -import { CdmLogger } from '@cdm-logger/core'; - -type ILogger = CdmLogger.ILogger; - -export class MongoConnector { - private client: Connection; - - private db: Db; - - private opts: ConnectOptions; - - private uri: string; - - private logger: ILogger; - - constructor(uri: string, opts?: ConnectOptions) { - this.opts = _.defaultsDeep(opts, {}); - this.uri = uri; - this.logger = logger.child({ className: 'MongoConnector' }); - } - - /** - * Connect to database - * - * @memberof MongoConnector - */ - public async connect(): Promise { - if (this.client) { - return this.client; - } - const conn = createConnection(this.uri, this.opts).asPromise(); - - conn.then((result) => { - this.client = result; - this.db = result.db; - this.logger.info(' MongoDB has connected successfully.'); - result.on('disconnected', () => this.logger.warn('Mongoose has disconnected.')); - result.on('error', (err) => this.logger.error('MongoDB error.', err)); - result.on('reconnect', () => this.logger.info('Mongoose has reconnected.')); - }); - - // eslint-disable-next-line @typescript-eslint/no-var-requires,global-require - // plugin(require('@kolinalabs/mongoose-consistent'), { - // actionDefault: 'no_action', - // }); - return conn; - } - - /** - * Disconnect from database - * - * @memberof MongoConnector - */ - public async disconnect() { - if (!this.client) { - return; - } - await connection.close(); - } -} diff --git a/servers/backend-server/src/connectors/nats-connector.ts b/servers/backend-server/src/connectors/nats-connector.ts deleted file mode 100755 index 156fd2e28..000000000 --- a/servers/backend-server/src/connectors/nats-connector.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as nats from 'nats'; -import * as _ from 'lodash'; -import { logger } from '@cdm-logger/server'; -import { CdmLogger } from '@cdm-logger/core'; - -type ILogger = CdmLogger.ILogger; - -export class NatsConnector { - private opts: nats.ClientOpts; - - private client: nats.Client; - - private logger: ILogger; - - private connected: boolean; - - constructor(opts: nats.ClientOpts) { - this.opts = _.defaultsDeep(opts, {}); - this.logger = logger.child({ className: 'NatsConnector' }); - } - - /** - * Connect to a NATS server - * - * @memberof NatsConnector - */ - public connect() { - if (this.client) { - return this.client; - } - return new Promise((resolve, reject) => { - const client = nats.connect(this.opts); - - client.on('connect', () => { - this.client = client; - this.connected = true; - this.logger.info('NATS client is connected.'); - resolve(client); - }); - - client.on('reconnect', () => { - this.logger.info('NATS client is reconnected.'); - this.connected = true; - }); - - client.on('reconnecting', () => { - this.logger.warn('NATS client is reconnecting...'); - }); - - client.on('disconnect', () => { - if (this.connected) { - this.logger.warn('NATS client is disconnected.'); - this.connected = false; - } - }); - - client.on('error', (e) => { - this.logger.error('NATS error.', e.message); - this.logger.debug(e); - reject(e); - }); - - client.on('close', () => { - this.logger.fatal('NATS connection close.'); - }); - }); - } - - /** - * Disconnect from a NATS server - * - * @memberof NatsTransporter - */ - public disconnect() { - if (this.client) { - this.client.flush(() => { - this.client.close(); - this.client = null; - }); - } - } -} diff --git a/servers/backend-server/src/connectors/redis-connector.ts b/servers/backend-server/src/connectors/redis-connector.ts deleted file mode 100755 index 950508046..000000000 --- a/servers/backend-server/src/connectors/redis-connector.ts +++ /dev/null @@ -1,73 +0,0 @@ - - -import * as _ from 'lodash'; -import { RedisClusterCache, RedisCache } from 'apollo-server-cache-redis'; -import * as IORedis from 'ioredis'; -import { logger } from '@cdm-logger/server'; -import { config } from '../config'; -import { CdmLogger } from '@cdm-logger/core'; -type ILogger = CdmLogger.ILogger; - -export class RedisConnector { - - - private client: RedisClusterCache | RedisCache; - private opts: IORedis.ClusterOptions | IORedis.RedisOptions; - private logger: ILogger; - - /** - * Creats an instance of Redis. - * - * @param {object} opts - */ - constructor(opts?: IORedis.ClusterOptions | IORedis.RedisOptions) { - this.opts = _.defaultsDeep(opts, { - prefix: null, - }); - this.logger = logger.child({ className: 'RedisConnector' }); - } - - /** - * Connect to the server - * - * @memberof RedisConnector - */ - public connect() { - return new Promise((resolve, reject) => { - reject('this method not implemented'); - }); - } - - - /** - * Return redis or redis.cluster Dataloader Client - * - * @memberof RedisConnection - */ - public getRedisDataloaderClient() { - let client: RedisClusterCache | RedisCache; - if (config.REDIS_CLUSTER_ENABLED) { - if (!config.REDIS_CLUSTER_URL) { - throw new Error(`No nodes defined for cluster, ${config.REDIS_CLUSTER_URL}`); - } - this.logger.info('Setting Redis.Cluster connection'); - client = new RedisClusterCache(config.REDIS_CLUSTER_URL as any, this.opts); - } else { - this.logger.info('Setting Redis connection'); - client = new RedisCache(config.REDIS_URL as any || this.opts); - } - return client; - } - - /** - * Close Redis client connection. - * - * @memberof RedisConnection - */ - public disconnect() { - if (!this.client) { - return; - } - return this.client.close(); - } -} diff --git a/servers/backend-server/src/env.ts b/servers/backend-server/src/env.ts new file mode 100644 index 000000000..12f15ea26 --- /dev/null +++ b/servers/backend-server/src/env.ts @@ -0,0 +1,5 @@ +/* eslint-disable jest/require-hook */ +import * as dotenv from 'dotenv'; +if (process.env.ENV_FILE) { + dotenv.config({ path: process.env.ENV_FILE }); +} \ No newline at end of file diff --git a/servers/backend-server/src/express-app.ts b/servers/backend-server/src/express-app.ts deleted file mode 100755 index 2861c409e..000000000 --- a/servers/backend-server/src/express-app.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -import * as express from 'express'; - -import modules from './modules'; -import { errorMiddleware } from './middleware/error'; -import { contextServicesMiddleware } from './middleware/services'; -import { IModuleService } from './interfaces'; - -const cookiesMiddleware = require('universal-cookie-express'); - -export function expressApp(options: IModuleService, middlewares, http?) { - const app: express.Express = express(); - - app.use(contextServicesMiddleware(options.createContext, options.serviceContext)); - - for (const applyBeforeware of modules.beforewares) { - applyBeforeware(app); - } - - app.use(cookiesMiddleware()); - - // Don't rate limit heroku - app.enable('trust proxy'); - - if (middlewares !== null) { - app.use(middlewares); - } - - app.use('/', express.static(__FRONTEND_BUILD_DIR__, { maxAge: '180 days' })); - app.use('/apollo-server-n-client/counter', express.static(__FRONTEND_BUILD_DIR__, { maxAge: '180 days' })); - - // app.use(corsMiddleware); - app.use((req, res, next) => { - res.header('Access-Control-Allow-Credentials', JSON.stringify(true)); - res.header('Access-Control-Allow-Origin', req.headers.origin as string); - res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); - res.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept'); - next(); - }); - - - const corsOptions = { - origin: true, - credentials: true, - }; - - for (const applyMiddleware of modules.middlewares) { - applyMiddleware(app); - } - - if (__DEV__) { - app.use(errorMiddleware); - } - - return app; -} diff --git a/servers/backend-server/src/index.ts b/servers/backend-server/src/index.ts index ba7f2e90e..282ac7565 100755 --- a/servers/backend-server/src/index.ts +++ b/servers/backend-server/src/index.ts @@ -1,10 +1,6 @@ /// -import * as dotenv from 'dotenv'; +import './env'; import 'reflect-metadata'; - -if (process.env.ENV_FILE) { - dotenv.config({ path: process.env.ENV_FILE }) -} import { logger } from '@cdm-logger/server'; import { Service } from './service'; diff --git a/servers/backend-server/src/interfaces/index.ts b/servers/backend-server/src/interfaces/index.ts deleted file mode 100755 index 6e2ea68c1..000000000 --- a/servers/backend-server/src/interfaces/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './module-interface'; diff --git a/servers/backend-server/src/interfaces/module-interface.ts b/servers/backend-server/src/interfaces/module-interface.ts deleted file mode 100755 index 7a27f2b40..000000000 --- a/servers/backend-server/src/interfaces/module-interface.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLSchema } from 'graphql'; -import { CdmLogger } from '@cdm-logger/core'; -type ILogger = CdmLogger.ILogger; - - -export interface IModuleService { - serviceContainer: any; - serviceContext: any; - dataSource: any; - defaultPreferences: any; - createContext: any; - schema: GraphQLSchema; - logger: ILogger; -} - - diff --git a/servers/backend-server/src/main.spec.ts b/servers/backend-server/src/main.spec.ts deleted file mode 100755 index 03d85636f..000000000 --- a/servers/backend-server/src/main.spec.ts +++ /dev/null @@ -1,129 +0,0 @@ -// import {GRAPHQL_ROUTE, GRAPHIQL_ROUTE, main} from './main'; -// import {get as httpGet, Server} from 'http'; -// import 'jest'; - -// const ERRNO_KEY = 'errno'; -// const PORT: number = 8080; - -// function getFromServer(uri) { -// return new Promise((resolve, reject) => { -// httpGet(`http://localhost:${PORT}${uri}`, (res) => { -// resolve(res); -// }).on('error', (err: Error) => { -// reject(err); -// }); -// }); -// } - -// describe('main', () => { -// it('should be able to Initialize a server (production)', () => { -// return main({ -// enableCors: false, -// enableGraphiql: false, -// env: 'production', -// port: PORT, -// }) -// .then(([server]) => { -// return (server).close(); -// }); -// }); - -// it('should be able to Initialize a server (development)', () => { -// return main({ -// enableCors: true, -// enableGraphiql: true, -// env: 'dev', -// port: PORT, -// }) -// .then(([server]) => { -// return (server).close(); -// }); -// }); - -// it('should have a working GET graphql (developemnt)', () => { -// return main({ -// enableCors: true, -// enableGraphiql: true, -// env: 'dev', -// port: PORT, -// }) -// .then(([server]) => { -// return getFromServer(GRAPHQL_ROUTE).then((res: any) => { -// (server).close(); -// // GET without query returns 400 -// expect(res.statusCode).toBe(400); -// }); -// }); -// }); - -// it('should have a working GET graphql (production)', () => { -// return main({ -// enableCors: false, -// enableGraphiql: false, -// env: 'production', -// port: PORT, -// }) -// .then(([server]) => { -// return getFromServer(GRAPHQL_ROUTE).then((res: any) => { -// (server).close(); -// // GET without query returns 400 -// expect(res.statusCode).toBe(400); -// }); -// }); -// }); - -// it('should have a working graphiql (developemnt)', () => { -// return main({ -// enableCors: true, -// enableGraphiql: true, -// env: 'dev', -// port: PORT, -// }) -// .then(([server]) => { -// return getFromServer(GRAPHIQL_ROUTE).then((res: any) => { -// (server).close(); -// expect(res.statusCode).toBe(200); -// }); -// }); -// }); - -// it('should have block graphiql (production)', () => { -// return main({ -// enableCors: false, -// enableGraphiql: false, -// env: 'production', -// port: PORT, -// }) -// .then(([server]) => { -// return getFromServer(GRAPHIQL_ROUTE).then((res: any) => { -// (server).close(); -// expect(res.statusCode).toBe(404); -// }); -// }); -// }); - -// it('should reject twice on same port', () => { -// return main({ -// enableCors: false, -// enableGraphiql: false, -// env: 'production', -// port: PORT, -// }) -// .then(([server]) => { -// return main({ -// enableCors: false, -// enableGraphiql: false, -// env: 'production', -// port: PORT, -// }) -// .then(([secondServer]) => { -// (server).close(); -// (secondServer).close(); -// throw new Error('Was able to listen twice!'); -// }, (err: Error) => { -// (server).close(); -// expect(err[ERRNO_KEY]).toBe('EADDRINUSE'); -// }); -// }); -// }); -// }); diff --git a/servers/backend-server/src/middleware/cors.ts b/servers/backend-server/src/middleware/cors.ts deleted file mode 100755 index 8f2ae69db..000000000 --- a/servers/backend-server/src/middleware/cors.ts +++ /dev/null @@ -1,30 +0,0 @@ -import * as cors from 'cors'; -import * as express from 'express'; -import { config } from '../config'; -import { logger } from '@cdm-logger/server'; - -const CLIENT_URL = config.CLIENT_URL; -const BACKEND_URL = config.BACKEND_URL; - -const corsWhitelist = [ - BACKEND_URL, - CLIENT_URL, - config.GRAPHQL_URL, -]; -logger.info('Cors whitelist: %j', corsWhitelist); -const corsOptions = { - origin: (origin, callback) => { - if (corsWhitelist.indexOf(origin) !== -1) { - callback(null, true); - } else { - // TODO: only throw when in debug mode - logger.error('url (%s) is not in the whitelist', origin); - // callback(new Error('Not allowed by CORS')) - logger.warn('allowing all origins temporarily, you need to disable it.'); - callback(null, true); - } - }, - credentails: false, -}; - -export const corsMiddleware = cors(corsOptions); diff --git a/servers/backend-server/src/middleware/error.ts b/servers/backend-server/src/middleware/error.ts deleted file mode 100755 index 719239079..000000000 --- a/servers/backend-server/src/middleware/error.ts +++ /dev/null @@ -1,55 +0,0 @@ -/// - -import * as path from 'path'; -import * as fs from 'fs'; -import * as url from 'url'; -import { logger } from '@cdm-logger/server'; - - -let assetMap; - -const stripCircular = (from, seen?: any) => { - const to = Array.isArray(from) ? [] : {}; - seen = seen || []; - seen.push(from); - Object.getOwnPropertyNames(from).forEach(key => { - if (!from[key] || (typeof from[key] !== 'object' && !Array.isArray(from[key]))) { - to[key] = from[key]; - } else if (seen.indexOf(from[key]) < 0) { - to[key] = stripCircular(from[key], seen.slice(0)); - } else { to[key] = '[Circular]'; } - }); - return to; -}; - -const { pathname } = url.parse(__BACKEND_URL__); - -export const errorMiddleware = - (e, req, res, next) => { - if (req.path === pathname) { - const stack = e.stack.toString().replace(/[\n]/g, '\\n'); - res.status(200).send(`[{"data": {}, "errors":[{"message": "${stack}"}]}]`); - } else { - logger.error(e); - - if (__DEV__ || !assetMap) { - assetMap = JSON.parse(fs.readFileSync(path.join(__FRONTEND_BUILD_DIR__, 'assets.json')).toString()); - } - - const serverErrorScript = ``; - const vendorScript = assetMap['vendor.js'] - ? `` - : ''; - - res.status(200).send( - `${serverErrorScript}
- ${vendorScript} - - `, - ); - } - }; - - diff --git a/servers/backend-server/src/middleware/moleculer-inter-namespace.ts b/servers/backend-server/src/middleware/moleculer-inter-namespace.ts deleted file mode 100644 index 4c6f1d8b1..000000000 --- a/servers/backend-server/src/middleware/moleculer-inter-namespace.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* eslint-disable no-param-reassign */ -import { ServiceBroker, Middleware } from 'moleculer'; -import * as _ from 'lodash'; - -export const InterNamespaceMiddleware = function (options): Middleware { - if (!Array.isArray(options)) { - throw new Error('Must be an Array'); - } - - let thisBroker: ServiceBroker; - const brokers: { [key: string]: ServiceBroker } = {}; - - return { - created(broker: ServiceBroker) { - thisBroker = broker; - options.forEach((nsOpts) => { - if (_.isString(nsOpts)) { - nsOpts = { - namespace: nsOpts, - }; - } - const ns = nsOpts.namespace; - - const brokerOpts = _.defaultsDeep( - {}, - nsOpts, - { nodeID: null, middlewares: null, created: null, started: null }, - broker.options, - ); - brokers[ns] = new ServiceBroker(brokerOpts); - }); - }, - - started() { - return Promise.all(Object.values(brokers).map((b) => b.start())); - }, - - stopped() { - return Promise.all(Object.values(brokers).map((b) => b.stop())); - }, - - call(next) { - return function (actionName, params, opts = {}) { - if (_.isString(actionName) && actionName.includes('@')) { - const [action, namespace] = actionName.split('@'); - - if (brokers[namespace]) { - return brokers[namespace].call(action, params, opts); - } - if (namespace === thisBroker.namespace) { - return next(action, params, opts); - } - throw new Error(`Unknown namespace: ${namespace}`); - } - - return next(actionName, params, opts); - }; - }, - }; -}; diff --git a/servers/backend-server/src/middleware/persistedQuery.ts b/servers/backend-server/src/middleware/persistedQuery.ts deleted file mode 100755 index a392c8728..000000000 --- a/servers/backend-server/src/middleware/persistedQuery.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { invert, isArray } from 'lodash'; -import { GRAPHIQL_ROUTE } from '../ENDPOINTS'; -import { logger } from '@cdm-logger/server'; - -let reqlib: any = require('app-root-path'); - -let persistCache = true; -let queryMap; -try { - queryMap = reqlib.require('@sample-stack/platform-browser/extracted_queries.json'); - -} catch (err) { - logger.warn('extracted_queries.json file is unavailable, disabling persist queries'); -} -export const persistedQueryMiddleware = (req, res, next) => { - - if (queryMap) { - const invertedMap = invert(queryMap); - - if (isArray(req.body)) { - req.body = req.body.map(body => { - const id = body['id']; - return { - query: invertedMap[id], - ...body, - }; - }); - next(); - } else { - if (!__DEV__ || (req.get('Referer') || '').indexOf(GRAPHIQL_ROUTE) < 0) { - res.status(500).send('Unknown GraphQL query has been received, rejecting...'); - } else { - next(); - } - } - } else { - next(); - } -}; - diff --git a/servers/backend-server/src/middleware/services.ts b/servers/backend-server/src/middleware/services.ts deleted file mode 100755 index 4dc7cfcc3..000000000 --- a/servers/backend-server/src/middleware/services.ts +++ /dev/null @@ -1,16 +0,0 @@ -import 'isomorphic-fetch'; - - -export const contextServicesMiddleware = (createContext, serviceContext) => (req, res, next) => { - Promise.all([ - createContext(req, res), - serviceContext(req, res), - ]) - .then(([ context, services ]) => { - req.context = context; - req.services = services; - - next(); - }) - .catch((err) => next()); -}; diff --git a/servers/backend-server/src/server-setup/graphql-server.ts b/servers/backend-server/src/server-setup/graphql-server.ts deleted file mode 100755 index 39409ed5f..000000000 --- a/servers/backend-server/src/server-setup/graphql-server.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { ApolloServer, ApolloServerExpressConfig } from 'apollo-server-express'; -import 'isomorphic-fetch'; -import { Express } from 'express'; -import * as http from 'http'; -import { RedisClusterCache, RedisCache } from 'apollo-server-cache-redis'; -import { CdmLogger } from '@cdm-logger/core'; -import { ApolloServerPluginDrainHttpServer, ApolloServerPluginLandingPageDisabled } from 'apollo-server-core'; -import { WebSocketServer } from 'ws'; -import { IModuleService } from '../interfaces'; -import { GraphqlWs } from './graphql-ws'; - -type ILogger = CdmLogger.ILogger; - -let debug = false; -if ((process.env.LOG_LEVEL && process.env.LOG_LEVEL === 'trace') || process.env.LOG_LEVEL === 'debug') { - debug = true; -} - -// @workaround as the `dataSources` not available in Subscription (websocket) Context. -// https://github.com/apollographql/apollo-server/issues/1526 need to revisit in Apollo-Server v3. -const constructDataSourcesForSubscriptions = (context, cache, dataSources) => { - const intializeDataSource = (instance) => { - instance.initialize({ context, cache }); - }; - // tslint:disable-next-line:forin - // eslint-disable-next-line guard-for-in - for (const prop in dataSources) { - // tslint:disable-next-line:no-console - intializeDataSource(dataSources[prop]); - } - return dataSources; -}; - -let wsServerCleanup: any; -export class GraphqlServer { - private logger: ILogger; - - // private wsServerCleanup: any; - constructor( - private app: Express, - private httpServer: http.Server, - private cache: RedisCache | RedisClusterCache, - private moduleService: IModuleService, - private enableSubscription = true, - ) { - this.logger = this.moduleService.logger.child({ className: 'GraphqlServer' }); - if (enableSubscription) { - const wsServer = new WebSocketServer({ - server: this.httpServer, - path: __GRAPHQL_ENDPOINT__, - }); - const graphqlWs = new GraphqlWs(wsServer, this.moduleService, this.cache); - wsServerCleanup = graphqlWs.create(); - // wsServerCleanup = useServer({ schema: this.moduleService.schema}, wsServer) - } - } - - public async initialize() { - this.logger.info('GraphqlServer initializing...'); - const apolloServer = this.configureApolloServer(); - await apolloServer.start(); - apolloServer.applyMiddleware({ app: this.app }); - this.logger.info('GraphqlServer initialized'); - } - - getUserIpAddress(req) { - let ip = (req?.headers['x-forwarded-for'] || '').split(',')[0] || req?.connection?.remoteAddress; - if (ip === '::1') { - ip = '127.0.0.1'; - } - return ip; - } - - private configureApolloServer(): ApolloServer { - const serverConfig: ApolloServerExpressConfig = { - debug, - schema: this.moduleService.schema, - dataSources: () => this.moduleService.dataSource, - cache: this.cache, - context: async ({ - req, - res, - connection, - }: { - req: Express.Request; - res: Express.Response; - connection: any; - }) => { - let context; - let addons = {}; - try { - if (connection) { - context = connection.context; - if (!context.dataSources) { - addons = { - // @workaround for apollo server issue #1526 - dataSources: constructDataSourcesForSubscriptions( - connection.context, - this.cache, - this.moduleService.dataSource, - ), - }; - } else { - addons = { - // @workaround for apollo server issue #1526 - dataSources: context.dataSources, - }; - } - } else { - const pureContext = await this.moduleService.createContext(req, res); - const contextServices = await this.moduleService.serviceContext(req, res); - context = { - ...pureContext, - ...contextServices, - preferences: this.moduleService.defaultPreferences, - // update: updateContainers, - }; - } - context.userIp = this.getUserIpAddress(req); - } catch (err) { - this.logger.error('adding context to graphql failed due to [%o]', err); - throw err; - } - return { - req, - // res, - ...context, - ...addons, - }; - }, - plugins: [ - // process.env.NODE_ENV === 'production' - // ? ApolloServerPluginLandingPageDisabled() - // : - // ApolloServerPluginLandingPageGraphQLPlayground(), - // ApolloServerPluginLandingPageDisabled(), - ApolloServerPluginDrainHttpServer({ httpServer: this.httpServer }), - ], - }; - if (this.enableSubscription) { - serverConfig.plugins.push({ - async serverWillStart() { - return { - drainServer: async () => { - await wsServerCleanup.dispose(); - }, - }; - }, - }); - } - return new ApolloServer(serverConfig); - } -} diff --git a/servers/backend-server/src/server-setup/graphql-subscription-server.ts b/servers/backend-server/src/server-setup/graphql-subscription-server.ts deleted file mode 100755 index 5710beecf..000000000 --- a/servers/backend-server/src/server-setup/graphql-subscription-server.ts +++ /dev/null @@ -1,113 +0,0 @@ -// import { SubscriptionServer, ConnectionContext, ExecutionParams } from 'subscriptions-transport-ws'; -// import { execute, subscribe, ExecutionResult } from 'graphql'; -// // import { GraphQLServerOptions } from 'apollo-server-core'; -// import { formatApolloErrors } from 'apollo-server-errors'; -// import { GraphQLServerOptions } from 'apollo-server-core/dist/graphqlOptions'; -// import { Context } from 'apollo-server-core'; -// import { RedisClusterCache, RedisCache } from 'apollo-server-cache-redis'; -// import { CdmLogger } from '@cdm-logger/core'; -// import { IModuleService } from '../interfaces'; - -// type ILogger = CdmLogger.ILogger; - -// // @workaround as the `dataSources` not available in Subscription (websocket) Context. -// // https://github.com/apollographql/apollo-server/issues/1526 need to revisit in Apollo-Server v3. -// const constructDataSourcesForSubscriptions = (context, cache, dataSources) => { -// const intializeDataSource = (instance) => { -// instance.initialize({ context, cache }); -// }; -// // tslint:disable-next-line:forin -// for (const prop in dataSources) { -// // tslint:disable-next-line:no-console -// intializeDataSource(dataSources[prop]); -// } -// return dataSources; -// }; - -// export class GraphqlSubscriptionServer { -// private subscriptionServer: SubscriptionServer; - -// private context: Context; - -// private logger: ILogger; - -// constructor( -// private moduleService: IModuleService, -// private cache: RedisCache | RedisClusterCache, -// private requestOptions: Partial> = Object.create(null), -// ) { -// this.logger = this.moduleService.logger.child({ className: 'GraphqlSubscriptionServer' }); -// } - -// public create() { -// this.subscriptionServer = SubscriptionServer.create( -// { -// schema: this.moduleService.schema as any, -// execute, -// subscribe, -// onConnect: async (connectionParams: any, webSocket: any, ctx: ConnectionContext) => { -// try { -// this.logger.debug(`Subscription client connected using built-in SubscriptionServer.`); -// const pureContext = await this.moduleService.createContext(connectionParams, webSocket); -// const contextServices = await this.moduleService.serviceContext(connectionParams, webSocket); -// const context = { -// ...contextServices, -// ...pureContext, -// preferences: this.moduleService.defaultPreferences, -// // update: updateContainers, -// wsCtx: ctx, -// }; -// const addons = { -// dataSources: constructDataSourcesForSubscriptions( -// context, -// this.cache, -// this.moduleService.dataSource, -// ), -// }; -// return { -// ...context, -// ...addons, -// }; -// } catch (e) { -// this.logger.error(e); -// } -// }, -// onOperation: async (message: { payload: any }, connection: ExecutionParams) => { -// connection.formatResponse = (value: ExecutionResult) => ({ -// ...value, -// errors: -// value.errors && -// formatApolloErrors([...value.errors], { -// formatter: this.requestOptions.formatError, -// debug: this.requestOptions.debug, -// }), -// }); -// let context: Context = this.context ? this.context : { connection }; -// try { -// context = -// typeof this.context === 'function' -// ? await this.context({ connection, payload: message.payload }) -// : context; -// } catch (e) { -// throw formatApolloErrors([e], { -// formatter: this.requestOptions.formatError, -// debug: this.requestOptions.debug, -// })[0]; -// } - -// return { ...connection }; // TODO: we didn't add `context` -// }, -// }, -// { -// noServer: true, -// }, -// ); -// return this.subscriptionServer; -// } - -// public disconnect() { -// if (this.subscriptionServer) { -// this.subscriptionServer.close(); -// } -// } -// } diff --git a/servers/backend-server/src/server-setup/graphql-ws.ts b/servers/backend-server/src/server-setup/graphql-ws.ts deleted file mode 100644 index 8563a79bb..000000000 --- a/servers/backend-server/src/server-setup/graphql-ws.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { useServer } from 'graphql-ws/lib/use/ws'; -import { GraphQLSchema, parse, getOperationAST, GraphQLError, validate } from 'graphql'; -import { GraphQLServerOptions } from 'apollo-server-core/dist/graphqlOptions'; -import { Context } from 'apollo-server-core'; -import { RedisClusterCache, RedisCache } from 'apollo-server-cache-redis'; -import { CdmLogger } from '@cdm-logger/core'; -import { Disposable, SubscribeMessage } from 'graphql-ws'; -import { WebSocketServer } from 'ws'; -import { IModuleService } from '../interfaces'; -import { createContextFromConnectionParams } from './utils'; - -type ILogger = CdmLogger.ILogger; - -// @workaround as the `dataSources` not available in Subscription (websocket) Context. -// https://github.com/apollographql/apollo-server/issues/1526 need to revisit in Apollo-Server v3. -const constructDataSourcesForSubscriptions = (context, cache, dataSources) => { - const intializeDataSource = (instance) => { - instance.initialize({ context, cache }); - }; - // tslint:disable-next-line:forin - for (const prop in dataSources) { - // tslint:disable-next-line:no-console - intializeDataSource(dataSources[prop]); - } - return dataSources; -}; - -/** - * Accept only subscription operations - * Code from graphql-ws recipes: - * https://github.com/enisdenjo/graphql-ws/blob/master/README.md#recipes - */ -function allowOnlySub(schema: GraphQLSchema, msg: SubscribeMessage) { - // construct the execution arguments - const args = { - schema, - operationName: msg.payload.operationName, - document: parse(msg.payload.query), - variableValues: msg.payload.variables, - }; - - const operationAST = getOperationAST(args.document, args.operationName); - if (!operationAST) { - // returning `GraphQLError[]` sends an `ErrorMessage` and stops the subscription - return [new GraphQLError('Unable to identify operation')]; - } - - // handle mutation and query requests - if (operationAST.operation !== 'subscription') { - // returning `GraphQLError[]` sends an `ErrorMessage` and stops the subscription - // return [new GraphQLError('Only subscription operations are supported')]; - - // or if you want to be strict and terminate the connection on illegal operations - throw new Error('Only subscription operations are supported'); - } - - // dont forget to validate - const errors = validate(args.schema, args.document); - if (errors.length > 0) { - // returning `GraphQLError[]` sends an `ErrorMessage` and stops the subscription - return errors; - } - - // ready execution arguments - return args; -} - -export class GraphqlWs { - private subscriptionServer: Disposable; - - private logger: ILogger; - - constructor( - private wsServer: WebSocketServer, - private moduleService: IModuleService, - protected cache: RedisCache | RedisClusterCache, - ) {} - - public create() { - this.subscriptionServer = useServer( - { - schema: this.moduleService.schema, - // Adding a context property lets you add data to your GraphQL operation context - context: async (ctx) => { - // Returning an object here will add that information to our - // GraphQL context, which all of our resolvers have access to. - // ctx is the `graphql-ws` Context where connectionParams live - try { - const pureContext = await this.moduleService.createContext(ctx.connectionParams, null); - const contextServices = await this.moduleService.serviceContext(ctx.connectionParams, null); - const context = { - ...contextServices, - ...pureContext, - preferences: this.moduleService.defaultPreferences, - // update: updateContainers, - ...ctx, - }; - const addons = { - dataSources: constructDataSourcesForSubscriptions( - context, - this.cache, - this.moduleService.dataSource, - ), - }; - return { - ...context, - ...addons, - ...ctx, - ...pureContext, - preferences: this.moduleService.defaultPreferences, - }; - } catch (err) { - this.logger.error(err); - } - }, - // onConnect: async (ctx) => { - // // do anything when connected - // }, - // onOperation: (ctx, msg) => { - - // } - // onOperation: async (message: { payload: any }, connection: ExecutionParams) => { - // connection.formatResponse = (value: ExecutionResult) => ({ - // ...value, - // errors: - // value.errors && - // formatApolloErrors([...value.errors], { - // formatter: this.requestOptions.formatError, - // debug: this.requestOptions.debug, - // }), - // }); - // let context: Context = this.context ? this.context : { connection }; - // try { - // context = - // typeof this.context === 'function' - // ? await this.context({ connection, payload: message.payload }) - // : context; - // } catch (e) { - // throw formatApolloErrors([e], { - // formatter: this.requestOptions.formatError, - // debug: this.requestOptions.debug, - // })[0]; - // } - - // return { ...connection }; // TODO: we didn't add `context` - // }, - }, - this.wsServer, - ); - return this.subscriptionServer; - } - - public disconnect() { - if (this.subscriptionServer) { - this.subscriptionServer.dispose(); - } - } -} diff --git a/servers/backend-server/src/server-setup/utils.ts b/servers/backend-server/src/server-setup/utils.ts deleted file mode 100644 index d531c09c5..000000000 --- a/servers/backend-server/src/server-setup/utils.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { RedisCache } from 'apollo-server-cache-redis'; -import { CdmLogger } from '@cdm-logger/core'; - -type ILogger = CdmLogger.ILogger; - -// @workaround as the `dataSources` not available in Subscription (websocket) Context. -// https://github.com/apollographql/apollo-server/issues/1526 need to revisit in Apollo-Server v3. -const constructDataSourcesForSubscriptions = (context, cache, dataSources) => { - const intializeDataSource = (instance) => { - instance.initialize({ context, cache }); - }; - // tslint:disable-next-line:forin - for (const prop in dataSources) { - // tslint:disable-next-line:no-console - intializeDataSource(dataSources[prop]); - } - return dataSources; -}; - -/** - * It'll get messy below but there's an issue currently with the state of the protocols that can be used (subscriptions-transport-ws vs graphql-ws). - * Read more from: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#the-graphql-ws-transport-library - * - * Bottomline is, if we use the newer and actively maintained `graphql-ws` lib, then the GraphQL Playground will not work because it uses the old protocol. - * - * The approach below tries to support both based on the template provided here but adjusted for our setup here. - * https://github.com/enisdenjo/graphql-ws#ws-backwards-compat - */ -export async function createContextFromConnectionParams( - context: any, - datasources: any, - cache: RedisCache, - logger: ILogger, -): Promise { - // logger.debug(`Subscription client connected using built-in SubscriptionServer.`); - const addons = { - dataSources: constructDataSourcesForSubscriptions(context, cache, datasources), - }; - return { - ...context, - ...addons, - }; -} \ No newline at end of file diff --git a/servers/backend-server/src/server-setup/websocket-multipath-update.ts b/servers/backend-server/src/server-setup/websocket-multipath-update.ts deleted file mode 100755 index 1cb518aa8..000000000 --- a/servers/backend-server/src/server-setup/websocket-multipath-update.ts +++ /dev/null @@ -1,88 +0,0 @@ -import * as url from 'url'; -// import { GraphqlSubscriptionServer } from './graphql-subscription-server'; -import { GraphqlWs } from './graphql-ws'; -import { WebSocketServer } from 'ws'; -import { Server } from 'http'; -import { RedisClusterCache, RedisCache } from 'apollo-server-cache-redis'; -import { IModuleService } from '../interfaces'; -import { useServer } from 'graphql-ws/lib/use/ws'; - -interface WebSocketsCache { - [key: string]: WebSocketServer; -} - -interface MultiWebsocketConfig { - [key: string]: any; -} -export class WebsocketMultiPathServer { - private webSockets: WebSocketsCache = {}; - // private graphqlSubscriptionServer: GraphqlSubscriptionServer; - private _graphqlWs: WebSocketServer; - constructor( - public moduleService: IModuleService, - public cache: RedisCache | RedisClusterCache, - multiplePathConfig?: MultiWebsocketConfig, - ) { - this._graphqlWs = new WebSocketServer({ noServer: true, path: __GRAPHQL_ENDPOINT__ }); - this.webSockets[__GRAPHQL_ENDPOINT__] = this._graphqlWs; - this.webSockets[__GRAPHQL_ENDPOINT__].on('connection', (ws, request) => { - console.log('--REQUESTED CONNECTION--', this._graphqlWs) - useServer({ - schema: moduleService.schema, - }, this._graphqlWs); - }); - - for (let key in multiplePathConfig) { - if (!multiplePathConfig.hasOwnProperty(key)) { - continue; - } - - if (!this.webSockets[key]) { - this.webSockets[key] = new WebSocketServer({ noServer: true }); - this.webSockets[key].on('connection', (ws, request) => { - Promise.all([ - moduleService.createContext(request, null), - moduleService.serviceContext(request, null), - - ]).then(multiplePathConfig[key](ws)); - }); - } - } - } - - public httpServerUpgrade(httpServer: Server) { - httpServer.on('upgrade', (request, socket, head) => { - const pathname = url.parse(request.url).pathname; - console.log('--UPGRADE', request.url) - - if (!this.webSockets[pathname]) { - // in development - if (pathname !== '/sockjs-node') { - // need to destroy - socket.destroy(); - } - } - - // code to run when a new connection is made - this.webSockets[pathname].handleUpgrade(request, socket, head, (ws) => { - this.webSockets[pathname].emit('connection', ws, request); - }); - }); - - // (new GraphqlWs(this.graphqlWs, this.moduleService, this.cache)).create(); - // useServer({ - // schema: this.moduleService.schema, - // }, this.graphqlWs); - - return httpServer; - } - - public get graphqlWs() { - return this._graphqlWs; - } - public close() { - for (const key in this.webSockets) { - this.webSockets[key].close(); - } - } -} diff --git a/servers/backend-server/src/service.ts b/servers/backend-server/src/service.ts index 6f9ef5d36..33c8a9627 100755 --- a/servers/backend-server/src/service.ts +++ b/servers/backend-server/src/service.ts @@ -1,17 +1,16 @@ -import { StackServer } from './stack-server'; import { logger } from '@cdm-logger/server'; import * as url from 'url'; +import modules, { settings } from './modules'; +import { MainStackServer } from '@common-stack/server-stack'; import { config } from './config'; const { port: serverPort, pathname, hostname } = url.parse(config.BACKEND_URL); export class Service { - - private app: StackServer; + private app: MainStackServer; public async initialize() { - - this.app = new StackServer(); + this.app = new MainStackServer(modules, settings); await this.app.initialize(); } diff --git a/servers/backend-server/src/stack-server.ts b/servers/backend-server/src/stack-server.ts deleted file mode 100755 index e24923faa..000000000 --- a/servers/backend-server/src/stack-server.ts +++ /dev/null @@ -1,263 +0,0 @@ -/* eslint-disable import/namespace */ -/* eslint-disable import/no-unresolved */ -/* eslint-disable import/no-extraneous-dependencies */ -// version 08/25/2021 -import * as http from 'http'; -import * as express from 'express'; -import { logger as serverLogger } from '@cdm-logger/server'; -import { Feature } from '@common-stack/server-core'; -import { ContainerModule, interfaces, Container } from 'inversify'; -import { ServiceBroker, ServiceSettingSchema } from 'moleculer'; -import { CommonType } from '@common-stack/core'; -import * as _ from 'lodash'; -import { CdmLogger } from '@cdm-logger/core'; -import { expressApp } from './express-app'; -import { GraphqlServer } from './server-setup/graphql-server'; -import { config } from './config'; -import { ConnectionBroker } from './connectors/connection-broker'; -import { brokerConfig } from './config/moleculer.config'; -import modules, { settings } from './modules'; -import { GatewaySchemaBuilder } from './api/schema-builder'; -import { WebsocketMultiPathServer } from './server-setup/websocket-multipath-update'; -import { IModuleService } from './interfaces'; -import { migrate } from './utils/migrations'; -import { InterNamespaceMiddleware } from './middleware/moleculer-inter-namespace'; -// This is temp and will be replaced one we add support for rules in Feature - -type ILogger = CdmLogger.ILogger; - -function startListening(port) { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const server = this; - return new Promise((resolve) => { - server.listen(port, resolve); - }); -} - -const infraModule = ({ broker, pubsub, mongoClient, logger }) => - new ContainerModule((bind: interfaces.Bind) => { - bind('Logger').toConstantValue(logger); - bind(CommonType.LOGGER).toConstantValue(logger); - bind('Environment').toConstantValue(config.NODE_ENV || 'development'); - bind(CommonType.ENVIRONMENT).toConstantValue(config.NODE_ENV || 'development'); - bind('PubSub').toConstantValue(pubsub); - bind(CommonType.PUBSUB).toConstantValue(pubsub); - bind(CommonType.MOLECULER_BROKER).toConstantValue(broker); - bind('MoleculerBroker').toConstantValue(broker); - bind('MongoDBConnection').toConstantValue(mongoClient); - }); - -/** - * Controls the lifecycle of the Application Server - * - * @export - * @class StackServer - */ -export class StackServer { - public httpServer: http.Server & { startListening?: (port) => void }; - - private app: express.Express; - - private logger: ILogger; - - private connectionBroker: ConnectionBroker; - - private mainserviceBroker: ServiceBroker; - - private microserviceBroker: ServiceBroker; - - private multiPathWebsocket: WebsocketMultiPathServer; - - private serviceContainer: Container; - - private microserviceContainer: Container; - - constructor() { - this.logger = serverLogger.child({ className: 'StackServer' }); - } - - public async initialize() { - this.logger.info('StackServer initializing'); - - // eslint-disable-next-line import/namespace - this.connectionBroker = new ConnectionBroker(brokerConfig.transporter, this.logger); - const redisClient = this.connectionBroker.redisDataloaderClient; - - const mongoClient = await this.connectionBroker.mongoConnection; - - // Moleculer Broker Setup - this.mainserviceBroker = new ServiceBroker({ - ...brokerConfig, - middlewares: [ - InterNamespaceMiddleware([ - { - namespace: 'api-admin', - transporter: brokerConfig.transporter, - }, - ]), - ], - started: async () => { - await modules.preStart(this.serviceContainer); - if (config.NODE_ENV === 'development') { - // await modules.microservicePreStart(this.micorserviceContainer); - } - - try { - this.logger.info('Starting Migration'); - await migrate(mongoClient, this.serviceContainer, this.logger); - this.logger.info('End Migration'); - } catch (e) { - this.logger.error('Error while running migrations', e); - this.logger.error(e.stack); - } - - try { - await modules.postStart(this.serviceContainer); - } catch (e) { - this.logger.error('Error while running Post Start', e); - this.logger.error(e.stack); - } - // start DB migration - - if (config.NODE_ENV === 'development') { - // await modules.microservicePostStart(this.micorserviceContainer); - } - }, - - // created, - async created() { - return Promise.resolve(); - }, - }); - - if (config.NODE_ENV === 'development') { - this.microserviceBroker = new ServiceBroker({ - ...brokerConfig, - nodeID: 'node-broker-2', - started: async () => { - await modules.microservicePreStart(this.microserviceContainer); - await modules.microservicePostStart(this.microserviceContainer); - }, - // created, - created: async () => Promise.resolve(), - }); - } - const pubsub = await this.connectionBroker.graphqlPubsub; - const InfraStructureFeature = new Feature({ - createContainerFunc: [ - () => - infraModule({ - broker: this.mainserviceBroker, - pubsub, - mongoClient, - logger: serverLogger, - }), - ], - createServiceFunc: (container) => ({ moleculerBroker: container.get(CommonType.MOLECULER_BROKER) }), - createHemeraContainerFunc: [ - () => - infraModule({ - broker: this.mainserviceBroker, - pubsub, - mongoClient, - logger: serverLogger, - }), - ], - }); - - const allModules = new Feature(InfraStructureFeature, modules as Feature); - const executableSchema = await new GatewaySchemaBuilder({ - schema: allModules.schemas, - resolvers: allModules.createResolvers({ - pubsub, - logger: serverLogger, - subscriptionID: `${settings.subTopic}`, - }), - directives: [], - directiveResolvers: allModules.createDirectives({ logger: this.logger }), - logger: serverLogger, - }).build(); - - // set the service container - this.serviceContainer = await allModules.createContainers({ ...settings, mongoConnection: mongoClient }); - const createServiceContext = allModules.createServiceContext({ ...settings, mongoConnection: mongoClient }); - const serviceBroker: IModuleService = { - serviceContainer: this.serviceContainer, - serviceContext: createServiceContext, - dataSource: allModules.createDataSource(), - defaultPreferences: allModules.createDefaultPreferences(), - createContext: async (req, res) => allModules.createContext(req, res), - logger: serverLogger, - schema: executableSchema, - }; - allModules.loadMainMoleculerService({ - broker: this.mainserviceBroker, - container: this.serviceContainer, - settings, - }); - if (config.NODE_ENV === 'development') { - this.microserviceContainer = await allModules.createHemeraContainers({ - ...settings, - mongoConnection: mongoClient, - }); - allModules.loadClientMoleculerService({ - broker: this.microserviceBroker, - container: this.microserviceContainer, - settings, - }); - } - - // initialize Servers - this.httpServer = http.createServer(); - this.app = await expressApp(serviceBroker, null, this.httpServer); - - this.httpServer.startListening = startListening.bind(this.httpServer); - this.httpServer.on('request', this.app); - this.httpServer.on('close', () => { - this.httpServer = undefined; - }); - - const customWebsocket = allModules.getWebsocketConfig(); - const customWebsocketEnable = !_.isEmpty(customWebsocket); - - if (customWebsocketEnable) { - this.multiPathWebsocket = new WebsocketMultiPathServer(serviceBroker, redisClient, customWebsocket); - this.httpServer = this.multiPathWebsocket.httpServerUpgrade(this.httpServer); - } - const graphqlServer = new GraphqlServer( - this.app, - this.httpServer, - redisClient, - serviceBroker, - !customWebsocketEnable, - ); - - await graphqlServer.initialize(); - } - - public async start() { - if (config.NODE_ENV === 'development') { - await Promise.all([this.mainserviceBroker.start(), this.microserviceBroker.start()]); - } else { - await this.mainserviceBroker.start(); - } - } - - public async cleanup() { - if (this.multiPathWebsocket) { - this.multiPathWebsocket.close(); - } - if (this.httpServer) { - await this.httpServer.close(); - } - if (this.connectionBroker) { - await this.connectionBroker.stop(); - } - if (this.mainserviceBroker) { - await this.mainserviceBroker.stop(); - } - if (this.microserviceBroker) { - await this.microserviceBroker.stop(); - } - } -} diff --git a/servers/backend-server/src/utils/migrations.ts b/servers/backend-server/src/utils/migrations.ts deleted file mode 100644 index 8301f25c0..000000000 --- a/servers/backend-server/src/utils/migrations.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable consistent-return */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable no-console */ -/* eslint-disable @typescript-eslint/restrict-template-expressions */ -import { Schema, Connection } from 'mongoose'; -import { Container } from 'inversify'; -import { CdmLogger } from '@cdm-logger/core'; - -export const MigrationSchema = new Schema({ - migrated_at: Date, - name: { required: true, type: String }, -}); - -export async function migrate(db: Connection, container: Container, logger: CdmLogger.ILogger) { - try { - const migrations = container.getAll<{ up: any; id: any }>('MongodbMigration'); - const model = db.model('Migration', MigrationSchema); - return await Promise.all( - migrations.map(async (migration) => { - const exists = await model.findOne({ name: migration.id }); - if (!exists) { - logger.info('Migrating %s', migration.id); - try { - await migration.up(); - await model.create({ name: migration.id, migrated_at: new Date() }); - } catch (e) { - console.log(`Can not process migration ${migration.id}: `, e); - } - } - return migration.id; - }), - ); - } catch (err) { - console.warn('ignoring migrate database due to ', err.message); - } -} diff --git a/servers/backend-server/tsconfig.base.json b/servers/backend-server/tsconfig.base.json new file mode 100755 index 000000000..ac196d55d --- /dev/null +++ b/servers/backend-server/tsconfig.base.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "ESNext", + "esModuleInterop": true, // Important for ESM interoperability + "moduleResolution": "node", + "jsx": "react", + "declaration": true, + "experimentalDecorators": true, + "preserveConstEnums": true, + "sourceMap": true, + "noImplicitAny": false, + "skipLibCheck": true, + "allowSyntheticDefaultImports": false, + "pretty": true, + "removeComments": false, + "lib": ["es2017", "dom", "esnext.asynciterable"], + "types": ["@types/node", "@types/jest"] + }, + "include": ["../../typings/*.d.ts"] +} diff --git a/servers/backend-server/tsconfig.json b/servers/backend-server/tsconfig.json index 4eb65fc98..4f5dca92c 100755 --- a/servers/backend-server/tsconfig.json +++ b/servers/backend-server/tsconfig.json @@ -1,19 +1,13 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "sourceMap": true, - "declaration": false, - "allowSyntheticDefaultImports": true, - "rootDirs": ["./src","../knexfile.ts"], - "outDir": "./dist" - }, - "include": [ - "../../typings/*.d.ts", - ], - "exclude": [ - "node_modules", - "lib", - "dist", - "webpack.config.js" - ] -} \ No newline at end of file + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@src/*": ["src/*"] + }, + "sourceMap": true, + "declaration": false, + "allowSyntheticDefaultImports": true, + "rootDirs": ["./src", "../knexfile.ts"], + "outDir": "./dist" + } +} diff --git a/servers/backend-server/webpack.config.js b/servers/backend-server/webpack.config.js index 5d90d2c24..dbfc4c158 100755 --- a/servers/backend-server/webpack.config.js +++ b/servers/backend-server/webpack.config.js @@ -1,17 +1,37 @@ +process.env.ENV_FILE !== null && require('dotenv').config({ path: process.env.ENV_FILE }); const webpack = require('webpack'); const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const nodeExternals = require('webpack-node-externals'); -const Dotenv = require('dotenv-webpack'); const NodemonPlugin = require('nodemon-webpack-plugin'); // Ding const EnvListPlugin = require('@common-stack/env-list-loader'); +const { writeBackendModuleFile } = require('@common-stack/rollup-vite-utils/lib/utils/utils.cjs'); +const packageConfig = require('./config.json'); const buildConfig = require('./build.config'); -const modulenameExtra = process.env.MODULENAME_EXTRA ? `${process.env.MODULENAME_EXTRA}|` : ''; -const modulenameRegex = new RegExp( - `(${modulenameExtra}@sample-stack*|ts-invariant|webpack/hot/poll)|(\\.(css|less|scss|png|ico|jpg|gif|xml|woff|woff2|otf|ttf|eot|svg)(\\?[0-9a-z]+)?$)`, -); +const modulenameExtra = process.env.BUILD_MODULE_TO_INCLUDE ? `${process.env.BUILD_MODULE_TO_INCLUDE}|` : ''; +let modulenameRegex; + +try { + modulenameRegex = new RegExp( + `(${modulenameExtra}ts-invariant|@common-stack/server-stack|webpack/hot/poll)|(\\.(css|less|scss|png|ico|jpg|gif|xml|woff|woff2|otf|ttf|eot|svg)(\\?[0-9a-z]+)?$)`, + ); + console.log('Module Name Regex: ', modulenameRegex); +} catch (error) { + console.error('Error creating regex for module name: ', error); +} + +if (process.env.BUILD_MODULE_TO_INCLUDE) { + console.log('Build Module to include (BUILD_MODULE_TO_INCLUDE): ', process.env.BUILD_MODULE_TO_INCLUDE); +} else { + console.log('BUILD_MODULE_TO_INCLUDE is not set.'); +} + +try { + writeBackendModuleFile(path.join(__dirname, 'src/modules'), packageConfig); +} catch (e) { + console.error(e); +} const config = { entry: { @@ -61,14 +81,32 @@ const config = { { loader: 'less-loader', options: { javascriptEnabled: true, sourceMap: true } }, ], }, - { test: /\.graphqls/, use: { loader: 'raw-loader' } }, + { test: /\.graphqls$/, use: { loader: 'raw-loader' } }, { test: /\.(graphql|gql)$/, use: [{ loader: 'graphql-tag/loader' }] }, + // { + // test: /\.[tj]sx?$/, + // use: { + // loader: 'babel-loader', + // options: { babelrc: true, rootMode: 'upward-optional' }, + // }, + // }, { - test: /\.[tj]sx?$/, - use: { - loader: 'babel-loader', - options: { babelrc: true, rootMode: 'upward-optional' }, + test: /\.tsx?$/, // for TypeScript + loader: 'esbuild-loader', + options: { + loader: 'tsx', // Or 'ts' for TypeScript without JSX + target: 'es2015', // Specify ECMAScript target version }, + exclude: /node_modules/, + }, + { + test: /\.jsx?$/, // for JavaScript + loader: 'esbuild-loader', + options: { + loader: 'jsx', // Or 'js' for plain JavaScript + target: 'es2015', + }, + exclude: /node_modules/, }, { // searches for files ends with /config/env-config.js or /config/public-config.js @@ -82,6 +120,10 @@ const config = { unsafeCache: false, }, resolve: { + alias: { + // Define your alias here + '@src': path.resolve(__dirname, 'src'), + }, symlinks: true, cacheWithContext: false, unsafeCache: false, @@ -102,7 +144,7 @@ const config = { watchOptions: { ignored: /dist/ }, output: { pathinfo: false, - filename: 'main.js', + filename: 'index.js', path: path.join(__dirname, 'dist'), publicPath: '/', sourceMapFilename: '[name].[chunkhash].js.map', @@ -129,14 +171,14 @@ const config = { })), ), ), - new CopyWebpackPlugin({ - patterns: [ - { - from: '../../tools/esm-wrapper.js', - to: 'index.js', - }, - ], - }), + // new CopyWebpackPlugin({ + // patterns: [ + // { + // from: '../../tools/esm-wrapper.js', + // to: 'index.js', + // }, + // ], + // }), ]), target: 'node', externals: [ diff --git a/servers/backend-server/webpack.config.mjs b/servers/backend-server/webpack.config.mjs new file mode 100644 index 000000000..0eb0953cf --- /dev/null +++ b/servers/backend-server/webpack.config.mjs @@ -0,0 +1,215 @@ +import 'source-map-support/register.js'; +import webpack from 'webpack'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import CopyWebpackPlugin from 'copy-webpack-plugin'; +import { CleanWebpackPlugin } from 'clean-webpack-plugin'; +import nodeExternals from 'webpack-node-externals'; +import Dotenv from 'dotenv-webpack'; +import NodemonPlugin from 'nodemon-webpack-plugin'; +import EnvListPlugin from '@common-stack/env-list-loader'; +import { writeBackendModuleFile } from '@common-stack/rollup-vite-utils/lib/utils/utils.cjs'; +import buildConfig from './build.config.mjs'; + +const modulenameExtra = process.env.BUILD_MODULE_TO_INCLUDE ? `${process.env.BUILD_MODULE_TO_INCLUDE}|` : ''; +let modulenameRegex; + +// Get the directory name of the current module +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +try { + modulenameRegex = new RegExp( + `(${modulenameExtra}ts-invariant|webpack/hot/poll)|(\\.(css|less|scss|png|ico|jpg|gif|xml|woff|woff2|otf|ttf|eot|svg)(\\?[0-9a-z]+)?$)`, + ); + console.log('Module Name Regex: ', modulenameRegex); +} catch (error) { + console.error('Error creating regex for module name: ', error); +} + +if (process.env.BUILD_MODULE_TO_INCLUDE) { + console.log('Build Module to include (BUILD_MODULE_TO_INCLUDE): ', process.env.BUILD_MODULE_TO_INCLUDE); +} else { + console.log('BUILD_MODULE_TO_INCLUDE is not set.'); +} + +try { + writeBackendModuleFile(path.join(__dirname, 'src/modules'), packageConfig); +} catch (e) { + console.error(e); +} + +const config = { + entry: { + // index: (process.env.NODE_ENV !== 'production' ? ['webpack/hot/poll?200'] : []).concat([ + // // 'raf/polyfill', + // './src/index.ts', + // ]), + server: { + import: './src/index.ts', + /* + * This prevents code-splitting of async imports into separate chunks. + * We can't allow that for the server, because Webpack will duplicate + * certain modules that must be shared into each chunk (context, + * gettext, DBDefs, linkedEntities, ...). + */ + chunkLoading: false, + }, + }, + name: 'server', + module: { + rules: [ + { + test: /\.(png|ico|jpg|gif|xml)$/, + use: { loader: 'url-loader', options: { name: '[hash].[ext]', limit: 100000 } }, + }, + { + test: /\.woff(2)?(\?[0-9a-z]+)?$/, + use: { loader: 'url-loader', options: { name: '[hash].[ext]', limit: 100000 } }, + }, + { + test: /\.(otf|ttf|eot|svg)(\?[0-9a-z]+)?$/, + use: { loader: 'file-loader', options: { name: '[hash].[ext]' } }, + }, + { + test: /\.css$/, + use: [ + { loader: 'isomorphic-style-loader' }, + { loader: 'css-loader', options: { sourceMap: true } }, + { loader: 'postcss-loader', options: { sourceMap: true } }, + ], + }, + { + test: /\.scss$/, + use: [ + { loader: 'isomorphic-style-loader' }, + { loader: 'css-loader', options: { sourceMap: true } }, + { loader: 'postcss-loader', options: { sourceMap: true } }, + { loader: 'sass-loader', options: { sourceMap: true } }, + ], + }, + { + test: /\.less$/, + use: [ + { loader: 'isomorphic-style-loader' }, + { loader: 'css-loader', options: { sourceMap: true } }, + { loader: 'postcss-loader', options: { sourceMap: true } }, + { loader: 'less-loader', options: { javascriptEnabled: true, sourceMap: true } }, + ], + }, + { test: /\.graphqls/, use: { loader: 'raw-loader' } }, + { test: /\.(graphql|gql)$/, use: [{ loader: 'graphql-tag/loader' }] }, + // { + // test: /\.[tj]sx?$/, + // use: { + // loader: 'babel-loader', + // options: { babelrc: true, rootMode: 'upward-optional' }, + // }, + // }, + { + test: /\.tsx?$/, // for TypeScript + loader: 'esbuild-loader', + options: { + loader: 'tsx', // Or 'ts' for TypeScript without JSX + target: 'es2015', // Specify ECMAScript target version + }, + exclude: /node_modules/, + }, + { + test: /\.jsx?$/, // for JavaScript + loader: 'esbuild-loader', + options: { + loader: 'jsx', // Or 'js' for plain JavaScript + target: 'es2015', + }, + exclude: /node_modules/, + }, + { + // searches for files ends with /config/env-config.js or /config/public-config.js + test: /config\/(env-config|public-config)\.(j|t)s/, + use: { + loader: '@common-stack/env-list-loader', + }, + }, + { test: /locales/, use: { loader: '@alienfast/i18next-loader' } }, + ], + unsafeCache: false, + }, + resolve: { + symlinks: true, + cacheWithContext: false, + unsafeCache: false, + extensions: [ + '.web.mjs', + '.web.js', + '.web.jsx', + '.web.ts', + '.web.tsx', + '.mjs', + '.js', + '.jsx', + '.ts', + '.tsx', + '.json', + ], + }, + watchOptions: { ignored: /dist/ }, + output: { + pathinfo: false, + filename: 'index.js', + path: path.join(__dirname, 'dist'), + // publicPath: '/', + sourceMapFilename: '[name].[chunkhash].js.map', + module: true, // Enable outputting ESM + library: { + type: 'module', // Specify library target as module + }, + }, + devtool: process.env.NODE_ENV === 'production' ? 'nosources-source-map' : 'cheap-module-source-map', + mode: process.env.NODE_ENV || 'development', + performance: { hints: false }, + plugins: (process.env.NODE_ENV !== 'production' + ? [ + // new Dotenv(), + new webpack.HotModuleReplacementPlugin(), + new NodemonPlugin({ script: './dist/index.js' }), + ] + : [] + ).concat([ + // The plugin lists the environment that required as well recommendation about the keys used. + new EnvListPlugin.Plugin(), + new CleanWebpackPlugin({ cleanOnceBeforeBuildPatterns: ['dist'] }), + new webpack.DefinePlugin( + Object.assign( + ...Object.entries(buildConfig).map(([k, v]) => ({ + [k]: typeof v !== 'string' ? v : `"${v.replace(/\\/g, '\\\\')}"`, + })), + ), + ), + // new CopyWebpackPlugin({ + // patterns: [ + // { + // from: '../../tools/esm-wrapper.js', + // to: 'index.js', + // }, + // ], + // }), + ]), + target: 'node18', + experiments: { + outputModule: true, + }, + externals: [ + nodeExternals(), + nodeExternals({ + modulesDir: path.resolve(__dirname, '../../node_modules'), + allowlist: [modulenameRegex], + }), + ], + optimization: { + concatenateModules: false, + minimize: false, + }, + // node: { __dirname: true, __filename: true }, +}; + +export default config; diff --git a/servers/frontend-server/.dockerignore b/servers/frontend-server/.dockerignore index 91e38de1d..e970f7fc4 100755 --- a/servers/frontend-server/.dockerignore +++ b/servers/frontend-server/.dockerignore @@ -1,5 +1,11 @@ * +!app/ +!public/ +!tools/ !dist/ +!build/ +!server.ts +!src !*.js !*.json !.npmrc \ No newline at end of file diff --git a/servers/frontend-server/.eslintrc.cjs b/servers/frontend-server/.eslintrc.cjs new file mode 100644 index 000000000..6bd1baae9 --- /dev/null +++ b/servers/frontend-server/.eslintrc.cjs @@ -0,0 +1,83 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + root: true, + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + env: { + browser: true, + commonjs: true, + es6: true, + }, + + // Base config + extends: ["eslint:recommended"], + + overrides: [ + // React + { + files: ["**/*.{js,jsx,ts,tsx}"], + plugins: ["react", "jsx-a11y"], + extends: [ + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + ], + settings: { + react: { + version: "detect", + }, + formComponents: ["Form"], + linkComponents: [ + { name: "Link", linkAttribute: "to" }, + { name: "NavLink", linkAttribute: "to" }, + ], + "import/resolver": { + typescript: {}, + }, + }, + }, + + // Typescript + { + files: ["**/*.{ts,tsx}"], + plugins: ["@typescript-eslint", "import"], + parser: "@typescript-eslint/parser", + settings: { + "import/internal-regex": "^~/", + "import/resolver": { + node: { + extensions: [".ts", ".tsx"], + }, + typescript: { + alwaysTryTypes: true, + }, + }, + }, + extends: [ + "plugin:@typescript-eslint/recommended", + "plugin:import/recommended", + "plugin:import/typescript", + ], + }, + + // Node + { + files: [".eslintrc.cjs", "server.js"], + env: { + node: true, + }, + }, + ], +}; diff --git a/servers/frontend-server/.gitignore b/servers/frontend-server/.gitignore new file mode 100644 index 000000000..d236ff75d --- /dev/null +++ b/servers/frontend-server/.gitignore @@ -0,0 +1,2 @@ +app/* +public/cdm-locales/* \ No newline at end of file diff --git a/servers/frontend-server/Dockerfile b/servers/frontend-server/Dockerfile index 7c582b257..7fba7469a 100755 --- a/servers/frontend-server/Dockerfile +++ b/servers/frontend-server/Dockerfile @@ -1,5 +1,5 @@ # The official nodejs docker image -FROM node:16.17-alpine +FROM node:20.16-bullseye ENV PYTHON /usr/bin/python # Copy package.json only to temp folder, install its dependencies, @@ -10,6 +10,7 @@ COPY .npmrc /tmp/.npmrc ADD package.json /tmp/package.json RUN set -ex \ && cd /tmp \ + && npm install -g node-gyp \ && yarn config set network-timeout 300000 \ && yarn install \ && rm -f /tmp/.npmrc \ @@ -17,6 +18,7 @@ RUN set -ex \ && cp -a /tmp/node_modules /home/app/ \ && rm -Rf /tmp/* + WORKDIR /home/app # Copy the rest of the files to the container workdir diff --git a/servers/frontend-server/babel.config.js b/servers/frontend-server/babel.config.js deleted file mode 100644 index c7513fd62..000000000 --- a/servers/frontend-server/babel.config.js +++ /dev/null @@ -1,48 +0,0 @@ -module.exports = (api) => { - const isTest = api.env('test'); - api.cache(true); - if (isTest) { - return { - compact: false, - presets: [ - '@babel/preset-typescript', - '@babel/preset-react', - ['@babel/preset-env', { modules: 'commonjs' }], - ], - plugins: [ - 'babel-plugin-dynamic-import-node', - '@babel/plugin-transform-destructuring', - '@babel/plugin-transform-modules-commonjs', - ['@babel/plugin-transform-for-of'], - '@babel/plugin-transform-regenerator', - '@babel/plugin-transform-runtime', - ['@babel/plugin-proposal-decorators', { legacy: true }], - '@babel/plugin-proposal-class-properties', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-optional-chaining', - ], - }; - } - return { - compact: false, - presets: ['@babel/preset-typescript', '@babel/preset-react', ['@babel/preset-env', { modules: false }]], - plugins: [ - '@babel/plugin-syntax-dynamic-import', - '@loadable/babel-plugin', - '@babel/plugin-transform-modules-commonjs', - '@babel/plugin-transform-destructuring', - ['@babel/plugin-transform-for-of'], - '@babel/plugin-transform-regenerator', - '@babel/plugin-transform-runtime', - ['@babel/plugin-proposal-decorators', { legacy: true }], - '@babel/plugin-proposal-class-properties', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-optional-chaining', - ], - env: { - production: { - compact: true, - }, - }, - }; -}; diff --git a/servers/frontend-server/build.config.js b/servers/frontend-server/build.config.js deleted file mode 100644 index 6f5c9ee57..000000000 --- a/servers/frontend-server/build.config.js +++ /dev/null @@ -1,13 +0,0 @@ -const config = { - ...require('../../build.config'), - __CLIENT__: true, - __SERVER__: false, - __DEV__: process.env.NODE_ENV !== 'production', - __TEST__: false, - __CDN_URL__: process.env.CDN_URL || '', - __GRAPHQL_URL__: process.env.GRAPHQL_URL || '/graphql', - __API_URL__: process.env.API_URL || '/graphql', - __FRONTEND_BUILD_DIR__: process.env.FRONTEND_BUILD_DIR || './dist/web', -}; - -module.exports = config; diff --git a/servers/frontend-server/codecept.json b/servers/frontend-server/codecept.json new file mode 100644 index 000000000..5b3b62c80 --- /dev/null +++ b/servers/frontend-server/codecept.json @@ -0,0 +1,28 @@ +{ + "output": "./e2e/output", + "helpers": { + "Puppeteer": { + "show": true, + "url": "http://localhost:3000" + } + }, + "include": { + "I": "./e2e/steps_file.js", + "loginPage": "./e2e/pages/Login.js", + "dashboardPage": "./e2e/pages/Dashboard.js", + "workspaceFormPage": "./e2e/pages/WorkspaceForm.js" + }, + "mocha": {}, + "bootstrap": false, + "teardown": null, + "hooks": [], + "gherkin": {}, + "plugins": { + "screenshotOnFail": { + "enabled": true + } + }, + "tests": "./e2e/**/*_test.js", + "timeout": 10000, + "name": "frontend-server" +} diff --git a/servers/frontend-server/config.json b/servers/frontend-server/config.json new file mode 100644 index 000000000..78bf08f1f --- /dev/null +++ b/servers/frontend-server/config.json @@ -0,0 +1,45 @@ +{ + "commonPaths": { + "appPath": "app", + "frontendStackPath": "frontend-stack-react" + }, + "copyOperations": [ + { + "packageName": "@common-stack/frontend-stack-react", + "destPath": "$.commonPaths.frontendStackPath", + "generateModule": true + } + ], + "i18n": { + "enabled": true, + "fallbackLng": "en", + "supportedLngs": ["en", "de", "es"], + "defaultNS": "common", + "react": { + "useSuspense": false + }, + "backend": { + "loadPath": "/cdm-locales/{{lng}}/{{ns}}.json", + "loadServerPath": "./public/cdm-locales/{{lng}}/{{ns}}.json" + }, + "packages": [] + }, + "modules": ["@sample-stack/counter-module-browser"], + "buildConfig": { + "__CLIENT__": false, + "__SERVER__": true, + "__DEV__": false, + "__TEST__": false, + "__CDN_URL__": "", + "__GRAPHQL_URL__": "http://localhost:8080/graphql", + "__DEBUGGING__": false, + "__SSR__": true, + "__API_URL__": "/graphql", + "__FRONTEND_BUILD_DIR__": "./dist/client", + "__WEB_DEV_SERVER_PORT__": 3000, + "__GRAPHQL_ENDPOINT__": "/graphql", + "__LOCAL_SERVER_HOST__": "localhost", + "__BACKEND_URL__": "http://localhost:3000" + }, + "uiFramework": "antui" +} diff --git a/servers/frontend-server/env.d.ts b/servers/frontend-server/env.d.ts new file mode 100644 index 000000000..2b08b07f7 --- /dev/null +++ b/servers/frontend-server/env.d.ts @@ -0,0 +1,9 @@ +/// +/// + +interface Window { + __ENV__: any, + __APOLLO_STATE__: any, + __PRELOADED_STATE__: any, + __SLOT_FILLS__: any, +} \ No newline at end of file diff --git a/servers/frontend-server/env.js b/servers/frontend-server/env.js new file mode 100644 index 000000000..5d86f0435 --- /dev/null +++ b/servers/frontend-server/env.js @@ -0,0 +1,5 @@ +/* eslint-disable jest/require-hook */ +import * as dotenv from 'dotenv-esm'; +if (process.env.ENV_FILE) { + dotenv.config({ path: process.env.ENV_FILE }); +} \ No newline at end of file diff --git a/servers/frontend-server/favicon.ico b/servers/frontend-server/favicon.ico new file mode 100644 index 000000000..d91e2d351 Binary files /dev/null and b/servers/frontend-server/favicon.ico differ diff --git a/servers/frontend-server/jest.config.js b/servers/frontend-server/jest.config.js new file mode 100644 index 000000000..dabe5f226 --- /dev/null +++ b/servers/frontend-server/jest.config.js @@ -0,0 +1,33 @@ +const base = require('../../jest.config.base'); +const packageJson = require('./package'); +const merge = require('merge') +const baseConfig = require('../../jest.config.base'); +const mongodbConfig = require('../../jest.config.mongodb'); + +const mergeData = merge.recursive( + baseConfig, + { + "transform": { + "\\.(js|jsx)?$": "../../transform.js", + }, + moduleNameMapper: { + '^__mocks__/(.*)$': '/../../__mocks__/$1', + // we'll use commonjs version of lodash for tests 👌 + // because we don't need to use any kind of tree shaking right?! + '^lodash-es$': '/../../node_modules/lodash/index.js', + '@adminide-stack\/core': '/../core/src/index.ts', + }, + roots: [ + // "src", + "loader" + ], + }, + mongodbConfig, + { + globals: { + + } + } +); + +module.exports = mergeData; \ No newline at end of file diff --git a/servers/frontend-server/package.json b/servers/frontend-server/package.json index 724822096..66177f294 100755 --- a/servers/frontend-server/package.json +++ b/servers/frontend-server/package.json @@ -1,119 +1,105 @@ { - "name": "sample-stack-frontend-server", - "version": "0.0.1", - "private": true, - "description": "Sample Client server", - "homepage": "https://github.com/cdmbase/fullstack-pro#readme", - "bugs": { - "url": "https://github.com/cdmbase/fullstack-pro/issues" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/cdmbase/fullstack-pro.git" - }, - "license": "MIT", - "author": "CDMBase LLC", - "main": "index.js", - "scripts": { - "prebuild": "yarn build:clean", - "build": "cross-env NODE_ENV=production webpack", - "build:clean": "rimraf dist .awcache", - "build:debug": "cross-env DEBUGGING=true NODE_ENV=production webpack", - "build:debug:verbose": "yarn build:debug -- -v", - "build:dev": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env webpack", - "build:stats": "cross-env BUNDLE_STATS=true yarn build:debug", - "docker:build": "cross-env NODE_OPTIONS='--max_old_space_size=4096' yarn build && docker build . -t $npm_package_name:$npm_package_version", - "docker:build:debug": "yarn build:debug && docker build . -t $npm_package_name:$npm_package_version", - "docker:run": "docker run --env-file ../../config/staging/docker-staging.env -p 3010:3010 -it $npm_package_name:$npm_package_version", - "jest": "./node_modules/.bin/jest", - "start": "cross-env NODE_ENV=production pm2-runtime dist/index.js", - "start:dev": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env node --harmony dist", - "start:staging": "cross-env NODE_ENV=staging ENV_FILE=../../config/staging/staging.env node --harmony dist", - "start:test": "cross-env NODE_ENV=test ENV_FILE=../../config/test/test.env node --harmony dist", - "test": "jest", - "test:debug": "npm test -- --runInBand", - "test:watch": "npm test -- --watch", - "watch": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env webpack-dev-server", - "watch:debug": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env yarn webpack-dev-server -- -v", - "watch:ssr": "cross-env SSR=true && yarn watch", - "watch:staging": "cross-env ENV_FILE=../../config/staging/staging.env yarn webpack-dev-server", - "watch:test": "cross-env ENV_FILE=../../config/test/test.env yarn webpack-dev-server" - }, - "dependencies": { - "@apollo/client": "~3.7.1", - "@apollo/react-components": "^4.0.0", - "@apollo/react-hoc": "^4.0.0", - "@cdm-logger/client": "^7.0.12", - "@cdm-logger/server": "^7.0.12", - "@common-stack/client-core": "0.5.1", - "@common-stack/client-react": "0.5.6", - "@common-stack/core": "0.5.1", - "@common-stack/server-core": "0.5.1", - "@emotion/css": "^11.10.0", - "@emotion/react": "^11.10.4", - "@emotion/server": "^11.10.0", - "@emotion/styled": "^11.10.4", - "@sample-stack/assets": "0.0.1", - "@sample-stack/core": "0.0.1", - "@sample-stack/counter-module-browser": "0.0.1", - "@sample-stack/platform-browser": "0.0.1", - "@loadable/component": "^5.15.2", - "@loadable/server": "^5.15.2", - "apollo-link-debounce": "^3.0.0", - "apollo-link-logger": "^2.0.0", - "apollo-server-errors": "^3.3.1", - "antd": "~5.1.7", - "classnames": "^2.2.6", - "compression": "^1.7.4", - "connected-react-router": "^6.9.1", - "cors": "^2.8.5", - "dotenv": "^8.2.0", - "envalid": "~7.2.2", - "error-stack-parser": "^2.0.4", - "esm": "^3.2.25", - "express": "^4.17.1", - "graphql": "^15.0.0", - "graphql-tag": "^2.11.0", - "graphql-ws": "^5.11.2", - "history": "^4.10.1", - "immutability-helper": "^3.0.1", - "inversify": "^5.0.1", - "isomorphic-fetch": "^2.2.1", - "js-cookie": "^2.2.1", - "lodash": "^4.17.15", - "ramda": "^0.26.1", - "react": "18.0.0", - "react-dom": "18.0.0", - "react-helmet": "^6.1.0", - "react-loadable": "^5.5.0", - "react-redux": "^7.1.3", - "react-router": "^5.3.3", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.3", - "react-transition-group": "^4.3.0", - "redux": "^4.0.5", - "redux-logger": "^3.0.6", - "redux-observable": "^1.2.0", - "redux-persist": "^6.0.0", - "redux-thunk": "^2.3.0", - "reflect-metadata": "^0.1.13", - "reselect": "^4.0.0", - "rxjs": "^6.5.3", - "rxjs-compat": "^6.5.3", - "rxjs-hooks": "^0.5.2", - "serialize-javascript": "^4.0.0", - "sourcemapped-stacktrace": "^1.1.11", - "ts-invariant": "^0.10.3", - "universal-cookie-express": "^4.0.1" - }, - "devDependencies": { - "@babel/polyfill": "7.12.1", - "cross-env": "^7.0.3", - "pm2": "^5.2.2", - "raf": "3.4.1", - "rimraf": "^3.0.2" - }, - "peerDependencies": { - "body-parser": "*" - } -} \ No newline at end of file + "name": "sample-stack-frontend-server", + "version": "0.0.1", + "private": true, + "description": "Sample Client server", + "homepage": "https://github.com/cdmbase/fullstack-pro#readme", + "bugs": { + "url": "https://github.com/cdmbase/fullstack-pro/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/cdmbase/fullstack-pro.git" + }, + "license": "MIT", + "author": "CDMBase LLC", + "type": "module", + "main": "index.js", + "scripts": { + "prebuild": "yarn build:clean && yarn genconfig", + "build": "cross-env SSR=true NODE_OPTIONS='--max_old_space_size=4096' NODE_ENV=production remix vite:build", + "build:SSR": "cross-env SSR=true NODE_ENV=production remix vite:build", + "build:clean": "rimraf dist build node_modules/.vite", + "build:debug": "cross-env DEBUGGING=true NODE_ENV=production remix vite:build", + "build:debug:verbose": "yarn build:debug -- -v", + "build:dev": "cross-env ENV_FILE=../../config/development/dev.env yarn build", + "build:devSSR": "cross-env SSR=true NODE_ENV=development ENV_FILE=../../config/development/dev.env yarn build", + "build:stats": "cross-env BUNDLE_STATS=true yarn build:debug", + "dev:vite:ssr": "cross-env SSR=true NODE_ENV=development ENV_FILE=../../config/development/dev.env node ./server.js", + "docker:build": "cross-env yarn build && docker build . -t $npm_package_name:$npm_package_version", + "docker:build:debug": "yarn build:debug && docker build . -t $npm_package_name:$npm_package_version", + "docker:run": "docker run --env-file ../../config/staging/docker-staging.env -p 3000:3000 -it $npm_package_name:$npm_package_version", + "genconfig": "node ./tools/mergeConfig.js", + "jest": "./node_modules/.bin/jest", + "preview": "cross-env SSR=true NODE_ENV=development ENV_FILE=../../config/development/dev.env node ./server.js", + "routes": "npx @remix-run/dev routes --json", + "prestart": "yarn genconfig", + "start": "cross-env NODE_ENV=production node ./server.js", + "start:SSR": "yarn build:SSR && yarn start", + "start:dev": "cross-env NODE_ENV=production ENV_FILE=../../config/development/dev.env node ./server.js", + "start:devSSR": "yarn build:devSSR && yarn start:dev", + "start:staging": "cross-env NODE_ENV=production ENV_FILE=../../config/staging/staging.env node ./server.js", + "start:test": "cross-env NODE_ENV=test ENV_FILE=../../config/test/test.env node ./server.js", + "start:vite:ssr": "cross-env SSR=true NODE_ENV=development ENV_FILE=../../config/development/dev.env yarn build && yarn preview", + "test": "jest", + "test:debug": "npm test -- --runInBand", + "test:watch": "npm test -- --watch", + "prewatch": "yarn genconfig", + "watch": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env node ./server.js", + "watch:debug": "cross-env NODE_ENV=development ENV_FILE=../../config/development/dev.env node ./server.js", + "watch:ssr": "cross-env SSR=true yarn watch", + "watch:staging": "cross-env ENV_FILE=../../config/staging/staging.env node ./server.js", + "watch:test": "cross-env ENV_FILE=../../config/test/test.env node ./server.js" + }, + "dependencies": { + "@emotion/react": "^11.10.4", + "@emotion/server": "^11.10.0", + "@files-stack/core": "3.0.2", + "@react-icons/all-files": "^4.1.0", + "@sample-stack/counter-module-browser": "link:../../packages-modules/counter/browser", + "antd": "^5.14.0", + "classnames": "^2.2.6", + "compression": "^1.7.4", + "glob-all": "^3.3.1", + "immutability-helper": "^3.0.1", + "is-plain-obj": "^3.0.0", + "isomorphic-fetch": "^2.2.1", + "lodash": "^4.17.15", + "lodash-es": "^4.17.21", + "moment": "2.29.1", + "ramda": "^0.29.1", + "react-ga4": "^2.1.0", + "react-helmet": "^6.1.0", + "react-helmet-async": "^1.3.0", + "react-icons": "~4.3.1", + "react-icons-converter": "^1.1.4", + "react-transition-group": "^4.3.0", + "react-use": "^17.2.4", + "reselect": "^4.0.0", + "rxjs": "^6.5.3", + "rxjs-compat": "^6.5.3", + "rxjs-hooks": "^0.5.2", + "vite-env-only": "^2.2.1" + }, + "devDependencies": { + "@cdmbase/vite-plugin-i18next-loader": "^2.0.12", + "@common-stack/frontend-stack-react": "6.0.6-alpha.0", + "@common-stack/rollup-vite-utils": "6.0.6-alpha.0", + "@remix-run/dev": "^2.8.1", + "@remix-run/serve": "^2.8.1", + "cross-env": "^7.0.3", + "dotenv-esm": "^16.0.3-4", + "pm2": "^5.2.2", + "raf": "3.4.1", + "rimraf": "^3.0.2", + "tsx": "^4.7.0", + "typescript": "^5.4.5", + "uuid": "^9.0.1", + "vite": "^5.1.0", + "vite-plugin-cjs-interop": "^2.1.1", + "vite-tsconfig-paths": "^4.2.1" + }, + "peerDependencies": { + "body-parser": "*" + } +} diff --git a/servers/frontend-server/postcss.config.js b/servers/frontend-server/postcss.config.js new file mode 100644 index 000000000..49c0612d5 --- /dev/null +++ b/servers/frontend-server/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/servers/frontend-server/public/favicon.ico b/servers/frontend-server/public/favicon.ico new file mode 100644 index 000000000..d91e2d351 Binary files /dev/null and b/servers/frontend-server/public/favicon.ico differ diff --git a/servers/frontend-server/server.js b/servers/frontend-server/server.js new file mode 100644 index 000000000..f6224d626 --- /dev/null +++ b/servers/frontend-server/server.js @@ -0,0 +1,78 @@ +import express from 'express'; +import compression from 'compression'; +import { createRequestHandler } from '@remix-run/express'; +import { installGlobals } from '@remix-run/node'; +import './env.js'; +import { + performCopyOperations, +} from '@common-stack/rollup-vite-utils/lib/preStartup/configLoader/configLoader.js'; +import config from './app/cde-webconfig.json' assert { type: 'json' }; + +installGlobals(); + +Object.keys(config.buildConfig).forEach((key) => { + global[key] = config.buildConfig[key]; +}); + +const startServer = async () => { + await performCopyOperations(config); + + const { corsMiddleware } = await import(`./${config.commonPaths.appPath}/${config.commonPaths.frontendStackPath}/backend/middlewares/cors.js`); + const { containerMiddleware } = await import(`./${config.commonPaths.appPath}/${config.commonPaths.frontendStackPath}/backend/middlewares/container.js`); + const { loadContext } = await import(`./${config.commonPaths.appPath}/${config.commonPaths.frontendStackPath}/load-context.server.js`); + + const viteDevServer = + process.env.NODE_ENV === 'production' + ? undefined + : await import('vite').then((vite) => { + return vite.createServer({ + server: { middlewareMode: true }, + }); + }); + + const remixHandler = createRequestHandler({ + getLoadContext: loadContext, + build: viteDevServer + ? () => viteDevServer.ssrLoadModule('virtual:remix/server-build') + : await import('./build/server/index.js'), + }); + + const app = express(); + + app.use(compression()); + app.disable('x-powered-by'); + + if (viteDevServer) { + app.use(viteDevServer.middlewares); + } else { + app.use('/assets', express.static('build/client/assets', { immutable: true, maxAge: '1y' })); + } + + app.use(express.static('build/client', { maxAge: '1h' })); + + app.use(corsMiddleware); + app.options('*', corsMiddleware); + + app.use(async (req, res, next) => { + let isAssetRequest = (url) => + /\.[jt]sx?$/.test(url) || + /@id\/__x00__virtual:/.test(url) || + /@vite\/client/.test(url) || + /node_modules\/vite\/dist\/client\/env/.test(url); + + if (isAssetRequest(req.url)) { + next(); + } else { + return await containerMiddleware(req, res, async () => { + return remixHandler(req, res, next); + }); + } + }); + + const port = process.env.PORT || 3000; + app.listen(port, () => console.log(`Express server listening at http://localhost:${port}`)); +}; + +startServer().catch((err) => { + console.error('Failed to start server:', err); +}); diff --git a/servers/frontend-server/src/app/500.tsx b/servers/frontend-server/src/app/500.tsx deleted file mode 100644 index c6d64b45e..000000000 --- a/servers/frontend-server/src/app/500.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import { Result, Button } from 'antd'; - -export const Error500 = ({ error }: any) => { - React.useEffect(() => { - console.trace(error); - }, [error]); - - return ( - Back Home} - /> - ); -} diff --git a/servers/frontend-server/src/app/ErrorBoundary.tsx b/servers/frontend-server/src/app/ErrorBoundary.tsx deleted file mode 100644 index 0439bd326..000000000 --- a/servers/frontend-server/src/app/ErrorBoundary.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import { Error500 } from './500'; -import { ServerError } from './ServerError'; - -type IErrorBoundryState = { error: any, type: string } - - -export class ErrorBoundary extends React.Component { - constructor(props) { - super(props); - const serverError: any = __CLIENT__ ? window.__SERVER_ERROR__ : null; - if (serverError) { - this.state = { error: new ServerError(serverError), type: 'serverError' }; - } else { - this.state = { error: undefined, type: undefined }; - } - } - - componentDidCatch(error) { - let type = undefined; - - if (process.env.NODE_ENV === 'production') { - type = '404' - } else { - type = '500' - } - // Update state so the next render will show the fallback UI. - this.setState({ error, type }); - } - - - render() { - const { error, type } = this.state; - if (error) { - return - } - return this.props.children; - } -} \ No newline at end of file diff --git a/servers/frontend-server/src/app/Main.tsx b/servers/frontend-server/src/app/Main.tsx deleted file mode 100755 index 28a9fdd9e..000000000 --- a/servers/frontend-server/src/app/Main.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/// -import * as React from 'react'; -import { ApolloProvider } from '@apollo/client'; -import { Provider } from 'react-redux'; -import { ConnectedRouter } from 'connected-react-router'; -import { PersistGate } from 'redux-persist/integration/react'; -import { persistStore } from 'redux-persist'; -import { createBrowserHistory } from 'history'; - -import { createReduxStore } from '../config/redux-config'; -import { createClientContainer } from '../config/client.service'; -import modules, { MainRoute } from '../modules'; -import { ErrorBoundary } from './ErrorBoundary'; - -const { apolloClient: client } = createClientContainer(); - -const history = createBrowserHistory(); -const { store } = createReduxStore(history); - -export class Main extends React.Component<{}, {}> { - public render() { - let persistor = persistStore(store); - return ( - - - - - {modules.getWrappedRoot( - ( - - - - ), - )} - - - - - ); - } -} - -export default Main; diff --git a/servers/frontend-server/src/app/ServerError.tsx b/servers/frontend-server/src/app/ServerError.tsx deleted file mode 100644 index 2cd2c8721..000000000 --- a/servers/frontend-server/src/app/ServerError.tsx +++ /dev/null @@ -1,12 +0,0 @@ - -class ServerError extends Error { - constructor(error: any) { - super(); - for (const key of Object.getOwnPropertyNames(error)) { - this[key] = error[key]; - } - this.name = 'ServerError'; - } -} - -export { ServerError }; \ No newline at end of file diff --git a/servers/frontend-server/src/backend/app.ts b/servers/frontend-server/src/backend/app.ts deleted file mode 100755 index 36ceb45f2..000000000 --- a/servers/frontend-server/src/backend/app.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* eslint-disable import/first */ -/* eslint-disable no-unused-expressions */ -/* eslint-disable global-require */ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable jest/require-hook */ -// tslint:disable-next-line:no-unused-expression -process.env.ENV_FILE !== null && require('dotenv').config({ path: process.env.ENV_FILE }); - -import { logger } from '@cdm-logger/server'; - -import './server'; - -process.on('uncaughtException', (ex: Error) => { - logger.error(ex, 'uncaughtException'); - process.exit(1); -}); - -process.on('unhandledRejection', (reason: Error) => { - logger.error(reason, 'unhandledRejection'); -}); - -if ((module as any).hot) { - (module as any).hot.status((event) => { - if (event === 'abort' || event === 'fail') { - logger.error(`HMR error status: ${event}`); - // Signal webpack.run.js to do full-reload of the back-end - process.exit(250); - } - }); - - (module as any).hot.accept(); -} diff --git a/servers/frontend-server/src/backend/middlewares/cors.ts b/servers/frontend-server/src/backend/middlewares/cors.ts deleted file mode 100755 index 823cbf953..000000000 --- a/servers/frontend-server/src/backend/middlewares/cors.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* eslint-disable jest/require-hook */ -import cors from 'cors'; -import { logger } from '@common-stack/client-core'; -import { config } from '../../config'; - -const { CLIENT_URL, BACKEND_URL } = config; - -const corsWhitelist = [CLIENT_URL, BACKEND_URL]; -logger.info('corsWhitelist (%j)', corsWhitelist); - -const corsOptions: cors.CorsOptions = { - origin: (origin, callback) => { - if (corsWhitelist.indexOf(origin) !== -1) { - callback(null, true); - } else { - // TODO: only throw when in debug mode - logger.error('url (%s) is not in the whitelist', origin); - // callback(new Error('Not allowed by CORS')) - logger.warn('allowing all origins temporarily, you need to disable it.'); - callback(null, true); - } - }, - credentials: false, -}; - -export const corsMiddleware = cors(corsOptions); diff --git a/servers/frontend-server/src/backend/middlewares/error.ts b/servers/frontend-server/src/backend/middlewares/error.ts deleted file mode 100755 index 1e4beafa1..000000000 --- a/servers/frontend-server/src/backend/middlewares/error.ts +++ /dev/null @@ -1,55 +0,0 @@ -/// - -import * as path from 'path'; -import * as fs from 'fs'; -import * as url from 'url'; -import { logger } from '@cdm-logger/server'; - - -let assetMap; - -const stripCircular = (from, seen?: any) => { - const to = Array.isArray(from) ? [] : {}; - seen = seen || []; - seen.push(from); - Object.getOwnPropertyNames(from).forEach(key => { - if (!from[key] || (typeof from[key] !== 'object' && !Array.isArray(from[key]))) { - to[key] = from[key]; - } else if (seen.indexOf(from[key]) < 0) { - to[key] = stripCircular(from[key], seen.slice(0)); - } else { to[key] = '[Circular]'; } - }); - return to; -}; - -const { pathname } = url.parse(__BACKEND_URL__); - -export const errorMiddleware = - (e, req, res, next) => { - if (req.path === pathname) { - const stack = e.stack.toString().replace(/[\n]/g, '\\n'); - res.status(200).send(`[{"data": {}, "errors":[{"message": "${stack}"}]}]`); - } else { - logger.error(e); - - if (__DEV__ || !assetMap) { - assetMap = JSON.parse(fs.readFileSync(path.join(__FRONTEND_BUILD_DIR__, 'assets.json')).toString()); - } - - const serverErrorScript = ``; - const vendorScript = assetMap['vendor.js'] - ? `` - : ''; - - res.status(200).send( - `${serverErrorScript}
- ${vendorScript} - - `, - ); - } - }; - - diff --git a/servers/frontend-server/src/backend/modules/index.ts b/servers/frontend-server/src/backend/modules/index.ts deleted file mode 100755 index c5c3222be..000000000 --- a/servers/frontend-server/src/backend/modules/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import modules from './modules'; -export default modules; diff --git a/servers/frontend-server/src/backend/modules/modules.ts b/servers/frontend-server/src/backend/modules/modules.ts deleted file mode 100755 index d38cf419d..000000000 --- a/servers/frontend-server/src/backend/modules/modules.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { Feature } from '@common-stack/server-core'; - -export default new Feature({}); - diff --git a/servers/frontend-server/src/backend/server.ts b/servers/frontend-server/src/backend/server.ts deleted file mode 100755 index 6319b35b3..000000000 --- a/servers/frontend-server/src/backend/server.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable jest/require-hook */ -import 'reflect-metadata'; -import express from 'express'; -import compression from 'compression'; -import * as bodyParser from 'body-parser'; -import * as http from 'http'; -import * as path from 'path'; -import * as url from 'url'; -import 'isomorphic-fetch'; -import { logger } from '@cdm-logger/server'; -import { websiteMiddleware } from './website'; -import { corsMiddleware } from './middlewares/cors'; -import { errorMiddleware } from './middlewares/error'; -import { config } from '../config'; -import modules from './modules'; - -const cookiesMiddleware = require('universal-cookie-express'); - -let server; - -const app = express(); - -app.use(corsMiddleware); -app.options('*', corsMiddleware); - -for (const applyBeforeware of modules.beforewares) { - applyBeforeware(app); -} - -app.use(cookiesMiddleware()); - -// By default it uses backend_url port, which may conflict with graphql server. -const { port: serverPort } = url.parse(config.LOCAL_BACKEND_URL); - -// Don't rate limit heroku -app.enable('trust proxy'); -if (!__DEV__) { - app.use(compression()); -} - -app.use(bodyParser.urlencoded({ extended: true })); -app.use(bodyParser.json()); - -app.use( - '/', - express.static(path.join(__FRONTEND_BUILD_DIR__), { - maxAge: '180 days', - }), -); - -if (__DEV__) { - app.use('/', express.static(__DLL_BUILD_DIR__, { maxAge: '180 days' })); -} - -app.use(websiteMiddleware); - -if (__DEV__) { - app.use(errorMiddleware); -} - -server = http.createServer(app); - -server.listen(serverPort, () => { - logger.info(`Client Server is now running on port ${serverPort}`); -}); - -server.on('close', () => { - server = undefined; -}); - -if ((module as any).hot) { - (module as any).hot.dispose(() => { - try { - if (server) { - server.close(); - } - } catch (error) { - logger.error(error.stack); - } - }); - (module as any).hot.accept(['./website'], () => { - logger.debug('...reloading middleware'); - }); - - (module as any).hot.accept(); -} - -export default server; diff --git a/servers/frontend-server/src/backend/ssr/html.tsx b/servers/frontend-server/src/backend/ssr/html.tsx deleted file mode 100755 index 5c68a0159..000000000 --- a/servers/frontend-server/src/backend/ssr/html.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/// - -import * as React from 'react'; -import serialize from 'serialize-javascript'; -import { HelmetData } from 'react-helmet'; - -/** - * A simple herlper function to prepare the HTML markup. This loads: - * - Page title - * - SEO meta tags - * - Preloaded state (for Redux, Apollo, additional Environment variables) depending on the current route - * - Code-split script tags depending on the current route - * @param param0 - */ -const Html = ({ - content, - state, - reduxState, - headElements, - env, - assetMap, - styleSheet, - helmet, - stylesInserts = [], - scriptsInserts = [], -}: { - content?: any; - state: any; - reduxState: any; - headElements: React.ReactElement[]; - assetMap?: string[]; - env: any; - styleSheet?: any; - helmet?: HelmetData; - stylesInserts?: any[]; - scriptsInserts?: string[]; -}) => { - const htmlAttrs = helmet.htmlAttributes.toComponent(); // react-helmet html document tags - const bodyAttrs = helmet.bodyAttributes.toComponent(); // react-helmet body document tags - return ( - - - {helmet.title.toComponent()} - {helmet.meta.toComponent()} - {helmet.link.toComponent()} - {helmet.style.toComponent()} - {helmet.script.toComponent()} - {helmet.noscript.toComponent()} - {assetMap['vendor.js'] && + + + +