Skip to content

CLI TypeScript migration#2802

Closed
Harjun751 wants to merge 62 commits intoMarkBind:masterfrom
Harjun751:cli-typescript-migration
Closed

CLI TypeScript migration#2802
Harjun751 wants to merge 62 commits intoMarkBind:masterfrom
Harjun751:cli-typescript-migration

Conversation

@Harjun751
Copy link
Contributor

@Harjun751 Harjun751 commented Jan 25, 2026

What is the purpose of this pull request?

  • Documentation update
  • Bug fix
  • Feature addition or enhancement
  • Code maintenance
  • DevOps
  • Improve developer experience
  • Others, please explain:

Overview of changes:
Bear with me

Partially completes #2754

  • Migrate functional code in CLI package to typescript

    • index.js and all util files converted to typescript
    • live-server patch is still JavaScript - added type definitions for now.
    • All test files are still JavaScript
    • Add typescript configs in the cli package - generated files go to cli/dist/
  • Package changes

    • Modified cli/package.json to properly prepare and publish the markbind-cli package.
    • Modified root package.json to use the built CLI module in cli/dist/
    • Updated selected packages to get built-in type definitions
    • Installed some type definitions
  • Developer-facing changes

    • Added a build/dev script in cli/package.json
    • Updated root build/dev scripts to utilize Project References (running the build/dev scripts are functionally the same as a developer)
    • Added tsx as a dev dependency - run TypeScript files directly using the bundled runner
    • Update dev docs on modified setup instructions & on how to use tsx to directly run files

Anything you'd like to highlight/discuss:
The migrated code is TypeScript, but it's not good TypeScript. There's liberal use of any, so it could definitely be improved. I think we could do refactoring as a separate PR, as I didn't want to clutter this PR any further. LMK, though.

Suggested things to refactor:
  1. Refactor core Site constructor. The CLI was using the constructor without properly specifying all the arguments - create an overloaded constructor for this?

  2. TypeScript 4.4 set the error type of catch variable to unknown. This means that to extract the error message, type narrowing was required. This introduces an extra branch and decision - what to log when the type of error is unknown? Currently, it simply logs "unknown error". Suggest: extract a variable/function that can be re-used in these cases, instead of hard-coding a message.

  3. Create an interface/type that can be used for the option parameter of the commander package callbacks

  4. Generally improve logger re-use in the cmd/ files - a lot of code duplication.

  5. Create an interface/type that can be used for the ServerConfig object in serve.ts

  6. Decrease the high level of coupling between the CLI and Core package

I chose tsx over ts-node since tsx seems more actively-maintained (the last release for ts-node was 3 years ago). Furthermore, tsx seems generally faster, and I had a good experience using tsx with Webstorm with zero configuration or issues. If there's another option worth considering, feel free to raise it up.

In terms of documentation, I did not add an entry on how to configure VSCode with tsx to run TypeScript code directly with, as I don't use VSC. If you think it's valuable, please do add it in!

Testing instructions:

⚠️⚠️⚠️ HELP ME OUT HERE ⚠️⚠️⚠️

Test out that npm link works with the new, TypeScript version of markbind-cli.
For your convenience (adapt as necessary):

  1. Clone this PR: git fetch upstream pull/2802/head:arjunscoolpr
  2. Check out: git checkout arjunscoolpr
  3. Uninstall markbind-cli if present: npm uninstall -g markbind-cli
  4. Install updated project dependencies: npm run setup
  5. Build the project in the root directory: npm run build:backend
  6. Navigate to packages/cli and link: npm link

Test out that markbind-cli works by running any command. Run npm run clean in the root directory. Check if the markbind-cli works. Now, build the project again and check if the markbind-cli works.

I checked this with @MoshiMoshiMochi on Windows, and it looks to work as expected. I'm having some issues on Linux (odd permission issues - I could have just borked my setup so YMMV), so I would like to see if it works on other devices. Thanks in advance.

Additionally, I would appreciate a sanity check on the updated root package.json. I updated the path of the CLI source, so everything should be working fine. I tested running the deploy scripts - it errored out after building the site due to the missing tokens so I think it works fine.

Proposed commit message: (wrap lines at 72 characters)

Migrate functional code in CLI package to TypeScript


Checklist: ☑️

  • Updated the documentation for feature additions and enhancements
  • Added tests for bug fixes or features
  • Linked all related issues
  • No unrelated changes

