npm icon-gen v1.2.0 release

2017年11月21日 0 開発 , ,

icon-gen v1.2.0 をリリースした。

今回の目玉は ICNS における is32il32 のサポート。Wikipedia の Apple Icon Image format によると ICNS は現行の macOS なら ic07ic14 があれば十分にみえる。

しかし GitHub にて Mac OS X finder uses also is32 and il32 icns. という Pull Request があった。どうやら is32il32 も必要とのこと。これらがないと Finder のリスト表示でアイコンが消えるらしい。

この報告をうけて High Sierra 環境でリスト表示を試したものの、正常に表示されていたので古い macOS 固有の問題かもしれない。私の環境だと再現できないので Pull Request をそのまま採用しようかな?と実装を確認したところ is32il32 の画像部分が PNG ベタ書きになっていた。

本来、これらの画像部分は特殊な圧縮がかかっている。以前、サポートしようとしときに見つけた以下のページによれば PackBits 形式らしい。また RGBA のうち R、G、B をチャンネル単位で圧縮し、A は s8mkl8mk という対となるマスクとして別ブロックに書き込むのだという。

このときは面倒そうだし、私の環境では問題おきてないし、なにより将来なくなるであろうレガシーな形式であることからサポートを見送った。しかし Pull Request がきたのを契機にあらためて PackBits 圧縮を検討してみることにした。

PackBits

PackBits は Run Length Encoding (以下、RLE) の一種である。

アルゴリズムとしては非常に単純なため様々な言語で実装されている。npm だと packbits が見つかった。しかしこれは String が対象。icon-gen 的には Buffer と Array、つまりバイナリーとして扱いたいので別の実装をあたることにした。結果、

が癖もなく分かりやすかったので Node に移植してみた。

PackBits は Apple がまだ Apple Computer だった時代に圧縮と展開のサンプル データを公開しており Wikipedia にもそれが掲載されている。元コードにはテストがなかったので、このサンプルを基準としたテストを実装し、正常に動作することが確認できた。

ではさっそく ICNS へ!というわけで動かしてみたところ、出力されたアイコンを macOS の Preview で確認すると色がめちゃくちゃだ。Alpha にあたる s8mkl8mk は無圧縮なので、これにあたる透過だけが正常に描画される状態。

PackBits としてはサンプルの圧縮と展開に成功、つまり正常と思われるのだが。…もしかして PackBits 圧縮ではない?

ICNS 専用 RLE

改めて Wikipedia の ICNS ページを読みなおしたら

Over time the format has been improved and there is support for compression of some parts of the pixel data. The 32-bit (“is32”, “il32”, “ih32″,”it32”) pixel data are often compressed (per channel) with a format similar to PackBits.[1]

PackBits ではなく似て非なるものらしい。出典としてあげられてる資料も確認。

あらら、やはり PackBits ではないのか。RLE の一種であることは確かだが Apple による公式な仕様はなくて Peter Stuer 氏がリバース エンジニアリングしたのだという。G. Brannon Smith による Java 実装もあるらしい。

とりあえず今後の開発にそなえて is32il32 の実データがほしい。しかし High Sierra に付属している iconutil でアイコン生成するとこれらは存在せず、かわりに ic04ic06 が埋め込まれている。

じゃあこれらを書けばいいのでは?とバイナリー エディタで調べてみたら画像部分の先頭に ARGB とあり、PNG とも RLE とも異なるようだ。これについては別途調査するとして、既存アプリのアイコンならどうかと Firefox.app から持ってきたものを調べたら is32il32 が存在した。

テスト用に ICNS からブロックのヘッダーとボディを抽出するメソッドを実装し、

export default class ICNSGenerator {
  /**
   * Unpack an icon block files from ICNS file (For debug).
   *
   * @param {String} src  Path of the ICNS file.
   * @param {String} dest Path of directory to output icon block files.
   *
   * @return {Promise} Promise object.
   */
  static _debugUnpackIconBlocks (src, dest) {
    return new Promise((resolve, reject) => {
      Fs.readFile(src, (err, data) => {
        if (err) {
          return reject(err)
        }

        for (let pos = HEADER_SIZE, max = data.length; pos < max;) {
          const header = data.slice(pos, pos + HEADER_SIZE)
          const id     = header.toString('ascii', 0, 4)
          const size   = header.readUInt32BE(4) - HEADER_SIZE

          pos += HEADER_SIZE
          const body  = data.slice(pos, pos + size)
          Fs.writeFileSync(Path.join(dest, id + '.header'), header, 'binary')
          Fs.writeFileSync(Path.join(dest, id + '.body'), body, 'binary')

          pos += size
        }

        resolve()
      })
    })
  }
}

