Skip to content

Implement CLI installer for Windows - #1152

Merged
nightnei merged 6 commits into
trunkfrom
nightnei/windows-cli-installer
Apr 8, 2025
Merged

Implement CLI installer for Windows#1152
nightnei merged 6 commits into
trunkfrom
nightnei/windows-cli-installer

Conversation

@nightnei

@nightnei nightnei commented Mar 28, 2025

Copy link
Copy Markdown
Contributor

Related issues

Proposed Changes

With this PR we are adding the opportunity to use "studio" command globally on Windows.

Notes: Actually, we need to handle the issue with versioning, so I implemented a proxy to handle it.

Why exactly winreg lib was used

  1. Initially I decided to go with setx PATH and handle everything w/o extra library, but long story short - I messed up my environment variable, lost default, github, vscode, etc, so I was installing all apps again to restore PATH. I made changes blindly with it, it's not a reliable way.
  2. Then, I decided to go with registry-js, which was proposed as option in the task.
    2.1 I spent a lot of time installing deps and configuring npm config, and at the end of a day I did it. But, it was feeling, that it's not great that we are complicating Studio dev env, but ok, I decided to proceed.
    2.2 Then it turned out that registry-js doesn't support latest node (18+) and electron, and I also found that GitHub Desktop did revert due to it. It was huge cons for this lib. Moreover the lib wasn't updated for more then 1 year. So it doesn't make sense to proceed with this lib.
  3. Then, I decided that regedit is also good option, since it supports promises and has similar rating in comparison with opponents. But, it turned out that for electron it's necessary to do this workaround (looks not nice), and during development I noticed that it messes up promises for this lib, so it feels not reliable and not nice from DX perspective.
  4. Then, finally, I decided to go with winreg - it's simple, works w/o extra dependencies, just read/write functionality. Only one thing - w/o promises, but at this moment, it wasn't important to me at all 😅

Testing Instructions

Apply the next diff:

diff --git a/src/index.ts b/src/index.ts
index 75d0168..7b739b1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -285,10 +285,7 @@ async function appBoot() {
                        'weekly'
                );
 
-               // temporary hidden since in development yet
-               if ( process.env.NODE_ENV === 'development' ) {
-                       await installCLIOnWindows();
-               }
+               await installCLIOnWindows();
 
                finishedInitialization = true;
        } );
  1. Build Studio npm run make
  2. Install Studio out/make/squirrel.windows/x64/studio-setup.exe
  3. Run studio in terminal (Note, you need to restart your terminal and sometimes necessary even to restart Windows)
  4. Assert that you see:
    Screenshot 2025-03-28 at 15 54 08

@fredrikekelund fredrikekelund left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I made a couple of code suggestions that would be great to address, but the fundamentals here look really solid to me and the PR tests well. Nice work 👍

Comment thread src/modules/cli/lib/install-windows.ts Outdated
});
};

const setPathToRegistry = ( updatedPath: string ): Promise<void> => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
const setPathToRegistry = ( updatedPath: string ): Promise<void> => {
const setPathInRegistry = ( updatedPath: string ): Promise<void> => {
Comment thread src/modules/cli/lib/install-windows.ts Outdated
Comment on lines +84 to +86
const appFolder = path.resolve( process.execPath, '..' );
const relativePath = 'resources/bin/studio-cli.bat';
const versionedPath = path.relative( localAppBinPath, path.join( appFolder, relativePath ) );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
const appFolder = path.resolve( process.execPath, '..' );
const relativePath = 'resources/bin/studio-cli.bat';
const versionedPath = path.relative( localAppBinPath, path.join( appFolder, relativePath ) );
const versionedCliPath = path.join(
path.dirname( app.getPath( 'exe' ) ),
'resources/bin/studio-cli.bat'
);
const relativeVersionedCliPath = path.relative( unversionedBinDirPath, versionedCliPath );

I think we can clarify the logic here along these lines.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good refactoring, makes sense 👍

Comment thread src/modules/cli/lib/install-windows.ts Outdated
Comment on lines +7 to +8
//Example of process.execPath - C:\Users\<USERNAME>\AppData\Local\studio\app-1.3.9-beta1\Studio.exe
const localAppBinPath = path.resolve(process.execPath, '../../bin');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
//Example of process.execPath - C:\Users\<USERNAME>\AppData\Local\studio\app-1.3.9-beta1\Studio.exe
const localAppBinPath = path.resolve(process.execPath, '../../bin');
// `unversionedBinDirPath` resolves to C:\Users\<USERNAME>\AppData\Local\studio\bin
const unversionedBinDirPath = path.resolve( path.dirname( app.getPath( 'exe' ) ), '../bin' );

A few things:

  1. I think a variable name like this is a lot clearer.
  2. app.getPath is the official Electron API for retrieving directories related to the current installation.
  3. Using path.dirname isn't required, it just clarifies that we we expect the final segment of the path to point at a file rather than a directory.
  4. Not super important, but I think it's preferable to write the resulting path in the comment as opposed to the path returned from app.getPath

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think a variable name like this is a lot clearer.

What about binDirPath? I think that mentioning unversioned is a bit extra and just confusing.

Using path.dirname isn't required, it just clarifies that we we expect the final segment of the path to point at a file rather than a directory.

Yeah, initially I considered adding path.dirname but decided that avoid using it make code more readable. But I don't have strong preference, so let's keep it 👌

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about binDirPath?

There are multiple bin dirs, which is why I think it's helpful to distinguish between them with the "versioned" and "unversioned" labels.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh, yeah, you are right 👍
Updated

Comment thread package.json Outdated
"tar": "^7.4.0",
"unzipper": "0.10.11",
"url-loader": "^4.1.1",
"winreg": "^1.2.4",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
"winreg": "^1.2.4",
"winreg": "1.2.4",

I don't have a strong opinion about this, but regedit seems ever so slightly more well-maintained than winreg. The gotcha you linked to with Electron apps doesn't look too bad either, IMO. The fact that we can't update to the latest version of winreg and that 1.2.4 is 8 years old is a bit of a red flag, IMO.

If we do stick with winreg, we should enforce the "don't upgrade to 1.2.5" constraint in package.json according to my suggestion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't have a strong opinion about this, but regedit seems ever so slightly more well-maintained than winreg. The gotcha you linked to with Electron apps doesn't look too bad either, IMO. The fact that we can't update to the latest version of winreg and that 1.2.4 is 8 years old is a bit of a red flag, IMO.

The thing that disappointed me is - it supports async, but I got issues with it as soon as I added that fix for Electron. But yeah, maybe it makes sense to try to spend more time with it to make it working well.
What I liked with winreg - it was only one package which started working w/o extra effort and make me feeling reliable.

I think that the best way is to go with winreg ATM, since I tested it well and everything is ready to move forward, And later, if we encounter some issues with it, then we can reconsider to take regedit or maybe even something else. But ARTM winreg does it's job well.

@nightnei nightnei Apr 8, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If we do stick with winreg, we should enforce the "don't upgrade to 1.2.5" constraint in package.json according to my suggestion.

Hm, I did install the exect version, weird that at some point it was reverted... good catch, removed ^👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks like this file needs to be reformatted for CI to pass

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, fixed
Later, I will spend some time making a convenient environment for developing on Windows :)

@nightnei
nightnei merged commit 668a610 into trunk Apr 8, 2025
@nightnei
nightnei deleted the nightnei/windows-cli-installer branch April 8, 2025 15:13
@nightnei nightnei self-assigned this Aug 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants