46 Commits

Author SHA1 Message Date
0cc24af26b 0.0.7
All checks were successful
Publish / lints (push) Successful in 29s
Publish / tests (push) Successful in 1m24s
Publish / Publishing (push) Successful in 1m41s
/ Misc Linters (push) Successful in 34s
/ Build App (push) Successful in 2m3s
2024-08-23 19:03:33 +02:00
c746f71622 Merge pull request 'chore(deps): update dependency svelte to v4.2.19' (#61) from renovate/svelte-4.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 37s
/ Build App (push) Successful in 1m35s
Reviewed-on: #61
2024-08-23 19:00:23 +02:00
b7e38dca12 chore(deps): update dependency svelte to v4.2.19
All checks were successful
/ Misc Linters (pull_request) Successful in 35s
/ Build App (pull_request) Successful in 1m43s
2024-08-23 16:56:16 +00:00
f098209b66 Merge pull request 'chore(deps): lock file maintenance' (#58) from renovate/lock-file-maintenance into main
All checks were successful
/ Misc Linters (push) Successful in 45s
/ Build App (push) Successful in 1m43s
Reviewed-on: #58
2024-08-23 18:53:20 +02:00
b5db49061e chore(deps): lock file maintenance
All checks were successful
/ Misc Linters (pull_request) Successful in 25s
/ Build App (pull_request) Successful in 1m48s
2024-08-23 18:44:14 +02:00
716799beb2 Merge pull request 'chore(deps): update dependency @sveltejs/vite-plugin-svelte to v3.1.2' (#59) from renovate/sveltejs-vite-plugin-svelte-3.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 30s
/ Build App (push) Successful in 1m49s
Reviewed-on: #59
2024-08-23 18:44:07 +02:00
a8c64f9d74 chore(deps): update dependency @sveltejs/vite-plugin-svelte to v3.1.2
All checks were successful
/ Misc Linters (pull_request) Successful in 30s
/ Build App (pull_request) Successful in 1m45s
2024-08-23 18:35:47 +02:00
fe7e1062d4 Merge pull request 'chore(deps): update dependency svelte-check to v3.8.6' (#60) from renovate/svelte-check-3.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 26s
/ Build App (push) Successful in 1m59s
Reviewed-on: #60
2024-08-23 18:35:38 +02:00
372d2b4253 chore(deps): update dependency svelte-check to v3.8.6
All checks were successful
/ Misc Linters (pull_request) Successful in 31s
/ Build App (pull_request) Successful in 1m37s
2024-08-22 19:02:02 +00:00
ad5705b00c Merge pull request 'chore(deps): update dependency vite to v5.4.2' (#57) from renovate/vite-5.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 24s
/ Build App (push) Successful in 1m39s
Reviewed-on: #57
2024-08-21 08:58:40 +02:00
868a518097 chore(deps): update dependency vite to v5.4.2
All checks were successful
/ Misc Linters (pull_request) Successful in 26s
/ Build App (pull_request) Successful in 1m34s
2024-08-20 16:02:01 +00:00
2836915aeb Merge pull request 'chore(deps): update dependency @sveltejs/kit to v2.5.24' (#55) from renovate/sveltejs-kit-2.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 24s
/ Build App (push) Successful in 1m45s
Reviewed-on: #55
2024-08-20 13:59:22 +02:00
353cc1bf1e Merge pull request 'chore(deps): update dependency typescript-eslint to v8.2.0' (#56) from renovate/typescript-eslint-monorepo into main
Some checks failed
/ Build App (push) Waiting to run
/ Misc Linters (push) Has been cancelled
Reviewed-on: #56
2024-08-20 13:59:12 +02:00
d60c1ddbbd Merge pull request 'chore(deps): update dependency @types/node to v20.16.1' (#54) from renovate/node-20.x-lockfile into main
Some checks failed
/ Build App (push) Has been cancelled
/ Misc Linters (push) Has been cancelled
Reviewed-on: #54
2024-08-20 13:58:58 +02:00
09a1e98afb chore(deps): update dependency typescript-eslint to v8.2.0
All checks were successful
/ Misc Linters (pull_request) Successful in 27s
/ Build App (pull_request) Successful in 1m27s
2024-08-19 19:01:56 +00:00
5c894e1462 chore(deps): update dependency @sveltejs/kit to v2.5.24
All checks were successful
/ Misc Linters (pull_request) Successful in 28s
/ Build App (pull_request) Successful in 1m36s
2024-08-19 13:01:38 +00:00
1eeec1215e chore(deps): update dependency @types/node to v20.16.1
All checks were successful
/ Misc Linters (pull_request) Successful in 18s
/ Build App (pull_request) Successful in 1m32s
2024-08-19 04:01:39 +00:00
d3016c765e Merge pull request 'chore(deps): update dependency @types/node to v20.16.0' (#52) from renovate/node-20.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 22s
/ Build App (push) Successful in 1m36s
Reviewed-on: #52
2024-08-18 16:03:16 +02:00
568da269a1 chore(deps): update dependency @types/node to v20.16.0
All checks were successful
/ Misc Linters (pull_request) Successful in 21s
/ Build App (pull_request) Successful in 1m20s
2024-08-18 15:51:05 +02:00
48b9a4c8fb Merge pull request 'Integration tests for API' (#53) from testing into main
All checks were successful
/ Misc Linters (push) Successful in 39s
/ Build App (push) Successful in 1m34s
Reviewed-on: #53
2024-08-18 15:48:51 +02:00
25ef50f358 Add test for page
All checks were successful
/ Misc Linters (pull_request) Successful in 27s
/ Build App (pull_request) Successful in 1m49s
2024-08-18 15:46:46 +02:00
20920586e0 Formatting
All checks were successful
/ Misc Linters (pull_request) Successful in 23s
/ Build App (pull_request) Successful in 1m21s
2024-08-18 15:38:03 +02:00
56f568634e Implement first integration tests
Some checks failed
/ Misc Linters (pull_request) Successful in 28s
/ Build App (pull_request) Failing after 27s
2024-08-18 15:34:36 +02:00
0fd1f178be Add playwright 2024-08-18 15:34:19 +02:00
da92ed6258 Move request-id header name to $lib 2024-08-18 15:33:56 +02:00
13b1d57e08 Merge pull request 'Healthcheck' (#51) from healthcheck into main
All checks were successful
/ Misc Linters (push) Successful in 31s
/ Build App (push) Successful in 1m8s
Reviewed-on: #51
2024-08-17 18:28:16 +02:00
1f69e739d0 Add healthcheck endpoint
All checks were successful
/ Misc Linters (pull_request) Successful in 18s
/ Build App (pull_request) Successful in 54s
2024-08-17 18:26:57 +02:00
58a65856aa Simplify
All checks were successful
/ Misc Linters (push) Successful in 23s
/ Build App (push) Successful in 52s
/ Misc Linters (pull_request) Successful in 27s
/ Build App (pull_request) Successful in 59s
2024-08-17 17:22:27 +02:00
e2a4335937 Define endpoint in TSy way
All checks were successful
/ Misc Linters (push) Successful in 25s
/ Build App (push) Successful in 52s
2024-08-17 17:12:05 +02:00
3160923f7e Fix typos and define handler in TSy way
All checks were successful
/ Misc Linters (push) Successful in 23s
/ Build App (push) Successful in 52s
2024-08-17 16:49:38 +02:00
37d6cd848d 0.0.6
All checks were successful
/ Misc Linters (push) Successful in 25s
Publish / lints (push) Successful in 25s
/ Build App (push) Successful in 1m1s
Publish / tests (push) Successful in 52s
Publish / Publishing (push) Successful in 1m30s
2024-08-17 16:43:34 +02:00
1aff6554b5 Copy node_modules over to runtime image
All checks were successful
/ Misc Linters (push) Successful in 20s
/ Build App (push) Successful in 52s
2024-08-17 16:37:29 +02:00
a6359d9516 Build using node 22 2024-08-17 16:37:13 +02:00
6ab52c1830 Fix lints
All checks were successful
/ Misc Linters (push) Successful in 23s
/ Build App (push) Successful in 52s
2024-08-17 15:54:33 +02:00
cfb5564830 Formatting with trailing comma 2024-08-17 15:45:52 +02:00
48948a180a Logging
Some checks failed
/ Misc Linters (push) Successful in 21s
/ Build App (push) Failing after 21s
2024-08-17 15:39:43 +02:00
1557d2cca9 More test cases 2024-08-17 15:38:23 +02:00
019d1d2e62 Log requests 2024-08-17 15:38:12 +02:00
1b4bb1d2e2 Measure execution time 2024-08-17 15:06:28 +02:00
824852fe7a Initialize and use bunyan logger 2024-08-17 15:06:12 +02:00
4e2b1b74a4 Add requestId to request context 2024-08-17 15:04:43 +02:00
0aa7a18f05 Add bunyan logger 2024-08-17 15:04:14 +02:00
10d6be5f82 Platform independent path validation
All checks were successful
/ Misc Linters (push) Successful in 21s
/ Build App (push) Successful in 51s
2024-08-17 15:00:54 +02:00
6615e2788a Platform independent path validation
All checks were successful
/ Misc Linters (push) Successful in 27s
/ Misc Linters (pull_request) Successful in 20s
/ Build App (pull_request) Successful in 58s
/ Build App (push) Successful in 47s
2024-08-17 14:59:55 +02:00
1f846bd5fe Merge pull request 'chore(deps): update dependency @types/node to v20.15.0' (#49) from renovate/node-20.x-lockfile into main
All checks were successful
/ Misc Linters (push) Successful in 34s
/ Build App (push) Successful in 1m1s
Reviewed-on: #49
2024-08-17 03:59:55 +02:00
da7b056095 chore(deps): update dependency @types/node to v20.15.0
All checks were successful
/ Misc Linters (pull_request) Successful in 19s
/ Build App (pull_request) Successful in 55s
2024-08-16 19:01:36 +00:00
21 changed files with 585 additions and 140 deletions

7
.gitignore vendored
View File

@ -20,5 +20,12 @@ Thumbs.db
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth
uploads uploads
.direnv .direnv

View File

@ -1,7 +1,7 @@
{ {
"useTabs": true, "useTabs": true,
"singleQuote": true, "singleQuote": true,
"trailingComma": "none", "trailingComma": "es5",
"printWidth": 100, "printWidth": 100,
"plugins": ["prettier-plugin-svelte"], "plugins": ["prettier-plugin-svelte"],
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]

View File

@ -1,4 +1,4 @@
FROM node:21-alpine AS builder FROM node:22-alpine AS builder
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
@ -7,11 +7,12 @@ RUN npm ci
COPY . . COPY . .
RUN npm run build RUN npm run build
FROM node:21-alpine FROM node:22-alpine
WORKDIR /app WORKDIR /app
RUN chown -R node:node /app RUN chown -R node:node /app
USER node:node USER node:node
COPY ./container/entrypoint.sh /entrypoint.sh COPY ./container/entrypoint.sh /entrypoint.sh
COPY package.json . COPY package.json .
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/build ./build COPY --from=builder /app/build ./build
ENTRYPOINT ["sh", "/entrypoint.sh"] ENTRYPOINT ["sh", "/entrypoint.sh"]

View File

@ -15,19 +15,19 @@ export default [
languageOptions: { languageOptions: {
globals: { globals: {
...globals.browser, ...globals.browser,
...globals.node ...globals.node,
} },
} },
}, },
{ {
files: ['**/*.svelte'], files: ['**/*.svelte'],
languageOptions: { languageOptions: {
parserOptions: { parserOptions: {
parser: ts.parser parser: ts.parser,
} },
} },
}, },
{ {
ignores: ['build/', '.svelte-kit/', 'dist/'] ignores: ['build/', '.svelte-kit/', 'dist/'],
} },
]; ];

411
package-lock.json generated
View File

@ -1,19 +1,26 @@
{ {
"name": "fotochallenge", "name": "fotochallenge",
"version": "0.0.5", "version": "0.0.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "fotochallenge", "name": "fotochallenge",
"version": "0.0.5", "version": "0.0.7",
"dependencies": {
"bunyan": "^1.8.15",
"uuid": "^10.0.0"
},
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.46.1",
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.2.0", "@sveltejs/adapter-node": "^5.2.0",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/bunyan": "^1.8.11",
"@types/eslint": "^9.0.0", "@types/eslint": "^9.0.0",
"@types/node": "^20.14.11", "@types/node": "^20.14.11",
"@types/uuid": "^10.0.0",
"bulma": "^1.0.1", "bulma": "^1.0.1",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@ -728,6 +735,22 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/@playwright/test": {
"version": "1.46.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.1.tgz",
"integrity": "sha512-Fq6SwLujA/DOIvNC2EL/SojJnkKf/rAwJ//APpJJHRyMi1PdKrY3Az+4XNQ51N4RTbItbIByQ0jgd1tayq1aeA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.46.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@polka/url": { "node_modules/@polka/url": {
"version": "1.0.0-next.25", "version": "1.0.0-next.25",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
@ -1085,9 +1108,9 @@
} }
}, },
"node_modules/@sveltejs/kit": { "node_modules/@sveltejs/kit": {
"version": "2.5.22", "version": "2.5.24",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.22.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.24.tgz",
"integrity": "sha512-PQ98baF2WzvG5yiO4cZKJZJG60XjHTZD1jyho3u9Kmthx2ytdGYyVPPvKXgKXpKSq4wwctD9dl0d2blSbJMcOg==", "integrity": "sha512-Nr2oxsCsDfEkdS/zzQQQbsPYTbu692Qs3/iE3L7VHzCVjG2+WujF9oMUozWI7GuX98KxYSoPMlAsfmDLSg44hQ==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@ -1112,15 +1135,15 @@
"node": ">=18.13" "node": ">=18.13"
}, },
"peerDependencies": { "peerDependencies": {
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1",
"svelte": "^4.0.0 || ^5.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0",
"vite": "^5.0.3" "vite": "^5.0.3"
} }
}, },
"node_modules/@sveltejs/vite-plugin-svelte": { "node_modules/@sveltejs/vite-plugin-svelte": {
"version": "3.1.1", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.2.tgz",
"integrity": "sha512-rimpFEAboBBHIlzISibg94iP09k/KYdHgVhJlcsTfn7KMBhc70jFX/GRWkRdFCc2fdnk+4+Bdfej23cMDnJS6A==", "integrity": "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1158,6 +1181,16 @@
"vite": "^5.0.0" "vite": "^5.0.0"
} }
}, },
"node_modules/@types/bunyan": {
"version": "1.8.11",
"resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.11.tgz",
"integrity": "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/cookie": { "node_modules/@types/cookie": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
@ -1191,13 +1224,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.14.15", "version": "20.16.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.15.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz",
"integrity": "sha512-Fz1xDMCF/B00/tYSVMlmK7hVeLh7jE5f3B7X1/hmV0MJBwE27KlS7EvD/Yp+z1lm8mVhwV5w+n8jOZG8AfTlKw==", "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~6.19.2"
} }
}, },
"node_modules/@types/pug": { "node_modules/@types/pug": {
@ -1214,18 +1247,25 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.2.0.tgz",
"integrity": "sha512-LlNBaHFCEBPHyD4pZXb35mzjGkuGKXU5eeCA1SxvHfiRES0E82dOounfVpL4DCqYvJEKab0bZIA0gCRpdLKkCw==", "integrity": "sha512-02tJIs655em7fvt9gps/+4k4OsKULYGtLBPJfOsmOq1+3cdClYiF0+d6mHu6qDnTcg88wJBkcPLpQhq7FyDz0A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.1.0", "@typescript-eslint/scope-manager": "8.2.0",
"@typescript-eslint/type-utils": "8.1.0", "@typescript-eslint/type-utils": "8.2.0",
"@typescript-eslint/utils": "8.1.0", "@typescript-eslint/utils": "8.2.0",
"@typescript-eslint/visitor-keys": "8.1.0", "@typescript-eslint/visitor-keys": "8.2.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -1249,16 +1289,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.2.0.tgz",
"integrity": "sha512-U7iTAtGgJk6DPX9wIWPPOlt1gO57097G06gIcl0N0EEnNw8RGD62c+2/DiP/zL7KrkqnnqF7gtFGR7YgzPllTA==", "integrity": "sha512-j3Di+o0lHgPrb7FxL3fdEy6LJ/j2NE8u+AP/5cQ9SKb+JLH6V6UHDqJ+e0hXBkHP1wn1YDFjYCS9LBQsZDlDEg==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.1.0", "@typescript-eslint/scope-manager": "8.2.0",
"@typescript-eslint/types": "8.1.0", "@typescript-eslint/types": "8.2.0",
"@typescript-eslint/typescript-estree": "8.1.0", "@typescript-eslint/typescript-estree": "8.2.0",
"@typescript-eslint/visitor-keys": "8.1.0", "@typescript-eslint/visitor-keys": "8.2.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -1278,14 +1318,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.2.0.tgz",
"integrity": "sha512-DsuOZQji687sQUjm4N6c9xABJa7fjvfIdjqpSIIVOgaENf2jFXiM9hIBZOL3hb6DHK9Nvd2d7zZnoMLf9e0OtQ==", "integrity": "sha512-OFn80B38yD6WwpoHU2Tz/fTz7CgFqInllBoC3WP+/jLbTb4gGPTy9HBSTsbDWkMdN55XlVU0mMDYAtgvlUspGw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.1.0", "@typescript-eslint/types": "8.2.0",
"@typescript-eslint/visitor-keys": "8.1.0" "@typescript-eslint/visitor-keys": "8.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1296,14 +1336,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.2.0.tgz",
"integrity": "sha512-oLYvTxljVvsMnldfl6jIKxTaU7ok7km0KDrwOt1RHYu6nxlhN3TIx8k5Q52L6wR33nOwDgM7VwW1fT1qMNfFIA==", "integrity": "sha512-g1CfXGFMQdT5S+0PSO0fvGXUaiSkl73U1n9LTK5aRAFnPlJ8dLKkXr4AaLFvPedW8lVDoMgLLE3JN98ZZfsj0w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.1.0", "@typescript-eslint/typescript-estree": "8.2.0",
"@typescript-eslint/utils": "8.1.0", "@typescript-eslint/utils": "8.2.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.3.0" "ts-api-utils": "^1.3.0"
}, },
@ -1321,9 +1361,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.2.0.tgz",
"integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==", "integrity": "sha512-6a9QSK396YqmiBKPkJtxsgZZZVjYQ6wQ/TlI0C65z7vInaETuC6HAHD98AGLC8DyIPqHytvNuS8bBVvNLKyqvQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -1335,14 +1375,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.2.0.tgz",
"integrity": "sha512-NTHhmufocEkMiAord/g++gWKb0Fr34e9AExBRdqgWdVBaKoei2dIyYKD9Q0jBnvfbEA5zaf8plUFMUH6kQ0vGg==", "integrity": "sha512-kiG4EDUT4dImplOsbh47B1QnNmXSoUqOjWDvCJw/o8LgfD0yr7k2uy54D5Wm0j4t71Ge1NkynGhpWdS0dEIAUA==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.1.0", "@typescript-eslint/types": "8.2.0",
"@typescript-eslint/visitor-keys": "8.1.0", "@typescript-eslint/visitor-keys": "8.2.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -1390,16 +1430,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.2.0.tgz",
"integrity": "sha512-ypRueFNKTIFwqPeJBfeIpxZ895PQhNyH4YID6js0UoBImWYoSjBsahUn9KMiJXh94uOjVBgHD9AmkyPsPnFwJA==", "integrity": "sha512-O46eaYKDlV3TvAVDNcoDzd5N550ckSe8G4phko++OCSC1dYIb9LTc3HDGYdWqWIAT5qDUKphO6sd9RrpIJJPfg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.1.0", "@typescript-eslint/scope-manager": "8.2.0",
"@typescript-eslint/types": "8.1.0", "@typescript-eslint/types": "8.2.0",
"@typescript-eslint/typescript-estree": "8.1.0" "@typescript-eslint/typescript-estree": "8.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -1413,13 +1453,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.2.0.tgz",
"integrity": "sha512-ba0lNI19awqZ5ZNKh6wCModMwoZs457StTebQ0q1NP58zSi2F6MOZRXwfKZy+jB78JNJ/WH8GSh2IQNzXX8Nag==", "integrity": "sha512-sbgsPMW9yLvS7IhCi8IpuK1oBmtbWUNP+hBdwl/I9nzqVsszGnNGti5r9dUtF5RLivHUFFIdRvLiTsPhzSyJ3Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.1.0", "@typescript-eslint/types": "8.2.0",
"eslint-visitor-keys": "^3.4.3" "eslint-visitor-keys": "^3.4.3"
}, },
"engines": { "engines": {
@ -1671,7 +1711,7 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
@ -1691,7 +1731,7 @@
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
@ -1741,6 +1781,24 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/bunyan": {
"version": "1.8.15",
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.15.tgz",
"integrity": "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==",
"engines": [
"node >=0.10.0"
],
"license": "MIT",
"bin": {
"bunyan": "bin/bunyan"
},
"optionalDependencies": {
"dtrace-provider": "~0.8",
"moment": "^2.19.3",
"mv": "~2",
"safe-json-stringify": "~1"
}
},
"node_modules/cac": { "node_modules/cac": {
"version": "6.7.14", "version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@ -1898,7 +1956,7 @@
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true, "devOptional": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/cookie": { "node_modules/cookie": {
@ -2038,6 +2096,20 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dtrace-provider": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
"integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==",
"hasInstallScript": true,
"license": "BSD-2-Clause",
"optional": true,
"dependencies": {
"nan": "^2.14.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/eastasianwidth": { "node_modules/eastasianwidth": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@ -2779,7 +2851,7 @@
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true, "devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"once": "^1.3.0", "once": "^1.3.0",
@ -2790,7 +2862,7 @@
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true, "devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
@ -3151,7 +3223,7 @@
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true, "devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
@ -3164,7 +3236,7 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -3184,7 +3256,7 @@
"version": "0.5.6", "version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"minimist": "^1.2.6" "minimist": "^1.2.6"
@ -3193,6 +3265,16 @@
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
} }
}, },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT",
"optional": true,
"engines": {
"node": "*"
}
},
"node_modules/mri": { "node_modules/mri": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
@ -3220,6 +3302,28 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/mv": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
"integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==",
"license": "MIT",
"optional": true,
"dependencies": {
"mkdirp": "~0.5.1",
"ncp": "~2.0.0",
"rimraf": "~2.4.0"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/nan": {
"version": "2.20.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
"integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
"license": "MIT",
"optional": true
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
@ -3246,6 +3350,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ncp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
"integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==",
"license": "MIT",
"optional": true,
"bin": {
"ncp": "bin/ncp"
}
},
"node_modules/normalize-path": { "node_modules/normalize-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -3289,7 +3403,7 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true, "devOptional": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"wrappy": "1" "wrappy": "1"
@ -3395,7 +3509,7 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true, "devOptional": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -3514,6 +3628,53 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/playwright": {
"version": "1.46.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.1.tgz",
"integrity": "sha512-oPcr1yqoXLCkgKtD5eNUPLiN40rYEM39odNpIb6VE6S7/15gJmA1NzVv6zJYusV0e7tzvkU/utBFNa/Kpxmwng==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.46.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.46.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.1.tgz",
"integrity": "sha512-h9LqIQaAv+CYvWzsZ+h3RsrqCStkBHlgo6/TJlFst3cOTlLghBQlJwPOZKQJTKNaD3QIB7aAVQ+gfWbN3NXB7A==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.41", "version": "8.4.41",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz",
@ -3752,39 +3913,35 @@
} }
}, },
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "2.7.1", "version": "2.4.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==",
"deprecated": "Rimraf versions prior to v4 are no longer supported", "deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC", "license": "ISC",
"optional": true,
"dependencies": { "dependencies": {
"glob": "^7.1.3" "glob": "^6.0.1"
}, },
"bin": { "bin": {
"rimraf": "bin.js" "rimraf": "bin.js"
} }
}, },
"node_modules/rimraf/node_modules/glob": { "node_modules/rimraf/node_modules/glob": {
"version": "7.2.3", "version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==",
"deprecated": "Glob versions prior to v9 are no longer supported", "deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC", "license": "ISC",
"optional": true,
"dependencies": { "dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4", "inflight": "^1.0.4",
"inherits": "2", "inherits": "2",
"minimatch": "^3.1.1", "minimatch": "2 || 3",
"once": "^1.3.0", "once": "^1.3.0",
"path-is-absolute": "^1.0.0" "path-is-absolute": "^1.0.0"
}, },
"engines": { "engines": {
"node": "*" "node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
@ -3860,6 +4017,13 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/safe-json-stringify": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
"integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
"license": "MIT",
"optional": true
},
"node_modules/sander": { "node_modules/sander": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
@ -3873,6 +4037,42 @@
"rimraf": "^2.5.2" "rimraf": "^2.5.2"
} }
}, },
"node_modules/sander/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sander/node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/sass": { "node_modules/sass": {
"version": "1.77.8", "version": "1.77.8",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz",
@ -4188,9 +4388,9 @@
} }
}, },
"node_modules/svelte": { "node_modules/svelte": {
"version": "4.2.18", "version": "4.2.19",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz", "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.19.tgz",
"integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==", "integrity": "sha512-IY1rnGr6izd10B0A8LqsBfmlT5OILVuZ7XsI0vdGPEvuonFV7NYEUK4dAkm9Zg2q0Um92kYjTpS1CAP3Nh/KWw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -4214,9 +4414,9 @@
} }
}, },
"node_modules/svelte-check": { "node_modules/svelte-check": {
"version": "3.8.5", "version": "3.8.6",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.5.tgz", "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.6.tgz",
"integrity": "sha512-3OGGgr9+bJ/+1nbPgsvulkLC48xBsqsgtc8Wam281H4G9F5v3mYGa2bHRsPuwHC5brKl4AxJH95QF73kmfihGQ==", "integrity": "sha512-ij0u4Lw/sOTREP13BdWZjiXD/BlHE6/e2e34XzmVmsp5IN4kVa3PWP65NM32JAgwjZlwBg/+JtiNV1MM8khu0Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -4532,15 +4732,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.1.0", "version": "8.2.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.1.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.2.0.tgz",
"integrity": "sha512-prB2U3jXPJLpo1iVLN338Lvolh6OrcCZO+9Yv6AR+tvegPPptYCDBIHiEEUdqRi8gAv2bXNKfMUrgAd2ejn/ow==", "integrity": "sha512-DmnqaPcML0xYwUzgNbM1XaKXpEb7BShYf2P1tkUmmcl8hyeG7Pj08Er7R9bNy6AufabywzJcOybQAtnD/c9DGw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.1.0", "@typescript-eslint/eslint-plugin": "8.2.0",
"@typescript-eslint/parser": "8.1.0", "@typescript-eslint/parser": "8.2.0",
"@typescript-eslint/utils": "8.1.0" "@typescript-eslint/utils": "8.2.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -4556,9 +4756,9 @@
} }
}, },
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "5.26.5", "version": "6.19.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.6.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "integrity": "sha512-e/vggGopEfTKSvj4ihnOLTsqhrKRN3LeO6qSN/GxohhuRv8qH9bNQ4B8W7e/vFL+0XTnmHPB4/kegunZGA4Org==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -4579,16 +4779,29 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/uuid": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.1", "version": "5.4.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.2.tgz",
"integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==", "integrity": "sha512-dDrQTRHp5C1fTFzcSaMxjk6vdpKvT+2/mIdE07Gw2ykehT49O0z/VHS3zZ8iV/Gh8BJJKHWOe5RjaNrW5xf/GA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.41", "postcss": "^8.4.41",
"rollup": "^4.13.0" "rollup": "^4.20.0"
}, },
"bin": { "bin": {
"vite": "bin/vite.js" "vite": "bin/vite.js"
@ -4890,7 +5103,7 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true, "devOptional": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": { "node_modules/yaml": {

View File

@ -1,24 +1,29 @@
{ {
"name": "fotochallenge", "name": "fotochallenge",
"version": "0.0.5", "version": "0.0.7",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest", "test": "npm run test:integration && npm run test:unit",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check . && eslint .", "lint": "prettier --check . && eslint .",
"format": "prettier --write ." "format": "prettier --write .",
"test:integration": "playwright test",
"test:unit": "vitest"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.46.1",
"@sveltejs/adapter-auto": "^3.0.0", "@sveltejs/adapter-auto": "^3.0.0",
"@sveltejs/adapter-node": "^5.2.0", "@sveltejs/adapter-node": "^5.2.0",
"@sveltejs/kit": "^2.0.0", "@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^3.0.0", "@sveltejs/vite-plugin-svelte": "^3.0.0",
"@types/bunyan": "^1.8.11",
"@types/eslint": "^9.0.0", "@types/eslint": "^9.0.0",
"@types/node": "^20.14.11", "@types/node": "^20.14.11",
"@types/uuid": "^10.0.0",
"bulma": "^1.0.1", "bulma": "^1.0.1",
"eslint": "^9.0.0", "eslint": "^9.0.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@ -39,5 +44,9 @@
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">20" "node": ">20"
},
"dependencies": {
"bunyan": "^1.8.15",
"uuid": "^10.0.0"
} }
} }

