- 投稿日:2020-03-21T21:53:29+09:00
node-gyp rebuildのerrorを解決する
Node.jsの開発スピードが速すぎて、公開しているNode-REDのライブラリがいつの間にかnpm installできなくなっていました。このライブラリはC++のドライバにアクセスするためNode.jsのC++ Addon(V8エンジン1)を利用していますが、npm install時のnode-gypで失敗しているようでした。V8 APIについては検索してもなかなか情報が見つからず参考になれば幸いです。
ちなみに、本家のサイト2によるとライブラリの開発には
- N-API
- nan
- direct use of internal V8, libuv and Node.js libraries
の3つがあると書かれていますが、ここで扱うのは3つ目のタイプです。
Node.jsのversion
10.xまでは問題なかったのですが、11.xからエラーが出るようになりました。現在最新のv13.11を対象としています。ちなみに、11.xからV8のMajor versionが6から7に上がっています。3
Object->Set()でエラー
Maybe versionになったため、引数にcontextを追加で渡せとのエラーです。Maybe versionの場合は.ToChecked()で返します。4
ログ
../nodes/***/***_wrap.cc:286:46: error: no matching function for call to ‘v8::Array::Set(int&, v8::Local<v8::Integer>)’ dst_addr->Set(i,Integer::New(isolate,tmp)); ^ In file included from /home/***/.cache/node-gyp/13.11.0/include/node/node.h:67:0, from ../nodes/***/***_wrap.cc:29: /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3547:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, v8::Local<v8::Value>, v8::Local<v8::Value>) V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context, ^~~ /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3547:37: note: candidate expects 3 arguments, 2 provided /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3550:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, uint32_t, v8::Local<v8::Value>) V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context, uint32_t index, ^~~ /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3550:37: note: candidate expects 3 arguments, 2 providedコード
// 修正前 Local<Array> dst_addr = Array::New(isolate,4); ... dst_addr->Set(i,Integer::New(isolate,tmp)); // 修正後 Local<Context> context = isolate->GetCurrentContext(); Local<Array> dst_addr = Array::New(isolate,4); ... dst_addr->Set(context,i,Integer::New(isolate,tmp)).ToChecked();Object->Set()でエラー その2
別パターンですが、Maybe versionに関するエラーです。同様にcontextを渡して、.ToChecked()で返します。String::NewFromUtf8()の引数、戻り値の型もMaybeLocalに変わったので、引数にNewStringType::kNormalを追加し、.ToLocalChecked()で返します。
ログ
../nodes/***/***_wrap.cc:292:83: error: no matching function for call to ‘v8::Object::Set(v8::MaybeLocal<v8::String>, v8::Local<v8::Integer>)’ obj->Set(String::NewFromUtf8(isolate,"header"),Integer::New(isolate,mac.header)); ^ In file included from /home/***/.cache/node-gyp/13.11.0/include/node/node.h:67:0, from ../nodes/***/***_wrap.cc:29: /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3547:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, v8::Local<v8::Value>, v8::Local<v8::Value>) V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context, ^~~ /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3547:37: note: candidate expects 3 arguments, 2 provided /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3550:37: note: candidate: v8::Maybe<bool> v8::Object::Set(v8::Local<v8::Context>, uint32_t, v8::Local<v8::Value>) V8_WARN_UNUSED_RESULT Maybe<bool> Set(Local<Context> context, uint32_t index, ^~~ /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3550:37: note: candidate expects 3 arguments, 2 providedコード
// 修正前 obj->Set(String::NewFromUtf8(isolate,"header"),Integer::New(isolate,mac.header)); // 修正後 obj->Set(context,(String::NewFromUtf8(isolate,"header",NewStringType::kNormal)).ToLocalChecked(),Integer::New(isolate,mac.header)).ToChecked();BooleanValue()でエラー
BooleanValueの引数が空なので、Isolate* isolateを引数で渡せとのエラーです。
※V8 7.4の前後でエラーの内容が変わるので両対応しています。
ログ
../nodes/***/***_wrap.cc:196:33: error: no matching function for call to ‘v8::Value::BooleanValue()’ latest = args[0]->BooleanValue(); ^ In file included from /home/***/.cache/node-gyp/13.11.0/include/node/node.h:67:0, from ../nodes/***/***_wrap.cc:29: /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:2771:8: note: candidate: bool v8::Value::BooleanValue(v8::Isolate*) const bool BooleanValue(Isolate* isolate) const; ^~~~~~~~~~~~ /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:2771:8: note: candidate expects 1 argument, 0 providedコード
// 修正前 static void foo(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); latest = args[0]->BooleanValue(); args.GetReturnValue().Set(Boolean::New(isolate,true)); return; } // 修正後 static void foo(const FunctionCallbackInfo<Value>& args) { Isolate* isolate = args.GetIsolate(); #if (V8_MAJOR_VERSION >= 7) && (V8_MINOR_VERSION >= 4) latest = args[0]->BooleanValue(isolate); #elif (V8_MAJOR_VERSION >= 7) Local<Context> context = isolate->GetCurrentContext(); latest = args[0]->BooleanValue(context).ToChecked(); #else latest = args[0]->BooleanValue(); #endif args.GetReturnValue().Set(Boolean::New(isolate,true)); return; }Object->Get()でエラー
Object->Set()と同様。Maybe versionに関するエラー。
ログ
../nodes/***/***_wrap.cc:449:26: error: no matching function for call to ‘v8::Array::Get(int)’ dst_addr[0] = arr->Get(0)->NumberValue(context).FromMaybe(0); ^ In file included from /home/***/.cache/node-gyp/13.11.0/include/node/node.h:67:0, from ../nodes/***/***_wrap.cc:29: /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3594:43: note: candidate: v8::MaybeLocal<v8::Value> v8::Object::Get(v8::Local<v8::Context>, v8::Local<v8::Value>) V8_WARN_UNUSED_RESULT MaybeLocal<Value> Get(Local<Context> context, ^~~ /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3594:43: note: candidate expects 2 arguments, 1 provided /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3597:43: note: candidate: v8::MaybeLocal<v8::Value> v8::Object::Get(v8::Local<v8::Context>, uint32_t) V8_WARN_UNUSED_RESULT MaybeLocal<Value> Get(Local<Context> context, ^~~ /home/***/.cache/node-gyp/13.11.0/include/node/v8.h:3597:43: note: candidate expects 2 arguments, 1 providedコード
// 修正前 dst_addr[0] = arr->Get(0)->NumberValue(context).FromMaybe(0); // 修正後 Local<Context> context = isolate->GetCurrentContext(); dst_addr[0] = arr->Get(context,0).ToLocalChecked()->NumberValue(context).FromMaybe(0);まとめ
Node.jsのv10.x(V8 6.8)からv11.x(V8 7.0)でV8 APIに大きな変更が入り5、全体的にMaybe versionが適用されているAPIでnode-gypエラーが出ていました。V8の文法に慣れないので読み解くのに苦労しました。
v8.dev (https://v8.dev) ↩
C++ Addons (https://nodejs.org/api/addons.html#addons_c_addons) ↩
Previous Releases (https://nodejs.org/en/download/releases/) ↩
Use Maybe version of V8 APIs (https://github.com/joyeecheung/node/blob/v8-maybe-doc/CPP_STYLE_GUIDE.md#use-maybe-version-of-v8-apis)
v8/node deprecated APIs and how to handle them #7 (https://github.com/bcoin-org/bcrypto/issues/7) ↩V8 API changes (https://docs.google.com/document/d/1g8JFi8T_oAE_7uAri7Njtig7fKaPDfotU6huOa1alds/edit) ↩
- 投稿日:2020-03-21T18:53:14+09:00
Node製CLIで「BLEACH」の始解・卍解を確認したい!
唐突に「BLEACH」の登場人物の解号・始解・卍解をパッと確認できるやつ欲しい!と思ったので作りました。
「blch」コマンド。 blch - npm
nodeによるcli作成〜公開まで。TL;DR
npm install blch
で!できること
- 登場人物のリスト表示
- 指定した人物の解号・斬魄刀・卍解の表示
- 始解
- 卍解
*全ての登場人物を網羅していません
*基本的に破面篇までの登場人物を扱いますが、一部卍解は千年血戦篇以降で発現したものも扱っています
*十刃(エスパーダ)の場合は始解・卍解ではなく帰刃を扱います人物追加・編集・修正はこちらから自由にしていただいて構いません。
https://docs.google.com/spreadsheets/d/1e7Ms9sX2m1xu_4r20AgyIZHFgByRQp1s93ZE2PZlEaM/edit#gid=0
このデータからCSVを作成し、該当するものを整形して表示するという仕組みになっています。
*スプレッドシート更新されたらコマンドも勝手に更新するような仕組みにはなってないです
*CLIを作って公開までをやってみたかっただけなので作りは荒いです(レスポンス速度など考慮してなかったり)CLI紹介篇
人物リスト表示 - human
キャラクターをリスト表示します。
オプション指定により、護廷十三隊、仮面の軍勢(ヴァイザード)、十刃(エスパーダ)などのリスト表示ができます。blch human --gotei13
概要表示 - tldr
登場人物の解号・斬魄刀・卍解を表示します。
blch tldr 黒崎一護非常にお世話になっている
tldr
コマンドから着想を得ています。
shellコマンドの説明と使用方法を数行で紹介してくれます。
brew install tldr
で!
https://github.com/tldr-pages/tldr始解・卍解 - echo
echoコマンドにより、始解または卍解を行います。
blch echo --shikai 朽木白哉 blch echo --bankai 朽木白哉コマンド組み合わせ
blch human -a | fzf | xargs blch tldr # もしくは(*fish記法です) blch tldr (blch human -a | fzf)ちなみにログインシェルはfishを使っていますが超おすすめです。
ログインシェルをfishにしてみる - QiitaCLI作成篇
commander
node.jsにおけるCLIの完璧なソリューションです。
と書いてあります。実際使いやすかったです。↓
commander - npmcommanderの使い方を
blch human
コマンドを例にとって説明してみます。
Typescriptです。
index.ts
import * as program from 'commander'; ===== // バージョン情報の登録 // blch -V or blch --version でバージョンが確認できる program .version('0.0.1', '-V, --version') // blch humanコマンドの作成 program .command('human') // コマンド名の登録 ここで登録したものを`blch ~`の形で使える .alias('hu') // alias登録により、`blch hu`と打つことも可能 .description('Output human names') // help用 // optionの登録(省略可能) .option("-a, --all", "List all") .option("-g, --gotei13", "List gotei 13") .option("-e, --espada", "List espada") .option("-v, --visored", "List visored") .option("-o, --other", "List other") .action( async (cmd, options) => { /** `blch human --gotei13`を実行した場合 `cmd`に`gotei13`のBooleanパラメータがtrueで追加される `gotei13`は上記optionで設定した`--gotei13`の部分に該当する `blch human --gotei13 4`を実行した場合 `blch human --gotei13`を実行した時とcmdの内容は同じ optionsは['4']となっている つまり`--gotei13 4`で指定したものが文字列として配列に入る 今回のblchコマンドでは考慮していないが、 オプションの重ねがけも可能。 `blch -g -e`とするとcmdの`gotei13`も`espada`もtrueになる また、`--gotei13 4 5`とオプションに入れると optinosは、`['4', '5']`となる 以下から`cmd`, `options`の値で条件分岐し出力する処理 */ // csvファイルから全リストを取得 const dataList: Human[] = await files.getHumanDataList() let targetCode: GroupCode = 'all' if (cmd.gotei13) { targetCode = 'gotei13' } if (cmd.espada) { targetCode = 'espada' } if (cmd.visored) { targetCode = 'visored' } if (cmd.other) { targetCode = 'other' } // オプションから対象を取得 const humans = findHumansByGroupCode(dataList, targetCode, options) if (humans.length === 0) { console.log('No matching') return } // ターミナルへ出力 humans.forEach(human => console.log(human.name)) }) // helpの定義 .on('--help', function() { console.log('\n Examples:') console.log() console.log(' $ blch human --gotei13') console.log(' $ blch hu -g') console.log() }) // blch humanコマンド以外を作成する場合は同じように追加していく program .command('tldr <target>') ====== // 最後にターミナル引数をparseする処理 program.parse(process.argv)サクッとコマンド作れて良い。
chalk
chalk - npm
ターミナル出力に色をつけたり太字にしたりできます。chalkを使うことで味のある出力になります。
今回この2つくらいしかCLI用のパッケージは使ってないですが、NodeのCLIには他にも使えそうなものは色々あるんだなということを知れました。
こちらはgitのpackage解決周りでバグってビルドできなかったけど参考になりました。
Build a JavaScript Command Line Interface (CLI) with Node.js — SitePointCLI公開篇
ほぼこれに沿って行ったので参照のみ貼ります。
npmへの公開は思っていたより簡単でした。
https://qiita.com/TsutomuNakamura/items/f943e0490d509f128ae2まとめ
- nodeでのCLI作成〜公開を行った。意外とすんなりできた。
- goとかrubyとかrustとかpythonでのCLI作成はどうなんだろう
- 完全に個人用のCLIだが作るきっかけができ、「BLEACH」に感謝。
- 網羅できていない部分は申し訳ありません
- (追記)千年血戦篇アニメめっちゃ楽しみ
- 投稿日:2020-03-21T18:25:31+09:00
Stripeでbusiness_typeをindividualにしたい - Node
これでできた
stripe.accounts.update( 'acct_1GP38OKO0yOOiwW9', {legal_entity: {type: 'individual'}}, function(err, account) { console.log("アカウント編集"); console.log(err); console.log(account); } );
- 投稿日:2020-03-21T16:54:08+09:00
express コマンドのcommand not found を解決するのに手間取った話
【事象】
node.jsの expreass generatorを使って見ようと思い、
ターミナルからexpressコマンドを叩いてみたが、command not foundexpress --version zsh: command not found: express先日、express-generatorのインストールだけをしていたのだが、
上手くいっていなかったのかと思い、再インストールをしてから試してみてもダメだった。【原因&解決方法】
パスが通ってなかった。
node.jsをインストールしたときに、他の方の記事を見て、思考停止で色々な事をコピペしていたのがいけていなかった。
(自分がnode.jsインストールするときに何か余計な事をしたのかも。。。)↓のコマンドでnpmのpathを確認する。
npm bin -g上記で表示されたpathを
.bash_profile
に設定する。
(↓のXXXXX部分を表示された内容に設定)
export PATH=XXXXX:${PATH}
設定したら、↓のコマンドを叩く。
source ~/.bash_profile再度、expressコマンドを叩いたら上手くいった。
express --version 4.16.1
- 投稿日:2020-03-21T13:24:09+09:00
JavaScript Multi-dimensional Array;
JavaScript (Node.js) で AtCoderの問題を解いていて困ったが、現時点で日本語の情報がなかったので後学者のためのメモ。
そもそもJavaScriptでAtCoderやってる人自体少ないので需要はあまりなさそうですが、興味ある人だけ読んでください。例えば
a = [[0, 0], [0, 0]]
というArrayを作って、a[0][0] = 1
とupdateしたいとする。まずは失敗例
var a = new Array(2).fill(new Array(2).fill(0)); a[0][0] = 1; console.log(a); // [ [ 1, 0 ], [ 1, 0 ] ] // a[0], a[1] は呼び出すメモリ上でidが"完全に同じ"なので、a[0][0]とa[1][0]も単に"値が等しいのではなく全く同じもの"になる。これは Python3でいうところの
a = [[0] * 2] * 2と同じ。
これを解決するには、
a[0], a[1]をそれぞれ別に作成する必要がある。
Python3だと、a = [[0] * 2 for _ in range(2)]と書けばよかったが、JavaScriptだとちょっと大変。
いろいろ試して、一応以下のように書くと良いことが解った。var a = new Array(2); for (var i = 0; i < 2; i++) { a[i] = new Array(2).fill(0); } a[0][0] = 1; console.log(a); // [ [ 1, 0 ], [ 0, 0 ] ]
- 投稿日:2020-03-21T01:15:15+09:00
【Node.js】ログにリクエストIDを記録する
概要
各APIリクエストに対して一意なIDを付与しログに記録しておくと、なにかと便利です。
- リクエストID付与なし
[2020-01-01T00:00:00.000] DEBUG system - start // APIリクエスト1 [2020-01-01T00:00:00.000] DEBUG system - start // APIリクエスト2 [2020-01-01T00:00:00.000] DEBUG system - start // APIリクエスト3 [2020-01-01T00:00:00.005] DEBUG system - continue [2020-01-01T00:00:00.005] DEBUG system - continue [2020-01-01T00:00:00.010] DEBUG system - continue [2020-01-01T00:00:00.010] DEBUG system - finish [2020-01-01T00:00:00.015] DEBUG system - finish [2020-01-01T00:00:00.015] ERROR system - error // どのAPIリクエストのエラー?
- リクエストID付与あり
[2020-01-01T00:00:00.000] [request-id-01] DEBUG system - start // APIリクエスト1 [2020-01-01T00:00:00.000] [request-id-02] DEBUG system - start // APIリクエスト2 [2020-01-01T00:00:00.000] [request-id-03] DEBUG system - start // APIリクエスト3 [2020-01-01T00:00:00.005] [request-id-01] DEBUG system - continue [2020-01-01T00:00:00.005] [request-id-02] DEBUG system - continue [2020-01-01T00:00:00.010] [request-id-03] DEBUG system - continue [2020-01-01T00:00:00.010] [request-id-03] DEBUG system - finish [2020-01-01T00:00:00.015] [request-id-02] DEBUG system - finish [2020-01-01T00:00:00.015] [request-id-01] ERROR system - error // APIリクエスト1でエラー本記事では、request-contextを使用してAPIリクエスト毎に一意なリクエストIDを管理、ログに記録する方法を書きます。
APIサーバーはNode.js + Express + log4jsの最小構成を想定、リクエストIDの発行はuuidで行います。参考
実装当時は上記の資料しか見つけることができず、その分とても助かりました。
後述するdomainのDeprecatedについても詳細に書かれています。本記事の投稿にあたって再度情報を整理していたところ発見しました。
当時の私が実現したかったことそのものであり、大変勉強になりました。request-context
変数をリクエスト単位で取り扱うことができるパッケージです。
request-context注意
request-contextにも書かれている通り、request-contextは現在Node.jsでdepricatedになっているdomainに依存しています。
See the Domain Docs for further information on error handling for domains. Note that the domain module is pending deprecation!
domainのdepricatedについては先述した参考資料内で詳細に取り扱われておりますので、そちらをご確認ください。
npm install
npm install request-context準備
const requestContext = require('request-context') app.use(requestContext.middleware('request')) // request: name of namespaceset
requestContext.set('request:id', requestId)get
contextService.get('request:id');サンプルコード
app.tsimport { NextFunction, Request, Response } from 'express' const express = require('express') const app = express() const port = 3001 // add requestId const { v4: uuidv4 } = require('uuid') const requestContext = require('request-context') app.use(requestContext.middleware('request')) app.use((req: Request, res: Response, next: NextFunction) => { const requestId: string = uuidv4() requestContext.set('request:id', requestId) next() }) const requestIdRouter = require('./routes/requestid.js') app.use('/requestid', requestIdRouter) app.listen(port, () => { console.log(`Example app listening on port ${port}!`) })modules/logger.tsconst log4js = require('log4js') const logger = log4js.getLogger('system') const requestContext = require('request-context') const retRequestId = (): string => { return requestContext.get('request:id') } log4js.configure({ appenders: { system: { type: 'file', filename: './log/system.log', layout: { type: 'pattern', pattern: '[%d{ISO8601}] [%x{requestId}] %-5p %c - %m', tokens: { requestId: retRequestId } } }, console: { type: 'console', level: 'debug', layout: { type: 'pattern', pattern: '[%d{ISO8601}] [%x{requestId}] %-5p %c - %m', tokens: { requestId: retRequestId } } } }, categories: { default: { appenders: ['system', 'console'], level: 'debug' } } }) module.exports = loggerroutes/requestid.tsimport { Request, Response } from 'express' const express = require('express') const router = express.Router() const logger = require('./../modules/logger') router.get('/', async (req: Request, res: Response) => { logger.debug('logger 1') logger.debug('logger 2') setTimeout(() => { logger.debug('logger 3') }, 1000) res.status(200).send() }) module.exports = router結果
GET /requestを2回実行
[2020-03-21T00:30:27.900] [319121f2-3c36-4c55-84c2-a7477d2b4e54] DEBUG system - logger 1 [2020-03-21T00:30:27.901] [319121f2-3c36-4c55-84c2-a7477d2b4e54] DEBUG system - logger 2 [2020-03-21T00:30:28.261] [f9f7f350-4549-430b-bd99-5ae17f907f21] DEBUG system - logger 1 [2020-03-21T00:30:28.262] [f9f7f350-4549-430b-bd99-5ae17f907f21] DEBUG system - logger 2 [2020-03-21T00:30:28.902] [319121f2-3c36-4c55-84c2-a7477d2b4e54] DEBUG system - logger 3 [2020-03-21T00:30:29.262] [f9f7f350-4549-430b-bd99-5ae17f907f21] DEBUG system - logger 3