Firefox から当該ブロックを取得。また il32 に相当する 32×32 の PNG を得るため ic11 のボディを PNG として保存。icon-gen は SVG だけでなく PNG からも ICNS を生成できるので、これを使用して Firedox の il32 と一致するバイナリーを生成できれば OK というわけだ。

参考資料にある libicns の実装を眺めてみる。

ソース コードとしては icns_rle24.c
icns_encode_rle24_data が ICNS の RLE 処理。ありがたいことに ImageMagick といった外部ライブラリーには依存せず、自己完結している。バイト配列の操作と標準関数のみで実装されているため Node にも移植しやすそうだ。

というわけで移植版を動作させたところ、出力されたバイナリーは見事にサンプルと一致した。ICNS ファイルを Preview で開いても正常に描画されている。

しかし libicns のライセンスは GPL である。icon-gen は既に MIT ライセンスで公開しており Dependents も 4。それらは MIT か Apache-2.0 なので icon-gen が libicns 移植を採用すると Copyleft により Dependents にも影響がある。

ならばスクラッチか。既に移植しているため完全なクリーン ルーム開発はできないが
icns_encode_rle24_data にまとまった仕様がコメントされている。

// Assumptions of what icns rle data is all about:
// A) Each channel is encoded indepenent of the next.
// B) An encoded channel looks like this:
//    0xRL 0xCV 0xCV 0xRL 0xCV - RL is run-length and CV is color value.
// C) There are two types of runs
//    1) Run of same value - high bit of RL is set
//    2) Run of differing values - high bit of RL is NOT set
// D) 0xRL also has two ranges
//    1) for set high bit RL, 3 to 130
//    2) for clr high bit RL, 1 to 128
// E) 0xRL byte is therefore set as follows:
//    1) for same values, RL = RL - 1
//    2) different values, RL = RL + 125
//    3) both methods will automatically set the high bit appropriately
// F) 0xCV byte are set accordingly
//    1) for differing values, run of all differing values
//    2) for same values, only one byte of that values
// Estimations put the absolute worst case scenario as the
// final compressed data being slightly LARGER. So we need to be
// careful about allocating memory. (Did I miss something?)
// tests seem to indicate it will never be larger than the original

雑に翻訳。原文と訳は libicns にならって GPL とする。

ICNS RLE データの仮定 :
A) 各チャンネルは次のチャンネルから独立してエンコードされる
B) エンコードされたチャンネルは以下のようになります
   0xRL 0xCV 0xCV 0xRL 0xCV - RL は Run Length で CV はカラー値です
C) 2 種類の実行 (Run)
   1) 同じ値の実行 - RL の上位ビットを設定します
   2) 異なる値の実行 - RL の上位ビットは設定されません
D) 0xRL には 2 つの範囲もあります
   1) 上位ビットの設定された RL は 3 〜 130
   2) 上位ビットの設定されない RL は 1 〜 128
      ※原文の "clr" は文脈的に clear の略 = 「設定されない」と解釈
E) 0xRL バイトは以下のように設定されます :
   1) 同じ値は RL = RL - 1
   2) 異なる値は RL = RL + 125
   3) 両方のメソッドは自動かつ適切に上位ビットを設定します
F) 0xCV はそれ (0xRL) に応じて設定されます
   1) 異なる値の場合は、すべての異なる値の実行
   2) 同じ値なら、その値を 1 バイトだけ

最終的な圧縮データはわずかに大きいため、最悪な場合のシナリオで見積もります。そのため私たちはメモリ割り当てについて注意する必要があります。 (なにか私は見落としていますか?)
テストではそれが元よりも大きくなることはないと考えられます。

これを見ながら途中まで実装したのだが、そういえばこの特殊な RLE がリバース エンジニアリングによって解明されたこと、Java 実装があるらしいことを思い出す。ならばそれもチェックしておこうかと探してみたら以下をみつけた。