12
playwright.config.ts Normal file
View File

@ -0,0 +1,12 @@
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
webServer: {
command: 'npm run build && npm run preview',
port: 4173,
},
testDir: 'tests',
testMatch: /(.+\.)?(test|spec)\.[jt]s/,
};
export default config;

View File

@ -1,4 +1,4 @@
{ {
$schema: 'https://docs.renovatebot.com/renovate-schema.json', $schema: 'https://docs.renovatebot.com/renovate-schema.json',
extends: ['local>renovate-bot/renovate-config'] extends: ['local>renovate-bot/renovate-config'],
} }

6
src/app.d.ts vendored
View File

@ -9,3 +9,9 @@ declare global {
// interface Platform {} // interface Platform {}
} }
} }
declare namespace App {
interface Locals {
requestId: string;
}
}

43
src/hooks.server.ts Normal file
View File

@ -0,0 +1,43 @@
import { log, timedExecution, requestIdHeader } from '$lib';
import { validate, v7 as uuidv7 } from 'uuid';
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
// use incoming requestId, if it is a valid uuid, else generate one
const reqIdFromRequest = event.request.headers.get(requestIdHeader);
const { requestId, fromRequest } =
reqIdFromRequest && validate(reqIdFromRequest)
? { requestId: reqIdFromRequest, fromRequest: true }
: { requestId: uuidv7(), fromRequest: false };
const context = {
requestId,
route: event.route.id,
method: event.request.method,
userAgent: event.request.headers.get('user-agent'),
clientIP: event.getClientAddress(),
};
if (fromRequest) {
log.trace(context, 'using incoming request-id');
}
// make requestId available to handlers
event.locals.requestId = requestId;
const { executionTime, result: response } = await timedExecution(async () => {
return await resolve(event);
});
response.headers.set(requestIdHeader, requestId);
log.info(
{
executionTime: `${executionTime}ms`,
status: response.status,
size: response.headers.get('content-length'),
...context,
},
'finished request'
);
return response;
};

