Skip to content

Windows support#289

Open
pidgeon777 wants to merge 14 commits into
jannis-baum:mainfrom
pidgeon777:windows-support
Open

Windows support#289
pidgeon777 wants to merge 14 commits into
jannis-baum:mainfrom
pidgeon777:windows-support

Conversation

@pidgeon777

Copy link
Copy Markdown

No description provided.

- Add isWindows detection
- Fix urlToPath to handle Windows drive letters (e.g., C:/)
- Fix pathToURL to properly encode Windows paths
- Update pcomponents to handle Windows root paths
- Add isAbsolutePath helper for cross-platform absolute path detection
- Add isAbsolutePath helper for cross-platform absolute path detection
- Support Windows AppData config location
- Fix relative path detection for Windows paths
Equivalent functionality to the Unix viv shell script:
- Launches vivify-server in background
- Monitors for 'STARTUP COMPLETE' signal
- Handles errors and provides bug report link
- Supports --help and --version flags
Alternative to viv.ps1 for users who prefer batch scripts:
- Same functionality as PowerShell version
- Works in CMD prompt without PowerShell
- Simpler implementation for basic usage
- Add BUILD_DIR_WINDOWS and Windows-specific paths
- Add 'windows' target for building on Windows
- Copy both viv.ps1 and viv.cmd scripts
- Create vivify-server.exe using Node SEA
- Add dev:win and viv:win npm scripts using cross-env
- Add cross-env as devDependency for Windows env var support
- Create scripts/build-windows.ps1 for easy Windows builds
- Document Windows installation steps
- Add Windows build instructions
- Document Windows config file locations
- Mention Windows support in features list
- Add isWindows detection
- Add isAbsolutePath helper for cross-platform support
- Update resolveIcon to recognize Windows absolute paths (C:\\, C:\\)
Giovanni Cerqui | NET GmbH added 6 commits February 4, 2026 07:43
Adds file-based server logging to diagnose client registration:
- Logs WebSocket connect/close
- Logs PATH registration from browser client
- Writes to %TEMP%/vivify-server.log by default (override with VIV_LOG_PATH)

Also adds a viv-debug.cmd launcher that runs the Node bundle and enables logs.
Updates viv-debug.cmd to:
- Keep server running in background (start /b)
- Write startup info to %TEMP%\vivify-server.log
- Capture stdout/stderr to %TEMP%\vivify-server.out.log / .err.log
- Log if node.exe or bundle.js is missing

