Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[🟦] Migrate server-side code to TypeScript #1573

Closed
4 tasks done
bkimminich opened this issue Jan 28, 2021 · 46 comments
Closed
4 tasks done

[🟦] Migrate server-side code to TypeScript #1573

bkimminich opened this issue Jan 28, 2021 · 46 comments

Comments

@bkimminich
Copy link
Member

bkimminich commented Jan 28, 2021

  • Introduce typescript and necesary type packages on backend
  • Rename all code files from .js to .ts
  • Augment npm install process to transpile
  • Change npm start to run server from transpiled JavaScript code

After the above steps typing can be gradually introduced in the codebase.

@paseaf
Copy link
Contributor

paseaf commented Jan 28, 2021

I would like to work on it :)

@bkimminich
Copy link
Member Author

You are more than welcome to! 👍
I'll assign the issue to you! 🎉

@paseaf
Copy link
Contributor

paseaf commented Jan 29, 2021

Hi, I have one questions :)

Rename all code files from .js to .ts

Do "all code files" also include test files?

@bkimminich
Copy link
Member Author

Absolutely, the tests should also be in TypeScript. In the end all code should be TS, so we can have consistent linting rules and programming patterns across frontend, backend and tests.

@paseaf
Copy link
Contributor

paseaf commented Jan 30, 2021

Understood! 👍

@bkimminich
Copy link
Member Author

Hi @paseaf! How's it going? Did you make some progress? Anything you need help with?

@paseaf
Copy link
Contributor

paseaf commented Feb 3, 2021

@bkimminich Hi! I've already installed and set up typescript, and am currently in the middle of migrating /test/**/*.js files to .ts. I've created a branch zl/ts-migrate_test zl/ts-migrate_v3 zl/ts-migrate_v4 zl/ts-migrate_v5.

I do have a question, which scripts should be passed locally for the migration? I'm currently checking test:server, e2e, and frisby for each step, but not sure if they are all I need to care about.

@bkimminich
Copy link
Member Author

Wow, that sounds great already! Yeah, those test scripts are exactly the ones that run on server side.

@paseaf
Copy link
Contributor

paseaf commented Feb 9, 2021

Help wanted
I just changed almost all require/module.exports to typescript syntax import/export to avoid modules being imported to the global scope.
But doing this has caused an error when running npm frisby:

/juice-shop/lib/startup/registerWebsocketEvents.ts:6
import { notifications, challenges } from '../../data/datacache'
^^^^^^

SyntaxError: Cannot use import statement outside a module

I have tried all fixes on Google's first page but none has worked.
Posting here in case anyone knows how to solve it.
Here is the branch: https://github.com/paseaf/juice-shop/tree/zl/ts-migrate_v3

I will try something different to avoid global import if it cannot be solved easily.

@J12934
Copy link
Member

J12934 commented Feb 9, 2021

@paseaf I'm no expert in this typescript / module stuff, but the problem seems to be that the registerWebsocketEvents itself is loaded via require and can therefore not use the modern import syntax. (https://www.typescriptlang.org/docs/handbook/modules.html#export--and-import--require)

https://github.com/paseaf/juice-shop/blob/zl/ts-migrate_v3/server.ts#L597

I've changed the way to the following and that seems to have fixed the error.
Though I dont think this dynamic import() here is really necessary.
Looks to me as if we can declare that to beforehand.

import('./lib/startup/registerWebsocketEvents').then(
  ({ default: registerWebsocketEvents }) => {
    registerWebsocketEvents(server);
  }
)

@paseaf
Copy link
Contributor

paseaf commented Feb 10, 2021

@J12934 You are right! 👍 Problem is solved after changing the syntax there.

@paseaf
Copy link
Contributor

paseaf commented Feb 15, 2021

Hey @bkimminich, I have some questions. 👋

