Thursday, 16 January 2025

Adventures with turborepo

I was accessing a project that builds a nextJs website. 
It was originally launched with npm run dev.
The project used devcert as a local proxy to allow HTTPS access locally.

Normally I would launch it in an Ubuntu environment running on Windows with WSL2.

This time I ran it direct from Windows. After all, node is multiplatform.

However I started hitting some issues:

Running devcert to install the certificates.

requires OpenSSL and sh

devcert requires OpenSSL and the sh command. These are installed with Git for Windows. Ensure that "C:\Program Files\Git\usr\bin" are part of your path.

devcert errors with Node 22

devcert was errorring because it uses a deprecated createCipher method. This is now deprecated in Node 22. To workaround this I used nvm, installed Node 21, and ran the installation code. This was a bit of a hack.

conditional sudo code

the installation script on Linux requires sudo permission, so package.json defined the following script:
"certificates:install": "sudo node ./src/install-certificates.js"

this wouldn't work on Windows. So instead a conditional script launcher was created:
"certificates:install": "node ./src/run-elevated.js ./src/install-certificates.js",

// This script detects the Operating System. If it is not Windows, it applies the sudo command before running the target script.
// Usage:
//    node ./run-elevated.js [script.js]
const { exec } = require("child_process");
const os = require("os");

const isWindows = os.platform() === "win32"; // Detect the OS
const targetScript = process.argv[2]; // The first argument after the script name

if (!targetScript) {
  console.error(
    "Error: No script specified. Usage: node run-script.js [your-script.js]",
  );
  process.exit(1);
}

// Command to execute
const command = isWindows
  ? `node ${targetScript}`
  : `sudo node ${targetScript}`;

// Execute the command
exec(command, (error, stdout, stderr) => {
  if (error) {
    console.error(`Error: ${error.message}`);
    return;
  }
  if (stderr) {
    console.error(`Stderr: ${stderr}`);
    return;
  }
  console.log(`Output: ${stdout}`);
});

stdio not accessible

When creating a certificate, devcert prompts for a password.  The code above wouldn't work because stido was not available to the launching console.

The code was changed to:

// This script detects the Operating System. If it is not Windows, it applies the sudo command before running the target script.
// Usage:
//    node ./run-elevated.js [script.js]
const { spawn } = require("child_process"); // Consider cross-spawn (https://www.npmjs.com/package/cross-spawn)
const os = require("os");

const isWindows = os.platform() === "win32"; // Detect the OS
const targetScript = process.argv[2]; // The first argument after the script name

if (!targetScript) {
  console.error(
    "Error: No script specified. Usage: node run-elevated.js [your-script.js]",
  );
  process.exit(1);
}

// Spawn the child process and inherit stdio to enable user interaction
const child = isWindows
  ? spawn("node", [targetScript], {
      stdio: "inherit", // Inherit stdio streams from the parent process
    })
  : spawn("sudo", ["node", targetScript], {
      stdio: "inherit", // Inherit stdio streams from the parent process
    });

// Handle errors if the child process fails
child.on("error", (error) => {
  console.error(`Error: ${error.message}`);
});

// Handle when the child process exits
child.on("exit", (code) => {
  console.log(`Child process exited with code ${code}`);
});


cross-spawn should still be considered as an improvement.

devcert uses environment variables on Windows. turbo doesn't pass them

The final problem was that devcert tries to locate the certificates using environment variables on Windows:

function win32 (name) {
  if (process.env.LOCALAPPDATA) {
    return path.join(process.env.LOCALAPPDATA, name)
  }

  return path.join(process.env.USERPROFILE, 'Local Settings', 'Application Data', name)
}

If you launch the project with npm run dev, the environment variables are passed.

This was verified by adding the following script to package.json and running it with npm run dev and npx turbo run dev. The first logged the environment variables and the second didn't.

{ "scripts": { "check-env": "node -e \"console.log(process.env)\"" } }

If you launch it with turbo dev, they are undefined, and the code errors.

The solution was to add the following to turbo.json:

"globalPassThroughEnv": ["LOCALAPPDATA"],

No comments:

Post a Comment