カテゴリー

役立ち情報

【Bun】bun.lockb でマージコンフリクトが起こった際の対処法

2023/10/21・役立ち情報

前提 Bunを使っていてGitでマージしようとした時、ロックファイルがコンフリクトすることがあります。しかし、Bunのロックファイルはバイナリなので、手作業でコンフリクトの解決をすることができません。以下は、Bunの自動コンフリクト解決機能を用いる方法です。 方法 リベース/マージを試みます。コンフリクトが起こります。 bun iを実行します。自動的にコンフリクトが解消されます。 git <rebase/merge> --continueを実行します。コミットメッセージをよしなに編集します。 これでコンフリクトが解消できます。

Xcode 15.0 Beta 5以降、pod install に失敗する問題の対処法

2023/08/27・役立ち情報

概要 Xcode 15.0 Beta 5以降、こんなエラーが出て CocoaPods の pod installに失敗する。 DT_TOOLCHAIN_DIR cannot be used to evaluate LIBRARY_SEARCH_PATHS, use TOOLCHAIN_DIR instead (in target 'TARGET' from project 'PROJECT') この不具合は既にIssueが立っており、修正がマージされている。しかし、v1.13.0にマイルストーンが立っており、リリースは進捗的に少し先になりそうな様子。それまでは以下の対処法でビルドを通すことができる。 2023/09/25追記: この問題の修正版、v1.13.0がリリースされた。現在は以下の対処法ではなく、CocoaPodsを更新することで対処できる。 対処法 Podfileを編集する。 # ... post_install do |installer| # ... installer.pods_project.targets.each do |target| # ... target.build_configurations.each do |config| # --- Workaround for Xcode 15.0 --- xcconfig_path = config.base_configuration_reference.real_path xcconfig = File.read(xcconfig_path) xcconfig_mod = xcconfig.gsub(/DT_TOOLCHAIN_DIR/, "TOOLCHAIN_DIR") File.open(xcconfig_path, "w") { |file| file << xcconfig_mod } # --------------------------------- end end end 参考 "Error 'DT_TOOLCHAIN_DIR cannot be used to evaluate LIBRARY_SEARCH_PATHS, use TOOLCHAIN_DIR instead' in Xcode 15 beta 5" (GitHub)

[Flutter] URIをネイティブからFlutter側に渡すためのベストプラクティス (多分)

2023/08/25・役立ち情報