ImageJ という Java の画像処理ライブラリーの一部らしい。ライセンスはコード冒頭をみるに修正 BDS。もしこれを移植して動くようなら採用しよう。

  • libicns 移植版で生成されたバイト配列をテストの基準とする
  • ImageJ 移植版で生成されたバイト配列がテストを満たすようにする

という条件のもとに移植。結果、テストをパスして icon-gen が出力したアイコンも正常に書き込まれていた。icon-gen v1.2.0 としてはこの実装を採用。

オマケとして libicns 移植版の圧縮処理を Gist へ公開。ライセンスはオリジナルにもとづき GPL。

iconutil の出力形式

macOS 付属の CLI ツール、iconutil により ICNS ファイルを生成できる。使用方法と必要なファイルについては以下の記事がわかりやすい。

さて、前述のように High Sierra と未満で iconutil が出力したファイル内の構成が異なる。icon-gen としては High Sierra 未満の ICNS を出力しているわけだが、現行のものへ正式対応する際の参考に調査した形式をまとめておく。

旧は Sierra、新は High Sierra の iconutil で出力したもの。一方にしかないものは他方を空欄してある。画像サイズは正方形だが、そうであることを明示するため WxH 形式で記述しておく。

サイズ 内容
is32 16×16 R、G、B を独立して特殊 RLE 圧縮。RRR、GGG、BBB と並ぶ。
s8mk 16×16 is32 の透過部分。RGBA の A を無圧縮。
il32 32×32 R、G、B を独立して特殊 RLE 圧縮。RRR、GGG、BBB と並ぶ。
l8mk 32×32 il32 の透過部分。RGBA の A を無圧縮。
ic04 16×16 32bit ARGB。画像形式は謎。
ic05 32×32
ic06 48×48
ic07 ic07 128×128 32bit ARGB。画像形式は PNG ファイル。
ic08 ic08 256×256
ic09 ic09 512×512
ic10 ic10 1024×1024
ic11 ic11 32×32
ic12 ic12 64×64
ic13 ic13 256×256
ic14 ic14 512×512
info info バイナリー形式の plist。plutil で XML 化すると iconutil のバージョン情報などが定義されていることを確認できる。

icon-gen は旧形に準拠するが info は書き出していない。plist バイナリーを埋め込んでもよいけれど、これなしにも有効な ICNS と見なされるようなので無視している。

Issue #54 で調査した際は Safari v10.1 (12603.1.30.0.34) が旧式で Firefox 54 は旧式から ic07ic11ic14 が抜けた状態だった。

まとめ

長らく懸念だった is32il32 処理が解決されてうれしい。

ところで今回は C# (PackBits)、C 言語と Java (特殊 RLE) を移植してみたのだが、Node の標準 API と JavaScript の Array が高機能なおかげで移植しやすかった。開発プラットフォームとして必要十分な機能はあるので OS やハードウェア依存の API (Win32 とか DirectX など) を使用していなければ、Node は移植先として有望と感じた。

しかし今回は局所的な移植だったからよかったものの、大規模なものはどうなのだろう。ある時点の移植はできても本家の変更に追従し続けるのは極めて難しそう。sqlite3 は移植ではなく node-gyp による wrapper となっているのは、この事情を踏まえてのことだろう。

などと考えていたらタイムリーな記事がはてブのホット エントリーにあがっていた。

C 言語の実装を kripken/emscripten で WASM にビルドして npm 配布したのだという。これは面白い。

Node は v8.0.0 から、Chrome も v58 から WASM に対応している。Electron でいうと v1.7.0 で Chrome v58、v1.8.0 から Node v8.2.1 を採用しているため、Electron v1.8 系なら Main/Renderer プロセスのどちらでも WASM を使用できるはず。

つまり豊富な C 言語の資産を流用しやすくなるのだ。すばらしい。

WASM は機能の縛りが厳しいため移植を完全に自動化するのは難しいだろうけど、そのへんは emscripten のような周辺ツールが解決すると予想している。例えば File I/O などが含まれていたら WASM とその wrapper で分担・抽象化するとか。

例えば前述の SQLite が WASM 化されたなら Electron アプリに組み込みやすくなるだろう。WASM はパフォーマンスよりもこのような移植方面で期待している。

Redmine テーマ minimalflat2 v1.3.5 リリース

2017年11月6日 0 開発 ,

Redmine テーマ minimalflat2 v1.3.5 をリリースした。

Redmine v3.4.3 対応

以下の問題が報告されたので

Redmine v3.4.2 と v3.4.3 の application.css を比較して変更点を反映してから修正しようとした。結果、CSS の差分はこの問題に対するものだけであることが判明。

よって Redmine v3.4.3 対応ついでに、チケット一覧のバージョン条件リストに名前の長いものがあっても省略されず表示するようになった。

マイページ画面の table が見切れる問題の修正

だいぶ前に報告されて放置してた

When “my project” is displayed in 1 column mode, the table protrudes – Issue #117

を修正した。マイページ画面を表示しながら Web ブラウザーの幅を狭めて 1 カラムにすると table が見切れる。標準テーマでは起きないとのことなので調べたら、

  • 標準テーマは body に font-size: 12px を指定している
  • そのため「たまたま」表示幅を狭めても table のコンテンツが収まる
  • minimalflat2 は特別な箇所をのぞき font-size は指定しない設計方針
  • そのため Web ブラウザーの文字サイズによっては table コンテンツがはみ出る = 見切れる

ということがわかった。短絡的に修正するなら標準テーマ同様に bodyfont-size: 12px を指定すれば済む。しかし現在の設計方針はユーザーの文字サイズを維持するためのものであり、ひとつの問題のため変えたくはない。マイページの table 限定で直すことも可能だが、ここだけ文字サイズが小さくなるのも収まりが悪い。

というわけで修正を断ろうとしたのだが、マイページをよく観察すると「ウォッチしているチケット」などの table は表示幅が狭くなったときに横スクロールバーを提供している。HTML の構造としては

<div class="autoscroll">
  <table class="list issues odd-even sort-by-updated-on sort-desc">
  </table>
</div>

こんな感じになっていて、table の親となる <div class="autoscroll">

.autoscroll {
  overflow-x: auto;
  padding: 1px;
  margin-bottom: 1.2em;
  position: relative;
}

となっているおかげでスクロール可能になっていた。ならば「作業時間」などの table もこれを指定すれば?と思うだろう。しかしわざわざ <div class="autoscroll"> が用意されていることから分かるように table へ指定してもダメだ。可変幅の table にしたければスクロールは外周の div が担当しなければならない。

というわけで theme.js 側に以下の関数を定義して実行するようにした。

// The table can be scrolled when "My Page" is displayed in one column
function setupMyPageAutoScroll () {
  $('#my-page table').each(function () {
    var parent = $(this).parent()
    if (parent.hasClass('autoscroll')) {
      return
    }

    $(this).wrap($('<div class="autoscroll"></div>'))
  })
}

<div class="autoscroll"> で囲われていない table 限定で囲いなおしている。これで無事、他の table も横スクロール可能となった。

設計的に一部の table だけスクロール可能になってるのは統一感がなくておかしいため、将来の Redmine では同様の修正が入るかもしれない。その場合でも処理的に誤動作することはないはず。

Electron を試す 10 – Main プロセスのデバッグ

2017年8月23日 0 開発 ,

このシリーズ第一回node-inspector を利用した Main プロセスのステップ実行を紹介した。しかし node-inspector は現在、非推奨である。Node v6 でサポートされた --inspect を使用することになっている。

Node 同様に Electron も v1.7.2--inspect が追加された。つまり Node/Electron 共に単体でステップ実行を利用できる。現時点の latest version は v1.7.5 なので開発者としても正式にこの機能を採用する時がきた。

というわけで、Electron が提供するデバッグ機能についてまとめる。

サンプル プロジェクトは以下。

Chrome

Electron 公式サイトに Main プロセスをデバッグするための方法が掲載されている。

Debugging the Main Process | Electron

Electron CLI を実行する際に --inspect=[port] オプションを指定することで、Electron の Main プロセスと外部デバッガーを連携できる。例えば

electron --inspect=5858 your/app

のようにする。package.json の npm-scripts に定義する場合は

{
  "scripts": {
    "app": "electron --inspect=5858 src/"
  }
}

こんな感じになる。実際に Terminal から実行すると

$ npm run app

> electron-audio-player@1.4.0 app .../examples-electron/audio-player
> electron --inspect=5858 src/

Debugger listening on port 5858.
Warning: This is an experimental feature and could change at any time.
To start debugging, open the following URL in Chrome:
    chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:5858/ee7b65f0-dc0d-46f7-9d9e-997441981f52

外部デバッガーとして使用する Chrome 用の URL が出力される。これを Chrome のアドレス バーに入力してページを表示すれば DevTools が割り当てられる。

--inspect と Chrome

あとは普段 DevTools を利用しているように Sources タブからブレーク ポイントを貼ってステップ実行したり、変数の内容を参照するなどのデバッグが可能となる。

なお --inspect=[port]=[port] は省略可能。その場合は自動的にポート 5858 が選択されたものとして扱われる。省略するかはお好みで。開発環境によってはポート番号が競合するかもしれないので、変更可能であることを明示するため規定値でもそのまま指定しておくのもよいだろう。私はそうしている。

Visual Studio Code

Atom と並んで Web アプリ開発者に人気のテキスト エディター Visual Studio Code (以下、vscode) にはデバッグ機能が実装されている。これは vscode の大きな特徴であり、エディターにも関わらず IDE のようなデバッグを可能とする。

これは汎用的に設計してあり、様々な言語へ対応できるようになっている。Microsoft 謹製の TypeScript や C# は当然として Python、Ruby、PHP といった言語から Node (JavaScript) まで幅広くサポート。もちろん Electron からも利用可能。

Main プロセスを素の Node で実装している場合は上記の公式資料にある設定だけで使い始められる。しかし Babel などの Transpiler、Browserifywebpack といった Bundler を利用して JavaScript を変換しているならば対象となるコードを指定するために工夫が要る。

vscode のデバッグ機能と設定ファイルの詳細は以下を参照のこと。

launch.json

はじめに vscode 用の設定ファイルを定義する。vscode で読み込むプロジェクトのルートに .vscode フォルダーを作成し、その中へ launch.json というファイルを作成。内容は以下。

{
  // Use IntelliSense to learn about possible Node.js debug attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Main Process",
      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron",
      "windows": {
        "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron.cmd"
      },
      "program": "${workspaceRoot}/src/js/main/Main.js",
      "outFiles": [
        "${workspaceRoot}/src/assets/main.js"
      ]
    }
  ]
}

configurations 内に設定を定義してゆく。

type はデバッグ実行のプラットフォームを表す。Electron 用の設定ではなく Node として実行する。request は新たにプロセスを起動する launch と起動されているものを利用する attach のいずれかを指定する。基本、launch でいい。name は役割で命名しておく。

${workspaceRoot} は特別な変数で、プロジェクトのルート フォルダーをあらわす。基本的に設定はこのフォルダー内で完結するだろうから、パスはこれで開始しておけばよい。

runtimeExecutable はデバッグ実行で使用する Electron のパスになる。Windows だけ起動方法が変わる (cmd.exe 経由になる) ため windows 内へ個別に定義が必要。

program はデバッグ実行のエントリー ポイントになるコードを指定する。Transpiler を利用しているなら変換元のエントリー ポイントになるファイルを指定する。変換先を指定しても動作するが、Bundler で複数ファイルを単一化しているとブレーク ポイントを貼りにくくなる。

outFilesprogram と関連付けるファイル一覧を定義。Transpiler を利用しているなら変換先のエントリー ポイントになるファイルを指定する。以前は sourceMapstrue に指定する必要もあったが、現在はデフォルトで有効になっている。Transpiler/Bundler と Source Maps を利用した最小の設定としては今回のもので十分。

他にもデフォルトが変更されて省略可能なものがあったり、outDir が deprecated で outFiles が代替になっているなどの変化がある。この記事ですら古びるかもしれないので、自分で設定する際は前述の公式資料を必ず確認すること。

Source Maps の注意点

私の環境は Babel + Browserify になる。元のコードは ES.next (例えば ES2015 の Modules など) で書いて、Babel により Electron 向けに変換している。これらのタスクは package.json の npm-scripts で組んでおり、デバッグに関連する定義は以下となる。

