1:I["e16f57074c8d",[],"ThemeToggle",1] 6:I["6929c83dd538",[],"SearchDialog",1] 7:I["e9b3dd2fb1b7",[],"Router",1] 8:I["99ecb960e9bc",[],"FsRouteSlot",1] a:"MissingRouteFallback" b:I["ef49241e6617",[],"$a",1] :HL["/assets/entries-0qEDkATL.css","style"] 0:{"root":[[["$","link","css:/assets/entries-0qEDkATL.css",{"rel":"stylesheet","precedence":"vite-rsc/importer-resources","href":"/assets/entries-0qEDkATL.css","data-rsc-css-href":"/assets/entries-0qEDkATL.css"}],"$undefined"],["$","html",null,{"lang":"ja","children":[["$","head",null,{"children":[["$","meta",null,{"charSet":"utf-8"}],["$","meta",null,{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","link",null,{"rel":"alternate","type":"application/atom+xml","href":"/atom.xml"}],["$","link",null,{"rel":"alternate","type":"application/rss+xml","href":"/feed.xml"}],["$","link",null,{"rel":"icon","href":"/img/favicon.ico"}],["$","script",null,{"dangerouslySetInnerHTML":{"__html":"(() => {\n const stored = window.localStorage.getItem(\"theme\");\n const system = window.matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n const theme = stored === \"dark\" || stored === \"light\" ? stored : system;\n document.documentElement.classList.toggle(\"dark\", theme === \"dark\");\n document.documentElement.classList.toggle(\"light\", theme === \"light\");\n })();"}}]]}],["$","body",null,{"className":"bg-background text-foreground antialiased min-h-screen","style":{"fontFamily":"Geist, ui-sans-serif, system-ui, sans-serif"},"children":[["$","nav",null,{"className":"h-[52px] border-b border-border flex items-center justify-between px-6 sticky top-0 z-50 bg-background/90 backdrop-blur-2xl","children":[["$","div",null,{"className":"flex items-center gap-8","children":[["$","a",null,{"href":"/","className":"text-[0.9375rem] font-semibold text-foreground no-underline tracking-[-0.01em]","children":"docs"}],["$","div",null,{"className":"hidden sm:flex gap-6","children":[["$","a",null,{"href":"/","className":"text-[0.8125rem] text-muted-foreground no-underline hover:text-foreground transition-colors duration-200","children":"Blog"}],["$","a",null,{"href":"/docs","className":"text-[0.8125rem] text-muted-foreground no-underline hover:text-foreground transition-colors duration-200","children":"Docs"}]]}]]}],["$","div",null,{"className":"flex items-center gap-2","children":[["$","$L1",null,{}],["$","a",null,{"href":"https://github.com/mkusaka/docs","target":"_blank","rel":"noopener noreferrer","children":["$","svg",null,{"width":"16","height":"16","viewBox":"0 0 24 24","fill":"currentColor","children":["$","path",null,{"d":"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"}]}],"data-slot":"button","data-variant":"ghost","data-size":"icon-sm","className":"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50 size-8","ref":null}]]}]]}],"$L2","$L3","$L4","$L5"]}]]}]]} 2:["$","$L6",null,{}] 3:["$","$L7",null,{"routes":[{"id":"0 page.tsx","path":"/","component":["$","$L8",null,{"route":{"id":"0 page.tsx"},"paramNames":[],"chunks":{"[]":"fun__rsc-payload/d85ef392475bbd97"}}]},{"id":"1 404/page.tsx","path":"/404","component":["$","$L8",null,{"route":{"id":"1 404/page.tsx"},"paramNames":[],"chunks":{"[]":"fun__rsc-payload/280f5d4e5f8c16e1"}}]},{"id":"2 docs/page.tsx","path":"/docs","component":["$","$L8",null,{"route":{"id":"2 docs/page.tsx"},"paramNames":[],"chunks":{"[]":"fun__rsc-payload/f3fea693a3a3b10b"}}]},{"id":"3 tags/page.tsx","path":"/tags","component":["$","$L8",null,{"route":{"id":"3 tags/page.tsx"},"paramNames":[],"chunks":{"[]":"fun__rsc-payload/19ab1344b11588e0"}}]},{"id":"4 tags/[tag]/page.tsx","path":"/tags/:tag","component":["$","$L8",null,{"route":{"id":"4 tags/[tag]/page.tsx"},"paramNames":["tag"],"chunks":{"[\"accessibility-support\"]":"fun__rsc-payload/72360948e3ea4714","[\"adblock\"]":"fun__rsc-payload/5e7c703fe04f7471","[\"ads\"]":"fun__rsc-payload/0b89ce73c5bfc544","[\"agents-md\"]":"fun__rsc-payload/1610f99c886b9774","[\"ai\"]":"fun__rsc-payload/e203e4053869db1e","[\"ai-agent\"]":"fun__rsc-payload/5402b0b6bbb2643f","[\"android\"]":"fun__rsc-payload/0299b3a8fc3989f2","[\"automation\"]":"fun__rsc-payload/b680d1b71f33b128","[\"blog\"]":"fun__rsc-payload/1127f03ce17411ec","[\"chatgpt\"]":"fun__rsc-payload/4c9d39f45a945840","[\"chrome-extension\"]":"fun__rsc-payload/9e34ed0efc4b1b5d","[\"circleci\"]":"fun__rsc-payload/60d2f7d42f02748d","[\"claude\"]":"fun__rsc-payload/e71b14541a9f9827","[\"claude-code\"]":"fun__rsc-payload/1e70f204b7affa3d","[\"cli\"]":"fun__rsc-payload/244145b6827f8e13","[\"codex\"]":"fun__rsc-payload/6cabde75e1af75f9","[\"coding-agent\"]":"fun__rsc-payload/8d4102ddd963a9a3","[\"configuration\"]":"fun__rsc-payload/bbda6e0387252ae1","[\"debug\"]":"fun__rsc-payload/f265ce443f82444e","[\"developer-mode\"]":"fun__rsc-payload/faa7b7cf10f59fa8","[\"devin\"]":"fun__rsc-payload/34078e8c2727d374","[\"dns\"]":"fun__rsc-payload/5409b5e8fdddd6a8","[\"docker\"]":"fun__rsc-payload/538a83b4c562bf88","[\"earth-mark\"]":"fun__rsc-payload/9050d6a8c1e70bf2","[\"errorlens\"]":"fun__rsc-payload/d951c396d7e330d0","[\"gh\"]":"fun__rsc-payload/ad908f652354d372","[\"ghcr\"]":"fun__rsc-payload/2ee9dff3664150c6","[\"github\"]":"fun__rsc-payload/882847c95bd22dcc","[\"github-apps\"]":"fun__rsc-payload/53988a4d46ec0fff","[\"github-actions\"]":"fun__rsc-payload/884bf4e3e43f91cb","[\"inspectionlens\"]":"fun__rsc-payload/ad2977cac4f99054","[\"intellij\"]":"fun__rsc-payload/dffb138dddd012a7","[\"i-term2\"]":"fun__rsc-payload/ab5a41c5ae9ce045","[\"jetbrains\"]":"fun__rsc-payload/90dc3f5e87467709","[\"keybinding\"]":"fun__rsc-payload/7e367555e22ed7ff","[\"keybindings\"]":"fun__rsc-payload/581fcab67757a63a","[\"keybinds\"]":"fun__rsc-payload/344ab629380503ac","[\"keyboard\"]":"fun__rsc-payload/c01c52da18cf0346","[\"llm\"]":"fun__rsc-payload/9409b18ef11d9a4b","[\"macos\"]":"fun__rsc-payload/bdc5294f5b60520f","[\"mcp\"]":"fun__rsc-payload/cb3ea687cf11e0d9","[\"memo\"]":"fun__rsc-payload/f12d14f14f28e752","[\"memory\"]":"fun__rsc-payload/8c7288aa076fcb38","[\"move\"]":"fun__rsc-payload/659a400463737dbf","[\"navigation-bar\"]":"fun__rsc-payload/5fce4a86991b3554","[\"next-js\"]":"fun__rsc-payload/4d4e334344cd9055","[\"next-dns\"]":"fun__rsc-payload/da8317c934019c66","[\"node\"]":"fun__rsc-payload/df35936644317934","[\"node-js\"]":"fun__rsc-payload/74c5f8fb4328a0ab","[\"oh-my-opencode\"]":"fun__rsc-payload/bb0965e1b35e228c","[\"oh-my-pi\"]":"fun__rsc-payload/ad8b3968c39a5a9f","[\"openai\"]":"fun__rsc-payload/8db961fa59bc52c7","[\"openapi-generator\"]":"fun__rsc-payload/bef95b7794fcee47","[\"opencode\"]":"fun__rsc-payload/7b3e6c76a4de8a0e","[\"opencode-zen\"]":"fun__rsc-payload/0424087103426226","[\"permission\"]":"fun__rsc-payload/5b22351e5ff878ff","[\"plugin\"]":"fun__rsc-payload/8355f79611658f7f","[\"pycharm\"]":"fun__rsc-payload/e06c86387f8df7df","[\"python\"]":"fun__rsc-payload/a8c755d5e9550cca","[\"react\"]":"fun__rsc-payload/5e3aaf777475c741","[\"rust\"]":"fun__rsc-payload/6cea083c500adceb","[\"rust-rover\"]":"fun__rsc-payload/8f1e263f2a9a6d6a","[\"security\"]":"fun__rsc-payload/e59a605f2e59b307","[\"server-actions\"]":"fun__rsc-payload/a642645820db090a","[\"slideshare\"]":"fun__rsc-payload/7e6de3ce0da35d4d","[\"sound\"]":"fun__rsc-payload/308534fb0e0392c7","[\"tab\"]":"fun__rsc-payload/dca37d31a377c3b4","[\"tab-shifter\"]":"fun__rsc-payload/b9caa6e6946aa31c","[\"terminal\"]":"fun__rsc-payload/faa49818c7b59bc9","[\"tips\"]":"fun__rsc-payload/9312030e49fdc16b","[\"tools\"]":"fun__rsc-payload/3cb1a0d7b9c68515","[\"tsx\"]":"fun__rsc-payload/e19d567a03f824cc","[\"tui\"]":"fun__rsc-payload/d55c10f8594c8adf","[\"tunnel\"]":"fun__rsc-payload/51a9654455931c5a","[\"typescript\"]":"fun__rsc-payload/3fa8e602eb0e1ce9","[\"typescript-fetch\"]":"fun__rsc-payload/4ab9723f5efa66f2","[\"vscode\"]":"fun__rsc-payload/cb7f954b97f9d030","[\"vs-code\"]":"fun__rsc-payload/b3955a00d54c8cf3","[\"yaml\"]":"fun__rsc-payload/dd6d8aeb898c71bb","[\"小ネタ\"]":"fun__rsc-payload/4b60f288a0015a95"}}]},{"id":"5 [year]/[month]/[day]/[slug]/page.tsx","path":"/:year/:month/:day/:slug","component":"$L9"}],"fallback":"static","ssr":{"path":"/2023/04/03/ciconfig"}}] 4:["$","$Lb",null,{}] 5:["$","button",null,{"data-search-trigger":true,"className":"lg:hidden fixed bottom-6 right-6 z-50 size-12 rounded-full bg-primary text-primary-foreground shadow-lg flex items-center justify-center active:scale-95 transition-transform","aria-label":"Search","children":["$","svg",null,{"width":"20","height":"20","viewBox":"0 0 24 24","fill":"none","stroke":"currentColor","strokeWidth":"2","strokeLinecap":"round","strokeLinejoin":"round","children":[["$","circle",null,{"cx":"11","cy":"11","r":"8"}],["$","path",null,{"d":"m21 21-4.3-4.3"}]]}]}] 9:["$","$L8",null,{"route":{"id":"5 [year]/[month]/[day]/[slug]/page.tsx"},"paramNames":["year","month","day","slug"],"chunks":{"[\"2026\",\"09\",\"26\",\"codex-cli-gpt-reserve\"]":"fun__rsc-payload/44fdfbc4db572d7b","[\"2026\",\"09\",\"16\",\"devin-cli-multiple-accounts\"]":"fun__rsc-payload/263c39e54182aa8a","[\"2026\",\"08\",\"31\",\"claude-code-statusline-rate-limit-cache\"]":"fun__rsc-payload/0573db1ad6e164f3","[\"2026\",\"08\",\"31\",\"gh-webhook-local-agent-ci-autofix\"]":"fun__rsc-payload/f5180b587490382c","[\"2026\",\"08\",\"29\",\"oh-my-pi-ctrl-h\"]":"fun__rsc-payload/a938ff370ce8bfa2","[\"2026\",\"08\",\"18\",\"chatgpt-local-mcp-proxy\"]":"fun__rsc-payload/c1e9ee30cc371232","[\"2026\",\"07\",\"24\",\"ai-agent-avoid-overly-defensive-code\"]":"fun__rsc-payload/0fce96e643bcf27a","[\"2026\",\"07\",\"02\",\"claude-code-profile-shared-sessions\"]":"fun__rsc-payload/141c3bf972808756","[\"2026\",\"06\",\"06\",\"codex-cli-multiple-accounts\"]":"fun__rsc-payload/d9002923460bf405","[\"2026\",\"03\",\"16\",\"iterm2-cycle-tabs-mru\"]":"fun__rsc-payload/44f662bd57396a59","[\"2026\",\"02\",\"15\",\"claude-code-opencode-zen\"]":"fun__rsc-payload/89809f49892596c4","[\"2026\",\"02\",\"15\",\"codex-cli-opencode-zen\"]":"fun__rsc-payload/580120ed03925212","[\"2026\",\"02\",\"11\",\"opencode-blog-config\"]":"fun__rsc-payload/92035b7c55169d73","[\"2026\",\"02\",\"01\",\"oh-my-opencode-claude-code-compat-only\"]":"fun__rsc-payload/2e3bd374dde92d63","[\"2026\",\"01\",\"20\",\"ai-cli-ctrl-z\"]":"fun__rsc-payload/ebf6b05510a068da","[\"2026\",\"01\",\"20\",\"opencode-ctrl-h\"]":"fun__rsc-payload/942e193d34f6a739","[\"2026\",\"01\",\"11\",\"chatgpt-developer-mode\"]":"fun__rsc-payload/76344c651c1a82f1","[\"2025\",\"08\",\"18\",\"llm-cli-mcp-difference\"]":"fun__rsc-payload/675abb09e7bb2a69","[\"2024\",\"11\",\"30\",\"react-debug\"]":"fun__rsc-payload/6bb690fdc385dfed","[\"2024\",\"11\",\"24\",\"android-earth-mark\"]":"fun__rsc-payload/3f4842c0b3808ea0","[\"2024\",\"09\",\"23\",\"debug-with-tsx\"]":"fun__rsc-payload/cf6b5b28035fa7b1","[\"2024\",\"05\",\"21\",\"Invalid-Python-interpreter-selected-for-the-project\"]":"fun__rsc-payload/e90ab8d4219803ba","[\"2024\",\"04\",\"14\",\"docker-pull-from-public-ghcr\"]":"fun__rsc-payload/26f960ba50e0190c","[\"2024\",\"03\",\"02\",\"vscode-sound\"]":"fun__rsc-payload/31a93cf9cfd9dc2f","[\"2024\",\"02\",\"26\",\"--max-old-space-size\"]":"fun__rsc-payload/ffa58f3ceb467bff","[\"2024\",\"01\",\"15\",\"jetbrains-virtual-space\"]":"fun__rsc-payload/45275e2d1340ffed","[\"2024\",\"01\",\"15\",\"server-actions-getServerSession\"]":"fun__rsc-payload/be8a763b8679b579","[\"2023\",\"11\",\"19\",\"intellijrust-rustrover-external-linters-on-the-fly\"]":"fun__rsc-payload/22c97b83b7a7bbea","[\"2023\",\"11\",\"13\",\"intellij-navigation-bar-position\"]":"fun__rsc-payload/44c7916c47e429b7","[\"2023\",\"08\",\"06\",\"inspection-lens\"]":"fun__rsc-payload/a79661ce4d64d73c","[\"2023\",\"08\",\"14\",\"github-intellij-org\"]":"fun__rsc-payload/ab9ee72bdc7f1d95","[\"2023\",\"07\",\"25\",\"githubapp-permission\"]":"fun__rsc-payload/4763d6671b19f92b","[\"2023\",\"06\",\"18\",\"nextdns\"]":"fun__rsc-payload/66d5fe4b4987ce5c","[\"2023\",\"04\",\"04\",\"refined-slideshare\"]":"fun__rsc-payload/9d874422b30b76a7","[\"2023\",\"04\",\"04\",\"intellij-tab-move-renew\"]":"fun__rsc-payload/c23c90aec2f12c4e","[\"2023\",\"04\",\"04\",\"newtab\"]":"fun__rsc-payload/9b91b47d8e4afef0","[\"2023\",\"04\",\"03\",\"ciconfig\"]":"fun__rsc-payload/3ecc4e73a07d8da6","[\"2023\",\"04\",\"03\",\"vscode-tab-shifter\"]":"fun__rsc-payload/4196b3c9a615c3fb","[\"2022\",\"05\",\"03\",\"openapi-generator-typescript-fetch-string-enums\"]":"fun__rsc-payload/41c08cf12bda904d"},"initialKey":"[\"2023\",\"04\",\"03\",\"ciconfig\"]","initial":"$Lc"}] d:I["de9f8e732323",[],"default",1] c:[["$","title",null,{"children":"CircleCI設定の痛みをTypeScriptで解消するアイディア - docs"}],["$","meta",null,{"name":"description","content":"CircleCI設定ファイルの編集やメンテナンスの悩みを解決するため、TypeScriptを活用する方法を紹介。"}],["$","div",null,{"className":"flex max-w-[1320px] mx-auto overflow-x-hidden","children":[["$","aside",null,{"className":"hidden lg:block w-[210px] shrink-0 sticky top-[52px] h-[calc(100vh-52px)]","children":["$","div",null,{"className":"py-8 pl-6 pr-4 h-full flex flex-col","children":[["$","button",null,{"data-slot":"button","data-variant":"outline","data-size":"default","className":"inline-flex items-center whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 px-4 has-[>svg]:px-3 w-full justify-start gap-2 mb-6 text-muted-foreground h-auto py-[7px] pl-2.5 pr-2","data-search-trigger":true,"children":[["$","$Ld",null,{"ref":"$undefined","icon":{"name":"search","size":24,"node":[["path",{"d":"m21 21-4.34-4.34","key":"14j7rj"}],["circle",{"cx":"11","cy":"11","r":"8","key":"4ej97u"}]]},"className":"size-3.5"}],["$","span",null,{"className":"flex-1 text-left text-xs","children":"Search"}],["$","kbd",null,{"className":"text-[0.6rem] text-muted-foreground/70 border rounded px-1 py-px","children":"⌘ K"}]]}],["$","a",null,{"href":"/","className":"block text-sm rounded-lg px-3 py-2 mb-6 no-underline transition-all text-muted-foreground hover:text-foreground","children":"All posts"}],["$","div",null,{"className":"mb-8","children":[["$","h3",null,{"className":"text-[0.6875rem] font-medium text-muted-foreground uppercase tracking-[0.1em] mb-3","children":"Recent"}],["$","div",null,{"className":"space-y-px","children":[["$","a","codex-cli-gpt-reserve",{"href":"/2026/09/26/codex-cli-gpt-reserve","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline leading-snug transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"Codex CLIでgpt-reserveを使う"}],["$","a","devin-cli-multiple-accounts",{"href":"/2026/09/16/devin-cli-multiple-accounts","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline leading-snug transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"Devin CLIで複数アカウント・orgを切り替える"}],["$","a","claude-code-statusline-rate-limit-cache",{"href":"/2026/08/31/claude-code-statusline-rate-limit-cache","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline leading-snug transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"Claude Code の statusline を使って外部から rate limit を確認する"}],["$","a","gh-webhook-local-agent-ci-autofix",{"href":"/2026/08/31/gh-webhook-local-agent-ci-autofix","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline leading-snug transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"GitHub Webhook でローカルエージェントに CI を直してもらう"}]]}]]}],["$","div",null,{"className":"mb-8","children":[["$","h3",null,{"className":"text-[0.6875rem] font-medium text-muted-foreground uppercase tracking-[0.1em] mb-3","children":"Archive"}],["$","div",null,{"className":"space-y-px","children":[["$","a","2026",{"href":"/#year-2026","className":"block text-[0.8125rem] text-muted-foreground hover:text-foreground hover:bg-accent/50 rounded-lg px-3 py-2 no-underline transition-all","children":"2026"}],["$","a","2025",{"href":"/#year-2025","className":"block text-[0.8125rem] text-muted-foreground hover:text-foreground hover:bg-accent/50 rounded-lg px-3 py-2 no-underline transition-all","children":"2025"}],"$Le","$Lf","$L10"]}]]}],"$L11"]}]}],"$L12","$L13"]}]] 54:I["fa570eec8d8e",[],"PostPageClient",1] 56:I["4601fb863de7",[],"TableOfContents",1] e:["$","a","2024",{"href":"/#year-2024","className":"block text-[0.8125rem] text-muted-foreground hover:text-foreground hover:bg-accent/50 rounded-lg px-3 py-2 no-underline transition-all","children":"2024"}] f:["$","a","2023",{"href":"/#year-2023","className":"block text-[0.8125rem] text-muted-foreground hover:text-foreground hover:bg-accent/50 rounded-lg px-3 py-2 no-underline transition-all","children":"2023"}] 10:["$","a","2022",{"href":"/#year-2022","className":"block text-[0.8125rem] text-muted-foreground hover:text-foreground hover:bg-accent/50 rounded-lg px-3 py-2 no-underline transition-all","children":"2022"}] 11:["$","div",null,{"className":"min-h-0 flex-1 flex flex-col","children":[["$","a",null,{"href":"/tags","className":"text-[0.6875rem] font-medium text-muted-foreground hover:text-foreground uppercase tracking-[0.1em] mb-3 shrink-0 no-underline transition-colors","children":"Tags"}],["$","div",null,{"className":"space-y-px overflow-y-auto sidebar-scroll","children":[["$","a","accessibility-support",{"href":"/tags/accessibility-support","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"accessibilitySupport"}],["$","a","adblock",{"href":"/tags/adblock","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"adblock"}],["$","a","ads",{"href":"/tags/ads","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"ads"}],["$","a","agents-md",{"href":"/tags/agents-md","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"agents-md"}],["$","a","ai",{"href":"/tags/ai","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"ai"}],["$","a","ai-agent",{"href":"/tags/ai-agent","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"ai-agent"}],["$","a","android",{"href":"/tags/android","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"android"}],["$","a","automation",{"href":"/tags/automation","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"automation"}],["$","a","blog",{"href":"/tags/blog","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"blog"}],["$","a","chatgpt",{"href":"/tags/chatgpt","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"chatgpt"}],["$","a","chrome-extension",{"href":"/tags/chrome-extension","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"chrome extension"}],["$","a","circleci",{"href":"/tags/circleci","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"circleci"}],["$","a","claude",{"href":"/tags/claude","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"claude"}],["$","a","claude-code",{"href":"/tags/claude-code","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"claude-code"}],["$","a","cli",{"href":"/tags/cli","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"cli"}],["$","a","codex",{"href":"/tags/codex","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"codex"}],"$L14","$L15","$L16","$L17","$L18","$L19","$L1a","$L1b","$L1c","$L1d","$L1e","$L1f","$L20","$L21","$L22","$L23","$L24","$L25","$L26","$L27","$L28","$L29","$L2a","$L2b","$L2c","$L2d","$L2e","$L2f","$L30","$L31","$L32","$L33","$L34","$L35","$L36","$L37","$L38","$L39","$L3a","$L3b","$L3c","$L3d","$L3e","$L3f","$L40","$L41","$L42","$L43","$L44","$L45","$L46","$L47","$L48","$L49","$L4a","$L4b","$L4c","$L4d","$L4e","$L4f","$L50","$L51","$L52","$L53"]}]]}] 55:T4d11,## tl;dr - circleciのyamlを書く/メンテンするのは辛い - javascirpt objectをjs-yamlでdumpするようにすればtypescirptの補完が受けられる - typescirptの型生成の方法はいくつかありそうだが、現状仕様変更に追従するのが厳しそう - [JSON schemaから生成する方法](https://github.com/mkusaka/ciconfig/blob/main/types/circleci-config-types.ts) - [chat GPTに書かせた例](https://www.typescriptlang.org/play#code/PTAEAsBdIBwZwFwgMYEsBOyA2BTNA6ZAewFtgATI5OYAJnwAZhiA7AM1QHMBXdAQ0ioiLALTocbHOJbIcwAMRw8g4QF5WHHvxUsAUKhaQpbPrNABhDNhzmAkueGbQAb12hQIULQA0Xxr6J0PwBGUABlHBxQSHAogG0AJRxuOANOC0cuAF0ACihYRBQrXAJiMkpqOkZgcRS0kQ0uYABKUArQNkDQPhZQIgA3KX7UHAB3PrYQ0ABrHABPOG7+vlQsPgAjXGiiUFSSGCxUNjnQOaJeUEJivFRmTM58OZIsDtWcX1qlXx7yUBg+fgkHBGdCoABeOF+ACsiOs4Pg3KBBuhUsIEF5QAAfPwMLEhADciM8AEFQCQ+DAJn10OsZvN4eFItFYqA4gB5GmgJKSaSyXL5eBIZjXUqkChUGj0JiBdYiAyQdBEFodLrkYErLDwxEyuAAfnRSWI6HIAB44Aq0r4OesACISAyoHQAPkJ7hJZIpVLK5JYv1mC3wjKiMXiSRSGy2DnYXC5EikOBkUQA4txUGr+dBBUVMCVUIQxRVJdVPvVGpxlZ0gmrIBqte5vT89Qa8IFTebQSxOL4HCQfeQ7RwWI6hCwXUSwKTyZSiJMcAAPPDcSBdf0MiLBlmJZJwCNRKNObnxxOgFNpnAZgpCtA5m758oSqpMEudhr3Cuq9WrOugeeL5co-VYyNNsLU7XwAFEF2QJdAgHB1nVdDwJw9adJhhWlV0DddmXiAApWFFkPXlzzyTNCmFG9RXvSopWAdC4BEc0cHgd8q0-TUEXcejAMNVszVArtQHw217SHBDx1ASdPRnUBRkCaY2CwIhxkwoMcNZAB1eTFOUwi42Ii8swo6wqPFGjqjk9AFKU0YaFaSs2nY79LOs3SeJbY1+I7QStKsnTRjgsSRzHABfXRdE8AVyOvEy8zKMyiyYMteAEEcxH0hNZAUHV0oAR1TcQGORVFRFoYIIonXoZUcwccEWPgUKpHBcCBQxFhiARTnOD0ThSYMdj2A4jhOM4LjLLCmRDVlrVAWxDEVQzopFOKCwfWiZTlealXsroej6ZFhjGKlqvDTgoh+H85z4fZcC1eVjFMKJrUC4dhBcCSpNQy6-xXekJo3UNt13DJo3SIjMuTVN01Iy9s1iu8EsfGpt1LN8drYmsv0477oP-JsgL49tLVASCfvQF7xLdZCpy9Ug+zpAM1Kmrdw02Pd7ljHkIZPKGSKiq9loRwskefThX1B1jHMxjjEQbX18d4zyibAjJex+CngsQ90aZk9CGbXSbN2EvSucTRaBcolbqMSuiCMYowWPRqXa2x7jm2ArzieEjXhDHKnJMamT-kBYEpH1-71LiAAFAFrtDoIwjmQw+Dnc24dzIW1uLFGXzLBRg7jkFGKTms51aJRkB0KkprDVJOxBpwKm4VrIBVDGXcRAugRBBWPJA7yrRpGOQ5BP2kPCcBAlbtU4GQUEYCrmSYlQRYZURGe59QBeR0A5XOEJMKKogMiLfh+Lhdo5LtDS8RTay+Q5fIBjxHyjA6pEYq0rKo-SUfmqDDqt0XYOB8rcxkkxeA3R6qNWXKAdYURfy40hKAAwQD0K+ATBGNIXVuDbFAJ8c6uw0hbD-mqQcr1eimEVHARYJBuBYEEAcKI9FAwADEugkECFEAwlZyRVyUADVktdgb7hjODY8p5ob83Trec+Wcnw5zFnnZoCJ7roBMGYHsfYfa9FcP7T6VIu7x3DkzTcw9C5h0TsnVOMMjIxQznI8yCi6i5zfPIIxRc4AlxTuXZQI5q4slrtg0R6Qm4tzbs7LGndY7dykL3D2e9ux0x+OY2J6Ax4QMQO4MIDs4hZC1mAMIk90DTzqpvbeb0l7gBXqAR+68ynzx0LvASB9wqRRPjI0yF9qhX1SsIdKd85DyEQTBFEeUCrv0-v07+JJegjP-P-FggCpoJmGIqFg4TUGjGqcgcA6lMlUganrUYqwXjwPwdwFg3wsA2WwaNPBBCgF104FseZH5yFVyoUQGhZJ6GMK2Cw0A7CgicPECg9ggQ+H+IEZHYRbMG5iIyhI3madjIONWk45GLilFowRJAOYMAoik1xrBUSFDQCqFADaKgsx0DEtGXiAAsqYapSz6ULOxMy5AbIwjsq6NiDSBhKC2T5egQkR9pFotkRim2vSdADKPPfCotKj4ABUWTUuQLSnGDK+A3N0jgvBfUqU0rDrA9AlycFBEBeBFloA9bkhOOAPggxfkMK3lsTV2rUDkjOgyYkNzanCExkslEFzeioKmjuIEoAlmQBct0X0tS9rekuagZAAgognJiD+O1RAQzoEDOqqIHAUStx9XwM6KD2oshgKCck6ATisBDWHCtVaLo1L6r8ByeqXiP0WOsE4ZC+B-MDMScg5AKF6qDcnABYaTmBvORa3o2a9krPmgSog8p9pSFBBOhMeD-jGrgDsDqrcQwnABOdZYqxgawKUOgYYZgX7cDqpARYbBFQkHUnWitjaZ0tsLfoQwD0zBeqkKK967hlVSHROB9AthfU4DyQU0AxbZLyTSAAfUneISugQTj+h6nAqIXa8FwEJWgY4QDcPKAIxEo5sJAwAE1uqOtAMa5eiwiP3so8NGjb98P-p7fa2E4Kf31oBE24NKxQ1FpZC5bDtGhMnBqds+MVrdgO0WMu7GinOw4cE-+OYzTvKIVWRgYQLd3IJIEr4PeY9tbSUmB4sOqlsLM1ScYqxpdUX2OldbEWijxaaHzjE+OxdrG+Mrv4qpUQgn1xCW0KgzcEytwctWDu7hXMAXdoTOzJMoKjK86PVDRSp6OVno02LkwuM6v-PUqrW8mnoj3q0iVHSpVdPkXcUGKV5W30VUM8kuyAFqpZFy1lCCisLN7Qa+55rLWjWtQRcTDU1STozUYcgGCYCxCBPwF4AA1RlslHRrpZJ0fVJz64UZuBwLbI4sm0CROYKOABVOAvgADs+AACsSYABCXJiSMt8MEBgDAgdUnNIEStOAWPdQzb0aCsOSDgkESduBvVnkSZwCIdYqYGEoKQ38RUwwNvY4sNcOw1IONKE08pFHKRlzfrbQj4DIINFREmwAyDeiPSjaWeiAX7h2embSIhEKiF9OcEM3h4zEvOzmZYGsqzaWbP5YHlp7yjnqbOb+OFkEJiPNmKN5Y7xNjJX+e65i0WIWmjuPN+gSLpdouL1q4ExRCLQkpfCRlpy2McvxK18TUVJWpB64nhVje1XKme5qW89AjXyktZ15L3Qh92mwy61bRGl97j9Zvki++I3vnjaiCNnl9XdrXcWAtnYhPVi-GW4L6vdaiBQmUAyW1uzRO0hqcuvolCOiFT2Vjs91bKsKiIHMZBnV12-Di-31RIH1GPVAFynl-PERl6yaL0Ac5iBqlay0xE0vESy-l3RxtSv96Igs+s6zeWlYFYc6hgxQdncm8NvECPCdLc-NBZHEbZ7c84ncR4pBXcfFgEYt491IEt0gkswk0sIlMsolstncQ9X9tdw9nco9ysSlKtU8atmRE8ZtAgU848WA792ts87FgCZUkY5Vi9BkFAbthU4AK9QBBVfQDUk9ug69DVYFjVGV01qEZxW5eDODGRH0w5aAGBggABOB5Jbc4IIaQg1FhTnUDKITQkVCgoIAXDg3SEXREMXJDU-Mzc-GXTDAzZTRXKwjPdwR-dXQwTXHA4md-D6QOFzb-dzX-Vkf-cIQA2xJaS2TOO3YLcAnLaAsuWAj3HCRAn3ZLaCf3D8aWLUYPDw-uMPQw--McTwQg0pJrCpKqBPRYJPXQWPZrHeJw5XTPNpMAa3RgwLAvPra+fpQbYiBQeiH+fvKfBqYgG5PxeAzJQMXvPZB1FnRybAK9RNGvIISfRPc7MOGDdAXwEbKbXwEw2yAIEFUwb5eTZhWEAAckWAOXmKH1QQameS2DTVblXXUlcI2VQLuyoxGHIEDGNm6DBTIQAV+FQVb3GieBeA4C2AunmLI1QVYChEuTgJXXOwwz8hsjujX25yElhB0XeiT0AlFR0UJEyXRByWYhQ24KvwcPoyI3Y3OTI143u0vTaCM3oxE3QkR1wXY042qW43mHIz42o3W2ZOE12gGMjVrUk3-WbVkykGOOROmCUyFNU0WHUzBVbwOV010ApMVNoN0BeOfwJk8JVm8Kcy+hyx-0EWCJ8xTgSNIJrm92QL91QID0yIRGyJf1yJVmEgKMJCKOKRKJIPgLq3QmqIaVqOEB1Kz2aM6xtzz26SSkL06NEG6IhnYO0lRKPl8lclsl+NIyUF+FgUCF2TfW0BOLhCp3+KHHrgakOHNCpHokTXzNiAwHDWpDVELR4LTINXmIrOQSBPUJnU0EeGeFeAhKTShLzPE1hPhKrieO0LUQxMzP8mxNcBNLrLEwCMER+PETMGdI7nonREXJsmEjJItE4DOlyzQ1BDPKkB0TJNXK-0gKCA3PUktMtxtMDK92xRSJQMMDQMD10DdINI9J8k7NGG9KPmKOIOoICTU1ApDNKLTza0aN0HxUJXCAdgpURGxGcHDQNEuRJMpDCncGwtqViC1XOEgHRHMDIumAooItACIrxBwqUEgG4BgCw3EE4SMBw1NXQGJOBDYqSC4pwHg3osYpIp3EGCwwzSLOJJdRsBZRwDEqwpcHwTfS4WksUubFh3EHMEUuUuItUrVAOFnzg2YiUjmAMqYu6AnSwxoXACw1XHRHHXIDCDCAAAkABpekKykigQGsXZLDFyCjR6Zy6AFlTMkK2QXy1SwlFEFeSALDZcIK+SKKnAdEKOOJBK1VIgSK-4aKjC8S1S9TFgKi4QSdHQPVGKnCy5W6LJKMCqkcKqwqlSnCx+eo9IIqpCjrHPGMyI2VBMvpJMkvIZZdHQ9fMwBIfCjCgXFgOOHU+sZJX0DqxCOAWIG5Ban8VXSzV49w90z2I0gSMedYUwaYTgRUS5cgQCdYIgIgXAHoRCFgIgLDCimAJcJKn1HACiiMpo4+Xq1o-PHpQagbEahQIs8ipccajE6ivAWipceigXf4GIH6o+fANG0AHIQANblAA7BnQuYlAEABKGQAZ4ZAB+hkAB+GQAVYZAByhjJsAE6GZoX6loiIkA5g4G1gobBQdYqGjfeDRDeHKDEneHFaxEEdZGsw9wdwPqdAOaoEYWiWw3GhSycgOWhixCPgWyLDUW8AQCA-dWuATW5AWQGhRy+YLDNMFW9wPWuyvAcQRK0wI2-W-0FWi-SMv6hg5mpg9ozQIvLo0G+QFyfyLg1CvQ0C4SClAW9CFW8EnuQCVhVYHuAkxEF9N+fGPeMkxakDOcSATa4OwCU4ikDvZYLAU4g+cVeg8Is+T2oGjooahVHo+QU8887mswVVK8885cxEWeWIcgehdKgW+sdZC2kcnudEOOhhOJRO9wF2xm6MgGuMrFZ5B3csCAixF3LxaxZup6Ieb-AXYO9Ec4gSU4vEU4+6c8o+7EU4m6u6nAHoc+0AU4hMZuO+h+ww5+zJEu+pEwP5QCHoOYFXZuH+lgOYdOqCsMmg4Ww+ecjfLRFJb-ecIweWUAa0YI5wMKKBswPAx8y6BBp+JB7erB1BzezE9YYI+BhMXB5BnetB9EjfQ85SUhrO8hxYShgh12wAEBU8bKRibybqa6bsbAAs7UAEr-QAewZABABksBvFp2xtpsADGGQAYYZAB1hkACuGCmwAQoZAAbhiJtpsAAh-pmyuto6u72xMuulMxQUYlgVQZMxMQASH-AAkhkACHlQAc0dAB1BkADMGQAeQZAADBkAFUGQAEQZAAgBl+rRsDByEAAlFQAaC9sbABmhjJsAEmGQAawZAAs33cYZp6vdv0cBvjJrpBrYLMdYvYs4vzRwB4q1SkCIYiHyaEqKdEpmsRCmXAfTwaOg14qwzWDn3QE0qF04GutuvupYDoKjP+o9oMayaMdrqsfvkkuKZktiHKfkr0qLIRsRCdsafv0wJiCyTTvyUv1iAaaQtdr0fRRGd6zGZyY5vkEKn-GmcUqIaSB0oUsWdqfcCctWZAZlpwE2pKpRvLtPiOcyZOa4B9uGtyeMosqIbtBMssqedjXmpVvatWdWvWqwE2r1I132sSVWeOtOvOvOF9B6evoesRCepeqXDesSsECBG+ogZnqGYyfnpYN9tyb4FsvspNoWCIZcrcq8p8uhcHHPN-Tag6rJIOdnuGf+YZeBfOf8pZRSqsjSo5fCt2TyseiWctsooReQp+c6VjJ6wlZMcTHziyvNCSueuCvypwCIcyvivNByuVYKvxoF0VHzRVqRvAE2YEmFZpfSb+fpbZsZfOZKqIYaqnSwFVa02YiyQIpAdYEarRA1ZFdpZ9d1b9clfrujriSIbHoTrJSrgF3WH4BkFiHxgP2ECwBMw6rxC2cQjF04CevEDv0rY9e2ansQhrE4GLfMOHzLYbexCrc7a4DrY+Yrd7abalwGdAA4fou4cppptpoEZEfEZp1sFAGkfkeUbUc0Z0cOYC3FZTf1cmfMcsZGrsacbca8b8cCaAA) # CircleCI設定の痛みをTypeScriptで解消するアイディア CircleCIの設定ファイルcircle.ymlをメンテナンスすることは、往々にして難しいです。YAMLの書式に苦戦したり、JSON Schemaによる補完が十分ではない場合があります。この記事では、TypeScriptを使ってCircleCI設定の問題を解決する方法を紹介します。 ## YAMLの悩み YAMLを直接書くことは煩雑で、アンカーを使って回避する方法も癖があります。また、JSON Schemaによる補完はある程度効果があるものの、十分強力ではないため、困難な状況がしばしば発生します。 ## circleci-config-sdk-tsの限界 [circleci-config-sdk-ts](https://github.com/CircleCI-Public/circleci-config-sdk-ts)は、CircleCIの設定を扱うための公式のライブラリですが、宣言的ではないため、思ったように書くことができません。 ## TypeScriptで解決 TypeScriptを使って以下の手順で問題を解決できそうです。 1. TypeScript用の型を作成する 1. 型に合致するJavaScriptオブジェクトを作成する 1. js-yamlでJavaScriptオブジェクトをYAMLに変換する 1. TypeScript用の型を作成する方法 1. 型を作成するには、以下の方法が考えられます。 以下で実際に型を生成するアイディアをまとめます ## JSON Schemaから生成する方法 JSON Schemaからtypescirptの型を生成するパッケージがあるため、それもとに生成することができます。 - https://github.com/mkusaka/ciconfig/blob/main/types/circleci-config-types.ts ## Chat GPTにconfig referenceから生成させる方法 [config reference](https://circleci.com/docs/configuration-reference/)を読ませて型を生成させる事もできます。 実際に生成させてみましたが、ある程度使えそうなものを出すことができました [出力例](https://www.typescriptlang.org/play#code/PTAEAsBdIBwZwFwgMYEsBOyA2BTNA6ZAewFtgATI5OYAJnwAZhiA7AM1QHMBXdAQ0ioiLALTocbHOJbIcwAMRw8g4QF5WHHvxUsAUKhaQpbPrNABhDNhzmAkueGbQAb12hQIULQA0Xxr6J0PwBGUABlHBxQSHAogG0AJRxuOANOC0cuAF0ACihYRBQrXAJiMkpqOkZgcRS0kQ0uYABKUArQNkDQPhZQIgA3KX7UHAB3PrYQ0ABrHABPOG7+vlQsPgAjXGiiUFSSGCxUNjnQOaJeUEJivFRmTM58OZIsDtWcX1qlXx7yUBg+fgkHBGdCoABeOF+ACsiOs4Pg3KBBuhUsIEF5QAAfPwMLEhADciM8AEFQCQ+DAJn10OsZvN4eFItFYqA4gB5GmgJKSaSyXL5eBIZjXUqkChUGj0JiBdYiAyQdBEFodLrkYErLDwxEyuAAfnRSWI6HIAB44Aq0r4OesACISAyoHQAPkJ7hJZIpVLK5JYv1mC3wjKiMXiSRSGy2DnYXC5EikOBkUQA4txUGr+dBBUVMCVUIQxRVJdVPvVGpxlZ0gmrIBqte5vT89Qa8IFTebQSxOL4HCQfeQ7RwWI6hCwXUSwKTyZSiJMcAAPPDcSBdf0MiLBlmJZJwCNRKNObnxxOgFNpnAZgpCtA5m758oSqpMEudhr3Cuq9WrOugeeL5co-VYyNNsLU7XwAFEF2QJdAgHB1nVdDwJw9adJhhWlV0DddmXiAApWFFkPXlzzyTNCmFG9RXvSopWAdC4BEc0cHgd8q0-TUEXcejAMNVszVArtQHw217SHBDx1ASdPRnUBRkCaY2CwIhxkwoMcNZAB1eTFOUwi42Ii8swo6wqPFGjqjk9AFKU0YaFaSs2nY79LOs3SeJbY1+I7QStKsnTRjgsSRzHABfXRdE8AVyOvEy8zKMyiyYMteAEEcxH0hNZAUHV0oAR1TcQGORVFRFoYIIonXoZUcwccEWPgUKpHBcCBQxFhiARTnOD0ThSYMdj2A4jhOM4LjLLCmRDVlrVAWxDEVQzopFOKCwfWiZTlealXsroej6ZFhjGKlqvDTgoh+H85z4fZcC1eVjFMKJrUC4dhBcCSpNQy6-xXekJo3UNt13DJo3SIjMuTVN01Iy9s1iu8EsfGpt1LN8drYmsv0477oP-JsgL49tLVASCfvQF7xLdZCpy9Ug+zpAM1Kmrdw02Pd7ljHkIZPKGSKiq9loRwskefThX1B1jHMxjjEQbX18d4zyibAjJex+CngsQ90aZk9CGbXSbN2EvSucTRaBcolbqMSuiCMYowWPRqXa2x7jm2ArzieEjXhDHKnJMamT-kBYEpH1-71LiAAFAFrtDoIwjmQw+Dnc24dzIW1uLFGXzLBRg7jkFGKTms51aJRkB0KkprDVJOxBpwKm4VrIBVDGXcRAugRBBWPJA7yrRpGOQ5BP2kPCcBAlbtU4GQUEYCrmSYlQRYZURGe59QBeR0A5XOEJMKKogMiLfh+Lhdo5LtDS8RTay+Q5fIBjxHyjA6pEYq0rKo-SUfmqDDqt0XYOB8rcxkkxeA3R6qNWXKAdYURfy40hKAAwQD0K+ATBGNIXVuDbFAJ8c6uw0hbD-mqQcr1eimEVHARYJBuBYEEAcKI9FAwADEugkECFEAwlZyRVyUADVktdgb7hjODY8p5ob83Trec+Wcnw5zFnnZoCJ7roBMGYHsfYfa9FcP7T6VIu7x3DkzTcw9C5h0TsnVOMMjIxQznI8yCi6i5zfPIIxRc4AlxTuXZQI5q4slrtg0R6Qm4tzbs7LGndY7dykL3D2e9ux0x+OY2J6Ax4QMQO4MIDs4hZC1mAMIk90DTzqpvbeb0l7gBXqAR+68ynzx0LvASB9wqRRPjI0yF9qhX1SsIdKd85DyEQTBFEeUCrv0-v07+JJegjP-P-FggCpoJmGIqFg4TUGjGqcgcA6lMlUganrUYqwXjwPwdwFg3wsA2WwaNPBBCgF104FseZH5yFVyoUQGhZJ6GMK2Cw0A7CgicPECg9ggQ+H+IEZHYRbMG5iIyhI3madjIONWk45GLilFowRJAOYMAoik1xrBUSFDQCqFADaKgsx0DEtGXiAAsqYapSz6ULOxMy5AbIwjsq6NiDSBhKC2T5egQkR9pFotkRim2vSdADKPPfCotKj4ABUWTUuQLSnGDK+A3N0jgvBfUqU0rDrA9AlycFBEBeBFloA9bkhOOAPggxfkMK3lsTV2rUDkjOgyYkNzanCExkslEFzeioKmjuIEoAlmQBct0X0tS9rekuagZAAgognJiD+O1RAQzoEDOqqIHAUStx9XwM6KD2oshgKCck6ATisBDWHCtVaLo1L6r8ByeqXiP0WOsE4ZC+B-MDMScg5AKF6qDcnABYaTmBvORa3o2a9krPmgSog8p9pSFBBOhMeD-jGrgDsDqrcQwnABOdZYqxgawKUOgYYZgX7cDqpARYbBFQkHUnWitjaZ0tsLfoQwD0zBeqkKK967hlVSHROB9AthfU4DyQU0AxbZLyTSAAfUneISugQTj+h6nAqIXa8FwEJWgY4QDcPKAIxEo5sJAwAE1uqOtAMa5eiwiP3so8NGjb98P-p7fa2E4Kf31oBE24NKxQ1FpZC5bDtGhMnBqds+MVrdgO0WMu7GinOw4cE-+OYzTvKIVWRgYQLd3IJIEr4PeY9tbSUmB4sOqlsLM1ScYqxpdUX2OldbEWijxaaHzjE+OxdrG+Mrv4qpUQgn1xCW0KgzcEytwctWDu7hXMAXdoTOzJMoKjK86PVDRSp6OVno02LkwuM6v-PUqrW8mnoj3q0iVHSpVdPkXcUGKV5W30VUM8kuyAFqpZFy1lCCisLN7Qa+55rLWjWtQRcTDU1STozUYcgGCYCxCBPwF4AA1RlslHRrpZJ0fVJz64UZuBwLbI4sm0CROYKOABVOAvgADs+AACsSYABCXJiSMt8MEBgDAgdUnNIEStOAWPdQzb0aCsOSDgkESduBvVnkSZwCIdYqYGEoKQ38RUwwNvY4sNcOw1IONKE08pFHKRlzfrbQj4DIINFREmwAyDeiPSjaWeiAX7h2embSIhEKiF9OcEM3h4zEvOzmZYGsqzaWbP5YHlp7yjnqbOb+OFkEJiPNmKN5Y7xNjJX+e65i0WIWmjuPN+gSLpdouL1q4ExRCLQkpfCRlpy2McvxK18TUVJWpB64nhVje1XKme5qW89AjXyktZ15L3Qh92mwy61bRGl97j9Zvki++I3vnjaiCNnl9XdrXcWAtnYhPVi-GW4L6vdaiBQmUAyW1uzRO0hqcuvolCOiFT2Vjs91bKsKiIHMZBnV12-Di-31RIH1GPVAFynl-PERl6yaL0Ac5iBqlay0xE0vESy-l3RxtSv96Igs+s6zeWlYFYc6hgxQdncm8NvECPCdLc-NBZHEbZ7c84ncR4pBXcfFgEYt491IEt0gkswk0sIlMsolstncQ9X9tdw9nco9ysSlKtU8atmRE8ZtAgU848WA792ts87FgCZUkY5Vi9BkFAbthU4AK9QBBVfQDUk9ug69DVYFjVGV01qEZxW5eDODGRH0w5aAGBggABOB5Jbc4IIaQg1FhTnUDKITQkVCgoIAXDg3SEXREMXJDU-Mzc-GXTDAzZTRXKwjPdwR-dXQwTXHA4md-D6QOFzb-dzX-Vkf-cIQA2xJaS2TOO3YLcAnLaAsuWAj3HCRAn3ZLaCf3D8aWLUYPDw-uMPQw--McTwQg0pJrCpKqBPRYJPXQWPZrHeJw5XTPNpMAa3RgwLAvPra+fpQbYiBQeiH+fvKfBqYgG5PxeAzJQMXvPZB1FnRybAK9RNGvIISfRPc7MOGDdAXwEbKbXwEw2yAIEFUwb5eTZhWEAAckWAOXmKH1QQameS2DTVblXXUlcI2VQLuyoxGHIEDGNm6DBTIQAV+FQVb3GieBeA4C2AunmLI1QVYChEuTgJXXOwwz8hsjujX25yElhB0XeiT0AlFR0UJEyXRByWYhQ24KvwcPoyI3Y3OTI143u0vTaCM3oxE3QkR1wXY042qW43mHIz42o3W2ZOE12gGMjVrUk3-WbVkykGOOROmCUyFNU0WHUzBVbwOV010ApMVNoN0BeOfwJk8JVm8Kcy+hyx-0EWCJ8xTgSNIJrm92QL91QID0yIRGyJf1yJVmEgKMJCKOKRKJIPgLq3QmqIaVqOEB1Kz2aM6xtzz26SSkL06NEG6IhnYO0lRKPl8lclsl+NIyUF+FgUCF2TfW0BOLhCp3+KHHrgakOHNCpHokTXzNiAwHDWpDVELR4LTINXmIrOQSBPUJnU0EeGeFeAhKTShLzPE1hPhKrieO0LUQxMzP8mxNcBNLrLEwCMER+PETMGdI7nonREXJsmEjJItE4DOlyzQ1BDPKkB0TJNXK-0gKCA3PUktMtxtMDK92xRSJQMMDQMD10DdINI9J8k7NGG9KPmKOIOoICTU1ApDNKLTza0aN0HxUJXCAdgpURGxGcHDQNEuRJMpDCncGwtqViC1XOEgHRHMDIumAooItACIrxBwqUEgG4BgCw3EE4SMBw1NXQGJOBDYqSC4pwHg3osYpIp3EGCwwzSLOJJdRsBZRwDEqwpcHwTfS4WksUubFh3EHMEUuUuItUrVAOFnzg2YiUjmAMqYu6AnSwxoXACw1XHRHHXIDCDCAAAkABpekKykigQGsXZLDFyCjR6Zy6AFlTMkK2QXy1SwlFEFeSALDZcIK+SKKnAdEKOOJBK1VIgSK-4aKjC8S1S9TFgKi4QSdHQPVGKnCy5W6LJKMCqkcKqwqlSnCx+eo9IIqpCjrHPGMyI2VBMvpJMkvIZZdHQ9fMwBIfCjCgXFgOOHU+sZJX0DqxCOAWIG5Ban8VXSzV49w90z2I0gSMedYUwaYTgRUS5cgQCdYIgIgXAHoRCFgIgLDCimAJcJKn1HACiiMpo4+Xq1o-PHpQagbEahQIs8ipccajE6ivAWipceigXf4GIH6o+fANG0AHIQANblAA7BnQuYlAEABKGQAZ4ZAB+hkAB+GQAVYZAByhjJsAE6GZoX6loiIkA5g4G1gobBQdYqGjfeDRDeHKDEneHFaxEEdZGsw9wdwPqdAOaoEYWiWw3GhSycgOWhixCPgWyLDUW8AQCA-dWuATW5AWQGhRy+YLDNMFW9wPWuyvAcQRK0wI2-W-0FWi-SMv6hg5mpg9ozQIvLo0G+QFyfyLg1CvQ0C4SClAW9CFW8EnuQCVhVYHuAkxEF9N+fGPeMkxakDOcSATa4OwCU4ikDvZYLAU4g+cVeg8Is+T2oGjooahVHo+QU8887mswVVK8885cxEWeWIcgehdKgW+sdZC2kcnudEOOhhOJRO9wF2xm6MgGuMrFZ5B3csCAixF3LxaxZup6Ieb-AXYO9Ec4gSU4vEU4+6c8o+7EU4m6u6nAHoc+0AU4hMZuO+h+ww5+zJEu+pEwP5QCHoOYFXZuH+lgOYdOqCsMmg4Ww+ecjfLRFJb-ecIweWUAa0YI5wMKKBswPAx8y6BBp+JB7erB1BzezE9YYI+BhMXB5BnetB9EjfQ85SUhrO8hxYShgh12wAEBU8bKRibybqa6bsbAAs7UAEr-QAewZABABksBvFp2xtpsADGGQAYYZAB1hkACuGCmwAQoZAAbhiJtpsAAh-pmyuto6u72xMuulMxQUYlgVQZMxMQASH-AAkhkACHlQAc0dAB1BkADMGQAeQZAADBkAFUGQAEQZAAgBl+rRsDByEAAlFQAaC9sbABmhjJsAEmGQAawZAAs33cYZp6vdv0cBvjJrpBrYLMdYvYs4vzRwB4q1SkCIYiHyaEqKdEpmsRCmXAfTwaOg14qwzWDn3QE0qF04GutuvupYDoKjP+o9oMayaMdrqsfvkkuKZktiHKfkr0qLIRsRCdsafv0wJiCyTTvyUv1iAaaQtdr0fRRGd6zGZyY5vkEKn-GmcUqIaSB0oUsWdqfcCctWZAZlpwE2pKpRvLtPiOcyZOa4B9uGtyeMosqIbtBMssqedjXmpVvatWdWvWqwE2r1I132sSVWeOtOvOvOF9B6evoesRCepeqXDesSsECBG+ogZnqGYyfnpYN9tyb4FsvspNoWCIZcrcq8p8uhcHHPN-Tag6rJIOdnuGf+YZeBfOf8pZRSqsjSo5fCt2TyseiWctsooReQp+c6VjJ6wlZMcTHziyvNCSueuCvypwCIcyvivNByuVYKvxoF0VHzRVqRvAE2YEmFZpfSb+fpbZsZfOZKqIYaqnSwFVa02YiyQIpAdYEarRA1ZFdpZ9d1b9clfrujriSIbHoTrJSrgF3WH4BkFiHxgP2ECwBMw6rxC2cQjF04CevEDv0rY9e2ansQhrE4GLfMOHzLYbexCrc7a4DrY+Yrd7abalwGdAA4fou4cppptpoEZEfEZp1sFAGkfkeUbUc0Z0cOYC3FZTf1cmfMcsZGrsacbca8b8cCaAA) ## 実際の使い方 実際にconfig.ymlを作る場合は以下のように型を使えばいい感じに型チェックができます ```ts import { type JSONSchemaForCircleCIConfigurationFiles } from "./types/circleci-config-types"; import { dump } from "js-yaml"; import fs from "fs"; const config: JSONSchemaForCircleCIConfigurationFiles = { version: 2.1, jobs: { build: { docker: [ { image: "circleci/:", auth: { username: "mydockerhub-user", password: "$DOCKERHUB_PASSWORD", }, }, ], steps: [ "checkout", "setup_remote_docker", { setup_remote_docker: { version: "20.10.18", }, }, { run: 'echo "this is the build job"', }, ], }, test: { docker: [ { image: "circleci/:", auth: { username: "mydockerhub-user", password: "$DOCKERHUB_PASSWORD", }, }, ], steps: [ "add_ssh_keys", { run: 'echo "this is the test job"', }, ], }, }, workflows: { version: 2, build_and_test: { jobs: ["build", "test"], }, }, }; fs.writeFileSync("output.yaml", dump(config)); ``` ## さらなる改善 今後の展望として、以下の改善が考えられます。 - npm packageとして公開 - Zodスキーマによる検証を追加 - TypeScriptでは、min/max/regex/defaultなどの検証ができないため、Zodスキーマに変換したい(現状では適切な変換ができていない) - デフォルトの設定を自動生成させるようにしたい - GitHub Workflow/GitHub Actionsでも同様のことができるはず - [actionlint](https://github.com/rhysd/actionlint)のように、GitHub Actionsの独自構文内の諸々にも対応できると良いでしょう - 以上で、CircleCI設定ファイルの書き方やメンテナンスが、TypeScriptを利用することでより容易になります。 今回紹介した手法を活用することで、CircleCIの設定ファイルの管理がスムーズになり、エラーや誤りを減らすことが期待できます。 ## 補足 以上の文章は適当にtl;drを作った上でchat GPTにだいたい書いてもらいました。いい感じの文章が出てきたので、ソレに適切にリファレンスを追加するだけで完了したので楽でした。 だいたいのプロンプト ``` https://mkusaka.com/ の文調を参考に、以下の概要を満たすようなブログを書きなさい # tl;dr ## circleciのyamlを書く/メンテンするのは辛い - yamlを生で書くのは辛い - 回避するためのアンカーも癖がある - json schemaによる補完もある程度効くが、そこまで強くないので困る事が多い ## [circleci-config-sdk-ts](https://github.com/CircleCI-Public/circleci-config-sdk-ts)があるが、宣言的ではないので思ったようにはかけない ## これらはtypescriptで補完させながら対応することで解消できそう 1. 何らかの方法でtypescript用の型を作成 2. 型に合致するようにjs objectを作る 3. js-yamlでjs objectをyamlに変換 ### typescript用の型を作成する方法 - json schemaから生成する方法 - json schemaから型を生成し、それにマッチするようにjavascriptのobjectを書く - chat gptにconfig referenceから生成させる方法 - config referenceを読ませることで型を生成させる だいたい生成できた結果を載せる - https://github.com/mkusaka/ciconfig - https://www.typescriptlang.org/ ## 次の1手 - zod schemaによる検証を加えたい - typescriptだとmin/max/regex/defaultなどの検証ができないので、zod schemaに翻訳させたい(現状は適切な変換ができていない) - デフォルトのものは自動で生成させるようにしたい - 同じことをgithub workflow/github actionsでもできるはず - actionlintのようにgithub actionsの独自構文内の諸々にも対応できると良さそう ```12:["$","main",null,{"className":"flex-1 min-w-0 border-l border-border lg:border-l-0","children":["$","div",null,{"className":"max-w-[680px] mx-auto px-6 sm:px-8 pt-12 pb-32 relative","data-toc-content":true,"children":[["$","div",null,{"className":"absolute top-[-80px] left-1/2 -translate-x-1/2 w-[500px] h-[300px] bg-[radial-gradient(ellipse,rgba(0,0,0,0.02)_0%,transparent_70%)] dark:bg-[radial-gradient(ellipse,rgba(255,255,255,0.015)_0%,transparent_70%)] pointer-events-none"}],["$","div",null,{"className":"flex items-center gap-3 text-sm text-muted-foreground mb-8","children":[["$","time",null,{"children":"2023-04-03"}],[["$","span",null,{"className":"text-muted-foreground/50","children":"·"}],["$","span",null,{"children":"ci"}]]]}],["$","h1",null,{"className":"text-[2.5rem] sm:text-5xl font-bold leading-[1.1] tracking-[-0.03em] mb-6 text-foreground","children":"CircleCI設定の痛みをTypeScriptで解消するアイディア"}],["$","p",null,{"className":"text-lg text-muted-foreground leading-relaxed mb-6 max-w-[560px]","children":"CircleCI設定ファイルの編集やメンテナンスの悩みを解決するため、TypeScriptを活用する方法を紹介。"}],["$","div",null,{"className":"flex gap-2 flex-wrap mb-8","children":[["$","a","typescript",{"href":"/tags/typescript","className":"text-[0.75rem] px-2.5 py-1 rounded-md bg-secondary text-muted-foreground hover:text-foreground hover:bg-accent no-underline transition-colors","children":"typescript"}],["$","a","circleci",{"href":"/tags/circleci","className":"text-[0.75rem] px-2.5 py-1 rounded-md bg-secondary text-muted-foreground hover:text-foreground hover:bg-accent no-underline transition-colors","children":"circleci"}],["$","a","yaml",{"href":"/tags/yaml","className":"text-[0.75rem] px-2.5 py-1 rounded-md bg-secondary text-muted-foreground hover:text-foreground hover:bg-accent no-underline transition-colors","children":"yaml"}]]}],["$","$L54",null,{"meta":{"slug":"ciconfig","path":"2023/04/03/ciconfig","title":"CircleCI設定の痛みをTypeScriptで解消するアイディア","date":"2023-04-03","description":"CircleCI設定ファイルの編集やメンテナンスの悩みを解決するため、TypeScriptを活用する方法を紹介。","categories":["ci"],"tags":["typescript","circleci","yaml"]},"rawContent":"$55","initialLanguage":"ja"}]]}]}] 13:["$","$L56",null,{}] 14:["$","a","coding-agent",{"href":"/tags/coding-agent","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"coding-agent"}] 15:["$","a","configuration",{"href":"/tags/configuration","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"configuration"}] 16:["$","a","debug",{"href":"/tags/debug","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"debug"}] 17:["$","a","developer-mode",{"href":"/tags/developer-mode","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"developer-mode"}] 18:["$","a","devin",{"href":"/tags/devin","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"devin"}] 19:["$","a","dns",{"href":"/tags/dns","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"dns"}] 1a:["$","a","docker",{"href":"/tags/docker","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"docker"}] 1b:["$","a","earth-mark",{"href":"/tags/earth-mark","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"earth-mark"}] 1c:["$","a","errorlens",{"href":"/tags/errorlens","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"errorlens"}] 1d:["$","a","gh",{"href":"/tags/gh","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"gh"}] 1e:["$","a","ghcr",{"href":"/tags/ghcr","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"ghcr"}] 1f:["$","a","github",{"href":"/tags/github","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"github"}] 20:["$","a","github-apps",{"href":"/tags/github-apps","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"github apps"}] 21:["$","a","github-actions",{"href":"/tags/github-actions","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"github-actions"}] 22:["$","a","inspectionlens",{"href":"/tags/inspectionlens","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"inspectionlens"}] 23:["$","a","intellij",{"href":"/tags/intellij","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"intellij"}] 24:["$","a","i-term2",{"href":"/tags/i-term2","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"iTerm2"}] 25:["$","a","jetbrains",{"href":"/tags/jetbrains","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"jetbrains"}] 26:["$","a","keybinding",{"href":"/tags/keybinding","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"keybinding"}] 27:["$","a","keybindings",{"href":"/tags/keybindings","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"keybindings"}] 28:["$","a","keybinds",{"href":"/tags/keybinds","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"keybinds"}] 29:["$","a","keyboard",{"href":"/tags/keyboard","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"keyboard"}] 2a:["$","a","llm",{"href":"/tags/llm","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"llm"}] 2b:["$","a","macos",{"href":"/tags/macos","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"macos"}] 2c:["$","a","mcp",{"href":"/tags/mcp","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"mcp"}] 2d:["$","a","memo",{"href":"/tags/memo","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"memo"}] 2e:["$","a","memory",{"href":"/tags/memory","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"memory"}] 2f:["$","a","move",{"href":"/tags/move","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"move"}] 30:["$","a","navigation-bar",{"href":"/tags/navigation-bar","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"Navigation Bar"}] 31:["$","a","next-js",{"href":"/tags/next-js","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"next.js"}] 32:["$","a","next-dns",{"href":"/tags/next-dns","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"NextDNS"}] 33:["$","a","node",{"href":"/tags/node","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"node"}] 34:["$","a","node-js",{"href":"/tags/node-js","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"node.js"}] 35:["$","a","oh-my-opencode",{"href":"/tags/oh-my-opencode","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"oh-my-opencode"}] 36:["$","a","oh-my-pi",{"href":"/tags/oh-my-pi","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"oh-my-pi"}] 37:["$","a","openai",{"href":"/tags/openai","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"openai"}] 38:["$","a","openapi-generator",{"href":"/tags/openapi-generator","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"openapi-generator"}] 39:["$","a","opencode",{"href":"/tags/opencode","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"opencode"}] 3a:["$","a","opencode-zen",{"href":"/tags/opencode-zen","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"opencode-zen"}] 3b:["$","a","permission",{"href":"/tags/permission","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"permission"}] 3c:["$","a","plugin",{"href":"/tags/plugin","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"plugin"}] 3d:["$","a","pycharm",{"href":"/tags/pycharm","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"pycharm"}] 3e:["$","a","python",{"href":"/tags/python","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"python"}] 3f:["$","a","react",{"href":"/tags/react","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"react"}] 40:["$","a","rust",{"href":"/tags/rust","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"Rust"}] 41:["$","a","rust-rover",{"href":"/tags/rust-rover","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"RustRover"}] 42:["$","a","security",{"href":"/tags/security","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"security"}] 43:["$","a","server-actions",{"href":"/tags/server-actions","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"server actions"}] 44:["$","a","slideshare",{"href":"/tags/slideshare","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"slideshare"}] 45:["$","a","sound",{"href":"/tags/sound","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"sound"}] 46:["$","a","tab",{"href":"/tags/tab","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"tab"}] 47:["$","a","tab-shifter",{"href":"/tags/tab-shifter","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"tab shifter"}] 48:["$","a","terminal",{"href":"/tags/terminal","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"terminal"}] 49:["$","a","tips",{"href":"/tags/tips","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"tips"}] 4a:["$","a","tools",{"href":"/tags/tools","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"tools"}] 4b:["$","a","tsx",{"href":"/tags/tsx","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"tsx"}] 4c:["$","a","tui",{"href":"/tags/tui","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"tui"}] 4d:["$","a","tunnel",{"href":"/tags/tunnel","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"tunnel"}] 4e:["$","a","typescript",{"href":"/tags/typescript","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"typescript"}] 4f:["$","a","typescript-fetch",{"href":"/tags/typescript-fetch","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"typescript-fetch"}] 50:["$","a","vscode",{"href":"/tags/vscode","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"vscode"}] 51:["$","a","vs-code",{"href":"/tags/vs-code","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"VSCode"}] 52:["$","a","yaml",{"href":"/tags/yaml","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"yaml"}] 53:["$","a","小ネタ",{"href":"/tags/小ネタ","className":"block text-[0.8125rem] rounded-lg px-3 py-2 no-underline transition-all text-muted-foreground hover:text-foreground hover:bg-accent/50","children":"小ネタ"}]