Reviewer checklist:

Indicate the SEMVER impact of the PR:

  • Major (when you make incompatible API changes)
  • Minor (when you add functionality in a backward compatible manner)
  • Patch (when you make backward compatible bug fixes)

At the end of the review, please label the PR with the appropriate label: r.Major, r.Minor, r.Patch.

Breaking change release note preparation (if applicable):

  • To be included in the release note for any feature that is made obsolete/breaking

Give a brief explanation note about:

  • what was the old feature that was made obsolete
  • any replacement feature (if any), and
  • how the author should modify his website to migrate from the old feature to the replacement feature (if possible).

build:backend uses `tsc --noEmitOnError` to build the project.

This causes built files to be placed directly next to .ts files in
all projects, as tsc uses the tsconfig in the root of the project.
(refer to
https://stackoverflow.com/questions/64626846/typescript-tsc-not-picking-up-tsconfig-json-inside-a-subdirectory)

We can update tsconfigs with project references to achieve this
functionality, but let's leverage lerna for simplicity instead.

This way, we use the sub-modules' already-defined `build` scripts
to build the projects in a consistent way.
Scripts in root package.json refer to index.js in cli/ package.

As part of TS migration, update scripts to refer to
the index.js that is created after building the project.

Add pre- scripts that build the project to ensure that
the latest index.js file is used whenever
a script is run. Extract cli path to a variable.
Typescript migration caused a regression where all
debug logs were being printed to console. This is likely
not intended behaviour as users of the cli
would not want to see build output be cluttered

Update cli console logger level to 'info' to retain
expected output when using markbind-cli
@Harjun751
Copy link
Contributor Author

Alright, updated the setting up workflow to something that's more sane. So npm run setup should build all relevant projects by default now.

@gerteck - let me know if there's any other issues. If all good I'll then focus on making a cleaned-up PR. No rush at all and thanks.

Copy link
Member

@gerteck gerteck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this @Harjun751

Preliminary glance at this seems okay! You can go ahead to work on another PR, but I suspect that it will still have some changes here and there as well.

Additionally, could you do some investigation into

#1934

I wonder if it is relevant? Or was it some bug that has been patched, or why it was originally not suitable.

"exclude": ["**/node_modules", "**/*.test.ts"],
// Prevent just `tsc` from building files - always use `tsc --build`
"files": [],
"references": [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, does this build them in correct dependency order?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, using the --build option resolves the referenced dependencies in the proper order.

function printHeader() {
logger.logo();
logger.log(` v${CLI_VERSION}`);
return '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a very band-aid fix since commander actually expects a string return value for the addHelpText function call. That is, we weren't really using it properly haha.

I could extract the logo print from the logger, but I felt it'd probably be better to address this in a PR specifically targeted to refactor/improve this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it! in that case maybe open an issue to track it jsut to rmb

@Harjun751
Copy link
Contributor Author

Additionally, could you do some investigation into

#1934

I wonder if it is relevant? Or was it some bug that has been patched, or why it was originally not suitable.

Alright, I'll do some proper investigation. But on a preliminary check, if this issue still exists we could reasonably circumvent this by removing the tsconfig.tsbuildinfo file in the clean script (which I've set it to do).

@Harjun751
Copy link
Contributor Author

TL;DR
The issue was that tsc --build does not build files if a tsconfig.tsbuildinfo is present, which might mean out-of-sync builds in rare cases.

Upon investigation,

  • This issue will never occur if using tsx
  • This issue will never occur if the git hooks are enabled
  • There is a tiny chance that this issue occurs when not using the git hooks. It can be easily fixed by running npm run clean or npm run setup. It will only occur if built files are deleted while the tsconfig.tsbuildinfo is preserved somehow.

Dev team concerns

tsc can't detect deleted built files

Essentially, if you build a project and you delete a generated .js file, running tsc --build won't create it if the tsbuildinfo file exists.

I don't think this is a problem that occurs much - when would you actually do this? The main concern was when switching branches, "some generated .js files from the typescript files may get removed". However, since all the generated files are ignored, I believe git would preserve them. There seems to be a fringe case in which if branch A doesn't ignore a file and branch B does, switching from A to B with that ignored file in your directory deletes it. However, we've already did a blanket-ignore on built files, so this should be really rare.

In any case, our pre- hooks are quite cautious now. Since the clean scripts (which remove the file) are triggered on switching branches, this issue will not occur if the pre- hooks are installed.

Orphaned Files

When a file gets renamed/deleted, running tsc --build --clean fails to detect the orphaned build artifacts, so it won't delete them.

This is still true, but I believe it's a non-issue due to the npm run clean script being able to do this.

This wouldn't be a problem if a dedicated dist/ folder was used. This was investigated before, though, and it seems that the reason it wasn't done was because non-ts files are not able to be copied over using tsc. (see here)

Further tests

I checked if tsc --build can detect file changes when switching branches, without the hooks. It can, and running with --watch is able to do this too. So in a normal workflow, switching branches should still keep your built files up-to-date. Using tsx does not cause this issue, since it doesn't use the built files.

Steps I took to test this:
  1. Disable git hooks
  2. Create new branch from current cli-typescript-migration
  3. Add a console.log("AHHHHHHHHHHHHHHHHHHHH") in core Site constructor
  4. Run tsc --build to generate the files
  5. Run serve command using the built .js files -> observe "AHHHHHH" being logged to console
  6. Switch back to cli-typescript-migration
  7. Run serve command using the same built .js files. "AHHHH" is printed. Expected since the project was not built yet.
  8. Run tsc --build
  9. Run serve command with built .js files. "AHHHHH" is not printed, even though the tsconfig.tsbuildinfo file still exists.

Similarly, I tested with the -watch.

istextorbinary developer dependency uses an old version
without typescript type definitions.

As part of migration of tests to typescript, the files
requiring istextorbinary now need type definitions.

Let's update istextorbinary to the latest version.

No breaking changes were introduced that affected the
usage of istextorbinary in the code.
To facilitate TypeScript migration, update `diff`
package to get in-built type declarations.

No breaking changes related to current usage
introduced.
test.ts uses the exported testSites to iterate
and index as a dict to reference built files for
checking.

Add an interface to explictly show that
the object may be indexed via a string to obtain
files, allowing for stricter type checks.
With migrated test files, eslint does not detect that
functional test files are tests, resulting in
raising "no-extraneous-dependencies" error.

This error suggests that dependencies used in the actual
application and not test harnesses/files should be in
the main dependencies section.

However, since these are test files, the dependencies
should rightfully be in `devDependencies`.

Hence, let's add an rule exclusion for
import/no-extraneous-dependencies for the functional
tests.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 209 out of 272 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

packages/cli/src/util/logger.ts:27

  • The winston logger configuration is removing deprecated options humanReadableUnhandledException and showLevel from DailyRotateFile transport. However, no migration notes or warnings are provided. If these options were being used for specific logging behavior, their removal could affect log output format or exception handling without proper documentation of the changes.
    packages/cli/src/util/logger.ts:53
  • The logger is now using named exports from '@markbind/core/src/utils/logger', but also defining local log and logo exports. The log function is a simple wrapper around console.log that adds a type annotation for the message parameter. However, the type is too restrictive - console.log accepts any type, not just strings. This could cause type errors when trying to log non-string values. Consider changing the type to any or accepting multiple arguments like console.log does.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +38 to +39
new Site(rootFolder, outputFolder, '', undefined, options.siteConfig,
false, false, () => {})
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Site constructor calls in deploy.ts and build.ts are passing additional parameters that weren't in the original JavaScript version. Specifically, they're passing empty strings and callback functions as positional parameters. This appears to be a workaround for TypeScript's strict type checking, but it's unclear if these parameters are actually required or if the constructor signature has changed. This should be verified against the actual Site constructor signature to ensure compatibility.

Suggested change
new Site(rootFolder, outputFolder, '', undefined, options.siteConfig,
false, false, () => {})
new Site(rootFolder, outputFolder, options.siteConfig)

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be addressed in the refactor (will open an issue once the migration is merged).

I was preserving the way it was done pre-migration.


# Compiled files from TypeScript
*.js.map
*.d.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hope that this does not affect anything

just to point it out since the commit addes index.d.ts for live-server

# --- packages/core end ---

# Manual type definitions need to be included
!packages/cli/src/lib/live-server/index.d.ts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh this was added

actually if everything gets compiled to dist we could overhaul the gitignore and simplify right

@Harjun751
Copy link
Contributor Author

Moved to #2830

@Harjun751 Harjun751 closed this Feb 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants