🏡 index : quine-global-boneyard/electron-builder.git

import { TmpDir, archFromString, copyDir } from "builder-util"
import { DIR_TARGET, Platform } from "electron-builder"
import { outputFile } from "fs-extra"
import * as fs from "fs/promises"
import * as path from "path"
import { Mode, RWX } from "stat-mode"
import { assertThat } from "./helpers/fileAssert"
import { app, appThrows, assertPack, checkDirContents, linuxDirTarget, modifyPackageJson } from "./helpers/packTester"
import { ExpectStatic } from "vitest"

test.ifDevOrLinuxCi("expand not defined env", ({ expect }) =>
  appThrows(expect, {
    targets: linuxDirTarget,
    config: {
      asar: false,
      // tslint:disable:no-invalid-template-strings
      files: ["${env.FOO_NOT_DEFINED}"],
    },
  })
)

process.env.__NOT_BAR__ = "!**/bar"

test.ifDevOrLinuxCi("files", ({ expect }) =>
  app(
    expect,
    {
      targets: linuxDirTarget,
      config: {
        asar: false,
        // tslint:disable:no-invalid-template-strings
        files: ["**/*", "!ignoreMe${/*}", "${env.__NOT_BAR__}", "dist/electron/**/*"],
      },
    },
    {
      projectDirCreated: projectDir =>
        Promise.all([
          outputFile(path.join(projectDir, "ignoreMe", "foo"), "data"),
          outputFile(path.join(projectDir, "ignoreEmptyDir", "bar"), "data"),
          outputFile(path.join(projectDir, "test.h"), "test that"),
          outputFile(path.join(projectDir, "dist/electron/foo.js"), "data"),
        ]),
      packed: context => {
        const resources = path.join(context.getResources(Platform.LINUX), "app")
        return checkDirContents(expect, resources)
      },
    }
  )
)

test.ifDevOrLinuxCi("files.from asar", ({ expect }) =>
  app(
    expect,
    {
      targets: linuxDirTarget,
      config: {
        asar: true,
        files: [
          {
            from: ".",
            to: ".",
            filter: ["package.json"],
          },
          {
            from: "app/node",
            to: "app/node",
          },
        ],
      },
    },
    {
      projectDirCreated: projectDir =>
        Promise.all([
          fs.mkdir(path.join(projectDir, "app/node"), { recursive: true }).then(() => fs.rename(path.join(projectDir, "index.js"), path.join(projectDir, "app/node/index.js"))),
          modifyPackageJson(projectDir, data => {
            data.main = "app/node/index.js"
          }),
        ]),
    }
  )
)

test.ifNotWindows("map resources", ({ expect }) =>
  app(
    expect,
    {
      targets: linuxDirTarget,
      config: {
        asar: false,
        extraResources: [
          {
            from: "foo/old",
            to: "foo/new",
          },
          {
            from: "license.txt",
            to: ".",
          },
        ],
      },
    },
    {
      projectDirCreated: projectDir => Promise.all([outputFile(path.join(projectDir, "foo", "old"), "data"), outputFile(path.join(projectDir, "license.txt"), "data")]),
      packed: context => {
        const resources = context.getResources(Platform.LINUX)
        return Promise.all([
          assertThat(expect, path.join(resources, "app", "foo", "old")).doesNotExist(),
          assertThat(expect, path.join(resources, "foo", "new")).isFile(),
          assertThat(expect, path.join(resources, "license.txt")).isFile(),
        ])
      },
    }
  )
)

async function doExtraResourcesTest(expect: ExpectStatic, platform: Platform) {
  const osName = platform.buildConfigurationKey
  await assertPack(
    expect,
    "test-app-one",
    {
      // to check NuGet package
      targets: platform.createTarget(platform === Platform.WINDOWS ? "squirrel" : DIR_TARGET),
      config: {
        extraResources: ["foo", "bar/hello.txt", "./dir-relative/f.txt", "bar/${arch}.txt", "${os}/${arch}.txt"],
        [osName]: {
          extraResources: ["platformSpecificR"],
          extraFiles: ["platformSpecificF"],
        },
      },
    },
    {
      projectDirCreated: async projectDir => {
        return Promise.all([
          outputFile(path.resolve(projectDir, "foo/nameWithoutDot"), "nameWithoutDot"),
          outputFile(path.resolve(projectDir, "bar/hello.txt"), "data"),
          outputFile(path.resolve(projectDir, "dir-relative/f.txt"), "data"),
          outputFile(path.resolve(projectDir, `bar/${process.arch}.txt`), "data"),
          outputFile(path.resolve(projectDir, `${osName}/${process.arch}.txt`), "data"),
          outputFile(path.resolve(projectDir, "platformSpecificR"), "platformSpecificR"),
          outputFile(path.resolve(projectDir, "ignoreMe.txt"), "ignoreMe"),
        ])
      },
      packed: async context => {
        const resourcesDir = context.getResources(platform, archFromString(process.arch))
        return Promise.all([
          assertThat(expect, path.resolve(resourcesDir, "foo")).isDirectory(),
          assertThat(expect, path.resolve(resourcesDir, "foo", "nameWithoutDot")).isFile(),
          assertThat(expect, path.resolve(resourcesDir, "bar", "hello.txt")).isFile(),
          assertThat(expect, path.resolve(resourcesDir, "dir-relative", "f.txt")).isFile(),
          assertThat(expect, path.resolve(resourcesDir, "bar", `${process.arch}.txt`)).isFile(),
          assertThat(expect, path.resolve(resourcesDir, osName, `${process.arch}.txt`)).isFile(),
          assertThat(expect, path.resolve(resourcesDir, "platformSpecificR")).isFile(),
          assertThat(expect, path.resolve(resourcesDir, "ignoreMe.txt")).doesNotExist(),
        ])
      },
    }
  )
}