{
  "babel": {
    "presets": [
      ["env", {"targets": {"electron": "1.7"}}],
      "react"
    ]
  },
  "scripts": {
    "watch:js-main": "watchify -v -t [ babelify ] ./src/js/main/Main.js --exclude electron --im --no-detect-globals --node -o \"exorcist --base ./src/assets ./src/assets/main.js.map > ./src/assets/main.js\" -d"
  },
  "devDependencies": {
    "babel-preset-env": "^1.6.0",
    "babel-preset-power-assert": "^1.0.0",
    "babel-preset-react": "^6.24.1",
    "babelify": "^7.3.0",
    "browserify": "^14.4.0",
    "electron": "^1.7.5",
    "exorcist": "^0.4.0",
    "watchify": "^3.9.0"
  },
}

watch:js-main が browserify (watchify) によるファイル監視つきの JavaScript 変換になる。

browserify により変換された内容を exorcist に渡して Source Maps となる main.js.map を生成し、その後に変換結果となる main.js を出力している。

これらのうち、vscode のデバッグで重要なのは Source Maps にあたる map ファイルである。map ファイルは変換元ファイルのパスとコード行などが定義されている。vscode のデバッグ機能で map ファイルを使用する場合、変換元ファイルのパスは map からの相対にしなければならない。

exorcist の README によれば、オプションを何も指定しないと変換元ファイルのパスは absolute path (絶対パス) になる。実際には exorcist を実行したフォルダーをルートにした相対パスになるようだが、どちらにせよ map ファイルから見た変換元ファイルへのパスにはならない。

この状態で vscode のデバッグを開始すると変換元を見つけられず program に指定されたファイルをそのまま解釈してしまう。デバッガーは Node として動作するため、現時点で未対応の import 構文などを見つけると Syntax Error 例外でプロセスが中断される。

これを防ぐため exorcist の --base オプションに map と変換されたファイルの出力先フォルダーを指定。変換元ファイルへのパスがそこから見た相対となるようにした。

exorcist --base ./src/assets ./src/assets/main.js.map > ./src/assets/main.js

--base [PATH] の有無で map ファイルの sources が変化することを確認できる。

デバッグ実行

すべての設定が正しく定義されていれば vscode のデバッグ機能を利用できる。JavaScript 変換 & map ファイル生成された状態で vscode のメニューから「デバッグ」、「デバッグの開始」を選ぶとアプリが起動されるはず。

次に vscode でブレーク ポイントを貼りたいファイルを開き、対象行の行番号の左側をクリックすると赤い丸印アイコンが表示される。

ブレーク ポイント

この状態でその場所を通るような操作をアプリから実行すると、その箇所でプロセスが一時停止 (ブレーク) する。このとき vscode のサイドバーからデバッグを開いていれば、変数なども確認できる。

デバッグ実行

コード編集に使用しているエディターとデバッガーがひとつになっていることの便利さ (IDE 感) を実感。これはいい。

userData のパス問題

Electron でアプリ固有のデータを保存する場合は app.getPath('userData') で得られたフォルダーを利用することが多いだろう。このパスは Electron の Web Storage や IndexedDB などを保存する場所で、OS のユーザー単位かつアプリ単位で作成される。

userData には

The directory for storing your app’s configuration files, which by default it is the appData directory appended with your app’s name.

とある。この説明なら package.jsonname が参照されることを期待するけど、実際にはアプリの実行ファイル名が使用されるようだ。例えば AudioPlayer.appAudioPlayer.exe
であれば AudioPlayer というフォルダーになる。

Chrome によるデバッグでは Electron プロセスを単体実行して Chrome と関連付けているだけなので、userData は通常のアプリと同じ場所になる。しかし vscode のデバッグ実行だとフォルダー名は Electron 一択となる。

そのため複数のアプリでデバッグ実行している場合、データが競合するかもしれない。これに気付いたのは、これまで --inspect を利用して IndexedDB などに保存していたデータが vscode のデバッグだと読み込めていなかったから。userData のパスを調べたら、どのプロジェクトでも Electron になっていた。app.getName()Electron を返す。

かなり致命的な問題と思うのだけど、ざっと調べたかぎり vscode 公式のデバッグ関連資料や GitHub issues にはこれについての言及が見つからなかった。後で issue あげるかも。

いちおう app.setPath('userData', path) でパスの上書きは可能だが app.getName()Electron なのでアプリ名を抽象化しきれず、ハード コードが発生する。そもそも開発環境の都合でアプリ側のコードが左右されるのも好ましくない。

この件について情報をお持ちの方は、コメント欄や Twitter などで知らせていただきたい。