前提 ネイティブからFlutterにURIを渡すためには、EventChannelを利用すると思います。単純にFlutter側でmain()でリスナーを設定し、ネイティブでapplication(_:open:options:)やonNewIntentなどでURIを送り込むと、アプリ起動中にURIが流れてきた場合は動作するしれませんが、URIによってアプリが起動された際にはURIを渡すことができません。何故なら、Flutterエンジンの初期化には時間がかかるためです。 そのため、リッスンされるまではネイティブのコードでURIを貯蔵しておき、リッスンが開始された際に流し込む、というようなアプローチが必要になります。 ホスト(ネイティブ)側のコード iOS class UriEventApi: NSObject { static let channelName = "com.example.app.event/uri" // Channel name (任意に変更) var channel: FlutterEventChannel var eventSink: FlutterEventSink? // リッスンが開始されたタイミングで代入され、キャンセルされたタイミングでnilにされる var pendingUri: String? init(binaryMessenger: FlutterBinaryMessenger) { channel = FlutterEventChannel(name: UriEventApi.channelName, binaryMessenger: binaryMessenger) } func initHandler() { channel.setStreamHandler(self) } func onUri(uri: String) { if (eventSink != nil) { // Flutter側で既にリッスンが開始されている場合、 eventSink!(uri) // そのままeventSinkを発火させる } else { // リッスンが開始されていない = まだFlutterエンジンが初期化されていない 場合、 pendingUri = uri // pendingUriに代入して貯めておく } } } extension UriEventApi: FlutterStreamHandler { func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { // リッスンが開始された際、 eventSink = events if (pendingUri != nil) { // 保留中のURIが存在する場合、 eventSink!(pendingUri) // eventSinkを発火させてFlutter側に伝える pendingUri = nil } return nil } func onCancel(withArguments arguments: Any?) -> FlutterError? { eventSink = nil return nil } } Android // 実装の内容はiOS(Swift)と同様 class UriEventApi(binaryMessenger: BinaryMessenger) : EventChannel.StreamHandler { private val channel = EventChannel(binaryMessenger, CHANNEL_NAME) companion object { const val CHANNEL_NAME = "com.example.event/uri" } private var eventSink: EventChannel.EventSink? = null private var pendingData: String? = null fun initHandler() { channel.setStreamHandler(this) } fun onUri(uri: String) { if (eventSink == null) { pendingData = uri } else { eventSink!!.success(uri) } } override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { eventSink = events if (pendingData != null) { eventSink!!.success(pendingData) pendingData = null } } override fun onCancel(arguments: Any?) { eventSink = null } } Flutter側のコード 扱いやすくするためにUriEventApiという名前でクラスを作ります。 // lib/event_api/uri_event_api.dart class UriEventApi { static const _eventName = "com.example.app.event/uri"; static const _channel = EventChannel(_eventName); StreamSubscription listen() { return _channel.receiveBroadcastStream().listen((uri) { // process }); } } これを任意のタイミングでリッスンします。 UriEventApi().listen(); このコードで基本的には動くはずです。

【Xcode 15/iOS/WidgetKit】"Error (Xcode): Cycle inside Runner; building could produce unreliable results." エラー

2023/08/08・役立ち情報

問題発生 久々にFlutterアプリをビルドしようとしたら、以下のようなエラーが発生して失敗する問題が起きた。 Error (Xcode): Cycle inside Runner; building could produce unreliable results. Cycle details: → Target 'Runner': CodeSign /Users/xxx/Library/Developer/Xcode/DerivedData/Runner-xxx/Build/Intermediates.noindex/ArchiveIntermediates/Runner/InstallationBuildProductsLocation/Applications/Runner.app ○ That command depends on command in Target 'Runner': script phase “[CP] Embed Pods Frameworks” ○ Target 'Runner' has copy command from '/Users/chika/Library/Developer/Xcode/DerivedData/Runner-xxx/Build/Intermediates.noindex/ArchiveIntermediates/Runner/BuildProductsPath/Release-iphoneos/WidgetKitExtension.appex' to '/Users/xxx/Library/Developer/Xcode/DerivedData/Runner-xxx/Build/Intermediates.noindex/ArchiveIntermediates/Runner/InstallationBuildProductsLocation/Applications/Runner.app/PlugIns/WidgetKitExtension.appex' ○ That command depends on command in Target 'Runner': script phase “Thin Binary” ○ Target 'Runner' has process command with output '/Users/xxx/Library/Developer/Xcode/DerivedData/Runner-xxx/Build/Intermediates.noindex/ArchiveIntermediates/Runner/InstallationBuildProductsLocation/Applications/Runner.app/Info.plist' ○ Target 'Runner' has copy command from '/Users/xxx/Library/Developer/Xcode/DerivedData/Runner-xxx/Build/Intermediates.noindex/ArchiveIntermediates/Runner/BuildProductsPath/Release-iphoneos/WidgetKitExtension.appex' to '/Users/xxx/Library/Developer/Xcode/DerivedData/Runner-xxx/Build/Intermediates.noindex/ArchiveIntermediates/Runner/InstallationBuildProductsLocation/Applications/Runner.app/PlugIns/WidgetKitExtension.appex' 環境 Flutter 3.10.6 % flutter --version Flutter 3.10.6 • channel stable • https://github.com/flutter/flutter.git Framework • revision f468f3366c (4 weeks ago) • 2023-07-12 15:19:05 -0700 Engine • revision cdbeda788a Tools • Dart 3.0.6 • DevTools 2.23.1 Xcode 15.0 Beta 4 対処法 Appleのエラーメッセージは非常に分かりにくいのですが、改めて隅々まで読んでみるとBuild Phasesの順序が問題で、[CP] Embed Pods FrameworksとThin BinaryのBuild Phasesは、WidgetKitのExtensionのコピー作業を前提としているために失敗している、と書かれています。多分。 そのため、Embed App ExtensionsのBuild Phaseを上記の2つより上に持ってくる ことで解決しました。 Build Phasesの内容は環境によってかなり異なるため、上記の画像と違う場合にはエラーメッセージを詳しく読んでみてください。

【GitHub Actions】Windowsホストで$GITHUB_OUTPUTが使えない

2023/06/08・役立ち情報

ハマった 以下のように、WindowsホストランナーでOutputしようとするとうまくいかない。 jobs: build_windows: runs-on: windows-latest steps: - uses: actions/checkout@v3 - id: test run: echo "FILENAME=test.zip" >> "$GITHUB_OUTPUT" - run: echo ${{ steps.test.outputs.FILENAME }} 対処法 WindowsホストのシェルであるPowershellにおいては、環境変数にアクセスするにはenv:のプレフィックスが必要な模様。 run: echo "FILENAME=test.zip" >> "${env:GITHUB_OUTPUT}"

【Nuxt3】SSG時に、サイトマップを動的ルートも含めて全自動で生成する方法

2023/06/01・役立ち情報

経緯 Google Search Index用にサイトマップを作りたくなった。サイトマップの生成自体はsitemapパッケージでどうにかなるが、いちいち全部のルートを取得してリストを作るのは面倒臭い。そこで、nitroに備わっているクローラーを使って、サイト内にあるリンクからルート一覧を生成してみる。 nitroのクローラーは、HTMLに含まれる、「/」で始まるhref属性を含むタグを自動的に認識し、リンク先のルートを生成できる。 壁 nitroでは、クローラーによって自動的に動的ルートのページが生成される。しかし、クロール後のルート一覧を取得する方法が存在しない。そこで、ページを生成するタイミングでフックを挟んで、変数のリストに追加していくことにする。 コード まず、sitemapパッケージをインストールしておく。 yarn add sitemap 次に、nuxt.config.tsのdefineNuxtConfigの上に以下を追加する。 const hostname = "<hostname>" const routes: string[] = [] export default defineNuxtConfig({ ... 続いて、defineNuxtConfigの中に以下を追加する。 export default defineNuxtConfig({ ... nitro: { hooks: { "prerender:route"(route) { routes.push(route.route) }, close() { if (routes.length > 0) { const links: SitemapItemLoose[] = routes.map(route => ({ url: route, })) const stream = new SitemapStream({ hostname, }) return streamToPromise(Readable.from(links).pipe(stream)) .then((sm) => { return fs.writeFileSync("dist/sitemap.xml", sm.toString()) }) } }, }, }, ... }) 9行目のif (routes.length > 0)は、これを入れないとnuxt prepare(yarn install)を実行した際にもこの処理が走ってしまうために入れています。

【IntelliJ IDEA + macOS】GPGで署名してgit commitするとエラーが発生する問題の対処法

2023/03/22・役立ち情報

問題発生 IntelliJ IDEA (というか全Jetbrains IDEで共通) でGitのコミットを署名する設定を、Macでこれに従って設定したところ、以下のようなエラーが出てコミットできなくなった。 error: gpg failed to sign the data fatal: failed to write commit object 原因 いろいろ調べた結果、パスワード入力用のダイアログの表示に失敗している様子。 GPGではセキュリティーのためか、pinentryというソフトでGUIのパスフレーズ入力ダイアログを出し、コンソールに直接パスフレーズを入力することを避けるようにしている。そのmacOS版であるpinentry-macをインストールすると、pinentryとpinentry-macという2種類のコマンドが実行できるようになる。しかし、なんと pinentryの方はmacOSでは動かない。 デフォルトではこのpinentryの方が使用されるようになっているためにエラーが発生している様子。~/.gnupg/gpg-agent.confでpinentryのパスを変更できるらしいが、なぜか設定しても反映されない。(いろいろ試したものの…) そこで、シンボリックリンクの作成という力技で対処することにする。 対処法 まず、現在設定されているpinentryのパス と、 pinentry-macのパス を調査する。 前者は以下のコマンドで調べられる。 % gpgconf gpg:OpenPGP:/opt/homebrew/Cellar/gnupg/2.4.0/bin/gpg ... pinentry:パスフレーズ入力:/opt/homebrew/opt/pinentry/bin/pinentry 上の実行結果でいうと、/opt/homebrew/opt/pinentry/bin/pinentryの部分が現在設定されているパス。そして、このファイルを削除する。 % rm <現在設定されているpinentryのパス> 次に、pinentry-macのパスを調べる。 % which pinentry-mac /opt/homebrew/bin/pinentry-mac 最後に、以下のコマンドでシンボリックリンクを作成する。 % ln -s <pinentry-macのパス> <現在設定されているpinentryのパス> これでコミットできるようになるはず。

【Firebase Cloud Functions + Puppeteer】Puppeteerのv19が動かない問題の対処法 (備忘録)

2023/03/14・役立ち情報

問題発生 OGPのサムネイル生成用に、Puppeteerを使ったCloud Functionsの関数をデプロイしようとした。 開発環境(エミュレーター)では正常に動作するのに、なぜかデプロイするとError: could not handle the requestと表示されて動作しなくなる。ログを確認すると、以下のようなエラーが出ていた。 Error: Could not find Chromium (rev. 1095492). This can occur if either 1. you did not perform an installation before running the script (e.g. `npm install`) or 2. your cache path is incorrectly configured (which is: /www-data-home/.cache/puppeteer). For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides/configuration. どうやら「Chromiumどこ…?」というエラーの様子。 結論 以下の手順で解決できる。 functionsディレクトリに.puppeteerrc.cjsファイルを作成し、以下のような中身にする。 /** * @type {import('puppeteer').Configuration} */ module.exports = { cacheDirectory: require("path").join(__dirname, ".cache", "puppeteer"), } firebase.jsonに以下を追記する。(※JSONではコメントの記述はできないため、//以下は削除してください) "functions": [ { "source": "functions", "codebase": "frontend", "ignore": [ "node_modules", ".git", "firebase-debug.log", "firebase-debug.*.log", ".cache" // <= 追記 ], "predeploy": [ "npm --prefix \"$RESOURCE_DIR\" run lint", "npm --prefix \"$RESOURCE_DIR\" run build" ] } ], package.jsonを以下のように変更する。(※JSONではコメントの記述はできないため、//以下は削除してください) "scripts": { "lint": "eslint --ext .js,.ts .", ... "postinstall": "node node_modules/puppeteer/install.js" // <= 追記 }, ... "engines": { "node": "16" // <= 16にする。詳細は下記。 } 解説 調べてみると、Puppeteerのv19で以下のような破壊的変更があった模様。 use ~/.cache/puppeteer for browser downloads (https://github.com/puppeteer/puppeteer/releases/tag/v19.0.0) ホームディレクトリはCloud Functionsのデプロイの対象にならないため、実際に実行される環境でChromiumが存在せず、エラーとなっているようです。 そのため、Puppeteerのインストール先をプロジェクト内に変更してやります。これが1.です。 この状態でnode_modulesを削除してnpm installし直すと、設定したパスに開発端末のOSに合ったChromiumがインストールされます。ここでfirebase deployしようとすると、「パッケージが大きすぎるためデプロイできません」のようなエラーが発生します。これは巨大なChromium本体がデプロイの対象となっているためで、これはデプロイ不要なので除外してやる必要があります。これが2.です。 あとは、Cloud Functionsではなぜかnpm install時にChromiumが自動的にインストールされないので、postinstallでインストールスクリプトを実行してやります。(3.) また、Nodeの最新バージョンは18ですが、2023/03/14現在、Node 18用のイメージにPuppeteerの実行に必要なライブラリが含まれていないため、16にします。

[Cloudflare Pages Functions + Nuxt3] 作成したFunctionがデプロイされない問題の対処法

2023/01/06・役立ち情報

経緯 GitHub連携を用いて、Cloudflare Pagesでページを公開し、一部のパスでFunctionが実行されるようにしたかったが、なぜかFunctionsだけデプロイされない。 公式ドキュメントの通り、プロジェクトルートにfunctionsディレクトリを作成し、その中にapi/showcase.tsを作り、example.com/api/showcaseでAPIが走るようにしたかった。しかし、そのままデプロイしてもNuxtの静的ページに飛び、Functionsに繋がらない。 対処法 pages functions not recognizedで検索したところ、https://github.com/cloudflare/wrangler2/issues/1859がヒットした。 結論としては、nuxt.config.tsに以下を追記する。 ... nitro: { preset: "node-server", }, ... https://github.com/cloudflare/wrangler2/issues/1859#issuecomment-1269616054によると、Nitroでは環境に応じて自動的に適切なプリセットが選択され、Cloudflare Pagesではcloudflare-pagesが設定されるようになります。本来はnuxt generate時に.output/publicに/functionsがコピーされるべきですが、Nuxt3の/serverとして扱われ、認識できるexportが存在しないことからレンダリング前に/functionsが空っぽにされ、結果全くデプロイされないようです。(私の認識が正しければ)

[Nuxt 3] IntelliJ IDEA / WebStorm にて、script setupのNuxt系メソッドをインポート不要にする方法

2022/11/19・役立ち情報

問題は起きた。 Jetbrains社のIDE、IntelliJ IDEA / WebStorm でNuxt3を使おうとすると、#importsからインポートしないとメソッドが解決されない。どうやら、VSCodeのプラグインはインポートしなくても解決できるようになっている模様。 これはめちゃくちゃ面倒臭い。 対処法 imlファイルを編集し、.nuxtディレクトリを処理(インデックス)の対象に含めます。通常、IntelliJ IDEAでは、「.(ドット)」から始まる隠しディレクトリはGUIでインデックスの対象に含めることができません。なので、プロジェクトの設定ファイルを直接編集します。 [プロジェクトルート]/.idea/[プロジェクト名].imlを開きます。そして、以下を追記します。 <?xml version="1.0" encoding="UTF-8"?> <module type="JAVA_MODULE" version="4"> <component name="NewModuleRootManager" inherit-compiler-output="true"> <!-- 略 --> <content url="file://$MODULE_DIR$/.nuxt" /> <!-- 追記 --> <!-- 略 --> </component> </module> こうすると、composableなどがimportなしで使えるようになります。