Current Progress [branch: zl/ts-migrate_v4]

  1. Introduced typescript and installed necessary types packages
  2. Renamed almost all .js files to .ts. (with one exception lib/insecurity.js mentioned in question 1 below)
  3. Passed all test:server and frisby tests directly on .ts files with ts-node
  4. Passed e2e tests on .ts files with the same results as in develop branch (Ran 231 of 238 specs
    231 specs, 62 failures, 3 pending specs)
  5. Augmented npm install process to transpile
  6. Changed npm start to run server from transpiled JavaScript code
  7. Changed npm run serve to run app with ts-node app.ts

Questions:

  1. Problem: npm run frisby fails once lib/insecurity.js is changed to .ts. I tried to change all module import/export syntax to typescript compliant but couldn't fix it.
    Failed suites: test/api/dataExportApiSpec.ts, test/api/basketApiSpec.ts, and test/api/basketApiSpec.ts .
    Question: Is it fine to leave lib/insecurity.js as .js and migrate it at a later time?
  2. Question: Should tests also be executed from the compiled directory, or directly on .ts files with ts-node? I assume that the former may be more suitable for production, and the latter for development, but I'm not sure.
  3. Question: Is my e2e test result mentioned above expected?
  4. Question: Are there any other config files that should be updated, for example Gruntfile.js?

@bkimminich
Copy link
Member Author

Impressive! 👍 I'll check out your fork in the next days and see if I can reproduce the e2e and insecurity behavior. 👀

@bkimminich
Copy link
Member Author

I'm running into an error with npm install for my fresh checkout of your fork:

> juice-shop@12.6.0-SNAPSHOT build:server C:\Data\GitHub\paseaf\juice-shop
> tsc; copyfiles -s "config/**/*" "data/**/*" "encryptionkeys/**/*" "frontend/dist/**/*" "ftp/**/*" "i18n/**/*" "uploads/**/*" "views/**/*" config.schema.yml package.json ./dist/

error TS5023: Unknown compiler option '-s'.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! juice-shop@12.6.0-SNAPSHOT build:server: `tsc; copyfiles -s "config/**/*" "data/**/*" "encryptionkeys/**/*" "frontend/dist/**/*" "ftp/**/*" "i18n/**/*" "uploads/**/*" "views/**/*" config.schema.yml package.json ./dist/`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the juice-shop@12.6.0-SNAPSHOT build:server script.

@paseaf
Copy link
Contributor

paseaf commented Feb 17, 2021

I'm running into an error with npm install for my fresh checkout of your fork:

> juice-shop@12.6.0-SNAPSHOT build:server C:\Data\GitHub\paseaf\juice-shop
> tsc; copyfiles -s "config/**/*" "data/**/*" "encryptionkeys/**/*" "frontend/dist/**/*" "ftp/**/*" "i18n/**/*" "uploads/**/*" "views/**/*" config.schema.yml package.json ./dist/

error TS5023: Unknown compiler option '-s'.
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! juice-shop@12.6.0-SNAPSHOT build:server: `tsc; copyfiles -s "config/**/*" "data/**/*" "encryptionkeys/**/*" "frontend/dist/**/*" "ftp/**/*" "i18n/**/*" "uploads/**/*" "views/**/*" config.schema.yml package.json ./dist/`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the juice-shop@12.6.0-SNAPSHOT build:server script.

Sorry, I didn't know Windows doesn't have ; operator.
I just pushed an update. I tested install, start, frisby, test:server, and e2e scripts on a 64-bit Windows 10 machine with Node v14.15.5. Hope it works now. :D

@paseaf
Copy link
Contributor

paseaf commented Feb 17, 2021

Impressive! 👍 I'll check out your fork in the next days and see if I can reproduce the e2e and insecurity behavior. 👀

I just realized that I could compare the compiled code with the code before migration to find the problem. I'll check it and see what I can find 👍

@bkimminich
Copy link
Member Author

This is what I now get on my Windows 10 with Node 14:

