Tumgik
#frontendops
0-9 · 3 years
Text
package.jsonが複数ある場合(npm or yarn) workspaceへの移行方法
最初からworkspaceで開発する場合、何も考えずに設定すればいい。
ただ、開発が進んでからworkspaceへ移行する場合、何も考えずに移行すると依存関係で問題が発生する可能性がある。
簡単な移行方法
一番上のpackage.jsonでworkspaceの設定を行い、各lock file(package-lock.jsonやyarn.lock)を削除すればいい。 (もし、package.jsonでresolutionsを指定している場合、まとめて一番上のpackage.jsonに指定する)
ただし、この場合、各lock fileで指定されているversionは無視されるため、意図せず依存関係が更新される問題が有る。
依存関係が更新されても良い場合、この方法で更新して良い。
安全な移行方法
もし、依存関係が更新されると困る場合、以下の方法で移行する。
package.jsonの依存関係から "^" を削除する できる限りパージョンを固定する
lock fileを作り直す yarnの場合 rm -f yarn.lock && yarn 、 npmの場合 rm -f package-lock.json && npm i する
lock fileの差分を確認し、問題ないか確認 ここで動作確認も行なう
すべてのlock fileを削除し、workspaceへ移行する 3で動作確認ができれいれば安全に移行できる
ある程度のバージョン更新は行われるが、ある程度安全に移行できるはず。
2 notes · View notes
0-9 · 3 years
Text
Sentry SDKの設定にテストを書く
社内でSentry SDKを使っているプロダクトが複数あり、ここに設定していくのが大変だったので設定を共有化した。 (特にフロントエンドは共通で弾きたいエラーが多いので)
その際「設定が正しく書かれているか?」をテストしたくなり色々やってみた。
Sentry SDK設定の共通化方法
こんな感じのpackageを社内のgithub packagesで公開している。
import * as Integrations from "@sentry/integrations"; import * as Sentry from "@sentry/browser"; import { Event, EventHint } from "@sentry/types/dist/event"; const setDenyUrls = ( denyUrls: Sentry.BrowserOptions["denyUrls"] ): Sentry.BrowserOptions["denyUrls"] => { return [ // localhostでのエラーはsentryへ送らない(port numberは可変なため指定しない) /^http:\/\/localhost:\d+\//, // ページを保存した状態はsentryへ送らない /^file:/, // ngrokは送らない /^https?:\/\/\w+\.ngrok\.io\//, //拡張系のエラーは無視する /^(chrome|safari)-extension:\/\//, ...BaseDenyUrls ]; }; const setIntegrations = ( integrations: Sentry.BrowserOptions["integrations"] ): Sentry.BrowserOptions["integrations"] => { return ( integrations || [ new Integrations.CaptureConsole(), new Integrations.Dedupe(), new Integrations.ExtraErrorData(), ] ); }; const setIgnoreErrors = ( ignoreErrors: Sentry.BrowserOptions["ignoreErrors"] ): Sentry.BrowserOptions["ignoreErrors"] => { return [ ...(ignoreErrors || []), "onSvFinishLoading", "Failed to fetch", "Network request failed", "timeout of 0ms exceeded", "サーバに接続できませんでした", "ネットワーク接続が切れました", "キャンセルしました", "Đã mất kết nối mạng.", "AbortError: Aborted", "Network Error", "Request aborted", "A network error occurred", "window.webkit.messageHandlers", ]; }; type MatcherType<l extends string r> = { [key in L]: () => R }; const extraFilter = <r>( extra: Event["extra"], matcher: MatcherType ): R => { if ("string" !== typeof extra?.["xxx"]) { return matcher.unmatch(); } if (extra["xxx"].match(/xxx/)) { return matcher.match(); } return matcher.unmatch(); }; const contextsFilter = <r>( contexts: Event["contexts"], matcher: MatcherType ): R => { if ("object" !== typeof contexts?.["ServerParseError"]) { return matcher.unmatch(); } if ("string" !== typeof contexts["ServerParseError"]["bodyText"]) { return matcher.unmatch(); } const bodyText = contexts["ServerParseError"]["bodyText"]; if (bodyText.match(/xxx/i)) { return matcher.match(); } return matcher.unmatch(); }; export function makeInit (options: Sentry.BrowserOptions): Sentry.BrowserOptions { if ("enabled" in options && options.enabled === false) { return options; } options.denyUrls = setDenyUrls(options.denyUrls); options.integrations = setIntegrations(options.integrations); options.ignoreErrors = setIgnoreErrors(options.ignoreErrors); const beforeSend = options.beforeSend; options.beforeSend = (event, hint?: EventHint) => { return extraFilter(event.extra, { match: () => null, unmatch: () => contextsFilter(event.contexts, { match: () => null, unmatch: () => beforeSend?.(event, hint) || event, }), }); }; return options; }
Sentry SDK設定に対するテスト
こんな感じでテストを書いている。 (DevDependenciesに@sentry/nodeを含めている)
以下の方法はsentry@v7で動かなくなった。
Sentry Testkit for JavaScript | Sentry Documentationが案内されていたので、- Sentry-Testkit - Testing Sentry Reports Became Easyを使用する。
import * as Sentry from "@sentry/node"; import { Transport } from "@sentry/types/dist/transport"; import { Status } from "@sentry/types/dist/status"; import SentrySetting from "../src"; import { NodeOptions } from "@sentry/node/dist/types"; import { captureEvent } from "@sentry/minimal"; class TestTransport implements Transport { public static sendEventMock = jest.fn(); sendEvent(event) { TestTransport.sendEventMock(event); return Promise.resolve({ status: Status.Skipped, }); } close() { return Promise.resolve(true); } } const ExpectMockCall = async (result: "filtered" | "throwing") => { // fakeTimerもsetTimeout(r)もだめだったので50ms待つ // (実際は10msでも行けたけど念の為5倍) await new Promise<void>((r) => setTimeout(r, 50)); const count = TestTransport.sendEventMock.mock.calls.length; expect(count ? "throwing" : "filtered").toStrictEqual(result); }; describe("Sentry SDK settings", () => { afterEach(() => { TestTransport.sendEventMock = jest.fn(); }); const sentryOptions: NodeOptions = { dsn: "正しいDNSをSentry設定画面から取得して書く。ただし、Transportで送信をブロックしているためSentryへは送信されない", transport: TestTransport, }; it("mock call", async () => { Sentry.init(sentryOptions); Sentry.captureException(new Error("test")); await ExpectMockCall("throwing"); }); it.each([ ["filtered", "onSvFinishLoading"], ["filtered", "Request aborted"], // ignoreErrorsは部分一致なので、追加の文字があってもフィルタされる ["filtered", "Request aborted hoge"], ["throwing", "hoge"], ] as const)("ignoreErrors: %s %s", async (result, error) => { Sentry.init(SentrySetting.makeInit(sentryOptions)); Sentry.captureException(new Error(error)); await ExpectMockCall(result); }); it.each([ // このファイルのファイル名を書く ["filtered", "sentry.test.ts"], ["throwing", "hoge"], ] as const)("denyUrls: %s %s", async (result, url) => { Sentry.init( SentrySetting.makeInit({ ...sentryOptions, denyUrls: [url], }) ); Sentry.captureException(new Error("error")); await ExpectMockCall(result); }); it.each([ ["filtered", "xxx"], ["throwing", "{}"], ] as const)("extraFilter: %s %s", async (result, xxx) => { Sentry.init(SentrySetting.makeInit(sentryOptions)); Sentry.captureEvent({ extra: { xxx, }, }); await ExpectMockCall(result); }); it.each([ ["filtered", "xxx"], ["throwing", "{}"], ] as const)("contextsFilter: %s %s", async (result, bodyText) => { Sentry.init(SentrySetting.makeInit(sentryOptions)); Sentry.captureEvent({ contexts: { ServerParseError: { bodyText, }, }, }); await ExpectMockCall(result); }); });
これで「実際にエラーが起こった場合に正しくFilterされるか?」をテストできる。
ちなみに、上記で設定している extra はSentryの設定画面で言うところの ADDITIONAL DATA にあたる。
テスト書いてて気づいたけど、 ignoreErrors は文字列指定では部分一致なので注意。 (完全一致にする場合、正規表現で ^xxx$ と指定する必要がある)
0 notes
0-9 · 3 years
Text
storybookをgithub pagesでhostingする方法
プロダクト支援チームでkintoneのStorybookをホスティングした話 - Cybozu Inside Out | サイボウズエンジニアのブログ で紹介されていた「GitHub Pagesでhosting」をやっているので方法を紹介します。
実際に開発してくれたのは shinya-maruyama ですが、サンプルコードを公開したのでここで紹介したいと思います。
サンプルリポジトリはこちらです
動作
以下のような動作を行います。
PR毎にStorybookをbuildし、PR毎に個別のURLを発行する
PR毎の個別URLをPR commentに投稿する
PRが閉じられたらPR毎の個別URLを破棄する
特定のbranch(ex. main, develop)へのmerge時に、特定のURLのstorybookを更新する
利点
他の様々な方法でも「PR毎に個別URLを発行する」、「特定のbranch(ex. main, develop)のstorybookは常に特定のURLで保持される」のはよく行われます。
しかし、github pagesを使うと認証をgithubに任せることができるため、外部への公開状態を制御したい場合に非常に便利です。
欠点
PRを生成するたびにgitのcommit logが追加されるので、「開発と無関係なcommit logが増えるのは嫌」という場合には他の方法のほうが良いかもしれません。
その他
github actionsとしてまとめようかと思いましたが、依存関係的に難しそうだったので一旦サンプルコードとして公開しました。
0 notes