зеркало из
1
0
Форкнуть 0
* moving files to TypeScript

* moving to ES import/export

* adding midding types

* more work to port to TypeScript

* fixing environment vars issue

* adding sourcemaps

* making the auth functions TypeScript too

* adding vscode build and launch tasks

* fixing some typescript compile issues

* overhaul of tests
- added ts-jest and a jest config to handle it
- removed unionfs (license is not a good license)
- removed memfs (not needed)
- using mock-fs to mock the fs easily
- forced time lies about types to mock shell.exit properly:

* chore: refactor TS migration

Closes #22

* removing unneeded file

* including json files in TS output

* making all functions async, to force a Promise return

* adding a watch command

* allowing SSL backends with dev certs

* running everything from the right path

* removing directive

* including custom env settings

* moving 404 page to a non-compile location

Co-authored-by: Aaron Powell <me@aaron-powell.com>
This commit is contained in:
Wassim Chegham 2020-11-03 23:24:30 +01:00 коммит произвёл GitHub
Родитель 398bc596db
Коммит dc469f6975
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
47 изменённых файлов: 731 добавлений и 556 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -41,3 +41,5 @@ venv.bak/
__pycache__/
*.py[cod]
*$py.class
dist

19
.vscode/launch.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,19 @@
{
"configurations": [
{
"command": "npm start",
"name": "Run npm start",
"request": "launch",
"type": "node-terminal"
},
{
"type": "node-terminal",
"request": "launch",
"name": "Jest Tests",
"command": "node ${workspaceRoot}/node_modules/jest/bin/jest.js -i",
// "args": ["-i"],
"preLaunchTask": "build",
"internalConsoleOptions": "openOnSessionStart"
}
]
}

16
.vscode/tasks.json поставляемый Normal file
Просмотреть файл

@ -0,0 +1,16 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "build",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": ["$tsc"],
"label": "build",
"detail": "tsc"
}
]
}

21
environment.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
StaticWebAppsAuthCookie?: string;
StaticWebAppsAuthContextCookie: string;
AppServiceAuthSession: string;
DEBUG: string;
GITHUB_CLIENT_ID: string;
GITHUB_CLIENT_SECRET: string;
SWA_EMU_AUTH_URI: string;
SWA_EMU_API_URI: string;
SWA_EMU_API_PREFIX: string;
SWA_EMU_APP_URI: string;
SWA_EMU_APP_LOCATION: string;
SWA_EMU_HOST: string;
SWA_EMU_PORT: string;
}
}
}
export {};

4
jest.config.js Normal file
Просмотреть файл

@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

Просмотреть файл

313
package-lock.json сгенерированный
Просмотреть файл