This prevents the window from closing silently and provides a log to inspect.
viv-debug.cmd now copies bundle.js to bundle.cjs before running node,
so Node doesn't treat it as ESM (package.json has type=module).
Also keeps startup/error logs in %TEMP% for debugging.
Switch viv-debug.cmd to run src/app.ts via ts-node/esm so that
server-side logging is active (it doesn't exist in bundle.js).
Captures stdout/stderr to %TEMP% logs and forces VIV_TIMEOUT=0
with NODE_ENV=development.
Fixes ERR_MODULE_NOT_FOUND for ts-node by:
- Pointing --loader to node_modules/ts-node/esm.mjs explicitly
- Running from repo root (pushd)
- Logging a clear error if node_modules is missing

Now viv-debug.cmd works without global ts-node.
Uses node_modules/ts-node/dist/bin-esm.js to avoid loader resolution
issues. This keeps debug server running from src/app.ts and enables
WebSocket path logging in %TEMP%/vivify-server.log.
@Tweekism

Tweekism commented Feb 4, 2026

Copy link
Copy Markdown
Collaborator

Hi @pidgeon777

Thanks for all your work on this one, sorry it has taken so long to get back to you on this. I had to setup my Windows environment to even test it, then i finished work and had to do it all again at home.

So far I got vivify-server.exe to build but then crashes when loading a file or doing a directory listing.

This is using your latest commit of 9074ec9

I'm about to head to dinner and will look some more afterwards, what exact version of node are you using?

image

@jannis-baum

Copy link
Copy Markdown
Owner

Hello @pidgeon777 ! A big thank you also from my side! As I've said in the issue I don't have any windows setup at all so I'll rely on @Tweekism and maybe other people who have commented on the issue to test this.

Looking ahead a bit, would you be interested in taking over long-term support/maintenance of Vivify Windows? If so, do you have the messaging app Signal? We have a Vivify contributor group there and would then like to invite you to join so we have an easier means of communication that also doesn't ping people on GitHub.

@pidgeon777

Copy link
Copy Markdown
Author

Hello @Tweekism, I'm still improving the branch.

node --version
v25.0.0

@jannis-baum I would love it, the problem is time. I have one job + running a startup, and time is very limited. Still, I'm working on an updated branch. If there is any issue, I will look at it when possible.

@Tweekism

Tweekism commented Feb 11, 2026

Copy link
Copy Markdown
Collaborator

@jannis-baum I would love it, the problem is time. I have one job + running a startup, and time is very limited. Still, I'm working on an updated branch. If there is any issue, I will look at it when possible.

Yeah we are all a bit like that at the moment.

So far this is what I get on my end:-

  • The node SEA binary seems to compile ok, no errors.
  • Running vivify-server.exe directly runs as expected and will terminate automatically after a few seconds (this is expected behavior)
  • While vivify server is running, browsing to http://localhost:31622/viewer/~ will give me a directory listing and I can navigate the file system and I can view .md files rendered as HTML. However I don't get any css stylesheeting (see my first screenshot for example)
  • Running viv.ps1 <somepath> throws an error (see below)
  • Running viv.cmd <somepath> seems to work ok
  • vivify-server.exe won't stay running once a browser is connected to it (@jannis-baum might be better to explain the expected behavior here 😅)

Error from PowerShell 7.5.4

Unhandled exception. System.Management.Automation.PSInvalidOperationException: There is no Runspace available to run scripts in this thread. You can provide one in the DefaultRunspace property of the System.Management.Automation.Runspaces.Runspace type. The script block you attempted to invoke was:
        if ($Even.ll
        }

   at System.Management.Automation.ScriptBlock.GetContextFromTLS()
   at System.Management.Automation.ScriptBlock.InvokeAsDelegateHelper(Object dollarUnder, Object dollarThis, Object[] args)
   at lambda_method72(Closure, Object, DataReceivedEventArgs)
   at System.Diagnostics.AsyncStreamReader.FlushMessageQueue(Boolean rethrowInNewThread)
--- End of stack trace from previous location ---
   at System.Threading.QueueUserWorkItemCallback.Execute()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart()

[process exited with code 3762504530 (0xe0434352)]
You can now close this terminal with Ctrl+D, or press Enter to restart.

Error from Windows PowerShell 5.1.26100.7462

PS C:\Users\jason> viv .

[process exited with code 2 (0x00000002)]
You can now close this terminal with Ctrl+D, or press Enter to restart.
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Install the latest PowerShell for new features and improvements! https://aka.ms/PSWindows

@jannis-baum

Copy link
Copy Markdown
Owner

However I don't get any css stylesheeting (see my first screenshot for example)

This may be related to how static files are served. The way this works is

  • the static/ directory in the repo's root is zipped in the build process and included in the binary (zipped in Makefile, included as asset in sea-config.json)
  • we then access this zip file at runtime via the Node SEA library and serve the file's contents in the /static route (see src/routes/static.ts)

If I had to guess where this breaks on Windows I'd say it's here because the paths might not be correct (e.g. classical / vs. \), maybe swapping the string interpolation out for a proper path could fix it

vivify-server.exe won't stay running once a browser is connected to it (@jannis-baum might be better to explain the expected behavior here 😅)

TLDR; If the static routing (see above) doesn't work then no websocket connections are established meaning the server doesn't know about its clients meaning it times itself out and terminates. What follows is the long version including how the timeout and the communication between server and client work, this might be relevant once the static routing is fixed.

The server will terminate on its own after the timeout when no clients are connected to it. Clients connect to the server via websockets. The timeout kill happens via these functions that are called in src/sockets.ts when the last socket disconnects or the first socket connects.

To be sure that the websocket clients are active and working there is a ping/pong that happens every second (ping, pong); if the client fails to pong then we consider it disconnected and when there are no more clients (also in the initial state before the first client has connected) the timeout starts.

The initial establishing of the connection works as follows and includes communication of which path the client is registered for which is needed for the API, e.g. for the Vim plugin:

  1. The path is hardcoded into the HTML served by the viewer:
    window.VIV_PATH = "${urlToPath(req.path)}";
  2. The client opens a websocket on page load:
    const ws = new WebSocket(`ws://localhost:${window.VIV_PORT}`);
  3. The client sends the path to the server via the websocket when it opens:

    Vivify/static/client.mjs

    Lines 88 to 90 in 0fee662

    ws.addEventListener('open', () => {
    ws.send(`PATH: ${window.VIV_PATH}`);
    });
  4. The server receives this message and registers the client for that path:

    Vivify/src/sockets.ts

    Lines 56 to 78 in 0fee662

    case 'PATH':
    sockets.get(id)!.path = value;
    // watch path (fails if path doesn't exist)
    try {
    sockets.get(id)!.watcher = fs.watch(value, (eventType) => {
    if (eventType !== 'change') return;
    onWrite(value);
    });
    } catch {}
    const queue = openQueue.get(value);
    if (!queue) return;
    let message: string | undefined = undefined;
    while (queue.length) {
    const item = queue.shift();
    if (item && item.timeout > Date.now()) {
    message = item.message;
    break;
    }
    }
    if (!queue.length) openQueue.delete(value);
    if (message) socket.send(message);
    break;

@pidgeon777

Copy link
Copy Markdown
Author

I made many improvements and modifications since my last commit. I have to find some time and check everything before a further push.

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