Skip to content

Commit

Permalink
refactor: use pnpm to load dependencies on install (#1213)
Browse files Browse the repository at this point in the history
  • Loading branch information
fwouts committed Nov 21, 2022
1 parent 9433d10 commit 8a0f515
Show file tree
Hide file tree
Showing 22 changed files with 3,927 additions and 8,443 deletions.
2 changes: 1 addition & 1 deletion integrations/cli/package.json
Expand Up @@ -19,7 +19,7 @@
},
"scripts": {
"prepublish": "cd .. && pnpm turbo run build --scope=@previewjs/cli --no-deps --include-dependencies",
"build": "rimraf dist && tsc && node esbuild.js && shx cp -rL ../../loader/src/release/node_modules dist/node_modules",
"build": "rimraf dist && tsc && node esbuild.js && shx cp -rL ../../node_modules/pnpm dist/pnpm && shx cp -rL ../../loader/src/release/* dist/",
"dev": "cross-env PREVIEWJS_MODULES_DIR=$INIT_CWD/../../dev-workspace PREVIEWJS_PACKAGE_NAME=@previewjs/app ts-node-dev --respawn src/main.ts",
"dev:pro": "cross-env PREVIEWJS_MODULES_DIR=$INIT_CWD/../../dev-workspace PREVIEWJS_PACKAGE_NAME=@previewjs/pro ts-node-dev --respawn src/main.ts"
},
Expand Down
2 changes: 1 addition & 1 deletion integrations/intellij/controller/package.json
Expand Up @@ -14,7 +14,7 @@
},
"homepage": "https://previewjs.com",
"scripts": {
"build": "rimraf dist && tsc && node esbuild.js && shx cp -rL ../../../loader/src/release/node_modules dist/node_modules"
"build": "rimraf dist && tsc && node esbuild.js && shx cp -rL ../../../node_modules/pnpm dist/pnpm && shx cp -rL ../../../loader/src/release/* dist/"
},
"devDependencies": {
"@previewjs/server": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions integrations/intellij/controller/src/main.ts
@@ -1,4 +1,4 @@
import { ensureServerRunning } from "@previewjs/server";
import { startServer } from "@previewjs/server";

const port = parseInt(process.argv[2] || "0", 10);
if (!port) {
Expand All @@ -15,7 +15,7 @@ if (!version) {
throw new Error(`Missing environment variable: PREVIEWJS_INTELLIJ_VERSION`);
}

ensureServerRunning({
startServer({
loaderInstallDir: __dirname,
packageName,
versionCode: `intellij-${version}`,
Expand Down
2 changes: 1 addition & 1 deletion integrations/vscode/package.json
Expand Up @@ -88,7 +88,7 @@
"main": "./dist/index.js",
"scripts": {
"vscode:prepublish": "pnpm build",
"build": "rimraf dist previewjs-1.16.1.vsix && tsc && node esbuild.js && shx cp -rL ../../loader/src/release/node_modules dist/node_modules",
"build": "rimraf dist previewjs-1.16.1.vsix && tsc && node esbuild.js && shx cp -rL ../../node_modules/pnpm dist/pnpm && shx cp -rL ../../loader/src/release/* dist/",
"prod": "cross-env PREVIEWJS_DEV=0 pnpm vsce package --no-dependencies && pnpm dev:install",
"dev": "cross-env PREVIEWJS_PACKAGE_NAME=@previewjs/app pnpm dev:build",
"dev:build": "cross-env PREVIEWJS_DEV=1 pnpm vsce package --no-dependencies && pnpm dev:install",
Expand Down
4 changes: 2 additions & 2 deletions integrations/vscode/src/index.ts
Expand Up @@ -4,7 +4,7 @@ import vscode from "vscode";
import { clientId } from "./client-id";
import { closePreviewPanel, updatePreviewPanel } from "./preview-panel";
import { ensurePreviewServerStarted } from "./preview-server";
import { startPreviewJsServer } from "./start-server";
import { ensureServerRunning } from "./start-server";
import { openUsageOnFirstTimeStart } from "./welcome";

const codeLensLanguages = [
Expand Down Expand Up @@ -39,7 +39,7 @@ let dispose = async () => {
export async function activate(context: vscode.ExtensionContext) {
const outputChannel = vscode.window.createOutputChannel("Preview.js");

const previewjsInitPromise = startPreviewJsServer(outputChannel)
const previewjsInitPromise = ensureServerRunning(outputChannel)
.catch((e) => {
outputChannel.appendLine(e.stack);
return null;
Expand Down
4 changes: 2 additions & 2 deletions integrations/vscode/src/server.ts
@@ -1,4 +1,4 @@
import { ensureServerRunning } from "@previewjs/server";
import { startServer } from "@previewjs/server";
import { readFileSync } from "fs";
import { SERVER_PORT } from "./port";

Expand All @@ -11,7 +11,7 @@ if (!packageName) {
throw new Error(`Missing environment variable: PREVIEWJS_PACKAGE_NAME`);
}

ensureServerRunning({
startServer({
loaderInstallDir: process.env.PREVIEWJS_MODULES_DIR || __dirname,
packageName,
versionCode: `vscode-${version}`,
Expand Down
120 changes: 66 additions & 54 deletions integrations/vscode/src/start-server.ts
@@ -1,14 +1,29 @@
import { Client, createClient } from "@previewjs/server/client";
import execa from "execa";
import { openSync, readFileSync } from "fs";
import { closeSync, openSync, readFileSync, utimesSync, watch } from "fs";
import path from "path";
import type { OutputChannel } from "vscode";
import { clientId } from "./client-id";
import { SERVER_PORT } from "./port";

export async function startPreviewJsServer(
const logsPath = path.join(__dirname, "server.log");
const serverLockFilePath = path.join(__dirname, "process.lock");

export async function ensureServerRunning(
outputChannel: OutputChannel
): Promise<Client | null> {
const client = createClient(`http://localhost:${SERVER_PORT}`);
await startServer(outputChannel);
const ready = streamServerLogs(outputChannel);
await ready;
await client.updateClientStatus({
clientId,
alive: true,
});
return client;
}

async function startServer(outputChannel: OutputChannel): Promise<boolean> {
const isWindows = process.platform === "win32";
let useWsl = false;
const nodeVersionCommand = "node --version";
Expand Down Expand Up @@ -39,7 +54,7 @@ export async function startPreviewJsServer(
invalidNode: if (checkNodeVersion.kind === "invalid") {
outputChannel.appendLine(checkNodeVersion.message);
if (!isWindows) {
return null;
return false;
}
// On Windows, try WSL as well.
outputChannel.appendLine(`Attempting again with WSL...`);
Expand All @@ -59,22 +74,21 @@ export async function startPreviewJsServer(
break invalidNode;
}
outputChannel.appendLine(checkNodeVersionWsl.message);
return null;
return false;
}
const logsPath = path.join(__dirname, "server.log");
const logs = openSync(logsPath, "w");
outputChannel.appendLine(
`🚀 Starting Preview.js server${useWsl ? " from WSL" : ""}...`
);
outputChannel.appendLine(`Streaming server logs to: ${logsPath}`);
outputChannel.appendLine(
`If you experience any issues, please include this log file in bug reports.`
);
const nodeServerCommand = "node --trace-warnings server.js";
const serverOptions = {
const serverOptions: execa.Options = {
cwd: __dirname,
stdio: ["ignore", logs, logs],
} as const;
stdio: "inherit",
env: {
PREVIEWJS_LOCK_FILE: serverLockFilePath,
PREVIEWJS_LOG_FILE: logsPath,
},
};
let serverProcess: execa.ExecaChildProcess<string>;
if (useWsl) {
serverProcess = execa(
Expand All @@ -90,53 +104,51 @@ export async function startPreviewJsServer(
detached: true,
});
}
serverProcess.unref();
return true;
}

const client = createClient(`http://localhost:${SERVER_PORT}`);
try {
const startTime = Date.now();
const timeoutMillis = 30000;
loop: while (true) {
try {
await client.info();
break loop;
} catch (e) {
if (serverProcess.exitCode) {
// Important: an exit code of 0 may be correct, especially if:
// 1. Another server is already running.
// 2. WSL is used, so the process exits immediately because it spans another one.
outputChannel.append(readFileSync(logsPath, "utf8"));
throw new Error(
`Preview.js server exited with code ${serverProcess.exitCode}`
);
}
if (Date.now() - startTime > timeoutMillis) {
throw new Error(
`Connection timed out after ${timeoutMillis}ms: ${e}`
);
function streamServerLogs(outputChannel: OutputChannel) {
const ready = new Promise<void>((resolve) => {
let lastKnownLogsLength = 0;
let resolved = false;
// Ensure file exists before watching.
// Source: https://remarkablemark.org/blog/2017/12/17/touch-file-nodejs/
try {
const time = Date.now();
utimesSync(logsPath, time, time);
} catch (e) {
let fd = openSync(logsPath, "a");
closeSync(fd);
}
watch(
logsPath,
{
persistent: false,
},
() => {
try {
const logsContent = ignoreBellPrefix(readFileSync(logsPath, "utf8"));
const newLogsLength = logsContent.length;
if (newLogsLength < lastKnownLogsLength) {
// Log file has been rewritten.
outputChannel.append("\n⚠️ Preview.js server was restarted ⚠️\n\n");
lastKnownLogsLength = 0;
}
outputChannel.append(logsContent.slice(lastKnownLogsLength));
lastKnownLogsLength = newLogsLength;
// Note: " running on " also appears in "Preview.js daemon server is already running on port 9315".
if (!resolved && logsContent.includes(" running on ")) {
resolve();
resolved = true;
}
} catch (e: any) {
// Fine, ignore. It just means log streaming is broken.
}
// Ignore the error and retry after a short delay.
await new Promise<void>((resolve) => setTimeout(resolve, 100));
}
}
client.info();
await client.waitForReady();
outputChannel.appendLine(`✅ Preview.js server ready.`);
serverProcess.unref();
} catch (e: any) {
if (e.stack) {
outputChannel.appendLine(e.stack);
}
outputChannel.appendLine(
`❌ Preview.js server failed to start. Please check logs above and report the issue: https://github.com/fwouts/previewjs/issues`
);
await serverProcess;
throw e;
}
await client.updateClientStatus({
clientId,
alive: true,
});
return client;
return ready;
}

function checkNodeVersionResult(result: execa.ExecaReturnValue<string>):
Expand Down
2 changes: 1 addition & 1 deletion loader/package.json
Expand Up @@ -34,7 +34,7 @@
},
"scripts": {
"prepublish": "cd .. && pnpm turbo run build --scope=@previewjs/loader --no-deps --include-dependencies",
"build": "rimraf dist && tsc && cd src/release && pnpm npm install --ignore-scripts -f"
"build": "rimraf dist && tsc"
},
"devDependencies": {
"@previewjs/core": "workspace:*",
Expand Down
31 changes: 17 additions & 14 deletions loader/src/modules.ts
@@ -1,15 +1,30 @@
import type * as core from "@previewjs/core";
import type * as vfs from "@previewjs/vfs";
import { chmodSync, constants, existsSync, lstatSync, readdirSync } from "fs";
import execa from "execa";
import fs from "fs";
import path from "path";

export function loadModules({
export async function loadModules({
installDir,
packageName,
}: {
installDir: string;
packageName: string;
}) {
if (
fs.existsSync(path.join(installDir, "pnpm")) &&
!fs.existsSync(path.join(installDir, "node_modules"))
) {
const pnpmProcess = execa.command(
`cd "${installDir}" && node pnpm/bin/pnpm.cjs install --frozen-lockfile`,
{
shell: true,
}
);
pnpmProcess.stdout?.on("data", (chunk) => process.stdout.write(chunk));
pnpmProcess.stderr?.on("data", (chunk) => process.stderr.write(chunk));
await pnpmProcess;
}
const coreModule = requireModule("@previewjs/core") as typeof core;
const vfsModule = requireModule("@previewjs/vfs") as typeof vfs;
const setupEnvironment: core.SetupPreviewEnvironment =
Expand All @@ -33,18 +48,6 @@ export function loadModules({
}
}

for (const f of readdirSync(path.join(installDir, "node_modules"))) {
if (f.startsWith("esbuild-")) {
const binPath = path.join(__dirname, "node_modules", f, "bin", "esbuild");
if (
existsSync(binPath) &&
!(lstatSync(binPath).mode & constants.S_IXUSR)
) {
chmodSync(binPath, "555");
}
}
}

return {
core: coreModule,
vfs: vfsModule,
Expand Down

0 comments on commit 8a0f515

Please sign in to comment.