Skip to content

Commit

Permalink
Merge branch 'VOICEVOX:main' into feature/#2011
Browse files Browse the repository at this point in the history
  • Loading branch information
jdkfx authored Jan 11, 2025
2 parents 47c42c7 + 10ce8de commit cdbc3d7
Show file tree
Hide file tree
Showing 97 changed files with 1,761 additions and 917 deletions.
12 changes: 7 additions & 5 deletions .env.test
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# テスト用の.envファイル。モックを使う。

VITE_APP_NAME=voicevox
VITE_DEFAULT_ENGINE_INFOS=`[
{
"uuid": "074fc39e-678b-4c13-8916-ffca8d505d1d",
"name": "VOICEVOX Engine",
"executionEnabled": true,
"executionFilePath": "../voicevox_engine/run.exe",
"name": "Mock Engine",
"uuid": "00000000-0000-0000-0000-000000000000",
"executionEnabled": false,
"executionFilePath": "dummy/path",
"executionArgs": [],
"host": "http://127.0.0.1:50021"
"host": "mock://mock"
}
]`
VITE_OFFICIAL_WEBSITE_URL=https://voicevox.hiroshiba.jp/
Expand Down
7 changes: 7 additions & 0 deletions .github/actions/download-engine/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ runs:
mkdir -p $DEST
mv $TEMPDIR/tmp-extract/$TARGET/* $DEST
# 実行ファイルのパーミッションを変更
if [ "${{ runner.os }}" = "Windows" ]; then
chmod +x $DEST/run.exe
else
chmod +x $DEST/run
fi
echo "::group::ll $DEST"
ls -al $DEST
echo "::endgroup::"
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/merge_gatekeeper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: "Merge Gatekeeper"

# auto mergeとmerge queue用のチェッカー。
# Approve数が足りているか、すべてのテストが通っているかを確認します。
# 詳細: https://github.com/VOICEVOX/merge-gatekeeper

on:
pull_request_target:
types: [auto_merge_enabled]
merge_group:
types: [checks_requested]

jobs:
merge_gatekeeper:
runs-on: ubuntu-latest
steps:
- uses: voicevox/merge-gatekeeper@main
with:
token: ${{ secrets.GATEKEEPER_TOKEN }}
required_score: 2
score_rules: |
#maintainer: 2
#reviewer: 1
- uses: upsidr/merge-gatekeeper@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
self: merge_gatekeeper
# https://github.com/upsidr/merge-gatekeeper/issues/71#issuecomment-1660607977
ref: ${{ github.event.pull_request && github.event.pull_request.head.sha || github.ref }}
timeout: 18000 # 5 hours
24 changes: 10 additions & 14 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,6 @@ jobs:
dest: ${{ github.workspace }}/voicevox_engine
target: ${{ matrix.voicevox_engine_asset_name }}

- name: Setup
run: |
# run.exe
chmod +x ${{ steps.download-engine.outputs.run_path }}
# .env
sed -i -e 's|"074fc39e-678b-4c13-8916-ffca8d505d1d"|"208cf94d-43d2-4cf5-abc0-9783cac36d29"|' .env.test
sed -i -e 's|"../voicevox_engine/run.exe"|"${{ steps.download-engine.outputs.run_path }}"|' .env.test
# GitHub Actions 環境だとたまに50021が封じられていることがあるので、ランダムなポートを使うようにする
PORT=$(node -r net -e "server=net.createServer();server.listen(0,()=>{console.log(server.address().port);server.close()})")
sed -i -e 's|"host": "http://127.0.0.1:50021"|"host": "http://127.0.0.1:'$PORT'"|' .env.test
sed -i -e 's|"executionArgs": \[\],|"executionArgs": ["--port='$PORT'"],|' .env.test
cp .env.test .env
- name: Run npm run test:browser-e2e
run: |
if [ -n "${{ runner.debug }}" ]; then
Expand All @@ -125,6 +111,14 @@ jobs:
- name: Run npm run test:electron-e2e
run: |
# .env
cp tests/env/.env.test-electron .env
sed -i -e 's|"path/to/engine"|"${{ steps.download-engine.outputs.run_path }}"|' .env
# GitHub Actions 環境だとたまに50021が封じられていることがあるので、ランダムなポートを使うようにする
PORT=$(node -r net -e "server=net.createServer();server.listen(0,()=>{console.log(server.address().port);server.close()})")
sed -i -e 's|random_port|'$PORT'|' .env
cat .env # ログ用
if [ -n "${{ runner.debug }}" ]; then
export DEBUG="pw:browser*"
fi
Expand All @@ -134,6 +128,8 @@ jobs:
npm run test:electron-e2e
fi
rm .env
- name: Run npm run test:storybook-vrt
run: |
if [ -n "${{ runner.debug }}" ]; then
Expand Down
2 changes: 1 addition & 1 deletion openapi.json

Large diffs are not rendered by default.

35 changes: 11 additions & 24 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,22 @@
import type { PlaywrightTestConfig, Project } from "@playwright/test";
import { z } from "zod";
/**
* e2eテストと .env の設計:
* - デフォルトで .env.test を読み込む。
* モックエンジンが使われる。
* - Electronテストはテストファイル内で様々な .env を読み込む。
* テスト条件によって用意したい環境が異なるため。
*/