test.ifDevOrLinuxCi("extraResources on Linux", ({ expect }) => doExtraResourcesTest(expect, Platform.LINUX))

// Squirrel.Windows is not supported on macOS anymore (32-bit)
// Skipped due to bug in rimraf on Windows: `at fixWinEPERM (../node_modules/.pnpm/fs-extra@8.1.0/node_modules/fs-extra/lib/remove/rimraf.js:117:5)`
test.ifLinux("extraResources on Windows", ({ expect }) => doExtraResourcesTest(expect, Platform.WINDOWS))

test.ifMac("extraResources on macOS", ({ expect }) => doExtraResourcesTest(expect, Platform.MAC))

test.ifNotWindows.ifNotCiWin("extraResources - two-package", ({ expect }) => {
  const platform = Platform.LINUX
  const osName = platform.buildConfigurationKey

  //noinspection SpellCheckingInspection
  return assertPack(
    expect,
    "test-app",
    {
      // to check NuGet package
      targets: platform.createTarget(DIR_TARGET),
      config: {
        asar: true,
        extraResources: ["foo", "bar/hello.txt", "bar/${arch}.txt", "${os}/${arch}.txt", "executable*"],
        [osName]: {
          extraResources: ["platformSpecificR"],
          extraFiles: ["platformSpecificF"],
        },
      },
    },
    {
      projectDirCreated: projectDir => {
        return Promise.all([
          outputFile(path.join(projectDir, "foo/nameWithoutDot"), "nameWithoutDot"),
          outputFile(path.join(projectDir, "bar/hello.txt"), "data", { mode: 0o400 }),
          outputFile(path.join(projectDir, `bar/${process.arch}.txt`), "data"),
          outputFile(path.join(projectDir, `${osName}/${process.arch}.txt`), "data"),
          outputFile(path.join(projectDir, "platformSpecificR"), "platformSpecificR"),
          outputFile(path.join(projectDir, "ignoreMe.txt"), "ignoreMe"),
          outputFile(path.join(projectDir, "executable"), "executable", { mode: 0o755 }),
          outputFile(path.join(projectDir, "executableOnlyOwner"), "executable", { mode: 0o740 }),
        ])
      },
      packed: async context => {
        const resourcesDir = context.getResources(platform, archFromString(process.arch))
        const appDir = path.join(resourcesDir, "app")

        await Promise.all([
          assertThat(expect, path.join(resourcesDir, "foo")).isDirectory(),
          assertThat(expect, path.join(appDir, "foo")).doesNotExist(),

          assertThat(expect, path.join(resourcesDir, "foo", "nameWithoutDot")).isFile(),
          assertThat(expect, path.join(appDir, "foo", "nameWithoutDot")).doesNotExist(),

          assertThat(expect, path.join(resourcesDir, "bar", "hello.txt")).isFile(),
          assertThat(expect, path.join(resourcesDir, "bar", `${process.arch}.txt`)).isFile(),
          assertThat(expect, path.join(appDir, "bar", `${process.arch}.txt`)).doesNotExist(),

          assertThat(expect, path.join(resourcesDir, osName, `${process.arch}.txt`)).isFile(),
          assertThat(expect, path.join(resourcesDir, "platformSpecificR")).isFile(),
          assertThat(expect, path.join(resourcesDir, "ignoreMe.txt")).doesNotExist(),

          allCan(path.join(resourcesDir, "executable"), true),
          allCan(path.join(resourcesDir, "executableOnlyOwner"), true),

          allCan(path.join(resourcesDir, "bar", "hello.txt"), false),
        ])

        expect(await fs.readFile(path.join(resourcesDir, "bar", "hello.txt"), "utf-8")).toEqual("data")
      },
    }
  )
})

// https://github.com/electron-userland/electron-builder/pull/998
// copyDir walks to a symlink referencing a file that has not yet been copied by postponing the linking step until after the full walk is complete
test.ifNotWindows("postpone symlink", async ({ expect }) => {
  const tmpDir = new TmpDir("files-test")
  const source = await tmpDir.getTempDir()
  const aSourceFile = path.join(source, "z", "Z")
  const bSourceFileLink = path.join(source, "B")
  await outputFile(aSourceFile, "test")
  await fs.symlink(aSourceFile, bSourceFileLink)

  const dest = await tmpDir.getTempDir()
  await copyDir(source, dest)

  await tmpDir.cleanup()
})

async function allCan(file: string, execute: boolean) {
  const mode = new Mode(await fs.stat(file))

  function checkExecute(value: RWX) {
    if (value.execute !== execute) {
      throw new Error(`${file} is ${execute ? "not " : ""}executable`)
    }
  }

  function checkRead(value: RWX) {
    if (!value.read) {
      throw new Error(`${file} is not readable`)
    }
  }

  checkExecute(mode.owner)
  checkExecute(mode.group)
  checkExecute(mode.others)

  checkRead(mode.owner)
  checkRead(mode.group)
  checkRead(mode.others)
}