data/datacreator.ts(24,26): error TS7006: Parameter 'file' implicitly has an 'any' type.
data/datacreator.ts(62,29): error TS7031: Binding element 'name' implicitly has an 'any' type.
data/datacreator.ts(62,35): error TS7031: Binding element 'category' implicitly has an 'any' type.
...
<hundreds of errors like this>
...
test/server/verifySpec.ts(303,9): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(305,32): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(305,42): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(305,52): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(312,9): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(314,32): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(314,42): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(314,52): error TS2532: Object is possibly 'undefined'.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! juice-shop@12.6.0-SNAPSHOT build:server: `tsc`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the juice-shop@12.6.0-SNAPSHOT build:server script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

@paseaf
Copy link
Contributor

paseaf commented Feb 17, 2021

This is what I now get on my Windows 10 with Node 14:

data/datacreator.ts(24,26): error TS7006: Parameter 'file' implicitly has an 'any' type.
data/datacreator.ts(62,29): error TS7031: Binding element 'name' implicitly has an 'any' type.
data/datacreator.ts(62,35): error TS7031: Binding element 'category' implicitly has an 'any' type.
...
<hundreds of errors like this>
...
test/server/verifySpec.ts(303,9): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(305,32): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(305,42): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(305,52): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(312,9): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(314,32): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(314,42): error TS2532: Object is possibly 'undefined'.
test/server/verifySpec.ts(314,52): error TS2532: Object is possibly 'undefined'.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! juice-shop@12.6.0-SNAPSHOT build:server: `tsc`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the juice-shop@12.6.0-SNAPSHOT build:server script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

These are expected type checking errors (because we only renamed the file without really adding any type annotations), and they shouldn't stop Typescript from compiling the code. I thought the current issue was only to rename the files without fixing type errors. But seems we should suppress these errors to avoid confusion.

@bkimminich
Copy link
Member Author

I'm totally fine with those warnings showing up, don't get me wrong. I just didn't expect the compiling to fail, and it seems it worked for you.

@paseaf
Copy link
Contributor

paseaf commented Feb 17, 2021

I'm totally fine with those warnings showing up, don't get me wrong. I just didn't expect the compiling to fail, and it seems it worked for you.

Does npm start work for you?
(I'm asking because I assume I have the same number of errors (Found 2713 errors) as you, but the code is still compiled and runnable on my machine.)

@paseaf
Copy link
Contributor

paseaf commented Feb 17, 2021

I just pushed an update to suppress the npm error. Hope it works now. :)
Seems it's still there. I'll try again and let you know.

@paseaf
Copy link
Contributor

paseaf commented Feb 18, 2021

The error was because npm run <script> will emit an error if the executed script ("build:server": tsc in this situation) emits any.

I found some trick to suppress the error for both Windows and Linux.

- "postinstall": cd frontend && npm install && cd .. && npm run build:frontend && npm run copy-static-files && npm run build:server
+ "postinstall": cd frontend && npm install && cd .. && npm run build:frontend && npm run copy-static-files && (npm run --silent build:server || cd .)

The update is pushed, and I hope you don't see any npm errors for npm install anymore. (Sorry for the mess😆)

Another question regarding path resolving: (Solved with a third approach)
My current postinstall script copies /frontend/dist to the ts compiled folder /dist/frontend/dist with copyfiles. But I just realized that a very small portion of fs functions such as fs.copyFileSync() are not using relative path path.resolve(__dirname, 'some/path').
This could lead to operating on /dist/frontend/dist for some code and /frontend/dist for others.