import type { PlaywrightTestConfig, Project } from "@playwright/test";
import dotenv from "dotenv";
dotenv.config({ override: true });

dotenv.config({ path: ".env.test", override: true });

let project: Project;
let webServers: PlaywrightTestConfig["webServer"];
const isElectron = process.env.VITE_TARGET === "electron";
const isBrowser = process.env.VITE_TARGET === "browser";
const isStorybook = process.env.TARGET === "storybook";

// エンジンの起動が必要
const defaultEngineInfosEnv = process.env.VITE_DEFAULT_ENGINE_INFOS ?? "[]";
const envSchema = z // FIXME: electron起動時のものと共通化したい
.object({
host: z.string(),
executionFilePath: z.string(),
executionArgs: z.array(z.string()),
executionEnabled: z.boolean(),
})
.passthrough()
.array();
const engineInfos = envSchema.parse(JSON.parse(defaultEngineInfosEnv));

const engineServers = engineInfos
.filter((info) => info.executionEnabled)
.map((info) => ({
command: `${info.executionFilePath} ${info.executionArgs.join(" ")}`,
url: `${info.host}/version`,
reuseExistingServer: !process.env.CI,
}));
const viteServer = {
command: "vite --mode test --port 7357",
port: 7357,
Expand All @@ -46,7 +33,7 @@ if (isElectron) {
webServers = [viteServer];
} else if (isBrowser) {
project = { name: "browser", testDir: "./tests/e2e/browser" };
webServers = [viteServer, ...engineServers];
webServers = [viteServer];
} else if (isStorybook) {
project = { name: "storybook", testDir: "./tests/e2e/storybook" };
webServers = [storybookServer];
Expand Down
2 changes: 1 addition & 1 deletion src/backend/browser/browserConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const defaultEngineId = EngineId(defaultEngine.uuid);
export async function getConfigManager() {
await configManagerLock.acquire("configManager", async () => {
if (!configManager) {
configManager = new BrowserConfigManager(isMac);
configManager = new BrowserConfigManager({ isMac });
await configManager.initialize();
}
});
Expand Down
16 changes: 16 additions & 0 deletions src/backend/browser/fakePath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { z } from "zod";
import { uuid4 } from "@/helpers/random";

const fakePathSchema = z
.string()
.regex(/^<browser-dummy-[0-9a-f]+>-.+$/)
.brand("FakePath");
export type FakePath = z.infer<typeof fakePathSchema>;

export const isFakePath = (path: string): path is FakePath => {
return fakePathSchema.safeParse(path).success;
};

export const createFakePath = (name: string): FakePath => {
return fakePathSchema.parse(`<browser-dummy-${uuid4()}>-${name}`);
};
118 changes: 92 additions & 26 deletions src/backend/browser/fileImpl.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { directoryHandleStoreKey } from "./contract";
import { openDB } from "./browserConfig";
import { createFakePath, FakePath, isFakePath } from "./fakePath";
import { SandboxKey } from "@/type/preload";
import { failure, success } from "@/type/result";
import { createLogger } from "@/domain/frontend/log";
import { uuid4 } from "@/helpers/random";
import { normalizeError } from "@/helpers/normalizeError";
import path from "@/helpers/path";
import { ExhaustiveError } from "@/type/utility";

const log = createLogger("fileImpl");

Expand Down Expand Up @@ -113,45 +114,81 @@ const getDirectoryHandleFromDirectoryPath = async (
}
};

export type WritableFilePath =
| {
// ファイル名のみ。ダウンロードとして扱われます。
type: "nameOnly";
path: string;
}
| {
// ディレクトリ内への書き込み。
type: "child";
path: string;
}
| {
// 疑似パス。
type: "fake";
path: FakePath;
};

// NOTE: fixedExportEnabled が有効になっている GENERATE_AND_SAVE_AUDIO action では、ファイル名に加えディレクトリ名も指定された状態でfilePathが渡ってくる
// また GENERATE_AND_SAVE_ALL_AUDIO action では fixedExportEnabled の有効の有無に関わらず、ディレクトリ名も指定された状態でfilePathが渡ってくる
export const writeFileImpl: (typeof window)[typeof SandboxKey]["writeFile"] =
async (obj: { filePath: string; buffer: ArrayBuffer }) => {
const filePath = obj.filePath;
// showExportFilePicker での疑似パスが渡ってくる可能性もある。
export const writeFileImpl = async (obj: {
filePath: WritableFilePath;
buffer: ArrayBuffer;
}) => {
const filePath = obj.filePath;

if (!filePath.includes(path.SEPARATOR)) {
switch (filePath.type) {
case "fake": {
const fileHandle = fileHandleMap.get(filePath.path);
if (fileHandle == undefined) {
return failure(new Error(`ファイルが見つかりません: ${filePath.path}`));
}
const writable = await fileHandle.createWritable();
await writable.write(obj.buffer);
return writable.close().then(() => success(undefined));
}

case "nameOnly": {
const aTag = document.createElement("a");
const blob = URL.createObjectURL(new Blob([obj.buffer]));
aTag.href = blob;
aTag.download = filePath;
aTag.download = filePath.path;
document.body.appendChild(aTag);
aTag.click();
document.body.removeChild(aTag);
URL.revokeObjectURL(blob);
return success(undefined);
}

const fileName = resolveFileName(filePath);
const maybeDirectoryHandleName = resolveDirectoryName(filePath);
case "child": {
const fileName = resolveFileName(filePath.path);
const maybeDirectoryHandleName = resolveDirectoryName(filePath.path);

const directoryHandle = await getDirectoryHandleFromDirectoryPath(
maybeDirectoryHandleName,
);
const directoryHandle = await getDirectoryHandleFromDirectoryPath(
maybeDirectoryHandleName,
);

directoryHandleMap.set(maybeDirectoryHandleName, directoryHandle);
directoryHandleMap.set(maybeDirectoryHandleName, directoryHandle);

return directoryHandle
.getFileHandle(fileName, { create: true })
.then(async (fileHandle) => {
const writable = await fileHandle.createWritable();
await writable.write(obj.buffer);
return writable.close();
})
.then(() => success(undefined))
.catch((e) => {
return failure(normalizeError(e));
});
};
return directoryHandle
.getFileHandle(fileName, { create: true })
.then(async (fileHandle) => {
const writable = await fileHandle.createWritable();
await writable.write(obj.buffer);
return writable.close();
})
.then(() => success(undefined))
.catch((e) => {
return failure(normalizeError(e));
});
}
default:
throw new ExhaustiveError(filePath);
}
};

export const checkFileExistsImpl: (typeof window)[typeof SandboxKey]["checkFileExists"] =
async (filePath) => {
Expand Down Expand Up @@ -182,7 +219,7 @@ export const checkFileExistsImpl: (typeof window)[typeof SandboxKey]["checkFileE
};

// FileSystemFileHandleを保持するMap。キーは生成した疑似パス。
const fileHandleMap: Map<string, FileSystemFileHandle> = new Map();
const fileHandleMap: Map<FakePath, FileSystemFileHandle> = new Map();

// ファイル選択ダイアログを開く
// 返り値はファイルパスではなく、疑似パスを返す
Expand All @@ -201,7 +238,7 @@ export const showOpenFilePickerImpl = async (options: {
});
const paths = [];
for (const handle of handles) {
const fakePath = `<browser-dummy-${uuid4()}>-${handle.name}`;
const fakePath = createFakePath(handle.name);
fileHandleMap.set(fakePath, handle);
paths.push(fakePath);
}
Expand All @@ -214,6 +251,9 @@ export const showOpenFilePickerImpl = async (options: {

// 指定した疑似パスのファイルを読み込む
export const readFileImpl = async (filePath: string) => {
if (!isFakePath(filePath)) {
return failure(new Error(`疑似パスではありません: ${filePath}`));
}
const fileHandle = fileHandleMap.get(filePath);
if (fileHandle == undefined) {
return failure(new Error(`ファイルが見つかりません: ${filePath}`));
Expand All @@ -222,3 +262,29 @@ export const readFileImpl = async (filePath: string) => {
const buffer = await file.arrayBuffer();
return success(buffer);
};

// ファイル選択ダイアログを開く
// 返り値はファイルパスではなく、疑似パスを返す
export const showExportFilePickerImpl: (typeof window)[typeof SandboxKey]["showExportFileDialog"] =
async (obj: {
defaultPath?: string;
extensionName: string;
extensions: string[];
title: string;
}) => {
const handle = await showSaveFilePicker({
suggestedName: obj.defaultPath,
types: [
{
description: obj.extensions.join("、"),
accept: {
"application/octet-stream": obj.extensions.map((ext) => `.${ext}`),
},
},
],
});
const fakePath = createFakePath(handle.name);
fileHandleMap.set(fakePath, handle);

return fakePath;
};
Loading

0 comments on commit cdbc3d7

Please sign in to comment.