@ -4,6 +4,12 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@azure/functions": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@azure/functions/-/functions-1.2.2.tgz",
"integrity": "sha512-p/dDHq1sG/iAib+eDY4NxskWHoHW1WFzD85s0SfWxc2wVjJbxB0xz/zBF4s7ymjVgTu+0ceipeBk+tmpnt98oA==",
"dev": true
},
"@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
@ -607,6 +613,12 @@
}
}
},
"@jest/create-cache-key-function": {
"version": "26.5.0",
"resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-26.5.0.tgz",
"integrity": "sha512-DJ+pEBUIqarrbv1W/C39f9YH0rJ4wsXZ/VC6JafJPlHW2HOucKceeaqTOQj9MEDQZjySxMLkOq5mfXZXNZcmWw==",
"dev": true
},
"@jest/environment": {
"version": "26.3.0",
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.3.0.tgz",
@ -1136,6 +1148,15 @@
"@babel/types": "^7.3.0"
}
},
"@types/blessed": {
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/@types/blessed/-/blessed-0.1.17.tgz",
"integrity": "sha512-BKvUtnrXksNdK0fOYV/9HJGkjCcAvOGMSCJsiHaBFyBeyqHwy2OHK32r5XNI+q0eXuAuGqtPOnDetHnbZoYqag==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/cacheable-request": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
@ -1154,6 +1175,22 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
"@types/cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==",
"dev": true
},
"@types/glob": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
"dev": true,
"requires": {
"@types/minimatch": "*",
"@types/node": "*"
}
},
"@types/graceful-fs": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz",
@ -1169,6 +1206,15 @@
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==",
"dev": true
},
"@types/http-proxy": {
"version": "1.17.4",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.4.tgz",
"integrity": "sha512-IrSHl2u6AWXduUaDLqYpt45tLVCtYv7o4Z0s1KghBCDgIIS9oW5K1H8mZG/A2CfeLdEa7rTd1ACOiHBc1EMT2Q==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz",
@ -1194,129 +1240,22 @@
}
},
"@types/jest": {
"version": "26.0.14",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.14.tgz",
"integrity": "sha512-Hz5q8Vu0D288x3iWXePSn53W7hAjP0H7EQ6QvDO9c7t46mR0lNOLlfuwQ+JkVxuhygHzlzPX+0jKdA3ZgSh+Vg==",
"version": "26.0.15",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.15.tgz",
"integrity": "sha512-s2VMReFXRg9XXxV+CW9e5Nz8fH2K1aEhwgjUqPPbQd7g95T0laAcvLv032EhFHIa5GHsZ8W7iJEQVaJq6k3Gog==",
"dev": true,
"requires": {
"jest-diff": "^25.2.1",
"pretty-format": "^25.2.1"
},
"dependencies": {
"@jest/types": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz",
"integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^1.1.1",
"@types/yargs": "^15.0.0",
"chalk": "^3.0.0"
}
},
"@types/istanbul-reports": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz",
"integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==",
"dev": true,
"requires": {
"@types/istanbul-lib-coverage": "*",
"@types/istanbul-lib-report": "*"
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"dev": true
},
"ansi-styles": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
"dev": true,
"requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"diff-sequences": {
"version": "25.2.6",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz",
"integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==",
"dev": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"jest-diff": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz",
"integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==",
"dev": true,
"requires": {
"chalk": "^3.0.0",
"diff-sequences": "^25.2.6",
"jest-get-type": "^25.2.6",
"pretty-format": "^25.5.0"
}
},
"jest-get-type": {
"version": "25.2.6",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz",
"integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==",
"dev": true
},
"pretty-format": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz",
"integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==",
"dev": true,
"requires": {
"@jest/types": "^25.5.0",
"ansi-regex": "^5.0.0",
"ansi-styles": "^4.0.0",
"react-is": "^16.12.0"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
"jest-diff": "^26.0.0",
"pretty-format": "^26.0.0"
}
},
"@types/jsonwebtoken": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz",
"integrity": "sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/keyv": {
@ -1328,18 +1267,43 @@
"@types/node": "*"
}
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
"dev": true
},
"@types/minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=",
"dev": true
},
"@types/mock-fs": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/@types/mock-fs/-/mock-fs-4.13.0.tgz",
"integrity": "sha512-FUqxhURwqFtFBCuUj3uQMp7rPSQs//b3O9XecAVxhqS9y4/W8SIJEZFq2mmpnFVZBXwR/2OyPLE97CpyYiB8Mw==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "14.0.27",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.27.tgz",
"integrity": "sha512-kVrqXhbclHNHGu9ztnAwSncIgJv/FaxmzXJvGXNdcCpV1b8u1/Mi6z6m0vwy0LzKeXFTPLH0NzwmoJ3fNCIq0g==",
"dev": true
},
"@types/node-fetch": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz",
"integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==",
"dev": true,
"requires": {
"@types/node": "*",
"form-data": "^3.0.0"
}
},
"@types/normalize-package-data": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@ -1367,6 +1331,16 @@
"@types/node": "*"
}
},
"@types/shelljs": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.8.tgz",
"integrity": "sha512-lD3LWdg6j8r0VRBFahJVaxoW0SIcswxKaFUrmKl33RJVeeoNYQAz4uqCJ5Z6v4oIBOsC5GozX+I5SorIKiTcQA==",
"dev": true,
"requires": {
"@types/glob": "*",
"@types/node": "*"
}
},
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@ -2287,6 +2261,15 @@
"integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
"dev": true
},
"bs-logger": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
"integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
"dev": true,
"requires": {
"fast-json-stable-stringify": "2.x"
}
},
"bser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
@ -3824,12 +3807,6 @@
"map-cache": "^0.2.2"
}
},
"fs-monkey": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz",
"integrity": "sha512-fcSa+wyTqZa46iWweI7/ZiUfegOZl0SG8+dltIwFXo7+zYU9J9kpS3NB6pZcSlJdhvIwp81Adx2XhZorncxiaA==",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@ -6912,6 +6889,12 @@
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=",
"dev": true
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
@ -6990,6 +6973,12 @@
}
}
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"makeerror": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz",
@ -7020,15 +7009,6 @@
"object-visit": "^1.0.0"
}
},
"memfs": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.2.0.tgz",
"integrity": "sha512-f/xxz2TpdKv6uDn6GtHee8ivFyxwxmPuXatBb1FBwxYNuVpbM3k/Y1Z+vC0mH/dIXXrukYfe3qe5J32Dfjg93A==",
"dev": true,
"requires": {
"fs-monkey": "1.0.1"
}
},
"meow": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/meow/-/meow-7.0.1.tgz",
@ -7297,6 +7277,12 @@
"minimist": "^1.2.5"
}
},
"mock-fs": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/mock-fs/-/mock-fs-4.13.0.tgz",
"integrity": "sha512-DD0vOdofJdoaRNtnWcrXe6RQbpHkPPmtqGq14uRX0F8ZKJ5nv89CVTYl/BZdppDxBDaV0hl75htg3abpEWlPZA==",
"dev": true
},
"modify-values": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz",
@ -10005,6 +9991,46 @@
"integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=",
"dev": true
},
"ts-jest": {
"version": "26.4.3",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.3.tgz",
"integrity": "sha512-pFDkOKFGY+nL9v5pkhm+BIFpoAuno96ff7GMnIYr/3L6slFOS365SI0fGEVYx2RKGji5M2elxhWjDMPVcOCdSw==",
"dev": true,
"requires": {
"@jest/create-cache-key-function": "^26.5.0",
"@types/jest": "26.x",
"bs-logger": "0.x",
"buffer-from": "1.x",
"fast-json-stable-stringify": "2.x",
"jest-util": "^26.1.0",
"json5": "2.x",
"lodash.memoize": "4.x",
"make-error": "1.x",
"mkdirp": "1.x",
"semver": "7.x",
"yargs-parser": "20.x"
},
"dependencies": {
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
},
"yargs-parser": {
"version": "20.2.3",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.3.tgz",
"integrity": "sha512-emOFRT9WVHw03QSvN5qor9QQT9+sw5vwxfYweivSMHTcAXPefwVae2FjO7JJjj8hCE4CzPOPeFM83VwT29HCww==",
"dev": true
}
}
},
"tslib": {
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz",
@ -10061,6 +10087,12 @@
"is-typedarray": "^1.0.0"
}
},
"typescript": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz",
"integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==",
"dev": true
},
"uglify-js": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.1.tgz",
@ -10088,15 +10120,6 @@
"set-value": "^2.0.1"
}
},
"unionfs": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/unionfs/-/unionfs-4.4.0.tgz",
"integrity": "sha512-N+TuJHJ3PjmzIRCE1d2N3VN4qg/P78eh/nxzwHnzpg3W2Mvf8Wvi7J1mvv6eNkb8neUeSdFSQsKna0eXVyF4+w==",
"dev": true,
"requires": {
"fs-monkey": "^1.0.0"
}
},
"unique-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",

Просмотреть файл

