diff --git a/build.config.ts b/build.config.ts index e6f0ac0..5750639 100644 --- a/build.config.ts +++ b/build.config.ts @@ -1,7 +1,7 @@ import { defineBuildConfig } from "obuild/config"; export default defineBuildConfig({ - entries: [{ type: "bundle", input: "src/index.ts" }], + entries: [{ type: "bundle", input: ["src/index", "src/containers/index", "src/wsl/index"] }], hooks: { rolldownOutput(cfg) { cfg.minify = true; diff --git a/src/containers/container.ts b/src/containers/container.ts new file mode 100644 index 0000000..3b738e4 --- /dev/null +++ b/src/containers/container.ts @@ -0,0 +1,22 @@ +import { statSync } from "node:fs"; +import { isDocker } from "./docker"; + +function hasContainerEnv() { + try { + statSync("/run/.containerenv"); + return true; + } catch { + return false; + } +} + +let isContainerCached: boolean; + +function _isContainer() { + if (isContainerCached === undefined) { + isContainerCached = hasContainerEnv(); + } + return isContainerCached; +} + +export const isContainer = _isContainer() || isDocker; diff --git a/src/containers/docker.ts b/src/containers/docker.ts new file mode 100644 index 0000000..2221092 --- /dev/null +++ b/src/containers/docker.ts @@ -0,0 +1,29 @@ +import { readFileSync, statSync } from "node:fs"; + +let isDockerCached: boolean; + +function _isDocker() { + if (isDockerCached === undefined) { + isDockerCached = hasDockerEnv() || hasDockerCGroup(); + } + return isDockerCached; +} + +function hasDockerEnv() { + try { + statSync("/.dockerenv"); + return true; + } catch { + return false; + } +} + +function hasDockerCGroup() { + try { + return readFileSync("/proc/self/cgroup", "utf8").includes("docker"); + } catch { + return false; + } +} + +export const isDocker = _isDocker(); diff --git a/src/containers/index.ts b/src/containers/index.ts new file mode 100644 index 0000000..3e5bb95 --- /dev/null +++ b/src/containers/index.ts @@ -0,0 +1,2 @@ +export * from "./docker"; +export * from "./container"; diff --git a/src/wsl/index.ts b/src/wsl/index.ts new file mode 100644 index 0000000..de9aacc --- /dev/null +++ b/src/wsl/index.ts @@ -0,0 +1,35 @@ +import { readFileSync } from "node:fs"; +import { release } from "node:os"; +import { isDocker } from "../containers"; + +let isWSLCached: boolean; + +function _isWsl() { + if (isWSLCached === undefined) { + isWSLCached = hasUnameOrProcVersion(); + } + return isWSLCached; +} + +function hasUnameOrProcVersion() { + if (globalThis.process?.platform !== "linux") { + return false; + } + if (release().toLowerCase().includes("microsoft")) { + if (isDocker) { + return false; + } + return true; + } + try { + return readFileSync("/proc/version", "utf8") + .toLowerCase() + .includes("microsoft") + ? !isDocker + : false; + } catch { + return false; + } +} + +export const isWsl = _isWsl(); diff --git a/test/containers.test.ts b/test/containers.test.ts new file mode 100644 index 0000000..98082f4 --- /dev/null +++ b/test/containers.test.ts @@ -0,0 +1,20 @@ +import { expect, it, describe } from "vitest"; +import * as stdEnv from "../src/containers"; + +describe("std-env containers", () => { + it("has expected exports", () => { + expect(Object.keys(stdEnv)).toMatchInlineSnapshot(` + [ + "isDocker", + "isContainer", + ] + `); + }); + + it("defaults", () => { + expect(stdEnv).toMatchObject({ + isDocker: expect.any(Boolean), + isContainer: expect.any(Boolean), + }); + }); +}); diff --git a/test/wsl.test.ts b/test/wsl.test.ts new file mode 100644 index 0000000..1ccde5c --- /dev/null +++ b/test/wsl.test.ts @@ -0,0 +1,18 @@ +import { expect, it, describe } from "vitest"; +import * as stdEnv from "../src/wsl"; + +describe("std-env wsl", () => { + it("has expected exports", () => { + expect(Object.keys(stdEnv)).toMatchInlineSnapshot(` + [ + "isWsl", + ] + `); + }); + + it("defaults", () => { + expect(stdEnv).toMatchObject({ + isWsl: expect.any(Boolean), + }); + }); +});