I can see two solutions:

  1. Update all fs operations to use relative paths with path.resolve or path.join (one example: https://github.com/paseaf/juice-shop/commit/581592911ac7d3d7fdb16fcaf7084dd02ee0f668)
  2. Wrap server code to a /src folder, and still compile to /dist. This requires updating all relevant paths. But we don't have to duplicate the /frontend/dist and other files, and it also seems safer to me. But I'm not sure which files should be moved to /src and how doable it is. (I could just try it out though)

I would prefer the 2nd solution but I'm open to any other solutions. :)

@bkimminich
Copy link
Member Author

I agree that 2.) sounds a lot cleaner, because fiddling with the frontend can be avoided and it kind of leaves server-side JS files where they are today.

I'm currently testing, compiling works now for me. npm test also passes. I'll check if the other tests work as is and then play with insecurity.js to see if I can reproduce the issues you ran into.

@bkimminich
Copy link
Member Author

Frisby tests pass for me with current version, Protractor suite also passed completely for me. So, that's all 👍! I'll check the insecurity.js issues next.

@bkimminich
Copy link
Member Author

Okay, I can confirm 10 frisby tests are failing once I rename insecurity.js into .ts. Seems that they all run into 401 errors where other status codes are expected, so I guess the prepared auth token somehow don't work in the tests?

@paseaf
Copy link
Contributor

paseaf commented Feb 19, 2021

Could be. I will look into it in the next days. 👍

@bkimminich
Copy link
Member Author

Hi @paseaf, if you have some new version for us to test locally, you'll ping us, right? You can of course also just open a PR and we'll test it that way (also our CI/CD will have a chance to take a look) but I'm also happy to do some testing rounds before. The last version looked really promising already, so I hope you didn't throw the towel or anything... 😁

@paseaf
Copy link
Contributor

paseaf commented Mar 1, 2021

Hi @bkimminich, sorry for the delay. I failed to figure out the issue regarding insecurity.ts last week, and then got a bit busy with other stuff (exam season :/) and forgot about the issue. Is it ok if I only move the server-side code into a /src folder for the PR, and maybe someone could help figure out why renaming insecurity breaks tests? Otherwise I would love to continue digging into the insecurity renaming issue, but it could take one or two more weeks...

@bkimminich
Copy link
Member Author

I think it‘s perfectly fine to keep insecurity.js and fix it later. The move into src would be really good to have, on the other hand. Good luck with your exams!

@paseaf
Copy link
Contributor

paseaf commented Mar 1, 2021

I suppose there might be some interactions between the test cases which I don't yet understand. (maybe due to updating the module syntax) Here is something I've tried

  1. Set package.json->jest->testMatch to "<rootDir>/test/api/basket*Spec.ts"
    Result on npm run frisby: both basketApiSpec.ts and basketItemApiSpec.ts have failed tests
  2. Set package.json->jest->testMatch to "<rootDir>/test/api/basketItemApiSpec.ts"
    Result on npm run frisby: All tests in basketItemApiSpec.ts passed.

Just posting here as a note.

@paseaf
Copy link
Contributor

paseaf commented Mar 1, 2021

I think it‘s perfectly fine to keep insecurity.js and fix it later. The move into src would be really good to have, on the other hand. Good luck with your exams!

Great! I'll just move things into /src then. Thanks!! :D

@paseaf
Copy link
Contributor

paseaf commented Mar 1, 2021