View File

@ -17,4 +17,16 @@ describe('safe path', () => {
it('accept happy path', () => { it('accept happy path', () => {
expect(safePath('./uplodas', 'foobar')).toBe(true); expect(safePath('./uplodas', 'foobar')).toBe(true);
}); });
it('accept names starting with `..`', () => {
expect(safePath('./uplodas', '..foobar')).toBe(true);
});
it('accept names ending with `..`', () => {
expect(safePath('./uplodas', 'foobar..')).toBe(true);
});
it('accept names starting and ending with `..`', () => {
expect(safePath('./uplodas', '..foobar..')).toBe(true);
});
}); });

View File

@ -1,5 +1,13 @@
// place files you want to import through the `$lib` alias in this folder. // place files you want to import through the `$lib` alias in this folder.
import path from 'path'; import path from 'path';
import bunyan from 'bunyan';
import type { MaybePromise } from '@sveltejs/kit';
export const log = bunyan.createLogger({
name: 'fotochallenge',
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
src: true,
});
function safePath(basePath: string, name: string): boolean { function safePath(basePath: string, name: string): boolean {
const fullPath = `${basePath}/${name}`; const fullPath = `${basePath}/${name}`;
@ -7,9 +15,9 @@ function safePath(basePath: string, name: string): boolean {
return ( return (
!!relative && !!relative &&
// does move out of `basePath` // does move out of `basePath`
!relative.startsWith('..') && !relative.startsWith(`..${path.sep}`) &&
// exactly one layer deep, e.g. no `./uplodas/foo/bar` // exactly one layer deep, e.g. no `./uplodas/foo/bar`
!relative.includes('/') && !relative.includes(path.sep) &&
// result is not an absolute path // result is not an absolute path
!path.isAbsolute(relative) !path.isAbsolute(relative)
); );
@ -17,8 +25,20 @@ function safePath(basePath: string, name: string): boolean {
const defaultPath: string = './uploads'; const defaultPath: string = './uploads';
if (!('STORAGE_PATH' in process.env)) { if (!('STORAGE_PATH' in process.env)) {
console.warn(`'STORAGE_PATH' environment variable is not set. Defaulting to ${defaultPath}`); log.warn(`'STORAGE_PATH' environment variable is not set. Defaulting to ${defaultPath}`);
} }
export const storagePath: string = process.env.STORAGE_PATH ?? defaultPath; export const storagePath: string = process.env.STORAGE_PATH ?? defaultPath;
export const requestIdHeader = 'x-request-id';
export default safePath; export default safePath;
export async function timedExecution<T>(
fn: () => MaybePromise<T>
): Promise<{ executionTime: number; result: T }> {
const start = process.hrtime();
const result = await fn();
const end = process.hrtime(start);
const executionTime = (end[0] * 1e6 + end[1]) / 1e6;
return { executionTime, result };
}

View File

@ -1,7 +1,7 @@
import { writeFileSync, mkdirSync, existsSync } from 'fs'; import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { fail } from '@sveltejs/kit'; import { fail } from '@sveltejs/kit';
import type { RequestEvent } from './$types'; import type { RequestEvent } from './$types';
import safePath, { storagePath } from '$lib'; import safePath, { storagePath, log } from '$lib';
import { hash } from 'crypto'; import { hash } from 'crypto';
import path from 'path'; import path from 'path';
@ -12,34 +12,44 @@ const mkdirIfNotExists = (path: string) => {
}; };
export const actions = { export const actions = {
default: async ({ request }: RequestEvent) => { default: async ({ request, locals }: RequestEvent) => {
let context: { [key: string]: string | string[] } = { requestId: locals.requestId };
const data = await request.formData(); const data = await request.formData();
const formFiles = data.getAll('files'); const formFiles = data.getAll('files');
if (!formFiles) { if (!formFiles) {
log.debug(context, 'missing files');
return fail(400, { field: 'files', files: formFiles, missing: true }); return fail(400, { field: 'files', files: formFiles, missing: true });
} else if (!(formFiles as File[])) { } else if (!(formFiles as File[])) {
log.debug(context, 'invalid files');
return fail(400, { field: 'files', files: formFiles, incorrect: true }); return fail(400, { field: 'files', files: formFiles, incorrect: true });
} }
const files = formFiles as File[]; const files = formFiles as File[];
console.log(files); const fileNames = files.map((file) => file.name);
context = { fileNames, ...context };
if (files.length === 0) { if (files.length === 0) {
log.debug(context, 'empty files');
return fail(400, { field: 'files', files: formFiles, empty: true }); return fail(400, { field: 'files', files: formFiles, empty: true });
} }
const formName = data.get('name'); const formName = data.get('name');
if (!formName) { if (!formName) {
log.debug(context, 'missing name');
return fail(400, { field: 'name', name: formName, missing: true }); return fail(400, { field: 'name', name: formName, missing: true });
} else if (!(formName as string)) { } else if (!(formName as string)) {
log.debug(context, 'invalid name');
return fail(400, { field: 'name', name: formName, incorrect: true }); return fail(400, { field: 'name', name: formName, incorrect: true });
} }
const name = formName as string; const name = formName as string;
context = { name, ...context };
if (!safePath(storagePath, name)) { if (!safePath(storagePath, name)) {
log.warn(context, 'Supplied name would cause dir traversal. Rejecting...');
return fail(400, { field: 'name', name: name, incorrect: true }); return fail(400, { field: 'name', name: name, incorrect: true });
} }
// const name = safePath(formName as string);
log.info(context, 'Uploading files');
files.forEach(async (file) => { files.forEach(async (file) => {
const outPath = `${storagePath}/${name}`; const outPath = `${storagePath}/${name}`;
@ -47,17 +57,19 @@ export const actions = {
const ext = path.extname(file.name); const ext = path.extname(file.name);
mkdirIfNotExists(outPath); mkdirIfNotExists(outPath);
const filename = hash('sha1', content); const filename = `${hash('sha1', content)}${ext}`;
const fullPath = `${outPath}/${filename}${ext}`; const fullPath = `${outPath}/${filename}`;
context = { file: fullPath, ...context };
if (existsSync(fullPath)) { if (existsSync(fullPath)) {
console.warn(`${fullPath} has already been uploaded. Skipping...`); log.debug(context, 'File has already been uploaded. Skipping...');
} else { } else {
log.debug(context, 'saving file');
writeFileSync(fullPath, Buffer.from(await file.arrayBuffer()), { flag: 'a+' }); writeFileSync(fullPath, Buffer.from(await file.arrayBuffer()), { flag: 'a+' });
} }
}); });
return { return {
success: true success: true,
}; };
} },
}; };

View File

@ -13,7 +13,7 @@
const siPrefixes = new Map([ const siPrefixes = new Map([
[1_000_000, 'M'], [1_000_000, 'M'],
[1_000, 'k'] [1_000, 'k'],
]); ]);
const fileSize = (files: FileList) => { const fileSize = (files: FileList) => {
const size = Array.from(files) const size = Array.from(files)

View File

@ -0,0 +1,50 @@
import { storagePath } from '$lib';
import { json } from '@sveltejs/kit';
import { stat, access, constants } from 'fs/promises';
import type { RequestHandler } from './$types';
type Status = 'OK' | 'ERROR';
type Result = { status: Status; checks: Checks };
type Checks = { [key: string]: CheckResult };
type CheckResult = true | string;
const fileExists = async (path: string) => !!(await stat(path).catch(() => false));
const isDirectory = async (path: string) =>
!!(await stat(path)
.then((s) => s.isDirectory())
.catch(() => false));
const isWritable = async (path: string) =>
await access(path, constants.W_OK)
.then(() => true)
.catch(() => false);
const checkStoragePath = async (path: string): Promise<CheckResult> => {
if (!fileExists(path)) {
return '`STORAGE_PATH` does not exist';
} else if (!isDirectory(path)) {
return '`STORAGE_PATH` is not a directory';
} else if (!isWritable(path)) {
return '`STORAGE_PATH` is not writable';
}
return true;
};
export const GET: RequestHandler = async () => {
const storagePathResult = await checkStoragePath(storagePath);
const checks: Checks = {
storagePath: storagePathResult,
};
const healthy = Object.values(checks)
.map((r) => (r === true ? true : false))
.reduce((prev, next) => prev && next, true);
const status = healthy ? 'OK' : 'ERROR';
const result: Result = { status, checks };
const httpStatus = healthy ? 200 : 500;
return json(result, { status: httpStatus, headers: { healthy: healthy.toString() } });
};

View File

@ -1,12 +1,12 @@
import { storagePath } from '$lib'; import { storagePath } from '$lib';
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import { readdirSync, statSync } from 'fs'; import { readdirSync, statSync } from 'fs';
import type { RequestHandler } from './$types';
export function GET() { export const GET: RequestHandler = () => {
const names = readdirSync(storagePath).filter((f) => const names = readdirSync(storagePath)
statSync(`${storagePath}/${f}`).isDirectory() .filter((f) => statSync(`${storagePath}/${f}`).isDirectory())
); .toSorted();
names.sort();
return json(names); return json(names);
} };

View File

@ -11,8 +11,8 @@ const config = {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported, or you settled on a specific environment, switch out the adapter. // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter() adapter: adapter(),
} },
}; };
export default config; export default config;

13
tests/health.test.ts Normal file
View File

@ -0,0 +1,13 @@
import { expect, test } from '@playwright/test';
test('healthy test', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('health');
await expect(response.status()).toBe(200);
await expect(response.headers()).toHaveProperty('healthy');
await expect(response.headers()['healthy']).toBe('true');
const body = await response.json();
await expect(body).toHaveProperty('status');
await expect(body['status']).toBe('OK');
});

10
tests/page.test.ts Normal file
View File

@ -0,0 +1,10 @@
import { expect, test } from '@playwright/test';
test('contains header text', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('');
await expect(response.status()).toBe(200);
const body = (await response.body()).toString();
await expect(body).toContain('Gabi und Hannes Fotochallenge');
});

37
tests/requestid.test.ts Normal file
View File

@ -0,0 +1,37 @@
import { expect, test } from '@playwright/test';
import { validate, NIL } from 'uuid';
import { requestIdHeader } from '$lib';
test('response contains request-id header', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('health');
const headers = response.headers();
await expect(headers).toHaveProperty(requestIdHeader);
});
test('request-id is valid uuid', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('health');
const headers = response.headers();
const requestId = headers[requestIdHeader];
await expect(validate(requestId)).toBe(true);
});
test('reuse valid incoming uuid', async ({ playwright }) => {
const context = await playwright.request.newContext();
const response = await context.get('health', { headers: { [requestIdHeader]: NIL } });
const headers = response.headers();
const requestId = headers[requestIdHeader];
await expect(requestId).toBe(NIL);
});
test('ignore invalid incoming uuid', async ({ playwright }) => {
const invalid = '00000000-0000-0000-0000-00000000000z';
const context = await playwright.request.newContext();
const response = await context.get('health', { headers: { [requestIdHeader]: invalid } });
const headers = response.headers();
const requestId = headers[requestIdHeader];
await expect(requestId).not.toBe(invalid);
});

View File

@ -5,14 +5,14 @@ export default defineConfig({
plugins: [sveltekit()], plugins: [sveltekit()],
test: { test: {
include: ['src/**/*.{test,spec}.{js,ts}'] include: ['src/**/*.{test,spec}.{js,ts}'],
}, },
css: { css: {
preprocessorOptions: { preprocessorOptions: {
scss: { scss: {
additionalData: '@use "src/variables.scss" as *;' additionalData: '@use "src/variables.scss" as *;',
} },
} },
} },
}); });