@ -4,11 +4,14 @@
"description": "Azure Static Web Apps Emulator for Auth, API and static content",
"scripts": {
"release": "release-it --preRelease=alpha",
"test": "jest"
"test": "jest",
"build": "tsc",
"prebuild": "rm -fr dist",
"watch": "tsc --watch"
},
"bin": {
"swa": "./bin/index.js",
"swa-emu": "./bin/index.js"
"swa": "./dist/cli.js",
"swa-emu": "./dist/cli.js"
},
"author": "Wassim Chegham <github@wassim.dev>",
"dependencies": {
@ -25,13 +28,22 @@
"yaml": "^1.10.0"
},
"devDependencies": {
"@azure/functions": "^1.2.2",
"@release-it/conventional-changelog": "^1.1.4",
"@types/jest": "^26.0.14",
"@types/blessed": "^0.1.17",
"@types/cookie": "^0.4.0",
"@types/http-proxy": "^1.17.4",
"@types/jest": "^26.0.15",
"@types/jsonwebtoken": "^8.5.0",
"@types/mock-fs": "^4.13.0",
"@types/node-fetch": "^2.5.7",
"@types/shelljs": "^0.8.8",
"jest": "^26.4.2",
"memfs": "^3.2.0",
"mock-fs": "^4.13.0",
"release-it": "^13.6.4",
"supertest": "^4.0.2",
"unionfs": "^4.4.0"
"ts-jest": "^26.4.3",
"typescript": "^4.0.3"
},
"homepage": "https://github.com/manekinekko/swa-emulator#readme",
"private": false,

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,8 +1,8 @@
const { response } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const SWA_EMU_HOST = "http://localhost:" + process.env.SWA_EMU_PORT;
module.exports = async function (context, req) {
const { provider } = context.bindingData;
const httpTrigger: AzureFunction = async function (context, req: HttpRequest) {
const { post_login_redirect_uri = "" } = req.query;
context.res = response({
@ -31,3 +31,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,9 +1,9 @@
const { response } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, _req: HttpRequest) {
const { provider } = context.bindingData;
const { post_login_redirect_uri } = req.query;
const location = `${SWA_EMU_AUTH_URI}/.redirect/${provider}?hostName=localhost&staticWebAppsAuthNonce=${context.invocationId}`;
@ -35,3 +35,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,12 +1,12 @@
const { response, validateCookie } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response, validateCookie } from "../../utils";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, req: HttpRequest) {
const cookie = req.headers.cookie;
const { post_logout_redirect_uri } = req.query;
if (!cookie || !validateCookie(cookie)) {
return response({
context.res = response({
context,
status: 401,
});
@ -31,3 +31,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,7 +1,8 @@
const { response } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const SWA_EMU_HOST = "http://localhost:" + process.env.SWA_EMU_PORT;
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, _req: HttpRequest) {
context.res = response({
context,
status: 302,
@ -39,3 +40,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,7 +1,8 @@
const { response, validateCookie, getProviderFromCookie } = require("../../utils");
const { currentUser } = require("../../userManager");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response, validateCookie } from "../../utils";
import { currentUser } from "../../userManager";
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, req: HttpRequest) {
const { cookie } = req.headers;
if (!cookie || !validateCookie(cookie)) {
@ -27,3 +28,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,21 +0,0 @@
const { response, ɵɵUseGithubDevToken } = require("../../utils");
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
const { provider } = context.bindingData;
const { redirect_uri, state, client_id } = req.query;
console.log("+++++++++");
console.log(req.query);
console.log("+++++++++");
location = `${redirect_uri}?code=CODE&state=${state}`;
context.res = response({
context,
status: 302,
headers: {
location,
},
});
};

Просмотреть файл

@ -0,0 +1,22 @@
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const httpTrigger: AzureFunction = async function (context, req: HttpRequest) {
const { redirect_uri, state } = req.query;
console.log("+++++++++");
console.log(req.query);
console.log("+++++++++");
const location = `${redirect_uri}?code=CODE&state=${state}`;
context.res = response({
context,
status: 302,
headers: {
location,
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,12 +1,13 @@
const { response } = require("../../utils");
const jwt = require("jsonwebtoken");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
import jwt from "jsonwebtoken";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
const { currentUser } = require("../../userManager");
import { currentUser } from "../../userManager";
const jwtKey = "123";
const jwtExpirySeconds = 300;
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, req: HttpRequest) {
const { cookie } = req.headers;
const payload = {
...currentUser(cookie),
@ -46,3 +47,5 @@ module.exports = async function (context, req) {
<script>f.submit();</script>`,
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,7 +1,8 @@
const { response, ɵɵUseGithubDevToken } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response, ɵɵUseGithubDevToken } from "../../utils";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, req: HttpRequest) {
const { provider } = context.bindingData;
const { post_login_redirect_uri } = req.query;
@ -52,3 +53,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,9 +1,8 @@
const { response } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
let { state, code, nonce } = req.query;
const httpTrigger: AzureFunction = async function (context, _req: HttpRequest) {
const location = `${SWA_EMU_AUTH_URI}/.auth/login/done`;
context.res = response({
context,
@ -35,3 +34,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,7 +1,8 @@
const { response } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, _req: HttpRequest) {
context.res = response({
context,
status: 302,
@ -20,3 +21,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,7 +1,8 @@
const { response } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, _req: HttpRequest) {
const location = `${SWA_EMU_AUTH_URI}/app/.auth/logout/complete`;
context.res = response({
@ -23,3 +24,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,8 +1,9 @@
const { response } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
const { hostName, post_logout_redirect_uri = "/" } = req.query;
const httpTrigger: AzureFunction = async function (context, req: HttpRequest) {
const { post_logout_redirect_uri = "/" } = req.query;
const location = `${SWA_EMU_AUTH_URI}/.auth/logout?post_login_redirect_uri=${post_logout_redirect_uri}`;
@ -25,3 +26,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -15,5 +15,6 @@
"direction": "out",
"name": "res"
}
]
],
"scriptFile": "./index.js"
}

Просмотреть файл

@ -1,7 +1,8 @@
const { response } = require("../../utils");
import { AzureFunction, HttpRequest } from "@azure/functions";
import { response } from "../../utils";
const SWA_EMU_AUTH_URI = process.env.SWA_EMU_AUTH_URI || `http://localhost:4242`;
module.exports = async function (context, req) {
const httpTrigger: AzureFunction = async function (context, req: HttpRequest) {
const { provider } = context.bindingData;
const { hostName, post_login_redirect_uri = "/.auth/login/done" } = req.query;
@ -26,3 +27,5 @@ module.exports = async function (context, req) {
},
});
};
export default httpTrigger;

Просмотреть файл

@ -1,15 +1,15 @@
const fs = require("fs");
const path = require("path");
const shell = require("shelljs");
const { readConfigFile } = require("./utils");
const { detectRuntime, RuntimeType } = require("./runtimes");
import fs from "fs";
import path from "path";
import shell from "shelljs";
import { readConfigFile } from "./utils";
import { detectRuntime, RuntimeType } from "./runtimes";
const exec = (command, options = {}) => shell.exec(command, { async: false, ...options });
const exec = (command: string, options = {}) => shell.exec(command, { async: false, ...options });
// use the concurrently binary provided by this emulator
const concurrentlyBin = path.resolve(__dirname, "..", "./node_modules/.bin/concurrently");
const nodeBuilder = (location, buildCommand, name, colour) => {
const nodeBuilder = (location: string, buildCommand: string, name: string, colour: string) => {
const appBuildCommand = [
"CI=1",
concurrentlyBin,
@ -24,7 +24,7 @@ const nodeBuilder = (location, buildCommand, name, colour) => {
});
};
const dotnetBuilder = (location, name, colour) => {
const dotnetBuilder = (location: string, name: string, colour: string) => {
const appBuildCommand = [
"CI=1",
concurrentlyBin,
@ -39,7 +39,7 @@ const dotnetBuilder = (location, name, colour) => {
});
};
module.exports = () => {
const builder = () => {
const { app_location, api_location, app_build_command, api_build_command } = readConfigFile();
const runtimeType = detectRuntime(app_location);
@ -84,3 +84,4 @@ module.exports = () => {
console.error(stderr);
}
};
export default builder;

Просмотреть файл

@ -1,14 +1,15 @@
#!/usr/bin/env node
const shell = require("shelljs");
const path = require("path");
const program = require("commander");
const builder = require("../src/builder");
const { readConfigFile } = require("../src/utils");
const { spawn } = require("child_process");
const { createRuntimeHost } = require("../src/runtimeHost");
import shell from "shelljs";
import path from "path";
import program from "commander";
import builder from "./builder";
import { readConfigFile } from "./utils";
import { spawn } from "child_process";
import { createRuntimeHost } from "./runtimeHost";
import { dashboard } from "./dashboard";
const EMU_PORT = 80;
const EMU_PORT = "80";
const AUTH_PORT = 4242;
const API_PORT = 7071;
const APP_PORT = 4200;
@ -21,8 +22,8 @@ program
.option("--api-uri <apiUri>", "set API uri", `http://localhost:${API_PORT}`)
.option("--api-prefix <apiPrefix>", "set API prefix", "api")
.option("--app-uri <appUri>", "set APP uri", `http://localhost:${APP_PORT}`)
.option("--use-api <useApi>", "Use running API dev server", null)
.option("--use-app <useApp>", "Use running APP dev server", null)
.option("--use-api <useApi>", "Use running API dev server", undefined)
.option("--use-app <useApp>", "Use running APP dev server", undefined)
.option("--host <host>", "set emulator host address", "0.0.0.0")
.option("--port <port>", "set emulator port value", EMU_PORT)
.option("--build", "build the API and APP before starting the emulator", false)
@ -47,7 +48,7 @@ const { app_artifact_location, api_location } = readConfigFile();
const envVarsObj = {
// set env vars for current command
StaticWebAppsAuthCookie: 123,
StaticWebAppsAuthCookie: "123",
StaticWebAppsAuthContextCookie: "abc",
AppServiceAuthSession: "1a2b3c",
DEBUG: program.debug ? "*" : "",
@ -84,10 +85,10 @@ const startCommand = [
`-c 'bgYellow.bold,bgMagenta.bold,bgCyan.bold,bgGreen.bold'`,
// start the reverse proxy
`"node ./src/proxy.js"`,
`"node ./dist/proxy.js"`,
// emulate auth
`"(cd ./src/auth/; func start --cors=* --port=${authUriPort})"`,
`"(cd ./dist/auth/; func start --cors=* --port=${authUriPort})"`,
// serve the app
`"${serveStaticContent}"`,
@ -110,8 +111,7 @@ if (program.build) {
if (program.ui) {
// print the dashboard UI
const { dashboard } = require("../src/dashboard");
const spawnx = (command, args) =>
const spawnx = (command: string, args: string[]) =>
spawn(`${command}`, args, {
shell: true,
env: { ...process.env, ...envVarsObj },
@ -127,11 +127,11 @@ if (program.ui) {
dashboard.stream("functions", functions);
// start auth
const auth = spawnx(`(cd ./src/auth/; func start --cors=* --port=${authUriPort})`, []);
const auth = spawnx(`(cd ./dist/auth/; func start --cors=* --port=${authUriPort})`, []);
dashboard.stream("auth", auth);
// start proxy
const status = spawnx(`node`, [`./src/proxy`]);
const status = spawnx(`node`, [`./dist/proxy`]);
dashboard.stream("status", status);
process.on("exit", () => {
@ -146,7 +146,7 @@ if (program.ui) {
cwd: path.resolve(__dirname, ".."),
env: { ...process.env, ...envVarsObj },
},
(code, stdout, stderr) => {
(_code, _stdout, stderr) => {
if (stderr.length) {
console.error(stderr);
}

Просмотреть файл

@ -1,83 +0,0 @@
// @ts-check
const blessed = require("blessed");
module.exports.dashboard = new (class Dashboard {
logText;
log;
constructor() {
this.screen = blessed.screen({
smartCSR: true,
dockBorders: false,
fullUnicode: true,
autoPadding: true,
});
this.screen.title = "Azure Static Web Apps Emulator";
this.screen.key(["escape", "q", "C-c"], () => {
process.kill(process.pid, "SIGINT");
});
this.hosting = this.addLogSection({ label: "Hosting" });
this.mapNavKeys({ logWidget: this.hosting });
this.functions = this.addLogSection({ top: "30%", label: "Functions" });
this.mapNavKeys({ logWidget: this.functions });
this.auth = this.addLogSection({ top: "58%", label: "Auth" });
this.mapNavKeys({ logWidget: this.auth });
this.status = this.addLogSection({ top: "90%", height: "15%", label: "Status" });
this.mapNavKeys({ logWidget: this.status });
this.screen.render();
}
addLogSection({ label, top = "0%", height = "30%" }) {
let logBox = blessed.log({
label,
padding: 1,
width: "100%",
height,
left: "0%",
top,
border: {
type: "line",
},
clickable: true,
focus: {
border: {
fg: 'green'
}
},
mouse: true,
});
this.screen.append(logBox);
const logWidget = blessed.log({ parent: logBox, tags: true, width: "100%-5" });
return logWidget;
}
mapNavKeys({ logWidget }) {
this.screen.key(["up"], () => {
logWidget.scroll(-1);
logWidget.screen.render();
});
this.screen.key(["down"], () => {
logWidget.scroll(1);
logWidget.screen.render();
});
}
stream(type, proc) {
if (proc) {
proc.stdout.on("data", (data) => this[type].log(data.toString("utf8")));
proc.stderr.on("data", (data) => this[type].log(data.toString("utf8")));
process.on("exit", () => {
process.kill(proc.pid);
});
}
}
})();

116
src/dashboard.ts Normal file
Просмотреть файл

@ -0,0 +1,116 @@
import { ChildProcessWithoutNullStreams } from "child_process";
import blessed from "blessed";
export const dashboard = new (class Dashboard {
#screen: blessed.Widgets.Screen;
#hosting: blessed.Widgets.Log;
#functions: blessed.Widgets.Log;
#auth: blessed.Widgets.Log;
#status: blessed.Widgets.Log;
constructor() {
this.#screen = blessed.screen({
smartCSR: true,
dockBorders: false,
fullUnicode: true,
autoPadding: true,
});
this.#screen.title = "Azure Static Web Apps Emulator";
this.#screen.key(["escape", "q", "C-c"], () => {
process.kill(process.pid, "SIGINT");
});
this.#hosting = this.addLogSection({ label: "Hosting" });
this.mapNavKeys({ logWidget: this.#hosting });
this.#functions = this.addLogSection({ top: "30%", label: "Functions" });
this.mapNavKeys({ logWidget: this.#functions });
this.#auth = this.addLogSection({ top: "58%", label: "Auth" });
this.mapNavKeys({ logWidget: this.#auth });
this.#status = this.addLogSection({ top: "90%", height: "15%", label: "Status" });
this.mapNavKeys({ logWidget: this.#status });
this.#screen.render();
}
addLogSection({ label = "", top = "0%", height = "30%" }) {
let logBox = blessed.log({
label,
padding: 1,
width: "100%",
height,
left: "0%",
top,
border: {
type: "line",
},
clickable: true,
focus: {
border: {
fg: "green",
},
},
mouse: true,
});
this.#screen.append(logBox);
const logWidget = blessed.log({ parent: logBox, tags: true, width: "100%-5" });
return logWidget;
}
mapNavKeys({ logWidget }: { logWidget: blessed.Widgets.Log }) {
this.#screen.key(["up"], () => {
logWidget.scroll(-1);
logWidget.screen.render();
});
this.#screen.key(["down"], () => {
logWidget.scroll(1);
logWidget.screen.render();
});
}
stream(type: "hosting" | "functions" | "auth" | "status", proc: ChildProcessWithoutNullStreams) {
if (proc) {
proc.stdout.on("data", (data) => {
const msg = data.toString("utf8");
switch (type) {
case "auth":
this.#auth.log(msg);
break;
case "functions":
this.#auth.log(msg);
break;
case "hosting":
this.#auth.log(msg);
break;
case "status":
this.#auth.log(msg);
break;
}
});
proc.stderr.on("data", (data) => {
const msg = data.toString("utf8");
switch (type) {
case "auth":
this.#auth.log(msg);
break;
case "functions":
this.#auth.log(msg);
break;
case "hosting":
this.#auth.log(msg);
break;
case "status":
this.#auth.log(msg);
break;
}
});
process.on("exit", () => {
process.kill(proc.pid);
});
}
}
})();

Просмотреть файл

@ -1,14 +1,21 @@
const fs = require("fs");
const path = require("path");
const http = require("http");
const httpProxy = require("http-proxy");
import fs from "fs";
import path from "path";
import http from "http";
import httpProxy from "http-proxy";
const proxyApp = httpProxy.createProxyServer({ autoRewrite: true });
const proxyApi = httpProxy.createProxyServer({ autoRewrite: true });
const proxyAuth = httpProxy.createProxyServer({ autoRewrite: false });
const { validateCookie } = require("./utils");
const { currentUser } = require("./userManager");
import { validateCookie } from "./utils";
import { currentUser } from "./userManager";
const serveStatic = (file, res) => {
type UserDefinedRoute = {
route: string;
allowedRoles?: string[];
statusCode?: number;
serve?: string;
};
const serveStatic = (file: string, res: http.ServerResponse) => {
fs.readFile(file, (err, data) => {
if (err) {
res.writeHead(404);
@ -21,7 +28,7 @@ const serveStatic = (file, res) => {
});
};
const readRoutes = (folder) => {
const readRoutes = (folder: string): UserDefinedRoute[] => {
if (!fs.existsSync(folder)) {
return [];
}
@ -35,9 +42,9 @@ const readRoutes = (folder) => {
return require(path.join(folder, routesFile)).routes || [];
};
const routes = readRoutes(process.env.SWA_EMU_APP_LOCATION);
const routes = readRoutes(process.env.SWA_EMU_APP_LOCATION || "");
const routeTest = (userDefinedRoute, currentRoute) => {
const routeTest = (userDefinedRoute: string, currentRoute: string) => {
if (userDefinedRoute === currentRoute) {
return true;
}
@ -50,7 +57,12 @@ const routeTest = (userDefinedRoute, currentRoute) => {
};
const server = http.createServer(function (req, res) {
const userDefinedRoute = routes.find((route) => req.url.endsWith(route.route));
// not quite sure how you'd hit an undefined url, but the types say you can
if (!req.url) {
return;
}
const userDefinedRoute = routes.find((route) => req.url!.endsWith(route.route));
// something from the `routes.json`
if (userDefinedRoute && routeTest(userDefinedRoute.route, req.url)) {
@ -67,7 +79,7 @@ const server = http.createServer(function (req, res) {
// Wanting a specific status code but no attached route
else if (userDefinedRoute.statusCode && !userDefinedRoute.serve) {
if (userDefinedRoute.statusCode === 404) {
const file404 = path.resolve(__dirname, "404.html");
const file404 = path.resolve(__dirname, "..", "mock-pages", "404.html");
serveStatic(file404, res);
return;
} else {
@ -94,7 +106,7 @@ const server = http.createServer(function (req, res) {
});
proxyAuth.on("proxyRes", function (proxyRes, req) {
console.log("auth>>", req.method, target + req.url);
console.log(JSON.stringify(proxyRes.headers, true, 2));
console.log(JSON.stringify(proxyRes.headers, undefined, 2));
});
proxyAuth.on("error", function (err, req) {
console.log("auth>>", req.method, target + req.url);
@ -148,6 +160,7 @@ const server = http.createServer(function (req, res) {
proxyApp.web(req, res, {
target,
secure: false,
});
proxyApp.on("error", function (err, req, res) {
@ -161,6 +174,8 @@ const server = http.createServer(function (req, res) {
}
});
const address = `${process.env.SWA_EMU_HOST || "0.0.0.0"}:${process.env.SWA_EMU_PORT || 80}`;
const port = parseInt(process.env.SWA_EMU_PORT || "", 10) || 80;
const host = process.env.SWA_EMU_HOST || "0.0.0.0";
const address = `${host}:${port}`;
console.log(`>> SWA listening on ${address}`);
server.listen(process.env.SWA_EMU_PORT || 80, process.env.SWA_EMU_HOST || "0.0.0.0");
server.listen(port, host);

Просмотреть файл

@ -1,10 +1,10 @@
const path = require("path");
const { readConfigFile } = require("./utils");
const { detectRuntime, RuntimeType } = require("./runtimes");
import path from "path";
import { readConfigFile } from "./utils";
import { detectRuntime, RuntimeType } from "./runtimes";
const httpServerBin = path.resolve(__dirname, "..", "./node_modules/.bin/http-server");
module.exports.createRuntimeHost = (port, proxyHost, proxyPort) => {
export const createRuntimeHost = (port: number, proxyHost: string, proxyPort: number) => {
const { app_location, app_artifact_location } = readConfigFile();
const runtimeType = detectRuntime(app_location);

Просмотреть файл

@ -1,16 +1,13 @@
const path = require("path");
const fs = require("fs");
import path from "path";
import fs from "fs";
const RuntimeType = {
dotnet: "dotnet",
node: "node",
unknown: "unknown",
export enum RuntimeType {
dotnet = "dotnet",
node ="node",
unknown = "unknown",
};
module.exports.RuntimeType = RuntimeType;
module.exports.detectRuntime = (app_location) => {
export const detectRuntime = (app_location: string) => {
if (fs.existsSync(app_location) === false) {
console.error(`The provided "app_location" was not found. Can't detect runtime!`);
console.error(app_location);

Просмотреть файл

@ -1,7 +0,0 @@
const users = require("./users.json");
const { getProviderFromCookie } = require("./utils");
module.exports.currentUser = (cookie) => {
const provider = getProviderFromCookie(cookie);
return users[provider] || null;
};

18
src/userManager.ts Normal file
Просмотреть файл

@ -0,0 +1,18 @@
import { getProviderFromCookie, SwaProviders } from "./utils";
type MockUser = {
identityProvider: string;
userId: string;
userDetails: string;
userRoles: string[];
};
type MockUsers = {
[key in SwaProviders]: MockUser;
};
const users: MockUsers = require("./users.json");
export const currentUser = (cookie?: string) => {
const provider = getProviderFromCookie(cookie);
return users[provider] || null;
};

Просмотреть файл

@ -1,27 +1,12 @@
const fs = require("fs");
const path = require("path");
const { Volume } = require("memfs");
const shell = require("shelljs");
import mockFs from "mock-fs";
import path from "path";
import shell from "shelljs";
const { response, validateCookie, getProviderFromCookie, readConfigFile } = require("./utils");
// mock fs
jest.mock(`fs`, () => {
const fs = jest.requireActual(`fs`);
const unionfs = require(`unionfs`).default;
unionfs.reset = () => {
unionfs.fss = [fs];
};
return unionfs.use(fs);
});
import { response, validateCookie, getProviderFromCookie, readConfigFile } from "./utils";
describe("Utils", () => {
beforeEach(() => {
delete process.env.DEBUG;
});
afterEach(() => {
fs.reset();
process.env.DEBUG = "";
});
describe("response()", () => {
@ -312,7 +297,7 @@ describe("Utils", () => {
},
});
expect(res.cookies).toBeDefined();
expect(res.cookies.foo).toBe("bar");
expect(res.cookies!.foo).toBe("bar");
});
});
@ -419,12 +404,12 @@ describe("Utils", () => {
describe("readConfigFile()", () => {
it("config file not found should throw", () => {
const mockExit = jest.spyOn(shell, "exit").mockImplementation(() => {});
jest.spyOn(shell, "exit").mockImplementation(((_) => {}) as (code?: number | undefined) => never);
expect(() => readConfigFile()).toThrow(/TypeError: GitHub action file content should be a string/);
});
it("config file not found should process.exit(0)", () => {
const mockExit = jest.spyOn(shell, "exit").mockImplementation(() => {});
const mockExit = jest.spyOn(shell, "exit").mockImplementation(((_) => {}) as (code?: number | undefined) => never);
// we know this will throw. Check previous test
try {
@ -435,149 +420,122 @@ describe("Utils", () => {
});
it("config file with wrong filename should process.exit(0)", () => {
const mockExit = jest.spyOn(shell, "exit").mockImplementation(() => {});
fs.use(
Volume.fromJSON(
{
"wrong-file-name-pattern.yml": "",
},
".github/workflows"
)
);
const mockExit = jest.spyOn(shell, "exit").mockImplementation(((_) => {}) as (code?: number | undefined) => never);
mockFs({
".github/workflows/wrong-file-name-pattern.yml": "",
});
expect(() => readConfigFile()).toThrow(/TypeError: GitHub action file content should be a string/);
expect(mockExit).toHaveBeenCalledWith(0);
mockFs.restore();
});
it("invalid YAML file should throw", () => {
const mockExit = jest.spyOn(shell, "exit").mockImplementation(() => {});
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps__not-valid.yml": "",
},
".github/workflows"
)
);
const mockExit = jest.spyOn(shell, "exit").mockImplementation(((_) => {}) as (code?: number | undefined) => never);
mockFs({
".github/workflows/azure-static-web-apps__not-valid.yml": "",
});
expect(() => readConfigFile()).toThrow(/could not parse the SWA workflow file/);
expect(mockExit).toHaveBeenCalledWith(0);
mockFs.restore();
});
describe("checking workflow properties", () => {
it("missing property 'jobs' should throw", () => {
const mockExit = jest.spyOn(shell, "exit").mockImplementation(() => {});
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps__not-valid.yml": `name: Azure Static Web Apps CI/CD`,
},
".github/workflows"
)
);
const mockExit = jest.spyOn(shell, "exit").mockImplementation(((_) => {}) as (code?: number | undefined) => never);
mockFs({
".github/workflows/azure-static-web-apps__not-valid.yml": `name: Azure Static Web Apps CI/CD`,
});
expect(() => readConfigFile()).toThrow(/missing property 'jobs'/);
expect(mockExit).toHaveBeenCalledWith(0);
mockFs.restore();
});
it("missing property 'jobs.build_and_deploy_job' should throw", () => {
const mockExit = jest.spyOn(shell, "exit").mockImplementation(() => {});
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
const mockExit = jest.spyOn(shell, "exit").mockImplementation(((_) => {}) as (code?: number | undefined) => never);
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
invalid_property:
`,
},
".github/workflows"
)
);
});
expect(() => readConfigFile()).toThrow(/missing property 'jobs.build_and_deploy_job'/);
expect(mockExit).toHaveBeenCalledWith(0);
mockFs.restore();
});
it("missing property 'jobs.build_and_deploy_job.steps' should throw", () => {
const mockExit = jest.spyOn(shell, "exit").mockImplementation(() => {});
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
const mockExit = jest.spyOn(shell, "exit").mockImplementation(((_) => {}) as (code?: number | undefined) => never);
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
invalid_property:
`,
},
".github/workflows"
)
);
});
expect(() => readConfigFile()).toThrow(/missing property 'jobs.build_and_deploy_job.steps'/);
expect(mockExit).toHaveBeenCalledWith(0);
mockFs.restore();
});
it("invalid property 'jobs.build_and_deploy_job.steps' should throw", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
`,
},
".github/workflows"
)
);
});
expect(() => readConfigFile()).toThrow(/missing property 'jobs.build_and_deploy_job.steps'/);
mockFs.restore();
});
it("invalid property 'jobs.build_and_deploy_job.steps[]' should throw", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
- name: Build And Deploy
`,
},
".github/workflows"
)
);
});
expect(() => readConfigFile()).toThrow(/invalid property 'jobs.build_and_deploy_job.steps\[\]'/);
mockFs.restore();
});
it("missing property 'jobs.build_and_deploy_job.steps[].with' should throw", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
- name: Build And Deploy
uses: Azure/static-web-apps-deploy@v0.0.1-preview
`,
},
".github/workflows"
)
);
});
expect(() => readConfigFile()).toThrow(/missing property 'jobs.build_and_deploy_job.steps\[\].with'/);
mockFs.restore();
});
});
describe("checking SWA properties", () => {
it("property 'app_location' should be set", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -586,19 +544,16 @@ jobs:
with:
app_location: "/"
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().app_location).toBe(path.normalize(process.cwd() + "/"));
mockFs.restore();
});
it("property 'app_location' should be set to '/' if missing", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -607,19 +562,16 @@ jobs:
with:
foo: bar
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().app_location).toBe(path.normalize(process.cwd() + "/"));
mockFs.restore();
});
it("property 'api_location' should be set", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -628,19 +580,16 @@ jobs:
with:
api_location: "/api"
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().api_location).toBe(path.normalize(process.cwd() + "/api"));
mockFs.restore();
});
it("property 'api_location' should be set to 'api' if missing", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -649,19 +598,16 @@ jobs:
with:
foo: bar
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().api_location).toBe(path.normalize(process.cwd() + "/api"));
mockFs.restore();
});
it("property 'app_artifact_location' should be set", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -670,19 +616,16 @@ jobs:
with:
app_artifact_location: "/"
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().app_artifact_location).toBe(path.normalize(process.cwd() + "/"));
mockFs.restore();
});
it("property 'app_artifact_location' should be set to '/' if missing", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -691,19 +634,16 @@ jobs:
with:
foo: bar
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().app_artifact_location).toBe(path.normalize(process.cwd() + "/"));
mockFs.restore();
});
it("property 'app_build_command' should be set", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -712,19 +652,16 @@ jobs:
with:
app_build_command: "echo test"
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().app_build_command).toBe("echo test");
mockFs.restore();
});
it("property 'app_build_command' should be set to default if missing", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -733,19 +670,16 @@ jobs:
with:
foo: bar
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().app_build_command).toBe("npm run build --if-present");
mockFs.restore();
});
it("property 'api_build_command' should be set", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -754,19 +688,16 @@ jobs:
with:
api_build_command: "echo test"
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().api_build_command).toBe("echo test");
mockFs.restore();
});
it("property 'api_build_command' should be set to default if missing", () => {
fs.use(
Volume.fromJSON(
{
"azure-static-web-apps.yml": `
mockFs({
".github/workflows/azure-static-web-apps.yml": `
jobs:
build_and_deploy_job:
steps:
@ -775,12 +706,11 @@ jobs:
with:
foo: bar
`,
},
".github/workflows"
)
);
});
expect(readConfigFile().api_build_command).toBe("npm run build --if-present");
mockFs.restore();
});
});
});

Просмотреть файл

@ -1,12 +1,16 @@
const cookie = require("cookie");
const { default: fetch } = require("node-fetch");
const path = require("path");
const fs = require("fs");
const shell = require("shelljs");
const YAML = require("yaml");
const { detectRuntime, RuntimeType } = require("./runtimes");
import cookie from "cookie";
import fetch from "node-fetch";
import path from "path";
import fs from "fs";
import shell from "shelljs";
import YAML from "yaml";
import { detectRuntime, RuntimeType } from "./runtimes";
module.exports.response = ({ context, status, headers, cookies, body = "" }) => {
type ResponseOptions = {
[key: string]: any;
};
export const response = ({ context, status, headers, cookies, body = "" }: ResponseOptions) => {
if (!context || !context.bindingData) {
throw Error(
"TypeError: context must be a valid Azure Functions context object. " +
@ -66,7 +70,7 @@ module.exports.response = ({ context, status, headers, cookies, body = "" }) =>
return res;
};
module.exports.validateCookie = (cookieValue) => {
export const validateCookie = (cookieValue: any) => {
if (typeof cookieValue !== "string") {
throw Error("TypeError: cookie value must be a string");
}
@ -80,16 +84,17 @@ module.exports.validateCookie = (cookieValue) => {
return false;
};
module.exports.getProviderFromCookie = (cookieValue) => {
export type SwaProviders = "aad" | "github" | "twitter" | "facebook" | "google";
export const getProviderFromCookie = (cookieValue: any): SwaProviders => {
if (typeof cookieValue !== "string") {
throw Error("TypeError: cookie value must be a string");
}
const cookies = cookie.parse(cookieValue);
return cookies.StaticWebAppsAuthCookie__PROVIDER;
return cookies.StaticWebAppsAuthCookie__PROVIDER as SwaProviders;
};
module.exports.ɵɵUseGithubDevToken = async () => {
export const ɵɵUseGithubDevToken = async () => {
console.log("!!!! Notice: You are using a dev GitHub token. You should create and use your own!");
console.log("!!!! Read https://docs.github.com/en/developers/apps/building-oauth-apps");
const swaTokens = `https://gist.githubusercontent.com/manekinekko/7fbfc79a85b0f1f312715f1beda26236/raw/740c51aac5b1fb970e69408067a49907485d1e31/swa-emu.json`;
@ -98,7 +103,7 @@ module.exports.ɵɵUseGithubDevToken = async () => {
return token.github;
};
module.exports.readConfigFile = () => {
export const readConfigFile = () => {
const githubActionFolder = path.resolve(process.cwd(), ".github/workflows/");
// find the SWA GitHub action file
@ -110,6 +115,10 @@ module.exports.readConfigFile = () => {
.filter((file) => file.includes("azure-static-web-apps") && file.endsWith(".yml"))
.pop();
if (!githubActionFile) {
throw Error("No SWA configuration build found. A SWA folder must contain a GitHub workflow file. Read more: https://bit.ly/31RAODu");
}
githubActionFile = path.resolve(githubActionFolder, githubActionFile);
githubActionContent = fs.readFileSync(githubActionFile, "utf8");
@ -144,7 +153,8 @@ module.exports.readConfigFile = () => {
);
}
const swaBuildConfig = swaYaml.jobs.build_and_deploy_job.steps.find((step) => step.uses && step.uses.includes("static-web-apps-deploy"));
// hacking this to have an `any` on the type in .find, mainly because a typescript definition for the YAML file is painful...
const swaBuildConfig = swaYaml.jobs.build_and_deploy_job.steps.find((step: any) => step.uses && step.uses.includes("static-web-apps-deploy"));
if (!swaBuildConfig) {
throw Error(
@ -177,10 +187,11 @@ module.exports.readConfigFile = () => {
app_artifact_location = path.normalize(app_artifact_location);
const detectedRuntimeType = detectRuntime(app_location);
if (detectedRuntimeType === RuntimeType.node) {
app_artifact_location = path.join(app_location, app_artifact_location);
} else {
if (detectedRuntimeType === RuntimeType.dotnet) {
// TODO: work out what runtime is being used for .NET rather than hard-coded
app_artifact_location = path.join(app_location, "bin", "Debug", "netstandard2.1", "publish", app_artifact_location);
} else {
app_artifact_location = path.join(app_location, app_artifact_location);
}
const config = {

35
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "es2019",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
/* Strict Type-Checking Options */
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
/* Additional Checks */
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
/* Advanced Options */
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"exclude": ["node_modules", "dist"],
"include": ["src/**/*.ts", "src/**/*.json", "environment.d.ts"]
}