Hey @bkimminich, to confirm before moving. 👀
I will move the following files to /src/:

  • /data/*
  • /lib/*
  • /models/*
  • /routes/*
  • /app.ts
  • /server.ts

Am I missing anything, or including any unnecessary files?

@bkimminich
Copy link
Member Author

bkimminich commented Mar 3, 2021

That should be it, yep... Also, very important, please do a rebase with develop before submitting a PR, because there've been quite some changes, including name refactorings which touched dozens of files plus some experimental feature etc.

@paseaf
Copy link
Contributor

paseaf commented Mar 5, 2021

@bkimminich I just realized that we can avoid copying static files to /dist folder by always using paths relative to the project root. This way both source code and compiled code will access the same static files, and creating a src/ folder seems not necessary anymore.
The PR above uses this approach. 🎉

@bkimminich
Copy link
Member Author

Running Juice Shop from source or a packaged distro now works fine on this branch. The only thing having trouble is the Docker image. On CI/CD it fails the smoke tests already, so the build/publish jobs are not even trying to run currently.

@bkimminich
Copy link
Member Author

I guess this is why the Docker tests fail from a missing build/ folder... 😁

sh: 1: tsc: not found

@bkimminich
Copy link
Member Author

Looks like everything's fine now! Smoke tests (I realized these never checked the unpackaged archive but the main folder ever since, so this was a nice catch along the way), Docker smoke tests, the code snippets are still working from source, ...

... anyone wants to do some more tests or specific checks before we merge into develop and 🍾?

@bkimminich
Copy link
Member Author

So, I think all sources should now be in the packaged archives and they should have been in the Dockerfile all along. To be sure I've added a smoke test that retrieves the snippet for one of the backend challenges, which will fail in case the underlying source file is missing.

I guess we really want to merge into develop before tackling the ✖ 1935 problems (0 errors, 1935 warnings), right? Anything else sounds like a trip to rebase-hell to me...

@bkimminich
Copy link
Member Author

@paseaf, please send me an email with your address so I can send some "Thank you!"-package* on its way, as this was a) your first and b) a veeery sophisticated contribution (that even we from core team didn't dare touch until now ourselves...)!!!

⭐⭐⭐⭐⭐! 😀

(*Depending on where you are located I might send you some gift card instead for getting a Juice Shop shirt printed locally or something like that.)

@paseaf
Copy link
Contributor

paseaf commented Mar 20, 2021

I guess we really want to merge into develop before tackling the ✖ 1935 problems (0 errors, 1935 warnings), right? Anything else sounds like a trip to rebase-hell to me...

I agree..
Another solution could be not renaming all .js files to .ts at once for this merge. We can rename files one by one in the future, each time solving all type warnings in the file. This way should be cleaner, because the warnings only show up in .ts files.
I could create a new branch from ts-backend and try it out. (so that we can compare)

@bkimminich
Copy link
Member Author

I just merged it into develop as is. Docker images have been published successfully, Heroku staging environment works. All good. Now it's just this weird insecurity.js issue and gradually changing the code base to actual TS. Can all be done in parallel to normal development afaik.

@paseaf
Copy link
Contributor

paseaf commented Mar 20, 2021

Great!!
I'm surprised there will be a package. That's very sweet of you. Thank you 😊

@J12934
Copy link
Member

J12934 commented Mar 23, 2021

Hey ho 👋
Whats our plan on moving forward on migrating the code to more idiomatic typescript?

I think there are some parts of the code base which are "harder" problem, e.g. the models but would provide massive benefits if we could figure out how to properly type them. Do we want to open up separate tickets for the different parts of the code base, so that we can work on them in a distributed manner? Or are there better ways to do it?

@bkimminich
Copy link
Member Author

I alteady started fixing some of the easier linter errors, like using template strings over concatenation of non-string types with strings.

Not sure if this approach will work out, as there are probably not so many "easy" issues to address.

@bkimminich
Copy link
Member Author

We're down to this list of ESLint complaints:

        '@typescript-eslint/no-misused-promises': 'off', // 97
        '@typescript-eslint/explicit-function-return-type': 'off', // 132
        '@typescript-eslint/restrict-template-expressions': 'off', // 259
        '@typescript-eslint/strict-boolean-expressions': 'off', // 388
        '@typescript-eslint/restrict-plus-operands': 'off', // 496
        '@typescript-eslint/no-var-requires': 'off' // 502

I would have hoped some a auto-fixable - like using template strings over string concatenation - but it seems none of them is, unfortunately.

@github-actions
Copy link

This thread has been automatically locked because it has not had recent activity after it was closed. 🔒 Please open a new issue for regressions or related bugs.

@github-actions github-actions bot locked and limited conversation to collaborators Mar 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants